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
* 如果用户已有同等级活跃会员,续期(叠加天数);
* 否则停用所有旧会员,创建新记录。
*/
@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
* 高级会员全部时长到期后,账号降级为免费普通会员(PLUS)。
*/
@Scheduled(cron = "0 */5 * * * ?")
@Transactional
public void expireMemberships() {
// 查找所有已过期但仍标记为有效的会员记录
List