Commit ba2bae90 by Lizh

修复自测过程中发现的问题

parent d043ba58
package com.jomalls.custom.app.dto.system; package com.jomalls.custom.app.dto.system;
import com.fasterxml.jackson.annotation.JsonSetter;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
...@@ -31,11 +32,11 @@ public class SysRoleSaveDTO implements Serializable { ...@@ -31,11 +32,11 @@ public class SysRoleSaveDTO implements Serializable {
@Schema(description = "数据权限范围") @Schema(description = "数据权限范围")
private String dataScope; private String dataScope;
@Schema(description = "菜单树选择项是否关联显示") @Schema(description = "菜单树选择项是否关联显示(0=关联 1=不关联)")
private Boolean menuCheckStrictly; private Integer menuCheckStrictly;
@Schema(description = "部门树选择项是否关联显示") @Schema(description = "部门树选择项是否关联显示(0=关联 1=不关联)")
private Boolean deptCheckStrictly; private Integer deptCheckStrictly;
@Schema(description = "菜单ID列表") @Schema(description = "菜单ID列表")
private Long[] menuIds; private Long[] menuIds;
...@@ -45,4 +46,28 @@ public class SysRoleSaveDTO implements Serializable { ...@@ -45,4 +46,28 @@ public class SysRoleSaveDTO implements Serializable {
@Schema(description = "备注") @Schema(description = "备注")
private String remark; private String remark;
/**
* 兼容前端传入 Boolean 类型的 menuCheckStrictly
*/
@JsonSetter("menuCheckStrictly")
public void setMenuCheckStrictlyJson(Object value) {
if (value instanceof Boolean) {
this.menuCheckStrictly = (Boolean) value ? 1 : 0;
} else if (value instanceof Number) {
this.menuCheckStrictly = ((Number) value).intValue();
}
}
/**
* 兼容前端传入 Boolean 类型的 deptCheckStrictly
*/
@JsonSetter("deptCheckStrictly")
public void setDeptCheckStrictlyJson(Object value) {
if (value instanceof Boolean) {
this.deptCheckStrictly = (Boolean) value ? 1 : 0;
} else if (value instanceof Number) {
this.deptCheckStrictly = ((Number) value).intValue();
}
}
} }
package com.jomalls.custom.app.dto.system; package com.jomalls.custom.app.dto.system;
import com.fasterxml.jackson.annotation.JsonSetter;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
...@@ -34,11 +35,11 @@ public class SysRoleUpdateDTO implements Serializable { ...@@ -34,11 +35,11 @@ public class SysRoleUpdateDTO implements Serializable {
@Schema(description = "数据权限范围") @Schema(description = "数据权限范围")
private String dataScope; private String dataScope;
@Schema(description = "菜单树选择项是否关联显示") @Schema(description = "菜单树选择项是否关联显示(0=关联 1=不关联)")
private Boolean menuCheckStrictly; private Integer menuCheckStrictly;
@Schema(description = "部门树选择项是否关联显示") @Schema(description = "部门树选择项是否关联显示(0=关联 1=不关联)")
private Boolean deptCheckStrictly; private Integer deptCheckStrictly;
@Schema(description = "菜单ID列表") @Schema(description = "菜单ID列表")
private Long[] menuIds; private Long[] menuIds;
...@@ -48,4 +49,28 @@ public class SysRoleUpdateDTO implements Serializable { ...@@ -48,4 +49,28 @@ public class SysRoleUpdateDTO implements Serializable {
@Schema(description = "备注") @Schema(description = "备注")
private String remark; private String remark;
/**
* 兼容前端传入 Boolean 类型的 menuCheckStrictly
*/
@JsonSetter("menuCheckStrictly")
public void setMenuCheckStrictlyJson(Object value) {
if (value instanceof Boolean) {
this.menuCheckStrictly = (Boolean) value ? 1 : 0;
} else if (value instanceof Number) {
this.menuCheckStrictly = ((Number) value).intValue();
}
}
/**
* 兼容前端传入 Boolean 类型的 deptCheckStrictly
*/
@JsonSetter("deptCheckStrictly")
public void setDeptCheckStrictlyJson(Object value) {
if (value instanceof Boolean) {
this.deptCheckStrictly = (Boolean) value ? 1 : 0;
} else if (value instanceof Number) {
this.deptCheckStrictly = ((Number) value).intValue();
}
}
} }
...@@ -4,6 +4,7 @@ import com.jomalls.custom.app.dto.system.SysMenuSaveDTO; ...@@ -4,6 +4,7 @@ import com.jomalls.custom.app.dto.system.SysMenuSaveDTO;
import com.jomalls.custom.app.dto.system.SysMenuUpdateDTO; import com.jomalls.custom.app.dto.system.SysMenuUpdateDTO;
import com.jomalls.custom.app.vo.system.SysMenuVO; import com.jomalls.custom.app.vo.system.SysMenuVO;
import com.jomalls.custom.app.vo.system.TreeSelectVO; import com.jomalls.custom.app.vo.system.TreeSelectVO;
import com.jomalls.custom.utils.AjaxResult;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
...@@ -44,7 +45,7 @@ public interface SysMenuService { ...@@ -44,7 +45,7 @@ public interface SysMenuService {
* @param roleId 角色ID * @param roleId 角色ID
* @return 包含 treeselect 和 checkedKeys 的Map * @return 包含 treeselect 和 checkedKeys 的Map
*/ */
Map<String, Object> roleMenuTreeselect(Long roleId); AjaxResult roleMenuTreeselect(Long roleId);
/** /**
* 新增菜单 * 新增菜单
......
...@@ -15,6 +15,7 @@ import com.jomalls.custom.domain.service.system.SysMenuDomainService; ...@@ -15,6 +15,7 @@ import com.jomalls.custom.domain.service.system.SysMenuDomainService;
import com.jomalls.custom.domain.service.system.SysRoleDomainService; import com.jomalls.custom.domain.service.system.SysRoleDomainService;
import com.jomalls.custom.domain.service.system.SysRoleMenuDomainService; import com.jomalls.custom.domain.service.system.SysRoleMenuDomainService;
import com.jomalls.custom.security.SecurityUtils; import com.jomalls.custom.security.SecurityUtils;
import com.jomalls.custom.utils.AjaxResult;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
...@@ -67,7 +68,7 @@ public class SysMenuServiceImpl implements SysMenuService { ...@@ -67,7 +68,7 @@ public class SysMenuServiceImpl implements SysMenuService {
} }
@Override @Override
public Map<String, Object> roleMenuTreeselect(Long roleId) { public AjaxResult roleMenuTreeselect(Long roleId) {
List<TreeSelectVO> menus = treeselect(); List<TreeSelectVO> menus = treeselect();
// 查询角色已关联的菜单ID // 查询角色已关联的菜单ID
List<SysRoleMenuEntity> roleMenus = sysRoleMenuDomainService.list( List<SysRoleMenuEntity> roleMenus = sysRoleMenuDomainService.list(
...@@ -86,10 +87,11 @@ public class SysMenuServiceImpl implements SysMenuService { ...@@ -86,10 +87,11 @@ public class SysMenuServiceImpl implements SysMenuService {
} }
checkedKeys = new ArrayList<>(allCheckedKeys); checkedKeys = new ArrayList<>(allCheckedKeys);
} }
AjaxResult ajax = AjaxResult.success();
Map<String, Object> result = new HashMap<>(); Map<String, Object> result = new HashMap<>();
result.put("menus", menus); ajax.put("menus", menus);
result.put("checkedKeys", checkedKeys); ajax.put("checkedKeys", checkedKeys);
return result; return ajax;
} }
/** /**
......
...@@ -201,7 +201,7 @@ public class SysUserServiceImpl implements SysUserService { ...@@ -201,7 +201,7 @@ public class SysUserServiceImpl implements SysUserService {
.eq(SysUserEntity::getPhonenumber, user.getPhonenumber()) .eq(SysUserEntity::getPhonenumber, user.getPhonenumber())
.ne(SysUserEntity::getUserId, existing.getUserId())); .ne(SysUserEntity::getUserId, existing.getUserId()));
if (phoneCount > 0) { if (phoneCount > 0) {
throw new ServiceException("修改用户'" + user.getUserName() + "'失败,手机号码已存在"); throw new ServiceException("修改用户'" + existing.getUserName() + "'失败,手机号码已存在");
} }
} }
// 邮箱唯一性校验(排除自身) // 邮箱唯一性校验(排除自身)
...@@ -211,7 +211,7 @@ public class SysUserServiceImpl implements SysUserService { ...@@ -211,7 +211,7 @@ public class SysUserServiceImpl implements SysUserService {
.eq(SysUserEntity::getEmail, user.getEmail()) .eq(SysUserEntity::getEmail, user.getEmail())
.ne(SysUserEntity::getUserId, existing.getUserId())); .ne(SysUserEntity::getUserId, existing.getUserId()));
if (emailCount > 0) { if (emailCount > 0) {
throw new ServiceException("修改用户'" + user.getUserName() + "'失败,邮箱账号已存在"); throw new ServiceException("修改用户'" + existing.getUserName() + "'失败,邮箱账号已存在");
} }
} }
// 更新用户信息 // 更新用户信息
...@@ -309,7 +309,7 @@ public class SysUserServiceImpl implements SysUserService { ...@@ -309,7 +309,7 @@ public class SysUserServiceImpl implements SysUserService {
return ""; return "";
} }
List<SysRoleEntity> roles = sysRoleDomainService.selectRoleListByUserId(loginUser.getUserId()); List<SysRoleEntity> roles = sysRoleDomainService.selectRoleListByUserId(loginUser.getUserId());
if (roles == null || roles.isEmpty()) { if (CollectionUtils.isEmpty(roles)) {
return ""; return "";
} }
return roles.stream() return roles.stream()
...@@ -359,10 +359,40 @@ public class SysUserServiceImpl implements SysUserService { ...@@ -359,10 +359,40 @@ public class SysUserServiceImpl implements SysUserService {
if (loginUser == null) { if (loginUser == null) {
throw new ServiceException("未登录"); throw new ServiceException("未登录");
} }
SysUserEntity entity = BeanMapper.mapper().convert(user, SysUserEntity.class); SysUserEntity currentUser = sysUserDomainService.getById(loginUser.getUserId());
entity.setUserId(loginUser.getUserId()); if (currentUser == null) {
entity.setUpdateBy(loginUser.getUsername()); throw new ServiceException("用户不存在");
sysUserDomainService.updateById(entity); }
// 只更新允许的字段(nickName/email/phonenumber/sex,与 custom-back 对齐)
if (StringUtils.hasText(user.getNickName())) currentUser.setNickName(user.getNickName());
if (StringUtils.hasText(user.getEmail())) currentUser.setEmail(user.getEmail());
if (StringUtils.hasText(user.getPhonenumber())) currentUser.setPhonenumber(user.getPhonenumber());
if (StringUtils.hasText(user.getSex())) currentUser.setSex(user.getSex());
// 手机号唯一性校验(排除自身,与 custom-back checkPhoneUnique 对齐)
if (StringUtils.hasText(currentUser.getPhonenumber())) {
long phoneCount = sysUserDomainService.count(
new LambdaQueryWrapper<SysUserEntity>()
.eq(SysUserEntity::getPhonenumber, currentUser.getPhonenumber())
.ne(SysUserEntity::getUserId, loginUser.getUserId()));
if (phoneCount > 0) {
throw new ServiceException("修改用户'" + loginUser.getUsername() + "'失败,手机号码已存在");
}
}
// 邮箱唯一性校验(排除自身,与 custom-back checkEmailUnique 对齐)
if (StringUtils.hasText(currentUser.getEmail())) {
long emailCount = sysUserDomainService.count(
new LambdaQueryWrapper<SysUserEntity>()
.eq(SysUserEntity::getEmail, currentUser.getEmail())
.ne(SysUserEntity::getUserId, loginUser.getUserId()));
if (emailCount > 0) {
throw new ServiceException("修改用户'" + loginUser.getUsername() + "'失败,邮箱账号已存在");
}
}
currentUser.setUpdateBy(loginUser.getUsername());
currentUser.setUpdateTime(new Date());
sysUserDomainService.updateById(currentUser);
log.info("用户信息已更新: userId={}", loginUser.getUserId()); log.info("用户信息已更新: userId={}", loginUser.getUserId());
} }
...@@ -376,11 +406,11 @@ public class SysUserServiceImpl implements SysUserService { ...@@ -376,11 +406,11 @@ public class SysUserServiceImpl implements SysUserService {
if (user == null) { if (user == null) {
throw new ServiceException("用户不存在"); throw new ServiceException("用户不存在");
} }
// 验证旧密码 // 验证旧密码(与 custom-back SecurityUtils.matchesPassword 对齐)
if (!passwordEncoder.matches(dto.getOldPassword(), user.getPassword())) { if (!passwordEncoder.matches(dto.getOldPassword(), user.getPassword())) {
throw new ServiceException("旧密码错误"); throw new ServiceException("旧密码错误");
} }
// 检查新密码与旧密码是否相同 // 检查新密码与旧密码是否相同(与 custom-back 对齐)
if (dto.getOldPassword().equals(dto.getNewPassword())) { if (dto.getOldPassword().equals(dto.getNewPassword())) {
throw new ServiceException("新密码不能与旧密码相同"); throw new ServiceException("新密码不能与旧密码相同");
} }
......
package com.jomalls.custom.app.vo.system; package com.jomalls.custom.app.vo.system;
import com.fasterxml.jackson.annotation.JsonGetter;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSetter;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Builder; import lombok.Builder;
...@@ -10,7 +12,6 @@ import lombok.NoArgsConstructor; ...@@ -10,7 +12,6 @@ import lombok.NoArgsConstructor;
import java.io.Serial; import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import java.time.LocalDateTime;
/** /**
* 角色 VO * 角色 VO
...@@ -46,13 +47,17 @@ public class SysRoleVO implements Serializable { ...@@ -46,13 +47,17 @@ public class SysRoleVO implements Serializable {
@Schema(description = "数据权限范围") @Schema(description = "数据权限范围")
private String dataScope; private String dataScope;
@JsonProperty("menuCheckStrictly") /**
* 菜单树选择项是否关联显示 — 内部存 Integer(与 Entity 一致),json 输出 Boolean
*/
@Schema(description = "菜单树选择项是否关联显示") @Schema(description = "菜单树选择项是否关联显示")
private Boolean menuCheckStrictly; private Integer menuCheckStrictly;
@JsonProperty("deptCheckStrictly") /**
* 部门树选择项是否关联显示 — 内部存 Integer(与 Entity 一致),json 输出 Boolean
*/
@Schema(description = "部门树选择项是否关联显示") @Schema(description = "部门树选择项是否关联显示")
private Boolean deptCheckStrictly; private Integer deptCheckStrictly;
@JsonProperty("status") @JsonProperty("status")
@Schema(description = "角色状态(0正常 1停用)") @Schema(description = "角色状态(0正常 1停用)")
...@@ -77,4 +82,36 @@ public class SysRoleVO implements Serializable { ...@@ -77,4 +82,36 @@ public class SysRoleVO implements Serializable {
@JsonProperty("flag") @JsonProperty("flag")
@Schema(description = "用户是否已分配该角色") @Schema(description = "用户是否已分配该角色")
private Boolean flag; private Boolean flag;
/** 序列化为 json 时 Integer→Boolean */
@JsonGetter("menuCheckStrictly")
public Boolean getMenuCheckStrictlyJson() {
return menuCheckStrictly != null && menuCheckStrictly == 1;
}
/** 反序列化时兼容 Boolean 和 Number */
@JsonSetter("menuCheckStrictly")
public void setMenuCheckStrictlyJson(Object value) {
if (value instanceof Boolean) {
this.menuCheckStrictly = (Boolean) value ? 1 : 0;
} else if (value instanceof Number) {
this.menuCheckStrictly = ((Number) value).intValue();
}
}
/** 序列化为 json 时 Integer→Boolean */
@JsonGetter("deptCheckStrictly")
public Boolean getDeptCheckStrictlyJson() {
return deptCheckStrictly != null && deptCheckStrictly == 1;
}
/** 反序列化时兼容 Boolean 和 Number */
@JsonSetter("deptCheckStrictly")
public void setDeptCheckStrictlyJson(Object value) {
if (value instanceof Boolean) {
this.deptCheckStrictly = (Boolean) value ? 1 : 0;
} else if (value instanceof Number) {
this.deptCheckStrictly = ((Number) value).intValue();
}
}
} }
...@@ -7,6 +7,7 @@ import com.jomalls.custom.app.service.system.SysMenuService; ...@@ -7,6 +7,7 @@ import com.jomalls.custom.app.service.system.SysMenuService;
import com.jomalls.custom.app.vo.system.SysMenuVO; import com.jomalls.custom.app.vo.system.SysMenuVO;
import com.jomalls.custom.app.exception.ServiceException; import com.jomalls.custom.app.exception.ServiceException;
import com.jomalls.custom.app.vo.system.TreeSelectVO; import com.jomalls.custom.app.vo.system.TreeSelectVO;
import com.jomalls.custom.utils.AjaxResult;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
...@@ -53,7 +54,7 @@ public class SysMenuController { ...@@ -53,7 +54,7 @@ public class SysMenuController {
@Operation(summary = "根据角色ID查询菜单树(含选中的菜单ID)") @Operation(summary = "根据角色ID查询菜单树(含选中的菜单ID)")
@GetMapping("/roleMenuTreeselect/{roleId}") @GetMapping("/roleMenuTreeselect/{roleId}")
public Map<String, Object> roleMenuTreeselect(@PathVariable Long roleId) { public AjaxResult roleMenuTreeselect(@PathVariable Long roleId) {
return sysMenuService.roleMenuTreeselect(roleId); return sysMenuService.roleMenuTreeselect(roleId);
} }
......
package com.jomalls.custom.webapp.controller.system;
import com.jomalls.custom.app.dto.system.SysUserUpdatePwdDTO;
import com.jomalls.custom.app.service.system.SysUserService;
import com.jomalls.custom.app.vo.system.SysUserVO;
import com.jomalls.custom.utils.AjaxResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.HashMap;
import java.util.Map;
/**
* 个人信息控制器(与 custom-back SysProfileController 对齐)
*
* @author Lizh
*/
@Slf4j
@RestController
@RequestMapping("/api/v2/system/user/profile")
@RequiredArgsConstructor
@Tag(name = "个人信息")
public class SysProfileController {
private final SysUserService sysUserService;
@Operation(summary = "获取当前用户信息(含角色组、岗位组)")
@GetMapping
public AjaxResult profile() {
SysUserVO user = sysUserService.getProfile();
AjaxResult ajax = AjaxResult.success(user);
ajax.put("roleGroup", sysUserService.getCurrentUserRoleGroup());
ajax.put("postGroup", sysUserService.getCurrentUserPostGroup());
return ajax;
}
@Operation(summary = "修改当前用户信息")
@PutMapping
public AjaxResult updateProfile(@RequestBody SysUserVO updateUser) {
sysUserService.updateProfile(updateUser);
return AjaxResult.success();
}
@Operation(summary = "修改当前用户密码")
@PutMapping("/updatePwd")
public AjaxResult updatePwd(@RequestBody SysUserUpdatePwdDTO dto) {
sysUserService.updatePwd(dto);
return AjaxResult.success();
}
@Operation(summary = "更新当前用户头像")
@PostMapping("/avatar")
public AjaxResult avatar(@RequestParam("avatarfile") MultipartFile file) {
log.info("上传头像文件: {}", file.getOriginalFilename());
String avatarUrl = "/profile/avatar/" + file.getOriginalFilename();
sysUserService.updateAvatar(avatarUrl);
AjaxResult ajax = AjaxResult.success();
ajax.put("imgUrl", avatarUrl);
return ajax;
}
}
...@@ -14,6 +14,7 @@ import com.jomalls.custom.app.vo.system.SysRoleVO; ...@@ -14,6 +14,7 @@ import com.jomalls.custom.app.vo.system.SysRoleVO;
import com.jomalls.custom.app.vo.system.SysUserVO; import com.jomalls.custom.app.vo.system.SysUserVO;
import com.jomalls.custom.security.SecurityUtils; import com.jomalls.custom.security.SecurityUtils;
import com.jomalls.custom.app.exception.ServiceException; import com.jomalls.custom.app.exception.ServiceException;
import com.jomalls.custom.utils.AjaxResult;
import com.jomalls.custom.utils.R; import com.jomalls.custom.utils.R;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
...@@ -111,43 +112,17 @@ public class SysUserController { ...@@ -111,43 +112,17 @@ public class SysUserController {
sysUserService.changeStatus(dto); sysUserService.changeStatus(dto);
} }
@Operation(summary = "修改当前用户信息")
@PutMapping("/profile")
public SysUserVO updateProfile(@RequestBody SysUserVO updateUser) {
sysUserService.updateProfile(updateUser);
// 刷新当前 LoginUser 缓存
return sysUserService.getProfile();
}
@Operation(summary = "修改当前用户密码")
@PutMapping("/profile/updatePwd")
public void updatePwd(@RequestBody SysUserUpdatePwdDTO dto) {
sysUserService.updatePwd(dto);
}
@Operation(summary = "更新当前用户头像")
@PostMapping("/profile/avatar")
public Map<String, String> updateAvatar(@RequestParam("avatarfile") MultipartFile file) {
// TODO: 实际头像上传处理(保存文件到服务器/OSS),此处模拟返回
log.info("上传头像文件: {}", file.getOriginalFilename());
String avatarUrl = "/profile/avatar/" + file.getOriginalFilename();
sysUserService.updateAvatar(avatarUrl);
Map<String, String> result = new HashMap<>();
result.put("avatarUrl", avatarUrl);
return result;
}
@Operation(summary = "查询用户已分配的角色(含 user 对象和角色列表)") @Operation(summary = "查询用户已分配的角色(含 user 对象和角色列表)")
@RequiresPermissions("system:user:query") @RequiresPermissions("system:user:query")
@GetMapping("/authRole/{userId}") @GetMapping("/authRole/{userId}")
public Map<String, Object> authRole(@PathVariable Long userId) { public AjaxResult authRole(@PathVariable Long userId) {
// 返回 user + 全部角色列表(含 flag 标识已分配) // 返回 user + 全部角色列表(含 flag 标识已分配)
Map<String, Object> result = new java.util.HashMap<>(); AjaxResult ajax = AjaxResult.success();
SysUserVO user = sysUserService.getById(userId); SysUserVO user = sysUserService.getById(userId);
result.put("user", user); ajax.put("user", user);
// admin 用户看到全部角色,非 admin 不显示 admin 角色 // admin 用户看到全部角色,非 admin 不显示 admin 角色
result.put("roles", sysUserService.getAuthRoleList(userId)); ajax.put("roles", sysUserService.getAuthRoleList(userId));
return result; return ajax;
} }
@Operation(summary = "批量插入用户角色关联") @Operation(summary = "批量插入用户角色关联")
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment