微信小程序传染病防控系统实战:源码拆解与Docker一键部署

发布时间:2026/10/18 5:53:36
微信小程序传染病防控系统实战:源码拆解与Docker一键部署 做这类“传染病防控”小程序最怕什么不是功能写不出来而是做出来之后没人敢用、没人认账。疫情信息涉及健康隐私用错一个字段、漏传一次数据都可能引发合规和信任问题。我自己前后做过几套类似的课题和项目从“单机demo”到“可上线的多端系统”都趟过一遍今天这篇就把一套微信小程序传染病防控系统的完整落地流程拆给你看从源码怎么组织到核心功能怎么实现再到最后怎么用Docker一键部署起来全程不绕弯子。这篇文章适合三种人第一种是要做毕业设计或课程项目的同学想找一个“功能完整、能跑通、有技术亮点”的参考案例第二种是刚转行前端、想了解小程序全栈开发套路的开发者这里会有代码级的过程第三种是学校、园区的小型项目负责人需要快速判断“这种系统到底怎么落地、有什么坑”。我会把踩过的坑和同行的经验都写进去尽量让每个环节都能直接对着操作。顺便说一句由于卫生信息场景的特殊性文章里不会提供任何真实病人数据或机构信息里面的案例和配置都做了抽象处理你只需要关心技术路径本身。下面开始。1. 系统定位与需求拆解1.1 这类小程序究竟在解决什么问题传染病防控工作在实际执行中最难的不是制定流程而是“信息收集的速度和准确性”。比如一个园区有上千人如果靠人工填表汇总从下发到回收再到统计至少耗费半天而且漏报错报无法纠正。小程序防控系统要解决的核心就是两件事一是让每个人在手机上花几十秒完成信息上报二是让管理员在一个后端页面实时看到统计结果和异常记录。所以这套系统的本质是“轻量级信息流 规则引擎”不是大数据分析平台也不是严格意义上的医院信息管理系统。它关注的是“本人健康状态、行程轨迹、接触史”这三类高频数据的快速采集与比对。如果一开始就把架构设计得很重比如引入复杂的流计算引擎和AI模型小项目的维护成本就会失控。个人建议至少在v1.0版本里把系统做“薄”只保留四类核心动作用户登录、每日打卡、异常上报、后台统计。1.2 核心功能模块清单我把场景设定为一个普通高校或企业园区的防控管理系统的使用角色分为两类普通用户和管理员。普通用户通过小程序完成每日健康信息填报、查看个人打卡记录、接收通知管理员通过后台Web页面查看汇总数据、导出报表、处理异常申诉。为了让源码解析更直观我列了一个功能矩阵模块用户端管理端核心数据字段身份认证微信授权登录账号密码登录openid、role、department每日打卡填报体温、症状、行程查看填报率、明细user_id、temperature、symptom、location、report_date风险预警收到风险提示设置中高风险区zone_name、risk_level、update_time数据统计个人记录查询日期筛选、图表、导出聚合查询结果注意“行程”字段不是让你做GPS定位而是让用户手动选择近7天去过的地方比如“校内食堂”“图书馆”等固定选项。一来减少开发复杂度二来避免过度采集个人位置信息造成合规问题。这是很多没经验的同学容易踩的雷以为必须用地图API实现轨迹记录其实普通防控场景完全可以用“区域勾选”代替。1.3 技术选型背后的取舍小程序端我选的是原生微信小程序框架而不是uni-app或Taro。原因很简单这个项目不涉及多端复用原生框架开发工具链最稳定组件和API说明也最全。虽然语法比不上Vue好写但遇到问题查官方文档最快。后端我选择了Spring Boot MyBatis Plus MySQL Redis这是Java技术栈里最“稳妥”的组合。学院派项目都看得懂招聘市场上Java岗位最多后续想扩展定时任务、消息队列都方便。如果求轻量你也可以用Node.js Express或Python FastAPI但考虑到并发场景学生每天集中在早上8点打卡Java的线程模型和生态会更让你省心。用Redis主要是为了做“每日打卡幂等”和“高频读取缓存”。简单说就是同一个用户同一天内不管手滑提交多少次后端只算第一次有效首页的统计面板和风险区域配置直接读Redis而不是每次查MySQL抗住几百QPS没问题。如果你做的规模再大一点还可以引入消息队列削峰但这就是后话了。2. 源码结构从入口到后端服务2.1 小程序端目录设计一个结构清晰的小程序项目应该像一本书看目录就能大概知道内容。我习惯这样组织mini-program/ ├── app.js # 全局入口检查登录态 ├── app.json # 页面路径和窗口配置 ├── pages/ │ ├── index/ # 首页快捷打卡入口和今日状态 │ ├── report/ # 每日打卡页 │ ├── records/ # 历史记录页 │ ├── profile/ # 个人中心 │ └── admin/ # 管理员入口根据角色显示 ├── components/ # 自定义组件温度计、日期选择等 ├── utils/ │ ├── request.js # 封装wx.request统一加header和错误处理 │ ├── auth.js # 登录态管理 │ └── format.js # 日期格式化等工具函数 ├── static/ # 图片、图标等资源 └── project.config.json # 项目配置文件记住一个开发原则页面文件里只负责onLoad、onShow和数据绑定所有与后端交互的逻辑扔到utils/request.js里。这样后期换接口地址、做mock数据都不用翻遍每个页面。我们项目里有一个常见思路是在app.js的onLaunch里先调用wx.login拿到code发给后端换openid和session再包装成一个自定义的登录态token后续每次请求都带上。2.2 核心页面的数据流每日打卡页是整个系统的“心脏”。打开这个页面时页面要做的第一件事不是让用户填东西而是先查询“今天是否已经打过卡”。如果已经打过就直接展示一个“今日已完成”的卡片防止重复提交。这个查询我用了一个很轻的接口GET /api/report/today?uidxxx。用户提交打卡时前端收集的数据包括体温、是否有咳嗽/乏力等症状、近期是否去过中高风险区域、当前所在地手动选择或文本输入。点击提交后utils/request.js会把数据POST给后端后端做完校验和幂等处理后返回success: true页面再跳转到完成状态。有人会问“用户每天填的都是一样的数据能不能做个‘一键和昨天一样’”技术上可以后端增加一个缓存昨天最后一条记录即可。但这会带来一个体验误区如果用户已经出现症状却懒得改一键复制就变成了漏报。所以我建议不要做“完全一键复制”可以做成“预览昨天记录允许修改后提交”从产品层面减少误操作。2.3 后端API的设计规范与权限控制后端目录我用的是标准分层的包结构src/main/java/com/example/epidemic/ ├── controller/ # 接口请求入口 ├── service/ # 业务逻辑 ├── mapper/ # MyBatis Plus数据访问 ├── entity/ # 数据库实体 ├── common/ # 通用返回体、异常处理 ├── config/ # 拦截器、Redis配置等 └── util/ # JWT、加密工具接口设计遵循RESTful风格但不追求极致。核心接口只有这几个POST /api/user/login # 登录参数: code返回token GET /api/user/info # 获取当前用户信息 POST /api/report/submit # 提交打卡 GET /api/report/today # 获取今日打卡状态 GET /api/report/list # 分页获取历史记录 POST /api/admin/stat # 管理员获取统计报表 POST /api/admin/export # 导出Excel关于权限控制我用了双保险拦截器层面所有/api/user/**和/api/admin/**都需要带token并且解析出用户角色业务层面管理员接口还会校验role admin。有人说“小程序前端可以隐藏管理员入口啊为什么后端还要校验”就是因为接口可以被直接抓包调用如果你不做服务端校验随便一个普通用户POST一个roleadmin就可能拿到后台数据这是非常严重的越权漏洞。凡是涉及管理功能的接口必须服务端鉴权这是底线。3. 核心代码模块实现详解3.1 用户登录与身份绑定小程序登录和网页登录最大的不同是它有一个“安静登录”的流程wx.login拿到的code是一次性的后端用code到微信服务器换openid。这里千万不要自己拼appid secret code去调微信接口虽然老教程都这么写但明文暴露secret的风险极大。正确的做法是小程序端只传给后端一个code由后端请求微信接口拿到openid后再在本地数据库里匹配用户。来个核心代码示例我简化了一些异常处理PostMapping(/user/login) public Result login(RequestBody LoginDTO dto) { String url String.format( https://api.weixin.qq.com/sns/jscode2session?appid%ssecret%sjs_code%sgrant_typeauthorization_code, appid, secret, dto.getCode()); String resp restTemplate.getForObject(url, String.class); JSONObject json JSON.parseObject(resp); String openid json.getString(openid); User user userMapper.selectOne( new LambdaQueryWrapperUser().eq(User::getOpenid, openid)); if (user null) { // 如果用户首次登录自动创建账号并默认绑定为“普通用户” user new User(); user.setOpenid(openid); user.setRole(user); userMapper.insert(user); } String token JwtUtil.createToken(user.getId(), user.getRole()); return Result.success(token); }需要注意的是生产环境里appid和secret不能硬编码在配置文件中提交到仓库建议使用环境变量或者配置中心的占位符。我在部署脚本里用“从环境变量注入”的方式防止代码仓库泄露密钥。3.2 每日健康打卡的完整链路打卡逻辑看起来简单但最容易出bug的是“重复提交”和“数据篡改”。先看正常的链路用户进入打卡页填体温和症状点击提交前端把数据POST给后端。后端第一件事不是写库而是检查Redis里有没有这个用户当天的打卡记录有就直接返回“已打卡”没有才继续。核心的幂等逻辑可以这样写public synchronized SubmitReportResult submit(ReportDTO dto) { String key report: dto.getUserId() : LocalDate.now(); boolean first redisTemplate.opsForValue() .setIfAbsent(key, 1, Duration.ofHours(24)); if (!first) { return SubmitReportResult.error(今日已打卡无需重复提交); } // 这里写真正的数据库插入和异常判断 Report report new Report(); report.setUserId(dto.getUserId()); report.setTemperature(dto.getTemperature()); report.setSymptom(dto.getSymptom()); report.setLocation(dto.getLocation()); reportMapper.insert(report); // 插入后检查温度是否超过阈值如果是记录一条风险日志 if (dto.getTemperature() 37.3) { riskRecordMapper.insert(new RiskRecord(dto.getUserId(), HIGH, 体温异常)); } return SubmitReportResult.ok(); }这段代码有两个细节值得展开。第一synchronized修饰方法在单体架构下有效但如果是多实例部署就不够了需要换成Redis分布式锁或者直接用setIfAbsent本身的原子性就够用。这里用了Redis的原子操作天然就是分布式安全的。第二体温数据要校验范围前端可以做0~50的限制后端也必须做一次校验防止有人写-1或999。这种“前后双校验”不是重复劳动而是防御恶意请求的关键。还有一个小坑日期格式的问题。如果用LocalDate.now()数据库时区必须和小程序所在时区一致否则在晚上接近24点的时候前端显示“已打完卡”后端却是第二天容易产生隔天数据错乱。我在部署环境里统一把JVM时区和MySQL时区都设置成Asia/Shanghai这个问题才彻底消失。3.3 后台数据统计与导出实现后台统计页是管理员的日常使用界面用来查看当天打卡率、异常人数、各区域分布。这里不需要前端做图表组件用echarts小程序版也可以但会增加包体我的做法是后端返回统一格式的JSON前端用简单的canvas或者CSS条形图展示够用就行。导出Excel是导师和领导最喜欢的功能虽然技术上不复杂但容易在处理大数据时卡死。我的建议是导出操作不要和预览操作用同一个接口这是很多新人容易踩的坑。比如点击“查看统计”是实时查询几万条记录点击“导出Excel”如果也实时生成同步等待时间过长可能让用户以为系统崩溃了。稳妥的做法是导出时先异步生成文件然后通过消息通知或轮询的方式下载。下面是异步导出的一个简化思路public void exportReport(User admin, ExportDTO dto) { // 1. 写一条导出任务记录状态为PROCESSING ExTask task new ExTask(); task.setUserId(admin.getId()); task.setStatus(PROCESSING); exTaskMapper.insert(task); // 2. 提交到线程池或消息队列 executor.submit(() - { ListReport list reportMapper.selectByDate(dto.getStartDate(), dto.getEndDate()); File file ExcelGenerator.generate(list); task.setFileUrl(uploadToOss(file)); task.setStatus(SUCCESS); exTaskMapper.updateById(task); }); }这种模式既保证了“点击导出秒回”又不会卡死请求线程。如果你不想引入太重的中间件直接用Spring自带的Async加一个线程池也完全满足小规模项目需求不要一上来就上Kafka杀鸡焉用牛刀。4. 一键部署方案从Dockerfile到容器编排4.1 为什么选择容器化部署很多开发同学有个误区能本地跑起来就算交付了。但实际到了服务器上环境不一致、依赖缺失、端口冲突都会让人崩溃。尤其这种有前端、后端、数据库、缓存的系统全靠手动部署出一次错就要排查一个小时。用Docker的好处是把环境固化到镜像里开发时是什么环境部署时还是什么环境。尤其是Java应用最烦的就是服务器上的JDK版本和本地不一致用Docker FROMopenjdk:8-jdk-alpine直接规避掉。数据库、Redis也分别用官方镜像互相隔离删除重建都非常干净。4.2 Dockerfile与镜像构建实录后端镜像我用了一个很常规但好用的两阶段构建# 第一阶段构建项目 FROM maven:3.8-openjdk-8 AS builder WORKDIR /app COPY pom.xml . RUN mvn dependency:go-offline COPY src ./src RUN mvn clean package -DskipTests # 第二阶段运行镜像 FROM openjdk:8-jre-alpine WORKDIR /app COPY --frombuilder /app/target/epide-p-0.0.1-SNAPSHOT.jar app.jar EXPOSE 8080 ENTRYPOINT [java, -jar, /app/app.jar]构建命令很简单docker build -t epide-backend:v1 .。这里有个小优化把mvn dependency:go-offline单独放一层后续如果只改业务代码Maven依赖层能走缓存构建速度会快很多。我在实际部署时第一次构建用了近10分钟后面每次只改代码基本2分钟就完成体验提升很大。前端小程序其实不需要构建成镜像因为小程序不是直接在服务器上运行的它打包成代码包上传到微信平台了。那部署什么呢部署的是“ 后端管理平台的Web页面 ”比如给管理员用的统计页面这个是Web应用可以用Nginx跑。它的Dockerfile更简单FROM nginx:alpine COPY dist/ /usr/share/nginx/html/ COPY nginx.conf /etc/nginx/conf.d/default.conf4.3 使用docker-compose拉起整套服务现在很多教程喜欢直接敲docker run跑N个容器这样一次两次没问题可一旦要重建就很痛苦。我推荐用一个docker-compose.yml管理整套服务代码托管时也可以把编排文件放在项目根目录别人拿到仓库只需要一条命令就能起服务这也是“一键部署”的真正含义。下面是我在某模拟项目X中使用的简化版编排配置version: 3.8 services: mysql: image: mysql:8.0 container_name: epide-mysql environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_DATABASE: epide TZ: Asia/Shanghai ports: - 3306:3306 volumes: - ./mysql-data:/var/lib/mysql networks: - epide-net redis: image: redis:6.2 container_name: epide-redis command: redis-server --appendonly yes ports: - 6379:6379 networks: - epide-net backend: build: ./backend container_name: epide-backend depends_on: - mysql - redis environment: SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/epide?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai SPRING_REDIS_HOST: redis TZ: Asia/Shanghai ports: - 8080:8080 networks: - epide-net web: build: ./web-admin container_name: epide-web depends_on: - backend ports: - 80:80 networks: - epide-net networks: epide-net: driver: bridge注意几个点第一数据库密码不要写在文件里用.env文件配合${MYSQL_ROOT_PASSWORD}来注入.env一定加进.gitignore第二depends_on只是控制启动顺序不代表MySQL已经完全可用所以后端启动时最好加一个“等待数据库就绪”的机制要么在启动脚本里做健康检查要么在Spring Boot配置里加上spring.datasource.hikari.connection-timeout并把重试时间调长第三生产环境尽量给MySQL和Redis的端口做内网隔离不要暴露到公网但自己学习阶段为方便可以走默认端口。执行流程走到这里就只剩三行命令了docker-compose up -d mysql redis docker-compose up -d --build backend docker-compose up -d --build web如果镜像都准备好了直接docker-compose up -d也一样。想停止时就docker-compose down数据卷会保留重启不会丢数据。4.4 HTTPS与域名绑定经验小程序正式上线有一个门槛所有请求的接口地址必须走HTTPS而且域名必须是在微信公众平台后台配置过的合法域名。很多新手把后端跑了但图省事用IP端口依旧无法请求因为微信只允许特定域名的请求。我自己的做法是自己有一个备案域名比如api.example.com虚构示例用Nginx做反向代理把/api/转发到http://127.0.0.1:8080/。然后申请免费SSL证书配置到Nginx上就可以实现https://api.example.com/api/user/login这样的合法API。下面是一段简化的Nginx配置部署在Web容器里server { listen 443 ssl; server_name api.example.com; ssl_certificate /etc/nginx/ssl/example.com.crt; ssl_certificate_key /etc/nginx/ssl/example.com.key; location /api/ { proxy_pass http://backend:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }这里最关键的是配置proxy_pass http://backend:8080;因为在docker-compose的网络里服务名backend就是容器的DNS名不需要写成127.0.0.1。如果你是在宿主机上直接跑了Nginx就要写成http://127.0.0.1:8080很容易弄混。5. 常见问题与排查记录5.1 小程序上传数据偶发超时有个同学跟我反馈打卡接口偶尔要超过10秒才返回但数据库查询都很正常。后来排查发现是上线前把wx.request的timeout设置成了默认60秒但后端在高峰期线程池被打满请求排队时间过长。这里有两层优化第一层前端把timeout改成15秒避免用户等太久以为卡死第二层后端给HikariCP连接池做了配置最大连接数从默认10调到了30并且给关键接口加了Redis缓存把高频读数据的耗时降到毫秒级。调整之后打卡接口平均响应稳定在600ms以内。5.2 数据库连接池爆满排查还有一次是部署到服务器后第二天早上管理员说系统打不开登录日志里全是“连接池无空闲连接”。我一看是数据库连接池线程都在等待但查询SHOW PROCESSLIST发现没有真正的慢查询说明问题不在SQL而是连接泄漏。很多初学Spring Boot的人不知道当自己手动开了数据库连接又没关时连接会一直占用。后来我把项目中所有JdbcTemplate或MyBatis的调用改成使用try-with-resources同时在application.yml里加了spring.datasource.hikari.max-lifetime: 1800000让连接最多存活30分钟才能被回收。这个配置看起来小但能避免不少隐性事故。最终的连接池配置可以参考spring: datasource: hikari: maximum-pool-size: 30 minimum-idle: 10 connection-timeout: 30000 max-lifetime: 18000005.3 部署后微信分享白屏的坑我们的系统有一个“风险提醒”页面用户点击分享按钮想把页面转发给同学。结果测试时发现在微信里打开这个分享卡片点击后会进入一个白屏页面。排查了半天发现原因是小程序页面路径写错了——因为我的页面路径是pages/admin/index/index但分享配置里写成了pages/index/index路径前面的二级目录被丢了。更重要的是所有分享出去的页面必须确认该页面是否需要登录态。如果用户A分享给用户BB打开时没有token页面一上来就请求数据token为空直接被后端拒绝页面就渲染不出来。正确的做法是在分享页面的onLoad里做好未登录时的容错处理要么引导用户先去登录要么展示一个“非本人身份访问”的提示页而不是把错误信息暴露出来。有一说一这种“微信分享白屏”的坑极其零碎但在实际运营中很影响体验。后来我在utils/request.js里加了一个统一处理请求返回401时自动跳转到登录页。这样共享页面至少不会直接白屏而是提醒你“身份已过期请重新登录”用户能明确知道下一步该怎么做。我还整理过一份“上线前巡检清单”这里直接分享出来检查项说明合法域名配置小程序后台是否已经添加HTTPS接口域名用户隐私协议是否在App.json里声明需要收集的信息字段服务端幂等打卡接口是否防止重复提交权限校验管理端接口是否做了角色校验时区设置MySQL和JVM是否统一为Asia/Shanghai数据备份是否配置了MySQL定时备份日志级别生产环境是否把日志级别调成了INFO以上写在最后这套系统我从0到1的完成时间前后大概用了两周。第一周把小程序端和后端接口全跑通第二周才专门处理部署和安全性调优。到最后你会发现真正费时间的不是写代码而是把各种边界条件和环境问题磨平。尤其是像“同一用户同一天只能打一次卡”“后端接口必须鉴权”“部署时密码不能写死”这些事虽然不起眼但没做好的话后面维护的每一分钟都在还债。如果你正在做类似的东西我给你的实际建议只有一条先把本地的整套流程跑顺再谈容器化。不要一开始就想着上K8s、上微服务这个项目用Docker Compose已经足够体面。等你的数据量真的到了需要弹性扩展的那天再考虑迁移也不迟。另外所有代码都建议配套写一份简单的README把部署命令和测试账号写清楚不然等过了一个月再回来看自己都不一定记得密码放在哪一个环境变量里。希望这篇实战拆解能帮你少踩几个坑。要是你在复现过程中还有别的奇怪问题可以带着报错日志和现象再聊排障那部分永远比写功能更精彩。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询