| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430 |
- 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<User>().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<User>().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<User>().eq(User::getPhone, request.getPhone()));
- User wechatUser = userMapper.selectOne(
- new LambdaQueryWrapper<User>().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<User>().eq(User::getPhone, request.getPhone()));
- } else {
- user = userMapper.selectOne(
- new LambdaQueryWrapper<User>().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<User>().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);
- }
- }
|