
搞这个项目之前我其实一直挺反感“打卡”这类学习方式的。因为身边太多人打卡打了三个月翻开笔记一片空白问学了什么全是概念。但没想到最后把我自己说服的是一套把学习完全游戏化的追踪系统——就是你看到的这个标题小白努力学习技术从1级升级开始目前等级9级(3/10)。这其实是一个给某位零基础学编程的朋友做的个人学习进度管理系统。他自称“小白”给自己定的规则很简单把要学的技术内容拆成经验任务每完成一个学习动作就获得经验经验满了就升级。从最开始的1级一路练到现在的9级当前经验进度是3/10也就是离10级还差最后那一段。作为开发者和使用者双重身份我把这套系统的设计思路、数据模型、核心代码和踩坑记录全部整理出来分享给每一个管不住进度、学不下去的自学者和技术爱好者。如果你也曾经列了一堆学习计划却没一个能坚持两周这篇文章很值得你看完。1. 系统整体设计与需求拆解1.1 单纯列学习计划到底败在哪里先聊聊我为什么一开始对“打卡”和“计划表”那么不感冒。你自己回忆一下是不是也经历过这样的场景某个周末热血上头打开笔记本写了一张“两周学会Python基础”的计划表周一按部就班看了一小时视频周二加班太累直接断档周三为了补进度熬夜看到凌晨结果大脑一片空白周四开始自我怀疑周五计划表就被扔进回收站了。传统计划表最大的问题不是没有目标而是反馈极度滞后。你计划“本周掌握函数定义”这个目标的达成没有明确信号。今天多学一小时和少学一小时在计划表上没有任何直观的差别。人的大脑对延迟满足的耐心是有限的当你看不到自己“到底变强了多少”时放弃就成了默认选项。1.2 为什么用等级体系而不是星级或积分做这套系统之前我也考虑过几种方案简单的积分制、日历连续打卡制、星级评价制。最后选等级制核心源于一个很直觉的观察——游戏行业已经把“成长反馈”这件事研究得最透了。等级制和积分表最大的区别在于积分是线性的、枯燥的而等级是台阶式的每一级都是一个阶段性里程碑。升到9级(3/10)给人的感受和“你累计积了7350分”完全不一样。前者的脑子会告诉你“我马上要跨过一个重要节点了”后者只会告诉你“数字又变大了”。而且等级会带来隐藏的心流效应当经验条接近满格时人会不由自主地多做一点点任务、想把最后那点经验补齐。这种“升级驱动”的效应恰恰是单靠计划表完全无法产生的。1.3 从1级到9级系统本身的成长脉络这套系统并不是一开始就长得像现在这么完整的。如果追溯版本它的成长脉络大概分成三个阶段。1级到3级阶段系统里只有最基础的任务登记和手动加经验说白了就是一个电子表格套了个网页壳。我从实际使用中很快发现手动加经验这件事太依赖自律加了几天就会忘。4级到6级阶段加上了任务模板和“一键完成自动结算经验”的机制使用成本大幅下降。这个阶段开始主人才真正养成每天打开看一眼的习惯。7级到现在系统已经支持主线任务、日常任务、成就任务三种任务类型还能自动统计补级和溢出经验。当前显示“9级(3/10)”就是这一版在跑的进度。2. 升级公式与数据模型背后这些机制才是核心2.1 经验值怎么算升级曲线设计的两种思路等级系统看起来简单但背后最关键的其实是“升一级到底需要多少经验”。我在设计升级公式时对比了两种方案线性曲线和递增曲线。线性曲线比如每级都是固定1000经验优点是计算简单但缺点是前期会让玩家吃亏后期极度枯燥。你想如果第1级就要打1000经验才能升对“小白”来说门槛太高如果到了第20级还是1000经验就能升又完全体现不出成长的重量感。所以最终采用了递增曲线。计算公式如下exp_needed level * level * 50 100这个公式的好处在于每一级所需经验的增量是缓慢上升的等级越高升级越难但在前期不会卡得太狠。我实际验证过前10级的数据等级升级所需经验累计经验1 → 21501502 → 33004503 → 455010004 → 590019005 → 6135032506 → 7190051507 → 8255077008 → 93300110009 → 10415015150可以明显看出来前三级几乎是给新人“送温暖”的让小白能快速感受到升级的快乐而从7级开始每一级需要的经验就开始变得有点分量了。这个设计不是随便拍脑袋定的。我参考了大部分RPG游戏的数值节奏前期升级快让人入坑中期逐渐拉高难度让人保持专注后期靠成就感和级别本身撑住动力。2.2 当前进度“9级(3/10)”是怎么算出来的标题里的“9级(3/10)”不是随便写的它对应一套精确的进度表示法。括号里的分母10是把当前等级升到下一级所需的经验等分成10份分子3代表已经积攒的份数。结合上面的公式9级升10级需要4150经验。3/10表示目前已经积累了其中30%的经验也就是约1245点。在界面上它会被渲染成一个从左到右填充的经验条并在旁边标注精确数值p当前{{ user.level }} 级/p p经验{{ user.current_exp }} / {{ exp_needed_for_next_level }}/p div classexp-bar div classexp-fill stylewidth: {{ progress_percent }}%;/div /div这里有一个很容易被忽略的细节如果用进度条展示“3/10”这样一个只有一维的表达会被自然转化成“目前还差70%才升级”的心理预期让人把注意力放在“还差多少”而不是“已经得了多少”——这正是游戏化设计中应用很广的“流失规避”技巧。2.3 数据表结构用户、任务、经验记录怎么拆系统后端我用的是Python Flask加SQLite数据量不大但表结构划分很重要。一共三张核心表玩家表、任务表、经验记录表。CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT NOT NULL UNIQUE, level INTEGER DEFAULT 1, current_exp INTEGER DEFAULT 0, total_exp INTEGER DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE tasks ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, description TEXT, task_type TEXT NOT NULL, -- main 主线, daily 日常, achievement 成就 exp_reward INTEGER NOT NULL, is_completed BOOLEAN DEFAULT 0, completed_at TIMESTAMP, user_id INTEGER ); CREATE TABLE exp_records ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER, task_id INTEGER, exp_amount INTEGER, earned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP );tasks表和exp_records表看起来有重复但实际分工完全不同。tasks表描述的是“有哪些任务可以做”它的is_completed字段标记完成状态exp_records表记录的是“经验值什么时候因为哪个任务到账的”它是一份流水账。之所以要把流水单独拆出来是为了排查问题。比如某一天经验突然不对我只需要查exp_records就能还原出当时的加减记录而不需要去改任务主表的数据。这也是我在实际运行中经验值对不上账之后才补上的设计刚开始偷懒没拆分后来踩了坑。3. 实操过程与代码实现从空白到可运行3.1 环境准备与项目初始化这部分直接给结论跑通这套项目需要的东西并不多。我用的环境是Python 3.10、Flask 2.3、SQLite3前端直接用Jinja2模板加一点原生的CSS。如果读者想在自己的机器上复现可以按下面步骤来。mkdir xiaobai-level-system cd xiaobai-level-system python -m venv venv source venv/bin/activate # Windows系统执行 venv\Scripts\activate pip install flask数据库不需要单独安装Python自带的sqlite3模块就够用。项目结构按功能拆成四个文件对小白非常友好xiaobai-level-system/ ├── app.py # 主入口路由和业务逻辑 ├── models.py # 数据表和数据库操作封装 ├── templates/ │ └── index.html # 前端页面 ├── static/ │ └── style.css # 页面样式 └── task_seed.py # 初始化任务模板数据我说实话一开始没必要把项目拆太细拆成模块是为了后续加功能时好维护整套系统就这么大的数据量单文件也完全能跑。用Flask的原因就一个生态成熟、资料多哪怕你之前只接触过一点Python遇到报错也能很快搜索到答案。3.2 核心代码等级计算与任务提交等级计算是整个系统里最核心的模块。我先把升级判断逻辑封装成一个函数放在models.py里def exp_needed_for_level(level): return level * level * 50 100 def add_exp(user_id, task_id, exp_amount): # 给指定用户加经验并处理连升多级的情况 conn get_db() cur conn.cursor() # 1. 更新当前经验 cur.execute( UPDATE users SET current_exp current_exp ? WHERE id ?, (exp_amount, user_id) ) # 2. 检查是否可以升级注意这里用循环处理一次加经验跨多级的情况 while True: row cur.execute( SELECT level, current_exp FROM users WHERE id ?, (user_id,) ).fetchone() level, current_exp row need exp_needed_for_level(level) if current_exp need: break remaining_exp current_exp - need cur.execute( UPDATE users SET level level 1, current_exp ? WHERE id ?, (remaining_exp, user_id) ) # 记录升级日志 cur.execute( INSERT INTO exp_records (user_id, task_id, exp_amount) VALUES (?, ?, ?), (user_id, task_id, -need) # 这里用负数代表扣除升级所需经验 ) # 3. 记录经验流水 cur.execute( INSERT INTO exp_records (user_id, task_id, exp_amount) VALUES (?, ?, ?), (user_id, task_id, exp_amount) ) conn.commit() conn.close()这段代码里最值得留意的是while循环。很多人第一次写的时候会下意识用if但实际使用中完全可能出现一个任务大量经验、一口气从9级冲到11级的情况。if只判断一次会漏掉连升多级后的二次升级判断。我第一次写就是因为用了if导致用户从8级升到10级时系统只给升了一级经验还凭空消失了一大截。用while之后才彻底解决。任务提交的逻辑比较简单在app.py里加一个路由app.route(/tasks/int:task_id/complete, methods[POST]) def complete_task(task_id): # 防止任务被重复提交 conn get_db() cur conn.cursor() row cur.execute( SELECT is_completed FROM tasks WHERE id ?, (task_id,) ).fetchone() if row is None: return 任务不存在, 404 if row[0] 1: return 该任务已经完成, 400 # 获取任务奖励经验 task cur.execute( SELECT exp_reward, title FROM tasks WHERE id ?, (task_id,) ).fetchone() # 更新任务状态 cur.execute( UPDATE tasks SET is_completed 1, completed_at datetime(now) WHERE id ?, (task_id,) ) # 给玩家加经验 add_exp(current_user_id, task_id, task[0]) conn.commit() conn.close() return 任务完成经验 {}.format(task[0])3.3 任务模板的初始化数据task_seed.py里有整套系统最初的任务清单。这部分数据不是锁死的是我经过两轮调整后沉淀下来的。主要分三类主线任务对应完整的课程学习模块是升级的主干道。日常任务每天刷新适合碎片时间完成奖励经验虽然不多但能帮玩家保持手感。成就任务是隐藏的惊喜比如“连续一周完成日常任务”这类任务的奖励经验最高但难度也大。任务名称类型经验奖励说明学完“变量与数据类型”视频主线300核心知识完成“变量练习”5道题主线200巩固知识阅读30分钟技术书籍日常100每天一次输出一篇学习笔记日常150每天一次连续7天完成日常任务成就800周期成就这里要重点说明经验数值的配比逻辑。主线任务是“升级的主要经验来源”所以单次奖励定在200-300日常任务是为了保持在线活跃度奖励太低没动力太高又会让主线失去意义所以压在100-150。成就任务则是用来制造“惊喜感”800经验瞬间到账能大幅度拉高玩家的情绪峰值。3.4 前端页面等级卡片的渲染整个前端只有一个页面核心就是那张等级卡片。我刻意没有做复杂的仪表盘因为对使用者来说最需要的信息就三个当前等级、经验条进度、还差多少升级。div classplayer-card div classplayer-avatar span{{ user.level }}/span /div h2{{ user.username }}/h2 p classlevel-text当前等级/p h3Lv.{{ user.level }}/h3 div classexp-label span{{ user.current_exp }}/span span//span span{{ exp_needed }}/span /div div classbar div classbar-fill stylewidth: {{ percent }}%;/div /div p classtip距离升到下一级还差 {{ remain_exp }} 经验/p /div进度条的高宽比、颜色过渡、底部提示文字这些细节都被我和“小白用户”反复调整过。最初进度条是纯蓝色的后来发现视觉疲劳太快改成绿色渐变加尾端发光效果后每次看到进度条推进都稍微有点小兴奋。别看这些是小事游戏化产品的反馈强度往往就决定于这类视觉机场。4. 常见问题与排查技巧实录4.1 经验值溢出与连续升级最容易写错的地方有人可能觉得加经验有什么可写的做加法谁都会。但真正跑起来之后你会发现一次性加800经验直接导致跨两级的情况特别常见。如果你在代码里用if判断一次升级那么剩余经验会丢失累计经验也可能和真实数字对不上。我遇到的一次典型问题是等级8才攒了2000经验突然完成了一个800经验的成就任务理论上应该连升两级到达9级并剩余不少经验但代码只升到8级就把经验清零了。从那以后我在add_exp函数里强制用while循环并且专门加了一条升级日志经验扣除也写在流水里方便事后核查。4.2 任务重复提交与防刷机制因为这套系统给的是正向激励没有惩罚机制“刷经验”就成了设计上必须考虑的问题。日常任务是每天一次如果我在前端加一个“完成”按钮就不管了用户疯狂点刷新就会重复领取奖励。防刷方案用了两层第一层是前端控制任务完成后立即禁用按钮并提示“今日已完成”第二层是后端兜底检查tasks表的is_completed字段如果已经是1就不再添加经验。后端校验才是真正的防线前端控制只是为了提升用户体验。想绕过前端直接向接口发请求后端的拦截逻辑会确保数据不会出错。4.3 数据备份SQLite文件的定时拷贝SQLite的优点是很轻量一个文件搞定所有数据但缺点也很明显如果用户把项目部署在一台普通电脑上硬盘坏了数据就全没了。我对这套系统的备份策略是写了一个极其简单的shell脚本每天用cron定时把数据库文件拷贝到另一块磁盘上。#!/bin/bash cp /path/to/xiaobai-level-system/data.db /backup/data_$(date %Y%m%d).db find /backup -name *.db -mtime 7 -exec rm {} \;备份策略并不复杂但足以应对大部分意外。数据库文件本身没多大保存最近七日的备份也占不了多少存储。一次意外断电导致数据库损坏后我就把这个脚本的优先级提到了最高。4.4 使用的节奏等级越高越需要重新设计任务还有一个偏心态层面的问题想聊聊。目前到了“9级(3/10)”这个进度我发现原有任务模板开始不够用了。因为随着技术内容深入单个任务的学习时长变长了但经验奖励还是固定的。如果继续按旧模板执行会出现一种“卡等级”的体验投入的时间明显变多经验条却走得无比缓慢挫败感直线上升。这时候需要重新调整任务经验配比和任务类型。我目前的做法是把主线任务拆得更细以前“学完变量视频”一个任务给300经验现在拆成“看视频做笔记自己写案例”三个任务总经验变成500。这相当于变相调高了中高等级的成长速度让使用者不会因为卡级而放弃。注意等级系统的核心是“保持玩家正反馈”当玩家觉得升级吃力时不要怀疑是他的问题先检查自己的数值设计是否没跟上玩家的成长速度。我个人的体会是做这套系统的过程跟学习技术本身一样都在不断“升级”。一开始以为只是写个网页后来才发现懂一点交互设计、懂一点任务机制、懂一点数据库防坑样样都对体验有实打实的影响。如果你也在做类似的学习追踪工具建议从最简版本跑起来然后按照实际使用中的反馈一点一点加功能——这比一开始就把所有功能都想全再动手靠谱得多。这里最后一个经验别把等级和真实的能力挂等号等级只是学习过程的副产品真正的收获是那个为了凑经验连续七天看笔记的自己。