package com.xuekairui.user.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.xuekairui.common.BusinessException; import com.xuekairui.common.ErrorCode; import com.xuekairui.common.ValidateUtil; import com.xuekairui.user.dto.*; import com.xuekairui.user.entity.User; import com.xuekairui.user.enums.MembershipLevel; import com.xuekairui.user.enums.OperationType; import com.xuekairui.user.event.UserRegisteredEvent; import com.xuekairui.user.mapper.UserMapper; import com.xuekairui.user.util.JwtUtil; import com.xuekairui.user.util.PasswordUtil; import io.jsonwebtoken.Claims; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.time.temporal.ChronoUnit; /** * 认证服务 */ @Slf4j @Service @RequiredArgsConstructor public class AuthService { private final UserMapper userMapper; private final VerificationCodeService verificationCodeService; private final WechatService wechatService; private final JwtUtil jwtUtil; private final ApplicationEventPublisher eventPublisher; private final PasswordUtil passwordUtil; private final OperationAuditLogService auditLogService; private final MembershipService membershipService; private final UserLevelService userLevelService; /** * 发送验证码 */ public void sendVerificationCode(SendCodeRequest request, String ip) { verificationCodeService.sendCode(request.getPhone(), request.getScene(), ip); } /** * 验证码登录/注册 * 如果手机号已注册则登录,否则自动注册 */ @Transactional public LoginResponse smsLogin(SmsLoginRequest request, String ip) { // 1. 验证验证码(兼容 scene=login 和 scene=register,统一用 login key) verificationCodeService.verifyCode(request.getPhone(), request.getCode()); // 2. 查找或创建用户 User user = userMapper.selectOne( new LambdaQueryWrapper().eq(User::getPhone, request.getPhone())); boolean isNewUser = false; if (user == null) { isNewUser = true; // 自动注册新用户 user = User.builder() .phone(request.getPhone()) .nickname("用户" + request.getPhone().substring(7)) .membershipLevel(MembershipLevel.PLUS) .loginSource("SMS") .status(1) .build(); userMapper.insert(user); // 发布用户注册事件(邀请模块监听处理邀请码,赠送会员奖励) eventPublisher.publishEvent(new UserRegisteredEvent(this, user.getId(), request.getInviteCode())); // 普通会员(PLUS)免费开放,注册即拥有,无需额外发放 } // 3. 更新登录信息 user.setLastLoginTime(LocalDateTime.now()); user.setLastLoginIp(ip); userMapper.updateById(user); // 4. 生成Token LoginResponse response = buildLoginResponse(user); response.setUserId(user.getId()); response.setNewUser(isNewUser); // 记录审计日志 String roleCode = user.getRole() != null ? user.getRole().getCode() : "USER"; if (isNewUser) { auditLogService.logSuccess(user.getId(), user.getNickname(), roleCode, OperationType.USER_REGISTER, "USER:" + user.getId(), user.getId()); } auditLogService.logSuccess(user.getId(), user.getNickname(), roleCode, OperationType.USER_LOGIN, "USER:" + user.getId(), user.getId()); return response; } /** * 微信扫码登录 */ @Transactional public LoginResponse wechatLogin(WechatLoginRequest request, String ip) { // 1. 获取微信用户信息 WechatService.WechatUserInfo wechatInfo = wechatService.getUserInfo(request.getCode()); // 2. 通过UnionID查找用户 User user = null; if (wechatInfo.getUnionId() != null) { user = userMapper.selectOne( new LambdaQueryWrapper().eq(User::getWechatUnionId, wechatInfo.getUnionId())); } // 3. 如果用户存在且已绑定手机号,直接登录 if (user != null && user.getPhone() != null) { user.setLastLoginTime(LocalDateTime.now()); user.setLastLoginIp(ip); userMapper.updateById(user); return buildLoginResponse(user); } // 4. 如果用户存在但未绑定手机号,或新用户,返回需要绑定手机号 if (user == null) { // 创建新用户(未绑定手机号) user = User.builder() .wechatOpenId(wechatInfo.getOpenId()) .wechatUnionId(wechatInfo.getUnionId()) .nickname(wechatInfo.getNickname()) .avatar(wechatInfo.getHeadImgUrl()) .membershipLevel(MembershipLevel.PLUS) .loginSource("WECHAT") .status(1) .build(); userMapper.insert(user); } else { // 更新微信信息 user.setWechatOpenId(wechatInfo.getOpenId()); user.setWechatUnionId(wechatInfo.getUnionId()); if (user.getNickname() == null) { user.setNickname(wechatInfo.getNickname()); } if (user.getAvatar() == null) { user.setAvatar(wechatInfo.getHeadImgUrl()); } user.setLastLoginTime(LocalDateTime.now()); user.setLastLoginIp(ip); userMapper.updateById(user); } // 5. 生成临时Token,要求绑定手机号 String tempToken = jwtUtil.generateTempToken(wechatInfo.getOpenId(), wechatInfo.getUnionId()); return LoginResponse.builder() .needBindPhone(true) .tempToken(tempToken) .userInfo(convertToUserInfoResponse(user)) .build(); } /** * 微信绑定手机号 */ @Transactional public LoginResponse wechatBindPhone(WechatBindRequest request, String ip) { // 1. 验证临时Token Claims claims = jwtUtil.parseToken(request.getTempToken()); String tokenType = claims.get("type", String.class); if (!"temp".equals(tokenType)) { throw new BusinessException(ErrorCode.TOKEN_INVALID); } String openId = claims.get("openId", String.class); String unionId = claims.get("unionId", String.class); // 2. 验证验证码 verificationCodeService.verifyCode(request.getPhone(), request.getCode()); // 3. 检查手机号是否已被其他用户绑定 User existUser = userMapper.selectOne( new LambdaQueryWrapper().eq(User::getPhone, request.getPhone())); User wechatUser = userMapper.selectOne( new LambdaQueryWrapper().eq(User::getWechatOpenId, openId)); if (wechatUser == null) { throw new BusinessException(ErrorCode.WECHAT_LOGIN_FAILED); } if (existUser != null && !existUser.getId().equals(wechatUser.getId())) { // 手机号已被其他账号绑定,合并账号 existUser.setWechatOpenId(openId); existUser.setWechatUnionId(unionId); existUser.setLastLoginTime(LocalDateTime.now()); existUser.setLastLoginIp(ip); userMapper.updateById(existUser); // 删除旧的微信用户 userMapper.deleteById(wechatUser.getId()); wechatUser = existUser; } else if (existUser == null) { // 绑定手机号到微信用户(首次完成注册) wechatUser.setPhone(request.getPhone()); wechatUser.setLoginSource("WECHAT"); wechatUser.setLastLoginTime(LocalDateTime.now()); wechatUser.setLastLoginIp(ip); userMapper.updateById(wechatUser); // 发布用户注册事件,邀请模块可据此建立邀请关系并发放奖励 eventPublisher.publishEvent(new UserRegisteredEvent(this, wechatUser.getId(), request.getInviteCode())); // 普通会员(PLUS)免费开放,注册即拥有,无需额外发放 } // 4. 生成正式Token return buildLoginResponse(wechatUser); } /** * 获取用户信息 */ public UserInfoResponse getUserInfo(Long userId) { User user = userMapper.selectById(userId); if (user == null) { throw new BusinessException(ErrorCode.USER_NOT_FOUND); } return convertToUserInfoResponse(user); } /** * 刷新Token */ public LoginResponse refreshToken(String refreshToken) { // 验证刷新Token String tokenType = jwtUtil.getTokenType(refreshToken); if (!"refresh".equals(tokenType)) { throw new BusinessException(ErrorCode.REFRESH_TOKEN_INVALID); } Long userId = jwtUtil.getUserId(refreshToken); User user = userMapper.selectById(userId); if (user == null) { throw new BusinessException(ErrorCode.USER_NOT_FOUND); } return buildLoginResponse(user); } /** * 构建登录响应 */ private LoginResponse buildLoginResponse(User user) { String roleCode = user.getRole() != null ? user.getRole().getCode() : "USER"; String accessToken = jwtUtil.generateAccessToken(user.getId(), user.getPhone(), roleCode); String refreshToken = jwtUtil.generateRefreshToken(user.getId()); return LoginResponse.builder() .userId(user.getId()) .accessToken(accessToken) .refreshToken(refreshToken) .expiresIn(jwtUtil.getExpirationInSeconds()) .userInfo(convertToUserInfoResponse(user)) .needBindPhone(false) .newUser(false) .role(roleCode) .build(); } /** * 转换用户信息响应 */ private UserInfoResponse convertToUserInfoResponse(User user) { UserLevelResponse levelInfo = null; if (user != null && user.getId() != null) { try { levelInfo = userLevelService.getUserLevel(user.getId()); } catch (Exception e) { log.warn("获取用户等级信息失败: userId={}, error={}", user.getId(), e.getMessage()); } // 填充会员过期信息 if (levelInfo != null) { try { MembershipResponse membership = membershipService.getMyMembership(user.getId()); if (membership != null && membership.getEffectiveTo() != null) { levelInfo.setMembershipExpireAt(membership.getEffectiveTo()); long daysRemaining = ChronoUnit.DAYS.between(LocalDateTime.now(), membership.getEffectiveTo()); levelInfo.setMembershipDaysRemaining(daysRemaining > 0 ? (int) daysRemaining : 0); } else { // 永久会员或无会员记录 levelInfo.setMembershipExpireAt(null); levelInfo.setMembershipDaysRemaining(-1); } } catch (Exception e) { log.warn("获取用户会员信息失败: userId={}, error={}", user.getId(), e.getMessage()); } } } return UserInfoResponse.builder() .id(user.getId()) .phone(ValidateUtil.maskPhone(user.getPhone())) .nickname(user.getNickname()) .avatar(user.getAvatar()) .wechatBound(user.getWechatOpenId() != null) .levelInfo(levelInfo) .createTime(user.getCreateTime()) .loginSource(user.getLoginSource() != null ? user.getLoginSource() : "UNKNOWN") .pharmacyName(user.getPharmacyName()) .province(user.getProvince()) .city(user.getCity()) .district(user.getDistrict()) .role(user.getRole() != null ? user.getRole().getCode() : "USER") .membershipLevel(levelInfo != null ? levelInfo.getLevelCode() : "PLUS") .membershipLevelName(levelInfo != null ? levelInfo.getLevelName() : "普通会员") .build(); } /** * 密码登录 * 支持手机号或用户名登录:phone 和 username 至少填一个 */ @Transactional public LoginResponse passwordLogin(PasswordLoginRequest request, String ip) { // 1. 校验:phone 和 username 至少填一个 boolean hasPhone = request.getPhone() != null && !request.getPhone().isBlank(); boolean hasUsername = request.getUsername() != null && !request.getUsername().isBlank(); if (!hasPhone && !hasUsername) { throw new BusinessException(ErrorCode.PARAM_ERROR, "手机号和用户名至少填写一个"); } // 2. 查找用户(优先手机号,其次用户名/昵称) User user; if (hasPhone) { user = userMapper.selectOne( new LambdaQueryWrapper().eq(User::getPhone, request.getPhone())); } else { user = userMapper.selectOne( new LambdaQueryWrapper().eq(User::getNickname, request.getUsername())); } if (user == null) { throw new BusinessException(ErrorCode.USER_NOT_FOUND); } // 2. 检查是否设置了密码 if (user.getPassword() == null) { throw new BusinessException(ErrorCode.PASSWORD_NOT_SET); } // 3. 验证密码 if (!passwordUtil.matches(request.getPassword(), user.getPassword())) { throw new BusinessException(ErrorCode.PASSWORD_INCORRECT); } // 4. 检查用户状态 if (user.getStatus() != null && user.getStatus() == 0) { throw new BusinessException(ErrorCode.USER_DISABLED); } // 5. 更新登录信息 user.setLastLoginTime(LocalDateTime.now()); user.setLastLoginIp(ip); userMapper.updateById(user); // 6. 生成Token LoginResponse response = buildLoginResponse(user); String roleCode = user.getRole() != null ? user.getRole().getCode() : "USER"; auditLogService.logSuccess(user.getId(), user.getNickname(), roleCode, OperationType.USER_LOGIN, "USER:" + user.getId(), user.getId()); return response; } /** * 设置/修改密码 */ @Transactional public void setPassword(Long userId, SetPasswordRequest request) { User user = userMapper.selectById(userId); if (user == null) { throw new BusinessException(ErrorCode.USER_NOT_FOUND); } // 如果已有密码,需要验证旧密码 if (user.getPassword() != null && request.getOldPassword() != null) { if (!passwordUtil.matches(request.getOldPassword(), user.getPassword())) { throw new BusinessException(ErrorCode.OLD_PASSWORD_INCORRECT); } } else if (user.getPassword() != null) { throw new BusinessException(ErrorCode.OLD_PASSWORD_REQUIRED); } // 加密并更新密码 user.setPassword(passwordUtil.encode(request.getNewPassword())); userMapper.updateById(user); log.info("用户{}设置/修改密码成功", userId); String roleCode = user.getRole() != null ? user.getRole().getCode() : "USER"; auditLogService.logSuccess(userId, user.getNickname(), roleCode, OperationType.USER_UPDATE, "PASSWORD:" + userId, userId); } /** * 修改手机号 */ @Transactional public void changePhone(Long userId, ChangePhoneRequest request) { User user = userMapper.selectById(userId); if (user == null) { throw new BusinessException(ErrorCode.USER_NOT_FOUND); } // 1. 验证验证码(使用新手机号和change场景) verificationCodeService.verifyCode(request.getNewPhone(), request.getCode()); // 2. 检查新手机号是否已被其他用户占用 User existUser = userMapper.selectOne( new LambdaQueryWrapper().eq(User::getPhone, request.getNewPhone())); if (existUser != null && !existUser.getId().equals(userId)) { throw new BusinessException(ErrorCode.PHONE_ALREADY_EXISTS); } // 3. 更新手机号 String oldPhone = user.getPhone(); user.setPhone(request.getNewPhone()); userMapper.updateById(user); log.info("用户{}修改手机号成功,旧手机号:{},新手机号:{}", userId, oldPhone, request.getNewPhone()); String roleCode = user.getRole() != null ? user.getRole().getCode() : "USER"; auditLogService.logSuccess(userId, user.getNickname(), roleCode, OperationType.USER_UPDATE, "PHONE:" + userId, userId); } }