package com.ruoyi.app.user; import com.ruoyi.common.constant.CacheConstants; import com.ruoyi.common.core.redis.RedisCache; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.utils.MessageUtils; import com.ruoyi.system.domain.InfoUser; import com.ruoyi.system.mapper.InfoUserMapper; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Date; /** 普通用户主动注销;调用登录恢复前必须已经完成短信或第三方身份验证。 */ @Service public class UserCancellationService { private final InfoUserMapper users; private final RedisCache redis; private final int graceDays; public UserCancellationService(InfoUserMapper users, RedisCache redis, @Value("${user.cancellation.grace-days:7}") int graceDays) { this.users = users; this.redis = redis; this.graceDays = graceDays > 0 && graceDays <= 365 ? graceDays : 7; } public record CancellationView( @com.fasterxml.jackson.annotation.JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") Date cancellationDeadlineAt, int graceDays) { } @Transactional(rollbackFor = Exception.class) public CancellationView request(Long userId) { // 与认证恢复共用数据库行锁;重复申请不能延长原截止时间。 InfoUser user = users.selectCancellationUserForUpdate(userId); requireUser(user); if (!"0".equals(user.getDelFlag())) throw unavailable(); Date deadline = Date.from(Instant.now().plus(graceDays, ChronoUnit.DAYS)); if (users.requestUserCancellation(userId, deadline) != 1) throw unavailable(); redis.deleteKeys(CacheConstants.USER_TOKEN_KEY + userId + ":*"); return new CancellationView(deadline, graceDays); } @Transactional(rollbackFor = Exception.class) public InfoUser authenticatedLogin(Long userId) { // 登录入口的外层事务持有此锁至签发新会话完成,防止并发注销覆盖恢复。 InfoUser user = users.selectCancellationUserForUpdate(userId); requireUser(user); if ("0".equals(user.getDelFlag())) return user; Date now = new Date(); if (!canRecover(user, now) || users.restoreCancelledUser(userId, now) != 1) throw unavailable(); user.setDelFlag("0"); user.setCancellationDeadlineAt(null); return user; } public static boolean canRecover(InfoUser user, Date now) { return user != null && "0".equals(user.getUserType()) && "0".equals(user.getStatus()) && "1".equals(user.getDelFlag()) && user.getCancellationDeadlineAt() != null && user.getCancellationDeadlineAt().after(now); } private void requireUser(InfoUser user) { if (user == null || !"0".equals(user.getUserType()) || !"0".equals(user.getStatus())) throw unavailable(); } private ServiceException unavailable() { return new ServiceException(MessageUtils.message("no.user.stop")); } }