浏览代码

注销重构,新增校验逻辑

zhangchen 7 小时之前
父节点
当前提交
a5ca19f58c

+ 1 - 1
alien-entity/src/main/java/shop/alien/entity/store/LifeUser.java

@@ -126,7 +126,7 @@ public class LifeUser implements Serializable {
     @TableField("invited_num")
     private String invitedNum;
 
-    @ApiModelProperty(value = "注销标记, 0:未注销, 1:已注销")
+    @ApiModelProperty(value = "注销标记, 0:未注销, 1:已注销, 2:注销冷静期")
     @TableField("logout_flag")
     private Integer logoutFlag;
 

+ 3 - 0
alien-entity/src/main/java/shop/alien/entity/store/vo/LifeUserVo.java

@@ -72,4 +72,7 @@ public class LifeUserVo extends LifeUser {
 
     @ApiModelProperty(value = "是否已设置登录密码")
     private Boolean hasLoginPassword;
+
+    @ApiModelProperty(value = "申请注销结束时间(logoutTime+7天),格式yyyy/mm/dd,未申请注销时为null")
+    private String logoutEndTime;
 }

+ 2 - 0
alien-gateway/src/main/java/shop/alien/gateway/service/LifeUserPasswordService.java

@@ -102,6 +102,8 @@ public class LifeUserPasswordService {
         userVo.setToken(token);
         addSessionToken(phoneNum, token);
         lifeUserService.addLifeUserLogInfo(user, macIp);
+        userVo.setHasLoginPassword(StringUtils.isNotBlank(user.getPassword()));
+        lifeUserService.fillLogoutEndTime(userVo, user);
         return userVo;
     }
 

+ 26 - 0
alien-gateway/src/main/java/shop/alien/gateway/service/LifeUserService.java

@@ -31,6 +31,7 @@ import shop.alien.mapper.second.SecondUserCreditRecordMapper;
 import shop.alien.util.common.JwtUtil;
 
 import java.time.LocalDateTime;
+import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
 import java.util.*;
 import java.util.stream.Collectors;
@@ -68,6 +69,29 @@ public class LifeUserService extends ServiceImpl<LifeUserGatewayMapper, LifeUser
     @Value("${jwt.expiration-time}")
     private String effectiveTime;
 
+    private static final DateTimeFormatter LOGOUT_END_TIME_FORMAT = DateTimeFormatter.ofPattern("yyyy/MM/dd");
+
+    /**
+     * 申请注销结束时间:logoutTime 起算第 7 天(T+0~T+7),与定时任务冷静期一致。
+     */
+    public static String resolveLogoutEndTime(Date logoutTime, Integer logoutFlag) {
+        if (logoutTime == null || logoutFlag == null || logoutFlag == 0) {
+            return null;
+        }
+        return logoutTime.toInstant()
+                .atZone(ZoneId.systemDefault())
+                .toLocalDate()
+                .plusDays(7)
+                .format(LOGOUT_END_TIME_FORMAT);
+    }
+
+    public void fillLogoutEndTime(LifeUserVo userVo, LifeUser user) {
+        if (userVo == null || user == null) {
+            return;
+        }
+        userVo.setLogoutEndTime(resolveLogoutEndTime(user.getLogoutTime(), user.getLogoutFlag()));
+    }
+
     public LifeUserVo userLogin(String phoneNum, String inviteCode, String macIp) {
         LifeUser user = getUserByPhone(phoneNum);
         if (user == null) {
@@ -93,6 +117,7 @@ public class LifeUserService extends ServiceImpl<LifeUserGatewayMapper, LifeUser
                 // 二手平台登录log,同一个macip登录多账号记录
                 addLifeUserLogInfo(user2, macIp);
                 userVo.setHasLoginPassword(StringUtils.isNotBlank(user2.getPassword()));
+                fillLogoutEndTime(userVo, user2);
 
                 return userVo;
             } else {
@@ -112,6 +137,7 @@ public class LifeUserService extends ServiceImpl<LifeUserGatewayMapper, LifeUser
             // 二手平台登录log,同一个macip登录多账号记录
             addLifeUserLogInfo(user, macIp);
             userVo.setHasLoginPassword(StringUtils.isNotBlank(user.getPassword()));
+            fillLogoutEndTime(userVo, user);
 
             return userVo;
         }

+ 29 - 3
alien-store/src/main/java/shop/alien/store/controller/LifeUserController.java

@@ -207,9 +207,35 @@ public class LifeUserController {
      */
     @ApiOperation("用户端撤回注销用户")
     @PostMapping("/liftCancelAccountUn")
-    public R<Boolean> liftCancelAccountUn(@RequestBody LifeUser user) {
-        log.info("StoreUserController.liftCancelAccountUn?LifeUser={}", user);
-        service.liftCancelAccountUn(user.getId());
+    public R<Boolean> liftCancelAccountUn(@RequestBody LifeUserVo user) {
+        log.info("LifeUserController.liftCancelAccountUn?LifeUserVo={}", user);
+        if (user == null || user.getId() == null) {
+            return R.fail("用户id不能为空");
+        }
+        LifeUser lifeUser = lifeUserMapper.selectById(user.getId());
+        if (lifeUser == null) {
+            return R.fail("用户不存在");
+        }
+        if (!StringUtils.hasText(lifeUser.getUserPhone())) {
+            return R.fail("用户手机号不存在");
+        }
+        if (!StringUtils.hasText(user.getVerificationCode())) {
+            return R.fail("验证码不能为空");
+        }
+        int code;
+        try {
+            code = Integer.parseInt(user.getVerificationCode().trim());
+        } catch (NumberFormatException e) {
+            return R.fail("验证码格式错误");
+        }
+        if (!aliSms.checkSmsCode(lifeUser.getUserPhone(), 0, 5, code)) {
+            return R.fail("验证码错误或已过期");
+        }
+        try {
+            service.liftCancelAccountUn(user.getId());
+        } catch (IllegalArgumentException e) {
+            return R.fail(e.getMessage());
+        }
         return R.success("撤回注销成功");
     }
 

+ 114 - 8
alien-store/src/main/java/shop/alien/store/service/LifeUserService.java

@@ -23,7 +23,9 @@ import org.springframework.transaction.annotation.Transactional;
 import org.springframework.util.CollectionUtils;
 import shop.alien.config.properties.RiskControlProperties;
 import shop.alien.entity.second.LifeUserLog;
+import shop.alien.entity.second.SecondGoods;
 import shop.alien.entity.second.SecondRiskControlRecord;
+import shop.alien.entity.second.enums.SecondGoodsStatusEnum;
 import shop.alien.entity.store.*;
 import shop.alien.entity.store.dto.LifeFansFollowOutcome;
 import shop.alien.entity.store.vo.LifeMessageVo;
@@ -31,8 +33,10 @@ import shop.alien.entity.store.vo.LifeUserVo;
 import shop.alien.entity.store.vo.WebSocketVo;
 import shop.alien.mapper.*;
 import shop.alien.mapper.second.LifeUserLogMapper;
+import shop.alien.mapper.second.SecondGoodsMapper;
 import shop.alien.mapper.second.SecondRiskControlRecordMapper;
 import shop.alien.mapper.second.SecondUserCreditMapper;
+import shop.alien.util.common.constant.LawyerStatusEnum;
 import shop.alien.store.config.BaseRedisService;
 import shop.alien.store.config.WebSocketProcess;
 import shop.alien.store.feign.SecondServiceFeign;
@@ -71,6 +75,28 @@ public class LifeUserService extends ServiceImpl<LifeUserMapper, LifeUser> {
     private static final Pattern LIFE_FANS_PAIR_ID_PATTERN = Pattern.compile("^(store|user)_[^\\s]{1,120}$");
     private static final int FAN_IDS_MAX_CHARS = 160;
 
+    /** 商家预订:待支付 / 待使用 / 商家预订 */
+    private static final List<Integer> PENDING_RESERVATION_ORDER_STATUSES =
+            Arrays.asList(0, 1, 8);
+
+    private static final String CANCEL_MSG_PENDING_RESERVATION =
+            "您有待消费的商家预订,请完成消费或取消预订后再申请注销。";
+    private static final String CANCEL_MSG_LISTED_SECOND_GOODS =
+            "您有上架的二手商品,请确认下架后再申请注销。";
+    private static final String CANCEL_MSG_CONSULT_IN_PROGRESS =
+            "您有进行中的法律咨询,请与律师确认服务完结后再申请注销。";
+    private static final String CANCEL_MSG_UNPAID_CONSULT_FEE =
+            "您有未结清的咨询费用,请完成支付后再申请注销。";
+    private static final String CANCEL_MSG_MULTIPLE_BLOCKERS =
+            "您有多项待办事项未处理,处理完成后即可申请注销。";
+    private static final String CANCEL_REVOKE_NOT_IN_COOLING_MSG =
+            "当前账号不在注销冷静期内,无法撤销";
+
+    /** C 端用户注销冷静期:logout_flag=2 */
+    private static final int LIFE_USER_LOGOUT_FLAG_COOLING = 2;
+
+    private static final int LOGOUT_COOLING_DAYS = 7;
+
     private final LifeUserMapper lifeUserMapper;
 
     private final LifeFansMapper lifeFansMapper;
@@ -115,6 +141,12 @@ public class LifeUserService extends ServiceImpl<LifeUserMapper, LifeUser> {
 
     private final TypeUtil typeUtil;
 
+    private final UserReservationOrderMapper userReservationOrderMapper;
+
+    private final SecondGoodsMapper secondGoodsMapper;
+
+    private final LawyerConsultationOrderMapper lawyerConsultationOrderMapper;
+
     @Autowired
     private RiskControlProperties riskControlProperties;
 
@@ -519,28 +551,102 @@ public class LifeUserService extends ServiceImpl<LifeUserMapper, LifeUser> {
         if (lifeUser == null) {
             throw new IllegalArgumentException("用户不存在");
         }
-        if (lifeUser.getLogoutFlag() != null && lifeUser.getLogoutFlag() == 1) {
+        if (lifeUser.getLogoutFlag() != null && lifeUser.getLogoutFlag() == 2) {
             throw new IllegalArgumentException("账号已在注销中");
         }
-        lifeUser.setLogoutFlag(1);
+        validateCancelAccountBlockers(user.getId());
+        // 设置成2,注销冷静期
+        lifeUser.setLogoutFlag(2);
         lifeUser.setLogoutReason(user.getLogoutReason());
         lifeUser.setLogoutTime(new Date());
         lifeUserMapper.updateById(lifeUser);
     }
 
+    /**
+     * 注销前业务校验:商家预订、二手上架、咨询进行中、咨询费用未结清。
+     * 单项不满足返回对应提示;多项不满足返回汇总提示。
+     */
+    private void validateCancelAccountBlockers(Integer userId) {
+        List<String> blockers = new ArrayList<>();
+
+        long pendingReservationCount = userReservationOrderMapper.selectCount(
+                new LambdaQueryWrapper<UserReservationOrder>()
+                        .eq(UserReservationOrder::getUserId, userId)
+                        .in(UserReservationOrder::getOrderStatus, PENDING_RESERVATION_ORDER_STATUSES));
+        if (pendingReservationCount > 0) {
+            blockers.add(CANCEL_MSG_PENDING_RESERVATION);
+        }
+
+        long listedGoodsCount = secondGoodsMapper.selectCount(
+                new LambdaQueryWrapper<SecondGoods>()
+                        .eq(SecondGoods::getUserId, userId)
+                        .eq(SecondGoods::getGoodsStatus, SecondGoodsStatusEnum.LISTED.getCode()));
+        if (listedGoodsCount > 0) {
+            blockers.add(CANCEL_MSG_LISTED_SECOND_GOODS);
+        }
+
+        long inProgressConsultCount = lawyerConsultationOrderMapper.selectCount(
+                new LambdaQueryWrapper<LawyerConsultationOrder>()
+                        .eq(LawyerConsultationOrder::getClientUserId, userId)
+                        .in(LawyerConsultationOrder::getOrderStatus,
+                                LawyerStatusEnum.WAIT_ACCEPT.getStatus(),
+                                LawyerStatusEnum.INPROGRESS.getStatus()));
+        if (inProgressConsultCount > 0) {
+            blockers.add(CANCEL_MSG_CONSULT_IN_PROGRESS);
+        }
+
+        long unpaidConsultCount = lawyerConsultationOrderMapper.selectCount(
+                new LambdaQueryWrapper<LawyerConsultationOrder>()
+                        .eq(LawyerConsultationOrder::getClientUserId, userId)
+                        .eq(LawyerConsultationOrder::getPaymentStatus, 0)
+                        .notIn(LawyerConsultationOrder::getOrderStatus,
+                                LawyerStatusEnum.CANCEL.getStatus(),
+                                LawyerStatusEnum.REFUNDED.getStatus(),LawyerStatusEnum.COMPLETE.getStatus()));
+        if (unpaidConsultCount > 0) {
+            blockers.add(CANCEL_MSG_UNPAID_CONSULT_FEE);
+        }
+
+        if (blockers.isEmpty()) {
+            return;
+        }
+        if (blockers.size() == 1) {
+            throw new IllegalArgumentException(blockers.get(0));
+        }
+        throw new IllegalArgumentException(CANCEL_MSG_MULTIPLE_BLOCKERS);
+    }
+
     public void liftCancelAccountUn(Integer id) {
-        LambdaQueryWrapper<LifeUser> lambdaQueryWrapper = new LambdaQueryWrapper<>();
-        lambdaQueryWrapper.eq(LifeUser::getId, id);
-        LifeUser lifeUser = lifeUserMapper.selectOne(lambdaQueryWrapper);
-        // 修改注销标记为0
+        if (id == null) {
+            throw new IllegalArgumentException("用户id不能为空");
+        }
+        LifeUser lifeUser = lifeUserMapper.selectById(id);
+        if (lifeUser == null) {
+            throw new IllegalArgumentException("用户不存在");
+        }
+        boolean logoutFlagCooling = lifeUser.getLogoutFlag() != null
+                && lifeUser.getLogoutFlag() == LIFE_USER_LOGOUT_FLAG_COOLING;
+        if (!logoutFlagCooling && !isLifeUserInLogoutCoolingPeriod(lifeUser)) {
+            throw new IllegalArgumentException(CANCEL_REVOKE_NOT_IN_COOLING_MSG);
+        }
         lifeUser.setLogoutFlag(0);
-        // 清空注销原因
         lifeUser.setLogoutReason(null);
-        // 清空注销申请时间
         lifeUser.setLogoutTime(null);
         lifeUserMapper.updateById(lifeUser);
     }
 
+    /**
+     * 是否在注销冷静期内(logoutTime 起 7 天内,含第 7 天当日)。
+     */
+    private static boolean isLifeUserInLogoutCoolingPeriod(LifeUser lifeUser) {
+        if (lifeUser == null || lifeUser.getLogoutTime() == null) {
+            return false;
+        }
+        Calendar calendar = Calendar.getInstance();
+        calendar.setTime(lifeUser.getLogoutTime());
+        calendar.add(Calendar.DAY_OF_YEAR, LOGOUT_COOLING_DAYS);
+        return !new Date().after(calendar.getTime());
+    }
+
     public List<String> getIds(String name, String phone) {
         if (Objects.isNull(name) && Objects.isNull(phone)) return Arrays.asList("");
         LambdaQueryWrapper<LifeUser> wrapper = new LambdaQueryWrapper<>();