diff --git a/xiaoyi-auth/src/main/java/top/crushtj/xiaoyishu/auth/cache/CacheLoader.java b/xiaoyi-auth/src/main/java/top/crushtj/xiaoyishu/auth/cache/CacheLoader.java index 74a2fc9..360f72d 100644 --- a/xiaoyi-auth/src/main/java/top/crushtj/xiaoyishu/auth/cache/CacheLoader.java +++ b/xiaoyi-auth/src/main/java/top/crushtj/xiaoyishu/auth/cache/CacheLoader.java @@ -10,6 +10,7 @@ import top.crushtj.xiaoyishu.auth.domain.entity.UserEntity; import top.crushtj.xiaoyishu.auth.domain.mappers.UserMapper; import static top.crushtj.xiaoyishu.auth.constant.RedisKeyConstants.XIAOYI_ID_GENERATOR_KEY; +import static top.crushtj.xiaoyishu.auth.constant.XiaoyiAuthConstants.XIAOYI_ID_INITIAL_VALUE; /** * @author ayi @@ -39,9 +40,9 @@ public class CacheLoader { queryWrapper.last("limit 1"); UserEntity user = userMapper.selectOne(queryWrapper); if (user != null){ - redisTemplate.opsForValue().set(XIAOYI_ID_GENERATOR_KEY, user.getXiaoyishuId()); + redisTemplate.opsForValue().set(XIAOYI_ID_GENERATOR_KEY, Long.valueOf(user.getXiaoyishuId())); }else { - redisTemplate.opsForValue().set(XIAOYI_ID_GENERATOR_KEY, 1000000L); + redisTemplate.opsForValue().set(XIAOYI_ID_GENERATOR_KEY, XIAOYI_ID_INITIAL_VALUE); } log.info("加载用户自增ID缓存结束..."); } diff --git a/xiaoyi-auth/src/main/java/top/crushtj/xiaoyishu/auth/constant/XiaoyiAuthConstants.java b/xiaoyi-auth/src/main/java/top/crushtj/xiaoyishu/auth/constant/XiaoyiAuthConstants.java index e015656..72e6bd9 100644 --- a/xiaoyi-auth/src/main/java/top/crushtj/xiaoyishu/auth/constant/XiaoyiAuthConstants.java +++ b/xiaoyi-auth/src/main/java/top/crushtj/xiaoyishu/auth/constant/XiaoyiAuthConstants.java @@ -10,4 +10,5 @@ package top.crushtj.xiaoyishu.auth.constant; public class XiaoyiAuthConstants { public static final String NICK_NAME_PREFIX = "咿呀_"; + public static final Long XIAOYI_ID_INITIAL_VALUE = 1000000L; } diff --git a/xiaoyi-auth/src/main/java/top/crushtj/xiaoyishu/auth/service/impl/UserServiceImpl.java b/xiaoyi-auth/src/main/java/top/crushtj/xiaoyishu/auth/service/impl/UserServiceImpl.java index d146f68..92b7746 100644 --- a/xiaoyi-auth/src/main/java/top/crushtj/xiaoyishu/auth/service/impl/UserServiceImpl.java +++ b/xiaoyi-auth/src/main/java/top/crushtj/xiaoyishu/auth/service/impl/UserServiceImpl.java @@ -8,7 +8,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionTemplate; import top.crushtj.framework.common.enums.DeleteEnum; import top.crushtj.framework.common.enums.StatusEnum; import top.crushtj.framework.common.exception.BizException; @@ -34,6 +34,7 @@ import java.util.Objects; import static top.crushtj.xiaoyishu.auth.constant.RedisKeyConstants.XIAOYI_ID_GENERATOR_KEY; import static top.crushtj.xiaoyishu.auth.constant.RoleConstants.COMMON_USER_ROLE_ID; import static top.crushtj.xiaoyishu.auth.constant.XiaoyiAuthConstants.NICK_NAME_PREFIX; +import static top.crushtj.xiaoyishu.auth.constant.XiaoyiAuthConstants.XIAOYI_ID_INITIAL_VALUE; /** * @author ayi @@ -53,6 +54,9 @@ public class UserServiceImpl extends ServiceImpl impleme @Resource private UserRoleRelMapper userRoleRelMapper; + @Resource + private TransactionTemplate transactionTemplate; + @Override public Response loginOrRegister(UserLoginReqVO userLoginReqVO) { String phone = userLoginReqVO.getPhone(); @@ -111,40 +115,61 @@ public class UserServiceImpl extends ServiceImpl impleme return Response.success(tokenInfo.tokenValue); } - @Transactional(rollbackFor = Exception.class) - public Long registerUser(String phone) { - long userId = IdGenerator.getInstance() - .nextId(); - Long xiaoyishuId = redisTemplate.opsForValue() - .increment(XIAOYI_ID_GENERATOR_KEY); - UserEntity userEntity = UserEntity.builder() - .id(userId) - .phone(phone) - .xiaoyishuId(String.valueOf(xiaoyishuId)) - .nickname(NICK_NAME_PREFIX + xiaoyishuId) - .status(StatusEnum.ENABLED.getValue()) - .createTime(LocalDateTime.now()) - .updateTime(LocalDateTime.now()) - .isDeleted(DeleteEnum.NO.getValue()) - .build(); - save(userEntity); + private Long registerUser(String phone) { + return transactionTemplate.execute(status -> { + try { + long userId = IdGenerator.getInstance() + .nextId(); + // 小壹书用户ID,非数据库主键ID + Long xiaoyishuId = redisTemplate.opsForValue() + .increment(XIAOYI_ID_GENERATOR_KEY); + UserEntity userEntity = UserEntity.builder() + .id(userId) + .phone(phone) + .xiaoyishuId(String.valueOf(xiaoyishuId)) + .nickname(NICK_NAME_PREFIX + xiaoyishuId) // 自动注册的用户昵称默认为"咿呀_" + 用户ID + .status(StatusEnum.ENABLED.getValue()) + .createTime(LocalDateTime.now()) + .updateTime(LocalDateTime.now()) + .isDeleted(DeleteEnum.NO.getValue()) + .build(); + save(userEntity); - UserRoleRelEntity userRoleRel = UserRoleRelEntity.builder() - .id(IdGenerator.getInstance() - .nextId()) - .userId(userId) - .roleId(COMMON_USER_ROLE_ID) - .createTime(LocalDateTime.now()) - .updateTime(LocalDateTime.now()) - .isDeleted(DeleteEnum.NO.getValue()) - .build(); - userRoleRelMapper.insert(userRoleRel); - List roles = new ArrayList<>(); - roles.add(COMMON_USER_ROLE_ID); - String userRolesKey = RedisKeyConstants.buildUserRolesKey(phone); - redisTemplate.opsForValue() - .set(userRolesKey, JsonUtils.toJsonString(roles)); - return userId; + UserRoleRelEntity userRoleRel = UserRoleRelEntity.builder() + .id(IdGenerator.getInstance() + .nextId()) + .userId(userId) + .roleId(COMMON_USER_ROLE_ID) // 自动注册的用户角色为普通用户 + .createTime(LocalDateTime.now()) + .updateTime(LocalDateTime.now()) + .isDeleted(DeleteEnum.NO.getValue()) + .build(); + int i = 1 / 0; + userRoleRelMapper.insert(userRoleRel); + + // 将用户角色信息存储到 Redis 中 + List roles = new ArrayList<>(); + roles.add(COMMON_USER_ROLE_ID); + String userRolesKey = RedisKeyConstants.buildUserRolesKey(phone); + redisTemplate.opsForValue() + .set(userRolesKey, JsonUtils.toJsonString(roles)); + return userId; + } catch (Exception e) { + // 回滚事务 + log.error("==> 用户注册异常,开始回滚事务: ", e); + status.setRollbackOnly(); + // 回滚 redis 自增ID + Long decrement = Long.valueOf(Objects.requireNonNull(redisTemplate.opsForValue() + .get(XIAOYI_ID_GENERATOR_KEY)) + .toString().trim()); + if (decrement.compareTo(XIAOYI_ID_INITIAL_VALUE) > 0) { + log.error("==> 用户注册异常,回滚redis自增ID: {}", decrement); + redisTemplate.opsForValue() + .decrement(XIAOYI_ID_GENERATOR_KEY); + } + } + return null; + }); } } diff --git a/xiaoyi-auth/src/main/java/top/crushtj/xiaoyishu/auth/service/impl/VerificationCodeServiceImpl.java b/xiaoyi-auth/src/main/java/top/crushtj/xiaoyishu/auth/service/impl/VerificationCodeServiceImpl.java index b56bf59..a367da5 100644 --- a/xiaoyi-auth/src/main/java/top/crushtj/xiaoyishu/auth/service/impl/VerificationCodeServiceImpl.java +++ b/xiaoyi-auth/src/main/java/top/crushtj/xiaoyishu/auth/service/impl/VerificationCodeServiceImpl.java @@ -15,10 +15,7 @@ import top.crushtj.xiaoyishu.auth.model.vo.verificationcode.SendVerificationCode import top.crushtj.xiaoyishu.auth.service.VerificationCodeService; import top.crushtj.xiaoyishu.auth.sms.AliyunSmsHelper; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; import static top.crushtj.xiaoyishu.auth.constant.RedisKeyConstants.VERIFICATION_CODE_EXPIRE_TIME; @@ -58,41 +55,41 @@ public class VerificationCodeServiceImpl implements VerificationCodeService { //=============== 开发环境不实际调用短信发送接口 =============== // 4. 异步发送短信(用CompletableFuture跟踪任务状态,捕获异常) - CompletableFuture smsSendFuture = CompletableFuture.supplyAsync(() -> { - // 设置线程名称,便于日志排查 - Thread.currentThread().setName("sms-send-" + MaskUtils.maskMobile(phoneNumber)); - String signName = "速通互联验证码"; - String templateCode = "100001"; - String templateParam = String.format("{\"code\":\"%s\",\"min\":\"%d\"}", verificationCode, VERIFICATION_CODE_EXPIRE_TIME); - try { - return aliyunSmsHelper.sendMessage(signName, templateCode, phoneNumber, templateParam); - } catch (Exception e) { - log.error("==> 手机号: {}, 短信发送接口调用异常", MaskUtils.maskMobile(phoneNumber), e); - return false; - } - }, taskExecutor); - - //5. 同步等待短信发送结果(超时控制,避免主线程阻塞过久) - boolean smsSendSuccess; - try { - smsSendSuccess = smsSendFuture.get(SMS_SEND_TIMEOUT_SECONDS, TimeUnit.SECONDS); - } catch (InterruptedException e) { - log.error("==> 手机号: {}, 短信发送任务被中断", MaskUtils.maskMobile(phoneNumber), e); - Thread.currentThread().interrupt(); // 恢复中断状态 - throw new BizException(ResponseCodeEnum.SMS_SEND_FAILED); - } catch (ExecutionException e) { - log.error("==> 手机号: {}, 短信发送任务执行异常", MaskUtils.maskMobile(phoneNumber), e); - throw new BizException(ResponseCodeEnum.SMS_SEND_FAILED); - } catch (TimeoutException e) { - log.error("==> 手机号: {}, 短信发送任务超时({}秒)", MaskUtils.maskMobile(phoneNumber), SMS_SEND_TIMEOUT_SECONDS, e); - throw new BizException(ResponseCodeEnum.SMS_SEND_TIMEOUT); - } - - // 6. 短信发送失败则直接抛异常,不存储Redis - if (!smsSendSuccess) { - log.error("==> 手机号: {}, 发送验证码失败(第三方接口返回失败)", MaskUtils.maskMobile(phoneNumber)); - throw new BizException(ResponseCodeEnum.SMS_SEND_FAILED); - } + //CompletableFuture smsSendFuture = CompletableFuture.supplyAsync(() -> { + // // 设置线程名称,便于日志排查 + // Thread.currentThread().setName("sms-send-" + MaskUtils.maskMobile(phoneNumber)); + // String signName = "速通互联验证码"; + // String templateCode = "100001"; + // String templateParam = String.format("{\"code\":\"%s\",\"min\":\"%d\"}", verificationCode, VERIFICATION_CODE_EXPIRE_TIME); + // try { + // return aliyunSmsHelper.sendMessage(signName, templateCode, phoneNumber, templateParam); + // } catch (Exception e) { + // log.error("==> 手机号: {}, 短信发送接口调用异常", MaskUtils.maskMobile(phoneNumber), e); + // return false; + // } + //}, taskExecutor); + // + // //5. 同步等待短信发送结果(超时控制,避免主线程阻塞过久) + //boolean smsSendSuccess; + //try { + // smsSendSuccess = smsSendFuture.get(SMS_SEND_TIMEOUT_SECONDS, TimeUnit.SECONDS); + //} catch (InterruptedException e) { + // log.error("==> 手机号: {}, 短信发送任务被中断", MaskUtils.maskMobile(phoneNumber), e); + // Thread.currentThread().interrupt(); // 恢复中断状态 + // throw new BizException(ResponseCodeEnum.SMS_SEND_FAILED); + //} catch (ExecutionException e) { + // log.error("==> 手机号: {}, 短信发送任务执行异常", MaskUtils.maskMobile(phoneNumber), e); + // throw new BizException(ResponseCodeEnum.SMS_SEND_FAILED); + //} catch (TimeoutException e) { + // log.error("==> 手机号: {}, 短信发送任务超时({}秒)", MaskUtils.maskMobile(phoneNumber), SMS_SEND_TIMEOUT_SECONDS, e); + // throw new BizException(ResponseCodeEnum.SMS_SEND_TIMEOUT); + //} + // + //// 6. 短信发送失败则直接抛异常,不存储Redis + //if (!smsSendSuccess) { + // log.error("==> 手机号: {}, 发送验证码失败(第三方接口返回失败)", MaskUtils.maskMobile(phoneNumber)); + // throw new BizException(ResponseCodeEnum.SMS_SEND_FAILED); + //} //=============== 开发环境不实际调用短信发送接口 =============== // 7. 短信发送成功后,记录日志(验证码脱敏,仅保留后2位)+ 存储Redis