绑定完请刷新页面
取消
刷新

分享好友

×
取消 复制
实战:如果让你用SpringBoot实现签到奖励的功能,你会怎么做?
2020-11-03 09:27:53

前言


近在做社交业务,用户进入APP后有签到功能,签到成功后获取相应的奖励:
项目状况:前期尝试业务阶段;

特点:

  • 快速实现(不需要做太重,满足初期推广运营即可)

  • 快速投入市场去运营


用户签到:

  • 用户在每次启动时查询签到记录(规则:连续7日签到从0开始,签到过程中有断签从0开始)

  • 如果今日未签到则提示用户可以进行签到

  • 用户签到获取相应的奖励


提到签到,脑海中首先浮现特点:

  • 需要记录每位用户每天的签到情况

  • 查询时根据规则进行签到记录情况


需求&流程设计&技术实现方案


1.需求原型图


2.查询签到记录


3.进行签到


4.技术实现方案

  • SpringBoot

  • MySQL


数据库表结构


1.签到记录新表

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='用户签到表';

2.签到记录历史表

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='用户签到历史表';


代码实现


1.完整代码(GitHub,欢迎大家Star,Fork,Watch)

https://github.com/dangnianchuntian/springboot

2.主要代码展示

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;
@RestControllerpublic 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.*;
@Servicepublic 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(, 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()); 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锁防止并发操作

  • 后续会持续分享更多业务中的亮点


别忘记点个在看,咱们下篇见

每天进步一点点
慢一点才能更快
<END>
分享好友

分享这个小栈给你的朋友们,一起进步吧。

程序员的成长之路
创建时间:2020-05-11 14:30:09
每天分享Java技术栈干货,欢迎关注同名公众号,回复 1024,你懂的。
展开
订阅须知

• 所有用户可根据关注领域订阅专区或所有专区

• 付费订阅:虚拟交易,一经交易不退款;若特殊情况,可3日内客服咨询

• 专区发布评论属默认订阅所评论专区(除付费小栈外)

栈主、嘉宾

查看更多
  • 良月柒
    栈主

小栈成员

查看更多
  • 栈栈
  • ?
  • 小雨滴
  • 墨墨酱
戳我,来吐槽~