From 90c08a5f145b447e1cec0bec5733a036f05fe29d Mon Sep 17 00:00:00 2001 From: chenxing Date: Fri, 2 Oct 2026 08:53:30 +0800 Subject: [PATCH] =?UTF-8?q?feat(oapi):=20=E6=9C=AC=E4=BA=BA=E8=81=94?= =?UTF-8?q?=E7=B3=BB=E6=96=B9=E5=BC=8F=E4=BF=AE=E6=94=B9=E7=AB=AF=E7=82=B9?= =?UTF-8?q?=20POST=20/oapi/user/self/contact=E2=80=94=E2=80=94=E5=8F=AA?= =?UTF-8?q?=E5=86=99=20sys=5Fuser=20phone/email=20=E4=B8=A4=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=EF=BC=88=E7=A9=BA=E7=99=BD=3D=E4=B8=8D=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=EF=BC=89=EF=BC=8C=E6=89=8B=E6=9C=BA=E5=8F=B7=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F/=E5=94=AF=E4=B8=80=E6=9F=A5=E9=87=8D+=E9=82=AE?= =?UTF-8?q?=E7=AE=B1=E6=A0=BC=E5=BC=8F=E6=A0=A1=E9=AA=8C=EF=BC=8CUserChang?= =?UTF-8?q?edEvent=20=E9=A9=B1=E9=80=90=20boaUser=20=E7=BC=93=E5=AD=98?= =?UTF-8?q?=EF=BC=88=E5=90=AB=E6=97=A7=20phone:=20key=20=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=EF=BC=89=EF=BC=9B=E9=99=84=E5=B8=A6=E4=BF=AE=E5=A4=8D=20BoaUse?= =?UTF-8?q?rLoader=20=E8=BF=87=E6=97=B6=E7=BD=AE=E7=A9=BA=EF=BC=9Aemail=20?= =?UTF-8?q?=E5=88=97=E6=97=A9=E5=B7=B2=E5=AD=98=E5=9C=A8=EF=BC=88=E5=AE=9E?= =?UTF-8?q?=E4=BD=93/=E7=AE=A1=E7=90=86=E7=AB=AF/=E5=AF=BC=E5=85=A5?= =?UTF-8?q?=E5=9D=87=E8=AF=BB=E5=86=99=EF=BC=89=EF=BC=8C=E5=9B=9E=E8=AF=BB?= =?UTF-8?q?=20BoaUser=20=E9=A1=BB=E6=90=BA=E5=B8=A6=20email=EF=BC=88?= =?UTF-8?q?=E4=BB=BB=E5=8A=A1B=C2=B7UserBase=20=E4=BE=A7=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/oapi/UserController.java | 14 ++++++ .../model/sysuser/SysUserContactRequest.java | 35 +++++++++++++ .../userbase/service/ISysUserService.java | 13 +++++ .../userbase/service/impl/BoaUserLoader.java | 6 ++- .../userbase/service/impl/SysUserService.java | 50 +++++++++++++++++++ 5 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/yunhai/userbase/model/sysuser/SysUserContactRequest.java 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 解析(与新增口同一约定,调用方不感知解析逻辑);