比如:淘宝、京东等各大电商平台,都有积分系统,各大社区系统也有积分系统,就连想在大城市中小学读书,都有个积分的说法。
在很多平台不叫积分,叫什么币,比如:金币、鱼币、喵币、京豆等。
信用卡有信用积分、加油卡也有加油积分、......
你也可以看看你用过的相关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
- *
- * 用户积分
- */
- @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
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
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
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
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
creditDetailList = creditDetailMapper.selectByUserId(userId, start, pageSize); - if (CollectionUtils.isEmpty(creditDetailList)) {
- return CommonResult.success(creditDetailList);
- }
-
- List
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,到这里我们的用户积分明细查询也就搞定了。
前期项目可能部署一台就够了,随着时间的增长,你会发现一台是应对不了,此时开始部署多台,既然部署多台,那就会面临很多分布式问题了。比如说:积分兑换商品时,此时需要扣减积分,可能正在扣除积分的同时又增加积分的业务出现,服务部署多台,那就会涉及到分布式锁了,不然这个积分很容易出现问题。
另外,积分明细表到后期了,这数据量肯定会越来越大,还会涉及到分库分表,然后有可能面临分布式事务的问题。
好了,今天的面经就分享到这里。
在线背面试八股文:http://woaijava.cc/mianshi/index
欢迎加入我的知识星球,做面试辅导、技术分享和技术指导,主要内容:
1、免费 修改简历
2、免费 模拟面试
3、免费 《java后端面试小抄-v3.0》
4、免费 使用刷题网站(已更新1046题)
5、免费 无限次数技术提问
6、共享 大量总结好的资料
7、共享源码分析系列(mybatis源码分析、dubbo源码分析、手写Spring IOC、AOP、MVC、手写RPC框架... )、项目实战(电商项目、医院项目、个人博客、OA系统)等
8、专门技术探讨群
扫下方二维码即可加入:

如果学生经济压力较大的,加我weixin:tj20120622 。可以获取优惠。
精彩文章