# 成长等级系统规则文档 ## 一、系统概述 成长等级系统是激励用户持续使用应用的核心机制,通过记录用户的日常行为(任务完成、记账、心情记录)来累计经验值,提升等级,解锁成就和特权。 --- ## 二、API 接口 ### 2.1 获取成长信息 **接口路径**: `POST /growth/info` **请求参数**: 无 **返回示例**: ```json { "code": 0, "msg": "success", "data": { "id": 1, "level": 3, "title": "自律达人", "titleIcon": "⭐", "experience": 650, "nextLevelExp": 1500, "totalDays": 45, "streakDays": 12, "maxStreakDays": 20, "avgCompletionRate": 0, "todayProgress": { "tasksCompleted": 3, "taskExp": 15, "billsRecorded": 5, "billExp": 13, "moodRecorded": true, "moodExp": 5, "streakBonus": 5, "protectBonus": 0, "totalExp": 38, "maxDailyExp": 120 }, "achievements": [ { "id": "first_record", "name": "初次尝试", "icon": "✨", "unlocked": true, "unlockDate": "2024-01-15" } ], "privileges": [ "解锁心情日历", "解锁数据报表" ] } } ``` ### 2.2 获取成就列表 **接口路径**: `POST /growth/achievements` **请求参数**: | 参数名 | 类型 | 必填 | 说明 | | :--- | :--- | :--- | :--- | | category | string | 否 | 过滤类别:streak/task/mood/bill | **返回示例**: ```json { "code": 0, "msg": "success", "data": { "unlocked": [ { "id": "first_record", "name": "初次尝试", "icon": "✨", "category": "streak", "description": "完成第1天记录", "expReward": 10, "progress": 1, "target": 1, "unlocked": true } ], "locked": [ { "id": "streak_7", "name": "一周打卡", "icon": "🌟", "category": "streak", "description": "连续记录7天", "expReward": 50, "progress": 3, "target": 7, "unlocked": false } ] } } ``` ### 2.3 增加经验 **接口路径**: `POST /growth/add` **请求参数**: | 参数名 | 类型 | 必填 | 说明 | | :--- | :--- | :--- | :--- | | source | string | 是 | 来源:task/bill/mood | | source_id | string | 否 | 关联记录ID | **返回示例**: ```json { "code": 0, "msg": "success", "data": { "expGained": 38, "totalExp": 688, "levelUp": false, "currentLevel": 3, "nextLevelExp": 1500, "newAchievements": [] } } ``` --- ## 三、等级体系 ### 3.1 等级列表 | 等级 | 头衔 | 图标 | 所需累计经验 | | :--- | :--- | :--- | :--- | | Lv.1 | 新手冒险家 | 🎒 | 0 | | Lv.2 | 坚持学徒 | 📝 | 200 | | Lv.3 | 自律达人 | ⭐ | 600 | | Lv.4 | 习惯大师 | 💎 | 1500 | | Lv.5 | 传奇记录者 | 👑 | 3000 | | Lv.6 | 时光守望者 | 🏆 | 6000 | | Lv.7 | 超级达人 | 💫 | 10000 | | Lv.8 | 传说存在 | 🌟 | 15000 | | Lv.9 | 永恒传奇 | ⭐ | 22000 | | Lv.10 | 至高王者 | 👑 | 30000 | ### 3.2 等级经验计算逻辑 等级根据用户累计经验值自动计算: ```go func (h *growthHandler) getLevelByExp(exp int) int { level := 1 for l := 10; l >= 1; l-- { if config, ok := LevelConfigs[l]; ok && exp >= config.Exp { level = l break } } return level } ``` --- ## 四、经验值获取规则 ### 4.1 基础经验 | 来源 | 基础经验 | | :--- | :--- | | 完成任务 (task) | 5 EXP | | 记账记录 (bill) | 3 EXP | | 心情记录 (mood) | 5 EXP | ### 4.2 任务完成经验加成 | 今日完成任务数 | 经验计算 | | :--- | :--- | | 1-2个 | 5 EXP/个 | | 3个以上 | 前2个 5 EXP/个,第3个起 10 EXP/个 | | 5个以上 | 前2个 5 EXP/个,3-4个 10 EXP/个,第5个起 20 EXP/个 | **示例**: - 完成2个任务:2 × 5 = 10 EXP - 完成3个任务:2 × 5 + 10 = 20 EXP - 完成5个任务:2 × 5 + 2 × 10 + 15 = 45 EXP ### 4.3 记账经验加成 | 今日记账笔数 | 经验计算 | | :--- | :--- | | 1-2笔 | 3 EXP/笔 | | 3笔以上 | 前2笔 3 EXP/笔,第3笔起 8 EXP/笔 | | 5笔以上 | 前2笔 3 EXP/笔,3-4笔 8 EXP/笔,第5笔起 13 EXP/笔 | **示例**: - 记录2笔账单:2 × 3 = 6 EXP - 记录3笔账单:2 × 3 + 8 = 14 EXP - 记录5笔账单:2 × 3 + 2 × 8 + 13 = 33 EXP ### 4.4 心情记录经验 - 每次记录心情:5 EXP - **每日仅限1次**:通过 Redis 记录 `mood_count:{userID}:{date}`,每日只能记录一次 ### 4.5 连续打卡加成 连续打卡天数越多,加成越高: | 连续天数范围 | 每日加成 | | :--- | :--- | | 1-7天 | 2 EXP/天 | | 8-30天 | 5 EXP/天 | | 31-90天 | 10 EXP/天 | | 91天以上 | 15 EXP/天 | ### 4.6 新用户保护期 - 新用户注册后享有 **7天保护期** - 保护期内享受 **50%额外加成** (`protectBonus = baseExp × 0.5`) - 保护期内忘记打卡不会完全重置连续天数 - 如果总记录天数 < 30天,保护期内的连续加成 **翻倍** - 保护期结束后 `ProtectedDays` 字段递减 ### 4.7 每日经验上限 **每日最高可获得 120 EXP**,超过部分会被截断。 --- ## 五、连续打卡机制 ### 5.1 打卡判断逻辑 ```go isFirstRecordToday := growth.LastRecordDate != today if isFirstRecordToday { yesterday := time.Now().AddDate(0, 0, -1).Format("2006-01-02") if growth.LastRecordDate == yesterday { // 昨天也打卡了,连续天数+1 growth.StreakDays++ } else if growth.LastRecordDate == "" || growth.LastRecordDate < yesterday { // 昨天没打卡(断了) if growth.StreakDays > 0 { // 有连续记录但断了,根据之前的连续天数给予部分保留 if growth.StreakDays <= 7 { growth.StreakDays = 0 // 7天内断了,直接清零 } else if growth.StreakDays <= 30 { growth.StreakDays = 3 // 保留3天 } else if growth.StreakDays <= 90 { growth.StreakDays = 7 // 保留7天 } else { growth.StreakDays = 15 // 保留15天 } } growth.StreakDays++ } growth.TotalDays++ } ``` ### 5.2 断签处理规则 | 之前的连续天数 | 断签后保留天数 | | :--- | :--- | | ≤7天 | 0天(完全重置) | | 8-30天 | 3天 | | 31-90天 | 7天 | | >90天 | 15天 | ### 5.3 连续天数重置规则图解 ``` 用户连续打卡25天后忘记打卡: ┌─────────────────────────────────────┐ │ 第1-7天: 连续7天 │ │ ↓ 断了 │ │ 保留3天 → 再+1天 = 当前4天连续 │ └─────────────────────────────────────┘ 用户连续打卡100天后忘记打卡: ┌─────────────────────────────────────┐ │ 第1-90天: 连续90天 │ │ ↓ 断了 │ │ 保留15天 → 再+1天 = 当前16天连续 │ └─────────────────────────────────────┘ ``` --- ## 六、成就系统 ### 6.1 成就配置总览 | 成就ID | 名称 | 类别 | 描述 | 奖励经验 | 目标类型 | 目标值 | | :--- | :--- | :--- | :--- | :--- | :--- | :--- | | first_record | 初次尝试 | streak | 完成第1天记录 | 10 | total_days | 1 | | streak_3 | 3天坚持 | streak | 连续记录3天 | 20 | streak | 3 | | streak_7 | 一周打卡 | streak | 连续记录7天 | 50 | streak | 7 | | streak_30 | 月度坚持 | streak | 连续记录30天 | 200 | streak | 30 | | streak_100 | 百日筑基 | streak | 连续记录100天 | 500 | streak | 100 | | streak_365 | 年度传奇 | streak | 连续记录365天 | 2000 | streak | 365 | | task_1 | 任务新手 | task | 完成第1个任务 | 10 | total_tasks | 1 | | task_100 | 任务达人 | task | 累计完成100个任务 | 200 | total_tasks | 100 | | task_500 | 任务大师 | task | 累计完成500个任务 | 800 | total_tasks | 500 | | perfect_day | 完美一天 | task | 单日完成所有待办 | 30 | perfect_day | 1 | | perfect_week | 完美一周 | task | 连续7天100%完成 | 200 | perfect_week | 7 | | mood_first | 心情记录者 | mood | 第1次记录心情 | 10 | total_moods | 1 | | mood_30 | 月度心情家 | mood | 累计记录30天心情 | 100 | total_moods | 30 | | mood_100 | 心情收藏家 | mood | 累计记录100天心情 | 300 | total_moods | 100 | | happy_streak | 阳光达人 | mood | 连续10天记录开心 | 150 | happy_streak | 10 | | bill_1 | 记账入门 | bill | 第1次记账 | 10 | total_bills | 1 | | bill_100 | 记账达人 | bill | 累计记账100笔 | 200 | total_bills | 100 | | bill_500 | 记账大师 | bill | 累计记账500笔 | 500 | total_bills | 500 | ### 6.2 成就分类 #### 📅 坚持类 (streak) - **first_record**: 初次尝试 - 完成第1天记录 - **streak_3**: 3天坚持 - 连续记录3天 - **streak_7**: 一周打卡 - 连续记录7天 - **streak_30**: 月度坚持 - 连续记录30天 - **streak_100**: 百日筑基 - 连续记录100天 - **streak_365**: 年度传奇 - 连续记录365天 #### ✅ 任务类 (task) - **task_1**: 任务新手 - 完成第1个任务 - **task_100**: 任务达人 - 累计完成100个任务 - **task_500**: 任务大师 - 累计完成500个任务 - **perfect_day**: 完美一天 - 单日完成所有待办 - **perfect_week**: 完美一周 - 连续7天100%完成 #### 😊 心情类 (mood) - **mood_first**: 心情记录者 - 第1次记录心情 - **mood_30**: 月度心情家 - 累计记录30天心情 - **mood_100**: 心情收藏家 - 累计记录100天心情 - **happy_streak**: 阳光达人 - 连续10天记录开心 #### 💰 记账类 (bill) - **bill_1**: 记账入门 - 第1次记账 - **bill_100**: 记账达人 - 累计记账100笔 - **bill_500**: 记账大师 - 累计记账500笔 ### 6.3 成就解锁机制 成就在以下时机检查并解锁: 1. **每次增加经验时**:检查相关成就是否达成 2. **升级时**:额外检查所有成就是否达成 解锁成就后会立即发放经验奖励,并记录到 `UserAchievement` 表。 --- ## 七、特权系统 ### 7.1 等级特权一览 | 等级 | 解锁特权 | | :--- | :--- | | Lv.1 | 基础功能 | | Lv.2 | 解锁心情日历 | | Lv.3 | 解锁心情日历、解锁数据报表 | | Lv.4 | 解锁心情日历、解锁数据报表、解锁自定义主题 | | Lv.5 | 解锁心情日历、解锁数据报表、解锁自定义主题、解锁全部功能 | | Lv.6 | 解锁心情日历、解锁数据报表、解锁自定义主题、解锁全部功能、解锁年度回顾 | | Lv.7+ | 解锁心情日历、解锁数据报表、解锁自定义主题、解锁全部功能、解锁年度回顾、解锁数据导出 | ### 7.2 特权获取逻辑 ```go func (h *growthHandler) getLevelPrivileges(level int) []string { privileges := []string{} switch level { case 1: // Lv.1 无特权 case 2: privileges = append(privileges, "解锁心情日历") case 3: privileges = append(privileges, "解锁心情日历", "解锁数据报表") case 4: privileges = append(privileges, "解锁心情日历", "解锁数据报表", "解锁自定义主题") case 5: privileges = append(privileges, "解锁心情日历", "解锁数据报表", "解锁自定义主题", "解锁全部功能") case 6: privileges = append(privileges, "解锁心情日历", "解锁数据报表", "解锁自定义主题", "解锁全部功能", "解锁年度回顾") case 7: privileges = append(privileges, "解锁心情日历", "解锁数据报表", "解锁自定义主题", "解锁全部功能", "解锁年度回顾", "解锁数据导出") default: privileges = append(privileges, "解锁心情日历", "解锁数据报表", "解锁自定义主题", "解锁全部功能", "解锁年度回顾", "解锁数据导出") } return privileges } ``` --- ## 八、数据统计指标 ### 8.1 用户成长数据 (UserGrowth) | 字段 | 类型 | 说明 | | :--- | :--- | :--- | | ID | uint | 主键 | | UserID | uint | 用户ID | | Level | int | 当前等级 | | Experience | int | 当前累计经验 | | TotalDays | int | 累计记录天数 | | StreakDays | int | 当前连续打卡天数 | | MaxStreakDays | int | 历史最高连续天数 | | LastRecordDate | string | 最后记录日期 (YYYY-MM-DD) | | ProtectedDays | int | 新用户保护期剩余天数 | | TotalTasks | int | 累计完成任务数 | | TotalBills | int | 累计记账笔数 | | TotalMoods | int | 累计心情记录数 | ### 8.2 每日进度 (DailyProgress) | 字段 | 类型 | 说明 | | :--- | :--- | :--- | | ID | uint | 主键 | | UserID | uint | 用户ID | | RecordDate | string | 记录日期 (YYYY-MM-DD) | | TaskCount | int | 今日完成任务数 | | BillCount | int | 今日记账笔数 | | MoodRecorded | bool | 今日是否记录心情 | | DailyExp | int | 今日获得总经验 | | ExpBreakdown | string | 经验明细 (JSON格式) | ### 8.3 经验变化日志 (ExpChangeLog) | 字段 | 类型 | 说明 | | :--- | :--- | :--- | | ID | uint | 主键 | | UserID | uint | 用户ID | | ChangeType | string | 变更类型:add/level_up | | ExpDelta | int | 经验变化量 | | Source | string | 来源:task/bill/mood/level | | SourceID | string | 关联记录ID | | BeforeExp | int | 变化前经验 | | AfterExp | int | 变化后经验 | | RecordDate | string | 记录日期 | ### 8.4 用户成就 (UserAchievement) | 字段 | 类型 | 说明 | | :--- | :--- | :--- | | ID | uint | 主键 | | UserID | uint | 用户ID | | AchievementID | string | 成就ID | | UnlockDate | string | 解锁日期 | | ExpRewarded | int | 已奖励经验值 | --- ## 九、防刷机制 ### 9.1 心情记录限制 ```go if source == "mood" { key := fmt.Sprintf("mood_count:%d:%s", userID, today) count, _ := global.Redis.Exists(ctx, key).Result() if count > 0 { utils.Fail(c, "今日心情已记录") return } } ``` - 每日只能记录一次心情 - 通过 Redis Key `mood_count:{userID}:{date}` 限制 - Key 过期时间:25小时 ### 9.2 并发控制 ```go lockKey := fmt.Sprintf("lock:growth:%d", userID) ok, _ := global.Redis.SetNX(ctx, lockKey, "1", 2*time.Second).Result() if !ok { utils.Fail(c, "系统繁忙,请稍后重试") return } defer global.Redis.Del(ctx, lockKey) ``` - 使用 Redis 分布式锁防止并发操作 - 锁过期时间:2秒 --- ## 十、经验计算示例 ### 10.1 普通用户(第1天) ``` 基础条件:无连续、无保护期 操作:完成1个任务 经验计算: - 任务基础经验:5 EXP - 连续加成:0 EXP(无连续) - 保护期加成:0 EXP(保护期需 TotalDays >= 30 才翻倍) 今日总计:5 EXP ``` ### 10.2 新用户(第7天,保护期内) ``` 基础条件:连续7天,保护期剩余0天 操作:完成3个任务,记录心情 经验计算: - 任务基础经验:2×5 + 10 = 20 EXP - 心情经验:5 EXP - 连续加成(1-7天):2 EXP - 保护期加成:5 × 50% = 2.5 EXP ≈ 2 EXP - 保护期连续加成翻倍:2 × 2 = 4 EXP(因为 TotalDays=7 < 30) 今日总计:20 + 5 + 4 + 2 = 31 EXP ``` ### 10.3 老用户(连续30天) ``` 基础条件:连续30天,保护期已过 操作:完成5个任务,记录3笔账单,记录心情 经验计算: - 任务经验:2×5 + 2×10 + 15 = 45 EXP - 账单经验:2×3 + 8 = 14 EXP - 心情经验:5 EXP - 连续加成(8-30天):5 EXP - 保护期加成:0 EXP(保护期已过) 今日总计:45 + 14 + 5 + 5 = 69 EXP ``` ### 10.4 满级用户(连续100天) ``` 基础条件:连续100天,保护期已过 操作:完成5个任务,记录5笔账单,记录心情 经验计算: - 任务经验:45 EXP - 账单经验:2×3 + 2×8 + 13 = 33 EXP - 心情经验:5 EXP - 连续加成(31-90天):10 EXP 今日总计:45 + 33 + 5 + 10 = 93 EXP ``` --- ## 十一、数据库表结构 ### 11.1 user_growths 表 ```sql CREATE TABLE `user_growths` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `created_at` datetime(3) DEFAULT NULL, `updated_at` datetime(3) DEFAULT NULL, `deleted_at` datetime(3) DEFAULT NULL, `user_id` bigint unsigned NOT NULL COMMENT '用户ID', `level` int DEFAULT '1' COMMENT '当前等级', `experience` int DEFAULT '0' COMMENT '当前经验值', `total_days` int DEFAULT '0' COMMENT '累计记录天数', `streak_days` int DEFAULT '0' COMMENT '连续记录天数', `max_streak_days` int DEFAULT '0' COMMENT '最高连续天数', `last_record_date` varchar(10) DEFAULT '' COMMENT '最后记录日期', `protected_days` int DEFAULT '7' COMMENT '新用户保护期剩余天数', `total_tasks` int DEFAULT '0' COMMENT '累计完成任务数', `total_bills` int DEFAULT '0' COMMENT '累计记账笔数', `total_moods` int DEFAULT '0' COMMENT '累计心情记录数', PRIMARY KEY (`id`), UNIQUE KEY `idx_user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ``` ### 11.2 daily_progress 表 ```sql CREATE TABLE `daily_progress` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `created_at` datetime(3) DEFAULT NULL, `updated_at` datetime(3) DEFAULT NULL, `deleted_at` datetime(3) DEFAULT NULL, `user_id` bigint unsigned NOT NULL COMMENT '用户ID', `record_date` varchar(10) NOT NULL COMMENT '记录日期', `task_count` int DEFAULT '0' COMMENT '今日完成任务数', `bill_count` int DEFAULT '0' COMMENT '今日记账笔数', `mood_recorded` tinyint(1) DEFAULT '0' COMMENT '是否记录心情', `daily_exp` int DEFAULT '0' COMMENT '今日获得经验', `exp_breakdown` text COMMENT '经验明细JSON', PRIMARY KEY (`id`), KEY `idx_user_date` (`user_id`,`record_date`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ``` ### 11.3 exp_change_logs 表 ```sql CREATE TABLE `exp_change_logs` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `created_at` datetime(3) DEFAULT NULL, `updated_at` datetime(3) DEFAULT NULL, `deleted_at` datetime(3) DEFAULT NULL, `user_id` bigint unsigned NOT NULL COMMENT '用户ID', `change_type` varchar(20) NOT NULL COMMENT '变更类型', `exp_delta` int NOT NULL DEFAULT '0' COMMENT '经验变化量', `source` varchar(20) DEFAULT '' COMMENT '来源', `source_id` varchar(50) DEFAULT '' COMMENT '关联记录ID', `before_exp` int DEFAULT '0' COMMENT '变化前经验', `after_exp` int DEFAULT '0' COMMENT '变化后经验', `record_date` varchar(10) NOT NULL COMMENT '记录日期', PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`), KEY `idx_record_date` (`record_date`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ``` ### 11.4 user_achievements 表 ```sql CREATE TABLE `user_achievements` ( `id` bigint unsigned NOT NULL AUTO_INCREMENT, `created_at` datetime(3) DEFAULT NULL, `updated_at` datetime(3) DEFAULT NULL, `deleted_at` datetime(3) DEFAULT NULL, `user_id` bigint unsigned NOT NULL COMMENT '用户ID', `achievement_id` varchar(50) NOT NULL COMMENT '成就ID', `unlock_date` varchar(10) NOT NULL COMMENT '解锁日期', `exp_rewarded` int DEFAULT '0' COMMENT '已奖励经验值', PRIMARY KEY (`id`), UNIQUE KEY `idx_user_achievement` (`user_id`,`achievement_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; ``` --- ## 十二、版本历史 | 版本 | 更新日期 | 更新内容 | | :--- | :--- | :--- | | v1.0 | 2024-01-01 | 初始版本,实现基础等级系统 | | v1.1 | 2024-02-15 | 新增成就系统,包含18个成就 | | v1.2 | 2024-03-20 | 新增连续打卡保护机制 | | v1.3 | 2024-04-25 | 优化经验计算规则,添加任务/账单完成加成 | | v1.4 | 2024-06-01 | 新增心情记录功能 | | v1.5 | 2024-08-15 | 优化防刷机制,使用Redis分布式锁 | --- **文档版本**: v1.5 **最后更新**: 2024年8月15日 **适用项目**: simple-memo 后端成长等级系统 **文档路径**: `/Users/sunct/WeChatProjects/simple-memo/docs/growth_rules.md`