1. 宠物医疗管理系统到底在管什么:业务需求先于技术选型
我得先泼一盆冷水:很多人拿到"基于SpringBoot+Vue的宠物医疗管理系统"这类课题,第一反应是先把技术栈摆出来,SpringBoot、Vue、MyBatis-Plus、MySQL一套全安排上,然后照着网上CRUD模板猛写,最后交出来一个"披着宠物外衣的图书管理系统"。这在答辩现场是极其致命的——老师随便问一句"你的诊疗流程里挂号和开药的数据是怎么关联的",就露馅了。
这个问题我见过太多次,因为宠物医疗管理系统它的难点压根不在技术,而在业务模型的特殊性。你很难找到第二个领域,既有严格的库存管理逻辑(药品、耗材)、又有精细的状态流转(预约、就诊、住院)、还需要对时间敏感的记录管理(疫苗周期、驱虫计划)。换句话说,这个系统中每种数据都不是"躺在数据库里的死字段",而是彼此咬合的齿轮。
我先说这个,就是想让你先把思路倒过来:先想清楚宠物医院是怎么运转的,再想怎么写代码。
1.1 它和人类医院的管理系统差在哪
宠物医疗管理系统的核心对象是"宠物+主人"的双主体结构。人类的医疗系统只需要围绕患者个人做记录,但宠物不是独立自然人,它的行为能力归它的主人管,所以系统里必须同时维护两条线:宠物的生物档案(品种、年龄、是否绝育、既往病史),和主人的联系信息(电话、地址、会员等级)。这两条线还会交叉:医院一般按"主人会员"做储值折扣,但看病时记录的是"某宠物"的病历。边界划不清楚,后面做充值、开处方、统计收入时会乱成一团。
更麻烦的是,动物不会说话。人类患者能自己描述"哪里不舒服",宠物只能靠主人转述和医生检查。所以宠物病历里大量出现的是"主诉(主人描述)+ 检查发现 + 初步诊断"三段式的结构化描述。系统设计病历表的时候,这三个字段是必须分开的,而不是塞进一个长长的文本备注里。这个小细节,恰恰是区分"真懂业务"和"照模板写"的分水岭。
另一个巨大的差异是疫苗和驱虫的周期性管理。宠物疫苗不是打一次就完了,幼犬第一年要打三针联苗加一针狂犬,之后每年续种一次;驱虫也分体内体外,不同体重有不同用药方案。一个不专业的系统只会在宠物档案表里留一个"疫苗记录"文本框,而专业的系统会把每次接种拆成独立的记录行,带着疫苗批号、接种剂量、下次应种时间。这直接决定你后端要怎么写定时提醒接口,前端要不要单独做一个"疫苗日历"页面。
1.2 三类使用者与一条核心就诊链路
这个系统最典型的使用者有三类:管理员、医生、前台。我在答辩里经常让学弟学妹当场画这三类人的权限边界,很多人画着画着就乱了。
- 管理员:管门店、管员工账号、管药品库存的上下限、看经营报表。他通常不下诊断,也不操作会员收银,但能看所有数据。
- 医生/助理:核心是接诊、写病历、开处方、安排住院和手术。医生只能看自己经手的病历,或经过授权查看会诊病例,不能直接改药品库存价格。
- 前台:负责挂号、收费、会员充值、预约登记。她是操作最频繁的角色,但没有任何医疗权限,不能看完整病历内容。
这三类角色对应的权限设计,后端做JWT拦截时就要在接口层面把接口分成三类:admin/、doctor/、reception/**。前端侧边栏也要跟着角色动态生成菜单。不要做那种"所有角色共用一套菜单、进去看运气"的简陋系统。
核心业务链路则是这样一条线:
用户在小程序或电话预约 → 前台确认预约并建档(若新客) → 医生按队列接诊 → 问诊+体检后写病历 → 开处方或医嘱 → 前台按处方划价收费 → 药房发药(若需要则办理住院) → 定期跟进复查/疫苗提醒。
这条链路上每一步都和前一步的数据强关联:没有挂号记录,接诊界面就拉不出宠物档案;没有处方单,收费就没有项目依据;没有收费完成的标识,取药确认就是空中楼阁。所以表结构设计必须保证"主键ID能顺着链路一路传下去"。我见过不少系统把挂号、病历、处方做成三张孤岛表,互相之间只有一个petId藕断丝连,这种设计一旦走到"某医生某月开了多少钱的药"统计,就会彻底崩盘。
1.3 容易被忽略的隐藏需求
除了主干链路,宠物医院还有几个"不显眼但老板很在意"的需求:
第一是会员储值。宠物看病费用高,医院普遍推"充值满赠、充值折扣"来锁定客流。这个需求意味着你至少要有充值记录表、余额字段、以及余额扣减的原子性保证。扣费必须和收费操作绑定在同一事务里,否则就可能出现前端显示扣款成功、后台余额还够再刷一次的尴尬场面。
第二是多宠物关联。一个主人可能带两只猫来看病,一只是复诊,一只是新发皮肤病。系统里必须通过owner_id把多份pet档案关联到同一个主人账号下,而主人账号本身又是会员储值的载体。这个"一对多主人到宠物"的关系,需要从表设计的第一天就固化下来。
第三是药品的批次和效期。宠物医院的药品效期管理往往比人用药更严格,因为宠物药开封后保质期更短。如果库存表里没有lot_no(批号)和expire_date(效期),实际药房管理人员会非常抗拒使用你的系统,宁可用Excel记录。这个字段看似不起眼,但在答辩时老师问到"药品快过期了系统怎么处理",这就是一个实打实的加分项。
2. 技术栈为什么锁定SpringBoot+Vue:一套务实的前后端分离方案
业务模型心里有数之后,再来聊技术选型就顺畅多了。这个课题选择SpringBoot+Vue,我不认为纯属跟风,而是这套组合在校园项目、中小型实际落地场景中确实是性价比最优的解。下面我从后端、前端、为什么不换别的三个角度拆解。
2.1 SpringBoot的后端优势:把"配置地狱"变成"自动装配"
很多教材还停留在SSH或SSM的阶段,但SpringBoot最核心的价值就是干掉了大量XML配置,把Spring生态从"每次新建项目要配一周环境"变成"一个spring-boot-starter-web就能跑起来"。对于宠物医疗系统这种业务偏重、技术复杂度适中的项目,你需要的不是更底层的控制力,而是快速把CRUD和业务逻辑铺开的能力。SpringBoot的starter机制(比如mybatis-plus-boot-starter、spring-boot-starter-validation)把常用组件的依赖和自动配置都打包好了,你只需要关注业务代码。
同时SpringBoot也天然具备后续扩展的能力。宠物医疗系统并不只停留在"毕业设计"层面,很多学长把它做成商业项目卖给了本地宠物诊所。一旦要接支付、接短信通知、接电子发票,SpringBoot庞大的生态都能覆盖到——Retrofit、Hutool、JustAuth这些都是现成的工具库,不用重新造轮子。
有个容易被忽视的点是SpringBoot的单元测试和打包部署体验。内置Tomcat使得spring-boot:run一键启动,package打出来的jar可以直接 java -jar 运行,这对后端部署到服务器非常友好。相比传统SSM项目要单独装Tomcat、改server.xml,运维成本低一个量级。做宠物医疗系统免不了要演示给客户或老师看,能一条命令从零跑到一个可用系统,这种"省心感"是实打实的。
2.2 Vue前端的价值:交互复杂页面真的需要组件化
宠物医疗系统的前台页面复杂程度,远超一个后台管理系统的平均水准。医生的接诊工作台需要同时展示宠物信息、病史时间线、检查项列表、常用处方模板,并且支持在一个页面内连续操作。如果用JQuery+模板引擎来做,DOM操作和状态同步会让你写到怀疑人生。而Vue的响应式数据绑定、组件化开发能让这种"多区块联动"的页面变得可维护。
举一个具体场景:在挂号收费页,前台选择宠物后,需要联动展示该宠物的会员折扣、剩余余额、历史欠费(如果有)。这个页面有三个联动变量:当前宠物、当前项目(挂号/检查/药品)、应收金额。用Vue写起来就是data里维护三个状态,computed里实时算金额,下拉框变化时自动触发。这套逻辑在JQuery里要手动监听每个select的change事件,再手动去更新多个DOM节点,容易写乱,后期加需求也容易崩。
Vue的组件化对于复用场景帮助也很大。系统里的"宠物档案卡片"会在挂号页、接诊页、住院页重复出现,封装成一个PetCard组件后用props传宠物对象就能复用。"处方明细编辑表格"在医生开药页和前台划价页长得几乎一样,做成同一个组件再加一个disabled属性就能区分。这种复用带来的开发效率提升,可能在第一次开发时感觉不明显,但后续维护和加功能时会深有体会。
2.3 Vue2还是Vue3,怎么选
说实话,这个问题我每次都会被问到。我的建议是:如果是从零开始做新项目,直接选Vue3 + Vite + Pinia。
理由很实际:Vue3的Composition API对复杂逻辑的聚合能力远超Vue2的Options API。接诊工作台这种需要同时维护"宠物信息、病历表单、处方列表、费用预结算"四个独立模块的页面,用Composition API可以把每个模块的响应式变量和操作函数集中到一个区域,代码可读性和维护性都更好。而Vue2的Options API会把同一模块的data、methods、computed拆得七零八落,改一个模块要上下翻好几屏。
另一个原因是生态现状。2025年已经很成熟,Element Plus(Vue3版组件库)、Vite(构建工具)、Pinia(状态管理)这套组合已经非常稳定,网上教程也多到泛滥。反观Vue2因为官方维护进入尾声,新项目再入坑不值。唯一要留意的坑是Node.js版本——Vite要求Node 18+,如果你电脑还停在14/16,要么升级,要么用nvm切换。
这里要提醒一个很多人踩过的坑:不要一上来就跑到官网复制一堆最新语法,而是先确认你下载的Element Plus版本和你抄的教程版本对齐。比如Element Plus 2.x和1.x在部分组件props上是有差异的,网上很多"Vue3后台管理系统"教程其实是拿Vue3+Element Plus 1.x写的,直接照搬到2.x项目里可能组件不渲染。稳妥做法是用脚手架先跑通一个最小可运行页面,再往里填业务。
3. 数据库建模:把整个系统的骨架立稳
这个阶段是整个项目里最不能快进的部分。我见过太多人一上来就"猛写代码",写到一半发现挂号和病历没关联、处方不知道挂在哪张表下,然后推翻重来。数据库设计要遵循"逆推法":从最终交付的功能往回推表结构。你想要统计报表,就逆推出订单表要有状态字段和金额快照;你想要疫苗提醒,就逆推出疫苗记录表要存带next_date的计划字段。
3.1 核心表结构一览
下面这个表清单是一套经过验证的核心结构,覆盖了从预约到住院的完整链路:
| 表名 | 核心字段 | 作用边界 |
|---|---|---|
| user | id, username, password, real_name, role, status | 系统登录用户,role区分管理员/医生/前台 |
| owner | id, name, phone, address, member_level, balance, card_no | 宠物主人(会员)档案与储值账户 |
| pet | id, owner_id, name, species, breed, gender, birthday, sterilized, avatar | 宠物档案,外键关联主人 |
| appointment | id, pet_id, owner_id, doctor_id, appoint_time, status | 预约与挂号记录 |
| medical_record | id, pet_id, doctor_id, appointment_id, chief_complaint, examination, diagnosis, conclusion | 病历主表,记录单次就诊 |
| prescription | id, record_id, total_amount, status | 处方单主表 |
| prescription_item | id, prescription_id, drug_id, quantity, amount, usage | 处方明细,关联药品与用法 |
| drug | id, name, category, spec, stock, warn_stock, price, batch_no, expire_date | 药品库存与效期 |
| vaccination_record | id, pet_id, drug_id, doctor_id, vaccinate_date, next_date, vaccine_no | 疫苗/驱虫接种记录 |
| hospitalization | id, pet_id, record_id, start_date, end_date, cage_no, status | 住院床位与状态流转 |
| recharge_record | id, owner_id, amount, method, operator, create_time | 会员储值流水 |
| payment_record | id, owner_id, biz_type, biz_no, amount, status, create_time | 收费流水,biz_type区分挂号/药品/住院 |
每张表的核心都在于和业务流程一一对应。比如prescription_item表必须有usage字段,用药方式是宠物医疗的刚需,"一天两次、一次半片、连吃五天"这种信息只写在备注里是不够的,后面统计药品消耗和做费用明细时,只有结构化字段才能参与计算。
3.2 几个关键设计的细想
软删除与逻辑状态:宠物档案删除在真实业务里非常敏感,可能涉及历史病历追溯,所以不建议用物理DELETE。设计时给表加一个status或deleted标志位(1表示逻辑删除),查询时统一过滤。同样道理,药品表被处方引用后就不能物理删掉,只能下架。这个设计逻辑要在数据模型旁边用注释写清楚,不然自己过两个月来看都容易懵。
金额与快照:收费流水表里的amount必须是"当时算出来的实收金额",而不是通过会员等级实时算。因为会员折扣规则会改,药品价格会调,一旦历史单据在月底统计时发现金额变了,那财务就乱套了。因此处方明细里的amount字段要在开单那一刻把单价快照下来,以后药品价格调整不影响历史单据。
预约状态的流转:appointment表的status字段我建议做成一个完整的枚举,比如待确认→已确认→已就诊→已取消。这个流转在之后前端页面做"今日接诊队列"的时候特别好用:前台确认后,医生接诊界面就能按就诊时间排序拉出一列待诊宠物,看完后一键把状态改成"已就诊"。用状态机而不是零散的"是否就诊"布尔值,是这类系统的关键设计。
资金账户的余额扣减:owner表里的balance字段要和recharge_record、payment_record放在同一个事务逻辑里考虑。会员储值、消费扣费、退款,每笔金额变动都要生成流水,账户余额不过是流水汇总出来的结果,而不是一个可以随意update的独立字段。这条原则如果守住,就不会出现"余额对不上账"的经典事故。
4. 后端工程落地:从Controller到Mapper的全链路实操
数据库设计一旦定稿,后端的开发节奏就可以非常线性。下面是实际落地时我认为最合适的实现路线,也把一些容易踩的坑一并写出来。
4.1 项目骨架搭建
用IDEA的Spring Initializr创建项目时,Dependencies建议先选这几个:Spring Web、MySQL Driver、Lombok、Validation。后面再手动引入MyBatis-Plus(习惯用Hutool的话也可以加上,工具类能省不少代码)。
这里有一个值得说的点:不建议在创建项目时盲目勾选Spring Security。宠物医疗系统做JWT权限校验,自己实现一个Filter往往更直观,尤其对于入门阶段的开发者。Spring Security的学习曲线陡峭,光是一个SecurityFilterChain配置就能劝退很多人,而且它的默认登录页、CSRF防护逻辑如果不熟悉,反而会干扰你的接口联调。用Sa-Token或者自己写一个简单的Token拦截器,完全够用,而且答辩时你能讲清楚每一步原理。当然,如果你已经熟练使用Spring Security,用也行,二选一。但我的经验是,十天半个月的开发周期里,不熟的东西最好别碰。
生成完骨架后,我习惯先做一个"技术验证闭环":把数据库连接调通,写一个最简单的findAll验证MyBatis-Plus能查到数,再配一个全局异常处理器,再去写业务。为什么这么做?因为如果到联调阶段才发现数据库连不上、时间字段时区不对,排查会更费劲。
4.2 登录、JWT与权限三件套
鉴权模块是整个后端最容易出问题的地方,但同时也是最好讲清楚的地方。我的实现思路是这样的:
登录接口接收用户名和密码,用BCrypt加密比对(Spring Security的crypto模块可以单独引入,只借用它的加密工具,不用整套安全框架),验证通过后用用户的id和role生成JWT,密钥放在application.yml里,过期时间建议设置为12小时。响应体里返回token和一个简单的userInfo对象(含username和role)。
前端每个请求会在Header里带上Authorization: Bearer <token>,后端写一个OncePerRequestFilter,从Header里取token,用JJWT库解析,验证通过后把userId和role塞进ThreadLocal里供后续Controller使用。这种方式比直接把用户信息放在session里更适合前后端分离的场景,因为服务器不保存会话状态,横向扩展的时候不需要考虑session同步问题。
在这里要特别提醒权限校验的两层逻辑:第一层是登录态拦截(没有token直接401),第二层是角色拦截(比如医生改药品库存接口就是非法操作)。实现上可以在自定义注解里写@RequireRole("doctor"),在拦截器里把当前用户的role和注解要求比对,比对失败返回403。这个设计能在Controller层用一行注解搞定权限管控,而且答辩时讲起来逻辑清晰。
4.3 统一响应体与全局异常:代码瘦身的最有效手段
我在翻学生代码时最常看到的问题,是每个Controller方法都用HashMap返回结果,前端拿到的东西五花八门。这个在小项目里虽然是"能用",但一旦页面多了,前端要兼容各种"code=200但data是字符串"的怪异返回,联调效率会非常低。
统一响应体的思路很简单,定义一个泛型类R :
@Data public class R<T> { private Integer code; // 200成功,其他为失败 private String message; // 提示信息 private T data; // 数据体 public static <T> R<T> ok(T data) { ... } public static <T> R<T> fail(String message) { ... } }配合@RestControllerAdvice全局异常处理器,把业务异常、参数校验异常、兜底Exception分别处理,统一包装成R对象返回。这样前端axios响应拦截器只需要处理一种结构,就算后端抛了NPE,前端拿到的也是"系统异常,请稍后重试"而不是一堆堆栈。这个习惯要在写第一个接口时就养成,不要后来再统一改,否则改一处漏一处很痛苦。
4.4 MyBatis-Plus实践与分页
MyBatis-Plus几乎是这类项目的默认选择,它的优势:内置通用的selectById、save、updateById,比纯MyBatis手写SQL省力很多。但需要注意几点:
第一,逻辑删除要配置@TableLogic注解加在deleted字段上,然后在application.yml里配置logic-delete-value和logic-not-delete-value。这样你写的DeleteById实际上执行的是UPDATE语句,查询时MP会自动追加过滤条件,非常省心。
第二,自动填充。创建时间和更新时间这种字段,可以通过实现MetaObjectHandler统一填充,不用在每个insert时手动set。这个做法很常规,但能减少很多低级漏填。
第三,分页插件一定要先注册,否则Page对象拿到的records是null。配置方式在MyBatis-Plus 3.x下是这样的:
@Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; }有了这个插件,分页查询只需要在service层page(new Page<>(current, size), wrapper)就能自动生成带LIMIT的SQL,同时还能拿到total总数。宠物列表管理页、药品库存页、收费流水页,全是这一套,写起来非常快。
5. Vue前端:请求封装、路由守卫与核心页面落地
前端部分我建议走"框架先行"路线:用Vite创建一个Vue3项目,装好Vue Router、Pinia、Element Plus和Axios,先把登录页跑通,再扩展业务页面。这样等于先建立了"前端可运行"的安全网,再往上面填功能,不会因为环境问题磨半天。
5.1 前端目录结构
一个经典的划分方式是这样:
src/ ├─ api/ // 按模块划分的接口请求定义 │ ├─ auth.js // 登录相关 │ ├─ pet.js // 宠物档案 │ ├─ medical.js // 病历处方 │ └─ system.js // 用户药品 ├─ components/ // 公共组件(PetCard、StatusTag等) ├─ layout/ // 后台布局(侧边栏、顶栏、面包屑) ├─ router/ // 路由配置 ├─ store/ // Pinia状态管理 ├─ views/ // 页面组件 │ ├─ login │ ├─ dashboard │ ├─ pet │ ├─ medical │ ├─ pharmacy │ └─ system └─ utils/ └─ request.js // axios实例封装这个结构的核心思路是"api层和页面层分离",页面里不直接写axios请求,而是调用api模块里的函数。好处是接口地址改动时只需要动一个文件,而且每个页面的代码量减半,人看着清爽很多。
5.2 axios封装:把token和错误处理集中起来
axios封装是所有前端联调体验的基石。我在request.js里通常做三件事:baseURL指向/api并在Vite devServer里做代理(解决跨域)、请求拦截器里从localStorage里取token加到Header,响应拦截器里对HTTP 200但业务code非200的情况统一弹提示,并针对401做跳转登录页的处理。
一个简化的拦截器写法:
service.interceptors.request.use(config => { const token = localStorage.getItem('token'); if (token) config.headers.Authorization = `Bearer ${token}`; return config; }); service.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 => { Message.error(error.message || '网络异常'); return Promise.reject(error); } );注意这里我在成功分支里直接返回了res.data而不是整个res,这意味着页面里调用api时能直接拿到数据体,少写一层点。这种约定要在整个团队/项目里统一,不然一半页面写res.data,一半页面写res,迟早出bug。
5.3 动态路由与侧边栏:按角色生成菜单
根据第1章里说到的三类角色,前端的路由不能写死。具体做法:登录成功后,后端返回当前用户的"权限标识列表"或角色字段,前端在Pinia里存一份。路由守卫里做判断:如果当前访问的路由meta里配置了roles: ['admin', 'doctor'],而当前用户角色不在其中,就重定向到403页面。
这样Router的meta信息就成了权限配置的唯一地方,侧边栏菜单通过遍历路由表里的meta.title和meta.icon自动生成。这样设计的好处是:系统的菜单不用在前端模板里硬编码,新增一个页面时只要在路由表里加一行,菜单就自动出现,角色权限也跟着生效。在答辩演示时,切换不同角色账号登录,能明显看到菜单项不同,效果比口头讲解权限设计直观得多。
5.4 核心页面逐一拆解
登录页:表单校验用了Element Plus的Form Rules,登录成功存token到localStorage,跳转到dashboard。这个页面做得漂亮点对印象分很有帮助,毕竟老师第一眼看的就是登录页。
接诊工作台(医生视角):这是整个系统前端最重的页面。左侧是"今日待诊队列",中间是宠物信息卡片和病历表单,右侧是处方明细表格。数据流是:进入页面时请求/doctor/todayAppointments拿到待诊列表,点击某个宠物后拉取宠物详情和既往病史。病历表单里,主诉、检查、诊断三个字段分别单独绑定,处方区内可以动态增删药品行,实时汇总金额。这里我会用Composition API把"当前待诊宠物、病历表单、处方列表"分别抽成几个独立的逻辑块,代码结构更清晰。
药品库存页:一个典型的列表页,但带了检索和库存预警。表格里列出药品名称、规格、当前库存、预警阈值,库存低于阈值的行高亮红色,顶部加一个"低库存药品"筛选开关。药品入库弹窗里需要选择批号、填入有效期,保存时做新增和原批次影响处理——这个逻辑后端Validate一下即可。
挂号收费页(前台视角):操作路径是"选择主人→选择该主人名下的宠物→选择挂号类型→收费"。宠物下拉框的数据来自选定主人,这在前端是一个watch(ownerId)联动请求的动作。收费完成后,页面显示本次挂号流水详情,并可打印小票(浏览器Ctrl+P即可,不用额外集成打印机)。
疫苗记录页:列表展示每只宠物已经接种过哪些疫苗、下次接种日期。这个页面的关键操作是"批量提醒":筛选出未来7天内需要接种的宠物列表,生成提醒,并支持一键标记已通知。前端就是一次接口调用,后端写一个简单的日期范围查询。
6. 从本地联调到上线部署:这些年踩过的坑合集
很多新手项目最后挂在"跑不起来"上——不是代码写不出来,而是本地能跑,换个环境就崩。我把这个过程中高频遇到的坑集中列一下,都是曾经真实发生过、且耗费过大量时间的问题。
6.1 本地联调阶段的跨域问题
Vue开发服务器默认跑在5173,后端跑在8080。如果前端直接发请求,浏览器会因为跨域拦截。这里有两种解决方式:第一种是后端配置@CrossOrigin或全局CorsFilter,适合快速解决;第二种是前端Vite devServer配置代理,更接近生产环境的行为。
我更推荐第二种。在vite.config.js里设置:
server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, rewrite: path => path.replace(/^\/api/, '') } } }这样前端请求/api/login时会自动转发到http://localhost:8080/login,浏览器看到的始终是同源的请求,规避了跨域,同时后端不需要写任何CORS配置,生产部署时再用Nginx做同样的反代。用这套方案最舒服的地方是"开发环境和生产环境的请求路径保持一致",前端不用做任何环境切换适配。
6.2 打包与部署的完整流程
前端构建:
npm run build会在dist目录下生成纯静态文件。后端构建:
mvn clean package -DskipTests生成一个可执行jar。生产环境最简单的部署方式是在服务器上装MySQL和JDK,然后:
java -jar pet-clinic.jar --spring.profiles.active=prodnginx配置中把/api代理到后端8080,其他请求全部指向dist目录。这个方案适合项目演示,也适合宠物医院这种小体量的真实环境。
如果你想要更好的部署体验,可以在此基础上加一层Docker。一个docker-compose文件里编排mysql、redis(如果用的话)、app-server、nginx四个容器,就能一键拉起整个系统。我第一次用Docker部署时花了整整一天,原因是Jar包在容器里时区不对、MySQL连接超时、静态资源挂载失败,全是一行配置的事。所以如果时间不充裕,可以先不用Docker,直接裸机跑通nginx+jar即可。上线这件事,稳定简单优先于花哨。
6.3 高发环境坑清单
- MySQL 8的时区问题:连接字符串里一定要加
serverTimezone=Asia/Shanghai,否则时间字段偏8小时,疫苗提醒功能全部错乱。 - Node版本太高或太低:Vite要求Node 18+,但npm install时又可能因为node-sass报错——解决方式是统一使用sass(DartSass)替代node-sass,或者干脆用Vite默认的less支持。
- JWT密钥不要写死:用配置项注入,否则打包后没法换;过期时间设短一点(12小时),配合前端401自动跳转登录,"长时间挂机被踢"要比"token过期但不感知"体验好一些。
- 文件上传大小限制:宠物头像一般不超过2MB,但如果要做高清影像上传(X光片),SpringBoot默认的1MB大小限制会导致前端一直报413。记得在application.yml里调整
spring.servlet.multipart.max-file-size为10MB。 - 跨域在部署后依然存在:如果生产环境你用域名直接访问前端,裸IP加端口访问后端,浏览器还是会拦。所以nginx必须把
/api也反代到后端,让浏览器始终面对同一个域名。
我在实际部署中最大的体会是:上线前一定要模拟一次"全新环境部署"。找一台干净的服务器,从零开始克隆代码、装依赖、起服务,把整个过程完整走一遍。你会发现很多"在自己电脑上没问题"的项目,到了干净环境会因为端口占用、环境变量缺失、版本差异立刻暴露问题。这个过程虽然痛苦,但它是保证答辩现场、甲方现场演示不翻车的最可靠方法。
写在最后
这个系统做到后期,我从业务里总结出的一个心得是:宠物医疗管理系统真正的核心价值不在技术炫技,而在"数据链路是否闭环"——从挂号那一刻起,宠物主人的电话、宠物的疫苗史、医生的诊断、处方的用药、收费的金额、库存的出库,全部被一条线有序串起来。技术选型SpringBoot+Vue只是这一串链条的载体。
如果在做完基础版本之后想再往上走,我建议优先做这三件事:一是给系统加一个基于Echarts的经营看板,让老板一眼看到今日收入、接诊量、库存预警;二是把疫苗提醒改成主动推送,接入公众号模板消息或短信通知,让系统从"被动查询"变成"主动服务";三是做一份多端口的联调文档,把前端、后端、数据库的启动方式和默认账号密码写清楚,方便任何人在新机器上一键跑起来。最后一个建议对毕业设计来说尤其重要,因为评阅老师拿到项目后第一件事肯定是尝试自己启动它,一个导不起来的项目,再好的功能也白搭。