diff --git a/src/main/java/com/yunhai/userbase/controller/oapi/UserController.java b/src/main/java/com/yunhai/userbase/controller/oapi/UserController.java index 8f32945..fe7c534 100644 --- a/src/main/java/com/yunhai/userbase/controller/oapi/UserController.java +++ b/src/main/java/com/yunhai/userbase/controller/oapi/UserController.java @@ -12,6 +12,7 @@ import com.yunhai.userbase.common.validation.ValidationGroups; import com.yunhai.userbase.config.Config; import com.yunhai.userbase.model.sysuser.BatchChangeStatusRequest; import com.yunhai.userbase.model.sysuser.SysUserChangeStatusRequest; +import com.yunhai.userbase.model.sysuser.SysUserContactRequest; import com.yunhai.userbase.model.sysuser.SysUserGroupDeleteItem; import com.yunhai.userbase.model.sysuser.SysUserGroupStateRequest; import com.yunhai.userbase.model.sysuser.SysUserMoveRequest; @@ -240,6 +241,19 @@ public class UserController { sysUserService.changePassword(request.getUserId(), request.getOldPassword(), request.getNewPassword()); } + /** + * 本人联系方式修改(手机号/邮箱):body {userId, phone, email},phone/email 至少一项非空, + * 空白=不修改该字段;只更新 sys_user 的 phone/email 两字段(其余一律不动), + * 手机号/邮箱格式与长度校验、手机号唯一查重在 Service 层。返回更新后的用户 + * (getUserInfoWithDeptById,事务提交后缓存已被 UserChangedEvent 驱逐,回读即新值)。
+ * 鉴权由过滤器层强制签名;POST 自动记 OapiLog。 + */ + @PostMapping("self/contact") + public BoaUser selfContact(@RequestBody @Validated SysUserContactRequest request) { + sysUserService.updateSelfContact(request.getUserId(), request.getPhone(), request.getEmail()); + return userCenterService.getUserInfoWithDeptById(request.getUserId()); + } + /** * 修改用户头像:校验图片(非空/≤20MB/扩展名白名单)→ 压缩 → 存储并写入 profile_picture,
* 返回新头像完整 Web URL。multipart 直传(form 字段 userId + 文件 part file)。
diff --git a/src/main/java/com/yunhai/userbase/model/sysuser/SysUserContactRequest.java b/src/main/java/com/yunhai/userbase/model/sysuser/SysUserContactRequest.java new file mode 100644 index 0000000..2c66432 --- /dev/null +++ b/src/main/java/com/yunhai/userbase/model/sysuser/SysUserContactRequest.java @@ -0,0 +1,35 @@ +package com.yunhai.userbase.model.sysuser; + +import lombok.Data; + +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; + +/** + * 本人联系方式修改请求(oapi {@code /oapi/user/self/contact} 专用)。
+ * 手机号/邮箱至少一项非空(Service 层校验);字段级格式、长度与唯一性校验同在 Service 层 + * (手机号口径与 {@code validateProfile} 一致,另做邮箱格式 + 手机号查重)。 + * + * @author chenx + */ +@Data +public class SysUserContactRequest { + + /** + * 用户ID(必填)。 + */ + @NotNull + private Integer userId; + + /** + * 手机号(可选;非空时走 1[3-9] 段位格式校验 + 全库唯一查重)。 + */ + @Size(max = 20, message = "手机号长度不能超过20字符") + private String phone; + + /** + * 邮箱(可选;非空时走邮箱格式校验)。 + */ + @Size(max = 100, message = "邮箱长度不能超过100字符") + private String email; +} diff --git a/src/main/java/com/yunhai/userbase/service/ISysUserService.java b/src/main/java/com/yunhai/userbase/service/ISysUserService.java index 77cb6cf..9774880 100644 --- a/src/main/java/com/yunhai/userbase/service/ISysUserService.java +++ b/src/main/java/com/yunhai/userbase/service/ISysUserService.java @@ -218,6 +218,19 @@ public interface ISysUserService { */ void changePassword(Integer userId, String oldPassword, String newPassword); + /** + * 修改本人联系方式(手机号/邮箱,oapi 开放接口 {@code /oapi/user/self/contact} 专用)。
+ * 只写 sys_user 的 phone/email 两字段,其余字段一律不动;phone/email 至少一项非空, + * 空白字段视为「不修改该字段」(不写库不清空);手机号改值时做全库唯一查重(phone 是 + * byPhone 身份维度,重复会破坏手机号找人)。更新后发 UserChangedEvent 精确驱逐 boaUser + * 缓存(含旧 phone: 维度 key 提示),调用方紧跟着的 byId 回读即新值。 + * + * @param userId 用户ID(目标用户本人) + * @param phone 新手机号(可空=不改) + * @param email 新邮箱(可空=不改) + */ + void updateSelfContact(Integer userId, String phone, String email); + /** * 修改用户头像:校验图片(非空/≤20MB/扩展名白名单)→ 压缩 → 存储至 avatar 子目录 → * 写入 profile_picture。oapi 开放接口专用;返回新头像完整 Web URL。 diff --git a/src/main/java/com/yunhai/userbase/service/impl/BoaUserLoader.java b/src/main/java/com/yunhai/userbase/service/impl/BoaUserLoader.java index c39f649..6c0046d 100644 --- a/src/main/java/com/yunhai/userbase/service/impl/BoaUserLoader.java +++ b/src/main/java/com/yunhai/userbase/service/impl/BoaUserLoader.java @@ -92,8 +92,10 @@ public class BoaUserLoader { boa.setIdcard(user.getIdcard()); boa.setPhone(user.getPhone()); boa.setProfilePicture(user.getProfilePicture()); - // 用户表无 email / gender 列,置 null - boa.setEmail(null); + // email 列已存在(SysUser.email,管理端编辑/批量导入均已读写): + // 本人联系方式修改(oapi self/contact)后回读 BoaUser 须携带新 email,旧注释「用户表无 email 列」已过时; + // gender 列虽在实体上,但当前无写入来源,保持置 null + boa.setEmail(user.getEmail()); boa.setGender(null); boa.setType(user.getType() != null ? user.getType().getKey() : null); // 状态语义对齐:本表 disabled(NORMAL=0 正常 / DISABLED=1 禁用) -> BoaUser(1 正常 / 0 禁用) diff --git a/src/main/java/com/yunhai/userbase/service/impl/SysUserService.java b/src/main/java/com/yunhai/userbase/service/impl/SysUserService.java index cb76044..17fd987 100644 --- a/src/main/java/com/yunhai/userbase/service/impl/SysUserService.java +++ b/src/main/java/com/yunhai/userbase/service/impl/SysUserService.java @@ -96,6 +96,10 @@ public class SysUserService implements ISysUserService { private static final Set SUPER_ADMIN_ACCOUNTS = new HashSet<>(Arrays.asList("yunhaisch", "yunhaisch2", "yunhaisch3")); + /** 邮箱格式(本人联系方式修改用):本地段常规字符 + 点分域名,总长另由请求 @Size 限制 */ + private static final java.util.regex.Pattern CONTACT_EMAIL = + java.util.regex.Pattern.compile("^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\\.[A-Za-z0-9-]+)+$"); + @Autowired private SysUserRepository sysUserRepository; @@ -289,6 +293,52 @@ public class SysUserService implements ISysUserService { sysUserRepository.save(entity); } + /** + * 修改本人联系方式(oapi /oapi/user/self/contact):只写 phone/email 两字段,其余不动。
+ * 空白入参=「不修改该字段」(不清空——清空语义不在本接口范围);手机号是 byPhone 身份维度, + * 改值前全库查重;更新后发 UPDATE_USER 推送 + UserChangedEvent(带旧 phone: key 提示)驱逐 + * boaUser 缓存,控制器紧跟着的 getUserInfoWithDeptById 回读即新值(AFTER_COMMIT 后于回读执行)。 + */ + @Override + @Transactional + public void updateSelfContact(Integer userId, String phone, String email) { + SysUser entity = sysUserRepository.findById(userId) + .orElseThrow(() -> new DException("系统用户不存在")); + String newPhone = StringUtils.trimToNull(phone); + String newEmail = StringUtils.trimToNull(email); + if (newPhone == null && newEmail == null) { + throw new DException("手机号与邮箱至少填写一项"); + } + if (newPhone != null && !ValidatorUtil.isPhone(newPhone)) { + throw new DException("手机号格式不正确"); + } + if (newEmail != null && !CONTACT_EMAIL.matcher(newEmail).matches()) { + throw new DException("邮箱格式不正确"); + } + if (newPhone != null && !newPhone.equals(entity.getPhone())) { + SysUser samePhone = sysUserRepository.findByPhone(newPhone); + if (samePhone != null && !samePhone.getId().equals(userId)) { + throw new DException("手机号已被其他用户使用"); + } + } + // 改手机号后旧 phone: 维度缓存 key 无处回查,须随事件显式驱逐(同 editInternal 口径); + // 仅改邮箱时手机号未变,不加提示(避免无谓多驱逐一个 key) + Set staleKeyHints = new HashSet<>(); + if (newPhone != null && StringUtils.isNotBlank(entity.getPhone()) + && !entity.getPhone().equals(newPhone)) { + staleKeyHints.add("phone:" + entity.getPhone()); + } + if (newPhone != null) { + entity.setPhone(newPhone); + } + if (newEmail != null) { + entity.setEmail(newEmail); + } + entity.setUpdateUser(SYSTEM_USER_ID); + sysUserRepository.save(entity); + publishUserEvent(PushInterfaceType.UPDATE_USER, userId, staleKeyHints); + } + /** * 开放接口的用户身份写入(追加语义,只加不删):
* 学校范围 = 显式 schoolId,缺省按 request.groupId 解析(与新增口同一约定,调用方不感知解析逻辑);