From 592345018e27a41feeba0e63917755b4cdaf99b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Mon, 20 Jul 2026 23:22:53 +0800 Subject: [PATCH] =?UTF-8?q?feat(system):=20=E5=AE=9E=E7=8E=B0=E6=89=8B?= =?UTF-8?q?=E6=9C=BA=E5=8F=B7=E6=8D=A2=E7=BB=91=E5=8F=8C=E9=87=8D=E7=9F=AD?= =?UTF-8?q?=E4=BF=A1=E9=AA=8C=E8=AF=81=E7=A0=81=E9=AA=8C=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 用户换绑手机号时新增两步短信验证码校验流程 - 第一步:向原手机号(或无原手机号时的新号)发送验证码验证账户归属 - 第二步:向新手机号发送验证码验证新号归属,若账户无旧手机号则跳过 - 新手机号绑定时增加唯一性校验,防止被其他账户占用 - 更新User实体,新增smsCodeNew属性用于存储新手机号验证码 - 优化Redis验证码缓存和删除逻辑,防止验证码滥用 --- .../system/controller/MainController.java | 34 +++++++++++++++---- .../gxwebsoft/common/system/entity/User.java | 4 +++ 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/gxwebsoft/common/system/controller/MainController.java b/src/main/java/com/gxwebsoft/common/system/controller/MainController.java index 4843570..b0ab0e3 100644 --- a/src/main/java/com/gxwebsoft/common/system/controller/MainController.java +++ b/src/main/java/com/gxwebsoft/common/system/controller/MainController.java @@ -392,21 +392,43 @@ public class MainController extends BaseController { update.setAvatar(user.getAvatar()); update.setBgImage(user.getBgImage()); update.setSex(user.getSex()); - // 手机号变更:仅当提交短信验证码且校验通过时才更新,防止未经验证的手机号被绑定 + // 手机号变更:两步验证 + // 第一步:短信验证码发往原手机号(验证当前账户归属),校验通过才允许换绑 + // 第二步:向新手机号发送验证码(验证新号归属);账号原本无手机号时,第一步的码已发往新号,无需第二步 if (StrUtil.isNotBlank(user.getSmsCode())) { String newPhone = user.getPhone(); if (StrUtil.isBlank(newPhone) || !CommonUtil.isValidPhoneNumber(newPhone)) { return fail("手机号格式不正确", null); } - String key = "code:" + newPhone; - String cached = redisUtil.get(key); + User currentUser = userService.getById(getLoginUserId()); + String currentPhone = currentUser != null ? currentUser.getPhone() : null; + // 第一步:校验原手机号(或账号原本无手机号时的新号)验证码 + String verifyPhone = StrUtil.isNotBlank(currentPhone) ? currentPhone : newPhone; + String oldKey = "code:" + verifyPhone; + String cachedOld = redisUtil.get(oldKey); String devCode = redisUtil.get(CACHE_KEY_VERIFICATION_CODE_BY_DEV_SMS); - if (StrUtil.isBlank(cached) || (!cached.equals(user.getSmsCode()) && !user.getSmsCode().equals(devCode))) { + if (StrUtil.isBlank(cachedOld) || (!cachedOld.equals(user.getSmsCode()) && !user.getSmsCode().equals(devCode))) { return fail("短信验证码不正确", null); } + // 第二步:校验新手机号验证码(仅当账号原本已绑定手机号,才需要二次验证新号归属) + if (StrUtil.isNotBlank(currentPhone)) { + String newKey = "code:" + newPhone; + String cachedNew = redisUtil.get(newKey); + if (StrUtil.isBlank(cachedNew) + || (!cachedNew.equals(user.getSmsCodeNew()) && !user.getSmsCodeNew().equals(devCode))) { + return fail("新手机号验证码不正确", null); + } + redisUtil.delete(newKey); + cacheClient.delete(newPhone); + } + // 新手机号唯一性校验:不可被其他账号占用 + User existed = userService.getByPhone(newPhone); + if (existed != null && !existed.getUserId().equals(getLoginUserId())) { + return fail("该手机号已被其他账号绑定", null); + } update.setPhone(newPhone); - redisUtil.delete(key); - cacheClient.delete(newPhone); + redisUtil.delete(oldKey); + cacheClient.delete(verifyPhone); } // 邮箱变更:仅当提交邮箱验证码且校验通过时才更新,防止未经验证的邮箱被绑定 if (StrUtil.isNotBlank(user.getEmailCode())) { diff --git a/src/main/java/com/gxwebsoft/common/system/entity/User.java b/src/main/java/com/gxwebsoft/common/system/entity/User.java index 47dce0e..b679890 100644 --- a/src/main/java/com/gxwebsoft/common/system/entity/User.java +++ b/src/main/java/com/gxwebsoft/common/system/entity/User.java @@ -69,6 +69,10 @@ public class User implements UserDetails { @TableField(exist = false) private String smsCode; + @Schema(description = "新手机号短信验证码(非数据库字段,仅用于换绑手机号时二次验证新号归属)") + @TableField(exist = false) + private String smsCodeNew; + @Schema(description = "资质") private String aptitude;