From 16a57a8fc6868cea9171a1119698572407a2fc03 Mon Sep 17 00:00:00 2001
From: hejianhao <15708179461@qq.com>
Date: 星期五, 28 八月 2026 15:21:46 +0800
Subject: [PATCH] fix0806: APP端去掉全部的市辖区选择

---
 tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/auth/service/AppAuthService.java |  226 ++++++++++++++++++++------------------------------------
 1 files changed, 82 insertions(+), 144 deletions(-)

diff --git a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/auth/service/AppAuthService.java b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/auth/service/AppAuthService.java
index 9f19c16..2506692 100644
--- a/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/auth/service/AppAuthService.java
+++ b/tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/auth/service/AppAuthService.java
@@ -12,7 +12,6 @@
 import com.tcm.training.admin.app.auth.vo.AppLoginUserVO;
 import com.tcm.training.admin.app.auth.vo.AppPasswordResetVerifyResponse;
 import com.tcm.training.admin.app.auth.vo.AppSmsCodeSendResponse;
-import com.tcm.training.admin.app.auth.exception.InviteCodeRequiredException;
 import com.tcm.training.admin.app.share.service.AppShareService;
 import com.tcm.training.admin.member.entity.AppUser;
 import com.tcm.training.admin.member.mapper.UserMapper;
@@ -22,21 +21,16 @@
 import org.springframework.stereotype.Service;
 
 import java.time.LocalDateTime;
-import java.util.UUID;
-import java.util.concurrent.TimeUnit;
 import java.util.regex.Pattern;
 
 /**
- * APP 登录认证服务,负责短信登录、密码登录、微信登录与忘记密码流程。
+ * APP 登录认证服务,负责短信登录、第三方登录与登录态刷新流程。
  */
 @Service
 public class AppAuthService {
 
     private static final Pattern PHONE_PATTERN = Pattern.compile("^1\\d{10}$");
-    private static final Pattern PASSWORD_PATTERN = Pattern.compile("^(?![0-9]+$)(?![a-zA-Z]+$)(?![^0-9a-zA-Z]+$).{8,32}$");
-    private static final String PASSWORD_RESET_TICKET_CACHE_PREFIX = "app:password-reset:ticket:";
-    private static final long PASSWORD_RESET_TICKET_EXPIRE_MINUTES = 10L;
-
+    private static final String PASSWORD_AUTH_DISABLED_MESSAGE = "当前版本仅支持验证码登录";
     private final UserMapper userMapper;
     private final AppSmsCodeService appSmsCodeService;
     private final BCryptPasswordEncoder passwordEncoder;
@@ -77,12 +71,11 @@
     }
 
     /**
-     * 为已注册 APP 用户生成忘记密码验证码。
+     * 当前版本已下线密码能力,旧客户端调用时返回统一提示。
      */
     public AppSmsCodeSendResponse sendPasswordResetCode(AppSmsCodeSendRequest request) {
-        String phone = normalizePhone(request.getPhone());
-        requireEnabledUserByPhone(phone);
-        return appSmsCodeService.createPasswordResetCode(phone);
+        throwPasswordAuthDisabled();
+        return null;
     }
 
     /**
@@ -96,16 +89,11 @@
     }
 
     /**
-     * 使用手机号密码完成 APP 用户登录。
+     * 当前版本已下线密码登录,旧客户端调用时返回统一提示。
      */
     public AppLoginResponse passwordLogin(AppPasswordLoginRequest request) {
-        String phone = normalizePhone(request.getPhone());
-        String password = normalizeLoginPassword(request.getPassword());
-        AppUser user = requireEnabledUserByPhone(phone);
-        if (!passwordEncoder.matches(password, user.getPassword())) {
-            throw new IllegalArgumentException("账号或密码错误");
-        }
-        return createLoginResponse(user);
+        throwPasswordAuthDisabled();
+        return null;
     }
 
     /**
@@ -115,7 +103,6 @@
         AppWechatUserProfile profile = appWechatAuthClient.resolveUserProfile(request);
         AppUser user = findUserByWechatOpenid(profile.getOpenid());
         if (user == null) {
-            requireInviteCodeForThirdPartyRegister(request.getInviteCode());
             user = buildWechatUser(profile);
             bindInviteRelationIfAbsent(user, request.getInviteCode());
             userMapper.insert(user);
@@ -136,7 +123,6 @@
         AppAppleUserProfile profile = appAppleAuthClient.resolveUserProfile(request);
         AppUser user = findUserByAppleSubject(profile.getSubject());
         if (user == null) {
-            requireInviteCodeForThirdPartyRegister(request.getInviteCode());
             user = buildAppleUser(profile);
             bindInviteRelationIfAbsent(user, request.getInviteCode());
             userMapper.insert(user);
@@ -151,32 +137,18 @@
     }
 
     /**
-     * 校验忘记密码验证码并生成一次性重置凭证。
+     * 当前版本已下线密码能力,旧客户端调用时返回统一提示。
      */
     public AppPasswordResetVerifyResponse verifyPasswordResetCode(AppPasswordResetVerifyRequest request) {
-        String phone = normalizePhone(request.getPhone());
-        requireEnabledUserByPhone(phone);
-        appSmsCodeService.validatePasswordResetCode(phone, normalizeCode(request.getCode()));
-
-        String resetTicket = createResetTicket();
-        cacheResetTicket(resetTicket, phone);
-        return new AppPasswordResetVerifyResponse(resetTicket);
+        throwPasswordAuthDisabled();
+        return null;
     }
 
     /**
-     * 使用一次性重置凭证更新 APP 用户密码。
+     * 当前版本已下线密码能力,旧客户端调用时返回统一提示。
      */
     public void resetPassword(AppPasswordResetRequest request) {
-        String resetTicket = normalizeResetTicket(request.getResetTicket());
-        String password = normalizeNewPassword(request.getPassword());
-        String confirmPassword = normalizeConfirmPassword(request.getConfirmPassword());
-        validatePasswordConfirmation(password, confirmPassword);
-
-        String phone = loadPhoneByResetTicket(resetTicket);
-        AppUser user = requireEnabledUserByPhone(phone);
-        user.setPassword(passwordEncoder.encode(password));
-        userMapper.updateById(user);
-        stringRedisTemplate.delete(buildResetTicketKey(resetTicket));
+        throwPasswordAuthDisabled();
     }
 
     /**
@@ -187,11 +159,28 @@
     }
 
     /**
+     * 刷新当前 APP 登录态,并重新签发一枚新的 token。
+     */
+    public AppLoginResponse refreshToken() {
+        Long userId = appAuthSessionService.requireLoginUserId();
+        AppUser user = requireEnabledUserById(userId);
+        // 当前 Sa-Token 版本不支持按 token 原地续期,这里直接为当前用户重新签发新 token。
+        appAuthSessionService.login(user.getId());
+        return buildLoginResponse(user, appAuthSessionService.getTokenInfo());
+    }
+
+    /**
      * 为成功登录的 APP 用户建立登录态并拼装统一响应。
      */
     private AppLoginResponse createLoginResponse(AppUser user) {
         appAuthSessionService.login(user.getId());
-        SaTokenInfo tokenInfo = appAuthSessionService.getTokenInfo();
+        return buildLoginResponse(user, appAuthSessionService.getTokenInfo());
+    }
+
+    /**
+     * 统一构造 APP 登录响应,保证登录与续期接口返回结构一致。
+     */
+    private AppLoginResponse buildLoginResponse(AppUser user, SaTokenInfo tokenInfo) {
         return new AppLoginResponse(
                 tokenInfo.getTokenValue(),
                 "Bearer",
@@ -235,6 +224,18 @@
      */
     private AppUser requireEnabledUserByPhone(String phone) {
         AppUser user = requireExistingUserByPhone(phone);
+        requireEnabledUser(user);
+        return user;
+    }
+
+    /**
+     * 按用户主键查询并校验 APP 用户状态。
+     */
+    private AppUser requireEnabledUserById(Long userId) {
+        AppUser user = userMapper.selectById(userId);
+        if (user == null || user.getDeleted() == null || user.getDeleted().intValue() != 0) {
+            throw new IllegalArgumentException("用户不存在");
+        }
         requireEnabledUser(user);
         return user;
     }
@@ -284,15 +285,6 @@
     }
 
     /**
-     * 第三方授权首次创建账号时必须携带医生邀请码。
-     */
-    private void requireInviteCodeForThirdPartyRegister(String inviteCode) {
-        if (normalizeInviteCode(inviteCode) == null) {
-            throw new InviteCodeRequiredException();
-        }
-    }
-
-    /**
      * 用户尚未绑定诊所关系时,根据医生邀请码补齐上下级关系。
      */
     private void bindInviteRelationIfAbsent(AppUser user, String inviteCode) {
@@ -325,13 +317,14 @@
     private void applyWechatProfile(AppUser user, AppWechatUserProfile profile, boolean syncLocalProfile) {
         String nickname = trimToNull(profile.getNickname());
         String avatar = trimToNull(profile.getAvatar());
+        String currentNickname = trimToNull(user.getNickname());
 
         user.setWechatOpenid(profile.getOpenid());
         user.setWechatNickname(nickname);
         user.setWechatAvatar(avatar);
         user.setWechatBoundAt(LocalDateTime.now());
 
-        if (syncLocalProfile || trimToNull(user.getNickname()) == null) {
+        if (shouldRefreshLocalNickname(currentNickname, profile.getOpenid(), nickname, syncLocalProfile)) {
             user.setNickname(nickname == null ? profile.getOpenid() : nickname);
         }
         if (syncLocalProfile || trimToNull(user.getAvatar()) == null) {
@@ -340,6 +333,40 @@
         if (syncLocalProfile || user.getGender() == null) {
             user.setGender(resolveWechatGender(profile.getSex()));
         }
+    }
+
+    /**
+     * 仅在首次建号、昵称为空、openid 兜底值或历史乱码场景下刷新本地昵称,避免覆盖用户手动修改结果。
+     */
+    private boolean shouldRefreshLocalNickname(String currentNickname,
+                                               String openid,
+                                               String latestWechatNickname,
+                                               boolean syncLocalProfile) {
+        if (syncLocalProfile) {
+            return true;
+        }
+        if (trimToNull(latestWechatNickname) == null) {
+            return false;
+        }
+        String normalizedCurrentNickname = trimToNull(currentNickname);
+        if (normalizedCurrentNickname == null) {
+            return true;
+        }
+        String normalizedOpenid = trimToNull(openid);
+        if (normalizedOpenid != null && normalizedOpenid.equals(normalizedCurrentNickname)) {
+            return true;
+        }
+        return containsNicknameMojibake(normalizedCurrentNickname);
+    }
+
+    /**
+     * 识别历史微信昵称乱码的常见特征,限定为已知乱码模式,避免误伤正常昵称。
+     */
+    private boolean containsNicknameMojibake(String nickname) {
+        return nickname.contains("ð")
+                || nickname.contains("Ã")
+                || nickname.contains("â")
+                || nickname.contains("�");
     }
 
     /**
@@ -394,14 +421,10 @@
     }
 
     /**
-     * 归一化密码登录场景下的密码输入,仅要求非空。
+     * 抛出 APP 密码能力下线后的统一业务提示。
      */
-    private String normalizeLoginPassword(String password) {
-        String normalized = trimToNull(password);
-        if (normalized == null) {
-            throw new IllegalArgumentException("请输入密码");
-        }
-        return normalized;
+    private void throwPasswordAuthDisabled() {
+        throw new IllegalArgumentException(PASSWORD_AUTH_DISABLED_MESSAGE);
     }
 
     /**
@@ -414,91 +437,6 @@
         }
         String upperInviteCode = normalized.toUpperCase();
         return upperInviteCode.matches("^[A-Z0-9]{6}$") ? upperInviteCode : null;
-    }
-
-    /**
-     * 归一化并校验忘记密码场景下的新密码格式。
-     */
-    private String normalizeNewPassword(String password) {
-        String normalized = trimToNull(password);
-        if (normalized == null) {
-            throw new IllegalArgumentException("请输入新密码");
-        }
-        if (!PASSWORD_PATTERN.matcher(normalized).matches()) {
-            throw new IllegalArgumentException("密码需为8-32位,且包含数字、字母、符号至少2种");
-        }
-        return normalized;
-    }
-
-    /**
-     * 归一化并校验确认密码输入。
-     */
-    private String normalizeConfirmPassword(String confirmPassword) {
-        String normalized = trimToNull(confirmPassword);
-        if (normalized == null) {
-            throw new IllegalArgumentException("请再次输入密码");
-        }
-        if (!PASSWORD_PATTERN.matcher(normalized).matches()) {
-            throw new IllegalArgumentException("密码需为8-32位,且包含数字、字母、符号至少2种");
-        }
-        return normalized;
-    }
-
-    /**
-     * 归一化并校验一次性重置凭证。
-     */
-    private String normalizeResetTicket(String resetTicket) {
-        String normalized = trimToNull(resetTicket);
-        if (normalized == null) {
-            throw new IllegalArgumentException("重置凭证已失效,请重新验证");
-        }
-        return normalized;
-    }
-
-    /**
-     * 校验两次输入的新密码保持一致。
-     */
-    private void validatePasswordConfirmation(String password, String confirmPassword) {
-        if (!password.equals(confirmPassword)) {
-            throw new IllegalArgumentException("两次密码输入不一致");
-        }
-    }
-
-    /**
-     * 生成忘记密码场景使用的一次性重置凭证。
-     */
-    private String createResetTicket() {
-        return UUID.randomUUID().toString().replace("-", "");
-    }
-
-    /**
-     * 将重置凭证与手机号绑定写入 Redis,供下一步更新密码使用。
-     */
-    private void cacheResetTicket(String resetTicket, String phone) {
-        stringRedisTemplate.opsForValue().set(
-                buildResetTicketKey(resetTicket),
-                phone,
-                PASSWORD_RESET_TICKET_EXPIRE_MINUTES,
-                TimeUnit.MINUTES
-        );
-    }
-
-    /**
-     * 从 Redis 读取并校验一次性重置凭证对应的手机号。
-     */
-    private String loadPhoneByResetTicket(String resetTicket) {
-        String phone = stringRedisTemplate.opsForValue().get(buildResetTicketKey(resetTicket));
-        if (phone == null || phone.trim().isEmpty()) {
-            throw new IllegalArgumentException("重置凭证已失效,请重新验证");
-        }
-        return phone;
-    }
-
-    /**
-     * 构造忘记密码重置凭证的 Redis key。
-     */
-    private String buildResetTicketKey(String resetTicket) {
-        return PASSWORD_RESET_TICKET_CACHE_PREFIX + resetTicket;
     }
 
     /**

--
Gitblit v1.7.1