文章目录
- 需求说明
- [Prompt 相关(AI 辅助开发)](#Prompt 相关(AI 辅助开发))
- 后端技术方案设计
- [RESTful API](#RESTful API)
- 数据表设计
- [安装 Redis](#安装 Redis)
- [Redis Bitmap 介绍](#Redis Bitmap 介绍)
需求说明


一、功能定位
签到功能作为用户激励体系的核心组件,已成为互联网产品的标准配置。该功能巧妙连接了用户行为激励与运营活动,通过轻量级的交互方式实现多重价值:用户获得即时满足感与成就感,产品方则有效提升活跃度与用户粘性。
二、功能定义与表现形式
签到功能引导用户定期访问产品并完成特定操作(如点击按钮、访问页面)的机制。为增强趣味性,不同产品会进行场景化包装:
- 基础型:"签到","打卡"
- 场景型:"喂养宠物","收取能量"
- 游戏化:"抽奖转盘","集卡玩法"
三、设计目标体系
-
用户维度
追求"实际收益=奖励价值-操作成本"的最优解。奖励形式包括:
物质奖励:积分、优惠券、实物
精神奖励:徽章、等级、排行榜
-
产品维度
-
商业维度
通过提升 DAU/MAU 等核心指标,为广告变现、会员服务等商业模式奠定用户基础。


四、体验设计原则
核心是构建"高价值感知 - 低成本操作"的平衡:
价值可视化:即时反馈奖励内容
操作简洁:1-2步完成流程
成长可视:进度条、连续天数提示
五、实现方式
一、互动签到
- 手动签到
核心特点:由用户主动触发操作(如点击按钮、滑动屏幕幕等),需用用户明确感知并参与,互动存在感强,可强化用户对签到行为的认知。
典型场景:
内容社区(如小红书、知乎):通过手动签到激励用户每日活跃,积累签到积分可换取社区权益(如勋章、流量奖励等)。
电商平台(如淘宝、京东):结合"每日签到领红包/优惠券"活动,吸引用户进入APP完成签到,提升商品浏览转率。
教育类产品:用于课后打卡,记录学习进度,培养用户持续学习习惯。
- 自动签到
核心特点:用户无感知完成签到,无需额外操作,仅需保持登录状态或启动应用即可触发,交互流程轻量化。
典型场景:
工具类产品(如办公软件WPS、笔记应用印象笔记):用户以效率为导向,自动签到到激励用户活跃数据。
高频使用的社交APP(如微信、QQ):默认自动签到统计登录天数,用于隐性激励(如等级体系成长),不干扰核心社交功能。
系统级应用(如手机系统自带的健康管理APP):自动同步设备使用数据,实现"无感打卡"健康记录。
- 互动签到
核心特点:通过多步骤任务或趣味玩法引导用户参与,融合"签到 + 互动"双重属性,提升用户参与和动机和娱乐性。
典型场景:
游戏化产品(如蚂蚁庄园、合成类小游戏):设置"签到 + 收集道具 + 解锁剧情"链条,例:每天签到领取种子,种植后换取奖励。
社交电商/裂变活动(如拼多多"签到领现金"):结合分享、浏览商品等任务,用户完成"签到→做任务→提现"流程,驱动拉新和转化。
儿童教育APP:采用动画、答题等互动形式,将签到转化为趣味关卡(如"每日解锁一个汉字拼图"),吸引低年龄用户持续使用。
- 周期设计
每日签到:基础奖励,培养习惯。
连续签到:周期内不断签到,奖励递增。
累计奖励:弹性机制,降低断签惩罚。
混合模式:结合每日+周期奖励。
六、适用性分析
适用场景
内容驱动型产品(社交、电商、资讯),需培养用户使用习惯,提升打开频次。
不适用场景
功能驱动型产品(工具、效率类),用户需求具有即时性,签到行为与核心价值关联弱。
Prompt 相关(AI 辅助开发)
生成UI设计图
你是一位资深全栈软件开发工程师,同时精通产品规划和UI设计。现在我要开发一个每天签到领取积分的APP,主要页面和功能如下:
项目有两个页面,首页和积分详情页。
- 首页页面最左展示总积分和查看详情链接,点击积分详情跳转到积分详情页查看积分明细记录。
- 首页中间展示当前月积分,展示当月连续签到天数和剩余可补签次数,以及当前月份每日签到详情。
- 积分详情页以列表形式展示积分明细记录。
参考下面的内容,结合用户需求,以产品经理的视角去规划APP的功能、页面和交互:
- 作为设计师思考这些原型界面的设计,并以设计师的视角去输出完整的UI/UX;
- 直接使用交互的HTML模板,确保所有元素都符合移动端的交互习惯,文件命名为:prototype.html;
- 使用 FontAwesome 等开源图标库,让原型更精美和接近真实;
- 引入 tailwindcss 来完成页面样式,而不使用 style 样式;
- 图片使用 unsplash。
输出的 prototype.html 需要可以直接进行前端开发。
生成前端代码
你是一个资深全栈开发工程师,使用 Vue3 + typescript + vite 设计开发一个每天签到领积分的 H5 应用。
- 以 prototype.html 文件为原型进行开发,去掉项目中无用的初始 vue 组件和样式。
- 尽量使用社区成熟的 UI 框架或插件来实现,使用 tailwindcss 和 FontAwesome。
- 使用 pinia 管理状态。
- 使用 axios 进行网络请求。
后端提供以下几个接口。
- 查询积分接口,返回总积分。
- 签到记录接口,返回当月签到记录、当月连续签到天数、当月可补签次数。
- 当天签到接口。
- 补签接口,参数带日期。
- 积分记录列表接口。
功能点:
- 签到领积分,每天签到领 1 积分。
- 登录后展示总积分,点击积分详情展示积分明细。
- 每月连续签到到 3 天、7 天、15 天有额外积分奖励,分别奖励 5 积分、10 积分、20 积分,每个月满签奖励 100 积分。
- 当月支持补签,每次补签消耗 100 积分,且每月最多补签三次。
- 展示用户的本月连续签到次数和当月剩余补签次数。
生成数据表设计和API设计
你是一个资深软件架构师,熟悉各种互联网应用程序的架构设计。现在需要你设计一个每日签到领取积分的应用程序。
主要功能点:
- 用户每天签到可得 1 积分。
- 每个月连续签到 3、7、15 天和当月满签还会额外获得 5 积分、10 积分、20 积分和 100 积分。
- 需要统计用户的累计签到次数和当月连续签到次数。
- 支持用户补签当月的记录,补签 1 次需要消耗 100 积分,每个月最多补签 3 次。
- 首页展示用户总积分和签到日历(标识出补签记录),支持查看之前的签到日历。
- 支持用户查看积分详情。
参考互联网大厂的设计规范和经验,请输出 MySQL 数据库表结构设计和遵循 RESTful 风格的 API 设计。
生成了 prototype.html 和 API 文档之后,你就找一个 code agent(Cursor/trae)让他写前端代码了。
后端技术方案设计
用户模块
主要实现 用户注册、用户登录和用户鉴权三部分。
鉴权使用 JWT,同时提供 刷新 Token 接口,支持使用 refresh token 获取新 token。
签到模块
签到功能分为「每日签到」和「补签」两部分,同时需要返回用户签到月历和当月连续签到天数。
每日签到只允许签到当天的日期。
补签只允许补签当月的日期,并且每个月最多补签 3 次。
签到还需要额外考虑,连续签到奖励。
- 每日签到触发连续签到奖励。
- 补签也可以触发连续签到奖励。
积分模块
积分模块主要为用户当前积分和积分明细两个功能,同时还要支持用户签到功能的联动。
- 每日签到 +1 积分
- 每月连续签到额外奖励 +x 积分
- 用户补签消耗 -100 积分
RESTful API
用户模块相关API

签到模块相关API

积分模块相关API

数据表设计
sql
-- 用户基本信息表
DROP TABLE IF EXISTS `userinfo`;
CREATE TABLE `userinfo` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户ID',
`username` VARCHAR(50) NOT NULL COMMENT '用户名',
`password` VARCHAR(60) NOT NULL COMMENT '用户密码(bcrypt 加密)',
`email` VARCHAR(100) COMMENT '用户邮箱',
`avatar` VARCHAR(512) COMMENT '用户头像',
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted_at` TIMESTAMP NULL COMMENT '删除时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_user_id` (`user_id`),
UNIQUE KEY `uk_username` (`username`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户基本信息表';
-- 用户积分汇总表
DROP TABLE IF EXISTS `user_points`;
CREATE TABLE `user_points` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户ID',
`points` BIGINT DEFAULT 0 COMMENT '当前可用积分',
`points_total` BIGINT DEFAULT 0 COMMENT '累计获得积分',
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted_at` TIMESTAMP NULL COMMENT '删除时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_user_id` (`user_id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户积分汇总表';
-- 用户积分交易明细表
DROP TABLE IF EXISTS `user_points_transactions`;
CREATE TABLE `user_points_transactions` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户ID',
`points_change` BIGINT NOT NULL COMMENT '积分变动值(正数为增加,负数为扣除)',
`current_balance` BIGINT NOT NULL COMMENT '当前余额',
`transaction_type` TINYINT NOT NULL COMMENT '交易类型(1:签到 2:连续签到 3:补签 4:每日任务 5:福利任务)',
`description` VARCHAR(100) COMMENT '积分变动说明',
`ext_json` VARCHAR(1024) NOT NULL DEFAULT '' COMMENT '扩展字段',
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted_at` TIMESTAMP NULL COMMENT '删除时间',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户积分明细表';
-- 月度奖励记录表
DROP TABLE IF EXISTS `user_monthly_bonus_log`;
CREATE TABLE `user_monthly_bonus_log` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户ID',
`year_month` CHAR(6) NOT NULL COMMENT '年月 (YYYYMM)',
`bonus_type` TINYINT NOT NULL COMMENT '奖励类型 (1:连续签到3天 2:连续签到7天 3:连续签到15天 4:月满签)',
`description` VARCHAR(100) COMMENT '积分变动说明',
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted_at` TIMESTAMP NULL COMMENT '删除时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_user_yearmonth_type` (`user_id`, `year_month`, `bonus_type`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = '用户月度连续签到奖励记录表';
-- 签到记录表
DROP TABLE IF EXISTS `user_checkin_records`;
CREATE TABLE `user_checkin_records` (
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '记录ID',
`user_id` BIGINT UNSIGNED NOT NULL COMMENT '用户ID',
`checkin_date` DATE NOT NULL COMMENT '签到日期',
`checkin_type` TINYINT NOT NULL DEFAULT 1 COMMENT '签到类型: 1=正常签到, 2=补签',
`points_awarded_base` INT NOT NULL DEFAULT 1 COMMENT '获得积分',
`created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted_at` TIMESTAMP NULL COMMENT '删除时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_user_date` (`user_id`, `checkin_date`),
KEY `idx_user_id` (`user_id`),
KEY `idx_checkin_date` (`checkin_date`),
KEY `idx_checkin_type` (`checkin_type`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COMMENT = '签到记录表';
采用 Redis Bitmap 存储用户签到数据,采用 MySQL 数据库存储用户数据和积分数据。
安装 Redis
bash
sudo apt-get install lsb-release curl gpg
curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg
sudo chmod 644 /usr/share/keyrings/redis-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list
sudo apt-get update
sudo apt-get install redis
bash
sudo systemctl enable redis-server
sudo systemctl start redis-server
bash
root@GoLang:~/proj/proj2/goframeProj# ps -ef | grep redis
redis 136758 1 0 21:54 ? 00:00:00 /usr/bin/redis-server 127.0.0.1:6379
root 137070 134397 0 21:55 pts/0 00:00:00 grep --color=auto redis

输入exit可以退出该命令行
Redis Bitmap 介绍
由于签到数据只有签到和没签到两种状态,可以用0和1来表示签到状态。
-
0:表示没签到
-
1:表示已签到
而签到日期又是一些数据量固定的连续数据,所以非常适合使用 Redis 的 Bitmap 来存储。
在介绍项目的 Redis Key 设置之前,先来了解下 Redis 中的 Bitmap。
一、核心概念
Redis Bitmap 是 Redis 中一种基于二进制位(bit)的存储结构,本质上是一个字节数组(byte array),每个元素(bit)只能是 0 或 1。它并非独立的数据类型,而是基于字符串(String)类型实现的位操作抽象,可以看作是一个可变长度的二进制向量,适用于大规模数据的状态统计、快速查询和内存优化场景。
二、技术特性
1. 存储原理
每个位(bit)仅占 1 位空间,1MB 内存可存储 8388608 个状态位(约 800 万),相比传统的布尔值数组(每个元素占 1 字节)节省 8 倍内存,最大支持 2^32-1 位(约42.9亿)。
按需分配:Bitmap 的内存分配并非预先确定,而是根据用户设置的最大偏移量(offset)动态调整。比如,当你设置了 SETBIT key 1000 1,Redis 会确保该 key 至少能容纳 1001 位(偏移量从 0 开始)。
字节对齐:内存是以字节(8 位)为单位进行分配的。若你设置的最大偏移量是 1000,Redis 会分配 ceil(1001/8) = 126 字节(也就是 1008 位)。
自动扩展机制:设置超出当前范围的 offset时会自动扩容

2. 性能优势
时间复杂度:基本操作O(1), 位运算O(N)
空间效率:1亿用户每日签到仅需约12MB存储
原子性操作:适合高并发场景
3. 常用命令


go
package main
import (
"context"
"fmt"
"github.com/redis/go-redis/v9"
)
// 拉取 go-redis v9 依赖
// go get github.com/redis/go-redis/v9
// 整理依赖(推荐)
// go mod tidy
// bitmap 示例代码
func main() {
// 初始化一个 Redis 客户端,并指定 Redis 服务所在的地址和端口。在后续的操作中,可以通过 rdb 对象与 Redis 服务器进行通信。
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379",
})
defer rdb.Close()
ctx := context.Background()
bitmapDemo(ctx, rdb)
}
// bitmap 示例
func bitmapDemo(ctx context.Context, rdb *redis.Client) {
key := "test:bitmap"
// setbit
// 把 test:bitmap 这张表里的 第 0 个位置 设成 1
// SETBIT 返回的是"设置之前这个位置的值"
// 第一次设置返回值一定是0(之前是没开)
val1 := rdb.SetBit(ctx, key, 0, 1).Val()
fmt.Printf("setbit ret: %v\n", val1)
// getbit
val2 := rdb.GetBit(ctx, key, 0).Val()
fmt.Printf("getbit ret: %v\n", val2)
// bitcount:统计有多少个 bit 是 1
// Start:0, End:-1:表示从第 0 个字节开始到最后一个字节(-1 是末尾)
val3 := rdb.BitCount(ctx, key, &redis.BitCount{Start: 0, End: -1}).Val()
fmt.Printf("bitcount ret: %v\n", val3)
// bitop
key2 := "test:bitmap:2"
rdb.SetBit(ctx, key2, 2, 1)
// offset: 0 1 2
// 1 0 0
// 0 0 1
key3 := "test:bitmap:3"
// BITOP AND:对两个 Bitmap 做按位与,结果写入 key3
val4 := rdb.BitOpAnd(ctx, key3, key, key2).Val()
fmt.Printf("bitop ret: %v\n", val4)
val5 := rdb.BitCount(ctx, key3, &redis.BitCount{Start: 0, End: -1}).Val()
fmt.Printf("bitcount ret: %v\n", val5)
// bitpos: 找"第一个等于 1 的 bit 在哪里"
val7 := rdb.BitPos(ctx, key2, 1).Val()
fmt.Printf("bitpos ret: %v\n", val7)
rdb.SetBit(ctx, key2, 0, 1)
rdb.SetBit(ctx, key2, 1, 1)
val6 := rdb.BitCount(ctx, key2, &redis.BitCount{Start: 0, End: -1}).Val()
fmt.Printf("get ret: %v\n", val6)
// Bitmap 在 Redis 里,本质还是 String,但它不是"给人看的字符串"
// val8 := rdb.Get(ctx, key2).Val()
// fmt.Printf("val8: %v\n", val8)
// 只有在你存的是 普通字符串 / 数字字符串 时:GET 才"合理"
rdb.Set(ctx, "score", "100", 0)
val9 := rdb.Get(ctx, "score").Val() // "100"
fmt.Printf("val9: %v\n", val9)
}
bash
root@GoLang:~/proj/proj2/goframeProj# go run ./main.go
setbit ret: 1
getbit ret: 1
bitcount ret: 1
bitop ret: 1
bitcount ret: 1
bitpos ret: 0
get ret: 3
val9: 100
root@GoLang:~/proj/proj2/goframeProj#
4. 典型应用场景
1. 用户在线状态统计
场景:记录用户是否在线(1 表示在线,0 表示离线)。
实现:用 user:online 作为键,用户 ID 作为 offset,通过 SETBIT 更新状态,GETBIT 查询状态。
优势:百万用户仅需约 125KB 内存(100 万 ÷ 8 ÷ 1024 ≈ 122KB)。
2. 签到打卡系统
场景:记录用户一个月内的签到情况(1 表示签到,0 表示未签到)。
实现:用 user::uid:month 作为键,天数(1-31)作为 offset,每日签到时调用 SETBIT。
统计:通过 BITCOUNT 计算当月签到天数,或用 BITOP AND 统计多个用户的共同签到天数。
3. 布隆过滤器(Bloom Filter)
场景:快速判断元素是否存在于集合中(存在误判可能,适合允许容错的场景)。
实现:利用多个哈希函数将元素映射到 Bitmap 的不同位,设置为 1;查询时检查所有映射位是否为 1。
优势:相比传统哈希表,内存占用极低(例如存储 100 万元素仅需约 1MB 内存)。
4. 活跃用户统计(UV)
场景:统计一段时间内的活跃用户数(如日活、周活)。
实现:每天用一个独立的 Bitmap(如 day:20231001::uv),用户 ID 作为 offset,登录时置为 1;通过 BITOP OR 合并多天的 Bitmap 后,用 BITCOUNT 统计总活跃用户数。
之后我会持续更新,如果喜欢我的文章,请记得一键三连哦,点赞关注收藏,你的每一个赞每一份关注每一次收藏都将是我前进路上的无限动力 !!!↖(▔▽▔)↗感谢支持!