ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SSM+Vue医院出诊挂号就诊检查管理系统实战解析与踩坑记录

SSM+Vue医院出诊挂号就诊检查管理系统实战解析与踩坑记录 这套SSMVue的医院出诊挂号就诊检查管理系统算是我这两年做过的业务链路最完整的单体项目之一。后端就是经典的Spring SpringMVC MyBatis前端Vue全家桶覆盖了出诊排班、患者挂号、医生接诊、检查开单与报告回传这条完整业务线。如果你正在做类似的医疗信息类毕业设计或者中小型诊所系统这篇东西能让你少走不少弯路我会把前后端的真实代码结构和踩坑记录都摊开讲。先交代一下背景我接手这个项目时客户的需求文档其实写得比较粗只说了“要有出诊、挂号、就诊、检查”四个大块。真正动手之后才发现这四个字背后分别对应着医生排班管理、号源库存、接诊叫号、电子病历、检查申请与报告状态机好几套逻辑而且前后端还有大量的联动场景。整个项目做下来差不多两个月前端页面20多个后端接口60多个中间踩过的坑都值得记一笔。1. 项目整体设计与技术选型1.1 这个系统到底要解决什么问题医院门诊的业务流程本质上是一条很清晰的流水线医生出诊排班 - 患者查询号源 - 挂号锁定号源 - 候诊叫号 - 医生接诊写病历 - 需要时开检查单 - 患者去做检查 - 检查科室回传报告 - 医生查看报告完成诊断。很多没做过医疗系统的朋友第一反应是“挂号不就是往表里插一条记录吗”但实际做下来你会发现号源是有数量的出诊是有时间段的检查是跨科室状态流转的。什么时候允许退号号源怎么释放接诊时病历怎么跟挂号记录关联检查报告没回来时医生能看什么这些都是需要预先设计的业务规则。这套系统我最终的核心设计思路是以“挂号记录”为业务主索引以“状态字段”驱动流程推进。挂号单有状态检查单有状态所有页面都围绕状态做展示和操作限制。前端只负责把状态和操作按钮渲染出来真正的状态判断全部放在后端校验避免绕过界面直接调接口导致业务错乱。1.2 为什么选了SSM Vue而不是Spring Boot 微服务选型的时候客户没有强绑定技术栈但我还是选了SSM Vue这个组合核心原因有几点。第一项目规模决定了没必要上Spring Boot那一套微服务治理。这个系统最多也就是几百个用户并发MySQL单库完全扛得住。SSM三个组件各司其职Spring管BeanSpringMVC管请求分发MyBatis管数据库访问逻辑非常直白出了问题也容易定位。第二SSM对新手和老手都友好。如果你是拿这个项目练手或者做课程设计SSM的配置会让你把“容器、依赖注入、AOP、前端控制器”这些概念真正过一遍而不是像Spring Boot那样一堆自动配置把细节藏掉。如果你是在生产环境维护老项目SSM这套技能也非常实用。第三前端用Vue是因为它的组件化确实适合这种后台管理系统。门诊的医生排班页、号源配置页、检查单列表页其实都是由“表格 表单 选项卡”这些重复块拼出来的。Vue的单文件组件能把页面拆成可复用模块后面接手的同事维护起来也轻松。1.3 功能模块划分与数据库设计思路整个系统我分成了五个模块系统管理、出诊管理、挂号管理、就诊管理、检查管理。数据库核心表我列一下表名基本可以照抄。sys_user系统用户表包含医生、护士、检查技师、管理员等角色通过user_type区分。sys_role/sys_menu/sys_role_menu经典的RBAC权限三件套。doctor_schedule出诊排班表存储医生、出诊日期、时间段、号源总数。registration_record挂号记录表关联排班和患者。patient_info患者信息表。medical_record就诊病历表关联挂号记录。examination_order检查申请单表。examination_report检查报告表。设计时最需要注意的是关联字段。挂号记录表里我不仅存了schedule_id还冗余了doctor_id、doctor_name、department_name、visit_date、time_slot这些字段。虽然有点违反第三范式但业务查询时非常爽列表页不用到处多表关联而且出诊排班表被修改后历史挂号记录仍然保留了当时的快照信息。2. 后端SSM核心实现与接口设计2.1 SpringMVC接口、常用注解与统一返回体后端我统一采用RestController风格接口但底层配置还是SpringMVC那一套。对整个项目来说最值得聊的是注解的使用习惯。Controller层的注解我用得比较固定RestController直接返回JSON页面不依赖JSP。RequestMapping(/api/registration)类上定义模块根路径。PostMapping(/create)/GetMapping(/page)方法上定义具体操作。RequestBody接收前端传过来的JSON对象。RequestParam接收简单的查询参数。关于注解有个很容易踩的坑RequestParam和RequestBody混用。比如我挂号接口既需要路径参数patientId又需要JSON体里的scheduleId和timeSlot如果图省事全部用RequestParam接收前端传JSON的时候就会一直报参数缺失。我的习惯是复杂对象一律用RequestBody简单标识参数才用RequestParam或PathVariable。统一返回体是我在后端最先定下来的东西直接决定了前后端联调的效率。public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.code 200; result.message success; result.data data; return result; } public static T ResultT error(Integer code, String message) { ResultT result new Result(); result.code code; result.message message; return result; } }前端不管是成功还是失败都先判断code 200业务错误码和HTTP状态码彻底分离。比如号源不足HTTP状态还是200但code返回50001前端拿到这个码弹“号源不足”的提示。这样就不容易出现浏览器控制台一堆红色报错用户那边却不知道发生了什么的情况。2.2 MyBatis动态SQL与通用MapperMyBatis这块我建议不要全写在注解里。查询条件一多注解里的Select简直没法看。我会把SQL全部放到XML文件里尤其是分页和动态条件用where和if组合非常舒服。以挂号记录分页查询为例select idselectRegistrationPage resultTypecom.demo.entity.RegistrationRecord SELECT rr.*, ps.real_name AS patient_name FROM registration_record rr LEFT JOIN patient_info ps ON rr.patient_id ps.id where if testpatientName ! null and patientName ! AND ps.real_name LIKE CONCAT(%, #{patientName}, %) /if if testdoctorId ! null AND rr.doctor_id #{doctorId} /if if teststatus ! null AND rr.status #{status} /if if testvisitDate ! null AND rr.visit_date #{visitDate} /if /where ORDER BY rr.create_time DESC /select这种写法最直观的好处是前端加一个筛选条件后端只要在XML里加一个if不用改Java代码里的SQL拼接逻辑。关于MyBatis字段映射我强烈建议开启驼峰转下划线映射。数据库字段要么全下划线要么全驼峰千万别混着来mybatis: configuration: map-underscore-to-camel-case: true这样doctor_schedule表里的total_count可以直接映射到实体类的totalCount字段省掉一大堆resultMap。分页我用的是PageHelper一个PageHelper.startPage(pageNum, pageSize)后面紧跟第一条查询语句即可。这个工具用起来简单但有两点必须注意一是startPage必须紧接着你要分页的那条查询中间不能插别的查询语句二是用了多表关联时PageHelper的count语句偶尔会生成得很奇怪需要在XML里单独写select count(*)不要依赖它自动生成的count。2.3 挂号扣号源的事务控制挂号是整个系统里业务约束最严格的功能本质上是“库存扣减”场景。患者挂号的瞬间要做三件事检查排班是否存在、检查当前号源是否有余量、扣减号源并插入挂号记录。这三个操作必须在一个事务里完成。我直接在Service方法上加了Transactional(rollbackFor Exception.class)。但光加事务还不够还有一个非常经典的并发问题如果两个患者同时挂同一个排班的最后一个号都查到了余量为1然后同时扣减就会超挂。解决这个问题我用的是数据库乐观锁的思路在doctor_schedule表上加了remaining_count字段扣号时执行带条件的UPDATEUPDATE doctor_schedule SET remaining_count remaining_count - 1 WHERE id #{scheduleId} AND remaining_count 0然后判断这个UPDATE的影响行数如果影响行数是0说明号源已经被抢光了直接抛业务异常。这个方案比先SELECT再UPDATE可靠得多不需要显式加行锁也不容易造成死锁。Transactional(rollbackFor Exception.class) public void createRegistration(RegistrationCreateDTO dto) { DoctorSchedule schedule scheduleMapper.selectById(dto.getScheduleId()); if (schedule null) { throw new BusinessException(排班不存在); } // 校验挂号的日期不能早于今天等业务规则... int updated scheduleMapper.decreaseRemainingCount(dto.getScheduleId()); if (updated 0) { throw new BusinessException(号源已被挂完); } RegistrationRecord record new RegistrationRecord(); // 组装挂号记录... registrationMapper.insert(record); }这个场景已经不只是SSM的技术问题而是业务设计问题。多想想“并发情况下会不会出事”是这类业务系统开发很重要的思维习惯。2.4 登录鉴权与拦截器既然是医院内部系统登录鉴权不能省。我没有引入Spring Security那套重家伙而是自己写了一个拦截器实现基于Token的登录校验。用户登录成功后后端生成一个UUID作为Token存到Redis里key是login:tokenvalue是用户ID和角色信息并设置过期时间。前端每次请求在Authorization请求头里带上Token后端拦截器统一校验。拦截器注册是SSM里的经典操作public class LoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录接口 if (request.getRequestURI().contains(/api/user/login)) { return true; } String token request.getHeader(Authorization); if (token null || !RedisUtil.hasKey(login: token)) { response.setStatus(401); return false; } // 把当前用户信息放进 request attribute request.setAttribute(currentUser, RedisUtil.get(login: token)); return true; } }这里有个细节如果拦截器直接返回401前端axios实例里就要做统一处理遇到401跳回登录页并把本地Token清掉。不然用户登录过期后页面会一直弹接口报错体验很差。3. 前端Vue工程搭建与核心功能实现3.1 Vue环境搭建与项目初始化前端我用的Vue 2 Element UI因为这套系统的目标浏览器还是以Chrome和360兼容模式为主Vue 2的生态最稳。如果你是新项目且不需要兼容老浏览器直接用Vue 3 Element Plus也没问题。环境搭建这块Node.js版本要特别注意。Vue CLI 5要求Node 12以上但也不能太新碰到Node 18有时会因为OpenSSL的原因报错。我在一台新电脑上装环境时就遇到过digital envelope routines::unsupported这个经典报错解决办法是在启动命令前加上NODE_OPTIONS--openssl-legacy-provider或者把Node降到16版本。安装依赖我用的是npm命令很简单npm install -g vue/cli vue create hospital-admin cd hospital-admin npm install element-ui axios vue-router vuex创建完项目后我第一件事是配置vue.config.js里的开发代理否则前端请求/api接口时会因为跨域被浏览器拦下来。module.exports { devServer: { port: 8080, proxy: { /api: { target: http://localhost:8081, changeOrigin: true, pathRewrite: { ^/api: } } } } }后端接口路径是/user/login这种前端统一请求/api/user/login代理转发时把/api前缀去掉。这样做的好处是前端代码里所有请求地址都是/api开头将来部署到Nginx时也只需要让Nginx把/api转发到后端服务前端代码一行都不用改。3.2 路由设计静态路由与动态路由权限这个系统的用户角色有管理员、医生、护士、检查技师不同角色看到的菜单和可访问页面完全不同。我采用了静态路由 动态路由结合的方式。静态路由放所有人都能访问的页面比如登录页和首页。动态路由是根据登录用户的角色从后端拉取可访问的菜单权限再通过router.addRoutes动态挂载。后端返回的菜单结构长这样[ { path: /schedule, name: Schedule, component: schedule/index, meta: { title: 出诊排班 } }, { path: /exam, name: Exam, component: exam/index, meta: { title: 检查管理 } } ]前端的核心处理逻辑是登录后获取当前用户的菜单列表用import()做动态组件的映射然后通过router.addRoutes挂到路由表上。// 将后端返回的component字符串映射到实际组件 const componentMap { schedule/index: () import(/views/schedule/index.vue), exam/index: () import(/views/exam/index.vue) }; function buildRoutes(menuList) { return menuList.map(item { item.component componentMap[item.component]; if (item.children) { item.children buildRoutes(item.children); } return item; }); }这个方案让我在后面加新页面时非常省事后端菜单表里加一条记录前端在componentMap里加一个映射刷新页面后菜单就出来了不需要重新发版。3.3 axios封装与mock数据前端axios我做了统一封装主要目的有三个统一携带Token、统一处理业务错误码、统一拦截401状态。import axios from axios; import { Message } from element-ui; import router from /router; const service axios.create({ baseURL: /api, timeout: 15000 }); service.interceptors.request.use(config { const token localStorage.getItem(token); if (token) { config.headers[Authorization] token; } return config; }); service.interceptors.response.use( response { const res response.data; if (res.code ! 200) { Message.error(res.message || 系统异常); return Promise.reject(new Error(res.message)); } return res.data; }, error { if (error.response error.response.status 401) { localStorage.removeItem(token); router.push(/login); } else { Message.error(网络请求失败); } return Promise.reject(error); } );开发环境强烈建议配合mock数据联调。我在前端工程里单独建了mock目录后端接口还没写完时前端先用mock模拟一份数据把页面跑起来。用的事axios的适配器方案改动量最小但要注意mock的路由需要在环境判断里区分别让mock代码打包到生产环境里。3.4 出诊排班页与行内编辑出诊排班页是典型的“表格 弹窗”页面但我用Element UI的el-table做成了行内编辑的模式。一个排班包含医生、科室、日期、上午/下午时段、号源总数、已约数、剩余数。这个页面有几个交互细节值得讲第一日期要禁用过去的日期。我用了el-date-picker的disabled-date属性把今天之前的日期全部禁掉防止医生排班排到过去。第二号源总数和已约数要有联动校验。如果已约数已经超过新填的号源总数保存时必须给出提示否则会出现“剩余数为负”的脏数据。第三同一医生同一天同一时段不能重复排班。这个校验我放在了后端前端只做友好提示。因为排班数据可能会被多个管理员同时操作只靠前端校验不可靠。3.5 按钮权限控制实战按钮权限是后台管理系统的常见需求。我的做法是封装一个自定义指令v-permission在后端返回的权限标识列表里判断当前用户是否拥有对应按钮的权限。Vue.directive(permission, { inserted(el, binding) { const required binding.value; const userPermissions store.state.user.permissions; if (required !userPermissions.includes(required)) { el.parentNode.removeChild(el); } } });页面上使用el-button v-permissionschedule:create typeprimary新增排班/el-button这个指令的优点是权限一变界面上的按钮自动消失逻辑集中在指令里不需要每个页面都写一遍v-if判断。但也要注意一个桌面问题指令的inserted钩子在组件初始化时执行此时store.state.user.permissions可能还没从后端拉取到如果权限数据是异步加载的按钮会被误删。解决方案是在路由守卫里保证进入页面前权限数据已经加载完毕。4. 业务核心模块实操复盘4.1 出诊排班与号源配置出诊排班的本质是提前生成未来一段时间的号源池。我的做法是让管理员按周批量生成比如选择周一至周五每个时段设置号源总数一次性生成未来两周的排班记录。前端页面实现批量生成时我遇到的最大的一个交互问题是如果逐条插入数据库用户生成两周的排班会非常慢。我用后端批量插入接口处理Transactional(rollbackFor Exception.class) public void batchCreateSchedule(ListScheduleCreateDTO list) { for (ScheduleCreateDTO dto : list) { // 校验重复排班 if (scheduleMapper.exists(dto.getDoctorId(), dto.getVisitDate(), dto.getTimeSlot()) 0) { throw new BusinessException(存在重复排班); } } scheduleMapper.batchInsert(list); }前端一次性把两周的数据POST到后端事务保证要么全部成功要么全部失败。这个方案比前端循环调用单条新增接口要稳得多也方便后端做统一校验。4.2 挂号流程与并发扣号挂号流程前面提到过核心是乐观锁扣号这里补充完整的状态流转。挂号记录的状态我用了一组数字0已预约待就诊1已报到患者到科室签到2就诊中医生点击开始接诊3已完成医生提交病历4已取消患者的挂号页面只展示“待就诊”状态下的号源。医生端看到的是所有已挂号的患者按号源序号排序叫号。这里要注意的是患者端不应该直接出现“锁定队列”这种概念而是由前端轮询接口获取当前的叫号进度这个轮询间隔我设置的是10秒一次避免对后端造成太大压力。退号场景大家容易忽略它需要把registration_record的状态改为已取消同时把doctor_schedule表的剩余号源加回去。这里同样要放在一个事务里而且更新语句要用remaining_count remaining_count 1不要先查出来再加再更新容易造成并发丢失更新。4.3 就诊叫号与病历管理就诊模块我设计的是医生工作站页面。医生登录后默认展示今天的挂号队列患者状态为“已报到”的排在前面医生点击“接诊”后状态变更为“就诊中”页面跳转到病历编辑。病历编辑页关联的字段非常多包括主诉、现病史、既往史、初步诊断、处理意见等。考虑到这是内部系统我用的是大文本表单直接存库没有用富文本编辑器。富文本编辑器在医疗场景里确实好看但打印和归档很麻烦纯文本配合CSS渲染更适合实际使用。医生提交病历后挂号记录状态变为“已完成”。这里有个业务细节如果医生开了检查单病历应该允许后续修改。所以我给病历表加了一个version字段每提交一次累加前端显示最近一次版本。这个设计越简单越好千万别搞复杂的审批流。4.4 检查开单、报告回传与状态流转检查模块是跨科室协作的核心。医生在就诊页面点击“开检查单”填写检查项目后生成一条examination_order状态为0待缴费。检查申请单的状态我定义了四个0待缴费1已缴费待检查2检查完成待报告3报告已出检查技师登录后列表里显示状态为1的申请单点击“开始检查”后状态变为2填写完报告内容保存后状态变为3。医生端在看检查列表时只能看到已经出报告的检查单未出报告的显示“报告生成中”。后端接口设计上检查模块有一个经验之谈不要只提供updateStatus这种万能接口因为谁都能调用会把状态机搞乱。我拆成了几个语义化接口比如startExamination、submitReport、payOrder每个接口只负责一次确定的状态变更并在方法里校验当前状态是否符合预期。5. 前后端联调、打包与部署5.1 跨域与Cookie问题处理开发环境跨域我用代理解决生产环境也建议用Nginx反向代理解决。前后端分离项目最怕的是“后端把CORS打开前端又配了代理”两个冲突起来会在浏览器控制台看到各种奇怪的报错。我的建议是开发环境只用vue.config.js代理生产环境只用Nginx代理后端不额外配置CORS。这样思维模型最简单——浏览器以为页面和后端是同源的不存在跨域也不存在Cookie跨域携带问题。如果你的系统非要后端配CORS那要注意前端axios必须设置withCredentials: true同时后端响应头不能写死Access-Control-Allow-Origin: *必须回显具体源。否则浏览器会直接拦掉请求。5.2 日期格式与JSON序列化问题日期格式是这类管理系统联调时的重灾区。后端返回java.util.Date默认会被序列化成时间戳前端拿到的是一个13位数字根本没法直接显示。我处理方式有两种推荐第二种。第一种是在Java实体类的日期字段上标注JsonFormat(pattern yyyy-MM-dd HH:mm:ss, timezone GMT8) private Date createTime;第二种是全局配置Jackson的日期格式避免每个字段都加注解。Configuration public class JacksonConfig { Bean public Jackson2ObjectMapperBuilderCustomizer jacksonCustomizer() { return builder - { builder.simpleDateFormat(yyyy-MM-dd HH:mm:ss); builder.timeZone(TimeZone.getTimeZone(GMT8)); }; } }前端传日期时也要注意时区问题。我遇到过前端传2025-06-01T00:00:00.000Z这种带Z的ISO格式后端解析后少了8小时。后来我统一让前端传yyyy-MM-dd格式的字符串后端用DateTimeFormat配合Date类型接收彻底消灭了差8小时问题。5.3 Vue打包放进SpringBoot的方式虽然我用的是前后端分离开发但实际部署时间客户希望只跑一个Java进程。所以我需要把Vue构建产物直接放进SpringBoot的静态资源目录。这个操作其实非常简单。Vue项目执行npm run build生成dist目录里面是index.html和static资源目录。然后把这个目录的内容复制到后端项目的src/main/resources/static/目录下重新打包成可执行的jar包启动后访问http://localhost:8081就能直接看到前端页面。这里有两个非常关键的坑第一Vue Router要使用hash模式不要用history模式。如果用了history模式用户访问/schedule这个路径时SpringBoot默认会返回404因为它不知道怎么把/schedule映射到index.html。虽然可以配置资源映射或者forward规则但对这个项目来说没必要直接用hash模式最省心。第二前端接口请求地址必须使用相对路径比如/api/login不能用http://localhost:8081/api/login这种写死的绝对地址。否则打包后部署到别的环境就全废了。5.4 服务器部署生产环境我是用Nginx SpringBoot的方式部署的。Nginx监听80端口把/路径指向前端静态文件目录把/api路径反向代理到后端的http://127.0.0.1:8081。Nginx的配置核心片段server { listen 80; server_name hospital.example.com; location / { root /opt/hospital/dist; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8081/; 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;是给history模式用的如果你用了hash模式其实可以不配置但写上也无妨。另外proxy_pass后面的末尾斜杠是路径替换的关键/api/user/login转发到后端会变成/user/login这正好和后端的接口路径对上。6. 常见问题与排查技巧实录6.1 SSM后端常见问题排查Invalid bound statement (not found)是我见过最多的MyBatis异常。90%的情况是Mapper接口和XML文件没有正确关联。检查顺序是XML文件的namespace是否等于Mapper接口的全限定名、接口方法名是否等于XML里的id、mybatis.mapper-locations配置是否指向了XML目录。还有一个非常隐蔽的问题IDEA没有把XML文件编译到target/classes目录需要在pom.xml里加一段资源打包配置。Failed to convert value of type java.lang.String to required type java.lang.Integer这个错误是后端接口接收参数时前端传了字符串。排班列表页如果从路由取参数this.$route.query.scheduleId拿到的是字符串。我前端处理方式是在axios参数里统一加了scheduleId: Number(this.$route.query.scheduleId)转换后端用RequestParam Integer scheduleId接收就不会报错了。6.2 Vue前端常见问题排查Element UI表格的el-table-column里如果放自定义插槽数据审核出来的字段经常不刷新。原因是el-table的缓存机制直接修改了表格绑定数组里某个对象的属性视图不会更新。必须要用this.$set(对象, 属性名, 新值)或者整个替换数组。我数据量不高直接重新从后端拉一次列表最简单。vue-router路由重复跳转报错比如用户连续点击菜单控制台出现NavigationDuplicated警告。可以给路由的push方法加一个catch处理router.push(/path).catch(() {});或者干脆注册一个全局的补丁方法把重复导航报错吞掉。这个问题纯粹是体验问题不影响功能但强迫症看着难受。6.3 业务数据问题排查实录上线一个多月后客户反馈某天的挂号数对不上。排查下来是出诊排班在外过且号源已满时后端仍然允许管理员把号源总数调小导致部分患者成功挂号但号源剩余数比实际已挂号数还小。后来我加了校验规则已约数大于0时禁止修改号源总数只能等第二天再调整。这个教训是典型的“技术没问题业务规则缺失”的案例。还有一次检查报告全部看不到查日志发现是检查单的状态一直被更新为“检查完成”但报告表里的report_content是空字符串。原因是前端表单提交时必填校验没拦住空值后端也没做空值校验。最终我在后端submitReport接口加了参数校验并且把空字符串直接判为非法。6.4 项目复盘中的一些心得这个项目做下来我最大的体会是前后端分离项目真正的成本不在编码而在“接口一致性”的沟通上。强烈建议你在一开始就定义好统一返回体、统一分页参数、统一状态码并且写一个接口文档。我用的是Swagger加上后端注释到了联调阶段几乎没有因为接口参数对不上而返工。第二是表单校验不要依赖前端。虽然前端Element UI的表单校验体验很好但它只能拦得住操作路径拦不住恶意请求。所有核心业务校验必须后端再做一遍千万别嫌重复。尤其是涉及金额、号源、状态流转这种会被用户频繁操作的字段。第三是这个项目的架构虽然简单但由于它是SSM单体将来如果真的要扩展建议优先考虑拆出独立的检查服务而不是直接上微服务。单体阶段把模块边界划清楚接口隔离好后面拆分才顺。数据库表设计时给检查相关的表都加了exam_order_no这种业务流水号字段就是为了将来独立服务做对账用的。最后分享一个我实际开发中的小习惯每次前端页面联调通过后我会把接口对应的SQL语句打印出来看一眼SQL的WHERE条件里有没有遗漏status状态过滤。很多“看不到数据”的问题其实就是列表查询忘了加状态条件导致把所有状态的记录都查出来了前端又没按状态过滤用户看到一堆不该出现的数据。这一眼看起来是小问题实际排查起来非常费时间不如一开始就多留个心眼。这套SSM Vue的医院出诊挂号就诊检查管理系统整体难度不高但胜在业务线长、边界清晰非常适合用来学习SpringMVC MyBatis Vue全家桶的完整协作方式。照着上面的表设计和接口思路走一遍你基本上就能掌握一套后台管理系统从零到上线的全过程。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进