比如:淘宝、京东等各大电商平台,都有积分系统,各大社区系统也有积分系统,就连想在大城市中小学读书,都有个积分的说法。
在很多平台不叫积分,叫什么币,比如:金币、鱼币、喵币、京豆等。
信用卡有信用积分、加油卡也有加油积分、......
你也可以看看你用过的相关app、网站系统,基本上大多数都有这个积分的概念。
很多游戏中,每天登陆也会送各种各样
由此,我们能看出一个积分系统在各大平台中的重要性。
积分体系连接用户与产品,能够有效引导用户成长,将新用户培养成高价值用户。
前两天有同学和我聊,说电商项目中,能不能把营销系统和积分给拆分开。
经过一番探讨后,觉得把积分系统单独出来。
核心功能
但是再大再小,都必须有下面四个核心功能:
- 增加积分
- 扣减积分
- 查询用户当前积分
- 查询积分明细列表
增加积分
很多平台,通过各种各样的运营策略来给用户添加积分,比如:每天登录系统增加积分、购买商品增加积分、看短视频15秒增加积分等。
积分还可以分类:
- 换商品
- 换优惠券
- 换抽奖机会
- ...
扣减积分
扣减积分,更多是说把积分兑换成商品、优惠券等,但也有积分到期了,需要扣减到期的积分。
查询用户当前积分
用户个人中心,通常都会展示自己当前积分。
查询积分明细列表
每次积分兑换商品了,会留下一条记录。每次积分兑换了抽奖,也会留下一条记录。....
用户可以通过个人中心查看自己积分变化情况。
代码实现
为了演示,这里就把积分项目单独出来,成一个web项目(我的电商项目里只是一个module,服务间调用用的是dubbo+nacos),但是这个项目里就简单是一个单体项目,项目结构如下:
整体业务实体关系(简单的演示):
基于这个,我们来实现一版简单的积分系统。
建两张表:用户积分表和积分明细表
CREATE TABLE `user_credit` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` int NOT NULL,
`credit` int NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
UNIQUE KEY `user_id_indx_uniq` (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE `credit_detail` (
`id` int NOT NULL AUTO_INCREMENT,
`user_id` int NOT NULL,
`type` int NOT NULL,
`number` int NOT NULL,
`order_no` varchar(255) NOT NULL,
`create_time` timestamp NOT NULL ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `order_no_index` (`order_no`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
mapper和entity这里就不展示了,这里我把controller和service贴出来:
controller层代码:
/**
* @author tianwc 公众号:java后端技术全栈、面试专栏
* @version 1.0.0
* @date 2022年11月27日 09:53
* <p>
* 用户积分
*/
@Slf4j
@Api(tags = "用户积分")
@Controller("/credit")
public class UserCreditController {
@Resource
private UserCreditService userCreditService;
@ApiOperation(value = "通过用户id获取用户当前积分", notes = "通过用户id获取用户当前积分")
@ApiImplicitParam(name = "userId", value = "用户id", dataType = "int ")
@ArgsLogAnnotation(methodDescription = "通过用户id获取用户积分")
@GetMapping("/user")
@ResponseBody
public CommonResult findByUserId(Integer userId) {
return userCreditService.findByUserId(userId);
}
@ApiOperation(value = "新增用户积分信息(初始化)", notes = "新增用户积分信息")
@ApiImplicitParam(name = "userCreditDto", value = "初始化用户积分账号", dataType = "object")
@ArgsLogAnnotation(methodDescription = "通过用户id获取用户积分")
@PostMapping("/init")
@ResponseBody
public CommonResult init(@RequestBody UserCreditDto userCreditDto) {
return userCreditService.initUserCredit(userCreditDto);
}
@ApiOperation(value = "增加用户积分", notes = "增加用户积分")
@ApiImplicitParam(name = "modifyCreditDto", value = "增加用户积分", dataType = "object ")
@ArgsLogAnnotation(methodDescription = "增加用户积分")
@PostMapping("/add")
@ResponseBody
public CommonResult addCredit(@RequestBody @Validated ModifyCreditDto modifyCreditDto) {
try {
return userCreditService.addCredit(modifyCreditDto);
} catch (Exception e) {
log.error("增加用户积分失败", e);
return CommonResult.failed("增加用户积分失败");
}
}
@ApiOperation(value = "扣减用户积分", notes = "扣减用户积分信息")
@ApiImplicitParam(name = "modifyCreditDto", value = "扣减积分信息", dataType = "object")
@ArgsLogAnnotation(methodDescription = "扣减用户积分")
@PostMapping("/reduce")
@ResponseBody
public CommonResult reduceCredit(@RequestBody @Validated ModifyCreditDto modifyCreditDto) {
try {
return userCreditService.addCredit(modifyCreditDto);
} catch (Exception e) {
log.error("扣减用户积分失败", e);
return CommonResult.failed("扣减用户积分失败");
}
}
@ApiOperation(value = "积分明细", notes = "通过用户id获取用户积分明细")
@ArgsLogAnnotation(methodDescription = "通过用户id获取用户积分")
@GetMapping("/detail/list")
@ResponseBody
public CommonResult findCreditDetailListByUserId(Integer userId, Integer start, Integer pageSize) {
return userCreditService.findUserCreditList(userId, start, pageSize);
}
}
当用户信息创建时,同时初始化用户积分信息,用户后面的各种积分操作就可以展开了。
service层代码实现:
/**
* @author tianwc 公众号:java后端技术全栈、面试专栏
* @version 1.0.0
* @date 2022年11月27日 11:05
*/
@Service
public class UserCreditServiceImpl implements UserCreditService {
@Resource
private UserCreditMapper userCreditMapper;
@Resource
private CreditDetailMapper creditDetailMapper;
@Override
public CommonResult<AddUserCreditDto> findByUserId(Integer userId) {
UserCredit userCredit = userCreditMapper.selectByUserId(userId);
if (userCredit != null) {
AddUserCreditDto addUserCreditDto = new AddUserCreditDto();
addUserCreditDto.setCredit(userCredit.getCredit());
addUserCreditDto.setId(userCredit.getId());
addUserCreditDto.setUserId(userCredit.getUserId());
return CommonResult.success(addUserCreditDto);
}
return CommonResult.failed("查询用户积分失败");
}
@Override
public CommonResult<String> initUserCredit(UserCreditDto userCreditDto) {
UserCredit userCredit = userCreditMapper.selectByUserId(userCreditDto.getUserId());
if (userCredit != null) {
return CommonResult.success("添加成功");
}
userCredit = new UserCredit();
userCredit.setUserId(userCreditDto.getUserId());
userCredit.setCredit(userCreditDto.getCredit());
int flag = userCreditMapper.insert(userCredit);
if (flag == 1) {
return CommonResult.success("添加成功");
}
return CommonResult.failed("添加失败");
}
/**
* 1、修改积分信息
* 2、新增明细
*
* @param modifyCreditDto 新增积分参数
* @return 添加成功 返回最新积分
*/
@Transactional(rollbackFor = Exception.class)
@Override
public CommonResult<UserCreditDto> addCredit(ModifyCreditDto modifyCreditDto) throws Exception {
UserCredit userCredit = userCreditMapper.selectByUserId(modifyCreditDto.getUserId());
if (userCredit == null) {
return CommonResult.failed("用户积分添加失败,userId=" + modifyCreditDto.getUserId() + " 有误");
}
userCredit.setCredit(userCredit.getCredit() + modifyCreditDto.getNumber());
//更新用户积分
if (checkUpdateCreditSuccess(userCredit)) {
//新增明细
if (checkAddCreditDetailSuccess(modifyCreditDto)) {
throw new Exception("新增积分明细失败");
}
}
UserCreditDto userCreditDto = new UserCreditDto();
userCreditDto.setUserId(modifyCreditDto.getUserId());
userCreditDto.setCredit(userCredit.getCredit());
return CommonResult.success(userCreditDto);
}
@Override
public CommonResult<UserCreditDto> reduceCredit(ModifyCreditDto modifyCreditDto) throws Exception {
UserCredit userCredit = userCreditMapper.selectByUserId(modifyCreditDto.getUserId());
if (userCredit == null) {
return CommonResult.failed("用户积分扣减失败,userId=" + modifyCreditDto.getUserId() + " 有误");
}
if (userCredit.getCredit() < modifyCreditDto.getNumber()) {
return CommonResult.failed("用户可用积分不足");
}
userCredit.setCredit(userCredit.getCredit() - modifyCreditDto.getNumber());
//更新用户积分
if (checkUpdateCreditSuccess(userCredit)) {
//新增明细
if (checkAddCreditDetailSuccess(modifyCreditDto)) {
throw new Exception("新增积分明细失败");
}
}
UserCreditDto userCreditDto = new UserCreditDto();
userCreditDto.setUserId(modifyCreditDto.getUserId());
userCreditDto.setCredit(userCredit.getCredit());
return CommonResult.success(userCreditDto);
}
@Override
public CommonResult findUserCreditList(Integer userId, Integer start, Integer pageSize) {
List<CreditDetail> creditDetailList = creditDetailMapper.selectByUserId(userId, start, pageSize);
if (CollectionUtils.isEmpty(creditDetailList)) {
return CommonResult.success(creditDetailList);
}
List<CreditDetailDto> creditDetailDtoList = new ArrayList<>();
for (CreditDetail creditDetail : creditDetailList) {
CreditDetailDto creditDetailDto = new CreditDetailDto();
creditDetailDto.setCreateTime(creditDetail.getCreateTime());
creditDetailDto.setNumber(creditDetail.getNumber());
creditDetailDto.setType(creditDetail.getType());
creditDetailDto.setOrderNo(creditDetail.getOrderNo());
creditDetailDtoList.add(creditDetailDto);
}
return CommonResult.success(creditDetailDtoList);
}
}
我们来测试一下:
测试
我们先来测试初始化用户积分,给用户初始化10个积分:
初始化用户积分
请求url:IP:PORT/credit/init
请求参数:{"userId":2,"credit":10}
相应参数:{"code":200,"message":"操作成功","data":"添加成功"}
再看看数据库变化:
初始化成功了。
我们再来测试用户查看当前积分:
查询用户当前积分
请求url:IP:PORT/credit/user
响应参数:{"code":200,"message":"操作成功","data":{"id":2,"userId":2,"credit":10}}
再来测试增加用户积分,给用户增加5个积分:
增加用户积分
请求url:IP:PORT/credit/add
请求参数:{"userId":2,"number":5,"orderNo":"ADD100001"}
响应参数:{"code":200,"message":"操作成功","data":{"userId":2,"credit":15}}
再来看看两个表数据:
接下来,我们来测试给用扣减积分(把积分用来兑换商品、兑换优惠券等)。
扣减用户积分
前面可知userId=2
的用户积分是15个,此时如果我们扣减20个积分明细是不够的。
我们来测试一把:
请求url:IP:PORT/credit/reduce
请求参数:{"userId":2,"number":20,"orderNo":"REDUCE100001"}
响应参数:{"code":500,"message":"用户可用积分不足","data":null}
既然不足,我们就把扣减积分减少:
请求参数:{"userId":2,"number":15,"orderNo":"REDUCE100001"}
响应参数:{"code":200,"message":"操作成功","data":{"userId":2,"credit":0}}
从响应参数可知,此时用户积分已用完。
我们再来查看用户积分明细:
查询用户积分明细
请求url:IP:PORT/credit/detail/list
响应参数:
{
"code": 200,
"message": "操作成功",
"data": [{
"ruleId": null,
"type": 0,
"number": 5,
"orderNo": "ADD100001",
"createTime": "2022-11-27T14:41:12.000+0000"
}, {
"ruleId": null,
"type": 0,
"number": 15,
"orderNo": "REDUCE100001",
"createTime": "2022-11-28T02:22:23.000+0000"
}]
}
OK,到这里我们的用户积分明细查询也就搞定了。