签到系统项目开发
项目开发
整个项目后端分为三个模块,
- 用户模块
- 签到模块
- 积分模块
快速搭建项目框架。
此部分请直接看视频和项目源码!此部分请直接看视频和项目源码!此部分请直接看视频和项目源码!
一、用户模块
如果公司已经存在独立的用户中心,则不需要开发这部分。直接通过 API/RPC 的方式接入即可。
本套课程为了项目演示方便,实现了一个简单的用户模块。
具体可参考 Go Web 进阶项目实战部分的用户模块设计。
用户注册
用户登录
鉴权
个人信息
二、签到模块
每日签到
签到日历
补签
三、积分模块
总积分
积分明细
四、签到提醒
每日签到提醒
可以在页面设置一个签到提醒开发,用户打开后,支持每天发Push或短信提醒用户签到。需要考虑短信的成本。
运营策略提醒
可以根据特定的运营策略实现签到提醒功能。
比如在每天 18:00 点对那些前两天签到了,但是今天还没有签到的用户发送即将断签提醒,引导用户及时完成签到,获得连续签到奖励。
用户量级比较大的时候,需要编写 Lua 脚本,并使用 script load 功能,提高执行效率。
-- KEYS[1]: 用户签到key
-- ARGV[1]: 当前日偏移量(从年初开始的天数)
-- ARGV[2]: 提醒阈值
local offset = tonumber(ARGV[1])
local threshold = tonumber(ARGV[2])
-- 检查今天是否已签到(最新位)
local today = redis.call('GETBIT', KEYS[1], offset)
if today == 1 then return 0 end -- 已签到无需提醒
-- 检查最近threshold天的签到情况
local continuous = true
for i = 1, threshold do
local bit = redis.call('GETBIT', KEYS[1], offset - i)
if bit ~= 1 then
continuous = false
break
end
end
-- 返回结果: 1需要提醒 0不需要
return continuous and 1 or 0在 Go 语言中使用 go:embed 将 lua 脚本编译到可执行文件中。
Goframe 版本
//go:embed remind.lua
var remindScript string
// CheckAndNotify 检查签到并发送通知
func CheckAndNotify(ctx context.Context, remindThreshold int) error {
// 1. 获取所有符合条件的用户
// 可以通过扫 MySQL 用户表找到最近几天有登录过的(如果有现成的用户登录时间记录可以直接拿来用)
// 或者可以在用户签到的时候记录一个 ZSet, userID:签到时间戳(如果用户量多需要拆分 Key)
userIDs := []uint64{25016147980058993}
rc := injection.MustInvoke[*redis.Client]()
// 2. 加载 lua script
sha, err := rc.ScriptLoad(ctx, remindScript).Result()
if err != nil {
fmt.Printf("ScriptLoad err: %v\n", err)
return err
}
// 3. 遍历判断每个用户
for _, userID := range userIDs {
key := fmt.Sprintf(yearSignKeyFormat, userID, time.Now().Year())
// 计算当前日偏移量(当年第几天)
dayOfYearOffset := time.Now().YearDay() - 1
fmt.Printf("key: %s, dayOfYearOffset: %d\n", key, dayOfYearOffset)
// 执行LUA脚本
result, err := rc.EvalSha(ctx, sha, []string{key}, dayOfYearOffset, 2).Int()
fmt.Printf("result: %d, err: %v\n", result, err)
if err != nil {
return err
}
if result == 1 {
fmt.Printf("用户%d需要发送断签提醒\n", userID)
// 发送到消息队列,执行后续的推送逻辑(APP Push 或 短信等)
}
}
return nil
}使用 goframe 框架的 gcron 实现定时任务。
// 开启定时任务
_, err = gcron.Add(ctx, "# 0 18 * * *", func(ctx context.Context) {
g.Log().Print(ctx, "每天18点跑定时任务")
err := impl.CheckAndNotify(ctx, 2)
fmt.Printf("CheckAndNotify err: %v\n", err)
})
if err != nil {
panic(err)
}Gin 版本
package task
import (
"context"
"fmt"
"sunflower-gin/internal/dao"
"time"
_ "embed"
)
const (
yearSignKeyFormat = "user:checkins:daily:%d:%d" // user:checkins:daily:12131321421312:2025
)
//go:embed remind.lua
var remindScript string
// CheckAndNotify 检查签到并发送通知
func CheckAndNotify(ctx context.Context, remindThreshold int) error {
fmt.Println("start check and notify...")
// 1. 获取所有符合条件的用户
// 可以通过扫 MySQL 用户表找到最近几天有登录过的(如果有现成的用户登录时间记录可以直接拿来用)
// 或者可以在用户签到的时候记录一个 ZSet, userID:签到时间戳(如果用户量多需要拆分 Key)
userIDs := []uint64{25016147980058993}
// 2. 加载 lua script
sha, err := dao.RedisClient.ScriptLoad(ctx, remindScript).Result()
if err != nil {
fmt.Printf("ScriptLoad err: %v\n", err)
return err
}
// 3. 遍历判断每个用户
now := time.Now()
for _, userID := range userIDs {
key := fmt.Sprintf(yearSignKeyFormat, userID, time.Now().Year())
// 计算当前日偏移量(当年第几天)
dayOfYearOffset := now.YearDay() - 1
fmt.Printf("key: %s, dayOfYearOffset: %d\n", key, dayOfYearOffset)
// 执行LUA脚本
result, err := dao.RedisClient.EvalSha(ctx, sha, []string{key}, dayOfYearOffset, remindThreshold).Int()
fmt.Printf("result: %d, err: %v\n", result, err)
if err != nil {
return err
}
if result == 1 {
fmt.Printf("用户%d需要发送断签提醒\n", userID)
// 发送到消息队列,执行后续的推送逻辑(APP Push 或 短信等)
}
}
return nil
}使用 github.com/robfig/cron/v3 执行定时任务。
// task.go
package task
import (
"context"
"time"
"github.com/robfig/cron/v3"
)
// 定时任务
/*
github.com/robfig/cron/v3 CRON 表达式格式
Field name | Mandatory? | Allowed values | Allowed special characters
---------- | ---------- | -------------- | --------------------------
Minutes | Yes | 0-59 | * / , -
Hours | Yes | 0-23 | * / , -
Day of month | Yes | 1-31 | * / , - ?
Month | Yes | 1-12 or JAN-DEC | * / , -
Day of week | Yes | 0-6 or SUN-SAT | * / , - ?
*/
func MustInit(ctx context.Context) *cron.Cron {
tz, err := time.LoadLocation("Local")
if err != nil {
panic(err)
}
c := cron.New(cron.WithLocation(tz))
c.AddFunc("25 20 * * *", func() { CheckAndNotify(ctx, 2) })
c.Start()
return c
}最后更新于 • Q1mi