我来为你设计一个完整的积分系统案例,这个案例将涵盖数据库设计、核心功能实现和业务规则。

积分系统完整设计案例
需求分析
业务场景:电商平台的会员积分系统
核心功能:
- 积分获取(消费、签到、评价、推荐)
- 积分消耗(兑换商品、抵扣现金)
- 积分查询与管理
- 积分过期与调整
数据库设计
-- 积分账户表 CREATE TABLE `points_account` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL COMMENT '用户ID', `total_points` int(11) NOT NULL DEFAULT '0' COMMENT '总积分', `available_points` int(11) NOT NULL DEFAULT '0' COMMENT '可用积分', `frozen_points` int(11) NOT NULL DEFAULT '0' COMMENT '冻结积分', `level` varchar(20) DEFAULT 'NORMAL' COMMENT '会员等级', `version` int(11) DEFAULT '0' COMMENT '乐观锁版本号', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 积分流水表 CREATE TABLE `points_record` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL COMMENT '用户ID', `points` int(11) NOT NULL COMMENT '变动积分(正负)', `type` varchar(30) NOT NULL COMMENT '变动类型', `order_no` varchar(50) DEFAULT NULL COMMENT '关联订单号', `source` varchar(50) DEFAULT NULL COMMENT '来源', `balance` int(11) NOT NULL COMMENT '变动后余额', `expire_time` datetime DEFAULT NULL COMMENT '过期时间', `remark` varchar(255) DEFAULT NULL COMMENT '备注', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_user_id_create_time` (`user_id`, `create_time`), KEY `idx_order_no` (`order_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 积分规则表 CREATE TABLE `points_rule` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `rule_code` varchar(30) NOT NULL COMMENT '规则编码', `rule_name` varchar(100) NOT NULL COMMENT '规则名称', `points_value` int(11) NOT NULL COMMENT '积分值', `rule_type` varchar(20) NOT NULL COMMENT '规则类型', `status` tinyint(1) DEFAULT '1' COMMENT '是否启用', `start_time` datetime DEFAULT NULL COMMENT '生效时间', `end_time` datetime DEFAULT NULL COMMENT '失效时间', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_rule_code` (`rule_code`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; -- 积分商品兑换表 CREATE TABLE `points_exchange` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` bigint(20) NOT NULL, `goods_id` bigint(20) NOT NULL COMMENT '商品ID', `goods_name` varchar(100) NOT NULL, `cost_points` int(11) NOT NULL COMMENT '消耗积分', `exchange_no` varchar(50) NOT NULL COMMENT '兑换单号', `status` varchar(20) DEFAULT 'PENDING' COMMENT '状态', `consignee` varchar(50) DEFAULT NULL COMMENT '收货人', `phone` varchar(20) DEFAULT NULL, `address` varchar(200) DEFAULT NULL, `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_exchange_no` (`exchange_no`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
核心代码实现
// 积分服务接口
public interface PointsService {
// 增加积分
Result addPoints(PointsAddRequest request);
// 扣减积分
Result deductPoints(PointsDeductRequest request);
// 冻结积分(下单时)
Result freezePoints(Long userId, int points, String orderNo);
// 解冻积分(取消订单)
Result unfreezePoints(Long userId, String orderNo);
// 确认扣减(订单完成)
Result confirmDeduct(Long userId, String orderNo);
// 查询积分明细
PageResult<PointsRecord> queryRecords(Long userId, int page, int size);
// 兑换商品
Result exchangeGoods(ExchangeRequest request);
}
// 实现类
@Service
@Slf4j
public class PointsServiceImpl implements PointsService {
@Autowired
private PointsAccountMapper accountMapper;
@Autowired
private PointsRecordMapper recordMapper;
@Autowired
private PointsRuleMapper ruleMapper;
@Autowired
private PointsExchangeMapper exchangeMapper;
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Override
@Transactional(rollbackFor = Exception.class)
public Result addPoints(PointsAddRequest request) {
// 1. 校验请求
if (request.getPoints() <= 0) {
return Result.error("积分必须大于0");
}
// 2. 获取规则配置
PointsRule rule = ruleMapper.selectByCode(request.getRuleCode());
if (rule == null || rule.getStatus() != 1) {
return Result.error("积分规则不存在或已禁用");
}
// 3. 防止重复发放(根据订单号)
String lockKey = "points:add:" + request.getOrderNo();
Boolean notExist = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 5, TimeUnit.MINUTES);
if (!Boolean.TRUE.equals(notExist)) {
return Result.error("该订单已发放过积分");
}
try {
// 4. 乐观锁更新积分
PointsAccount account = accountMapper.selectByUserIdForUpdate(request.getUserId());
if (account == null) {
// 创建账户
account = createAccount(request.getUserId());
}
int updated = accountMapper.addPoints(
account.getId(),
request.getPoints(),
account.getVersion()
);
if (updated == 0) {
throw new BusinessException("更新积分失败");
}
// 5. 记录积分流水
PointsRecord record = new PointsRecord();
record.setUserId(request.getUserId());
record.setPoints(request.getPoints());
record.setType(request.getRuleCode());
record.setOrderNo(request.getOrderNo());
record.setBalance(account.getTotalPoints() + request.getPoints());
// 设置过期时间(一般有效期1年)
record.setExpireTime(LocalDateTime.now().plusYears(1));
record.setRemark(request.getRemark());
recordMapper.insert(record);
return Result.success("积分增加成功");
} catch (Exception e) {
log.error("增加积分失败", e);
throw new BusinessException("增加积分失败");
} finally {
redisTemplate.delete(lockKey);
}
}
@Override
@Transactional(rollbackFor = Exception.class)
public Result deductPoints(PointsDeductRequest request) {
// 1. 校验积分是否足够
PointsAccount account = accountMapper.selectByUserIdForUpdate(request.getUserId());
if (account.getAvailablePoints() < request.getPoints()) {
return Result.error("积分不足");
}
// 2. 扣减积分
int updated = accountMapper.deductPoints(
account.getId(),
request.getPoints()
);
if (updated == 0) {
throw new BusinessException("扣减积分失败");
}
// 3. 记录流水(负积分)
PointsRecord record = new PointsRecord();
record.setUserId(request.getUserId());
record.setPoints(-request.getPoints());
record.setType(request.getType());
record.setOrderNo(request.getOrderNo());
record.setBalance(account.getAvailablePoints() - request.getPoints());
record.setRemark(request.getRemark());
recordMapper.insert(record);
return Result.success("扣减成功");
}
@Override
public Result exchangeGoods(ExchangeRequest request) {
// 1. 查询商品信息
PointsGoods goods = goodsService.getById(request.getGoodsId());
if (goods == null) {
return Result.error("商品不存在");
}
// 2. 检查库存
if (goods.getStock() <= 0) {
return Result.error("商品库存不足");
}
// 3. 检查用户积分
PointsAccount account = accountMapper.selectByUserId(request.getUserId());
if (account.getAvailablePoints() < goods.getPoints()) {
return Result.error("积分不足");
}
// 4. 生成兑换单号
String exchangeNo = generateExchangeNo();
// 5. 创建兑换记录并扣减积分(事务)
this.doExchange(request, goods, exchangeNo);
return Result.success("兑换成功", exchangeNo);
}
}
积分规则配置案例
public enum PointsRuleEnum {
REGISTER("REGISTER", "注册奖励", 100),
DAILY_SIGN("DAILY_SIGN", "每日签到", 5),
PERFECT_INFO("PERFECT_INFO", "完善资料", 50),
FIRST_ORDER("FIRST_ORDER", "首次下单", 100),
CONSUME("CONSUME", "消费积分", 0), // 按消费金额比例
REVIEW("REVIEW", "评价奖励", 20),
SHARE("SHARE", "分享奖励", 30),
INVITE("INVITE", "邀请注册", 200);
private String code;
private String name;
private int defaultPoints;
// 消费积分规则:每消费1元得1积分
public static int calculateConsumePoints(BigDecimal amount) {
return amount.setScale(0, RoundingMode.DOWN).intValue();
}
// 会员等级倍率
public static double getLevelRate(String level) {
switch (level) {
case "SILVER": return 1.2;
case "GOLD": return 1.5;
case "DIAMOND": return 2.0;
default: return 1.0;
}
}
}
签到功能实现
@Service
public class SignInService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private PointsService pointsService;
// 签到奖励规则
private static final Map<Integer, Integer> SIGN_REWARDS = Maps.newHashMap();
static {
SIGN_REWARDS.put(1, 5);
SIGN_REWARDS.put(2, 6);
SIGN_REWARDS.put(3, 7);
SIGN_REWARDS.put(4, 8);
SIGN_REWARDS.put(5, 10);
SIGN_REWARDS.put(6, 12);
SIGN_REWARDS.put(7, 20); // 连续7天额外奖励
}
public Result dailySign(Long userId) {
String today = LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);
String key = "user:sign:" + userId + ":" + today;
// 1. 防止重复签到
Boolean signed = redisTemplate.opsForValue()
.setIfAbsent(key, "1", 24, TimeUnit.HOURS);
if (!Boolean.TRUE.equals(signed)) {
return Result.error("今日已签到");
}
// 2. 计算连续签到天数
int continuousDays = getContinuousDays(userId);
// 3. 获取签到奖励积分
int rewardPoints = SIGN_REWARDS.getOrDefault(continuousDays, 5);
// 4. 发放积分
PointsAddRequest request = new PointsAddRequest();
request.setUserId(userId);
request.setPoints(rewardPoints);
request.setRuleCode("DAILY_SIGN");
request.setRemark("第" + continuousDays + "天连续签到");
return pointsService.addPoints(request);
}
private int getContinuousDays(Long userId) {
// 使用BitMap存储签到状态
String signKey = "user:sign:bitmap:" + userId;
LocalDate today = LocalDate.now();
// 计算连续天数逻辑(省略具体实现)
int count = 0;
for (int i = 0; i < 7; i++) {
LocalDate date = today.minusDays(i);
Boolean hasSigned = redisTemplate.opsForValue()
.getBit(signKey, date.getDayOfMonth() - 1);
if (hasSigned) {
count++;
} else {
break;
}
}
return count;
}
}
积分过期处理
@Component
public class PointsExpireJob {
@Autowired
private PointsRecordMapper recordMapper;
@Autowired
private PointsAccountMapper accountMapper;
@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行
public void processExpirePoints() {
log.info("开始处理过期积分");
// 1. 查询已过期未处理的积分记录
List<PointsRecord> expireRecords = recordMapper
.selectExpiredRecords(LocalDateTime.now());
// 2. 按用户分组处理
Map<Long, List<PointsRecord>> groupByUser = expireRecords.stream()
.collect(Collectors.groupingBy(PointsRecord::getUserId));
for (Map.Entry<Long, List<PointsRecord>> entry : groupByUser.entrySet()) {
Long userId = entry.getKey();
List<PointsRecord> records = entry.getValue();
int totalExpire = records.stream()
.mapToInt(PointsRecord::getPoints)
.sum();
// 3. 更新账户积分
PointsAccount account = accountMapper.selectByUserIdForUpdate(userId);
int newAvailable = account.getAvailablePoints() - totalExpire;
// 4. 更新账户并记录过期流水
if (newAvailable >= 0) {
accountMapper.updateAvailablePoints(account.getId(), newAvailable);
recordMapper.insertExpireRecord(userId, -totalExpire);
}
}
log.info("过期积分处理完成,共处理{}条记录", expireRecords.size());
}
}
积分查询接口
@RestController
@RequestMapping("/api/points")
public class PointsController {
@Autowired
private PointsService pointsService;
@Autowired
private PointsQueryService queryService;
// 查询积分总览
@GetMapping("/summary")
public Result getPointsSummary(@RequestParam Long userId) {
PointsSummary summary = new PointsSummary();
// 获取账户信息
PointsAccount account = queryService.getAccountByUserId(userId);
summary.setTotalPoints(account.getTotalPoints());
summary.setAvailablePoints(account.getAvailablePoints());
summary.setFrozenPoints(account.getFrozenPoints());
// 本月获得积分
summary.setMonthEarn(recordMapper.getMonthEarnPoints(userId));
// 本月消耗积分
summary.setMonthSpend(recordMapper.getMonthSpendPoints(userId));
// 即将过期积分(30天内)
summary.setExpiringSoon(recordMapper.getExpiringPoints(userId, 30));
return Result.success(summary);
}
// 查询积分明细(带筛选)
@GetMapping("/records")
public Result getPointsRecords(
@RequestParam Long userId,
@RequestParam(required = false) String type,
@RequestParam(required = false) String startTime,
@RequestParam(required = false) String endTime,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "10") int size) {
PageResult<PointsRecord> result = queryService.queryRecords(
userId, type, startTime, endTime, page, size);
return Result.success(result);
}
}
前端页面展示
<!-- 积分管理页面 -->
<div class="points-page">
<!-- 积分总览卡片 -->
<div class="points-summary">
<div class="total-points">
<h3>当前积分</h3>
<span class="number" id="totalPoints">2,580</span>
</div>
<div class="points-statistics">
<div class="stat-item">
<label>本月获得</label>
<span>356</span>
</div>
<div class="stat-item">
<label>本月消耗</label>
<span>120</span>
</div>
<div class="stat-item" style="color: #e6a23c;">
<label>30天内过期</label>
<span>200</span>
</div>
</div>
</div>
<!-- 积分明细表格 -->
<div class="points-records">
<div class="filter-bar">
<select id="typeFilter">
<option value="">全部类型</option>
<option value="CONSUME">消费获取</option>
<option value="EXCHANGE">兑换消耗</option>
<option value="SIGN">签到</option>
</select>
<button onclick="loadRecords()">查询</button>
</div>
<table>
<thead>
<tr>
<th>时间</th>
<th>类型</th>
<th>金额变动</th>
<th>余额</th>
<th>说明</th>
</tr>
</thead>
<tbody id="recordsTable">
</tbody>
</table>
<div class="pagination" id="pagination"></div>
</div>
</div>
关键业务场景流程图
[消费场景]
用户下单(锁定积分) → 支付成功(+积分) → 订单完成(确认积分)
↓
未支付/取消订单 → 释放锁定积分
[积分兑换场景]
用户选择商品 → 验证积分 → 创建兑换单(扣减积分) → 配货发货
↓
取消兑换 → 退还积分(冻结中)
系统优化建议
-
性能优化:
- 使用Redis缓存积分明细
- 积分流水异步写入
- 定时批量处理过期积分
-
安全控制:
- 防止并发重复发放(分布式锁)
- 积分倍率控制
- 异常交易监控
-
业务扩展:
- 积分商城
- 积分转赠
- 积分抽奖
- 积分抵扣优惠券
这个案例涵盖了积分系统的主要功能模块,实际应用中还需要根据具体业务场景进行适当调整和扩展,如果需要详细实现某个具体功能,可以进一步深入探讨。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播