强烈推荐一个大神的人工智能的教程:http://www.captainai.net/zhanghan
前言
最近在做社交业务,用户进入APP后有签到功能,签到成功后获取相应的奖励:
项目状况:前期尝试业务阶段;
特点:
- 快速实现(不需要做太重,满足初期推广运营即可)
- 快速投入市场去运营
用户签到:
- 用户在每次启动时查询签到记录(规则:连续7日签到从0开始,签到过程中有断签从0开始)
- 如果今日未签到则提示用户可以进行签到
- 用户签到获取相应的奖励
提到签到,脑海中首先浮现特点:
- 需要记录每位用户每天的签到情况
- 查询时根据规则进行签到记录情况
需求&流程设计&技术实现方案
- 需求原型图
- 查询签到记录
- 进行签到
- 技术实现方案
- SpringBoot
- MySQL
数据库表结构
- 签到记录最新表
CREATE TABLE `zh_sign_in` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `bu_no` varchar(32) DEFAULT NULL COMMENT '业务编码', `customer_id` varchar(32) DEFAULT NULL COMMENT '签到用户编码', `sign_in_date` datetime DEFAULT NULL COMMENT '签到日期(单位精确到日)', `reward_money` int(11) DEFAULT NULL COMMENT '本次签到奖励金币个数', `continuite_day` int(2) DEFAULT '1' COMMENT '连续签到天数(A:7天内如果有断签从0开始 B:7天签满从0开始)', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间', `param1` int(2) DEFAULT NULL COMMENT '预留字段1', `param2` int(4) DEFAULT NULL COMMENT '预留字段2', `param3` int(11) DEFAULT NULL COMMENT '预留字段3', `param4` varchar(20) DEFAULT NULL COMMENT '预留字段4', `param5` varchar(32) DEFAULT NULL COMMENT '预留字段5', `param6` varchar(64) DEFAULT NULL COMMENT '预留字段6', PRIMARY KEY (`id`) USING BTREE, UNIQUE KEY `uk_zh_sign_in_buno` (`bu_no`), UNIQUE KEY `uk_zh_sign_in_cid_signindate` (`customer_id`,`sign_in_date`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户签到表';
- 签到记录历史表
CREATE TABLE `zh_sign_in_hist` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `bu_no` varchar(32) DEFAULT NULL COMMENT '业务编码', `customer_id` varchar(32) DEFAULT NULL COMMENT '签到用户编码', `sign_in_date` datetime NULL DEFAULT NULL COMMENT '签到日期(单位精确到日)', `reward_money` int(11) DEFAULT NULL COMMENT '本次签到奖励金币个数', `continuite_day` int(2) DEFAULT '1' COMMENT '连续签到天数(A:7天内如果有断签从0开始 B:7天签满从0开始)', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间', `param1` int(2) DEFAULT NULL COMMENT '预留字段1', `param2` int(4) DEFAULT NULL COMMENT '预留字段2', `param3` int(11) DEFAULT NULL COMMENT '预留字段3', `param4` varchar(20) DEFAULT NULL COMMENT '预留字段4', `param5` varchar(32) DEFAULT NULL COMMENT '预留字段5', `param6` varchar(64) DEFAULT NULL COMMENT '预留字段6', PRIMARY KEY (`id`) USING BTREE, UNIQUE KEY `uk_zh_sign_in_hist_cid_signindate` (`customer_id`,`sign_in_date`) USING BTREE, KEY `key_zh_sign_in_hist_buno` (`bu_no`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户签到历史表';
代码实现
- 完整代码(GitHub,欢迎大家Star,Fork,Watch)
https://github.com/dangnianchuntian/springboot
- 主要代码展示
- Controller
/* * Copyright (c) 2020. zhanghan_java@163.com All Rights Reserved. * 项目名称:Spring Boot实战:签到奖励实现方案 * 类名称:SignInController.java * 创建人:张晗 * 联系方式:zhanghan_java@163.com * 开源地址: https://github.com/dangnianchuntian/springboot * 博客地址: https://zhanghan.blog.csdn.net */ package com.zhanghan.zhsignin.controller; import com.zhanghan.zhsignin.controller.request.PostSignInRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import com.zhanghan.zhsignin.controller.request.ListSignInDetailRequest; import com.zhanghan.zhsignin.service.SignInService; @RestController public class SignInController { @Autowired private SignInService signInService; /** * 查询签到记录 */ @RequestMapping(value = "/list/sign/in/detail", method = RequestMethod.POST) public Object listSignInDetail(@RequestBody @Validated ListSignInDetailRequest listSignInDetailRequest) { return signInService.listSignInDetail(listSignInDetailRequest); } /** * 用户进行签到 */ @RequestMapping(value = "/post/sign/in", method = RequestMethod.POST) public Object postSignIn(@RequestBody @Validated PostSignInRequest postSignInRequest) { return signInService.postSignIn(postSignInRequest); } }
- service
/* * Copyright (c) 2020. zhanghan_java@163.com All Rights Reserved. * 项目名称:Spring Boot实战:签到奖励实现方案 * 类名称:SignInServiceImpl.java * 创建人:张晗 * 联系方式:zhanghan_java@163.com * 开源地址: https://github.com/dangnianchuntian/springboot * 博客地址: https://zhanghan.blog.csdn.net */ package com.zhanghan.zhsignin.service.impl; import cn.hutool.core.util.IdUtil; import com.zhanghan.zhsignin.config.SignInRewardMoneyListConfig; import com.zhanghan.zhsignin.constant.SignInConstant; import com.zhanghan.zhsignin.controller.request.ListSignInDetailRequest; import com.zhanghan.zhsignin.controller.request.PostSignInRequest; import com.zhanghan.zhsignin.controller.response.ListSignInDetailResponse; import com.zhanghan.zhsignin.mybatis.entity.XZhSignInEntity; import com.zhanghan.zhsignin.mybatis.entity.XZhSignInHistEntity; import com.zhanghan.zhsignin.mybatis.mapper.XZhSignInHistMapper; import com.zhanghan.zhsignin.mybatis.mapper.XZhSignInMapper; import com.zhanghan.zhsignin.service.SignInService; import com.zhanghan.zhsignin.util.DateUtils; import com.zhanghan.zhsignin.util.wrapper.WrapMapper; import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; import java.util.Date; import java.util.List; import java.util.stream.Collectors; import static com.zhanghan.zhsignin.constant.SignInConstant.*; @Service public class SignInServiceImpl implements SignInService { @Autowired private XZhSignInMapper xZhSignInMapper; @Autowired private XZhSignInHistMapper xZhSignInHistMapper; //校验连续天数是否为7 @Value("#{T(java.lang.Integer).parseInt('${zh.sign.in.continuite.day.threshold:7}')}") public Integer continuiteDayThreshold; //签到奖励金币集合配置 @Autowired public SignInRewardMoneyListConfig signInRewardMoneyListConfig; /** * 查询用户签到记录 */ @Override public Object listSignInDetail(ListSignInDetailRequest listSignInDetailRequest) { //若配置文件中未配置签到奖励则不展示签到记录 List<Integer> signInRewardMoneyListConfigList = signInRewardMoneyListConfig.getList(); if (CollectionUtils.isEmpty(signInRewardMoneyListConfigList)) { return WrapMapper.ok(new ListSignInDetailResponse(false)); } String customerId = listSignInDetailRequest.getCustomerId(); XZhSignInEntity xZhSignInEntity = xZhSignInMapper.findByCustomerId(customerId); List<ListSignInDetailResponse.SignInDetail> signInDetailList = signInRewardMoneyListConfigList.stream().map(aa -> new ListSignInDetailResponse.SignInDetail(0, aa)).collect(Collectors.toList()); //该用户之前未签到过 if (null == xZhSignInEntity) { return WrapMapper.ok(new ListSignInDetailResponse(TODAY_NOT_SIGN_IN, SignInConstant.CONTINUITE_DAY_ZERO, signInDetailList)); } long signInDateTime = xZhSignInEntity.getSignInDate().getTime(); //最近一次签到是否为昨日之前 if (signInDateTime < DateUtils.getYesterdayDateTime()) { return WrapMapper.ok(new ListSignInDetailResponse(TODAY_NOT_SIGN_IN, SignInConstant.CONTINUITE_DAY_ZERO, signInDetailList)); } //最近一次签到是否为昨日 Integer todaySignStatus = TODAY_YES_SIGN_IN; Integer continuiteDay = xZhSignInEntity.getContinuiteDay(); if (signInDateTime < DateUtils.getTodayDateTime()) { //最近一次签到是昨日且之前已连续签到7日 if (continuiteDay >= continuiteDayThreshold) { return WrapMapper.ok(new ListSignInDetailResponse(TODAY_NOT_SIGN_IN, SignInConstant.CONTINUITE_DAY_ZERO, signInDetailList)); } //最近一次签到是昨日且之前连续未超7日 todaySignStatus = TODAY_NOT_SIGN_IN; } //查询用户签到历史记录 List<XZhSignInHistEntity> xZhSignInHistEntitieList = xZhSignInHistMapper.listByCustomerIdAndLimit(customerId, continuiteDay); for (XZhSignInHistEntity xZhSignInHistEntity : xZhSignInHistEntitieList) { ListSignInDetailResponse.SignInDetail signInDetail = new ListSignInDetailResponse.SignInDetail(TODAY_YES_SIGN_IN, xZhSignInHistEntity.getRewardMoney()); signInDetailList.remove(xZhSignInHistEntity.getContinuiteDay() - 1); signInDetailList.add(xZhSignInHistEntity.getContinuiteDay() - 1, signInDetail); } return WrapMapper.ok(new ListSignInDetailResponse(todaySignStatus, continuiteDay, signInDetailList)); } /** * 进行签到 */ @Override public Object postSignIn(PostSignInRequest postSignInRequest) { //若配置文件中未配置签到奖励则不展示签到记录 List<Integer> signInRewardMoneyListConfigList = signInRewardMoneyListConfig.getList(); if (CollectionUtils.isEmpty(signInRewardMoneyListConfigList)) { return WrapMapper.ok(); } //获取session用户对象 String customerId = postSignInRequest.getCustomerId(); //根据customerId查询用户签到记录 XZhSignInEntity xZhSignInEntityByCustomerId = xZhSignInMapper.findByCustomerId(customerId); //签到记录是否为空 if (null == xZhSignInEntityByCustomerId) { XZhSignInEntity xZhSignInEntity = new XZhSignInEntity(); xZhSignInEntity.setBuNo(IdUtil.simpleUUID()); xZhSignInEntity.setCustomerId(customerId); xZhSignInEntity.setContinuiteDay(CONTINUITE_DAY_ONE); xZhSignInEntity.setRewardMoney(signInRewardMoneyListConfigList.get(0)); xZhSignInEntity.setSignInDate(DateUtils.getTodayDate()); insertSigninAndHist(xZhSignInEntity); return WrapMapper.ok(); } long signInDateTime = xZhSignInEntityByCustomerId.getSignInDate().getTime(); if (signInDateTime == DateUtils.getTodayDateTime()) { return WrapMapper.error("今天已经签到"); } //获取连续签到天数 Integer continuiteDay = continuiteDay(xZhSignInEntityByCustomerId.getContinuiteDay(), signInDateTime); xZhSignInEntityByCustomerId.setSignInDate(DateUtils.getTodayDate()); xZhSignInEntityByCustomerId.setContinuiteDay(continuiteDay); xZhSignInEntityByCustomerId.setRewardMoney(signInRewardMoneyListConfigList.get(continuiteDay - 1)); xZhSignInEntityByCustomerId.setUpdateTime(new Date()); xZhSignInEntityByCustomerId.setBuNo(IdUtil.simpleUUID()); updateSignInAndInsertHist(xZhSignInEntityByCustomerId); return WrapMapper.ok(); } private Integer continuiteDay(Integer continuiteDay, Long signInDateTime) { if (signInDateTime < DateUtils.getYesterdayDateTime()) { return CONTINUITE_DAY_ONE; } if (continuiteDay >= continuiteDayThreshold) { return CONTINUITE_DAY_ONE; } return continuiteDay + 1; } private void insertSigninAndHist(XZhSignInEntity xZhSignInEntity) { xZhSignInMapper.insertSelective(xZhSignInEntity); XZhSignInHistEntity xZhSignInHistEntity = new XZhSignInHistEntity(); BeanUtils.copyProperties(xZhSignInEntity, xZhSignInHistEntity); xZhSignInHistEntity.setId(null); xZhSignInHistMapper.insertSelective(xZhSignInHistEntity); } private void updateSignInAndInsertHist(XZhSignInEntity xZhSignInEntity) { xZhSignInMapper.updateByPrimaryKeySelective(xZhSignInEntity); XZhSignInHistEntity xZhSignInHistEntity = new XZhSignInHistEntity(); BeanUtils.copyProperties(xZhSignInEntity, xZhSignInHistEntity); xZhSignInHistEntity.setId(null); xZhSignInHistMapper.insertSelective(xZhSignInHistEntity); } }
测试
- 模拟用户进行签到
- 进行请求
- 查看数据库结果
- 模拟用户查询签到记录
- 进行请求
总结
- 亮点:实现业务连续签到,断签以及奖励的业务
- 注意点:基于数据库查询做的,在进行签到接口需要用redis锁防止并发操作
- 后续会持续分享更多业务中的亮点