| | |
| | | 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; |
| | |
| | | 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 { |
| | |
| | | private final AppSmsCodeService appSmsCodeService; |
| | | private final BCryptPasswordEncoder passwordEncoder; |
| | | private final AppAuthSessionService appAuthSessionService; |
| | | private final AppWechatAuthClient appWechatAuthClient; |
| | | private final StringRedisTemplate stringRedisTemplate; |
| | | |
| | | /** |
| | |
| | | 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; |
| | | } |
| | | |
| | |
| | | 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); |
| | | } |
| | | |
| | |
| | | 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()); |
| | |
| | | */ |
| | | 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; |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | 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()); |
| | | } |
| | | |
| | | /** |