这次我们来看一个非常典型的全栈实战项目:SpringBoot + Vue + Uniapp 三端联动的医疗预约问诊小程序。这类项目在 CSDN 和 Git 上一直热度不低,原因是它不只是一个 Demo,而是把小程序端、管理后台端、服务端这三块全部串起来了。对前端转全栈的同学来说,它提供的不是单个页面的实现,而是一条完整的业务链路:用户注册登录、科室医生展示、预约挂号、在线图文问诊、处方归档、健康档案管理。
这个项目最值得关注的点有三个。第一,端侧覆盖完整,患者用 Uniapp 小程序,医生和管理员用 Vue 后台,业务接口统一由 SpringBoot 提供,一次联调就能理解前后端协作方式。第二,业务场景真实,挂号要处理号源扣减的并发问题,问诊要维护会话和消息记录,处方和健康档案要做数据关联,这些都不是简单的增删改查。第三,就业导向明确,前端简历缺后端经验、后端简历缺前端能力的,都可以拿这个项目补一块。
本文会带你从头过一遍:技术选型和系统架构、数据库表设计、SpringBoot 后端核心接口、Vue 管理后台页面、Uniapp 患者端小程序、本地联调验证、部署上线流程,最后再聊聊面试和简历上怎么把这个项目讲出亮点。
适合人群:前端想转全栈的开发者、Java 后端想补全栈能力的初学者、准备课程设计或毕业设计的学生,以及需要在简历里增加一个完整项目经验的人。
1. 核心能力速览
| 能力项 | 说明 |
|---|---|
| 项目类型 | 医疗预约问诊小程序,全栈实战项目 |
| 技术栈 | SpringBoot 后端 + Vue3 管理后台 + Uniapp 患者端 |
| 核心业务 | 用户登录、科室医生展示、预约挂号、在线问诊、处方记录、健康档案 |
| 端侧覆盖 | Uniapp 患者小程序端、Vue 管理后台端、SpringBoot 服务端 |
| 推荐运行环境 | JDK 17 + MySQL 8 + Node 18 及以上 |
| 启动方式 | 后端 IDE 或 jar 启动,Vue 使用 npm run dev,Uniapp 使用 HBuilderX 运行到微信开发者工具 |
| API 能力 | 后端提供 RESTful 接口,统一返回 JSON,支持 Postman / Apifox 调试 |
| 批量任务 | 业务侧主要依赖分页列表和批量状态更新,接口设计预留相应能力 |
| 适合场景 | 前端转全栈练手、Java 后端补前端、课程设计、简历项目 |
| 就业价值 | 补齐后端接口开发、数据库设计、权限认证、联调部署经验 |
补充一句关于定位的问题。医疗两个字听起来很重,但本文按技术演示项目来做:预约、排班、问诊记录、健康档案都属于业务流程演示,使用脱敏测试数据,不涉及真实诊疗和真实患者隐私数据。如果要把小程序上架到微信公众平台,医疗类目需要额外资质,这个在部署章节会再提醒。
2. 适用场景与使用边界
2.1 适合谁
前端转全栈的开发者,是这个项目最主要的受众。很多前端同学的简历里只有页面,没有接口、没有数据库、没有权限控制。面试官问一句“登录状态怎么保存”“接口怎么鉴权”“数据库表你怎么设计”,项目就立不住了。这个项目把这些问题全部覆盖。
Java 后端初学者也适合。SpringBoot 单看增删改查很容易,但结合小程序端之后,就要考虑跨域、Token 传递、文件上传、事务回滚,这些才是实际工作中的常见问题。
在校学生做课设或毕设同样推荐。医疗预约问诊是常见的选题方向,而且有明确的三端结构,论文和答辩的素材都会比较完整。
2.2 能解决什么问题
它解决的是“会单端、不会整条链路”的问题。前端同学能从这里学会写接口、建表、做鉴权;后端同学能学会用 Uniapp 写跨端页面、封装请求、对接微信登录;想做简历项目的人,能获得一个可演示、可讲解、可扩展的完整系统。
业务闭环也完整。患者从浏览科室、选择医生、预约挂号,到发起问诊、收到处方、查看健康档案,整个流程都能跑通。这个闭环本身就是面试时最有说服力的部分。
2.3 不适合什么场景
不要在真实医院或诊所环境直接使用。系统没有经过医疗软件资质认证,也没有对接真实 HIS 系统,只能作为技术练习。
不要使用真实患者数据。开发环境和演示环境一律使用模拟数据,包括姓名、手机号、身份证号、病历内容。
不要在小程序里做“AI 自动诊断”“自动开药”等涉及医疗器械资质判断的功能。技术演示可以聊算法,业务上线必须遵守监管要求。
2.4 合规边界
医疗健康数据属于敏感个人信息。这个项目的正确用法是:所有数据脱敏、所有账号用测试数据、所有图片素材使用无版权风险的内容。
涉及到用户上传的头像、检查报告图片,开发阶段建议保存到本地临时目录或私有测试存储,上线前清理。如果后续要商用,需要走完整的隐私协议、用户授权、数据加密存储和合规审查流程。
小程序上架前,需要确认微信公众平台的类目资质。医疗类目通常要求提供相关许可证。如果只是本地运行、真机体验版、或作为个人学习项目,不受影响。
3. 全栈架构与数据库设计
3.1 端侧分工与技术栈
整个系统的架构可以从三个端来理解。
| 端侧 | 技术栈 | 核心职责 |
|---|---|---|
| 患者小程序端 | Uniapp + Vue3 + Pinia + uni-ui | 科室浏览、医生列表、预约挂号、在线问诊、健康档案 |
| 管理后台端 | Vue3 + Vite + Element Plus + Pinia | 登录、科室管理、医生管理、挂号列表、问诊处理、处方管理 |
| 服务端 | SpringBoot + MyBatis-Plus + MySQL | 登录认证、业务接口、号源控制、问诊会话、文件上传 |
数据流也很清晰。Uniapp 小程序通过封装后的 uni.request 访问后端接口,Vue 后台通过 axios 访问后端接口,后端统一返回{ code, message, data }结构。管理后台和患者端共用一套业务接口,但通过角色权限区分数据范围。
使用 MyBatis-Plus 的原因是开发效率高,单表 CRUD 不需要写 XML,分页插件也能直接复用。业务稍微复杂一点的查询再用自定义 SQL 补充。
3.2 核心业务表设计
数据库是这个项目的骨架。很多人写代码不先设计表,导致后面关联查询非常痛苦。这里建议至少规划这些核心表:
- user:普通用户,也就是患者
- doctor:医生
- department:科室
- appointment:预约挂号单
- consultation:问诊会话
- consultation_message:问诊消息
- prescription:处方记录
- health_record:健康档案
- sys_user:后台账号,对应管理员和医生账号
表关系如下:
- department 一对多 doctor
- doctor 一对多 appointment
- user 一对多 appointment
- user 一对多 consultation
- consultation 一对多 consultation_message
- consultation 一对一 prescription
- user 一对多 health_record
这里特别说明一个设计点:患者账号和后台账号不要混在同一张表里。患者端走的是微信登录体系,医生和管理员走的是账号密码体系,两者的字段、权限、使用场景都不同,分开建表更干净。
3.3 核心建表 SQL 示例
下面是医生表的简化建表语句,实际开发可以在此基础上加排班、评分、擅长领域等字段。
CREATE TABLE `doctor` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', `dept_id` bigint NOT NULL COMMENT '科室ID', `doctor_name` varchar(50) NOT NULL COMMENT '医生姓名', `title` varchar(50) DEFAULT NULL COMMENT '职称', `intro` text COMMENT '医生简介', `avatar` varchar(255) DEFAULT NULL COMMENT '头像URL', `reg_fee` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '挂号费', `total_slots` int NOT NULL DEFAULT '20' COMMENT '每日总号源', `left_slots` int NOT NULL DEFAULT '20' COMMENT '剩余号源', `status` tinyint NOT NULL DEFAULT '1' COMMENT '1启用 0停诊', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='医生表';预约挂号表是这个项目的核心业务表。
CREATE TABLE `appointment` ( `id` bigint NOT NULL AUTO_INCREMENT, `patient_id` bigint NOT NULL COMMENT '患者用户ID', `doctor_id` bigint NOT NULL COMMENT '医生ID', `appointment_date` varchar(20) NOT NULL COMMENT '就诊日期 yyyy-MM-dd', `time_slot` varchar(20) NOT NULL COMMENT '时间段,如 09:00-10:00', `status` tinyint NOT NULL DEFAULT '0' COMMENT '0待就诊 1已完成 2已取消', `order_no` varchar(32) DEFAULT NULL COMMENT '业务单号', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_doctor_date` (`doctor_id`, `appointment_date`), KEY `idx_patient` (`patient_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='预约挂号表';这里有几点值得注意。对外展示的业务单号建议单独生成,不要直接暴露自增 ID。高频查询场景,比如“某医生某天剩余号源”,要加联合索引。另外,更完整的方案是建一张独立的排班表 schedule,按日期生成医生每天可预约的时段和号源数,而不是直接在 doctor 表里留 total_slots 和 left_slots。上面的 SQL 是简化模型,先把链路跑通,后续扩展排班表会更灵活。
4. SpringBoot 后端核心模块开发
4.1 工程目录结构
后端建议按职责分层,目录结构如下:
medical-server/ ├── src/main/java/com/example/medical/ │ ├── config/ # 跨域配置、MyBatis-Plus 配置、Redis 配置 │ ├── controller/ # 接口层 │ ├── service/ # 业务层 │ ├── mapper/ # 数据访问层 │ ├── entity/ # 实体类 │ ├── dto/ # 入参对象 │ ├── vo/ # 出参对象 │ ├── common/ # 统一返回结构、异常处理、枚举 │ └── MedicalApplication.java ├── src/main/resources/ │ ├── mapper/ # MyBatis XML,复杂查询时使用 │ └── application.yml └── pom.xmlcontroller 只负责参数接收和结果返回,业务逻辑放在 service,数据访问放在 mapper。这样后面加功能、改需求的时候不会把所有代码堆在一个类里。
4.2 登录鉴权:JWT 方案
小程序端和后台端共用后端接口,使用 JWT 做无状态鉴权比较合适。流程是:用户登录成功后,后端生成 token 返回给前端,前端在后续请求的 Header 中携带Authorization: Bearer token,后端通过拦截器解析 token 并设置当前用户 ID。
下面是一个简化版的 JWT 拦截器示例。
@Component public class JwtInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 非接口请求直接放行 if (!(handler instanceof HandlerMethod)) { return true; } String token = request.getHeader("Authorization"); if (token == null || token.isEmpty()) { throw new BusinessException(401, "未登录"); } // 解析 token,失败时抛出 401 Long userId = JwtUtil.parseToken(token.replace("Bearer ", "")); request.setAttribute("userId", userId); return true; } }生产环境建议使用成熟的 JWT 库,比如 jjwt,或者直接用 Spring Security + OAuth2。这里展示的是最简实现,目的是理解鉴权链路。
4.3 挂号模块:接口与业务逻辑
挂号模块是后端最核心的部分,它要处理三个问题:医生状态校验、号源扣减、挂号单创建。其中号源扣减必须考虑并发,否则会出现“多个用户同时挂同一个号”的问题。
先看 Controller 层。
@RestController @RequestMapping("/api/appointment") public class AppointmentController { @Resource private AppointmentService appointmentService; @PostMapping("/create") public Result<AppointmentVO> create(@RequestBody AppointmentCreateDTO dto, HttpServletRequest request) { Long userId = (Long) request.getAttribute("userId"); return Result.success(appointmentService.createAppointment(userId, dto)); } }再来看 Service 层的关键逻辑。
@Service public class AppointmentServiceImpl implements AppointmentService { @Resource private DoctorMapper doctorMapper; @Resource private AppointmentMapper appointmentMapper; @Override @Transactional(rollbackFor = Exception.class) public AppointmentVO createAppointment(Long userId, AppointmentCreateDTO dto) { // 1. 校验医生状态与号源 Doctor doctor = doctorMapper.selectById(dto.getDoctorId()); if (doctor == null || doctor.getStatus() != 1) { throw new BusinessException("医生不存在或停诊"); } if (doctor.getLeftSlots() <= 0) { throw new BusinessException("号源已满"); } // 2. 乐观更新剩余号源,防止并发超卖 int updated = doctorMapper.reduceLeftSlots(doctor.getId()); if (updated == 0) { throw new BusinessException("号源已满,请选择其他时间"); } // 3. 创建挂号单 Appointment appointment = new Appointment(); appointment.setPatientId(userId); appointment.setDoctorId(doctor.getId()); appointment.setAppointmentDate(dto.getAppointmentDate()); appointment.setTimeSlot(dto.getTimeSlot()); appointment.setStatus(0); appointment.setOrderNo(generateOrderNo()); appointmentMapper.insert(appointment); return AppointmentVO.from(appointment); } }对应的扣减 SQL 如下:
UPDATE doctor SET left_slots = left_slots - 1 WHERE id = #{doctorId} AND left_slots > 0这种“乐观扣减 + 受影响行数判断”的方式,是解决号源超卖最简单有效的方案。多个请求同时进来时,数据库行锁保证只有一个 update 能成功,影响行数为 0 的请求直接返回号源已满。加上@Transactional,扣号源和创建挂号单要么全部成功,要么全部回滚。
4.4 问诊与处方模块
问诊模块按会话模型来设计。患者发起问诊时创建一条 consultation 记录,之后双方通过 consultation_message 表发送消息。消息内容包含消息类型 text 或 image,图片消息在发送前先走文件上传接口拿到 URL,再把 URL 存入消息体。
医生端在 Vue 后台可以查看问诊会话列表,进入会话后回复消息。问诊结束时,医生填写诊断结论和处方信息,系统创建 prescription 记录,并把诊断摘要追加到患者的 health_record 中。这样患者端“我的健康档案”就能看到历史问诊记录,业务流程闭环。
这里的关键点不是代码量,而是状态流转。建议在 consultation 表里增加 status 字段:0 进行中、1 已结束,避免已结束的会话还能继续发消息。
4.5 统一返回结构与异常处理
所有接口返回统一结构,前端处理逻辑会简单很多。
{ "code": 200, "message": "success", "data": {} }异常处理使用@RestControllerAdvice,把业务异常、参数校验异常、系统异常分别映射到对应的 code。前端只需要判断 code 是否为 200,省去大量重复判断代码。
5. Vue 管理后台核心页面开发
5.1 后台页面结构
管理后台是医生和管理员使用的端,页面不算多,核心是围绕业务数据做展示和操作:
- 登录页
- 工作台
- 科室管理
- 医生管理
- 挂号列表
- 问诊会话
- 处方管理
- 健康档案
技术栈使用 Vue3 + Vite + Element Plus + Pinia。Vite 启动快,Element Plus 的表格、表单、弹窗组件可以直接复用,适合后台类系统快速开发。
5.2 axios 请求封装
后台所有接口请求通过 axios 统一封装。请求拦截器负责加 token,响应拦截器负责处理业务错误码和登录失效。
import axios from 'axios' import { useAuthStore } from '@/stores/auth' import { ElMessage } from 'element-plus' import router from '@/router' const service = axios.create({ baseURL: '/api', timeout: 10000 }) service.interceptors.request.use(config => { const authStore = useAuthStore() if (authStore.token) { config.headers.Authorization = `Bearer ${authStore.token}` } return config }) service.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { ElMessage.error(res.message || '请求失败') return Promise.reject(new Error(res.message)) } return res }, error => { if (error.response?.status === 401) { router.push('/login') } return Promise.reject(error) } ) export default service本地开发时,Vite 配置代理把/api转发到后端服务,这样可以避免跨域问题。
server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }5.3 医生管理列表页示例
后台最常见的页面就是“列表 + 分页 + 搜索 + 状态操作”。以医生管理为例:
<script setup> import { ref, onMounted } from 'vue' import { getDoctorList, updateDoctorStatus } from '@/api/doctor' const list = ref([]) const total = ref(0) const query = ref({ page: 1, pageSize: 10, keyword: '' }) async function loadList() { const res = await getDoctorList(query.value) list.value = res.data.records total.value = res.data.total } async function handleStatusChange(row) { await updateDoctorStatus(row.id, row.status) ElMessage.success('状态已更新') } onMounted(loadList) </script> <template> <el-card> <el-form :inline="true"> <el-form-item label="医生姓名"> <el-input v-model="query.keyword" placeholder="输入姓名搜索" @keyup.enter="loadList" /> </el-form-item> <el-form-item> <el-button type="primary" @click="loadList">查询</el-button> </el-form-item> </el-form> <el-table :data="list" border stripe> <el-table-column prop="doctorName" label="医生姓名" /> <el-table-column prop="title" label="职称" /> <el-table-column prop="deptName" label="科室" /> <el-table-column prop="regFee" label="挂号费" /> <el-table-column prop="leftSlots" label="剩余号源" /> <el-table-column label="状态"> <template #default="{ row }"> <el-switch v-model="row.status" :active-value="1" :inactive-value="0" @change="handleStatusChange(row)" /> </template> </el-table-column> <el-table-column label="操作"> <template #default="{ row }"> <el-button link type="primary" @click="openEdit(row)">编辑</el-button> </template> </el-table-column> </el-table> <el-pagination v-model:current-page="query.page" v-model:page-size="query.pageSize" :total="total" layout="total, prev, pager, next" @current-change="loadList" /> </el-card> </template>这个页面的逻辑很简单:进入页面加载第一页数据,搜索时重置页码,切换状态后调用更新接口。但它覆盖了后台系统最常见的套路,看完这个页面,科室管理、挂号列表、处方管理等页面可以照同样的模式快速实现。
6. Uniapp 患者端小程序开发
6.1 小程序页面结构
患者端是 C 端体验,页面按用户操作路径组织。
pages/ ├── index/index # 首页,展示科室入口、推荐医生 ├── department/list # 科室列表 ├── doctor/list # 医生列表 ├── doctor/detail # 医生详情与排班 ├── appointment/create # 创建预约 ├── appointment/list # 我的预约 ├── consultation/list # 问诊会话列表 ├── consultation/chat # 问诊聊天页 ├── health/record # 健康档案 └── mine/index # 我的页面Uniapp 的页面路由和 Vue Router 不同,它基于pages.json配置,tabBar 页面注册在 tabBar 节点,普通页面直接放在 pages 数组里。
6.2 小程序请求封装
Uniapp 没有 axios,一般用uni.request封装一个 Promise 请求工具,统一处理 baseURL、token 和错误提示。
// utils/request.js const BASE_URL = 'https://api.example.com/api' export function request(options) { return new Promise((resolve, reject) => { const token = uni.getStorageSync('token') uni.request({ url: BASE_URL + options.url, method: options.method || 'GET', data: options.data || {}, header: { 'Content-Type': 'application/json', Authorization: token ? `Bearer ${token}` : '' }, success: (res) => { if (res.statusCode === 200 && res.data.code === 200) { resolve(res.data.data) } else if (res.statusCode === 401) { uni.removeStorageSync('token') uni.navigateTo({ url: '/pages/login/index' }) reject(res.data) } else { uni.showToast({ title: res.data.message || '请求失败', icon: 'none' }) reject(res.data) } }, fail: (err) => { uni.showToast({ title: '网络异常', icon: 'none' }) reject(err) } }) }) }这里的BASE_URL需要替换成实际后端地址。本地开发时可以填局域网 IP 加后端端口,真机调试时确保手机和电脑在同一网络。上线前必须改成 HTTPS 域名。
6.3 微信登录流程
微信小程序登录流程和传统账号密码登录完全不同。
前端调用uni.login获取临时 code,把 code 传给后端。后端拿着 code 调用微信接口换取 openid,然后根据 openid 查询用户表,不存在则自动注册,最后生成 token 返回前端。
async function wxLogin() { const { code } = await uni.login({ provider: 'weixin' }) const { token } = await request({ url: '/auth/wx-login', method: 'POST', data: { code } }) uni.setStorageSync('token', token) uni.switchTab({ url: '/pages/index/index' }) }开发阶段要注意,微信开发者工具需要勾选“不校验合法域名”,否则本地请求会被拦截。上线前则必须在微信公众平台配置合法的 request 域名。
6.4 预约挂号页面交互
患者端的挂号流程是:科室列表进入医生列表,在医生详情页选择日期和时间段,点击预约后调createAppointment接口。页面要做按钮 loading 状态,防止用户快速连点产生重复订单。下单成功之后跳转到“我的预约”页面,状态显示为待就诊。
这个流程的体验重点是反馈要即时。点击预约后按钮立刻变 loading,接口成功后提示“预约成功”,失败时把后端的错误信息直接展示给用户,比如“号源已满,请选择其他时间”。
7. 前后端联调与本地启动验证
7.1 启动顺序与端口规划
本地联调建议按下面的顺序启动:
| 服务 | 端口 | 启动方式 |
|---|---|---|
| MySQL | 3306 | 本地服务 |
| Redis | 6379 | 本地服务,缓存科室和医生数据时使用 |
| SpringBoot 后端 | 8080 | IDE 启动或mvn spring-boot:run |
| Vue 后台 | 5173 | npm run dev |
| Uniapp 小程序 | 微信开发者工具 | HBuilderX 运行到微信开发者工具 |
端口是开发环境常见默认值,实际项目里如果端口被占用,在后端 application.yml 和 Vite 代理配置里改掉即可。
7.2 联调验证清单
前后端分开开发时各自都能跑,但真正的问题往往出在联调阶段。建议按照下面的清单做一轮功能验证:
| 验证项 | 操作 | 预期结果 |
|---|---|---|
| 小程序登录 | 微信开发者工具里触发登录 | 数据库 user 表生成记录,返回 token |
| 科室列表 | 小程序首页加载科室 | 科室数据正常展示 |
| 医生列表 | 选择科室进入医生列表 | 医生按科室过滤,号源显示正确 |
| 创建预约 | 选择医生和时间段提交 | 医生剩余号源减一,我的预约出现记录 |
| 后台登录 | 管理员账号登录 Vue 后台 | 进入工作台,token 写入本地存储 |
| 医生管理 | 后台新增或停用医生 | 小程序端医生列表同步变化 |
| 在线问诊 | 患者端发消息,后台医生回复 | 消息记录两端可见 |
| 处方归档 | 医生后台填写处方 | 患者端健康档案出现对应记录 |
7.3 跨域问题处理
本地联调最常见的报错就是跨域。Vue 后台本地开发通过 Vite 代理解决,Uniapp 小程序不受 CORS 跨域限制,但微信开发者工具有合法域名校验。如果后端单独开启 CORS,注意只对测试环境开放,生产环境统一走 nginx 反向代理更安全。
8. 部署上线与小程序审核
8.1 后端打包部署
后端打包使用 Maven。
mvn clean package打包后在 target 目录下生成 jar 文件,上传到服务器。可以用 nohup 临时启动,也可以用 systemd 做成服务。
nohup java -jar medical-server.jar --server.port=8080 > logs/app.log 2>&1 &生产环境需要改数据库连接密码、关闭调试日志、设置 JVM 内存参数,比如:
java -Xms512m -Xmx1024m -jar medical-server.jar8.2 Vue 管理后台部署
Vue 后台构建后是纯静态文件。
npm run build把生成的 dist 目录上传到服务器,用 nginx 托管,并通过反向代理转发/api请求。
server { listen 80; server_name your-domain.example.com; location / { root /var/www/admin; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }注意 SPA 应用刷新页面会找不到路由,try_files是必须配置的。
8.3 小程序上传与审核注意
Uniapp 项目在 HBuilderX 里点击“发行 -> 小程序-微信”,生成微信小程序代码,然后用微信开发者工具打开并上传到微信公众平台。
小程序发布有几个硬性条件。request 合法域名必须是 HTTPS,且已经备案。医疗相关类目大概率需要资质。如果只是学习项目,建议保持“体验版”状态,让微信开发者工具里的成员扫码体验即可,不需要走正式审核上线。
9. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 后端启动失败,报数据库连接错误 | MySQL 未启动、账号密码错误、数据库不存在 | 检查 application.yml 与 MySQL 服务状态 | 创建数据库并导入 SQL,核对连接配置 |
| 小程序请求失败,报 url not in domain list | 微信公众平台未配置合法域名 | 查看微信开发者工具控制台 | 开发模式勾选“不校验合法域名”,上线前配置 HTTPS 域名 |
| 接口返回 401 或 token 解析失败 | 请求头没带 token、token 过期 | 查看请求 Header 与 JWT 过期时间 | 前端统一在拦截器加 Authorization,过期后跳转登录 |
| 创建预约提示号源已满 | 号源被扣完或并发争抢 | 查看 doctor 表的 left_slots | 使用乐观更新 SQL,必要时增加 Redis 分布式锁 |
| Vue 后台接口跨域 | Vite 代理未配置或后端 CORS 缺失 | 浏览器 F12 查看 Network | 配置 Vite proxy 或后端 CORS |
| 图片加载不出来 | 图片地址是本地绝对路径或存储未开放访问 | 检查返回 URL 与存储配置 | 替换为可访问的地址,确认存储桶权限 |
| 小程序上传图片失败 | 上传接口未实现、文件大小超限 | 查看后端日志 | 限制大小,使用 uni.uploadFile 调用对应接口 |
| 部署后页面白屏 | 静态资源路径错误、nginx 配置问题 | 查看 nginx error.log | 配置 SPA try_files 规则,检查 base 路径 |
| 本地能跑,服务器上接口很慢 | JVM 参数未设置、数据库连接池过小 | 查看内存和慢查询日志 | 设置 -Xms/-Xmx,调大连接池 |
| 问诊会话结束后还能发消息 | 状态机未控制 | 查看后端接口逻辑 | 接口判断 consultation.status,已结束则拒绝新消息 |
10. 前端转全栈的就业视角与最佳实践
10.1 简历项目怎么写
同样是医疗小程序,简历上是否“会写”差别很大。建议用四段式结构,写清楚技术栈、项目职责、核心难点、量化结果。
医疗预约问诊小程序(全栈项目) 技术栈:SpringBoot + MyBatis-Plus + Redis + Vue3 + Element Plus + Uniapp 职责: - 设计用户、医生、挂号、问诊、健康档案等核心业务表,覆盖完整预约就诊闭环 - 实现 JWT 登录鉴权,区分患者端、医生端、管理员端角色权限 - 实现预约挂号号源扣减,使用乐观更新解决并发超卖问题 - 使用 Redis 缓存科室和医生基础数据,降低数据库查询压力 - 独立开发 Uniapp 患者端和 Vue 管理后台,完成前后端接口联调与部署这段话的核心是让面试官一眼看到:这个人做过数据库设计、做过权限、处理过并发、做过多个端。这些正是全栈岗位考察的重点。
10.2 面试常问的高频问题
这个项目能引出的面试题很多,建议提前准备:
- 为什么用 JWT 不用 Session?
- 号源扣减怎么防止超卖?事务和乐观锁如何配合?
- 微信小程序登录的完整流程是什么?openid 的作用是什么?
- Redis 在这个项目里缓存了什么?缓存和数据库一致性怎么处理?
- user 表和 sys_user 表为什么要分开?
- 如果同一时间有大量用户抢号,服务器层面怎么优化?
- 在线问诊的消息记录用什么数据结构存储?聊天记录如何高效查询?
能把这些讲清楚,项目价值就完全体现出来了。
10.3 从“能跑”到“有亮点”的扩展方向
基础功能跑通后,可以按下面的优先级做扩展。第一优先级是支付闭环,接入微信支付完成挂号费订单。第二优先级是排班表设计,把医生表里的总号源和剩余号源抽离成独立的 schedule 表,支持按天生成号源。第三优先级是消息队列,用 RabbitMQ 处理问诊通知和订单状态变更。第四优先级是 AI 辅助分诊,患者先描述症状,系统给出科室建议,只做分诊参考,不做诊断结论。
扩展方向不用全部做完,选一个做到生产级质量,比做一堆半成品更有说服力。
10.4 最佳实践与合规提醒
开发顺序上,先跑通最小闭环:登录、看医生、挂号。再扩展问诊、处方、健康档案。一次做完全部功能,排错成本会很高。
工程规范上,数据库密码、微信 secret 这类敏感信息不要提交到 Git 仓库,放配置文件并用.gitignore忽略。接口命名统一用/api前缀,后端接口和前端请求封装保持一致。
数据安全上,开发数据一律脱敏。假姓名、假手机号、假病历,不要图方便直接导入真实数据。图片素材注意版权,人物头像和病历截图不要随意下载使用。
发布之前检查一遍:是否还有调试接口、是否开启了 Swagger、数据库连接信息是否正确、小程序合法域名是否已配置、HTTPS 证书是否有效。医疗数据是敏感个人信息,学习项目用测试数据没有任何问题,一旦涉及真实数据和商用场景,必须走完整的合规流程。
这个项目最值得尝试的就是把三端串起来,跑通一个完整的预约问诊闭环。按照上面的顺序做一遍,你会理解为什么后端接口要这样设计、前端为什么这样封装请求、小程序登录为什么必须走后端换 openid。建议先按文章顺序复现一次,跑通之后试着改一个功能,比如加排班表、加微信支付或者优化号源并发控制。改完以后,这个项目就不再是别人的教程,而是真正属于你的项目。