当“传染病防控”从应急处置走向常态化管理之后,基层的信息采集压力一下子变大了。纸质登记效率低、数据汇总慢、跨部门流转全靠人工,这些痛点在这个项目里其实都指向同一个答案:用微信小程序做前端采集端,再用一套后台管理系统做数据汇聚和预警。前段时间我完整做了一个“基于微信小程序的传染病防控系统”,源码、文档、调试全流程走了一遍,今天把这套东西拆开聊聊。
这个项目包含两个端:用户使用的微信小程序端,以及管理员使用的Web管理端。小程序端负责健康打卡、信息登记、行程上报、防控资讯浏览,管理端负责人员管理、打卡审核、数据统计和预警处理。整体适合正在做毕业设计、课程设计,或者想快速搭建一套“信息采集+数据展示”小系统的开发者参考。
我会把整套系统从需求拆解、功能设计、核心代码实现,到文档编写、调试排坑完整讲一遍。尤其是调试那一块,很多问题不是代码写错,而是微信小程序运行环境的特殊性导致的,这块经验外面文档很少写全。
1. 需求分析与整体设计思路
1.1 这个系统到底要解决什么问题
先别急着写代码,任何一个系统在动手之前都要把问题定义清楚。传染病防控这个场景,核心痛点可以归结为三个字:采、报、管。
“采”是指信息采集,包括个人基本信息、健康状况、体温、行程轨迹、接触史等。传统方式靠填Excel表或者纸质登记,效率低而且容易漏。“报”是指信息上报,采集到的数据需要汇总到防控管理部门,形成统计报表。“管”是指异常处理,如果有人体温异常或者来自高风险地区,系统要能触发预警,提醒管理人员及时跟进。
所以系统核心流程可以概括为:用户在小程序端填报信息,数据写入数据库,管理端实时汇总展示,异常数据触发预警通知。这个流程在技术上并不复杂,但涉及的角色、状态、权限有不少细节要处理。
1.2 核心模块划分与技术选型
我把系统拆成了三个端:小程序端、服务端、管理端。小程序端是用户直接接触的界面,负责信息采集;服务端提供接口和数据处理逻辑;管理端给防控人员使用,负责查询、审核、统计。
技术选型上,小程序端自然用微信官方原生框架,毕竟标题就是“微信小程序”,不需要额外引入uni-app之类的跨端框架,原生开发在调试和真机适配上都更直接。服务端我用的是Spring Boot,因为用户量级不大,Spring Boot自带的Tomcat完全够用,而且生态成熟,网上资料多,遇到问题好查。数据库选了MySQL,原因很简单:这系统的数据是结构化数据为主,用户表、打卡记录表、行程表之间有关系,用关系型数据库管理最稳。
管理端用Vue 2 + Element UI,这套组合在后台管理系统中非常常见。Vue 2虽然不算最新,但胜在稳定、资料多,对毕业设计或者企业内部的小系统来说完全够用。如果你更熟悉Vue 3也可以替换,核心逻辑不受影响。
1.3 为什么微信小程序是合理的产品载体
用户在传染病防控场景下需要的是一个“打开即用”的工具,微信小程序天然满足这个需求。小程序不需要下载安装,扫码或者搜索就能打开,学习成本几乎为零。尤其是面对年纪偏大、不太会装App的使用者,小程序的门槛明显更低。
而且微信小程序提供了一些原生能力,对防控场景非常有用。比如wx.getLocation可以获取用户位置,配合逆地址解析就能得到详细的行程地点;wx.scanCode可以扫场所码,快速登记到访信息;订阅消息可以主动推送防控通知给用户。这些能力如果换成H5,实现起来要费不少功夫。
还有一点很多人会忽略:微信小程序的审核机制和更新机制。小程序发布后,用户下次打开会自动更新到最新版本,不需要用户手动操作。对于防控政策可能随时调整的场景来说,这个特性很关键——昨天还在用“健康码+行程卡”双查验,今天可能就要改成“场所码登记”,小程序端的功能可以做到快速迭代上线。
2. 核心功能实现与关键代码细节
2.1 登录态与用户身份绑定:5分钟跑通
小程序登录是整套系统的基础,所有的用户操作都要先绑定微信身份。微信小程序登录流程说白了就是三步:小程序端调wx.login拿到一个临时code,把code发给后端;后端拿着code去微信接口换openid和session_key;后端生成自定义登录态返回给前端,前端存起来,后续请求带着这个登录态就行。
很多新手在这里会踩一个坑:直接把openid返回给前端当登录凭证。这其实是把敏感信息暴露了。openid是用户在微信体系内的唯一标识,一旦泄露,别人就可以伪造你的用户身份。正确做法是后端自己生成一个token,比如用UUID或者JWT,然后和openid关联存储。
// 后端代码示意:微信登录接口 @PostMapping("/api/auth/login") public Result login(@RequestBody LoginDTO dto) { // 1. 使用 code 换取 openid String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appId + "&secret=" + appSecret + "&js_code=" + dto.getCode() + "&grant_type=authorization_code"; String result = restTemplate.getForObject(url, String.class); JSONObject json = JSON.parseObject(result); String openid = json.getString("openid"); // 2. 查询用户是否存在,不存在则自动注册 User user = userMapper.selectByOpenid(openid); if (user == null) { user = new User(); user.setOpenid(openid); user.setCreateTime(new Date()); userMapper.insert(user); } // 3. 生成自定义登录态 String token = UUID.randomUUID().toString().replace("-", ""); redisTemplate.opsForValue().set("login:token:" + token, openid, 7, TimeUnit.DAYS); return Result.success(token); }小程序端登录代码要注意一个问题:wx.login拿到的code只能用一次,而且有效期只有5分钟。如果你在页面里反复调wx.login,后面的code会失效。正确做法是:启动时调一次,拿到token后存起来,后续接口都带着token走。
2.2 健康打卡表单:防重复提交的那点事
健康打卡是防控系统的核心高频功能,用户每天至少提交一次。设计打卡功能时我重点考虑了三个问题:表单校验、防重复提交、数据可追溯。
表单校验是最基本的,体温范围、手机号格式、必填项检查都要在前端做一层,后端也要做一层。后端校验是安全底线,不能依赖前端。我当时写了个简单的手写校验工具类,判断体温是否在35到42度之间,手机号是否符合11位数字规则。
防重复提交这个在真实场景里非常考验细节。用户可能手滑点了两次提交,也可能网络差导致前端重试。如果不加处理,数据库里就会出现重复记录。最简单有效的方案就是打一个“唯一索引”在数据库层兜底:在打卡记录表里给user_id和date加联合唯一索引。这样即使前端怎么重复请求,数据库也只保留一条。
ALTER TABLE health_checkin ADD UNIQUE KEY uk_user_date (user_id, checkin_date);同时前端也要做按钮置灰处理,提交中状态锁定提交按钮,避免用户多次点击。实测下来双保险最稳,单靠前端限制还是不放心。
2.3 异常预警与管理端数据看板
数据采集上来之后,最重要的就是异常预警。我设计了一套规则引擎逻辑:体温超过37.3度自动标记为“发热”,行程中选择了高风险地区标记为“高风险”,接触过确诊人员标记为“密接”。这些规则不复杂,用简单的判断逻辑就能实现,但支撑的数据结构要设计好。
打卡表里除了基础信息,还要预留一个status字段,用整数状态码区分:0代表正常,1代表发热,2代表高风险,3代表密接。这样管理端查询异常记录时只需要一句SELECT * FROM health_checkin WHERE status > 0 AND submit_date = ?就能搞定,效率很高。
管理端的统计看板是另一个工作量重点。我实现了几个核心图表:每日打卡人数趋势图(用折线图)、人员健康状态分布(用饼图)、异常记录列表(用表格)。前端用ECharts实现,后端提供统计接口,返回的数据直接是图表可用的JSON结构。这一步的核心是SQL的聚合查询:
// 按日统计打卡人数 SELECT submit_date, COUNT(DISTINCT user_id) FROM health_checkin WHERE submit_date BETWEEN ? AND ? GROUP BY submit_date;2.4 后台管理端的最小可用实现
管理端不用做得很花哨,核心是“查询、审核、导出”。用户管理负责查看注册用户和重置账号状态;打卡管理负责按日期、状态筛选打卡记录;预警管理是重点,管理员可以查看异常预警列表并标记处理状态;数据统计负责导出报表,导出Excel这块我直接用了EasyExcel,配合一个简单的前端按钮就能实现导出。
这里有个容易被忽视的权限问题。管理端和小程序端是同一个用户体系吗?不是。管理员是内部人员,不能走小程序的自动注册逻辑。我的方案是单独建一张管理员表,手动预置账号,登录走传统账号密码方式,用拦截器校验管理员身份。小程序端的用户即使拿到了管理端地址,因为没有账号也无法登录,这样就实现了权限隔离。
3. 项目文档的编写与组织
标题里写了“源码+文档”,实际做项目的时候,文档的重要性往往被低估。代码写完了,如果文档一团糟,后面接手维护的人会非常痛苦。我这次把文档拆成了五类,每类都有明确的作用。
3.1 需求文档与原型说明
需求文档是给所有人看的,要说明系统给谁用、解决什么问题、有哪些角色、每个角色能做什么。这个文档不需要写技术细节,但要把功能场景写清楚。比如“用户在小程序端提交每日健康打卡,管理员在管理端查看汇总数据”这种,就是核心需求。
原型说明建议配合截图写。我当时把小程序每个页面的截图和管理端每个页面的截图都放进文档里,配上页面跳转逻辑和交互说明。这样即使不看代码,也能对系统有直观认知,对答辩或者项目汇报特别有用。
3.2 接口文档与数据库设计文档
接口文档是前后端联调的契约。我是用Apifox写的接口文档,每个接口标注请求方式、URL、请求参数、返回结果、错误码。这里有个心得:接口返回结构一定要统一,推荐用Result.success(data)和Result.error(code, msg)这种统一包装,前端处理起来非常舒服。
数据库设计文档要把每张表的字段都列清楚,字段名、类型、长度、是否为空、注释都要写全。最重要的是外键关系和索引设计要说明白。比如用户表、打卡表、行程表之间的关联关系,如果不画ER图,后面维护的人很容易搞混。
3.3 部署文档与测试报告
部署文档要覆盖从零到跑通的完整步骤:安装JDK、安装MySQL、导入SQL脚本、修改配置文件、打包部署、小程序后台配置域名。每一步都要给出具体命令和截图。我在部署文档里踩过一个大坑:小程序请求的接口必须是HTTPS域名,而且要配置在微信公众平台的服务器域名白名单里。如果忘了配置,前端就会报“域名不合法”。
测试报告不用写得很学术,但要有真实的测试记录。我当时整理了一张测试用例表,包含用例编号、测试场景、操作步骤、预期结果、实际结果、是否通过。这个文档在答辩时特别加分,因为能证明你是真实跑过系统,而不只是写了代码。
4. 完整开发与调试实战记录
4.1 从搭建环境到跑通第一个接口
开发环境搭建这块,我建议按这个顺序来做:先装JDK 8+和Maven,再装MySQL 5.7+,然后装微信开发者工具,最后装IDEA。顺序不能乱,因为后端项目启动依赖MySQL,小程序调试依赖后端起服务。
后端项目启动后第一个要测的就是登录接口。我建了一个测试用户,在微信开发者工具里模拟调用wx.login,拿到code后请求后端登录接口。接口返回成功且能拿到token,整个系统的前后端链路就算通了。这一步走通了,后面的开发就都是“填空”。
要注意的是微信开发者工具默认的模拟器环境可以正常请求http://localhost:8080,但真机调试不行。真机小程序必须请求HTTPS域名。如果还没有正式域名和证书,可以在开发者工具里勾选“不校验合法域名”选项来临时调试。
4.2 真机调试与远程调试的区别
很多初学者只在开发者工具里点一点就算调试完了,这个习惯我建议趁早改掉。模拟器能模拟大部分功能,但有些问题只存在于真机环境:手机网络下的请求速度、不同机型的状态栏高度、小程序冷启动加载速度等。
微信开发者工具提供了“真机调试”和“远程调试”两个功能,很多人分不清。真机调试是把开发版小程序通过二维码在手机上打开,适合体验真实设备的功能表现;远程调试则是在手机上打开小程序,同时在电脑上打开调试工具的面板,可以实时看网络请求、Console日志和DOM结构。
我实操下来发现,微信小程序的Screen(页面栈)、Wxml面板、Network面板这三个调试工具最常用。Network面板能看清每个请求的耗时和返回状态,如果接口返回401,说明token过期;返回500,优先看后端日志。这些信息在自己排查问题时非常管用。
4.3 调试中的网络工具与问题定位
在整个开发过程中,我发现最让人头疼的就是“小程序前端报错但不知道后端发生了什么”。解决这个问题最快的方式就是看后端日志和数据库里面的实际数据,而不是光看前端报错信息。
后端我配置了logback日志框架,把日志级别调到DEBUG,这样请求参数、SQL执行、异常堆栈都能打印出来。前端配合使用console.log输出关键变量,两边日志一对照,问题基本就能定位。
另外有一个常见问题:小程序端请求后端接口超时。微信小程序的默认超时时间是60秒,但实际体验来看,超过10秒用户就已经不耐烦了。我封装了一个统一的请求工具类,设置合理的超时时间,同时对网络错误做统一处理。
5. 常见问题与排查技巧实录
5.1 “获取登录后的微信用户失败”如何处理
这个报错我调试的时候遇到过,当时被折腾了整整一下午。报错原因在小程序端拿不到用户的微信头像昵称——微信官方在2021年前后调整了获取用户信息的规则,wx.getUserInfo不再直接返回用户的真实头像昵称,必须通过“头像昵称填写能力”让用户主动填写获取。
这个改动让很多旧代码直接失效,解决方案也没那么复杂。现在推荐的做法是:在小程序端做一个引导页面,让用户点击“授权头像昵称”按钮,然后通过button组件的open-type="chooseAvatar"和input的type="nickname"功能来获取。获取到的信息再传给后端更新用户资料,不要依赖wx.getUserInfo。
看到标题所附的热搜词“小程序获取登录后的微信用户失败:wx1cb4398e1413dce7”,这个错误码前缀是appid的识别码,说明是小程序自身的AppID,报错本质原因大概率是上述授权规则的问题。排查路径我建议按照这个顺序来:先确认基础库版本在2.21.2以上,再检查有没有调用wx.getUserProfile成功,然后看后端日志里有没有openid,最后检查登录token有没有过期。
5.2 打卡数据死活提交不上去怎么查
打卡提交失败是高频问题。我遇到过一次线上事故:某个用户连续三天提交打卡都失败,最后排查发现是手机号格式校验太严格,把“手机号带空格”的情况拦下来了。
排查这类问题的思路,我总结了一个四步法:一看前端有没有把数据传给后端,二看后端有没有收到请求,三看SQL有没有执行成功,四看数据库里到底有没有写入数据。任何一步断了都能找到具体原因。
还有就是要注意微信小程序的并发请求限制。小程序同时最多发起10个请求,如果页面加载时多个接口并发请求,很可能后面的请求就被排队了。我优化过一次打卡页面,把不相关的接口改为懒加载,打卡提交的响应速度快了不少。
5.3 省市区联动选择器与自定义组件的坑
防控系统里有一个高频率使用的功能就是选择“当前所在地”,需要做成省市区三级联动。微信小程序原生的picker组件只支持单选,不支持多列联动。我当时第一时间就想到了自定义组件的方式。
网上有现成的省市区组件,比如mpvue-city-picker,但它依赖mpvue。我最终选择用微信小程序的picker-view组件自己实现,数据结构用三层嵌套对象,通过两级联动判断子级数据。核心逻辑就是:第一列变化时,重置第二列和第三列的数据;第二列变化时,重置第三列的数据。
这里有个体验细节要说明:如果你直接把省市区数据放在小程序端的JS文件里,包体积会很大。省市区数据有几千条,建议放到后端接口返回,前端做缓存即可。这样既能缩减包体积,更新数据也不用重新发版。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| 请求报“域名不合法” | 未配置服务器域名白名单 | 在微信公众平台添加request合法域名 |
| 登录后获取不到用户信息 | 微信授权规则调整 | 使用头像昵称填写能力重新采集 |
| 接口返回401 | token过期或未传递 | 检查请求拦截器,统一携带token |
| 打卡数据重复 | 前端重复提交 | 数据库加唯一索引,前端按钮置灰 |
| 真机调试请求失败 | 缺少HTTPS证书或网络不通 | 配置HTTPS证书,检查手机网络 |
| 管理端污染用户数据 | 权限控制不到位 | 管理员独立账号体系,接口校验身份 |
写到最后
这个系统从需求梳理到最终调试通过,前后大概花了两周时间。坦白说,功能本身不算复杂,真正的复杂度在于微信小程序的各类规则限制和边界情况处理。比如授权规则调整、域名白名单这些,文档里不会主动告诉你,都是踩过坑才长记性。最后想给正在做类似项目的朋友两个建议:第一,先把数据库设计想清楚再写代码,我见过太多项目做了一半发现表结构要改,改动成本巨大;第二,尽早用真机调试,不要等到全部功能写完再上真机,否则排查问题的范围会大到你不想面对。希望这套经验能帮你少走一些弯路。