各位做计算机毕业设计的朋友应该都有体会:选题方向直接决定后面几个月的开发节奏。医疗类小程序一直是答辩现场比较受欢迎的方向,因为它既有业务深度,又能体现完整的工程能力。今天要分享的这套基于微信小程序在线问诊与电子处方流转平台,把在线挂号问诊、电子处方开具、处方审核流转、药房取药这几个环节串成了一条完整业务链,无论是用于毕业设计还是作为医疗信息化入门项目,都很有参考价值。
文章会从项目背景、技术架构、数据库设计、核心功能实现、部署上线到常见问题排查做一次完整拆解,代码片段都会给出具体文件路径。内容偏向实战落地,零基础的同学也能跟着一步步搭起来。
1. 项目背景与功能定位
1.1 在线问诊与电子处方流转是什么
在线问诊并不是新鲜概念,但医疗行业的问诊流程和普通电商完全不同。线下就医时,医生通过面对面问诊确定病情,然后开具纸质处方,患者凭处方到药房取药。在这套流程中,处方是核心凭证,它连接了“医生诊断”和“药品发放”两个环节。
电子处方流转平台要解决的核心问题,就是把纸质处方电子化,并让这张处方能够在医生、药师、患者、药房之间安全地流动。从业务链路来看,可以简单拆成:
- 患者端:提交问诊申请、与医生图文交流、查看电子处方、在线购药或到店取药。
- 医生端:接收问诊单、查看患者病情描述、在线开具处方、对处方进行电子签名。
- 药师端:审核医生开具的处方,检查用药合理性,审核通过后处方才能进入配药环节。
- 管理后台:管理医生信息、药品目录、处方记录、问诊记录、数据统计等。
微信小程序作为患者端和医生端的主要载体,优势很明显:用户不用下载App,扫一扫就能用,微信生态内的登录、支付、消息通知都能直接复用。这也解释了为什么近几年微信小程序在医疗信息化项目中越来越常见。
1.2 这类毕业设计为什么值得做
从毕业设计的评分角度来看,这个项目覆盖了后端接口开发、数据库设计、小程序前端开发、权限控制、业务流程状态机等多个考察点,技术广度足够。更重要的是,电子处方流转本身有明确的业务规则,比如“处方必须由医生开具”“药师必须审核通过才能发药”“处方有效期通常为3天”等,这些都是可以展开讲述的业务难点。
从实际开发角度来看,这套系统的代码量适中,小程序端 + 后端管理端 + 数据库三部分边界清晰,非常适合两个月左右的开发周期。如果你打算在简历上写“医疗信息化项目经验”,这套流程也能讲出完整的业务闭环。
1.3 功能模块总览
整个平台可以拆成以下核心模块:
| 模块 | 功能说明 | 使用者 |
|---|---|---|
| 登录认证 | 微信授权登录、手机号绑定、医生账号密码登录 | 患者、医生、药师 |
| 在线问诊 | 提交问诊单、医生接单、图文聊天、问诊记录查询 | 患者、医生 |
| 电子处方 | 医生开具处方、处方模板、电子签名 | 医生 |
| 处方审核 | 药师审核处方、驳回重开、审核记录 | 药师 |
| 处方流转 | 处方状态流转、药房接收、取药码核销 | 患者、药房 |
| 药品管理 | 药品目录维护、库存管理、价格管理 | 管理员 |
| 个人中心 | 个人信息、我的问诊、我的处方、家庭成员 | 患者 |
| 数据统计 | 问诊量、处方量、用药统计 | 管理员 |
这里的“处方流转”是项目的灵魂模块。一张处方从“待医生开具”到“待药师审核”,再到“待取药”,最后“已完成”,每一步都有状态变更记录。状态机设计得好不好,直接决定系统是否经得起推敲。
2. 技术架构与开发环境
2.1 技术选型
考虑到毕业设计项目的开发效率和学习成本,这里推荐一套非常常见的技术组合:
- 小程序端:微信小程序原生框架(WXML + WXSS + JavaScript)
- 后端接口:Spring Boot 2.x + MyBatis Plus
- 数据库:MySQL 8.0
- 缓存:Redis(用于验证码、登录态、高频数据缓存)
- 对象存储:阿里云 OSS 或腾讯云 COS(用于用户头像、问诊图片上传)
- 接口文档:Swagger / Knife4j
- 部署环境:云服务器 + Nginx
这套技术栈不一定是最新的,但胜在稳定、资料多、答辩时能讲清楚。如果你想挑战更现代的方案,也可以把后端换成 Spring Cloud Alibaba 微服务架构,将用户服务、处方服务、药品服务拆开,但开发量和部署复杂度会明显上升,毕业设计周期内需要谨慎评估。
2.2 版本声明与运行环境说明
版本需要根据你的项目实际情况调整,下面以常见环境为例,重点演示配置思路:
| 软件 | 版本建议 | 说明 |
|---|---|---|
| JDK | 1.8 或 11 | Spring Boot 2.x 推荐 |
| Maven | 3.6+ | 项目依赖管理 |
| MySQL | 8.0 | 5.7 也可以,注意驱动差异 |
| Redis | 6.x | 用于缓存与登录态 |
| 微信开发者工具 | 最新稳定版 | 调试小程序端 |
| Node.js | 14+ | 某些构建工具需要 |
如果后续要发布上线,还需要准备一个已认证的微信小程序账号。个人主体小程序部分医疗类接口可能受限,毕业设计阶段使用测试号即可,但如果是真实上线演示,建议提前确认类目资质要求。
2.3 项目工程结构
整个项目建议采用前后端分离结构:
online-consult-platform/ ├── miniapp/ # 微信小程序端 │ ├── pages/ │ │ ├── index/ # 首页 │ │ ├── consult/ # 问诊相关 │ │ ├── prescription/ # 处方相关 │ │ ├── user/ # 个人中心 │ │ └── login/ # 登录页 │ ├── utils/ │ │ ├── request.js # 网络请求封装 │ │ └── auth.js # 登录态处理 │ ├── app.js │ ├── app.json │ └── project.config.json ├── server/ # 后端服务 │ ├── src/main/java/com/example/consult/ │ │ ├── controller/ # 接口层 │ │ ├── service/ # 业务逻辑层 │ │ ├── mapper/ # 数据访问层 │ │ ├── entity/ # 实体类 │ │ ├── config/ # 配置类 │ │ └── common/ # 通用工具与返回结果 │ ├── src/main/resources/ │ │ ├── mapper/ # MyBatis XML(如需要) │ │ └── application.yml │ └── pom.xml └── sql/ # 数据库初始化脚本前后端分离的好处是接口可以单独测试,小程序端开发不依赖后端联调进度。后续如果时间充裕,可以再单独做一个 Web 管理后台。
3. 数据库设计
数据库设计是这类平台的重中之重。问诊、处方流转这些业务都有明确的状态依赖,表结构设计得合理,写业务代码时会轻松很多;表结构设计不合理,后期改起来会非常痛苦。
3.1 核心表结构
这里列出最重要的几张表:
用户表sys_user
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| openid | varchar(64) | 微信openid |
| phone | varchar(20) | 手机号 |
| username | varchar(50) | 用户名(医生/管理员登录用) |
| password | varchar(100) | 密码(BCrypt加密) |
| real_name | varchar(20) | 真实姓名 |
| role | tinyint | 1患者 2医生 3药师 0管理员 |
| doctor_title | varchar(20) | 医生职称 |
| department | varchar(50) | 所属科室 |
| create_time | datetime | 创建时间 |
问诊单表consult_record
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| patient_id | bigint | 患者用户ID |
| doctor_id | bigint | 医生用户ID |
| patient_desc | text | 病情描述 |
| images | varchar(500) | 病情图片,逗号分隔 |
| status | tinyint | 1待接诊 2问诊中 3已完成 4已取消 |
| create_time | datetime | 提交时间 |
| finish_time | datetime | 完成时间 |
处方表prescription
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| consult_id | bigint | 关联问诊单ID |
| patient_id | bigint | 患者ID |
| doctor_id | bigint | 开方医生ID |
| pharmacist_id | bigint | 审核药师ID |
| prescription_no | varchar(32) | 处方编号,唯一 |
| diagnosis | varchar(200) | 诊断结果 |
| status | tinyint | 1待审核 2审核通过 3已驳回 4已取药 5已过期 |
| effective_days | int | 有效期天数,默认3 |
| signature | varchar(200) | 医生电子签名 |
| reject_reason | varchar(200) | 驳回原因 |
| create_time | datetime | 开方时间 |
| audit_time | datetime | 审核时间 |
处方明细表prescription_item
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| prescription_id | bigint | 处方ID |
| drug_id | bigint | 药品ID |
| drug_name | varchar(50) | 药品名称快照 |
| spec | varchar(50) | 规格 |
| dosage | varchar(50) | 用法用量 |
| days | int | 用药天数 |
| quantity | int | 数量 |
| price | decimal(10,2) | 单价快照 |
药品表drug_info
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| drug_code | varchar(32) | 药品编码 |
| drug_name | varchar(50) | 药品名称 |
| spec | varchar(50) | 规格 |
| unit | varchar(10) | 单位 |
| manufacturer | varchar(100) | 生产厂家 |
| price | decimal(10,2) | 销售价格 |
| stock | int | 库存 |
| status | tinyint | 0下架 1上架 |
处方流转记录表prescription_flow
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| prescription_id | bigint | 处方ID |
| from_status | tinyint | 原状态 |
| to_status | tinyint | 新状态 |
| operator_id | bigint | 操作人ID |
| remark | varchar(200) | 操作说明 |
| create_time | datetime | 操作时间 |
处方流转记录表很多人容易忽略,但它非常重要。一张处方从开立到最终核销,中间经历过哪些操作、分别是谁操作的、什么时间操作的,都要能追溯。这也是答辩时可以和老师重点讲的业务亮点。
3.2 数据库初始化脚本示例
下面是prescription表的建表 SQL:
CREATE TABLE `prescription` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', `consult_id` bigint(20) DEFAULT NULL COMMENT '问诊单ID', `patient_id` bigint(20) NOT NULL COMMENT '患者ID', `doctor_id` bigint(20) NOT NULL COMMENT '医生ID', `pharmacist_id` bigint(20) DEFAULT NULL COMMENT '审核药师ID', `prescription_no` varchar(32) NOT NULL COMMENT '处方编号', `diagnosis` varchar(200) DEFAULT NULL COMMENT '诊断结果', `status` tinyint(4) NOT NULL DEFAULT '1' COMMENT '1待审核 2审核通过 3已驳回 4已取药 5已过期', `effective_days` int(11) NOT NULL DEFAULT '3' COMMENT '有效期天数', `signature` varchar(200) DEFAULT NULL COMMENT '医生电子签名', `reject_reason` varchar(200) DEFAULT NULL COMMENT '驳回原因', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `audit_time` datetime DEFAULT NULL COMMENT '审核时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_prescription_no` (`prescription_no`), KEY `idx_patient_id` (`patient_id`), KEY `idx_doctor_id` (`doctor_id`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='电子处方表';关于索引,有三点建议:prescription_no要建唯一索引,因为处方编号是业务上的唯一标识;patient_id、doctor_id是高频查询条件,必须建普通索引;status在后台管理列表筛选时经常用到,也建议建索引。小数据量时看不出差别,但到了答辩演示几万条测试数据时,有索引和没索引的查询速度差别会很明显。
4. 核心后端接口实现
4.1 统一返回结果封装
后端接口必须有一个统一的返回结构,这样小程序端处理响应时逻辑才简洁。建议使用以下结构:
// 文件路径:server/src/main/java/com/example/consult/common/Result.java package com.example.consult.common; import lombok.Data; @Data public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> success(T data) { Result<T> result = new Result<>(); result.setCode(200); result.setMessage("操作成功"); result.setData(data); return result; } public static <T> Result<T> error(String message) { Result<T> result = new Result<>(); result.setCode(500); result.setMessage(message); return result; } public static <T> Result<T> error(Integer code, String message) { Result<T> result = new Result<>(); result.setCode(code); result.setMessage(message); return result; } }接口层所有方法都返回Result<T>,前端统一从res.data.code判断请求是否成功。代码层面约定code=200表示成功,其他都为失败,这比让前端逐接口判断字段要省事得多。
4.2 小程序登录接口设计
微信小程序的登录流程和多端系统有区别。核心流程是:
- 小程序端调用
wx.login获取临时code。 - 将
code发送到后端。 - 后端调用微信接口
https://api.weixin.qq.com/sns/jscode2session,用code换取openid和session_key。 - 首次登录的用户,在后端用户表中自动创建账号。
- 后端生成自定义登录态 token,返回给小程序端。
- 小程序端后续请求携带 token,后端根据 token 识别用户身份。
需要注意,2021 年之后微信官方逐步收紧了wx.getUserProfile获取用户头像昵称的能力,现在推荐直接使用头像昵称填写能力,通过button组件上的open-type="chooseAvatar"和nicknameinput 来获取头像昵称。毕业设计阶段如果不想处理这个流程,可以直接让用户绑定手机号,或者使用默认头像。
后端登录接口的核心代码如下:
// 文件路径:server/src/main/java/com/example/consult/controller/AuthController.java @RestController @RequestMapping("/api/auth") public class AuthController { @Autowired private AuthService authService; @PostMapping("/login") public Result<LoginResponse> login(@RequestBody LoginRequest request) { if (StringUtils.isBlank(request.getCode())) { return Result.error("code不能为空"); } LoginResponse response = authService.wxLogin(request.getCode()); return Result.success(response); } }AuthService中主要做四件事:调微信接口换 openid、查询用户是否已存在、不存在则注册、存在则生成 token 并更新登录时间。
4.3 医生开具处方接口
开方接口是整个系统里业务规则最多的一个接口。它涉及三张表的写入操作:处方主表、处方明细表、处方流转记录表。因此这个接口必须使用事务。
// 文件路径:server/src/main/java/com/example/consult/service/impl/PrescriptionServiceImpl.java @Override @Transactional(rollbackFor = Exception.class) public PrescriptionVO createPrescription(PrescriptionCreateDTO dto) { // 1. 校验问诊单状态,必须处于"问诊中" ConsultRecord consult = consultRecordMapper.selectById(dto.getConsultId()); if (consult == null || consult.getStatus() != 2) { throw new BusinessException("问诊单状态异常,无法开方"); } // 2. 校验开方医生是否是接诊医生 if (!consult.getDoctorId().equals(dto.getDoctorId())) { throw new BusinessException("非接诊医生无权开方"); } // 3. 创建处方主记录 Prescription prescription = new Prescription(); prescription.setConsultId(dto.getConsultId()); prescription.setPatientId(consult.getPatientId()); prescription.setDoctorId(dto.getDoctorId()); prescription.setPrescriptionNo(generatePrescriptionNo()); prescription.setDiagnosis(dto.getDiagnosis()); prescription.setStatus(1); prescription.setEffectiveDays(3); prescriptionMapper.insert(prescription); // 4. 保存处方明细 for (PrescriptionItemDTO item : dto.getItems()) { PrescriptionItem detail = new PrescriptionItem(); detail.setPrescriptionId(prescription.getId()); detail.setDrugId(item.getDrugId()); detail.setDrugName(item.getDrugName()); detail.setSpec(item.getSpec()); detail.setDosage(item.getDosage()); detail.setDays(item.getDays()); detail.setQuantity(item.getQuantity()); detail.setPrice(item.getPrice()); prescriptionItemMapper.insert(detail); } // 5. 记录处方流转 PrescriptionFlow flow = new PrescriptionFlow(); flow.setPrescriptionId(prescription.getId()); flow.setFromStatus(0); flow.setToStatus(1); flow.setOperatorId(dto.getDoctorId()); flow.setRemark("医生开具处方"); prescriptionFlowMapper.insert(flow); return convertToVO(prescription); }这里有两个容易被忽略的点:一是开方前必须确认问诊单状态,否则会出现已经完成的问诊单再次被开方的情况;二是从业务角度讲,非接诊医生不能给这个患者开方,代码里也要做校验。有了这些约束,答辩时老师问“你怎么保证医疗流程的严谨性”,就能给出具体回答。
4.4 药师审核处方接口
药师审核是处方流转中极其关键的一个环节。审核接口会改变处方状态,审核通过后处方才能进入后续取药流程,审核驳回则需要填写驳回原因。
// 文件路径:server/src/main/java/com/example/consult/service/impl/PrescriptionServiceImpl.java @Override @Transactional(rollbackFor = Exception.class) public void auditPrescription(Long prescriptionId, Long pharmacistId, Integer auditResult, String rejectReason) { Prescription prescription = prescriptionMapper.selectById(prescriptionId); if (prescription == null) { throw new BusinessException("处方不存在"); } if (prescription.getStatus() != 1) { throw new BusinessException("该处方不在待审核状态"); } if (auditResult == 2) { // 审核通过 prescription.setStatus(2); prescription.setPharmacistId(pharmacistId); prescription.setAuditTime(new Date()); } else if (auditResult == 3) { // 审核驳回 prescription.setStatus(3); prescription.setPharmacistId(pharmacistId); prescription.setRejectReason(rejectReason); prescription.setAuditTime(new Date()); } else { throw new BusinessException("无效的审核结果"); } prescriptionMapper.updateById(prescription); // 记录流转日志 PrescriptionFlow flow = new PrescriptionFlow(); flow.setPrescriptionId(prescription.getId()); flow.setFromStatus(1); flow.setToStatus(auditResult); flow.setOperatorId(pharmacistId); flow.setRemark(auditResult == 2 ? "药师审核通过" : "药师驳回:" + rejectReason); prescriptionFlowMapper.insert(flow); }审核驳回时必须填写原因,这既符合医疗场景的规范要求,也是一个很好的功能展示点。审核通过的处方如果要发药,药房那边会根据取药码来核销,确保处方被使用且只能使用一次。
4.5 处方状态流转整体说明
平台中处方状态的完整流转路径如下:
医生开方(1) → 药师审核(2/3) → 待取药(2) → 药房核销(4) ↘ 驳回(3) → 医生可重新调整或结束状态使用tinyint存储,不要直接存中文。实体类中建议补充状态字段的说明注释,小程序端再做一层映射。这样数据库层面、后端代码、前端展示三方都不会乱。
5. 微信小程序端开发
5.1 小程序项目初始化与目录结构
微信小程序端使用原生框架开发。打开微信开发者工具,选择“小程序”项目,填入 AppID。这里有一点要特别注意:如果使用测试号,登录接口的jscode2session也能正常调用,但部分 wx 接口(如 getUserProfile)在测试号下会有限制。毕业设计演示环境建议直接注册一个小程序账号,个人主体即可,开发阶段不需要发布。
以本项目为例,小程序的app.json需要注册页面路径和底部 tabBar:
{ "pages": [ "pages/index/index", "pages/consult/list", "pages/consult/detail", "pages/consult/create", "pages/prescription/detail", "pages/prescription/list", "pages/user/index" ], "window": { "navigationBarTitleText": "在线问诊", "navigationBarBackgroundColor": "#07c160", "navigationBarTextStyle": "white" }, "tabBar": { "color": "#999999", "selectedColor": "#07c160", "list": [ { "pagePath": "pages/index/index", "text": "首页" }, { "pagePath": "pages/consult/list", "text": "问诊" }, { "pagePath": "pages/user/index", "text": "我的" } ] } }tabBar 里没有放处方入口,是因为处方是从问诊详情页进入的。这样用户的操作路径更聚焦:首页/问诊列表 → 进入问诊详情 → 查看处方 → 凭处方取药。
5.2 网络请求封装
每个小程序页面都直接调用wx.request会很难维护,封装一个统一的请求工具类是基本工程素养。
// 文件路径:miniapp/utils/request.js const BASE_URL = 'https://your-server-domain.com/api'; function request(url, method, data, needAuth = true) { return new Promise((resolve, reject) => { // 从本地缓存读取 token const token = wx.getStorageSync('token'); const header = { 'Content-Type': 'application/json' }; if (needAuth && token) { header['Authorization'] = token; } wx.request({ url: BASE_URL + url, method: method, data: data, header: header, success: (res) => { if (res.statusCode === 200) { const bizData = res.data; if (bizData.code === 200) { resolve(bizData.data); } else { // 登录态失效时统一跳到登录页 if (bizData.code === 401) { wx.removeStorageSync('token'); wx.navigateTo({ url: '/pages/login/index' }); } wx.showToast({ title: bizData.message, icon: 'none' }); reject(bizData); } } else { wx.showToast({ title: '服务器异常', icon: 'none' }); reject(res); } }, fail: (err) => { wx.showToast({ title: '网络请求失败', icon: 'none' }); reject(err); } }); }); } module.exports = { get: (url, data) => request(url, 'GET', data), post: (url, data) => request(url, 'POST', data) };这里把Authorization放在 header 中携带 token,后端通过拦截器统一校验,除了登录接口和药品列表等公开接口外,其余接口都需要登录才能访问。
5.3 登录页面与代码实现
登录页面的核心代码主要包括两步:获取微信头像昵称 + 调用后端登录接口。这里涉及一个常见坑点:wx.login获取的 code 只有 5 分钟有效期,且一次性,必须及时传给后端换 openid。
小程序登录参考实现:
<!-- 文件路径:miniapp/pages/login/index.wxml --> <view class="login-container"> <view class="avatar-wrapper"> <button class="avatar-btn" open-type="chooseAvatar" bind:chooseavatar="onChooseAvatar"> <image class="avatar" src="{{avatarUrl}}" mode="aspectFill"/> </button> </view> <input type="nickname" class="nickname-input" placeholder="请输入昵称" value="{{nickname}}" bind:blur="onNicknameChange" /> <button class="login-btn" bindtap="onLogin">微信一键登录</button> </view>// 文件路径:miniapp/pages/login/index.js const request = require('../../utils/request'); Page({ data: { avatarUrl: '/assets/default-avatar.png', nickname: '' }, onChooseAvatar(e) { this.setData({ avatarUrl: e.detail.avatarUrl }); }, onNicknameChange(e) { this.setData({ nickname: e.detail.value }); }, onLogin() { const that = this; // 1. 获取微信登录 code wx.login({ success(res) { if (res.code) { // 2. 调用后端登录接口 request.post('/auth/login', { code: res.code, nickname: that.data.nickname, avatarUrl: that.data.avatarUrl }).then(data => { // 3. 存储登录态 wx.setStorageSync('token', data.token); wx.setStorageSync('userInfo', data.userInfo); wx.switchTab({ url: '/pages/index/index' }); }).catch(() => {}); } else { wx.showToast({ title: '登录失败', icon: 'none' }); } } }); } });需要特别提醒的是,真机调试时经常出现wx.login获取到 code 但后端换 openid 失败的情况。常见原因包括:小程序 AppID 与后端配置的 AppSecret 不一致、请求域名没有在小程序后台配置为服务器域名白名单、服务器无法访问微信接口等。这些在后续排错章节会重点说明。
5.4 问诊单提交页面
问诊单提交页面是患者使用频率最高的页面之一。核心信息包括症状描述、病情图片、科室、紧急程度等。这里展示提交问诊单的核心数据结构和图片上传部分。
// 文件路径:miniapp/pages/consult/create.js 核心片段 Page({ data: { symptom: '', images: [], department: '', urgency: 1 }, // 选择病情图片 onChooseImage() { const that = this; wx.chooseMedia({ count: 6, mediaType: ['image'], sourceType: ['album', 'camera'], success(res) { const tempFiles = res.tempFiles; const uploadTasks = tempFiles.map(file => that.uploadImage(file.tempFilePath)); Promise.all(uploadTasks).then(urls => { that.setData({ images: that.data.images.concat(urls) }); }); } }); }, uploadImage(filePath) { return new Promise((resolve, reject) => { const token = wx.getStorageSync('token'); wx.uploadFile({ url: BASE_URL + '/file/upload', filePath: filePath, name: 'file', header: { 'Authorization': token }, success(res) { const data = JSON.parse(res.data); if (data.code === 200) { resolve(data.data); } else { reject(data); } }, fail: reject }); }); }, onSubmit() { const { symptom, department, images } = this.data; if (!symptom.trim()) { wx.showToast({ title: '请描述病情', icon: 'none' }); return; } if (!department) { wx.showToast({ title: '请选择科室', icon: 'none' }); return; } request.post('/consult/create', { symptom: symptom, department: department, images: images }).then(() => { wx.showToast({ title: '提交成功', icon: 'success' }); setTimeout(() => { wx.switchTab({ url: '/pages/consult/list' }); }, 1500); }); } });图片上传用wx.uploadFile,每次只传一个文件,多张图片通过Promise.all并发上传。后端接收后返回可访问的 URL,前端再把 URL 数组传给问诊单创建接口。
5.5 处方详情展示
处方详情页是患者查看电子处方的核心页面,主要展示诊断信息、药品清单、开方医生、审核药师、处方状态、取药码等。
对于取药码,建议直接根据处方号生成,规则可以是“前缀 + 处方号后六位”。核销时药房在管理端输入取药码,后端验证处方状态是否为“审核通过”,只有审核通过的处方能核销成功。这样可以保证一张处方只能被使用一次。
处方状态需要在前端做映射:
const STATUS_MAP = { 1: { text: '待审核', color: 'orange' }, 2: { text: '审核通过', color: 'green' }, 3: { text: '已驳回', color: 'red' }, 4: { text: '已取药', color: 'blue' }, 5: { text: '已过期', color: 'gray' } };6. 安全与权限控制设计
医疗项目与其他业务系统相比,用户数据的敏感性更高。毕业设计可以简化,但相关设计思路一定要讲清楚。
6.1 登录态校验
后端建议使用拦截器统一校验 token。自我实现一个简单 token 方案也可以:登录成功后生成 UUID 作为 token,存到 Redis 中,并设置过期时间;后续请求从 header 中取 token,到 Redis 中查询对应的用户 ID。
这里不推荐使用 JWT 存大量用户信息,因为医疗场景中需要能主动让 token 失效(比如患者申诉账号异常、管理员封禁账号)。Redis + token 方案更适合这类需要强管控的场景。
6.2 接口权限控制
不同角色的接口权限必须限制。最直接的做法是在后端定义接口注解。
// 文件路径:server/src/main/java/com/example/consult/config/RequireRole.java @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface RequireRole { int[] value(); }在 Controller 中使用:
@PostMapping("/prescription/create") @RequireRole({2}) // 仅医生可访问 public Result<PrescriptionVO> createPrescription(@RequestBody PrescriptionCreateDTO dto) { return Result.success(prescriptionService.createPrescription(dto)); } @PostMapping("/prescription/audit") @RequireRole({3}) // 仅药师可访问 public Result<Void> auditPrescription(@RequestBody AuditDTO dto) { prescriptionService.auditPrescription(dto.getPrescriptionId(), dto.getPharmacistId(), dto.getAuditResult(), dto.getRejectReason()); return Result.success(null); }拦截器读取当前用户的角色,如果不在允许范围内则直接返回 403。这样即使有人拿到了普通患者的 token,也无法调用医生开方接口。
6.3 数据权限校验
除了接口权限,数据权限也同样重要。患者只能查看自己的问诊单、处方,医生只能查看分配给自己的问诊单。比如“查询处方详情”接口,代码里必须校验当前登录用户是否是该处方的患者或开方医生或审核药师。
很多毕业设计项目只做了接口权限、不做数据权限,导致任意登录用户只要知道处方ID就能查别人的数据。无论是在答辩还是在实际工作中,这都是严重的逻辑漏洞。这部分可以作为项目亮点重点强调。
6.4 敏感信息脱敏
用户手机号、身份证号、家庭住址等敏感字段在接口返回时要做脱敏处理。手机号脱敏规则是保留前3位和后4位,如138****5678。日志输出时也不要打印完整手机号、openid 等敏感信息。
7. 部署上线与开发调试
7.1 后端部署
后端推荐部署到云服务器上。核心步骤如下:
- 在服务器安装 JDK、MySQL、Redis、Nginx。
- 创建数据库并导入 SQL 脚本。
- 修改
application.yml中的数据库地址、Redis 地址、微信小程序 AppID 和 AppSecret。 - 使用 Maven 打包:
mvn clean package -DskipTests- 将 jar 包上传到服务器并启动:
java -jar online-consult-server.jar --spring.profiles.active=prod后端接口地址需要是小程序后台可访问的 HTTPS 域名。注意,微信小程序正式环境要求域名必须备案,且必须 HTTPS,不支持 IP 地址访问。
7.2 小程序端配置
在request.js中,将BASE_URL修改为正式环境域名。然后登录微信公众平台,在小程序后台配置 request 合法域名。
本地开发阶段可以使用开发工具的“不校验合法域名”选项,真机调试时如果不想改域名,也可以开启调试模式。但正式演示前一定要把合法域名配置好,否则真机上所有请求都会被拦截。
7.3 常见部署报错汇总
下面这几个问题在部署阶段经常遇到:
| 问题现象 | 常见原因 | 解决思路 |
|---|---|---|
小程序请求接口报http://或域名不在合法域名列表中 | 未配置 request 合法域名 | 后台添加 HTTPS 域名并上传校验文件 |
| 后端能登录,小程序端报“登录失败” | AppID 和 AppSecret 不匹配 | 检查 application.yml 与小程序后台 AppID |
真机调试请求失败net::ERR_CONNECTION_RESET | 云服务器防火墙未开放端口,或域名没有解析 | 检查安全组与 Nginx 配置 |
数据库连接报Access denied | 账号密码错误或授权不足 | 单独用客户端工具连接测试 |
| 上传图片成功但无法访问 | OSS Bucket 权限设置为私有 | 修改为公共读或使用签名 URL |
7.4 上线注意事项
在项目上线前,强烈建议走一遍下面的清单:
- 微信小程序后台实名认证和类目审核是否已完成。
- 所有敏感接口是否已配置权限注解。
- 数据库是否做过完整备份,是否有定期备份策略。
- 服务器是否配置了 HTTPS 证书,证书是否在有效期内。
- 小程序体验版二维码是否已生成,便于演示前快速用真机体验。
- 系统是否已去除开发者工具中的调试打印日志。
如果只是毕业答辩演示,体验版即可满足需求,不需要提交审核发布。体验版二维码在小程序后台的“版本管理”中可以找到。
8. 常见问题与排查思路
8.1 小程序获取登录用户失败
这是微信小程序开发中比较常见的问题。旧版本代码通常用wx.getUserProfile来实现登录,但现在很多场景下该接口返回的信息有限甚至报错。
排查步骤:
- 确认小程序基础库版本,建议在 2.27.1 以上使用头像昵称填写能力。
- 检查是否使用了
open-type="chooseAvatar"获取头像。 - 检查昵称 input 是否设置了
type="nickname"。 - 确认调用
wx.login后,code 是否成功传递到后端。
8.2 真机调试时请求失败
真机测试报failed: net::ERR_CONNECTION_RESET是高频问题。这类报错通常与网络环境或域名配置有关。
排查顺序:
- 如果使用本地后端,请确保手机和电脑在同一个局域网。
- 如果使用云服务器,检查云厂商安全组的入方向端口是否放开。
- 小程序端 BASE_URL 是否用的是 HTTPS 域名,且域名已备案。
- 在真机上用浏览器访问该接口地址,看是否能正常返回 JSON 数据。
8.3 微信开发者工具报递归错误
[微信小程序开发者工具] maximum setlocal recursion level reached.这类报错通常不是项目代码问题,而是开发者工具自身或缓存导致的。解决方法是:清理缓存、关闭项目重新打开、升级开发者工具版本。如果依然报错,可以尝试删除项目目录下的node_modules(如果有)以及重新编译。
8.4 处方状态被错误流转
“处方状态被跳步”是业务逻辑中比较容易出现的 bug。比如待审核的处方直接变成已取药,跳过了审核环节。
这类问题要从两个方向修复:
- 后端每个状态流转接口都要做前置状态校验。
- 所有状态变更必须通过统一的状态机处理,不要在多个 Service 里重复写状态修改逻辑。
建议做一个PrescriptionStatusHandler来统一管理状态流转,每个流转动作都声明允许的前置状态和操作角色,这样逻辑就不会散落各处。
9. 工程建议与项目优化方向
9.1 代码层面的建议
- 所有状态字段使用常量类统一管理,不要散落魔法值。建议定义
PrescriptionStatus、ConsultStatus常量类。 - Service 层尽量瘦身,一个方法做好一件事,超过 60 行的方法要思考是否可以拆分。
- 异常统一使用自定义业务异常
BusinessException,配合全局异常处理器返回友好提示。 - 接口入参要做基础校验,建议使用
@Validated注解 +validation依赖。
9.2 架构层面的优化点
如果时间和精力允许,可以考虑以下扩展方向:
- 引入消息队列(如 RocketMQ)处理问诊单分配和处方状态通知,解耦系统模块。
- 接入 WebSocket 或定时轮询实现实时消息通知,医生接诊和药师审核结果能第一时间推送给患者。
- 增加 OAuth2 或 Spring Security 做更标准化的认证授权体系。
- 增加数据统计维度,如医生接诊量排行、药品销量排行、科室问诊趋势图。
9.3 学习与扩展路线
完成这个项目后,你已经掌握了以下核心技能:
- Spring Boot 后端接口开发与事务处理。
- MyBatis Plus 数据持久化操作。
- 微信小程序原生开发与登录流程。
- 角色权限控制与业务状态机设计。
- 前后端联调与云服务器部署。
后续如果想继续深入,可以从两个方向突破:一是深入学习 Spring Security + JWT / OAuth2,把权限体系做到更接近企业级;二是研究微服务架构,把用户、问诊、处方拆成独立服务,引入注册中心 Nacos、远程调用 OpenFeign 等。不管选择哪个方向,这个项目都会是很好的基础支撑。
最后提醒一句:无论是毕业设计还是实际项目,涉及医疗数据时一定要把安全性和合规性放在第一位,演示环境的数据要做好脱敏处理。如果在开发过程中有问题,欢迎留言交流,也可以把这篇文章收藏备用。