feat(memberCenter): 新增会员权益功能并优化会员积分逻辑

- 新增 MemberBenefit 类用于定义会员权益
- 创建 MemberBenefitMapper、MemberBenefitService 和 MemberBenefitServiceImpl
- 添加 MemberBenefitTypeEnum 枚举定义权益类型
- 新增会员等级和权益列表接口及实现
- 修改 Member 类,将积分类型改为 Double- 优化会员创建和积分充值逻辑
- 更新会员任务处理,重置积分逻辑
feature/comment
yang 2025-01-03 18:45:45 +08:00
parent 9014190840
commit 79bfa1a981
20 changed files with 406 additions and 52 deletions

View File

@ -4,10 +4,15 @@ import com.mcwl.common.core.domain.AjaxResult;
import com.mcwl.common.core.domain.entity.SysUser;
import com.mcwl.memberCenter.domain.MemberConsume;
import com.mcwl.memberCenter.domain.Member;
import com.mcwl.memberCenter.dto.UserMemberDto;
import com.mcwl.memberCenter.domain.MemberLevel;
import com.mcwl.memberCenter.service.MemberBenefitService;
import com.mcwl.memberCenter.service.MemberLevelService;
import com.mcwl.web.controller.memberCenter.pojo.dto.RechargePointsDto;
import com.mcwl.web.controller.memberCenter.pojo.dto.UserMemberDto;
import com.mcwl.memberCenter.service.MemberConsumeService;
import com.mcwl.memberCenter.service.MemberService;
import com.mcwl.system.service.ISysUserService;
import com.mcwl.web.controller.memberCenter.pojo.vo.PointsVO;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@ -24,6 +29,8 @@ public class MemberController {
private final MemberConsumeService memberConsumeService;
private final MemberBenefitService memberBenefitService;
/**
*
@ -46,14 +53,17 @@ public class MemberController {
return AjaxResult.warn("支付方式错误,请重新支付");
}
Member member = memberService.createUserMember(userId, memberLevelId, paymentMethod);
if (member == null) {
return AjaxResult.warn("创建会员失败");
}
return AjaxResult.success(member);
}
/**
*
*
* @param userId id
* @return
* @return
*/
@GetMapping("getPoints/{id}")
public AjaxResult getPoints(@PathVariable("id") Long userId) {
@ -66,18 +76,61 @@ public class MemberController {
return AjaxResult.warn("用户不存在");
}
List<Member> member = memberService.getUseUserMemberByUserId(userId);
if (member == null || member.isEmpty()) {
Member member = memberService.getUseUserMemberByUserId(userId);
if (member == null) {
return AjaxResult.warn("用户未开通会员");
}
PointsVO pointsVO = new PointsVO();
// 会员消费记录
List<MemberConsume> memberConsumeList = memberConsumeService
.lambdaQuery()
.eq(MemberConsume::getUserId, userId)
.list();
pointsVO.setPoints(member.getPoints());
pointsVO.setMemberConsumeList(memberConsumeList);
return AjaxResult.success(memberConsumeList);
return AjaxResult.success(pointsVO);
}
/**
*
*/
@PostMapping("rechargePoints")
public AjaxResult rechargePoints(@RequestBody RechargePointsDto rechargePointsDto) {
Long userId = rechargePointsDto.getUserId();
Double points = rechargePointsDto.getPoints();
if (userId == null) {
return AjaxResult.warn("用户未登录");
}
SysUser sysUser = sysUserService.selectUserById(userId);
if (sysUser == null) {
return AjaxResult.warn("用户不存在");
}
if (points == null) {
return AjaxResult.warn("充值积分为空");
}
if (points <= 0) {
return AjaxResult.warn("充值积分必须大于0");
}
Member member = memberService.rechargePoints(userId, points);
// 返回充值积分
if (member == null) {
return AjaxResult.warn("充值积分失败");
}
rechargePointsDto.setPoints(member.getPoints());
return AjaxResult.success(rechargePointsDto);
}

View File

@ -0,0 +1,63 @@
package com.mcwl.web.controller.memberCenter;
import com.mcwl.common.core.domain.AjaxResult;
import com.mcwl.memberCenter.domain.MemberBenefit;
import com.mcwl.memberCenter.domain.MemberLevel;
import com.mcwl.memberCenter.service.MemberBenefitService;
import com.mcwl.memberCenter.service.MemberLevelService;
import com.mcwl.web.controller.memberCenter.pojo.vo.MemberBenefitVO;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
@RestController
@RequestMapping("memberLevel")
@RequiredArgsConstructor
public class MemberLevelController {
private final MemberLevelService memberLevelService;
private final MemberBenefitService memberBenefitService;
/**
*
*
* @return
*/
@GetMapping("list")
public AjaxResult list() {
List<MemberLevel> memberLevelList = memberLevelService.list();
return AjaxResult.success(memberLevelList);
}
/**
*
*/
@GetMapping("getMemberBenefitList")
public AjaxResult getMemberBenefitList() {
List<MemberBenefitVO> memberBenefitVOList = new ArrayList<>();
List<MemberLevel> memberLevelList = memberLevelService.list();
for (MemberLevel memberLevel : memberLevelList) {
MemberBenefitVO memberBenefitVO = new MemberBenefitVO();
memberBenefitVO.setMemberLevel(memberLevel);
// 获取会员等级对应的权益
List<MemberBenefit> memberBenefitList = memberBenefitService.lambdaQuery()
.eq(MemberBenefit::getMemberLevelId, memberLevel.getId())
.list();
memberBenefitVO.setMemberBenefitList(memberBenefitList);
memberBenefitVOList.add(memberBenefitVO);
}
return AjaxResult.success(memberBenefitVOList);
}
}

View File

@ -0,0 +1,14 @@
package com.mcwl.web.controller.memberCenter.pojo.dto;
import lombok.Data;
@Data
public class RechargePointsDto {
// 用户ID
private Long userId;
// 充值积分
private Double points;
}

View File

@ -1,4 +1,4 @@
package com.mcwl.memberCenter.dto;
package com.mcwl.web.controller.memberCenter.pojo.dto;
import lombok.Data;

View File

@ -0,0 +1,20 @@
package com.mcwl.web.controller.memberCenter.pojo.vo;
import com.mcwl.memberCenter.domain.MemberBenefit;
import com.mcwl.memberCenter.domain.MemberLevel;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class MemberBenefitVO {
// 会员等级
private MemberLevel memberLevel;
// 会员权益
private List<MemberBenefit> memberBenefitList = new ArrayList<>();
}

View File

@ -0,0 +1,20 @@
package com.mcwl.web.controller.memberCenter.pojo.vo;
import com.mcwl.memberCenter.domain.MemberConsume;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
*
*/
@Data
public class PointsVO {
// 积分余额
private Double points;
// 积分消费记录
private List<MemberConsume> memberConsumeList = new ArrayList<>();
}

View File

@ -7,6 +7,7 @@ import com.mcwl.memberCenter.service.MemberLevelService;
import com.mcwl.memberCenter.service.MemberService;
import com.mcwl.memberCenter.task.UserMemberTask;
import com.mcwl.web.controller.memberCenter.MemberController;
import com.mcwl.web.controller.memberCenter.MemberLevelController;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
@ -15,7 +16,7 @@ import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest(classes = McWlApplication.class)
public class MemberLevelTest {
public class MemberCenterTest {
@Autowired
@ -30,6 +31,9 @@ public class MemberLevelTest {
@Autowired
private UserMemberTask userMemberTask;
@Autowired
private MemberLevelController memberLevelController;
@Autowired
private EmptyPointsRemindConsumer emptyPointsRemindConsumer;
@Test
@ -64,4 +68,9 @@ public class MemberLevelTest {
System.out.println("points = " + points);
}
@Test
public void getMemberLevelListTest() {
System.out.println("memberLevelController.getMemberBenefitList() = " + memberLevelController.getMemberBenefitList());
}
}

View File

@ -4,7 +4,7 @@ package com.mcwl.memberCenter.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.mcwl.common.core.domain.BaseEntity;
import com.mcwl.memberCenter.enums.MemberMenu;
import com.mcwl.memberCenter.enums.MemberEnum;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -31,10 +31,10 @@ public class Member extends BaseEntity {
private Date endDate;
// 会员积分
private Integer points;
private Double points;
// 订阅状态 active活跃连续包月、inactive非活跃不连续包月、pending待支付和expired过期
private MemberMenu subscriptionStatus;
private MemberEnum subscriptionStatus;
// 支付方式
private String paymentMethod;

View File

@ -0,0 +1,34 @@
package com.mcwl.memberCenter.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.mcwl.common.core.domain.BaseEntity;
import com.mcwl.memberCenter.enums.MemberBenefitTypeEnum;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("mem_member_benefit")
public class MemberBenefit extends BaseEntity {
@TableId
private Long id;
// 会员等级id
private Long memberLevelId;
// 权益名称
private String benefitName;
// 权益描述
private String benefitDesc;
// 权益类型
private MemberBenefitTypeEnum benefitType;
// 权益折扣 当权益类型为折扣时记录折扣的具体数值如0.9代表9折
private Double benefitDiscount;
}

View File

@ -20,10 +20,10 @@ public class MemberConsume extends BaseEntity {
private Long userId;
// 消费积分
private Integer consumePoints;
private Double consumePoints;
// 剩余积分
private Integer remainingPoints;
private Double remainingPoints;
// 消费时间
private Date consumeTime;

View File

@ -3,6 +3,7 @@ package com.mcwl.memberCenter.domain;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.mcwl.common.core.domain.BaseEntity;
import com.mcwl.memberCenter.enums.MemberPeriodicEnum;
import lombok.Data;
import lombok.EqualsAndHashCode;
@ -23,9 +24,6 @@ public class MemberLevel extends BaseEntity {
// 会员原价
private Double originalPrice;
// 会员积分
private Integer points;
// 订阅周期(天)
private Integer subscriptionPeriod;
// 订阅周期(年,季度,月,包月)
private MemberPeriodicEnum subscriptionPeriod;
}

View File

@ -0,0 +1,26 @@
package com.mcwl.memberCenter.enums;
import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum MemberBenefitTypeEnum {
DISCOUNT("discount", "折扣"),
POINTS("points", "积分"),
CASH("cash", "现金"),
FREE("free", "免费");
private final String name;
@EnumValue
private final String value;
}

View File

@ -6,7 +6,7 @@ import lombok.Getter;
@Getter
@AllArgsConstructor
public enum MemberMenu {
public enum MemberEnum {
MEMBER_CENTER_ACTIVE("active", "活跃"),
MEMBER_CENTER_INACTIVE("inactive", "非活跃"),
MEMBER_CENTER_PENDING("pending", "待支付"),

View File

@ -0,0 +1,19 @@
package com.mcwl.memberCenter.enums;
import com.baomidou.mybatisplus.annotation.EnumValue;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum MemberPeriodicEnum {
YEAR("year", "年"),
QUARTER("quarter", "季度"),
MONTH("month", "月"),
CONTINUE_MONTH("continueMonth", "包月");
private final String name;
@EnumValue
private final String value;
}

View File

@ -0,0 +1,9 @@
package com.mcwl.memberCenter.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.mcwl.memberCenter.domain.MemberBenefit;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface MemberBenefitMapper extends BaseMapper<MemberBenefit> {
}

View File

@ -0,0 +1,10 @@
package com.mcwl.memberCenter.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.mcwl.memberCenter.domain.MemberBenefit;
public interface MemberBenefitService extends IService<MemberBenefit> {
}

View File

@ -27,7 +27,8 @@ public interface MemberService extends IService<Member> {
* @param userId id
* @return
*/
List<Member> getUseUserMemberByUserId(Long userId);
Member getUseUserMemberByUserId(Long userId);
Member rechargePoints(Long userId, Double points);
}

View File

@ -0,0 +1,14 @@
package com.mcwl.memberCenter.service.impl;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.mcwl.memberCenter.domain.MemberBenefit;
import com.mcwl.memberCenter.mapper.MemberBenefitMapper;
import com.mcwl.memberCenter.service.MemberBenefitService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class MemberBenefitServiceImpl extends ServiceImpl<MemberBenefitMapper, MemberBenefit> implements MemberBenefitService {
}

View File

@ -4,16 +4,21 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.mcwl.common.core.domain.entity.SysUser;
import com.mcwl.common.exception.ServiceException;
import com.mcwl.memberCenter.domain.MemberBenefit;
import com.mcwl.memberCenter.domain.MemberLevel;
import com.mcwl.memberCenter.domain.Member;
import com.mcwl.memberCenter.enums.MemberMenu;
import com.mcwl.memberCenter.enums.MemberBenefitTypeEnum;
import com.mcwl.memberCenter.enums.MemberEnum;
import com.mcwl.memberCenter.enums.MemberPeriodicEnum;
import com.mcwl.memberCenter.mapper.MemberMapper;
import com.mcwl.memberCenter.service.MemberBenefitService;
import com.mcwl.memberCenter.service.MemberLevelService;
import com.mcwl.memberCenter.service.MemberService;
import com.mcwl.system.service.ISysUserService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import javax.validation.constraints.NotNull;
import java.util.*;
@Service
@ -24,27 +29,26 @@ public class MemberServiceImpl extends ServiceImpl<MemberMapper, Member> impleme
private final ISysUserService sysUserService;
private final MemberBenefitService memberBenefitService;
@Override
public Member createUserMember(Long userId, Long memberLevelId, String paymentMethod) {
if (userId == null) {
throw new ServiceException("用户不能为空");
return null;
}
SysUser user = sysUserService.selectUserById(userId);
if (user == null) {
throw new ServiceException("用户不存在");
}
if (memberLevelId == null) {
throw new ServiceException("会员等级不能为空");
if (user == null || memberLevelId == null) {
return null;
}
MemberLevel memberLevel = memberLevelService.getById(memberLevelId);
if (memberLevel == null) {
throw new ServiceException("会员等级不存在");
return null;
}
// 查询用户是否已经存在会员
LambdaQueryWrapper<Member> lqw = new LambdaQueryWrapper<>();
lqw.eq(Member::getUserId, userId)
// subscriptionStatus不为expired
.ne(Member::getSubscriptionStatus, MemberMenu.MEMBER_CENTER_EXPIRED)
.ne(Member::getSubscriptionStatus, MemberEnum.MEMBER_CENTER_EXPIRED)
.orderBy(true, false, Member::getEndDate);
List<Member> memberList = baseMapper.selectList(lqw);
Member member = new Member();
@ -56,29 +60,38 @@ public class MemberServiceImpl extends ServiceImpl<MemberMapper, Member> impleme
Member lastMember = memberList.get(0);
Date endDate = lastMember.getEndDate();
// 设置会员开始时间和结束时间
Date startDate = new Date(endDate.getTime() + 1000L * 60L * 60L * 24L);
member.setStartDate(startDate);
member.setEndDate(new Date(startDate.getTime() + memberLevel.getSubscriptionPeriod() * 1000L * 60L * 60L * 24L));
Calendar calendar = Calendar.getInstance();
calendar.setTime(endDate);
calendar.add(Calendar.DAY_OF_MONTH, 1);
member.setStartDate(calendar.getTime());
member.setEndDate(this.getEndDate(memberLevel.getSubscriptionPeriod(), calendar));
} else {
// 用户不存在会员
// 设置会员开始时间和结束时间
Date startDate = new Date();
member.setStartDate(startDate);
member.setEndDate(new Date(startDate.getTime() + memberLevel.getSubscriptionPeriod() * 1000L * 60L * 60L * 24L));
Calendar calendar = Calendar.getInstance();
member.setStartDate(calendar.getTime());
member.setEndDate(this.getEndDate(memberLevel.getSubscriptionPeriod(), calendar));
}
// 设置积分
member.setPoints(memberLevel.getPoints());
MemberBenefit memberBenefit = memberBenefitService.lambdaQuery()
.eq(MemberBenefit::getMemberLevelId, memberLevelId)
.eq(MemberBenefit::getBenefitType, MemberBenefitTypeEnum.POINTS)
.one();
if (memberBenefit != null) {
member.setPoints(memberBenefit.getBenefitDiscount());
}
// 设置订阅状态
if (memberLevel.getSubscriptionPeriod() == null) {
if (memberLevel.getSubscriptionPeriod() == MemberPeriodicEnum.CONTINUE_MONTH) {
// 连续包月,会员状态为活跃
member.setSubscriptionStatus(MemberMenu.MEMBER_CENTER_ACTIVE);
member.setSubscriptionStatus(MemberEnum.MEMBER_CENTER_ACTIVE);
Calendar calendar = Calendar.getInstance();
calendar.setTime(member.getEndDate());
calendar.add(Calendar.MONTH, 1);
member.setNextBillingDate(calendar.getTime());
} else {
member.setSubscriptionStatus(MemberMenu.MEMBER_CENTER_INACTIVE);
member.setSubscriptionStatus(MemberEnum.MEMBER_CENTER_INACTIVE);
}
// 设置支付时间
member.setLastPaymentDate(new Date());
@ -100,22 +113,73 @@ public class MemberServiceImpl extends ServiceImpl<MemberMapper, Member> impleme
@Override
public List<Member> getUseUserMember() {
return this.getUseUserMemberByUserId(null);
return this.getUseUserMember(null);
}
@Override
public List<Member> getUseUserMemberByUserId(Long userId) {
public Member getUseUserMemberByUserId(Long userId) {
List<Member> memberList = this.getUseUserMember(userId);
if (memberList == null || memberList.isEmpty()) {
return null;
}
return memberList.get(0);
}
@Override
public Member rechargePoints(Long userId, Double points) {
if (userId == null) {
return null;
}
if (points == null) {
return null;
}
Member member = getUseUserMemberByUserId(userId);
if (member != null) {
member.setPoints(member.getPoints() + points);
baseMapper.updateById(member);
return member;
}
return null;
}
private List<Member> getUseUserMember(Long userId) {
// startDate 小于等于当前时间、endDate 大于等于当前时间
// subscriptionStatus 不为 "过期" 或 "待支付"
// status 为 0 的
LambdaQueryWrapper<Member> qw = new LambdaQueryWrapper<>();
qw.le(Member::getStartDate, new Date())
.ge(Member::getEndDate, new Date())
.ne(Member::getSubscriptionStatus, MemberMenu.MEMBER_CENTER_EXPIRED)
.ne(Member::getSubscriptionStatus, MemberMenu.MEMBER_CENTER_PENDING)
.ne(Member::getSubscriptionStatus, MemberEnum.MEMBER_CENTER_EXPIRED)
.ne(Member::getSubscriptionStatus, MemberEnum.MEMBER_CENTER_PENDING)
.eq(Member::getStatus, "0")
.eq(userId != null, Member::getUserId, userId);
return baseMapper.selectList(qw);
}
/**
*
* @param subscriptionPeriod
* @param calendar
* @return
*/
private Date getEndDate(MemberPeriodicEnum subscriptionPeriod, Calendar calendar) {
switch (subscriptionPeriod) {
case YEAR:
calendar.add(Calendar.YEAR, 1);
break;
case QUARTER:
calendar.add(Calendar.MONTH, 3);
break;
case MONTH:
case CONTINUE_MONTH:
calendar.add(Calendar.MONTH, 1);
break;
default:
break;
}
return calendar.getTime();
}
}

View File

@ -3,7 +3,7 @@ package com.mcwl.memberCenter.task;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.mcwl.common.constant.QueueConstants;
import com.mcwl.memberCenter.domain.Member;
import com.mcwl.memberCenter.enums.MemberMenu;
import com.mcwl.memberCenter.enums.MemberEnum;
import com.mcwl.memberCenter.service.MemberService;
import lombok.RequiredArgsConstructor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
@ -47,7 +47,7 @@ public class UserMemberTask {
return;
}
for (Member member : memberList) {
member.setPoints(0);
member.setPoints(0.0);
member.setUpdateTime(new Date());
}
memberService.updateBatchById(memberList);
@ -60,8 +60,8 @@ public class UserMemberTask {
LambdaQueryWrapper<Member> qw = new LambdaQueryWrapper<>();
// endDate大于当前时间, subscriptionStatus不为过期
qw.gt(Member::getEndDate, System.currentTimeMillis())
.ne(Member::getSubscriptionStatus, MemberMenu.MEMBER_CENTER_EXPIRED)
.ne(Member::getSubscriptionStatus, MemberMenu.MEMBER_CENTER_PENDING);
.ne(Member::getSubscriptionStatus, MemberEnum.MEMBER_CENTER_EXPIRED)
.ne(Member::getSubscriptionStatus, MemberEnum.MEMBER_CENTER_PENDING);
List<Member> memberList = memberService.list(qw);
if (memberList == null || memberList.isEmpty()) {
return;
@ -70,10 +70,10 @@ public class UserMemberTask {
System.out.println("userMemberList = " + memberList);
for (Member member : memberList) {
MemberMenu subscriptionStatus = member.getSubscriptionStatus();
if (subscriptionStatus == MemberMenu.MEMBER_CENTER_ACTIVE) {
MemberEnum subscriptionStatus = member.getSubscriptionStatus();
if (subscriptionStatus == MemberEnum.MEMBER_CENTER_ACTIVE) {
// 如果subscriptionStatus是活跃的表示连续包月。订阅状态改为"待支付"
member.setSubscriptionStatus(MemberMenu.MEMBER_CENTER_PENDING);
member.setSubscriptionStatus(MemberEnum.MEMBER_CENTER_PENDING);
// nextBillingDate设置为当前会员结束日期的下个月的同一天,发送账单通知
Calendar calendar = Calendar.getInstance();
calendar.setTime(member.getEndDate());
@ -82,9 +82,9 @@ public class UserMemberTask {
memberService.updateById(member);
// 发送会员账单消息如果支付成功更新last_payment_date并重新计算end_datestart_date + 1个月
rabbitTemplate.convertAndSend(QueueConstants.MEMBER_BILLING_QUEUE, member);
} else if (subscriptionStatus == MemberMenu.MEMBER_CENTER_INACTIVE) {
} else if (subscriptionStatus == MemberEnum.MEMBER_CENTER_INACTIVE) {
// 不是连续包月,会员状态改为过期,状态改为"1"
member.setSubscriptionStatus(MemberMenu.MEMBER_CENTER_EXPIRED);
member.setSubscriptionStatus(MemberEnum.MEMBER_CENTER_EXPIRED);
member.setStatus("1");
member.setUpdateTime(new Date());
}