资讯动态

基于SpringBoot的社区诊所在线挂号与排队管理系统设计与实现

发布时间:2026/9/8 7:19:11 来源:尧图企业网站定制
社区诊所的挂号和排队管理是典型的“业务简单但流程繁琐”的场景。患者要起早到现场排队前台要手工登记、手工叫号医生要看纸质名单才知道下一个是谁。换成基于 SpringBoot 的在线挂号与排队应用系统后患者可以在手机上查看医生排班、预约挂号、查看当前叫号进度诊所前台和医生也能在后台统一管理号源和队列。这个题目非常适合作为 Java 计算机毕设项目它既有用户认证、增删改查、状态流转这些基本功又有排班冲突、号源并发、排队顺序这些值得展开的设计点工作量适中答辩时也容易讲清楚。本文围绕该毕设题目从需求拆解、技术选型、数据库设计、核心代码实现到运行验证、常见问题排查和答辩要点给出完整的技术方案。文中所有代码和配置都给出具体示例但项目落地时要结合自己的包名、数据库版本和 SpringBoot 版本做相应调整。1. 先理解系统要解决什么业务问题1.1 线下挂号和排队的核心痛点社区诊所的规模通常不大但高峰时段的挂号压力却不小。常见问题集中在三处患者只能到现场排队不知道当前医生是否出诊、号源还剩多少白白跑一趟。前台靠手工登记患者信息和挂号时间高峰期容易漏记、错记也难统计每天的挂号量。叫号依赖人工喊号医生和患者之间缺少一个公开、可查询的排队进度患者不敢离开候诊区。在线挂号与排队系统的目标就是把“查排班、挂号、排队、叫号”这条链路搬到线上让每个环节都有数据记录、有状态可查。1.2 系统角色与核心业务流程从业务角色看系统至少需要三类用户角色核心诉求典型操作患者快速挂号、知道排队进度注册登录、查看排班、在线挂号、查询排队医生查看当日号源和候诊队列查看排班、查看候诊列表、叫号管理员/前台维护基础数据、统计挂号情况管理科室、管理医生、设置排班、查看统计核心业务流程可以理解为一条状态链路查看排班 - 选择号源 - 提交挂号 - 支付或确认 - 进入候诊队列 - 医生叫号 - 就诊完成每一步都对应数据库中的状态字段。挂号记录有“待就诊、候诊中、已叫号、已完成、已取消”等状态排班表有“未开始、进行中、已结束”等状态。把这些状态设计清楚后面的代码实现才会顺手。1.3 为什么选 SpringBoot 作为毕设技术栈SpringBoot 适合做这类管理系统的原因有三个第一启动成本低。内置 Tomcat不需要单独部署外部容器写一个main方法就能启动 Web 服务。第二生态完善。要连数据库有 MyBatis、MyBatis-Plus、Spring Data JPA要做鉴权有 Spring Security、Sa-Token、JWT要写接口文档有 Knife4j、Swagger几乎每个功能点都能找到成熟集成方案。第三答辩容易展示。Spring Boot 的核心特征比如自动配置、约定优于配置、起步依赖都是面试和答辩中常被追问的点。用这个题目做毕设等于把 Spring 家族的知识串了一遍。2. 技术选型与项目结构设计2.1 后端技术栈与版本建议毕设项目不需要追求最新版本稳定、资料多才是重点。推荐组合如下组件推荐选择说明开发语言Java 8 或 Java 17SpringBoot 2.x 用 Java 8SpringBoot 3.x 必须 Java 17后端框架SpringBoot 2.7.18 或 3.x2.7.x 资料最多3.x 更贴近新项目ORMMyBatis-Plus自带分页插件和代码生成器减少重复 CRUD数据库MySQL 8.0使用 InnoDB 引擎utf8mb4 字符集鉴权方案JWT 拦截器无状态适合前后端分离接口文档Knife4j 或 Springdoc方便答辩时现场演示接口构建工具Maven毕设首选依赖管理直观需要说明的是SpringBoot 3.x 与 2.x 在依赖命名上有差别比如javax.servlet变成了jakarta.servlet。如果用的是网上找的旧教程务必先确认 SpringBoot 版本否则会大面积报编译错误。2.2 前端方案选择前端有两种常见路线一种是前后端分离使用 Vue 3 Element Plus通过 Axios 调用后端接口。这种方案更接近企业开发方式但需要额外准备 Node.js 环境工作量大一些。另一种是服务端渲染后端模板使用 Thymeleaf 配合 Bootstrap、Layui 等 UI 库。这种方案不用单独启动前端工程部署简单适合时间紧、想把重点放在 Java 代码上的同学。如果目标是快速完成毕设并保证演示稳定推荐后端接口 Vue 管理端的方式患者端用简单 HTML 页面管理端用 Vue Element Plus。如果只做一个端建议优先做管理端因为挂号、排班、叫号这些核心功能都在管理端体现。2.3 项目目录结构一个典型的前后端分离后端工程目录如下clinic-registration ├── src/main/java/com/example/clinic │ ├── ClinicApplication.java │ ├── controller │ │ ├── AuthController.java │ │ ├── DoctorController.java │ │ ├── ScheduleController.java │ │ └── RegistrationController.java │ ├── service │ │ ├── UserService.java │ │ ├── ScheduleService.java │ │ ├── RegistrationService.java │ │ └── QueueService.java │ ├── mapper │ │ ├── UserMapper.java │ │ ├── ScheduleMapper.java │ │ └── RegistrationMapper.java │ ├── entity │ │ ├── User.java │ │ ├── Doctor.java │ │ ├── Schedule.java │ │ └── Registration.java │ ├── common │ │ ├── Result.java │ │ ├── GlobalExceptionHandler.java │ │ └── JwtUtil.java │ └── config │ ├── WebMvcConfig.java │ └── MybatisPlusConfig.java └── src/main/resources ├── application.yml └── mapper ├── UserMapper.xml └── RegistrationMapper.xml分包原则是controller 只做参数接收和结果封装service 写业务逻辑mapper 访问数据库。不要把所有代码堆在 controller 里否则答辩时问“业务逻辑在哪一层”会很难回答。3. 数据库设计与核心表结构3.1 实体关系分析社区诊所挂号系统的核心实体有五张表关系如下department科室表例如内科、外科、儿科。doctor医生表归属某个科室。schedule排班表记录某医生某天哪个时段出诊、剩余号源数。user用户表区分患者、医生、管理员三种角色。registration挂号记录表记录患者挂了哪个医生哪个时段的号并关联排队状态。关系可以概括为一个科室有多名医生一名医生有多个排班一个排班可以被多个挂号记录引用一次挂号对应一个患者。3.2 建表 SQL下面给出核心表的 MySQL 建表语句实际项目中可以根据需要增加字段。CREATE DATABASE IF NOT EXISTS clinic_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE clinic_db; CREATE TABLE department ( id BIGINT PRIMARY KEY AUTO_INCREMENT, dept_name VARCHAR(50) NOT NULL COMMENT 科室名称, description VARCHAR(200) COMMENT 科室说明, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINEInnoDB COMMENT科室表; CREATE TABLE doctor ( id BIGINT PRIMARY KEY AUTO_INCREMENT, dept_id BIGINT NOT NULL COMMENT 所属科室, name VARCHAR(50) NOT NULL COMMENT 医生姓名, title VARCHAR(50) COMMENT 职称如主任医师, avatar VARCHAR(200) COMMENT 头像地址, introduction TEXT COMMENT 医生简介, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINEInnoDB COMMENT医生表; CREATE TABLE schedule ( id BIGINT PRIMARY KEY AUTO_INCREMENT, doctor_id BIGINT NOT NULL COMMENT 医生ID, work_date DATE NOT NULL COMMENT 出诊日期, time_slot VARCHAR(20) NOT NULL COMMENT 时段如上午、下午, total_count INT NOT NULL DEFAULT 20 COMMENT 总号源数, remain_count INT NOT NULL DEFAULT 20 COMMENT 剩余号源数, status TINYINT DEFAULT 0 COMMENT 0未开始 1进行中 2已结束, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_doctor_date_slot (doctor_id, work_date, time_slot) ) ENGINEInnoDB COMMENT医生排班表; CREATE TABLE user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(100) NOT NULL COMMENT BCrypt加密后的密码, real_name VARCHAR(50) COMMENT 真实姓名, phone VARCHAR(20) COMMENT 手机号, role TINYINT DEFAULT 0 COMMENT 0患者 1医生 2管理员, doctor_id BIGINT COMMENT 如果角色是医生关联医生表, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINEInnoDB COMMENT用户表; CREATE TABLE registration ( id BIGINT PRIMARY KEY AUTO_INCREMENT, schedule_id BIGINT NOT NULL COMMENT 排班ID, user_id BIGINT NOT NULL COMMENT 患者用户ID, patient_name VARCHAR(50) NOT NULL COMMENT 患者姓名, patient_phone VARCHAR(20) COMMENT 患者手机号, status TINYINT DEFAULT 0 COMMENT 0待就诊 1候诊中 2已叫号 3已完成 4已取消, queue_no INT COMMENT 排队号, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_schedule_status (schedule_id, status) ) ENGINEInnoDB COMMENT挂号记录表;几个关键点schedule表加了唯一索引uk_doctor_date_slot防止同一医生同一天同一时段重复创建排班。remain_count用于控制号源数量每次挂号先判断是否大于 0再扣减。queue_no是排队号生成规则为“当天该排班下已有挂号数加 1”这样患者能直观看到自己是第几个。3.3 挂号状态与排队状态设计registration.status是整个系统的核心状态字段设计时要注意状态只能按合法方向流转0待就诊 - 1候诊中 - 2已叫号 - 3已完成 - 4已取消待就诊或候诊中时状态流转需要写在 service 层不建议让前端直接传状态值覆盖。比如“已完成”只能由医生端在叫号后触发患者端不能把自己改成已完成。答辩时可以重点讲这个设计它体现了“状态机”思想避免数据被随意篡改。4. 环境准备与项目初始化4.1 开发环境要求在启动项目之前先把环境检查一遍避免后期把时间浪费在环境问题上。项目最低要求建议JDK1.8使用 JDK 8 或 17并配置 JAVA_HOMEMaven3.6使用阿里云镜像加速依赖下载MySQL5.7推荐 8.0注意 root 密码IDEIntelliJ IDEA安装 Lombok 插件Node.js16仅前端分离方案需要如果电脑上还没有配置 Java 环境在命令行执行java -version能看到版本信息才算配置成功。很多同学项目启动失败不是代码问题而是 JDK 安装后没有设置JAVA_HOME导致 IDEA 或 Maven 找不到 JDK。4.2 创建项目与核心依赖在 start.spring.io 或 IDEA 中创建 SpringBoot 项目Maven 依赖如下dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.2/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-impl/artifactId version0.11.5/version scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies这里要提醒一点如果使用 SpringBoot 3.xmysql-connector-j的 scope 保持 runtime 即可如果使用 SpringBoot 2.7.x 且 MySQL 驱动版本不匹配可能出现“Public Key Retrieval is not allowed”报错此时在 JDBC 连接串后加?allowPublicKeyRetrievaltrueuseSSLfalse可以解决。4.3 配置文件 application.ymlserver: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/clinic_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrueuseSSLfalse username: root password: 123456 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 mybatis-plus: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl jwt: secret: clinic-registration-secret-key expire-hours: 24配置说明map-underscore-to-camel-case: true可以自动把数据库的dept_id映射为实体的deptId减少大量 resultMap 配置。log-impl配置为 StdOutImpl 后控制台会打印 SQL方便调试。生产环境应移除或改成其他日志实现。JWT 的 secret 在真实项目中不能写死应该放到环境变量或配置中心这里只是示例。5. 核心功能实现5.1 用户登录与 JWT 认证登录流程是前端提交用户名和密码后端校验通过后生成 JWT 返回给前端前端在后续请求中通过Authorization: Bearer token携带令牌。后端用拦截器统一校验。先生成令牌的工具类Component public class JwtUtil { Value(${jwt.secret}) private String secret; Value(${jwt.expire-hours}) private Long expireHours; public String generateToken(Long userId, String username, Integer role) { return Jwts.builder() .setSubject(username) .claim(userId, userId) .claim(role, role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() expireHours * 3600 * 1000)) .signWith(SignatureAlgorithm.HS256, secret) .compact(); } public Claims parseToken(String token) { return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); } }再写一个拦截器只拦截需要登录的接口Component public class AuthInterceptor implements HandlerInterceptor { Resource private JwtUtil jwtUtil; Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String auth request.getHeader(Authorization); if (auth null || !auth.startsWith(Bearer )) { throw new BusinessException(401, 未登录); } String token auth.substring(7); Claims claims jwtUtil.parseToken(token); request.setAttribute(userId, claims.get(userId)); request.setAttribute(role, claims.get(role)); return true; } }登录接口的实现重点是密码加密。不要用明文存储密码推荐使用 BCryptService public class AuthService { Resource private UserMapper userMapper; Resource private JwtUtil jwtUtil; public Result login(String username, String password) { User user userMapper.selectByUsername(username); if (user null) { return Result.error(用户不存在); } if (!BCrypt.checkpw(password, user.getPassword())) { return Result.error(密码错误); } String token jwtUtil.generateToken(user.getId(), user.getUsername(), user.getRole()); return Result.success(token); } }这里体现了一个常见的设计原则密码校验不告诉用户“用户名不存在”还是“密码错误”更安全但在毕设场景中为了演示方便通常可以直接返回具体错误信息。5.2 医生排班查询接口患者挂号的入口是查看排班。排班查询需要联表返回医生姓名、科室、剩余号源等信息。RestController RequestMapping(/api/schedule) public class ScheduleController { Resource private ScheduleService scheduleService; GetMapping(/list) public Result listSchedule(RequestParam(required false) Long deptId, RequestParam(required false) String workDate) { return Result.success(scheduleService.listSchedule(deptId, workDate)); } }Service 层使用 MyBatis-Plus 的 LambdaQueryWrapperpublic ListScheduleVO listSchedule(Long deptId, String workDate) { LocalDate date workDate null ? LocalDate.now() : LocalDate.parse(workDate); return scheduleMapper.selectScheduleVO(deptId, date); }如果只查单表用 QueryWrapper 就够了需要联查医生姓名时建议在 Mapper 里写自定义 SQLselect idselectScheduleVO resultTypecom.example.clinic.vo.ScheduleVO SELECT s.id, s.work_date AS workDate, s.time_slot AS timeSlot, s.total_count AS totalCount, s.remain_count AS remainCount, d.name AS doctorName, dp.dept_name AS deptName FROM schedule s JOIN doctor d ON s.doctor_id d.id JOIN department dp ON d.dept_id dp.id WHERE s.work_date #{workDate} AND (#{deptId} IS NULL OR d.dept_id #{deptId}) ORDER BY s.work_date, s.time_slot /select用 VO 类而不是直接返回实体可以避免把数据库字段暴露到前端这是接口设计的好习惯。5.3 在线挂号接口挂号是系统中最需要“防并发”的业务。核心逻辑是判断排班剩余号源是否充足扣减remain_count创建挂号记录生成排队号。Transactional(rollbackFor Exception.class) public Registration createRegistration(Long scheduleId, Long userId, String patientName, String patientPhone) { // 1. 查询排班并加行锁防止并发下超挂 Schedule schedule scheduleMapper.selectByIdForUpdate(scheduleId); if (schedule null) { throw new BusinessException(排班不存在); } if (schedule.getRemainCount() 0) { throw new BusinessException(号源已满); } // 2. 判断同一患者是否重复挂号 Long count registrationMapper.countByUserAndScheduleAndStatus( userId, scheduleId, 0, 1, 2); if (count 0) { throw new BusinessException(您已挂过该时段的号请勿重复挂号); } // 3. 扣减号源 schedule.setRemainCount(schedule.getRemainCount() - 1); scheduleMapper.updateById(schedule); // 4. 生成排队号 int queueNo registrationMapper.countByScheduleId(scheduleId) 1; // 5. 创建挂号记录 Registration registration new Registration(); registration.setScheduleId(scheduleId); registration.setUserId(userId); registration.setPatientName(patientName); registration.setPatientPhone(patientPhone); registration.setStatus(0); registration.setQueueNo(queueNo); registrationMapper.insert(registration); return registration; }这里的selectByIdForUpdate是排他锁select idselectByIdForUpdate resultTypecom.example.clinic.entity.Schedule SELECT * FROM schedule WHERE id #{id} FOR UPDATE /selectFOR UPDATE的作用是当两个请求同时挂号时第二个请求会等待第一个请求的事务提交后再读取数据从而避免超卖。这是面试和答辩中一个很好的加分点但要说明它只适用于单体应用和小并发场景分布式场景需要用 Redis 分布式锁或乐观锁。5.4 排队叫号与状态流转医生端进入“工作台”后可以看到当天排班的候诊列表。叫号逻辑如下查询当前排班下状态为“待就诊”和“候诊中”的挂号记录。把最靠前的“待就诊”记录改为“候诊中”。把前一条“候诊中”记录改为“已叫号”。返回当前叫到的排队号和患者信息。简化代码如下public QueueInfo callNext(Long scheduleId) { // 找到最早一个待就诊的挂号记录 Registration next registrationMapper.selectFirstWaiting(scheduleId); if (next null) { throw new BusinessException(当前没有待就诊患者); } // 将之前候诊中的记录置为已叫号 registrationMapper.updateStatusBySchedule(scheduleId, 1, 2); // 将最早待就诊的记录置为候诊中 next.setStatus(1); registrationMapper.updateById(next); return new QueueInfo(next.getQueueNo(), next.getPatientName()); }前端每隔 5 秒轮询一次“当前叫号”接口就可以在大屏或患者端展示“当前呼叫5 号张三”。轮询是毕设中最容易实现也最稳定的方式接口设计为GetMapping(/current) public Result currentQueue(RequestParam Long scheduleId) { Registration current registrationMapper.selectCurrentCall(scheduleId); return Result.success(current); }如果想做得更“高级”可以扩展为 SSE 或 WebSocket 推送但优先级不高。5.5 前端页面与接口联调示例前端以 Vue 3 为例挂号页面的核心逻辑是选择排班、点击挂号、查看排队号。script setup import axios from axios import { ref } from vue const scheduleList ref([]) const loading ref(false) async function loadSchedule() { loading.value true const res await axios.get(/api/schedule/list, { headers: { Authorization: Bearer localStorage.getItem(token) } }) scheduleList.value res.data.data loading.value false } async function doRegister(scheduleId) { const res await axios.post(/api/registration/create, { scheduleId, patientName: localStorage.getItem(realName), patientPhone: localStorage.getItem(phone) }) alert(挂号成功您的排队号是 res.data.data.queueNo) } /script联调时要注意前端请求的/api路径要代理到后端8080端口。Vite 开发环境下在vite.config.js中配置export default defineConfig({ server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })不配置代理时前端直接访问http://localhost:8080/api/xxx也可以但会产生跨域问题需要后端额外允许跨域。6. 运行验证与接口测试6.1 启动项目与基础检查启动流程按顺序执行# 1. 启动 MySQL确认 clinic_db 数据库已创建 mysql -uroot -p # 2. 执行 src/main/resources/sql/init.sql 建表 # 3. 在 IDEA 中运行 ClinicApplication.java # 4. 看到日志中 Tomcat started on port(s): 8080 表示启动成功启动后先访问数据库确认表结构USE clinic_db; SHOW TABLES; SELECT COUNT(*) FROM department;6.2 核心接口测试场景用 Postman 或 Apifox 按照以下顺序测试步骤接口入参示例预期结果1POST /api/auth/registerusernametest, password123456注册成功2POST /api/auth/loginusernametest, password123456返回 token3GET /api/schedule/listdeptId1返回排班列表4POST /api/registration/createscheduleId1返回排队号5GET /api/queue/currentscheduleId1返回当前叫号6.3 异常场景验证除了正常流程还要主动测试异常分支这些也是答辩演示的重点号源已满时挂号应返回“号源已满”。同一个患者对同一排班重复挂号应返回“请勿重复挂号”。未携带 token 访问挂号接口应返回 401。医生对空队列叫号应返回“当前没有待就诊患者”。把这些测试用例整理成表格放进论文的“功能测试”章节比只写“系统运行正常”有说服力得多。7. 常见问题与排查路径7.1 项目启动失败数据库连接异常现象启动时日志报Communications link failure或Access denied for user。排查顺序确认 MySQL 是否已启动systemctl status mysql或任务管理器查看服务。确认数据库名、用户名、密码是否与application.yml一致。确认连接串中的serverTimezone是否设置时区不对可能导致启动报错。用 Navicat 或命令行先直连一次数据库确认不是密码问题。常见原因是创建数据库时写了clinic_db配置里却写成了clinicdb这类低级错误优先排查。7.2 表字段映射失败查询结果全为 null现象接口返回成功但deptName、doctorName等字段为 null。原因通常是数据库字段的蛇形命名dept_id没有映射到实体的驼峰属性deptId。检查方式确认application.yml中map-underscore-to-camel-case: true是否配置。自定义 SQL 中是否使用了别名例如d.dept_id AS deptId。实体类字段是否有TableField注解且注解里的字段名与数据库一致。推荐做法是单表查询依赖 MyBatis-Plus 的驼峰映射联表查询统一用别名两层保障。7.3 端口被占用现象启动报Port 8080 was already in use。处理命令# Windows netstat -ano | findstr 8080 taskkill /PID 进程号 /F # Linux / macOS lsof -i :8080 kill -9 进程号如果不想杀进程也可以直接改application.yml中的server.port。7.4 JWT 解析失败拦截器报错现象前端登录后请求接口返回 500日志中有JWT signature does not match或io.jsonwebtoken.security.SignatureException。原因通常是 JWT 工具类中signWith使用的 secret 长度不满足算法要求或者前端传的 token 被截断。HS256 算法要求 secret 至少 256 位32 字节推荐使用至少 32 个字符的字符串。检查方式打印前端实际发送的Authorization头确认 token 没有多出空格。确认后端 JWT 工具类中生成和解析使用同一个 secret没有因为环境变量覆盖产生两套值。7.5 前端跨域请求失败现象浏览器控制台报CORS policy: No Access-Control-Allow-Origin。解决方式有两个一是后端配置全局跨域Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowCredentials(true) .maxAge(3600); } }二是前端使用 Vite 代理把/api转发给后端。代理方案更接近生产环境推荐优先使用。8. 毕设答辩要点与生产环境扩展8.1 答辩时建议按这条链路讲解答辩演示不要只点页面建议按业务链路走边操作边说明注册登录展示密码加密和 JWT 鉴权流程。管理员创建排班说明唯一索引如何防止重复排班。患者选号挂号说明FOR UPDATE行锁如何防止号源超卖。医生端叫号说明状态如何从“待就诊”流转到“候诊中”“已叫号”。查询统计展示当日挂号量、科室分布等统计结果。老师最常问的三个问题要提前准备为什么用 JWT 而不用 Session答前后端分离场景下 JWT 无状态服务端不保存会话扩展性好。并发挂号时怎么保证不超卖答数据库行锁 事务先锁排班记录再扣减号源。排队顺序怎么保证答queue_no在生成时取当前排班最大序号加一查询时按queue_no升序。8.2 功能扩展方向如果时间充裕可以在基础功能上增加以下模块扩展方向技术点对答辩的加分点短信通知阿里云短信 SDK体现第三方服务集成能力就诊记录电子病历 CRUD体现一对多关联设计统计报表ECharts 图表体现数据可视化能力排班规则校验每周固定排班生成体现复杂业务规则设计分布式锁Redis Redisson体现高并发设计意识建议优先做“就诊记录”和“统计报表”这两个模块数据关联清晰页面展示效果好答辩时最容易讲。8.3 学习环境与生产环境的差距毕设项目能跑通只是第一步要清楚生产环境还需要补什么配置不能写死在application.yml应使用环境变量或配置中心。密码、JWT secret 等敏感信息不能明文提交到代码仓库。接口要有参数校验、统一异常处理和操作日志不能只是“能返回数据”。数据库连接池参数需要根据并发量调整默认配置只适合开发环境。需要补充监控至少了解 Spring Boot Actuator 的/actuator/health健康检查接口。8.4 给新手的练习建议如果第一次接触 SpringBoot不要直接照着完整项目敲。建议按顺序做三个小练习先写一个用户的增删改查理解 Controller、Service、Mapper 三层结构。再加登录、JWT 和拦截器理解无状态鉴权的完整流程。最后做排班和挂号理解事务、锁和状态流转。这三个练习做完社区诊所挂号系统的主干功能就已经搭建完成剩下的填充内容和页面优化只是时间问题。这个题目的价值不在“新”而在“完整”。它把 SpringBoot 最常用的能力——Web 开发、ORM 操作、事务管理、权限认证、状态设计——全部串起来并且每一步都能在真实业务中找到对应场景。做的时候不求功能多把挂号到叫号这条主链路做扎实再熟练讲清楚每个设计决策就是一份合格的毕业设计作品。

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

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

免费获取报价