SpringBoot实战(十八):签到奖励实现方案(转载)

前言
最近在做社交业务,用户进入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 © 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 © 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锁防止并发操作
后续会持续分享更多业务中的亮点

原文链接:https://blog.csdn.net/zhanghan18333611647/article/details/108306434?utm_medium=distribute.pc_feed.none-task-blog-cf-3.nonecase&depth_1-utm_source=distribute.pc_feed.none-task-blog-cf-3.nonecase&request_id=5f4c61096fbae3485ef38a58

内容概要:本文档介绍了一款面向人工智能技术应用专业的“数据采集与数据处理教学平台”的设计与实现,旨在解决传统教学中硬件采集与软件处理割裂、爬虫教学流于表面、作业批改负担重、学习进度难追踪、教学资源分散五大痛点。平台采用前后端分离架构(Vue 3 + FastAPI + SQLite),融合ESP32硬件数据采集、四步骤引导式爬虫实训、AI智能评分、作业自动验证与学习进度可视化等核心功能,构建了“采集→传输→存储→处理→可视化”的完整数据闭环教学体系。通过GLM-4大模型实现四维度(功能、逻辑、代码、运行)自动化评分,3秒内反馈结果,大幅提升教学效率。; 适合人群:技工院校人工智能技术应用专业的学生、承担相关课程的教师以及从事职业教育信息化平台开发的技术人员。; 使用场景及目标:①支持18周全流程课程教学,覆盖Python基础、传感器采集、网络爬虫、数据处理与可视化等环节;②实现学生与ESP32设备绑定,开展真实数据采集实训;③通过四步骤爬虫工作台培养学生的代码编写与数据解析能力;④利用AI评分系统实现作业自动批改与即时反馈,减轻教师负担;⑤通过仪表盘实时追踪学生学习进度与班级整体表现。; 阅读建议:本文档适用于希望了解教育类平台系统设计与技术实现的教学开发者,建议结合架构图、数据库设计与核心代码片段进行深入研读,重点关注“四步骤爬虫”与“AI评分集成”的设计逻辑,同时注意开发过程中遇到的路由冲突、缓存问题、硬件兼容性等典型工程问题及其解决方案,具有较强的实践参考价值。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值