先说明一下:这种“中小型医院网站系统”的项目,我在实际开发里接过不止一次。别误会,不是真的给医院建核心HIS系统,而是很多高校实训、毕业设计、以及一些小型诊所或私立机构的对外门户站,需求都是这一挂:能展示科室和医生、能挂号和预约、有简单的后台管理。技术栈固定得很死,SpringBoot + Vue + MyBatis + MySQL,前后端分离,要完整源码,要能跑起来。这篇文章就把我从零搭这套系统的完整思路、表结构设计、核心接口实现、前端联调细节、以及部署时容易踩的坑全部捋一遍。项目不大,但五脏俱全,搞懂这套,同类管理系统你基本都能拿下来。
1. 需求定位与功能模块梳理
1.1 中小型医院网站到底要做什么
很多人一上来就把项目想复杂了,一个门诊部级别的医院网站,硬要往三甲医院综合信息系统上靠,结果前后端代码堆了一堆没用的功能,最后演示时连核心流程都跑不通。我做了几个这类项目之后,总结下来的经验是:中小型医院网站系统的核心价值就三个词——信息展示、预约挂号、后台管理。
信息展示是门面,包括医院简介、科室介绍、医生排班、健康科普文章。用户进来先看这个,觉得靠谱才会用下一步的预约功能。预约挂号是业务核心,用户选科室、选医生、选时间段,提交预约单,后台能够审核或者自动通过。后台管理是运营支撑,管理员维护科室、医生、排班、公告、预约记录,得能统计每日预约量。
我建议项目迭代上先做基础版:前台用户端(访客可浏览,注册登录后预约)+ 管理后台(管理员维护基础数据、查看预约)。如果你的项目要求带简单问诊或者在线咨询,那是二期扩展,别塞进基础版里,不然后端接口和前端页面都会失控。
说白了,这套系统本质就是一个“内容管理 + 预约业务”的复合型Web应用。核心难点不在业务复杂度,而在前后端数据交互的一致性、预约排班的边界条件、以及权限控制是否干净。
1.2 角色划分与权限设计
权限这个东西,很多学生项目做得一团糟,本质原因是角色和接口没有对齐。我习惯先定义清楚三类角色:
- 未登录访客(ROLE_GUEST):可以浏览科室、医生、公告,但不能提交预约。
- 注册用户(ROLE_PATIENT):登录后可以提交预约、查看自己的预约记录、取消未接诊的预约。
- 管理员(ROLE_ADMIN):维护科室、医生、排班、公告,审核或查询所有预约。
权限控制在后端做,前端按钮隐藏在安全上没有任何意义。我的做法是Spring Security + JWT,接口上直接标@PreAuthorize("hasRole('ADMIN')")之类的注解,角色定义就放在 JWT 的 authorities 字段里。访客和患者的公共接口用permitAll,涉及预约写操作的接口必须要求登录,管理端接口单独收敛到/api/admin/**路径下,统一拦截校验。
前端路由上也要做对应处理:Vue Router 里配置全局守卫,根据本地存的 token 和角色信息决定能否进入后台页面。导航菜单按照角色动态渲染,比如未登录只显示“登录/注册”入口,患者登录后显示“我的预约”,管理员登录后显示“后台管理”。前后端双重控制,这才是能上线交付的样子。
2. 技术选型与项目搭建
2.1 为什么是这套技术栈
SpringBoot 的社区生态和自动配置能力,能让你在 10 分钟内拉起一个可运行的 Web 服务,内嵌 Tomcat,部署只需要一个 jar 包,省去独立配置服务器的繁琐步骤。Vue 对前端组件化和状态管理的支撑很成熟,配合 Element Plus 组件库,后台管理页面几乎是拼积木一样搭出来的。MyBatis 虽然不是最轻量的,但胜在 SQL 可控性强,复杂多表联查时直接手写 SQL,排除问题时一眼能看到底。MySQL 则中规中矩,免费稳定,中小型项目没有任何性能瓶颈。
这套组合最舒服的地方在于:团队里每个人都能找到自己熟悉的部分,Java 后端、JavaScript 前端、SQL 数据,三层各司其职。如果换成一套全栈框架,反而会模糊前后端边界,不利于项目演示和后续维护。
2.2 工程结构规划
后端工程我习惯按模块分包,而不是按技术类型分包。对比一下,很多项目是 controller、service、mapper 三个包平铺,结构是:
com.hospital.demo ├── controller ├── service ├── mapper这在小项目里还能看,一旦功能多了,controller 栈会比较混乱,比如预约相关接口和用户相关接口都挤在一堆文件里。
我更推荐按业务域分包:
com.hospital.system ├── common # 返回结果封装、异常处理、工具类 ├── config # 安全配置、CORS配置、MyBatis配置 ├── modules │ ├── auth # 登录注册、JWT处理 │ ├── user # 患者用户信息 │ ├── department # 科室 │ ├── doctor # 医生与排班 │ ├── appointment # 预约挂号的业务逻辑 │ └── announcement每个 module 内部再按 controller / service / mapper / entity 分层。这样定位问题时路径清晰,比如预约时间冲突问题,直接钻到modules/appointment里找 service,不用在一堆平铺文件里翻。
前端我更建议用 Vue CLI 或者 Vite 创建单页应用,目录结构按 views、components、router、store、api 划分。核心原则是组件只负责渲染和交互,业务数据请求全部收敛到api/目录里封装成独立函数,避免在组件里散落一堆axios.get。
2.3 环境版本建议
我踩过一个典型坑:某台机器上 Java 版本过新,SpringBoot 用 2.x,结果 JWT 库和某些注解不兼容,编译报错报了半天。版本这块先问清楚项目要求,没有强制要求的话,我这套组合实测最稳:
- JDK 1.8 或者 11
- SpringBoot 2.7.x
- Vue 2.6.14 或 Vue 3.2(我倾向 Vue 3 + Vite,组件写法更舒服)
- Element Plus(配 Vue 3)或 Element UI(配 Vue 2)
- MyBatis 2.2.x(配合 SpringBoot 官方 starter)
- MySQL 8.0
注意 MyBatis 不是 MyBatis-Plus,虽然 Plus 在开发效率上更高,但题意指定的是 MyBatis。如果你确实想省事,可以引入 MyBatis-Plus 的通用 CRUD 能力,不过项目报告里要说明你用的其实是 MyBatis 体系,避免指导教师或评审追问时对不上。
3. 数据库设计与核心表结构
3.1 设计思路
中小型医院网站的数据量不会很大,表结构不需要过度设计,但有几张表的边界条件必须想明白,否则后面写业务逻辑会痛苦。我规划了 7 张核心表,每一张都能回答一个具体的业务问题:
- 用户表:谁能登录系统?
- 科室表:医院有哪些诊疗方向?
- 医生表:每个科室有哪些医生?
- 排班表:医生在哪个时间段出诊?
- 预约表:患者约了哪个医生哪个时间段?
- 公告表:首页展示什么通知?
- 评论/留言表(可选):患者对就诊体验的反馈。
实际项目中,我把用户表设计成统一账号表。管理员和普通患者都用同一张 user 表,用 role 字段区分。表结构清晰,登录时只查一张表,不用去区分患者表和员工表两张表。
3.2 核心表结构说明
先看用户表:
CREATE TABLE `sys_user` ( `id` bigint NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '登录账号', `password` varchar(100) NOT NULL COMMENT 'BCrypt加密后的密码', `real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名', `gender` tinyint DEFAULT NULL COMMENT '0未知 1男 2女', `phone` varchar(20) DEFAULT NULL, `id_card` varchar(18) DEFAULT NULL COMMENT '身份证号(可选)', `role` varchar(20) NOT NULL DEFAULT 'PATIENT' COMMENT 'ADMIN/PATIENT', `status` tinyint NOT NULL DEFAULT '1' COMMENT '1启用 0禁用', `created_at` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统用户表';密码字段必须存加密后的数据,用 BCrypt。明文密码是最低级的错误,实训项目里如果被问到安全问题,这也能证明你考虑过防护。
科室表:
CREATE TABLE `department` ( `id` bigint NOT NULL AUTO_INCREMENT, `dept_name` varchar(100) NOT NULL, `intro` text COMMENT '科室介绍', `location` varchar(100) COMMENT '科室位置', `sort_order` int DEFAULT '0', `status` tinyint DEFAULT '1', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='科室表';医生表与排班表是预约功能的基础,我把排班设计成一句话:排班记录的是一个医生在某天的某个午别(上午/下午)是否有号、剩余多少号。医生表关联科室,排班表关联医生和时间。
预约表是整个系统最关键的表:
CREATE TABLE `appointment` ( `id` bigint NOT NULL AUTO_INCREMENT, `user_id` bigint NOT NULL COMMENT '预约人ID', `doctor_id` bigint NOT NULL, `schedule_id` bigint NOT NULL COMMENT '排班ID', `appointment_date` date NOT NULL, `time_slot` varchar(10) NOT NULL COMMENT 'MORNING/AFTERNOON', `status` tinyint NOT NULL DEFAULT '0' COMMENT '0待接诊 1已完成 2已取消', `remark` varchar(255) DEFAULT NULL COMMENT '病情描述', `created_at` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='预约挂号表';注意这里我把appointment_date和time_slot冗余到了预约表里。虽然通过 schedule_id 可以关联查出来,但冗余存储有两个好处:一是业务上展示很方便,查预约记录时不需要每一条都去连接排班表;二是排班表后续调整时,预约记录不受影响。
3.3 唯一索引与并发控制
预约表的(schedule_id, appointment_date, time_slot, user_id)要加唯一索引,避免同一个人重复提交同一个时段的预约。这个唯一索引比在 Service 里手工判断要可靠得多,因为高并发场景下,两个请求同时查到没预约过,然后同时插入,Service 判断防不住,但数据库唯一索引能挡住。
类似这种场景的例子是抢课、抢号。医院预约就是典型的有限并发场景,数据库层面的兜底必不可少。
4. 后端核心功能实现要点
4.1 统一返回结构与全局异常处理
前后端分离项目,必须定义一个统一的返回格式。我习惯下面这种:
{ "code": 200, "message": "success", "data": {...} }对应的 Java 类:
@Data public class Result<T> { private Integer code; private String message; private T data; public static <T> Result<T> ok(T data) { Result<T> r = new Result<>(); r.code = 200; r.message = "success"; r.data = data; return r; } public static <T> Result<T> error(Integer code, String message) { Result<T> r = new Result<>(); r.code = code; r.message = message; return r; } }同时配合@RestControllerAdvice做全局异常拦截。业务抛异常、参数校验失败、认证失败,统一转换成 Result 结构返回,前端不管遇到什么问题,解析的都是同一个 JSON 结构,联调时不用到处找字段名。
4.2 JWT认证流程
登录接口流程是这样的:接收用户名密码,查库,用 BCrypt 校验密码,通过后生成 JWT 令牌返回给前端。前端把 token 存在 localStorage 或 pinia/vuex 状态里,之后每个请求在 axios 拦截器中自动带上Authorization: Bearer <token>。
后端通过 OncePerRequestFilter 过滤器,对每个请求做 token 解析和校验,把用户信息塞进 SecurityContext,后续接口通过@AuthenticationPrincipal或者 SecurityContextHolder 获取当前用户信息。关键代码:
String authHeader = request.getHeader("Authorization"); if (authHeader != null && authHeader.startsWith("Bearer ")) { String token = authHeader.substring(7); try { Claims claims = Jwts.parser() .setSigningKey(SECRET_KEY) .parseClaimsJws(token) .getBody(); String username = claims.getSubject(); // 封装并存储到 SecurityContext } catch (Exception e) { // token 无效,不设置认证信息,后续权限拦截自然会拒绝 } }我建议 token 有效期设置成 2 到 4 小时,别太长。别人演示时不会在意这种细节,但你项目文档里如果能写清楚令牌过期时间、刷新策略,这是加分项。
4.3 预约接口的核心逻辑
预约提交是整个系统最核心的接口,逻辑有几层要堵:
- 查出排班记录,确认状态是启用中的。
- 校验排班日期是否大于等于今天,过去的日期不能预约。
- 检查剩余号数,大于 0 才能预约。
- 插入预约记录,同时扣减排班表的剩余号数。
- 如果已经约满,返回友好提示“该时段已约满”。
这里有个事务问题:扣减号数和插入预约记录必须放在同一个事务里。我是用@Transactional注解包住整个方法,然后做乐观锁控制。
@Transactional(rollbackFor = Exception.class) public Appointment createAppointment(Long scheduleId, String remark) { DoctorSchedule schedule = scheduleMapper.selectByIdForUpdate(scheduleId); if (schedule.getRemaining() <= 0) { throw new BusinessException("该时段已约满"); } schedule.setRemaining(schedule.getRemaining() - 1); scheduleMapper.updateById(schedule); // 构建预约记录并插入 }selectByIdForUpdate是SELECT ... FOR UPDATE的行级锁,把排班记录锁住。这样两个并发请求同时进来时,第二个只能等第一个事务提交后才能拿到排班记录,此时剩余号数已经被扣减,再判断就会返回约满。这是解决并发预约的关键手段。
4.4 MyBatis多表查询的写法
MyBatis 的设计哲学是写 SQL。预约列表接口需要展示科室、医生、日期、时间段,这时候连接查询比多张表分开查再组装要高效,代码也直观。典型 Mapper XML:
<select id="selectAppointmentDetail" resultType="com.hospital.system.modules.appointment.vo.AppointmentVO"> SELECT a.id, a.appointment_date, a.time_slot, a.status, a.remark, d.dept_name, doc.name AS doctor_name, doc.title AS doctor_title FROM appointment a LEFT JOIN doctor doc ON a.doctor_id = doc.id LEFT JOIN department d ON doc.dept_id = d.id WHERE a.id = #{id} </select>VO(视图对象)专门用来承接这种联表查询结果,尽量别把数据库实体直接吐给前端。理由是:前端不关心你的表字段结构,只关心页面需要展示什么。比如 user 实体里有密码字段,如果你直接返回实体,前端虽然拿不到(要是写了敏感字段的 getter),但一旦序列化疏忽,密码字段就漏出去了。安全习惯要从这个层面就养起来。
5. 前端页面开发与接口联调
5.1 路由与页面结构规划
前端路由分成两块:访客/患者端和管理端。我按业务设计的页面:
- 首页
/:轮播图、医院简介、公告列表、快捷挂号入口。 - 科室列表
/departments:卡片式展示科室,点进去看到科室和该科室医生。 - 医生详情
/doctor/:id:医生简介、排班表、预约按钮。 - 登录
/login、注册/register:注册即患者角色。 - 个人中心
/my-appointments:当前登录患者的预约记录,可取消操作。 - 管理后台
/admin:仪表盘、科室管理、医生管理、排班管理、预约管理、公告管理。
路由守卫实现这样:
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token'); if (to.path.startsWith('/admin')) { const role = localStorage.getItem('role'); if (token && role === 'ADMIN') next(); else next('/login'); } else if (to.path === '/my-appointments' && !token) { next('/login'); } else { next(); } });5.2 axios封装与请求拦截
前端请求层我封装了一个统一的 request 模块,导出各业务模块对应的 API 函数。axios 拦截器里做两件事:请求时带 token,响应时统一处理 code。
request.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) { config.headers.Authorization = 'Bearer ' + token; } return config; }); request.interceptors.response.use( res => { const { code, message, data } = res.data; if (code === 200) { return data; } else if (code === 401) { localStorage.clear(); router.push('/login'); return Promise.reject(new Error(message)); } else { ElMessage.error(message); return Promise.reject(new Error(message)); } }, err => Promise.reject(err) );这里要注意,401 统一跳登录页面是个技术关键处理。如果不做,token 过期之后用户操作没有任何反应,页面看起来像卡住了一样。
5.3 预约表单的前后交互逻辑
前端预约表单有排班选择。点击某个医生排班后,前端要拿到该排班的剩余号数,实时显示“剩余 12 人”。如果剩余为 0,禁止提交。这个判断背后依赖的是后端接口/api/schedule/listByDoctorAndDate。
我处理的方式是:医生详情页初始加载时,调用后端子啊接口拉取该医生 7 天内的排班,前端以卡片横排展示。选中某天的排班后,下方显示预约按钮;若当天已约满,按钮置灰并显示“约满”。患者确认提交后写 remark 病情描述,提交到/api/appointment接口。
前端拿到的数据是展示层模型,前端不做业务判断,严格要求后端校验排班时间、剩余号数、重复预约等多个边界条件。前后端分离的设计原则是前端负责“展示对不对”,后端负责“业务能不能做”。
5.4 ECharts图表在管理后台的运用
管理后台的仪表盘,我一般展示三块数据:今日预约量、各科室预约占比(饼图)、近一周每日预约趋势(柱状图)。用 ECharts 实现不复杂,但关键在于给后端提供对应的统计接口。
后端统计接口是一个典型的练习点:近七日的每日预约量,SQL 里GROUP BY appointment_date即可。各科室预约占比需要连接科室、医生、预约三张表。这个接口返回的格式要和 ECharts 的数据结构对齐,比如饼图需要{ name: '内科', value: 35 },后端可以直接构造 List
这类统计功能虽然业务逻辑简单,但把 SQL 聚合、Map 构造、图表数据绑定这几个环节都打通了,整个项目的完整度会明显提升。
6. 部署流程与常见问题排查
6.1 本地快速启动指南
拿到源码后的部署流程,我提供一个实操过的顺序。假设你用的是 IDEA + 命令行前端工具。
后端启动:
- 创建本地数据库
hospital_web,执行sql/init.sql脚本初始化表和默认数据。 - 修改
application.yml的数据库连接,改成你自己的账号密码。 - 运行
mvn spring-boot:run,默认端口 8080。
默认数据里我会预置一个管理员账号:admin / admin123,一个患者测试账号:patient / 123456。演示时直接能登录,不用手动注册。
前端启动:
- 进入
frontend/目录,执行npm install。 npm run serve,默认端口 5173(Vite)或 8081(Vue CLI)。- 浏览器访问前端地址,用测试账号登录。
如果前端接口连不上后端,优先检查前端.env文件里的VITE_API_BASE_URL,确保代理配置指向http://localhost:8080。Vite 开发服务器配置代理:
server: { port: 5173, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }这样前端请求/api/login时会自动转发到后端的http://localhost:8080/api/login,不需要跨域配置也能工作。生产环境下,Nginx 反向代理也是同样原理。
6.2 前后端跨域配置
很多初学前后端分离的人在本地联调时遇到跨域,乱加一通 header 解决不了,我来给一个最可靠的两个位置解决方案:
后端明确开启全局 CORS 配置:
@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }但注意,如果你已经用了 Spring Security,CORS 配置必须生效于 Security 过滤器链之前。所以安全配置里要加http.cors(),只是WebMvcConfigurer配置有可能被 Security 层的拦截器挡住,OPTIONS 预检请求直接被 401 拦截掉。解决方法是安全配置中放行 OPTIONS 请求。这个坑非常经典。
如果你使用了 Vite 或 Vue CLI 开发服务器代理,那么前端请求和后端同源,跨域问题根本不存在。生产环境由 Nginx 反向代理,同样不存在跨域。所以我个人浅见,后端 CORS 配置是兜底方案,首选是代理。
6.3 常见错误清单
我把做这类项目过程中遇到频率最高的错误列成一张表,你可以直接对照排查:
| 错误现象 | 可能原因 | 解决方案 |
|---|---|---|
| 前端请求报 401,页面反复跳登录 | JWT 过期、token 没带上、Security 放行了错误路径 | 检查后端放行名单是否包含 /api/login、检查前端拦截器是否带 token |
| 登录时报“密码错误”,但密码明明是 admin123 | 默认数据脚本里的密码没有经过 BCrypt 加密 | 直接去数据库跑一个 BCrypt 加密工具生成密文,或者用注册接口注册一个新管理员 |
| 预约接口返回 500,控制台报“数据被截断” | 枚举值或长度超限 | 检查 status 字段类型,确保用 tinyint 映射 0/1/2 |
| 页面能出数据,但图片全部裂开 | 上传的图片路径是相对路径,跨域或代理后失效 | 统一用绝对 URL 组装图片路径,生产环境配合 Nginx 设置静态资源路由 |
| 部署到服务器后接口能通但访问静态资源 404 | 打包后的 dist 目录路径问题 | Nginx 里配置root /your_project/dist,location 指向准确路径,try_files $uri /index.html |
顺带说一个特别常见的坑:SpringBoot 多模块项目里,如果 resources 目录下没有mapper文件夹,或者 Mapper XML 文件没有被 Maven 打包进 jar,运行时 MyBatis 会报Invalid bound statement (not found)。解决方案是在 pom.xml 的 build 插件里配置 resources 包含 XML 文件路径,或者把 Mapper XML 放在 resources/mapper 目录下,保证打包后 classes 里有对应文件。
6.4 Nginx部署实战
生产环境部署我通常不用 jar 直接裸奔,而是前端打包后交由 Nginx 托管,后端 jar 通过 systemd 守护进程方式运行。
后端 jar 启动:
nohup java -jar hospital-system-1.0.0.jar --server.port=8080 > app.log 2>&1 &Nginx 配置同时处理前端静态资源和 API 反向代理:
server { listen 80; server_name your-domain.com; root /usr/share/nginx/hospital-frontend; index index.html; # 前端路由 history 模式 location / { 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; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }这里try_files $uri $uri/ /index.html是 Vue Router history 模式的关键,不加这行,直接访问/my-appointments就会 404。
还有一个小细节是数据库连接池配置。默认的 HikariCP 在长时间空闲后,MySQL 可能会断开空闲连接,报Communications link failure。在application.yml里加:
spring: datasource: hikari: connection-test-query: SELECT 1 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000这就是保活设置。很多项目跑到第二天突然接口全部超时,大概率就是这个原因。
7. 项目扩展与进阶心得
7.1 可扩展的方向
基础功能做完后,这套系统往上扩展的空间很大。
一个方向是引入 Redis 缓存。目前科室、医生列表这类热点数据每次请求都打 MySQL,如果访问量上来,压力会逐步增加。引入 Redis 做缓存,把科室列表、医生列表先缓存,预约时再走数据库强一致操作。
另一个方向是增加消息推送。患者预约成功、预约前一天提醒,这类需求可以通过接入简单消息服务实现。不过,如果是高校项目,不建议在这个方向耗费太多时间——评估要求里没有这项,价值不大。把基础功能做扎实、写清楚,比堆功能更能拿高分。
至于在线支付功能,我劝你别在基础版里加。支付涉及商户号申请和真实交易流程,项目演示时引入复杂度远大于收益。如果导师非要求有支付环节,你做一个“线下到付”状态标记即可,预约成功显示“请到院支付挂号费”。
7.2 代码规范与项目文档
项目文档的重要性,很多时候被团队低估了。代码写得再好,如果 README 里没有启动步骤,数据库脚本没给全,部署说明一团糊,演示的时候手忙脚乱,印象分还是会打折扣。
我惯用的 README 骨架是:
- 项目简介与角色说明。
- 技术栈列表。
- 数据库初始化步骤。
- 后端启动步骤。
- 前端启动步骤。
- 默认账号。
- 接口文档简表(路径、方法、鉴权要求)。
- 常见问题(启动失败、连不上数据库等)。
接口文档不一定要用 Swagger,但可以引入,SpringBoot 集成 springdoc 很轻松。访问/swagger-ui.html就能看到在线接口列表。这个对评审、对自测、对前后端联调都非常有用,强烈建议加。
7.3 一点个人建议
最后分享一条我在这类项目上反复体会到的经验:功能宁可少而稳,不要多而崩。评审老师或者客户在演示时,更看重的是核心流程能不能从头到尾走通,以及你对自己代码逻辑的理解程度。与其堆一堆花哨功能但到处都是坑,不如把挂号预约这条主链路做深做透,把事务、并发、权限这些细节处理干净。这套系统的表结构、接口设计、部署方式,做完这一次,你后面再做 CRM、教务、物资管理,本质上就是换皮加改几个业务字段——底层套路都是相通的。