package com.xuekairui.user.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.xuekairui.common.BusinessException; import com.xuekairui.common.ErrorCode; import com.xuekairui.user.dto.GrantMembershipRequest; import com.xuekairui.user.dto.MembershipBenefitResponse; import com.xuekairui.user.dto.MembershipResponse; import com.xuekairui.user.entity.MembershipBenefit; import com.xuekairui.user.entity.User; import com.xuekairui.user.entity.UserMembership; import com.xuekairui.user.enums.MembershipLevel; import com.xuekairui.user.enums.OperationType; import com.xuekairui.user.mapper.MembershipBenefitMapper; import com.xuekairui.user.mapper.UserMapper; import com.xuekairui.user.mapper.UserMembershipMapper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; /** * 会员体系服务 */ @Slf4j @Service @RequiredArgsConstructor public class MembershipService { private final UserMembershipMapper userMembershipMapper; private final MembershipBenefitMapper membershipBenefitMapper; private final UserMapper userMapper; private final OperationAuditLogService auditLogService; /** * 获取用户当前生效的会员信息 *

无活跃会员记录时,回退到用户表的当前等级(默认PLUS普通会员) */ public MembershipResponse getMyMembership(Long userId) { UserMembership membership = userMembershipMapper.selectActiveByUserId(userId); if (membership != null) { return toResponse(membership); } // 无活跃会员记录,默认为免费普通会员(PLUS) return MembershipResponse.builder() .userId(userId) .level(MembershipLevel.PLUS.getCode()) .levelName(MembershipLevel.PLUS.getDescription()) .source("SYSTEM") .effectiveFrom(null) .effectiveTo(null) .status(1) .remark("免费会员") .build(); } /** * 查询用户会员历史记录 */ public List getUserMembershipHistory(Long userId) { List list = userMembershipMapper.selectListByUserId(userId); return list.stream() .map(this::toResponse) .collect(Collectors.toList()); } /** * 查询指定会员等级的权益列表 */ public List getBenefits(String levelCode) { MembershipLevel level = MembershipLevel.fromCode(levelCode); if (level == null) { throw new BusinessException(ErrorCode.PARAM_ERROR, "会员等级无效"); } List benefits = membershipBenefitMapper.selectEnabledByLevel(level); return benefits.stream() .map(this::toBenefitResponse) .collect(Collectors.toList()); } /** * 发放/调整用户会员等级(运营/超管) */ @Transactional public MembershipResponse grantMembership(GrantMembershipRequest request, Long operatorId, String operatorRole) { User user = userMapper.selectById(request.getUserId()); if (user == null) { throw new BusinessException(ErrorCode.USER_NOT_FOUND); } MembershipLevel level = MembershipLevel.fromCode(request.getLevel()); if (level == null) { throw new BusinessException(ErrorCode.PARAM_ERROR, "会员等级无效"); } // 停用该用户其他有效会员记录 List activeList = userMembershipMapper.selectList( new LambdaQueryWrapper() .eq(UserMembership::getUserId, request.getUserId()) .eq(UserMembership::getStatus, 1)); for (UserMembership active : activeList) { active.setStatus(0); userMembershipMapper.updateById(active); } // 创建新会员记录 UserMembership membership = UserMembership.builder() .userId(request.getUserId()) .level(level) .source(request.getSource() != null ? request.getSource() : "ADMIN_GRANT") .effectiveFrom(request.getEffectiveFrom() != null ? request.getEffectiveFrom() : LocalDateTime.now()) .effectiveTo(request.getEffectiveTo()) .status(1) .remark(request.getRemark()) .build(); userMembershipMapper.insert(membership); // 同步到用户表当前会员等级与过期时间 user.setMembershipLevel(level); user.setMembershipExpireAt(membership.getEffectiveTo()); userMapper.updateById(user); // 审计日志 auditLogService.logSuccess(operatorId, "管理员", operatorRole, OperationType.USER_UPDATE, "MEMBERSHIP:" + request.getUserId() + "->" + level.getCode(), request.getUserId()); log.info("运营人员{}为用户{}发放会员等级{}", operatorId, request.getUserId(), level.getCode()); return toResponse(membership); } /** * 统一发放或续期会员(互斥模式) *

* 如果用户已有同等级活跃会员,续期(叠加天数); * 否则停用所有旧会员,创建新记录。 */ @Transactional public void grantOrExtendMembership(Long userId, MembershipLevel level, int days, String source, String remark) { User user = userMapper.selectById(userId); if (user == null) { return; } LocalDateTime newExpireAt; MembershipLevel effectiveLevel = level; UserMembership existing = userMembershipMapper.selectActiveByUserId(userId); if (existing != null) { if (existing.getLevel() == level) { // 同等级续期:在原到期时间基础上追加 LocalDateTime base = existing.getEffectiveTo() != null && existing.getEffectiveTo().isAfter(LocalDateTime.now()) ? existing.getEffectiveTo() : LocalDateTime.now(); existing.setEffectiveTo(base.plusDays(days)); userMembershipMapper.updateById(existing); newExpireAt = existing.getEffectiveTo(); } else if (existing.getLevel().getWeight() > level.getWeight()) { // 已有更高等级:不降级,直接续期更高等级(如ULTRA用户邀请奖励仍保持ULTRA) LocalDateTime base = existing.getEffectiveTo() != null && existing.getEffectiveTo().isAfter(LocalDateTime.now()) ? existing.getEffectiveTo() : LocalDateTime.now(); existing.setEffectiveTo(base.plusDays(days)); userMembershipMapper.updateById(existing); newExpireAt = existing.getEffectiveTo(); effectiveLevel = existing.getLevel(); } else { // 升级:停用所有低等级旧会员,创建新记录 deactivateAllActive(userId); LocalDateTime now = LocalDateTime.now(); UserMembership membership = UserMembership.builder() .userId(userId) .level(level) .source(source) .effectiveFrom(now) .effectiveTo(now.plusDays(days)) .status(1) .remark(remark) .build(); userMembershipMapper.insert(membership); newExpireAt = membership.getEffectiveTo(); } } else { // 无活跃会员:创建新记录 deactivateAllActive(userId); LocalDateTime now = LocalDateTime.now(); UserMembership membership = UserMembership.builder() .userId(userId) .level(level) .source(source) .effectiveFrom(now) .effectiveTo(now.plusDays(days)) .status(1) .remark(remark) .build(); userMembershipMapper.insert(membership); newExpireAt = membership.getEffectiveTo(); } // 同步用户表:等级 + 过期时间(仅当等级变化或过期时间变化时更新) boolean needUpdate = false; if (user.getMembershipLevel() == null || user.getMembershipLevel().getWeight() < effectiveLevel.getWeight()) { user.setMembershipLevel(effectiveLevel); needUpdate = true; } if (user.getMembershipExpireAt() == null || !user.getMembershipExpireAt().equals(newExpireAt)) { user.setMembershipExpireAt(newExpireAt); needUpdate = true; } if (needUpdate) { userMapper.updateById(user); } } /** * 邀请人奖励:赠送1个月(30天)高级会员(PRO) *

* 无论邀请人当前是什么等级,统一赠送高级会员时长。 * 同等级续期叠加,不同等级则停用旧会员并启用新的PRO会员。 */ @Transactional public void grantInviteRewardMembership(Long inviterId, Long inviteeId) { grantOrExtendMembership(inviterId, MembershipLevel.PRO, 30, "INVITE", "邀请奖励:邀请用户" + inviteeId + "注册"); log.info("邀请奖励:用户{}获得30天高级会员(PRO)", inviterId); } /** * 支付购买激活会员(互斥模式:同等级续期,不同等级替换) * @param userId 用户ID * @param level 会员等级 * @param durationDays 有效期天数(月卡30天,年卡365天) * @param orderNo 关联支付订单号 * @return 创建或续期的会员记录 */ @Transactional public UserMembership activateFromPayment(Long userId, MembershipLevel level, int durationDays, String orderNo) { User user = userMapper.selectById(userId); if (user == null) { throw new BusinessException(ErrorCode.USER_NOT_FOUND); } grantOrExtendMembership(userId, level, durationDays, "RECHARGE", "支付购买: " + orderNo); // 审计日志 auditLogService.logSuccess(userId, user.getNickname(), "USER", OperationType.USER_UPDATE, "PAY:" + orderNo + "->" + level.getCode(), userId); log.info("用户{}通过支付{}激活会员等级{}, 有效期{}天", userId, orderNo, level.getCode(), durationDays); // 返回当前活跃会员记录 return userMembershipMapper.selectActiveByUserId(userId); } /** * 分页查询会员记录 */ public Page listMemberships(Integer pageNum, Integer pageSize, Long userId, String level) { Page page = new Page<>(pageNum, pageSize); LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); if (userId != null) { wrapper.eq(UserMembership::getUserId, userId); } if (level != null && !level.isBlank()) { MembershipLevel membershipLevel = MembershipLevel.fromCode(level); if (membershipLevel != null) { wrapper.eq(UserMembership::getLevel, membershipLevel); } } wrapper.orderByDesc(UserMembership::getCreateTime); Page result = userMembershipMapper.selectPage(page, wrapper); Page responsePage = new Page<>(result.getCurrent(), result.getSize(), result.getTotal()); // 批量查询用户,避免N+1 List userIds = result.getRecords().stream() .map(UserMembership::getUserId) .distinct() .collect(Collectors.toList()); Map userMap = userIds.isEmpty() ? new HashMap<>() : userMapper.selectBatchIds(userIds).stream() .collect(Collectors.toMap(User::getId, u -> u)); responsePage.setRecords(result.getRecords().stream() .map(m -> toResponse(m, userMap.get(m.getUserId()))) .collect(Collectors.toList())); return responsePage; } private MembershipResponse toResponse(UserMembership membership) { if (membership == null) { return null; } User user = membership.getUserId() != null ? userMapper.selectById(membership.getUserId()) : null; return toResponse(membership, user); } /** * 停用用户所有活跃会员记录 */ private void deactivateAllActive(Long userId) { List activeList = userMembershipMapper.selectList( new LambdaQueryWrapper() .eq(UserMembership::getUserId, userId) .eq(UserMembership::getStatus, 1)); for (UserMembership active : activeList) { active.setStatus(0); userMembershipMapper.updateById(active); } } private MembershipResponse toResponse(UserMembership membership, User user) { if (membership == null) { return null; } MembershipLevel level = membership.getLevel(); return MembershipResponse.builder() .id(membership.getId()) .userId(membership.getUserId()) .phone(user != null ? user.getPhone() : null) .nickname(user != null ? user.getNickname() : null) .level(level != null ? level.getCode() : null) .levelName(level != null ? level.getDescription() : null) .source(membership.getSource()) .effectiveFrom(membership.getEffectiveFrom()) .effectiveTo(membership.getEffectiveTo()) .membershipExpireAt(membership.getEffectiveTo()) .status(membership.getStatus()) .remark(membership.getRemark()) .createTime(membership.getCreateTime()) .build(); } private MembershipBenefitResponse toBenefitResponse(MembershipBenefit benefit) { return MembershipBenefitResponse.builder() .benefitType(benefit.getBenefitType()) .benefitTypeName(toBenefitTypeName(benefit.getBenefitType())) .benefitValue(benefit.getBenefitValue()) .description(benefit.getDescription()) .build(); } private String toBenefitTypeName(String type) { return switch (type) { case "DAILY_QUOTA" -> "每日查询配额"; case "MONTHLY_QUOTA" -> "每月爬虫加成"; case "MAX_CONCURRENT" -> "最大并发加成"; default -> type; }; } /** * 定时任务:每5分钟检查过期会员,自动降级 *

* 高级会员全部时长到期后,账号降级为免费普通会员(PLUS)。 */ @Scheduled(cron = "0 */5 * * * ?") @Transactional public void expireMemberships() { // 查找所有已过期但仍标记为有效的会员记录 List expired = userMembershipMapper.selectList( new LambdaQueryWrapper() .eq(UserMembership::getStatus, 1) .isNotNull(UserMembership::getEffectiveTo) .lt(UserMembership::getEffectiveTo, LocalDateTime.now())); if (expired.isEmpty()) { return; } Set affectedUserIds = new HashSet<>(); for (UserMembership m : expired) { m.setStatus(0); userMembershipMapper.updateById(m); affectedUserIds.add(m.getUserId()); } // 对每个受影响的用户,检查是否还有其他有效会员 for (Long userId : affectedUserIds) { UserMembership active = userMembershipMapper.selectActiveByUserId(userId); if (active == null) { // 无有效会员,降级为普通会员 User user = userMapper.selectById(userId); if (user != null && user.getMembershipLevel() != MembershipLevel.PLUS) { user.setMembershipLevel(MembershipLevel.PLUS); user.setMembershipExpireAt(null); userMapper.updateById(user); log.info("用户{}会员已到期,降级为普通会员", userId); } } } log.info("处理过期会员记录: {}条,涉及用户: {}个", expired.size(), affectedUserIds.size()); } }