微信小程序打卡系统源码解析:前后端分离与MySQL表设计实战
简介这套基于小程序的互动打卡项目源代码采用JavaSSM构建后端服务前端基于uniapp/原生小程序开发并配套MySQL 5.7数据库脚本是一套完整可运行的前后端工程适合Java学习者及毕业设计、课程设计参考。压缩包共含1248个文件涵盖198个JavaScript脚本、122个CSS样式、96个XML配置、67个Java源码、64个HTML页面以及SQL、WXML/WXSS等前后端资源整体大小约8.77MB目录按Maven结构组织便于查阅。项目实现了签到打卡、积分排名、好友互动等典型功能可灵活用于企业考勤、学校签到、活动打卡等场景增加用户参与感。随包附有详细的小程序项目部署文档从环境配置、Tomcat7部署到Navicat导入数据库均有说明配合源码即可快速搭建并二次开发。目前已有152人学习下载。1. 解压这份打卡小程序源码前先想清楚三件事拿到一个标题写着“基于小程序的互动打卡小程序源代码完整前后端mysql.zip”的压缩包第一反应别急着解压导入微信开发者工具。这类源码包在技术社区和源码分享站里很常见价值不在“能跑”而在它把一套完整的前后端闭环摆在面前小程序端负责打卡与互动界面后端API负责业务逻辑与鉴权MySQL负责所有数据的持久化。它适合两类人想快速验证打卡类MVP的独立开发者以及刚接触前后端分离项目、需要一个真实业务练手的初级工程师。解压之前先想清楚三件事打卡记录怎么存才不重不漏、重复提交怎么拦、互动数据点赞、评论、排行榜怎么算。这三个问题想明白了源码只是参考答案。2. 互动打卡的业务逻辑与数据模型打卡记录到底怎么存2.1 打卡不是一条insert状态流转与补卡规则打卡业务看起来简单用户点一下按钮后端insert一条记录界面弹“打卡成功”。但真实产品里一个打卡行为背后是一整套状态流转。今天的状态可能是未打卡、已打卡、已补卡、已豁免明天的状态又取决于昨天有没有打。我接触过的打卡源码包里十有八九是用一张checkin_record表存所有明细靠查询结果反推当前状态而不是单独维护状态字段。这种做法是对的打卡状态不是存储出来的是计算出来的。明细记录是事实状态是推断。补卡规则是第一个设计分叉点。允许补卡的产品要回答三个问题补卡窗口多久、补卡后连续天数算不算、补卡和正常打卡在排行榜上是否同权。常见做法是checkin_record表加一个type字段0为正常打卡1为补卡2为豁免。连续天数统计时补卡能不能续上连续天数我一般建议不从简补卡只补次数不补连续天数。理由很现实打卡产品的核心激励来自连续天数一旦补卡能续连续天数排行榜立刻变成刷数据重灾区。这个决策要在写代码前定下来否则后面所有统计接口都要返工。这里给新手一个提醒不要把打卡状态存在user表里。有人图省事在user表加一个last_checkin_date字段打卡时更新它。单人单计划场景没问题一旦出现多计划并存用户同时参与早起打卡和读书打卡这个字段根本没法表达。一用户多计划打卡记录必然是一张独立的明细表按plan_id关联这是“一”和“多”的关系不要在用户表里塞“多”的数据。2.2 MySQL表设计用户、计划、打卡记录、互动四张表这类源码包的表结构大同小异核心是四张表用户表、计划表、打卡记录表、互动表。下面是我整理过的最常见形态具体字段以你解压出来的SQL为准。先看用户表CREATE TABLE user ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, openid VARCHAR(64) NOT NULL COMMENT 微信openid业务唯一键, nickname VARCHAR(64) DEFAULT , avatar_url VARCHAR(255) DEFAULT , created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_openid (openid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;openid是微信生态里识别用户的钥匙。wx.login拿到code后端拿code去微信接口换openid这个openid要存下来并建唯一索引。对外接口统一用openid定位用户不要把自增id当业务标识暴露。计划表负责承载“打卡什么”CREATE TABLE plan ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, user_id INT UNSIGNED NOT NULL COMMENT 创建者, title VARCHAR(128) NOT NULL, start_date DATE NOT NULL, end_date DATE NULL, status TINYINT NOT NULL DEFAULT 1 COMMENT 1进行中 2已结束 3已删除, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT打卡计划表;打卡记录表是整张数据模型的地基CREATE TABLE checkin_record ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, plan_id INT UNSIGNED NOT NULL, user_id INT UNSIGNED NOT NULL, checkin_date DATE NOT NULL COMMENT 打卡日期以后端时区为准, type TINYINT NOT NULL DEFAULT 0 COMMENT 0正常 1补卡 2豁免, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_plan_user_date (plan_id, user_id, checkin_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT打卡记录表;这个uk_plan_user_date唯一索引是整个打卡系统的最后一道防线。前端防抖会失效后端查重可能漏但数据库唯一索引不会。并发点击同一个打卡按钮第二条insert必然报Duplicate entry后端捕获这个错误返回“今天已打卡”。没有这个索引的源码包相当于只穿了防弹衣没戴头盔。互动表承载点赞和评论CREATE TABLE interaction ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, plan_id INT UNSIGNED NOT NULL, user_id INT UNSIGNED NOT NULL COMMENT 产生互动的人, record_id INT UNSIGNED NOT NULL COMMENT 被互动的打卡记录, type TINYINT NOT NULL COMMENT 1点赞 2评论, content VARCHAR(255) DEFAULT COMMENT 评论内容点赞时为空, status TINYINT NOT NULL DEFAULT 1 COMMENT 1有效 0已删除, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_record_id (record_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT互动表;互动表是典型的“一表多用”用type区分点赞和评论。点赞没有内容评论有内容。status支持软删除用户取消点赞时不物理删除而是status置0这样恢复点赞时更省事。注意评论内容也放进interaction表而不是单独再建一张comment表。对打卡这种轻量互动场景一张互动表足够表越多关联越重。2.3 为什么选“微信小程序 后端API MySQL”这套组合这类源码包普遍选微信小程序而不是H5或App原因不在技术在业务闭环。微信小程序自带wx.login免密登录体系不需要自己搭短信验证订阅消息可以做到“今晚还没打卡推一条提醒”分享卡片可以直接带参进打卡页拉群互动。这三件事是打卡类产品做增长的核心抓手H5在微信里跳来跳去要过各种验证App的门槛又太高。后端和MySQL的组合则出于两个务实理由。第一前后端分离是当前最主流的分工方式前端可以用原生小程序也可以用uni-app后端暴露纯JSON接口换客户端不动后端。这正好对应前后端分离项目实战里反复强调的边界接口契约定死两端各自演进。第二MySQL在打卡这种低频写入、高频统计的业务里非常合适。单用户一天最多几条打卡记录写入量极小但排行榜、连续天数统计、动态流都是复杂查询MySQL的SQL表达能力和索引机制让这些查询可以直接写出来不需要引入额外的计算引擎。补充一句现在不少源码包的小程序端是用uni-app写的。你解压后看到的可能不是原生小程序的app.json结构而是多了manifest.json、uni.scss这些uni-app工程文件。这种情况不能直接把源码目录导入微信开发者工具要先在HBuilderX里打开工程执行发行-小程序-微信生成dist目录后再用开发者工具打开。这个流程就是常见的uniapp微信小程序打包区别只在多一步编译。3. 从压缩包到小程序预览完整部署步骤3.1 环境准备Node.js版本、MySQL 5.7/8.0、微信开发者工具动手跑源码前先把环境对齐否则后面全是玄学报错。我按顺序检查三样东西。第一Node.js。后端如果是Express或Koa写的package.json里engines字段会写要求的版本范围。打开源码包里的server目录先看package.json再装Node。太老的源码可能只支持Node 12太新的依赖比如某些ORM可能要Node 16以上。装错了版本npm install阶段就会一片红。需要多个Node版本时用nvm管理这是血泪经验别用系统包管理器装一个固定的版本对付所有项目。第二MySQL。这里有个高频版本坑MySQL 8.0默认使用caching_sha2_password认证插件老版本的Node数据库驱动比如mysql2.x不认这个插件连接报错ER_NOT_SUPPORTED_AUTH_MODE。看到这个错要么装MySQL 5.7要么把驱动换成mysql2一行npm install mysql2就能解决连接池参数基本不用改。源码包如果年代比较早大概率用的还是mysql你按mysql 5.7.44的安装过程装一个5.7是最省事的。8.0也不是不行但要坚持用mysql2驱动。第三微信开发者工具。用正式版就行导入工程时选择“小程序”项目类型appid可以先填测试号。本地调试阶段不需要真实appid只有要用wx.login换取真实openid时才需要换成你自己注册的小程序appid。3.2 数据库初始化建库、建表、导入SQL的完整命令源码包里的database目录一般放着init.sql或schema.sql。先建库再导入业务账号和root分开。mysql -u root -p进入MySQL命令行后依次执行CREATE DATABASE IF NOT EXISTS checkin_db DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_unicode_ci; USE checkin_db; SOURCE /path/to/your/source/server/sql/init.sql; CREATE USER checkinlocalhost IDENTIFIED WITH mysql_native_password BY your_password; GRANT ALL PRIVILEGES ON checkin_db.* TO checkinlocalhost; FLUSH PRIVILEGES;这几步的作用分别是建库并指定utf8mb4字符集切换当前库执行SQL文件建表创建一个专用的checkin账号并把checkin_db库的权限给它。字符集必须盯死utf8mb4。微信昵称和评论内容里经常有emojiemoji在MySQL里要占4字节utf8字符集最多3字节直接报Incorrect string value。这是打卡互动场景里出现频率最高的一条报错凡是评论里带表情就崩十有八九是当初建库没建对。导入完成后验证一下SHOW TABLES; SELECT COUNT(*) AS user_count FROM user;确认四张核心表都在。user表count为0也没关系说明源码的SQL只建表不灌数据用户数据要靠后面的登录注册接口生成。3.3 后端配置与启动连接参数、jwt密钥与npm start后端启动前必须改两个文件数据库连接配置和jwt密钥。几乎所有源码包都会把配置放在config目录或根目录的config.js里。// server/config/index.js module.exports { port: process.env.PORT || 3000, mysql: { host: process.env.DB_HOST || 127.0.0.1, port: 3306, user: process.env.DB_USER || checkin, password: process.env.DB_PASSWORD || your_password, database: process.env.DB_NAME || checkin_db }, jwt: { secret: process.env.JWT_SECRET || please_change_this_secret, expiresIn: 7d } };这里有两个注意点。第一数据库账号用上一节SQL里创建的checkin账号不要填root至少不要把root密码写进代码仓库。第二jwt的secret必须改。默认secret是打卡接口被伪造的入口别人只要知道默认secret就能自己造token调你的接口越权打别人的卡。上线前改成一段随机长字符串比如用openssl rand -hex 32生成。启动cd server npm install npm startnpm install如果报错优先看报错栈里有没有node-gyp字样。有的话说明某个依赖需要本地编译常见元凶是bcrypt。解决办法是把bcrypt换成bcryptjs纯JS实现安装不需要编译接口用法几乎一样。启动后自测登录链路curl -X POST http://127.0.0.1:3000/api/login \ -H Content-Type: application/json \ -d {code: test_code}这个请求如果返回JSON格式的错误信息说明后端起来了路由通了。返回连接拒绝说明后端没起来先看端口监听日志。code用test_code换不到真实openid这是正常的要拿到真实token得在微信开发者工具里用wx.login生成的code去换。3.4 小程序端改造baseURL、appid与域名校验小程序端的改造集中在两个文件utils/request.js请求封装和project.config.json项目配置。先看请求封装// utils/request.js const BASE_URL http://127.0.0.1:3000/api; module.exports { request(path, method GET, data {}) { return new Promise((resolve, reject) { wx.request({ url: ${BASE_URL}${path}, method, data, header: { Content-Type: application/json, Authorization: wx.getStorageSync(token) || }, success: (res) { if (res.data.code 0) { resolve(res.data.data); } else { reject(res.data.message || 请求失败); } }, fail: reject }); }); } };BASE_URL改成你本机的地址token从storage里取每次请求自动带在Authorization头里。这段封装是这类源码的标准写法你拿到的包大概率长这样只需要改BASE_URL和确认token的存储key对应上。注意微信开发者工具的“不校验合法域名”开关位置在详情-本地设置勾选后模拟器里才能用http请求本机接口。这个开关只影响开发者工具里的模拟器真机预览时照样会被拦。真机预览的规矩是必须是https域名、域名要完成ICP备案、要在微信公众平台后台的开发管理-服务器域名里配置request合法域名。本地用IP调试、开发工具不校验、测试版用https测试域名、正式发布用备案后的正式域名这条链路卡掉过很多第一次接触小程序的新人不是代码问题是平台规则。另外如果源码用了自定义导航栏navigationStyle: custom顶部导航栏高度在不同机型上不一致一般用wx.getSystemInfoSync()拿statusBarHeight和胶囊按钮位置来动态计算。打卡页面如果堆满按钮这个细节会让布局在全面屏上差一截。4. 打卡、互动与列表三个核心模块的实现细节4.1 打卡接口的状态校验与幂等处理打卡接口是整个系统最核心的一段逻辑也是最容易出现重复提交的地方。常见做法是把查重和插入合成一步靠唯一索引兜底async function checkin(userId, planId) { const plan await db.query( SELECT id FROM plan WHERE id ? AND user_id ?, [planId, userId] ); if (!plan.length) { return { code: 1, message: 计划不存在或无权操作 }; } const today getTodayInServerTimeZone(); try { await db.query( INSERT INTO checkin_record (plan_id, user_id, checkin_date, type) VALUES (?, ?, ?, 0), [planId, userId, today] ); return { code: 0, message: 打卡成功 }; } catch (e) { if (e.code ER_DUP_ENTRY) { return { code: 1, message: 今天已打卡 }; } throw e; } }这段代码的逻辑分三层。第一层先校验计划归属防止用户A打卡用户B的计划这是越权漏洞的高发点必须用user_id过滤。第二层取后端时区的今天不信任前端传的任何日期参数。第三层直接INSERT撞上唯一索引就捕获Duplicate entry错误返回“今天已打卡”。比先SELECT再INSERT更安全因为SELECT和INSERT之间有一个时间窗口两个并发请求可能同时通过SELECT。INSERT直接撞唯一索引是性能最好、写法最简的幂等方案原理就是MySQL事务处理里的唯一约束。参数层面planId和userId都走预处理占位符不要拼字符串防SQL注入。前端配合的按钮防抖是另一道防线。点击后立刻把按钮disabled请求完成后再恢复配合后端幂等才能彻底解决“前后端对于按钮重复提交校验方法”这个问题。4.2 点赞与评论事务与计数一致性点赞接口的难点不在点赞本身在取消点赞时计数怎么减。一般做法是interaction表里记状态checkin_record表上挂一个like_count冗余字段。冗余字段能避免每次展示都去COUNT但代价是状态翻转时要同步更新计数async function toggleLike(userId, recordId) { const conn await pool.getConnection(); try { await conn.beginTransaction(); const [rows] await conn.execute( SELECT status FROM interaction WHERE user_id ? AND record_id ? AND type 1, [userId, recordId] ); const currentStatus rows.length ? rows[0].status : 0; const nextStatus currentStatus 1 ? 0 : 1; await conn.execute( INSERT INTO interaction (plan_id, user_id, record_id, type, status) VALUES ((SELECT plan_id FROM checkin_record WHERE id ?), ?, ?, 1, ?) ON DUPLICATE KEY UPDATE status VALUES(status), [recordId, userId, recordId, nextStatus] ); await conn.execute( UPDATE checkin_record SET like_count like_count ? WHERE id ?, [currentStatus 1 ? -1 : 1, recordId] ); await conn.commit(); return { code: 0, liked: nextStatus 1 }; } catch (e) { await conn.rollback(); throw e; } finally { conn.release(); } }逻辑说明事务里先查用户对这条打卡记录的点赞状态旧状态决定新状态和计数增减方向然后两条写操作一起提交。interaction表上要建(user_id, record_id, type)的唯一索引ON DUPLICATE KEY UPDATE保证重复点击走更新而不是插入新行。计数增减和状态翻转放在同一个事务里要么都成功要么都回滚避免出现“按钮点了两下、计数对不上”的数据错乱。评论功能更简单就是给interaction表插入一条type2的记录查询时按record_id拉列表按created_at排序。4.3 排行榜与连续天数计算ORDER BY与断卡判定排行榜的核心指标是连续打卡天数算法是“从今天往回数遇到第一个断点就停”。在Node里写比在SQL里写更容易维护const { subDays, format } require(date-fns); function calcContinuousDays(records, baseDate new Date()) { const dateSet new Set( records.map(r format(new Date(r.checkin_date), yyyy-MM-dd)) ); let count 0; for (let i 0; i 365; i) { const day format(subDays(baseDate, i), yyyy-MM-dd); if (dateSet.has(day)) { count 1; } else { break; } } return count; }这段代码的逻辑把打卡记录先转成日期字符串的Set从今天开始逐天往前查遇到第一个缺失日期就break。用date-fns的subDays处理日期减法避免手动做86400000毫秒减法在月末和夏令时上的问题。连续天数算完后排行榜就是一个排序问题SELECT user_id, checkin_date FROM checkin_record WHERE checkin_date DATE_SUB(CURDATE(), INTERVAL 30 DAY) ORDER BY user_id, checkin_date;这条SQL先把最近30天记录取出来按user_id和checkin_date排序保证同一个用户的记录排在一起应用层一遍循环就能把所有人的连续天数全算出来。MySQL排序在这里用的是联合排序两个字段都写在ORDER BY里。数据量大了以后可以考虑窗口函数但打卡MVP阶段完全没必要。4.4 动态列表加载更多分页参数与前端防抖打卡动态流是“页面列表加载更多”的标准场景。前后端的分页约定是page从1开始pageSize固定后端返回hasMore。前端触底加载的正确写法Page({ data: { feedList: [], page: 1, hasMore: true, loading: false }, onReachBottom() { if (!this.data.hasMore || this.data.loading) return; this.loadFeed(); }, loadFeed() { this.setData({ loading: true }); wx.request({ url: ${BASE_URL}/feed?page${this.data.page}pageSize10, success: (res) { const { list, hasMore } res.data.data; this.setData({ feedList: this.data.feedList.concat(list), page: this.data.page 1, hasMore }); }, complete: () this.setData({ loading: false }) }); } });前端三个配套规则page必须在请求成功后才自增hasMore为false时onReachBottom直接returnloading标记防止触底事件并发重复请求。前端发来的page和pageSize后端这样消费const page parseInt(req.query.page) || 1; const pageSize parseInt(req.query.pageSize) || 10; const offset (page - 1) * pageSize; const [rows] await pool.execute( SELECT id, nickname, checkin_date FROM feed_view ORDER BY created_at DESC LIMIT ? OFFSET ?, [pageSize, offset] ); const [countRows] await pool.execute(SELECT COUNT(*) AS total FROM feed_view); const hasMore offset rows.length countRows[0].total; res.json({ code: 0, data: { list: rows, hasMore } });offset (page - 1) * pageSize是分页标准换算。LIMIT和OFFSET是MySQL分页的经典组合pageSize限制每页条数OFFSET跳过已加载的行。hasMore通过当前偏移量加本页条数是否小于总数来判断。这三个规则加前端那三个写进代码能省掉一晚上的调试。5. 避坑这5个翻车点占了打卡项目排错的大半5.1 npm install卡在node-gyp编译失败怎么办现象执行npm install进度条走到一半报错红字里全是node-gyp、binding.gyp、python not found装啥都失败。原因项目里有bcrypt、sharp这类带C插件的原生模块npm从源码编译需要本机有Python和C编译工具链Windows机器上默认啥都没有macOS上Xcode Command Line Tools没装也一样报。解决最快的办法是把bcrypt换成bcryptjs。package.json里改依赖名代码引用处从require(bcrypt)改成require(bcryptjs)两个库的hash和compare调用方式几乎一致。改完删除node_modules重新npm install全程纯JS编译不再依赖系统工具链。如果还有别的原生模块卡住先npm install --ignore-scripts跳过编译步骤再逐个替换。5.2 小程序请求全部失败报“不在合法域名列表”现象开发者工具里所有wx.request都失败控制台提示url not in domain list。原因小程序平台规定request的url必须是https且域名要在微信公众平台配置过。本机用http://127.0.0.1调试默认必被拦。解决本地调试在开发者工具详情-本地设置里勾选“不校验合法域名”。真机预览和上线必须走正式流程申请https域名、完成ICP备案、在mp.weixin.qq.com后台的开发管理-服务器域名里添加request合法域名配置后等几分钟生效。注意合法域名不能带路径只填到域名根。这个坑不是代码问题改代码没用先看配置。5.3 打卡日期错位今晚的卡算到了明天现象用户晚上11点半打卡打卡记录上的日期变成第二天连续天数断掉用户来投诉。原因小程序端new Date()取的是用户手机本地时间如果用户手机时区设置偏了或者后端服务器时区不是东八区前后端各自算出的“今天”就不一致。解决一切以服务器时区为准。后端在启动时统一设置process.env.TZ Asia/ShanghaiMySQL连接串加timezone参数checkin_date字段用DATE类型而不是DATETIME。小程序端不要传用户时间给后端打卡日期一律由后端生成。这个坑排查起来很隐蔽因为白天测试永远正常只有深夜才翻车修复后记得补一条针对UTC时区的测试用例。5.4 用户连点两次按钮生成两条打卡记录现象快速连点“打卡”按钮数据库出现同一用户同一计划同一日期的两条记录界面却只提示了一次成功。原因前端button的tap事件没有防抖后端的查重加插入也不是原子操作两个请求同时通过了SELECT校验都走到了INSERT。解决三层兜底。前端在checkin请求发出后立刻把按钮disabled并在回调里恢复后端在INSERT时依赖唯一索引捕获ER_DUP_ENTRY返回“今天已打卡”数据库层面checkin_record表必须有(plan_id, user_id, checkin_date)的唯一索引。三层每一层都可能被绕过但三层一起失效的概率几乎为零。只改前端防抖不改数据库索引压测一上来还是会穿。5.5 真机调试登录失败code2session返回40029现象开发者工具模拟器里一切正常手机扫码预览后登录接口报40029 invalid code。原因wx.login生成的code是一次性的且5分钟有效。如果你拿同一份code先在模拟器里试过一次再拿到真机去换code已经失效了。另外开发者工具里的appid和后端配置的appid不一致换回来的openid对不上也会出现类似问题。解决每次调用登录接口前重新执行wx.login拿新code不要缓存code后端配置的appid和secret必须和当前小程序账号一致怀疑配置问题时在开发者工具里清缓存重新编译再试。还有一个细节后端日志里把微信接口返回的原始报文打出来别只打印业务错误很多问题是微信侧返回的errcode没透传导致的。6. 上线前先做这三步验证幂等脚本、补卡算法与数据备份6.1 用并发脚本验证打卡接口的幂等上线前我会写一个20并发的小脚本对着打卡接口打一轮期望结果只有一条成功。脚本用axios循环发20次请求统计“打卡成功”的出现次数。出现多次就说明后端幂等有洞优先检查checkin_record的唯一索引是不是真建上了以及INSERT报错时有没有捕获ER_DUP_ENTRY。const axios require(axios); const token 替换成真实token; async function hammer() { const results []; for (let i 0; i 20; i) { const res await axios.post( http://127.0.0.1:3000/api/checkin, { planId: 1 }, { headers: { Authorization: Bearer ${token} } } ); results.push(res.data.message); } const okCount results.filter(m m 打卡成功).length; console.log(打卡成功次数, okCount); } hammer();这段脚本循环发20个打卡请求统计“打卡成功”的出现次数正常应该等于1。这个验证一分钟跑完能在真实用户进来之前把最基础的数据完整性问题摁住。6.2 补卡后的连续天数重新算一遍如果源码里有补卡接口验证补卡后连续天数不被续上。手动在数据库插一条type1的补卡记录重新调用排行榜接口确认连续天数没变。这一步容易漏因为补卡逻辑经常是在旧记录上UPDATE改完就忘了统计口径。顺手再验证一次豁免记录type2不会把连续天数算断边界条件要单独写用例。6.3 备份MySQL数据再做改动每次改表结构或大改业务逻辑前先导出备份给改版留后悔药mysqldump -u root -p checkin_db backup_$(date %Y%m%d).sql恢复用mysql -u root -p checkin_db backup_20250101.sql。这个习惯救过我很多次尤其是动字段类型的时候一个ALTER TABLE下去数据可能就救不回来了。备份文件定期清理保留最近两周即可。我接手这类打卡工程最深的体会是源码能跑只是起点数据模型和状态规则才是真正要盯的东西。一套打卡源码前端的点击动画、后端的接口写法都容易改唯独“同一天只能打一次卡”“补卡不续连续天数”这类规则写死在代码里之后再改就要牵连排行榜、动态流、消息提醒一大片。现在我的习惯是先复述一遍规则再动代码规则说清楚了代码只是填空题。希望帮到你。本文还有配套的精品资源点击获取

相关新闻

微信小程序互动打卡系统:MySQL表结构设计与前后端完整实现

微信小程序互动打卡系统:MySQL表结构设计与前后端完整实现

简介:这套基于小程序的互动打卡完整前后端源码,采用Java与SSM框架构建后端服务,搭配uniapp与原生小程序前端,适合毕业设计、课程设计或小程序开发学习者参考。资源共1248个文件,压缩包约8.77MB,核心包含Jav…

2026/10/13 3:21:48 阅读更多 →
CS231n KNN作业深度解析:从像素距离到视觉本质

CS231n KNN作业深度解析:从像素距离到视觉本质

1. 这不是“写个KNN”——而是用最朴素的代码照见计算机视觉的底层逻辑你点开CS231n作业1-Q1,看到“写一个K近邻分类器”,第一反应可能是:不就是算距离、排个序、投个票?抄几行numpy就完事了。我当年也是这么想的,直到…

2026/10/13 3:20:47 阅读更多 →
51AI插件图层拆分教程:扁平化海报一键拆成文字可编辑PS图层

51AI插件图层拆分教程:扁平化海报一键拆成文字可编辑PS图层

一句话速览:AI 生成的海报好看,却是一张扁平图,文字、产品、背景全压在一起,根本没法单独改。用 51AI(51PsAI)PS 插件的「图层拆分」功能,可以把这张扁平化图片反向拆分成多个可编辑的 Photosho…

2026/10/13 3:20:47 阅读更多 →

最新新闻

mruby-struct 深入解析:mruby 中的 Struct 类定义、keyword_init 模式与模式匹配支持

mruby-struct 深入解析:mruby 中的 Struct 类定义、keyword_init 模式与模式匹配支持

编程语言语言运行时编译器解释器嵌入式 【免费下载链接】mruby Lightweight Ruby 项目地址: https://gitcode.com/gh_mirrors/mr/mruby 点击查看 免费下载 导读 mruby-struct 是 mruby 标准库中提供 Struct 类的 mrbgem。它允许你无需手写类定义,即可将…

2026/10/13 4:07:00 阅读更多 →
SHIB 250%暴涨背后:真假突破与链上数据的判断逻辑

SHIB 250%暴涨背后:真假突破与链上数据的判断逻辑

这几天,SHIB三个字母几乎是币圈社群里被刷得最多的词。一只柴犬主题的Meme币,价格在短时间窗口里拉出约百分之250的涨幅,于是“重大价格突破或即将到来”的说法,迅速从各个分析帖里蔓延开来。很多人心里其实都在打鼓:这…

2026/10/13 4:07:00 阅读更多 →
Linux页框回收机制深度解析:从水位线到回收路径

Linux页框回收机制深度解析:从水位线到回收路径

1. 内存明明够用,系统却在悄悄回收页框:从一次延迟尖峰说起1.1 现象:物理内存白白闲置,负载却出现换页先讲一个真实场景。前两年我维护一台容器节点,物理内存 128G,日常用量也就 60G 左右,内存看…

2026/10/13 4:07:00 阅读更多 →
Java开发者如何抓住AI应用红利:从Spring AI到LangChain4j的落地实践

Java开发者如何抓住AI应用红利:从Spring AI到LangChain4j的落地实践

1. AI应用开发红利期,Java开发者的机会窗口先说结论:AI应用开发这波红利,绝不是Python开发者的专属饭。某知名市场研究机构2025年发布的行业报告显示,AI应用层开发的市场规模正在以远超预期的速度膨胀,而这里面最缺的恰…

2026/10/13 4:07:00 阅读更多 →
Learn-Web-Hacking 深入解析:PHP 流(Streams)体系、封装协议与 php://filter 安全利用

Learn-Web-Hacking 深入解析:PHP 流(Streams)体系、封装协议与 php://filter 安全利用

文档网络安全教程 【免费下载链接】Learn-Web-Hacking Study Notes For Web Hacking / Web安全学习笔记 项目地址: https://gitcode.com/gh_mirrors/le/Learn-Web-Hacking 点击查看 免费下载 导读:PHP 流(Streams)自 PHP 4.3 引入…

2026/10/13 4:07:00 阅读更多 →
Flask实战:从零搭建在线教育平台的核心实现与避坑指南

Flask实战:从零搭建在线教育平台的核心实现与避坑指南

去年做模拟项目X的时候,接了一个比较完整的活儿:从零搭一个python网络课程在线学习平台。这类项目听起来好像就是“课程列表视频播放”,但真正动手才发现,一个能被老师真正拿去用、学生愿意天天打开的平台,背后牵扯的模…

2026/10/13 4:06:00 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/13 2:54:56 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/13 1:58:08 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →