做高校医务室预约系统这类项目的同学和同行,这几年是越来越多了。原因其实很现实:Spring Boot + Vue 这套前后端分离组合,既能支撑起一个完整可运行的业务项目,也能在简历里讲成有真实使用场景的作品,而高校医务室的业务恰好又足够“小而全”——预约挂号、医生排班、健康档案、后台管理,每块业务都不算难,但串起来却能把全栈能力练得比较扎实。
这篇文章没什么高深理论,我把当时做完这套系统的完整思路拿出来聊聊,包括需求怎么拆、表怎么设计、预约并发怎么处理、Spring Boot 后端和 Vue 前端怎么落地,以及部署上线和排障过程中的一些经验教训。无论你是准备拿它做毕业设计或课程设计,还是想在企业里快速搭一套类似的预约平台,按这个思路走,基本不会跑偏。
1. 项目全貌与核心需求拆解
1.1 高校医务室预约到底在解决什么
先想明白业务背景。高校医务室跟三甲医院不一样,它服务的人群相对固定,就是本校师生,病种也偏向感冒发烧、跌打损伤、慢性复查这几类。但它的痛点很突出:看病人流高度聚集在课间和饭后,校医人数少,学生来了只能现场排队,而排队时间长又会把普通不适拖得更难受。更麻烦的是,以往的纸质登记大多散落在收费记录里,想统计季度常见病、跟踪某个学生的复诊情况,基本靠翻本子。
所以这个系统的定位其实有两层。第一层是预约调度:学生提前选定日期和时段,校医按排班接诊,现场不需要长时间等待,医务室也能按号源准备药品和人力。第二层是健康管理:就诊记录进入电子档案,学生可以查看自己的历史病历和健康数据,校医能看到同一位学生的既往情况,管理员还能按院系、时间维度做简单的常见病统计。预约负责引流,档案负责沉淀,两个模块靠“一次就诊”这条链路串在一起,这才是完整的高校健康管理平台。
1.2 为什么是 Spring Boot + Vue
技术选型上,现在类似系统的主流答案就是 Spring Boot 加 Vue,这背后有三层原因。第一层是后端生态成熟:Spring Boot 约定优于配置,一个 starter 就能把 Web、数据访问、参数校验、事务管理全部接好,内置 Tomcat 意味着打包成 jar 就能跑,开发调试成本很低,对个人开发者非常友好。网上教程、面试题、开源项目也大部分围绕这套技术栈,遇到问题搜得到,不容易卡死。
第二层是前端 Vue 的渐进式设计,对单人全栈开发特别合适。你不需要一开始就搞重型工程化方案,一个组件、一个路由、一个 store,按需引入即可;等页面多起来了,基于 Vite 的构建和热更新也很省心。第三层是分离架构本身带来的好处:后端只出 REST 接口,前端只处理界面,前后端可以并行开发,部署也能分开,这个模式在职场上也是最普遍的协作方式。相比之下传统的 Thymeleaf 模板拼页面的做法,学习成本低一些,但做出来的系统在展示和扩展上都要吃亏不少。
1.3 功能矩阵先定下来
动手写代码前,我建议先把功能矩阵画清楚,省得做到一半发现缺角色、缺页面。我按三种角色拆功能,供你直接参考。
| 角色 | 核心功能 | 关键动作 |
|---|---|---|
| 学生 | 登录注册、浏览公告、查看医生排班、在线预约/取消、预约记录、健康档案 | 选日期、选时段、填写病情描述、查看自己的就诊历史 |
| 校医 | 登录、排班管理、接诊记录、查看预约列表、维护健康档案 | 设定可预约时段、填写看诊结论、在档案中追加病情记录 |
| 管理员 | 用户管理、医生信息管理、公告发布、数据统计、系统配置 | 分配校医角色、查看预约率、导出简单统计报表 |
我这里有意把“数据统计”放到了管理员端,而不是让前端图表满天飞。原因是预约数据量没那么大,统计逻辑用 SQL 分组聚合就能完成,前端一个表格加两个柱状图就够用,没必要引入重量级 BI。后续如果要做“按院系统计感冒发生率”,只需在 appointment 表关联 user 表的 department 字段做 group by,扩展成本很低。
2. 数据库设计与核心难点
2.1 核心表结构怎么设计
数据库是这个项目的命根子,预约错乱、档案丢失基本都是表设计埋的雷。我最终落地的核心表包括:用户表、医生信息表、排班表、预约表、健康档案表、公告表。重点说几个容易出问题的设计。
用户表 sys_user,字段包括 id、username、password、real_name、role、student_no、department、phone、create_time。password 只存加密后的哈希,不要明文。role 用字符串枚举 student、doctor、admin 就行,权限粒度没复杂到需要单独建角色表。
医生信息表 doctor_info,核心字段是 doctor_user_id 关联 sys_user、title、department、intro、avatar。不要把所有医生资料都塞进用户表,因为医生信息里有职称、简介这类字段,和学生身份不是一回事,拆开更干净。
排班表 schedule 是最关键的一张表。字段包括 id、doctor_id、work_date、period、total、remaining、status,period 我用枚举表示上午、下午、晚上三个时段。这里最关键的是加一个唯一索引:
UNIQUE KEY uk_doctor_date_period (doctor_id, work_date, period)这个唯一索引保证同一个医生同一天同一时段只能有一条排班记录,业务上天然防重。
预约表 appointment,字段包括 id、schedule_id、student_user_id、student_name、reason、status、create_time。其中 status 我用枚举:已预约、已完成、已取消。这张表也要加唯一约束,但约束字段要谨慎:
UNIQUE KEY uk_schedule_student (schedule_id, student_user_id)这表示同一个学生不能在同一时间段重复预约。实际业务中“取消后重新预约”不能受影响,所以约束只在 status 为已预约时生效,具体要靠下面讲的并发控制配合,而不是单纯依赖数据库唯一约束。
健康档案表 health_record,字段包括 id、user_id、record_type、content、doctor_id、create_time。record_type 用来区分是体检数据、就诊记录还是健康宣教备注。content 统一存文本即可,第一版别想复杂,真要做结构化体检指标,再拆一张指标表就行。
2.2 预约并发这个坎怎么过
预约系统最典型的场景是:热门时段放号 20 个,几十上百个学生同时点预约。如果 service 层只写“先查剩余号源,大于 0 就插入预约”,并发条件下一定会超卖。我见过不少项目在这里翻车,演示时人多一点,预约记录就像雪崩一样超出排班名额。
解决方案要分两层。第一层是扣减号源用条件更新,也就是把“查询再更新”换成一条语句:
UPDATE schedule SET remaining = remaining - 1 WHERE id = #{scheduleId} AND remaining > 0这条 SQL 执行后返回影响行数。如果影响行数为 1,说明扣减成功,可以继续插入预约记录;如果影响行数为 0,说明已约满,直接抛出“该时段已满”的业务异常。配合事务,把扣减号源和插入预约放在一个方法里,数据库行锁保证了并发安全。
第二层是同一学生防重。除了唯一索引,还要在插入预约前判断该学生是否有同时间处于已预约状态的记录。哪怕前面判断通过了,唯一索引也会在并发插入时兜底,把重复记录直接挡在数据库外面,不会产生脏数据。
再往上加一层乐观锁也行:在 schedule 表加 version 字段,更新时带 where version = #{oldVersion}。不过条件更新已经够用,再加 version 反而增加代码复杂度,我实测下来没必要。真正要注意的是别用 synchronized 或本机锁,因为系统部署多个实例后,本地锁互相之间拦不住,不如数据库条件更新来得通用。
2.3 健康档案的权限边界
健康档案属于敏感数据,权限边界要从接口设计阶段就划清楚。我的原则是:学生只能读自己的档案,校医可以读自己接诊过的学生档案,管理员只能看统计数据,不能打开具体病历内容。
具体实现上,登录后从 JWT 中解析 userId 和 role,在查询档案的 Service 方法中强制拼接归属条件。比如学生查询:xxxxMapper.selectById 之前先判断 record 的 user_id 是否等于当前登录用户,不是则抛权限异常。校医查询则需要额外判断接诊记录里有没有当前医生,不能因为登录了医生账号就能看全校学生的健康信息。
这些边界看起来繁琐,但真上线后能规避很大风险。给老师演示的时候,特别是一键导出学生健康档案这种功能,一定要做操作日志,管理员做了什么、谁看了哪位学生的档案,都要留痕。安全不是加分项,是底线项。
3. Spring Boot 后端落地细节
3.1 项目结构与核心依赖
后端项目结构我按标准分层来走,分包清晰对个人维护和答辩都友好。controller 只接收参数和返回结果,service 写业务逻辑,mapper 操作数据库,entity 放实体类,config 放配置类,common 放统一返回和异常处理。
pom.xml 里核心依赖就这几样:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.5</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>这里要提醒一下版本问题。现在很多教程直接上 Spring Boot 3.x,但 3.x 要求 JDK 17,如果你的电脑还是 JDK 8,或者你用的是学校机房的老环境,请老老实实选 Spring Boot 2.7.x,对应把 MyBatis-Plus 选 3.5.3 左右就行。热词里天天有人问“Spring Boot 版本太高”,绝大部分都是 JDK 版本没跟上导致启动直接报错,不是代码问题。
application.yml 里重点写清楚数据源和时区:
server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/school_health?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: configuration: map-underscore-to-camel-case: true时区这段是我踩坑换来的。如果 serverTimezone 不配,或者配成 UTC,排班表里存的日期在页面显示时会莫名其妙差 8 小时,预约记录看起来就像“背约”,排查起来非常折腾。所有时间字段统一用 Asia/Shanghai,后端、MySQL、前端三层保持一致,能省很多事。
3.2 统一返回体和全局异常处理
接口返回格式从第一天就要统一,不然前端 axios 拦截器怎么写都会别扭。我定义了一个 Result 类,格式固定为 code、message、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> 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 做全局异常处理,业务异常、参数校验异常、系统异常分开处理,前端就能根据 code 做统一提示,而不是每次都要去抓莫名其妙的堆栈信息。
@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(BusinessException.class) public Result<Void> handleBusinessException(BusinessException e) { return Result.error(e.getCode(), e.getMessage()); } @ExceptionHandler(MethodArgumentNotValidException.class) public Result<Void> handleValidException(MethodArgumentNotValidException e) { return Result.error(400, e.getBindingResult().getFieldError().getDefaultMessage()); } }这个写法看起来基础,但价值很大。尤其是前端联调时,接口报错原因一眼能看到是“参数没传”还是“业务不允许”,不用两边来回猜。
3.3 JWT 登录认证和角色权限
登录接口校验用户名密码后,用 JWT 生成 token 返回前端。JWT 里我放三个字段:userId、username、role。后续前端请求在 Header 里带 Authorization: Bearer token,后端用一个拦截器统一解析。
核心拦截器逻辑不复杂:
@Component public class JwtInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("Authorization"); if (token == null || !token.startsWith("Bearer ")) { throw new BusinessException(401, "未登录"); } Claims claims = Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(token.substring(7)) .getBody(); UserContext.set(claims); return true; } }再配合一个 ThreadLocal 的 UserContext 存放当前登录用户信息,Service 层随时可以取,不需要反复读数据库。角色权限我用自定义注解加拦截器二次校验,比如 @RequireRole("doctor"),演示时讲权限设计也会有东西可说。
3.4 排班预约服务的核心实现
预约服务这段代码是整个系统最重要的部分,我直接给出当时可运行的核心写法:
@Service @Transactional(rollbackFor = Exception.class) public class AppointmentServiceImpl implements AppointmentService { @Autowired private ScheduleMapper scheduleMapper; @Autowired private AppointmentMapper appointmentMapper; @Override public Appointment createAppointment(AppointmentRequestDto dto) { // 1. 扣减号源,条件更新保证不超卖 int rows = scheduleMapper.decreaseRemaining(dto.getScheduleId()); if (rows == 0) { throw new BusinessException(500, "该时段已约满"); } // 2. 插入预约记录,唯一索引兜底防重复 Appointment appointment = new Appointment(); appointment.setScheduleId(dto.getScheduleId()); appointment.setStudentUserId(UserContext.getUserId()); appointment.setStudentName(UserContext.getUsername()); appointment.setReason(dto.getReason()); appointment.setStatus("booked"); try { appointmentMapper.insert(appointment); } catch (DuplicateKeyException e) { throw new BusinessException(500, "您已预约该时段,请勿重复操作"); } return appointment; } }scheduleMapper.decreaseRemaining 对应的 SQL 就是 2.2 节里的条件更新语句。事务注解保证扣号源和插预约要么同时成功,要么同时回滚。这里还有个小细节:事务上加 rollbackFor = Exception.class,不写的话 Spring 默认只对 RuntimeException 回滚,业务异常是自定义 RuntimeException 子类,尚可,但显式写出来更稳妥。
4. Vue 前端项目搭建与页面实现
4.1 从创建项目到成功跑起来
前端我用的 Vue 3 + Vite。创建命令很简单:
npm create vue@latest按提示选择需要的功能,如果只是做预约系统,Router、Pinia 选上,TS 看个人习惯。我建议新手先选 JavaScript,这样心智负担小一点,毕竟项目核心是业务不是类型体操。创建完以后 npm install 如果报 ERESOLVE 错误,多半是 node 版本和依赖版本冲突,升级 node 到 18+ 基本能解决。
这里有个高频问题值得单独说一下。很多同学把整个项目文件夹压缩发给别人,node_modules 动辄几百兆,对方解压后运行还会报各种路径错。标准做法是只发源码,接收方自己执行 npm install 安装依赖。如果是 Git 协作,记得在 .gitignore 里写死 node_modules,这个不写,仓库体积瞬间飙升。
环境配置上,我习惯在项目根目录建 .env.development 和 .env.production,分别配置接口地址。开发环境用 Vite 代理,生产环境用 nginx 转发,前端代码里只写相对路径 /api,不写死 IP。
4.2 页面路由与目录规划
src 下的目录我按 views、components、router、store、api、utils 组织。页面按角色拆分:student、doctor、admin 三个文件夹,每个角色相关的页面放在一起,找起来特别顺手。
路由是典型的动态路由,登录后根据角色加载对应菜单。学生端能进预约页、档案页;医生端能进排班管理、接诊记录;管理员端能进用户管理、统计页。前端动态路由的写法各家不同,我用的是一个通用思路:router.addRoute 在登录后按角色批量添加。
需要注意刷新后 404 问题。开发模式下用 createWebHistory 时,刷新某个深层路由会出现白屏,这是路由 history 模式和服务器配置不匹配导致的。开发阶段可以在 vite.config.js 里配 historyApiFallback,生产环境则是 nginx 的 try_files 配置,后面部署章节详细说。
4.3 Axios 封装与接口对接
axios 二次封装是必须做的,不然每个页面都写一遍完整请求代码,后面改 baseURL 或者加 token 头时能让人崩溃。
import axios from 'axios' const request = axios.create({ baseURL: '/api', timeout: 10000 }) request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) request.interceptors.response.use( response => { const res = response.data if (res.code === 200) { return res.data } if (res.code === 401) { router.push('/login') } return Promise.reject(new Error(res.message)) }, error => { return Promise.reject(error) } ) export default request这样页面里调接口就非常清爽,比如预约列表只需要写一行 request.get('/appointment/my')。响应拦截器里统一处理 401 跳登录页,每个接口都不用重复写“未登录就跳转”的逻辑。
Pinia 用来存用户信息,登录成功后把用户对象和 token 存进去,同时同步到 localStorage,刷新后还能恢复登录态。这个场景下不需要 Vuex,Pinia 更简洁,占用也小。
4.4 预约页面和组件化细节
预约页面是学生端最核心的交互,我拆成了三个步骤:选日期、选医生时段、填写病情描述确认。
日期选择用日期组件,限制只能选未来 7 天内且是工作日的时间。医生时段列表用一个卡片式组件展示,每张卡片显示医生姓名、职称、剩余号源数,剩余为 0 时置灰不可点击。这里用到了 Vue 的插槽(slot)机制,卡片组件内部只做展示框架,按钮内容由父组件通过插槽传入,这样不同场景可以复用同一个卡片组件。
表单校验我直接用了组件自带校验规则,病情描述限制 200 字以内,必填项没填不让点提交。提交按钮要加防重复标记,请求发出后立刻禁用,等响应回来再恢复,避免手抖连点产生两条预约。这个细节看起来小,但在演示时非常能体现基本功。
样式冲突问题也值得提一下。如果组件里不写 scoped,全局样式很容易互相污染,比如两边都用 .btn 类,样式就乱了。我所有组件的 style 标签都加 scoped,公共样式放到 assets 里的全局 css 文件。热词里经常有人搜“vue样式冲突”,十有八九就是没用 scoped 或者全局样式命名太随意。
4.5 扩展一点:健康宣传视频播放
如果平台后续要放健康宣传视频或者心理辅导课程,前端播放 m3u8 格式视频不用装大而全的播放器。引入 hls.js 就能在浏览器里直接播放,代码量很小且免安装插件。我实测过跨域和格式兼容问题,hls.js 处理得都比较稳定,适合做个视频学习模块,给平台增加一点使用场景。这里不需要复杂封装,一个组件里初始化 hls 实例即可。
5. 联调部署与问题排查速查
5.1 前后端联调时做什么
前后端联调阶段最容易出问题的就是接口路径和跨域。我的规矩是后端所有接口都以 /api 开头,控制器类上统一加 RequestMapping("/api/xxx")。前端开发环境用 Vite 代理把 /api 转发到 localhost:8080,这样浏览器没有跨域问题。
vite.config.js 的代理配置片段:
export default defineConfig({ plugins: [vue()], server: { port: 5173, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } } })生产环境则是 nginx 配置 location /api 反向代理到后端端口。前后端联调时先约定好接口文档,可以用 Apifox 或直接写在 README 里,避免前端等后端、后端等前端互相干等。
5.2 打包与部署方案
后端部署最简单的方式是打成 jar 包直接运行。在项目根目录执行 mvn clean package,出来一个 jar 文件,然后 java -jar 运行即可。运行环境需要 JDK 版本和打包版本一致,JDK 8 的包放到 JDK 17 环境跑通常会报错,反过来一样。
前端构建执行 npm run build,输出 dist 目录,把 dist 内容放到 nginx 的 html 目录下。nginx 关键配置:
server { listen 80; server_name your-domain.com; root /usr/share/nginx/html; index index.html; 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; } }try_files 这行就是解决前端路由刷新 404 的关键,它保证所有路径最终都回退到 index.html,由前端路由接管。
如果服务器装了宝塔面板,可以直接用宝塔的 Java 项目管理器部署 jar 包,或者用 Docker 跑后端镜像。Docker 部署也不复杂,写个 Dockerfile 基于 java 镜像,把 jar 复制进去启动就行。数据库建议单独跑 MySQL 容器或者用宝塔自带的 MySQL,不要塞进同一个容器里,方便后期备份。
这里回应一个很多新手问的点:Spring Boot 可以不内置 Tomcat 吗?答案是可以,把 spring-boot-starter-tomcat 依赖去掉或标记为 provided,打包成 war 放到外部 Tomcat 的 webapps 下也能跑。但在 2025 年这个时间点,jar 加内置 Tomcat 的方式明显更轻量,也更好做 Docker 部署,外部 Tomcat 方案除非你们学校机房有强制要求,否则不建议绕这个路。
5.3 高频问题排查速查表
我把这个项目从搭建到上线遇到的高频问题整理成了一张速查表,方便直接对着排查。
| 现象 | 可能原因 | 解决方法 |
|---|---|---|
| Spring Boot 启动报 UnsupportedClassVersionError | JDK 版本和框架版本不匹配 | Spring Boot 3.x 用 JDK 17+,2.7.x 用 JDK 8+,二选一保证一致 |
| Maven 依赖下载慢或失败 | 默认中央仓库访问慢 | 修改 settings.xml 配置镜像源 |
| Vue npm install 报 ERESOLVE | 依赖树冲突或 node 版本过低 | 升级 node 到 18+,或换用 pnpm 安装依赖 |
| 创建项目后 tsconfig not found | 手改文件时破坏了 TS 配置路径 | 用 create-vue 重新生成模板,或检查 tsconfig.app.json 引用路径 |
| 前端刷新页面 404 | 路由 history 模式未配置回退 | 开发环境配 historyApiFallback,生产环境 nginx 配 try_files |
| 接口跨域报错 | 前端域名和后端端口不一致 | 开发环境用 Vite proxy,生产环境用 nginx 反代 |
| 数据库中文乱码 | 连接串没指定 utf8 | url 添加 characterEncoding=utf8,库表字符集用 utf8mb4 |
| 端口被占用 | 本地有多个服务在跑 | 换端口或查进程结束后重启 |
| 预约总数和已预约数对不上 | 排班号源扣减和预约创建没有事务 | Service 方法加 @Transactional(rollbackFor = Exception.class) |
| 页面时间多了 8 小时 | 时区配成了 UTC | 连接串 serverTimezone=Asia/Shanghai,前端和数据库统一 |
5.4 控制台和日志排查经验
最后说点排障的实践经验。后端出问题时,先看控制台有没有 Java 异常堆栈,别急着问前端。我见过不少同学一看到接口报错就截图给前端,结果最后发现是后端数据库没连上。统一处理异常后,大部分业务错误会以 Result 的 code 返回,前端拦截器也会统一提示,排起错来很快。
前端的问题多发生在控制台 Network 面板里。打开浏览器开发者工具,点一下请求,看请求 URL、请求头、响应体,基本能定位是路径错了、跨域了还是参数没传对。这个习惯养成之后,联调效率能高一倍。
部署后的日志也要提前规划。后端 jar 启动时用 nohup java -jar xxx.jar > app.log 2>&1 & 把日志输出到文件,出问题直接 tail -200 app.log 看最后 200 行。数据库执行慢的时候,用 explain 看一下 SQL 是否走了索引。排班表、预约表的数据量不算大,但 work_date 和 schedule_id 的索引一定要加,不然统计时段报表会明显卡顿。
6. 预约系统做完之后还能怎么扩展
这个系统的核心链路做到这里已经完整可用了。如果时间充裕,我建议在原有基础上加一个简单的消息通知功能:预约成功或取消时,给学生发送一条站内信提醒。技术上就是建一张 notification 表,预约事务提交后向这张表插一条记录,学生在首页角标看到未读数量。整个扩展不需要引入消息队列,因为业务量还没到那个级别。
健康管理维度也可以加深。第一版健康档案只有医生填写的文本描述,后续可以加体检数据录入页面,比如身高、体重、血压、视力这些数值字段,按学期维度展示变化曲线。前端用 ECharts 画一个折线图,后端提供一个按 userId 查询历年体检数据的接口,这个功能在答辩时很加分,因为它是真正的数据沉淀,不是简单的 CRUD。
另外可以考虑接入校园一卡通数据进行身份同步。虽然这个扩展涉及外部系统,接口设计上只要在学生登录时加一个“学号 + 统一身份密码”的校验,或者提供一个第三方登录入口,就能避免单独维护一套学生账户体系。不过这个优先级取决于学校的信息化程度,第一版用账号密码注册登录完全没问题。
整套系统做下来,我最满意的地方不是页面有多漂亮,而是预约表的那几个唯一索引帮我在演示时扛住了全班同学的集中访问。最后想分享一个很容易被忽略的小细节:上线前一定要把服务器时区、MySQL 时区和前端展示时区统一,我用 Asia/Shanghai 后,排班日期就没再错过一天。希望这份拆解能对正在做类似项目的你有点帮助,如果在预约并发处理上你有更好的办法,欢迎多交流。