首页 > 其他分享 >如何从0到1设计积分系统?

如何从0到1设计积分系统?

时间:2023-06-11 23:04:34浏览次数:36  
标签:modifyCreditDto CommonResult 积分 系统 用户 return userCredit 设计


比如:淘宝、京东等各大电商平台,都有积分系统,各大社区系统也有积分系统,就连想在大城市中小学读书,都有个积分的说法。

在很多平台不叫积分,叫什么币,比如:金币、鱼币、喵币、京豆等。

信用卡有信用积分、加油卡也有加油积分、......

你也可以看看你用过的相关app、网站系统,基本上大多数都有这个积分的概念。

很多游戏中,每天登陆也会送各种各样

由此,我们能看出一个积分系统在各大平台中的重要性。

积分体系连接用户与产品,能够有效引导用户成长,将新用户培养成高价值用户。

前两天有同学和我聊,说电商项目中,能不能把营销系统和积分给拆分开。

经过一番探讨后,觉得把积分系统单独出来。

如何从0到1设计积分系统?_ci

核心功能

但是再大再小,都必须有下面四个核心功能:

  • 增加积分
  • 扣减积分
  • 查询用户当前积分
  • 查询积分明细列表

增加积分

很多平台,通过各种各样的运营策略来给用户添加积分,比如:每天登录系统增加积分、购买商品增加积分、看短视频15秒增加积分等。

积分还可以分类:

  • 换商品
  • 换优惠券
  • 换抽奖机会
  • ...

扣减积分

扣减积分,更多是说把积分兑换成商品、优惠券等,但也有积分到期了,需要扣减到期的积分。

查询用户当前积分

用户个人中心,通常都会展示自己当前积分。

查询积分明细列表

每次积分兑换商品了,会留下一条记录。每次积分兑换了抽奖,也会留下一条记录。....

用户可以通过个人中心查看自己积分变化情况。

代码实现

为了演示,这里就把积分项目单独出来,成一个web项目(我的电商项目里只是一个module,服务间调用用的是dubbo+nacos),但是这个项目里就简单是一个单体项目,项目结构如下:

如何从0到1设计积分系统?_初始化_02

整体业务实体关系(简单的演示):

如何从0到1设计积分系统?_IP_03

基于这个,我们来实现一版简单的积分系统。

如何从0到1设计积分系统?_ci_04

建两张表:用户积分表和积分明细表

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":"添加成功"}

再看看数据库变化:

如何从0到1设计积分系统?_IP_05

初始化成功了。

我们再来测试用户查看当前积分:

查询用户当前积分

请求url:IP:PORT/credit/user

如何从0到1设计积分系统?_ci_06

响应参数:{"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}}

再来看看两个表数据:

如何从0到1设计积分系统?_IP_07

如何从0到1设计积分系统?_ci_08


接下来,我们来测试给用扣减积分(把积分用来兑换商品、兑换优惠券等)。

扣减用户积分

前面可知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

如何从0到1设计积分系统?_初始化_09

响应参数:

{
 "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,到这里我们的用户积分明细查询也就搞定了。


标签:modifyCreditDto,CommonResult,积分,系统,用户,return,userCredit,设计
From: https://blog.51cto.com/u_11702014/6459233

相关文章

  • 【计算机组成原理期末总预习-自顶向下方法】第4-6章 指令系统,CPU,流水线
    第4-6章指令系统,CPU,流水线一、指令系统设计指令结构与设计思想操作码:指定操作类型(操作码长度:固定/可变)源操作数或其地址:一个或多个源操作数所在的地址(操作数来源:主(虚)存/寄存器/I/O端口/指令本身)结果的地址:产生的结果存放何处(目的操作数)(结果地址:主(虚)存/寄......
  • 苹果宣布最新操作系统:visionOS
    苹果宣布最新操作系统:visionOS来源:OSCHINA编辑: 局2023-06-0612:12:00 5今天凌晨,WWDC23全球开发者大会正式开幕。大会上,苹果展示了包括iOS17、iPadOS17、watchOS10和macOSSonoma在内的新系统。硬件方面,苹果发布了15英寸的MacBookAir、搭载M2U......
  • Zephyr配置系统保姆级上手教程
    本文结合代码演示讲解Zephyr配置系统,代码部分是基于聆思科技的CSK6011ANano开发板配套的SDK。主要分三个部分来讲解:第一部分:zephyr配置系统简介第二部分:介绍Kconfig的常用的基础语法第三部分:通过简单的示例工程演示应用配置zephyr配置系统简介构建流程来了解一下zephyr的一个配置......
  • 家居购买管理系统(含源码)
    家居购买管理系统源码地址:https://pan.baidu.com/s/1UWQTntNOIiJ5xlaby6R60Q?pwd=cwq6提取码:cwq6开发工具:IDEA2021负责模块:主要负责管理员的订单管理、商品管理和用户的订单管理、购物车管理、首页、登录功能、后端的接口设计和实现。技术选型:java+jsp+MySQL+servlet1.模拟......
  • nginx cache命中率设计
    nginx提供了$upstream_cache_status这个变量来显示缓存的状态,我们可以在配置中添加一个http头来显示这一状态,达到类似squid的效果。location/{proxy_redirectoff;proxy_set_headerHost$host;proxy_set_headerX-......
  • 软件设计原则(转)
    原文:https://mp.weixin.qq.com/s?__biz=MzA3NjU5ODU1Nw==&mid=2247484119&idx=1&sn=a05e38fc0f67e9de93cd44fe2647a6bb&chksm=9f5f9ea5a82817b373c45f39fc87706f29b64eb1f8db40a688a6726c49ffe07804269e73824a&scene=27自己的理解:开闭原则对类或函数,尽量用扩展代替修改,如果要增......
  • kafka的使用—系统保卫战
    前言最近有个需求,在不同的系统中做数据同步。我们是java+mysql、他们是c#+sqlserver。需求是sqlserver提出的,并且他们提出要实时,并且要我们主动推数据给他们。他们接口都提供好了,说要我们对数据库表操作的时候调用他们的接口把数据传他们。咋看没有什么事,不就是一个接口的调用么。......
  • Python使用Redis实现一个简单作业调度系统
        概述Redis作为内存数据库的一个典型代表,已经在很多应用场景中被使用,这里仅就Redis的pub/sub功能来说说怎样通过此功能来实现一个简单的作业调度系统。这里只是想展现一个简单的想法,所以还是有很多需要考虑的东西没有包括在这个例子中,比如错误处理,持久化等。下面是实现上......
  • Python使用multiprocessing实现一个最简单的分布式作业调度系统
    介绍Python的multiprocessing模块不但支持多进程,其中managers子模块还支持把多进程分布到多台机器上。一个服务进程可以作为调度者,将任务分布到其他多个机器的多个进程中,依靠网络通信。想到这,就在想是不是可以使用此模块来实现一个简单的作业调度系统。实现Job首先创建一个Job类,为......
  • m基于FPGA的QPSK调制解调通信系统verilog实现,包含testbench,不包含载波同步
    1.算法仿真效果本系统进行了两个平台的开发,分别是:Vivado2019.2Quartusii18.0+ModelSim-Altera6.6dStarterEdition其中Vivado2019.2仿真结果如下:Quartusii18.0+ModelSim-Altera6.6dStarterEdition的测试结果如下:2.算法涉及理论知识概要QPSK是一种数字调制方式,它将......