“mitao”
2026-04-07 6ad7a6330add80e0b5a83227888df95e92d7ba87
tcm-training-admin-backend/src/main/java/com/tcm/training/admin/app/auth/service/AppAuthService.java
@@ -6,6 +6,7 @@
import com.tcm.training.admin.app.auth.dto.AppPasswordResetVerifyRequest;
import com.tcm.training.admin.app.auth.dto.AppSmsCodeSendRequest;
import com.tcm.training.admin.app.auth.dto.AppSmsLoginRequest;
import com.tcm.training.admin.app.auth.dto.AppWechatLoginRequest;
import com.tcm.training.admin.app.auth.vo.AppLoginResponse;
import com.tcm.training.admin.app.auth.vo.AppLoginUserVO;
import com.tcm.training.admin.app.auth.vo.AppPasswordResetVerifyResponse;
@@ -17,12 +18,13 @@
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
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 {
@@ -36,6 +38,7 @@
    private final AppSmsCodeService appSmsCodeService;
    private final BCryptPasswordEncoder passwordEncoder;
    private final AppAuthSessionService appAuthSessionService;
    private final AppWechatAuthClient appWechatAuthClient;
    private final StringRedisTemplate stringRedisTemplate;
    /**
@@ -45,11 +48,13 @@
                          AppSmsCodeService appSmsCodeService,
                          BCryptPasswordEncoder passwordEncoder,
                          AppAuthSessionService appAuthSessionService,
                          AppWechatAuthClient appWechatAuthClient,
                          StringRedisTemplate stringRedisTemplate) {
        this.userMapper = userMapper;
        this.appSmsCodeService = appSmsCodeService;
        this.passwordEncoder = passwordEncoder;
        this.appAuthSessionService = appAuthSessionService;
        this.appWechatAuthClient = appWechatAuthClient;
        this.stringRedisTemplate = stringRedisTemplate;
    }
@@ -91,6 +96,25 @@
        if (!passwordEncoder.matches(password, user.getPassword())) {
            throw new IllegalArgumentException("账号或密码错误");
        }
        return createLoginResponse(user);
    }
    /**
     * 使用微信授权结果完成 APP 登录,首次登录自动创建普通用户。
     */
    public AppLoginResponse wechatLogin(AppWechatLoginRequest request) {
        AppWechatUserProfile profile = appWechatAuthClient.resolveUserProfile(request);
        AppUser user = findUserByWechatOpenid(profile.getOpenid());
        if (user == null) {
            user = buildWechatUser(profile);
            userMapper.insert(user);
            return createLoginResponse(user);
        }
        requireEnabledUser(user);
        // 登录时同步微信专用资料,并仅在本地昵称头像为空时补齐展示字段。
        applyWechatProfile(user, profile, false);
        userMapper.updateById(user);
        return createLoginResponse(user);
    }
@@ -145,7 +169,7 @@
        loginUser.setId(user.getId());
        loginUser.setNickname(resolveNickname(user));
        loginUser.setPhone(user.getPhone());
        loginUser.setAvatar(user.getAvatar());
        loginUser.setAvatar(resolveAvatar(user));
        loginUser.setType(user.getType());
        loginUser.setStatus(user.getStatus());
        loginUser.setClinicId(user.getClinicId());
@@ -172,10 +196,77 @@
     */
    private AppUser requireEnabledUserByPhone(String phone) {
        AppUser user = requireExistingUserByPhone(phone);
        requireEnabledUser(user);
        return user;
    }
    /**
     * 校验给定 APP 用户是否处于启用状态。
     */
    private void requireEnabledUser(AppUser user) {
        if (user.getStatus() == null || user.getStatus() != 1) {
            throw new IllegalArgumentException("账号已被禁用");
        }
    }
    /**
     * 按微信 openid 查询本地 APP 用户。
     */
    private AppUser findUserByWechatOpenid(String openid) {
        return userMapper.selectOne(new LambdaQueryWrapper<AppUser>()
                .eq(AppUser::getWechatOpenid, openid)
                .eq(AppUser::getDeleted, 0)
                .last("limit 1"));
    }
    /**
     * 基于微信资料构造首次登录时的本地普通用户。
     */
    private AppUser buildWechatUser(AppWechatUserProfile profile) {
        AppUser user = new AppUser();
        user.setType(0);
        user.setStatus(1);
        applyWechatProfile(user, profile, true);
        return user;
    }
    /**
     * 将微信资料回填到本地用户,必要时补齐昵称头像展示字段。
     */
    private void applyWechatProfile(AppUser user, AppWechatUserProfile profile, boolean syncLocalProfile) {
        String nickname = trimToNull(profile.getNickname());
        String avatar = trimToNull(profile.getAvatar());
        user.setWechatOpenid(profile.getOpenid());
        user.setWechatNickname(nickname);
        user.setWechatAvatar(avatar);
        user.setWechatBoundAt(LocalDateTime.now());
        if (syncLocalProfile || trimToNull(user.getNickname()) == null) {
            user.setNickname(nickname == null ? profile.getOpenid() : nickname);
        }
        if (syncLocalProfile || trimToNull(user.getAvatar()) == null) {
            user.setAvatar(avatar);
        }
        if (syncLocalProfile || user.getGender() == null) {
            user.setGender(resolveWechatGender(profile.getSex()));
        }
    }
    /**
     * 将微信性别值转换为本地用户性别枚举。
     */
    private Integer resolveWechatGender(Integer sex) {
        if (sex == null) {
            return null;
        }
        if (sex == 1) {
            return 1;
        }
        if (sex == 2) {
            return 0;
        }
        return null;
    }
    /**
@@ -301,7 +392,25 @@
     */
    private String resolveNickname(AppUser user) {
        String nickname = trimToNull(user.getNickname());
        return nickname == null ? user.getPhone() : nickname;
        if (nickname != null) {
            return nickname;
        }
        String wechatNickname = trimToNull(user.getWechatNickname());
        if (wechatNickname != null) {
            return wechatNickname;
        }
        return user.getPhone();
    }
    /**
     * 优先使用本地头像,缺失时回退到微信头像。
     */
    private String resolveAvatar(AppUser user) {
        String avatar = trimToNull(user.getAvatar());
        if (avatar != null) {
            return avatar;
        }
        return trimToNull(user.getWechatAvatar());
    }
    /**