完善注册功能事务管理
Sync All Branches to GitHub / sync (push) Successful in 2s

This commit is contained in:
2026-01-20 19:43:19 +08:00
parent b87abe0e3e
commit 37ba2a02ad
4 changed files with 98 additions and 74 deletions
@@ -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缓存结束...");
}
@@ -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;
}
@@ -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<UserMapper, UserEntity> impleme
@Resource
private UserRoleRelMapper userRoleRelMapper;
@Resource
private TransactionTemplate transactionTemplate;
@Override
public Response<String> loginOrRegister(UserLoginReqVO userLoginReqVO) {
String phone = userLoginReqVO.getPhone();
@@ -111,40 +115,61 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, UserEntity> 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<Long> 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<Long> 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;
});
}
}
@@ -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<Boolean> 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<Boolean> 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