资讯动态

SpringBoot+Vue+Uniapp三端联动:医疗预约小程序全栈实战

发布时间:2026/10/18 12:34:54 来源:尧图企业网站定制
做前端做了两三年最焦虑的事情往往不是框架又更新了而是简历里能写的项目翻来覆去还是“基于 Vue 的中后台管理系统”。这类项目不是没有价值而是面试官看过的实在太多了。真正能拉开差距的是一个业务完整、有多端协作、能体现后端能力的项目。这篇文章要聊的就是用 SpringBoot Vue Uniapp 三端联动从零实现一个医疗小程序实战项目。先给一个判断医疗小程序这个方向不是因为医疗行业本身多热门才值得做而是因为它的业务复杂度正好卡在“适合练手”和“足够写简历”之间。它有预约、排班、状态流转、角色权限业务链条完整但又不像大型电商那样动辄几十张表、一堆分布式组件。对前端转全栈的开发者来说这是性价比很高的练习项目。这篇文章会从数据库设计讲起一路写到 SpringBoot 后端接口、Uniapp 小程序端、Vue 管理后台最后给出联调验证的步骤和常见坑的排错清单。读完以后你可以照着把项目跑通更重要的是理解每一步“为什么这么做”而不是背出一堆脚手架代码。1. 这篇文章真正要解决的问题先聊一个很多前端同学的困境。日常业务开发中前端能接触到的内容往往很有限写页面、调接口、处理组件状态。接口是怎么设计的数据库表是怎么拆的预约这种业务的状态是怎么流转的这些东西如果不在项目中亲手做一遍光靠看八股文是记不住的。而在跳槽面试中后端基础又是高频考点这就形成了一个死循环不会所以不写不写所以不会。全栈项目恰好能打破这个循环。它逼你站在全局视角思考问题一个预约请求从前端页面发起经过后端接口校验落到数据库表中最后回到前端展示状态。任何一个环节出问题你都得自己排查。这个过程积累的东西比单纯刷题扎实得多。项目里遇到的问题越多你在面试时能讲出的真实细节就越多。那为什么选医疗小程序三个原因业务完整。科室、医生、排班、预约、取消、状态更新天然存在复杂的关联和状态机能锻炼数据建模能力。端足够多。用户端是小程序管理端是 Web 后台后端是 SpringBoot。这意味着你能在一套业务里同时掌握 C 端和 B 端的开发差异。话题有辨识度。面试官看到“医疗预约小程序”比看到“商城管理系统”更愿意多问两句因为业务里有真实的设计决策可以聊。当然什么人不适合也要说清楚。如果你连 Vue 的基本语法都还不熟建议先做一个简单的后台管理项目再上手这个。本项目的知识点密度较高直接硬啃容易劝退。如果你已经能独立完成一个单体管理系统那这个项目就是你从“能写页面”走向“能设计系统”的必经一步。2. 技术栈选型与医疗业务场景拆解2.1 为什么是 SpringBoot Vue Uniapp先说后端。Java 是后端就业市场占有率最高的语言之一SpringBoot 又是 Java 后端的事实标准。虽然 Node 后端也能做全栈但如果目标是“提升就业几率”SpringBoot 的覆盖面明显更广社区资料、面试题、岗位数量都更充足。对前端转后端的同学来说Java 的学习曲线虽然比 Node 陡一点但它的生态成熟度能让你在遇到问题时更容易搜到答案。再说前端。用户端选择 Uniapp是因为它可以一套代码同时编译到微信小程序、H5 和 App开发成本和上线成本都比较低。管理后台选择 Vue 3 Element Plus作为传统的 Web 管理端非常顺手。前端与后端通过 RESTful 接口通信这就是标准的前后端分离架构。这套技术栈还有一个隐藏优势你只需要掌握 JavaScript 和 Java 两门语言就能覆盖三条业务线。对前端转全栈的人而言学习曲线比“前端 Go 小程序原生”的组合平滑很多。这也意味着你不需要在项目启动前先去补一堆陌生语言可以立刻把精力放在业务逻辑上。2.2 医疗小程序的典型业务模块一个完整的医疗小程序项目通常包含三个端对应三类角色用户端小程序患者注册登录、浏览科室和医生、查看医生排班、预约挂号、查看我的预约和就诊记录。管理后台管理员维护科室和医生信息、设置排班、查看和审核预约记录、统计预约数据。后端服务提供所有数据访问和业务逻辑统一处理身份认证、参数校验、状态流转。本文不会把三个端的所有功能都写完而是挑最核心的“预约挂号”作为主线因为它把科室、医生、用户、预约四张核心表都串起来了。把这条主链跑通之后其他功能都是锦上添花而且你会发现后面每个功能都是在重复同一套方法论设计表、写接口、做页面、联调验证。2.3 前后端分离与多端复用的架构思路整个项目的调用链是这样的小程序端用户选择科室 → 请求后端获取该科室的医生列表 → 用户选择医生和日期 → 提交预约请求 → 后端校验号源是否充足、是否重复预约 → 生成预约记录 → 用户端展示“待就诊 / 已完成 / 已取消”状态 → 管理后台可以查询用户的预约数据。这中间最值得关注的设计点是“状态字段不要用中文硬编码”。预约状态应该用数字表示比如 0 待就诊、1 已完成、2 已取消。后端用枚举或常量管理前端用字典映射显示文案。这个细节虽然小但在多人协作项目中非常重要也是面试官喜欢问的点。如果状态用字符串直接存“待就诊”后续一旦要改文案就得动数据库代价非常高。3. 数据库设计核心表结构与建表 SQL3.1 表结构设计原则医疗预约系统的数据模型重点是处理好三个关系科室与医生一个科室有多个医生一对多。医生与排班一个医生在不同时间段有不同排班一对多。用户与预约一个用户可以有多个预约记录但同一个医生同一天不能重复预约。设计时遵循几个基本原则每张表都有主键时间字段统一用 datetime状态字段使用 int/tinyint关联字段加上索引字符集统一使用 utf8mb4因为要存用户昵称这类可能包含生僻字或表情的文本。不要小看这些基础规范很多新手项目挂在面试官面前时第一眼暴露问题的就是表设计。3.2 建表 SQL 实现下面是核心表的建表 SQL可以直接在 MySQL 中执行。medical_mini是数据库名执行前先创建数据库CREATE DATABASE IF NOT EXISTS medical_mini DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE medical_mini; -- 科室表 CREATE TABLE department ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键, name VARCHAR(50) NOT NULL COMMENT 科室名称, description VARCHAR(500) DEFAULT NULL COMMENT 科室简介, sort INT DEFAULT 0 COMMENT 排序号越小越靠前, status TINYINT DEFAULT 1 COMMENT 状态1启用 0停用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT科室表; -- 医生表 CREATE TABLE doctor ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键, department_id BIGINT NOT NULL COMMENT 所属科室ID, name VARCHAR(50) NOT NULL COMMENT 医生姓名, title VARCHAR(50) DEFAULT NULL COMMENT 职称如主任医师, avatar VARCHAR(255) DEFAULT NULL COMMENT 头像地址, introduction TEXT COMMENT 医生简介, status TINYINT DEFAULT 1 COMMENT 状态1在职 0离职, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), KEY idx_department_id (department_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT医生表; -- 患者用户表 CREATE TABLE patient ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键, openid VARCHAR(64) DEFAULT NULL COMMENT 小程序openid, nickname VARCHAR(50) DEFAULT NULL COMMENT 昵称, phone VARCHAR(20) DEFAULT NULL COMMENT 手机号, status TINYINT DEFAULT 1 COMMENT 状态1正常 0禁用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), UNIQUE KEY uk_openid (openid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT患者用户表; -- 预约记录表 CREATE TABLE appointment ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 主键, patient_id BIGINT NOT NULL COMMENT 患者ID, doctor_id BIGINT NOT NULL COMMENT 医生ID, appointment_date DATE NOT NULL COMMENT 预约日期, time_slot TINYINT NOT NULL COMMENT 时间段1上午 2下午, status TINYINT NOT NULL DEFAULT 0 COMMENT 状态0待就诊 1已完成 2已取消, remark VARCHAR(200) DEFAULT NULL COMMENT 备注, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), KEY idx_patient_id (patient_id), KEY idx_doctor_id (doctor_id), KEY idx_appointment_date (appointment_date), UNIQUE KEY uk_doctor_date_slot (doctor_id, appointment_date, time_slot) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT预约记录表;这段 SQL 里有几个值得注意的设计点预约表用唯一索引uk_doctor_date_slot从数据库层面保证“一个医生同一天同一时段不能被重复预约”。这是防止并发重复预约最直接的手段比只在业务层判断可靠得多。日期和时间分开存储。appointment_date只存日期time_slot存上午或下午查询“某医生某天是否有号”非常快。用户表把openid设为唯一索引为微信小程序登录场景做准备。需要说明的是这里的设计是一个简化的教学模型一个医生一天两个时段每个时段最多一个预约。实际生产系统会引入排班表、号源数量、余号扣减等设计。先把简单模型跑通再逐步扩展是练手项目最合理的路径。4. SpringBoot 后端工程搭建与基础配置4.1 创建工程与 Maven 依赖你可以用 Spring Initializr 创建工程也可以在 IDEA 里新建 Spring Boot 项目。依赖方面最小集合是 Web、MySQL 驱动、MyBatis-Plus。下面是一个参考的pom.xml依赖片段parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.5/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies注意几点如果使用 Spring Boot 3.x部分包名会从javax变成jakarta拦截器、Servlet 相关代码要对应调整。上述版本号只是演示。实践时以你自己环境中验证过的组合为准重点是理解每个依赖的作用。MyBatis-Plus 是 MyBatis 的增强工具它把单表 CRUD 封装好了能省掉大量 Mapper XML很适合快速搭项目。它不是替代 MyBatis而是在 MyBatis 基础上少写重复代码。4.2 配置文件与数据源在src/main/resources/application.yml中配置数据源server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/medical_mini?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: id-type: automap-underscore-to-camel-case让数据库的create_time自动映射到 Java 的createTime少写很多字段映射代码。log-impl配置成StdOutImpl后控制台会打印 SQL联调排错时非常有用生产环境记得去掉。如果你本机 MySQL 的 root 密码不是 123456记得改成你实际的配置。4.3 统一返回结果前后端分离项目中接口统一返回 JSON 结构能大幅减少前后端沟通成本。定义一个通用结果类比如ResultT// 文件路径src/main/java/com/example/medical/common/Result.java package com.example.medical.common; import lombok.Data; Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(success); result.setData(data); return result; } public static T ResultT error(Integer code, String message) { ResultT result new Result(); result.setCode(code); result.setMessage(message); return result; } }这里没有用复杂的错误码体系200 表示成功。实际项目中可以定义更细的错误码比如 401 未登录、400 参数错误、500 服务异常但核心思想是一样的让前端只需要判断code就能知道请求是否成功。后续所有 Controller 都返回ResultT前端请求封装就只需要处理一套结构。5. 预约挂号核心接口从需求到实现5.1 接口设计预约挂号主流程涉及三个接口GET /api/doctor/list?departmentId1获取某科室的医生列表。GET /api/doctor/schedule?doctorId1date2025-06-10获取医生某天剩余号源。POST /api/appointment/book提交预约。接口的请求体和返回结构要先约定清楚再动手写代码。预约提交请求体可以设计成如下 JSON{ patientId: 1001, doctorId: 2003, appointmentDate: 2025-06-10, timeSlot: 1 }对应的 DTO 类也很简单字段与 JSON 一一对应// 文件路径src/main/java/com/example/medical/dto/AppointmentBookDTO.java package com.example.medical.dto; import lombok.Data; Data public class AppointmentBookDTO { private Long patientId; private Long doctorId; private String appointmentDate; private Integer timeSlot; }5.2 Controller 实现新建AppointmentController代码示例如下// 文件路径src/main/java/com/example/medical/controller/AppointmentController.java package com.example.medical.controller; import com.example.medical.common.Result; import com.example.medical.dto.AppointmentBookDTO; import com.example.medical.entity.Appointment; import com.example.medical.service.AppointmentService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; RestController RequestMapping(/api/appointment) public class AppointmentController { Autowired private AppointmentService appointmentService; PostMapping(/book) public ResultAppointment book(RequestBody AppointmentBookDTO dto) { return Result.success(appointmentService.book(dto)); } GetMapping(/my-list) public ResultListAppointment myList(RequestParam Long patientId) { return Result.success(appointmentService.listMyAppointments(patientId)); } }Controller 只负责接收参数和返回结果不写业务逻辑。业务逻辑放在 Service 层这样接口好测试也方便后续复用。新手最容易犯的错就是把 SQL 查询、业务判断全堆在 Controller 里短期看功能能跑但后续每加一个功能都要动 Controller维护成本很高。5.3 Service 层防重复预约与状态校验预约的核心业务逻辑在 Service。这里的关键点有两个一是用户自己的重复预约检查二是并发场景下的防超卖。先看一个常规实现// 文件路径src/main/java/com/example/medical/service/impl/AppointmentServiceImpl.java package com.example.medical.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.example.medical.dto.AppointmentBookDTO; import com.example.medical.entity.Appointment; import com.example.medical.exception.BusinessException; import com.example.medical.mapper.AppointmentMapper; import com.example.medical.service.AppointmentService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; Service public class AppointmentServiceImpl implements AppointmentService { Autowired private AppointmentMapper appointmentMapper; Override Transactional(rollbackFor Exception.class) public Appointment book(AppointmentBookDTO dto) { // 1. 检查同一用户是否重复预约了同医生的同一天 Long count appointmentMapper.selectCount(new LambdaQueryWrapperAppointment() .eq(Appointment::getPatientId, dto.getPatientId()) .eq(Appointment::getDoctorId, dto.getDoctorId()) .eq(Appointment::getAppointmentDate, dto.getAppointmentDate()) .ne(Appointment::getStatus, 2)); if (count 0) { throw new BusinessException(您已预约该医生该日期请勿重复预约); } // 2. 尝试插入预约记录依靠唯一索引在数据库层面兜底 Appointment appointment new Appointment(); appointment.setPatientId(dto.getPatientId()); appointment.setDoctorId(dto.getDoctorId()); appointment.setAppointmentDate(dto.getAppointmentDate()); appointment.setTimeSlot(dto.getTimeSlot()); appointment.setStatus(0); appointmentMapper.insert(appointment); return appointment; } Override public ListAppointment listMyAppointments(Long patientId) { return appointmentMapper.selectList(new LambdaQueryWrapperAppointment() .eq(Appointment::getPatientId, patientId) .orderByDesc(Appointment::getCreateTime)); } }这段代码做了两层防护业务层先查询一次同一患者是否重复预约。数据库唯一索引保证即使两个请求同时进来也只有一条记录能插入成功。插入失败的一方会抛出唯一键冲突异常由全局异常处理器转换为“号源已被占用”的提示而不是 500 错误。真正生产级的预约系统还会配合 Redis 预扣库存、分布式锁等方式控制并发但作为全栈练习项目先理解“业务校验 唯一索引”的组合已经能解决大部分问题。面试时能把这一层讲出来已经比大多数只会调 CRUD 的候选人强了。5.4 接口测试方式后端写完可以先用 Postman 或 IDEA 自带的 HTTP Client 测试不需要等前端POST http://localhost:8080/api/appointment/book Content-Type: application/json { patientId: 1001, doctorId: 2003, appointmentDate: 2025-06-10, timeSlot: 1 }预期返回{ code: 200, message: success, data: { id: 1, patientId: 1001, doctorId: 2003, appointmentDate: 2025-06-10, timeSlot: 1, status: 0 } }如果重复提交同一条数据应该收到业务异常提示而不是 500 错误。这一步验证通过说明后端的核心路径是通的接下来可以放心进入前端开发。6. Uniapp 用户端从页面到请求封装6.1 项目初始化Uniapp 项目可以通过 HBuilderX 可视化创建也可以使用 CLI 创建。对新手来说推荐 HBuilderX省去很多环境配置。创建项目时选择“默认模板”即可因为医疗小程序的首页不需要复杂的 UI 框架支撑原生组件足够。项目结构建议如下pages/ index/index.vue // 首页科室列表 doctor/doctor.vue // 医生列表 appointment/book.vue // 预约提交页 order/order-list.vue // 我的预约列表 utils/ request.js // 请求封装页面之间通过uni.navigateTo跳转并携带参数。比如从医生列表进入预约页时把doctorId通过url参数传递过去。6.2 预约页面实现以预约提交页为例核心代码如下!-- 文件路径pages/appointment/book.vue -- template view classbook-page view classcard text classlabel就诊日期/text picker modedate :valuedate changeonDateChange view classpicker-value{{ date || 请选择日期 }}/view /picker /view view classcard text classlabel时间段/text view classslot-group view v-forslot in timeSlots :keyslot.value classslot-item :class{ active: slot.value timeSlot } clicktimeSlot slot.value {{ slot.label }} /view /view /view button classsubmit-btn :disabledsubmitting clicksubmitBooking提交预约/button /view /template script import request from ../../utils/request.js export default { data() { return { doctorId: , date: , timeSlot: 1, timeSlots: [ { value: 1, label: 上午 }, { value: 2, label: 下午 } ], submitting: false } }, onLoad(options) { this.doctorId options.doctorId }, methods: { onDateChange(e) { this.date e.detail.value }, submitBooking() { if (!this.date) { uni.showToast({ title: 请选择日期, icon: none }) return } this.submitting true request({ url: /api/appointment/book, method: POST, data: { patientId: uni.getStorageSync(patientId), doctorId: this.doctorId, appointmentDate: this.date, timeSlot: this.timeSlot } }).then(() { uni.showToast({ title: 预约成功, icon: success }) setTimeout(() { // 如果 order-list 是底部 tabBar 页面这里应改用 uni.switchTab uni.navigateTo({ url: /pages/order/order-list }) }, 1500) }).finally(() { this.submitting false }) } } } /script页面的逻辑并不复杂难点在于和现有项目风格的统一。如果你已经有一个封装好的请求层页面里就不应该再直接调uni.request而是统一走封装方法这样后续做登录态、错误提示、埋点都只需要改一处。6.3 请求封装与登录态uni.request本身功能简单但项目里所有请求都需要统一处理 baseUrl、token、错误提示所以封装一层request.js非常重要// 文件路径utils/request.js const BASE_URL http://localhost:8080 export default function request(options) { return new Promise((resolve, reject) { uni.request({ url: BASE_URL options.url, method: options.method || GET, data: options.data || {}, header: { Content-Type: application/json, Authorization: uni.getStorageSync(token) || }, success: (res) { if (res.data.code 200) { resolve(res.data.data) } else { uni.showToast({ title: res.data.message, icon: none }) reject(res.data) } }, fail: (err) { uni.showToast({ title: 网络异常请稍后重试, icon: none }) reject(err) } }) }) }这里把token从本地存储取出放进请求头后续做微信登录时只需要在登录成功后把token存进uni.setStorageSync所有请求就都能带上认证信息了。这个封装是用户端最值得打磨的部分。页面可以后补但请求层如果设计不合理后面每个接口都要跟着改返工成本很高。7. Vue 管理后台补齐管理端的最后一块拼图7.1 后台选型管理后台用 Vue 3 Element Plus 是标准选择。千万不要认为后台不重要它是整个业务链里不可或缺的一环。没有它科室、医生、排班这些基础数据根本没地方维护小程序的科室列表就是空的。后台的核心页面包括登录页、科室管理、医生管理、预约记录列表。对练习项目来说不用做太复杂能跑通增删改查就算达标。做后台的意义在于体验 B 端开发的节奏表格、分页、搜索、表单校验、路由权限这些是另一个高频面试话题。7.2 预约记录列表页面预约记录列表是后台最典型的“表格 搜索 分页”页面代码示例如下!-- 文件路径src/views/appointment/index.vue -- template div classappointment-page el-card el-form inline el-form-item label患者ID el-input v-modelquery.patientId placeholder请输入患者ID clearable / /el-form-item el-form-item el-button typeprimary clickloadData查询/el-button el-button clickresetQuery重置/el-button /el-form-item /el-form /el-card el-card el-table :datalist v-loadingloading el-table-column propid label预约ID width80 / el-table-column proppatientId label患者ID width100 / el-table-column propdoctorId label医生ID width100 / el-table-column propappointmentDate label预约日期 / el-table-column label时间段 width100 template #default{ row } {{ row.timeSlot 1 ? 上午 : 下午 }} /template /el-table-column el-table-column label状态 width100 template #default{ row } el-tag :typestatusMap[row.status].type{{ statusMap[row.status].label }}/el-tag /template /el-table-column /el-table el-pagination v-model:current-pagequery.pageNum :page-sizequery.pageSize :totaltotal current-changeloadData / /el-card /div /template script import axios from axios export default { data() { return { list: [], total: 0, loading: false, statusMap: { 0: { label: 待就诊, type: warning }, 1: { label: 已完成, type: success }, 2: { label: 已取消, type: info } }, query: { patientId: , pageNum: 1, pageSize: 10 } } }, mounted() { this.loadData() }, methods: { async loadData() { this.loading true const { data } await axios.get(/api/admin/appointment/list, { params: this.query }) this.list data.data.records this.total data.data.total this.loading false }, resetQuery() { this.query.patientId this.query.pageNum 1 this.loadData() } } } /script注意上面代码假设axios已经配置了baseURL或者在开发环境配了代理。实际项目中你可以在src/main.js里设置axios.defaults.baseURL也可以在vite.config.js里配置开发代理。无论哪种方式目标都是让前端代码里不写死接口地址方便切换环境。前端状态展示用statusMap做映射而不是把后端数字当成文案直接用。前端字典和后端状态码对应关系明确前后端各改各的也不会乱。7.3 路由与权限后台通常需要加一个简单的路由守卫未登录不能访问内部页面。用 Vue Router 的全局前置守卫实现// 文件路径src/router/index.js router.beforeEach((to, from, next) { const token localStorage.getItem(admin-token) if (to.path ! /login !token) { next(/login) } else { next() } })这段代码虽然简单但回答了一个值得思考的问题管理后台和小程序端的认证方式是有区别的。小程序端通常靠微信登录换取 token管理后台则是用户名密码登录。两者都依赖 token 识别身份但获取 token 的方式完全不同。这也是全栈项目里很适合在面试时展开聊的点。8. 全栈联调启动步骤、验证路径与常见问题8.1 启动顺序建议按以下顺序启动项目启动 MySQL确认medical_mini库和四张表已创建。启动 SpringBoot 后端确认端口 8080 启动成功控制台无报错。启动 Vue 管理后台通过npm install安装依赖后执行npm run dev。在 HBuilderX 中运行 Uniapp 项目选择“运行到浏览器”或“运行到微信开发者工具”。后端启动成功后先在浏览器访问http://localhost:8080/api/doctor/list?departmentId1能返回 JSON 再继续下一步。这能快速判断问题是出在后端还是前端。8.2 验证路径推荐按用户真实操作路径验证科室列表是否有数据。没有的话先在数据库手动插入科室和医生数据或在后台管理页面创建。前端提交预约后数据库appointment表是否新增了一条记录。重复提交预约前端是否提示“请勿重复预约”。后台预约列表是否能查询到刚才的数据状态是否正确展示。8.3 联调常见问题问题现象可能原因排查思路解决方案小程序请求接口 404baseUrl 或端口不对看浏览器开发者工具的请求地址把request.js的 BASE_URL 改成后端实际访问地址后端接口报跨域错误未配置 CORS看控制台 CORS 报错信息后端添加全局跨域配置或使用代理数据库中文乱码表和连接字符集不一致查看数据库表字符集统一使用 utf8mb4预约重复提交不报错唯一索引未生效查看表结构是否包含唯一索引补上uk_doctor_date_slot唯一索引前端传的 patientId 是字符串类型转换失败看后端日志参数解析报错前端转成数字或后端 DTO 字段放宽处理Vue 后台列表无数据接口返回结构与前端约定不一致直接浏览器访问接口看返回 JSON调整后端返回结构或前端解析逻辑跨域是最容易卡住新手的问题。开发环境下最简单的方式是在后端加一个全局 CORS 配置类// 文件路径src/main/java/com/example/medical/config/CorsConfig.java package com.example.medical.config; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }allowCredentials(true)配合allowedOriginPatterns(*)在 Spring Boot 2.4 之后是允许的但生产环境建议把允许来源限定为你的域名而不是开放给所有来源。这里体现的工程习惯是开发环境可以宽松生产环境必须收紧。9. 最佳实践练习项目如何变成简历亮点9.1 代码规范与分层后端一定要分层。Controller 只做参数接收和结果返回Service 写业务逻辑Mapper 只访问数据库。这个分层规范在练习时容易被忽略但在面试和实际项目中是硬要求。项目代码里最好加上全局异常处理器把业务异常和系统异常区分开避免把堆栈信息直接抛给前端。前端同样要规范。页面组件按功能拆分公共请求和工具方法放到utils里接口地址集中管理或按模块拆分。如果你能在简历上写“设计并封装了统一请求层支持 token 注入和统一错误处理”比写“熟悉 Vue”有说服力得多。9.2 业务安全与数据合规医疗数据属于敏感数据真实上线前必须考虑数据安全与合规要求。作为学习项目你至少要做到几件事密码不能明文存储必须加盐哈希接口不能把敏感字段完整返回给前端后台管理接口需要权限校验不能对所有登录用户开放全部功能。这里要特别说明医疗行业不是普通的 CRUD 业务真实产品涉及资质、隐私、数据安全等一系列合规要求。本文的项目适合学习技术不适合未经合规审查就直接上线服务真实患者。这一点在面试中主动提出来反而能让面试官觉得你有工程风险意识。9.3 部署与展示练习项目做完了不要只留在本地。可以部署到云服务器至少做到后端用mvn package打成 jar 包运行前端打包后放入 Nginx小程序端提交体验版。部署过程中会遇到环境问题、端口问题、日志排查这个环节本身就是很好的工程训练。运维方面不需要做得很重能跑起来、能访问、日志能看到就足够了。如果你能在简历上写“项目已部署至云服务器支持小程序端、Web 管理端访问”这比在本地跑通更能证明你的交付能力。9.4 简历与面试表达简历上不要写“完成了预约挂号功能”而要写清楚难度和方案。例如设计医疗预约数据模型解决科室、医生、患者与预约记录之间的状态关联。通过唯一索引与业务校验双重机制防止并发场景下重复预约。封装多端统一请求层支持登录态注入、异常统一处理。面试时被问“为什么选这个项目”可以这样回答因为这个项目让我完整走了一遍从数据库设计、后端接口、小程序端到管理后台的全流程同时让我意识到状态设计和并发控制是预约类业务的核心难点。这样的回答比背概念更能体现真实项目经验。10. 总结与后续学习方向回到开头的问题前端转全栈到底应该怎么做答案不是去背更多框架 API而是亲手做一个业务完整、链路清晰的项目。这个医疗小程序把四张核心表、三条业务端、一套主流程串了起来跑通它你就掌握了“从页面到数据库”的完整链路。跑通之后后续可以往这几个方向深入把预约状态扩展成完整的排班和号源体系引入 Redis 处理高并发预约用微信登录替换手工传入的 patientId增加操作日志和后台权限管理。每加一个点项目深度和面试可聊度都会往上走一截。最后给一个实际建议做这个项目时不要直接复制代码先自己在纸上把表和接口设计画一遍再动手写。设计过程中犯的错、改过的表结构才是这个项目给你留下的最值钱的经验。项目本身只是起点真正值钱的是你通过它建立起来的“从需求到落地的完整视角”。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑