add 积分系统功能

This commit is contained in:
Guoqs
2025-12-24 15:41:11 +08:00
parent c2a1ae1966
commit e135db56b0
11 changed files with 917 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
package com.jsowell.pile.domain;
import java.math.BigDecimal;
import java.util.Date;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
/**
* 用户积分表
*/
@Data
@Accessors(chain = true)
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MemberPointsInfo {
/**
* 主键
*/
private Long id;
/**
* 用户ID
*/
private String memberId;
/**
* 当前可用积分
*/
private BigDecimal totalPoints;
/**
* 更新时间
*/
private Date updateTime;
}

View File

@@ -0,0 +1,59 @@
package com.jsowell.pile.domain;
import java.math.BigDecimal;
import java.util.Date;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
/**
* 积分流水表
*/
@Data
@Accessors(chain = true)
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MemberPointsRecord {
/**
* 主键
*/
private Long id;
/**
* 用户ID
*/
private String memberId;
/**
* 变动分值
*/
private BigDecimal points;
/**
* 类型1-充电奖励, 2-消费抵扣)
*/
private Integer type;
/**
* 关联订单号
*/
private String orderCode;
/**
* 变动前积分
*/
private BigDecimal beforePoints;
/**
* 变动后积分
*/
private BigDecimal afterPoints;
/**
* 创建时间
*/
private Date createTime;
}

View File

@@ -0,0 +1,57 @@
package com.jsowell.pile.mapper;
import com.jsowell.pile.domain.MemberPointsInfo;
import org.apache.ibatis.annotations.Param;
import java.math.BigDecimal;
/**
* 用户积分Mapper接口
*/
public interface MemberPointsInfoMapper {
/**
* 根据主键查询
*/
MemberPointsInfo selectByPrimaryKey(Long id);
/**
* 根据会员ID查询积分信息
*/
MemberPointsInfo selectByMemberId(@Param("memberId") String memberId);
/**
* 插入积分记录
*/
int insert(MemberPointsInfo record);
/**
* 选择性插入积分记录
*/
int insertSelective(MemberPointsInfo record);
/**
* 根据主键更新
*/
int updateByPrimaryKey(MemberPointsInfo record);
/**
* 根据主键选择性更新
*/
int updateByPrimaryKeySelective(MemberPointsInfo record);
/**
* 原子性增加积分
*/
int addPoints(@Param("memberId") String memberId, @Param("points") BigDecimal points);
/**
* 原子性扣减积分
*/
int deductPoints(@Param("memberId") String memberId, @Param("points") BigDecimal points);
/**
* 根据主键删除
*/
int deleteByPrimaryKey(Long id);
}

View File

@@ -0,0 +1,57 @@
package com.jsowell.pile.mapper;
import com.jsowell.pile.domain.MemberPointsRecord;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 积分流水Mapper接口
*/
public interface MemberPointsRecordMapper {
/**
* 根据主键查询
*/
MemberPointsRecord selectByPrimaryKey(Long id);
/**
* 根据会员ID查询积分流水列表
*/
List<MemberPointsRecord> selectByMemberId(@Param("memberId") String memberId);
/**
* 根据会员ID分页查询积分流水列表
*/
List<MemberPointsRecord> selectByMemberIdWithPage(@Param("memberId") String memberId);
/**
* 根据订单号查询积分流水
*/
List<MemberPointsRecord> selectByOrderCode(@Param("orderCode") String orderCode);
/**
* 插入积分流水记录
*/
int insert(MemberPointsRecord record);
/**
* 选择性插入积分流水记录
*/
int insertSelective(MemberPointsRecord record);
/**
* 根据主键更新
*/
int updateByPrimaryKey(MemberPointsRecord record);
/**
* 根据主键选择性更新
*/
int updateByPrimaryKeySelective(MemberPointsRecord record);
/**
* 根据主键删除
*/
int deleteByPrimaryKey(Long id);
}

View File

@@ -0,0 +1,65 @@
package com.jsowell.pile.service;
import com.jsowell.pile.domain.MemberPointsInfo;
import com.jsowell.pile.domain.MemberPointsRecord;
import java.math.BigDecimal;
import java.util.List;
/**
* 用户积分Service接口
*/
public interface MemberPointsInfoService {
/**
* 根据会员ID查询积分信息
*
* @param memberId 会员ID
* @return 积分信息
*/
MemberPointsInfo selectByMemberId(String memberId);
/**
* 获取会员积分余额
*
* @param memberId 会员ID
* @return 积分余额
*/
BigDecimal getPointsBalance(String memberId);
/**
* 增加积分(充电奖励)
*
* @param memberId 会员ID
* @param points 积分数量
* @param orderCode 关联订单号
* @return 操作结果
*/
boolean addPoints(String memberId, BigDecimal points, String orderCode);
/**
* 扣减积分(消费抵扣)
*
* @param memberId 会员ID
* @param points 积分数量
* @param orderCode 关联订单号
* @return 操作结果
*/
boolean deductPoints(String memberId, BigDecimal points, String orderCode);
/**
* 查询会员积分流水列表
*
* @param memberId 会员ID
* @return 积分流水列表
*/
List<MemberPointsRecord> getPointsRecordList(String memberId);
/**
* 初始化会员积分账户
*
* @param memberId 会员ID
* @return 操作结果
*/
int initMemberPoints(String memberId);
}

View File

@@ -0,0 +1,43 @@
package com.jsowell.pile.service;
import com.jsowell.pile.domain.MemberPointsRecord;
import java.util.List;
/**
* 积分流水Service接口
*/
public interface MemberPointsRecordService {
/**
* 根据主键查询
*
* @param id 主键
* @return 积分流水记录
*/
MemberPointsRecord selectByPrimaryKey(Long id);
/**
* 根据会员ID查询积分流水列表
*
* @param memberId 会员ID
* @return 积分流水列表
*/
List<MemberPointsRecord> selectByMemberId(String memberId);
/**
* 根据订单号查询积分流水
*
* @param orderCode 订单号
* @return 积分流水列表
*/
List<MemberPointsRecord> selectByOrderCode(String orderCode);
/**
* 插入积分流水记录
*
* @param record 积分流水记录
* @return 操作结果
*/
int insert(MemberPointsRecord record);
}

View File

@@ -0,0 +1,156 @@
package com.jsowell.pile.service.impl;
import com.jsowell.common.exception.BusinessException;
import com.jsowell.common.util.StringUtils;
import com.jsowell.pile.domain.MemberPointsInfo;
import com.jsowell.pile.domain.MemberPointsRecord;
import com.jsowell.pile.mapper.MemberPointsInfoMapper;
import com.jsowell.pile.mapper.MemberPointsRecordMapper;
import com.jsowell.pile.service.MemberPointsInfoService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
/**
* 用户积分Service实现类
*/
@Service
public class MemberPointsInfoServiceImpl implements MemberPointsInfoService {
private static final Logger logger = LoggerFactory.getLogger(MemberPointsInfoServiceImpl.class);
/**
* 积分类型:充电奖励
*/
private static final int POINTS_TYPE_CHARGE_REWARD = 1;
/**
* 积分类型:消费抵扣
*/
private static final int POINTS_TYPE_CONSUME_DEDUCT = 2;
@Resource
private MemberPointsInfoMapper memberPointsInfoMapper;
@Resource
private MemberPointsRecordMapper memberPointsRecordMapper;
@Override
public MemberPointsInfo selectByMemberId(String memberId) {
return memberPointsInfoMapper.selectByMemberId(memberId);
}
@Override
public BigDecimal getPointsBalance(String memberId) {
MemberPointsInfo pointsInfo = memberPointsInfoMapper.selectByMemberId(memberId);
if (pointsInfo == null) {
return BigDecimal.ZERO;
}
return pointsInfo.getTotalPoints() == null ? BigDecimal.ZERO : pointsInfo.getTotalPoints();
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean addPoints(String memberId, BigDecimal points, String orderCode) {
if (StringUtils.isBlank(memberId) || points == null || points.compareTo(BigDecimal.ZERO) <= 0) {
throw new BusinessException("参数错误");
}
// 检查积分账户是否存在,不存在则初始化
MemberPointsInfo pointsInfo = memberPointsInfoMapper.selectByMemberId(memberId);
if (pointsInfo == null) {
initMemberPoints(memberId);
pointsInfo = memberPointsInfoMapper.selectByMemberId(memberId);
}
// 记录变动前积分
BigDecimal beforePoints = pointsInfo.getTotalPoints() == null ? BigDecimal.ZERO : pointsInfo.getTotalPoints();
// 原子性增加积分
int updateResult = memberPointsInfoMapper.addPoints(memberId, points);
if (updateResult <= 0) {
logger.error("增加积分失败memberId: {}, points: {}", memberId, points);
throw new BusinessException("增加积分失败");
}
// 计算变动后积分
BigDecimal afterPoints = beforePoints.add(points);
// 插入积分流水记录
MemberPointsRecord record = MemberPointsRecord.builder()
.memberId(memberId)
.points(points)
.type(POINTS_TYPE_CHARGE_REWARD)
.orderCode(orderCode)
.beforePoints(beforePoints)
.afterPoints(afterPoints)
.createTime(new Date())
.build();
memberPointsRecordMapper.insert(record);
logger.info("增加积分成功memberId: {}, points: {}, beforePoints: {}, afterPoints: {}, orderCode: {}",
memberId, points, beforePoints, afterPoints, orderCode);
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deductPoints(String memberId, BigDecimal points, String orderCode) {
if (StringUtils.isBlank(memberId) || points == null || points.compareTo(BigDecimal.ZERO) <= 0) {
throw new BusinessException("参数错误");
}
// 检查积分余额是否充足
BigDecimal beforePoints = getPointsBalance(memberId);
if (beforePoints.compareTo(points) < 0) {
throw new BusinessException("积分余额不足");
}
// 原子性扣减积分
int updateResult = memberPointsInfoMapper.deductPoints(memberId, points);
if (updateResult <= 0) {
logger.error("扣减积分失败memberId: {}, points: {}", memberId, points);
throw new BusinessException("扣减积分失败");
}
// 计算变动后积分
BigDecimal afterPoints = beforePoints.subtract(points);
// 插入积分流水记录(扣减积分记录为负数)
MemberPointsRecord record = MemberPointsRecord.builder()
.memberId(memberId)
.points(points.negate())
.type(POINTS_TYPE_CONSUME_DEDUCT)
.orderCode(orderCode)
.beforePoints(beforePoints)
.afterPoints(afterPoints)
.createTime(new Date())
.build();
memberPointsRecordMapper.insert(record);
logger.info("扣减积分成功memberId: {}, points: {}, beforePoints: {}, afterPoints: {}, orderCode: {}",
memberId, points, beforePoints, afterPoints, orderCode);
return true;
}
@Override
public List<MemberPointsRecord> getPointsRecordList(String memberId) {
return memberPointsRecordMapper.selectByMemberIdWithPage(memberId);
}
@Override
public int initMemberPoints(String memberId) {
MemberPointsInfo pointsInfo = MemberPointsInfo.builder()
.memberId(memberId)
.totalPoints(BigDecimal.ZERO)
.updateTime(new Date())
.build();
return memberPointsInfoMapper.insert(pointsInfo);
}
}

View File

@@ -0,0 +1,39 @@
package com.jsowell.pile.service.impl;
import com.jsowell.pile.domain.MemberPointsRecord;
import com.jsowell.pile.mapper.MemberPointsRecordMapper;
import com.jsowell.pile.service.MemberPointsRecordService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
/**
* 积分流水Service实现类
*/
@Service
public class MemberPointsRecordServiceImpl implements MemberPointsRecordService {
@Resource
private MemberPointsRecordMapper memberPointsRecordMapper;
@Override
public MemberPointsRecord selectByPrimaryKey(Long id) {
return memberPointsRecordMapper.selectByPrimaryKey(id);
}
@Override
public List<MemberPointsRecord> selectByMemberId(String memberId) {
return memberPointsRecordMapper.selectByMemberId(memberId);
}
@Override
public List<MemberPointsRecord> selectByOrderCode(String orderCode) {
return memberPointsRecordMapper.selectByOrderCode(orderCode);
}
@Override
public int insert(MemberPointsRecord record) {
return memberPointsRecordMapper.insert(record);
}
}