
做个宠物医院信息管理系统这个题目我前前后后经手过好几个版本但真正交付出去能在一个小诊所里稳定跑起来的靠的从来不是堆页面和CRUD代码而是对业务链路的透彻理解。这篇文章把我的设计方案、开发思路和踩过的坑完整写一遍适合正在准备Java方向毕设的同学也适合想给自己的小门诊配一套内部管理系统的朋友参考。项目整体结构是JavaSSM作为核心业务后端Flask作为辅助服务覆盖宠物档案、挂号、病历、开药、收费、库存、疫苗提醒和统计报表这些关键环节配套源码、文档、调试说明都能一起交付照着这个思路做整体流程不会偏。1. 项目定位与技术选型医院管理系统的第一刀1.1 先想清楚宠物医院的业务是怎么回事很多人一拿到这种题目就开始建表、写登录、做增删改查做完才发现跟真实诊所对不上号。我自己动手之前专门在两家小型宠物医院蹲过几天观察前台和医生的真实操作流程才发现这里面的核心不是“宠物信息管理”而是“一次就诊到底经历了哪些状态变化”。一次完整的就诊业务是这样的主人带宠物到前台挂号系统给这只宠物和医生建立一次关联医生在诊室里看到待诊列表叫号然后开始问诊、检查、写病历检查完如果开了药处方流到收费处收费完成之后药房从库存里扣减对应药品最后如果这只宠物需要打疫苗或者复诊还要在档案里留下记录方便后续提醒。这条链路上至少有五个角色前台、医生、收费员、药房、宠物主人。每个角色关心的数据不一样页面和操作权限也不一样。这个理解定下来之后系统的模块划分和数据库设计才不会有大的返工。你看很多管理系统做出来不好用普遍原因是把系统做成了“信息录入工具”而不是“流程支撑工具”宠物医院的管理系统尤其吃这一套就诊流程走不顺代码写得再多也白搭。1.2 为什么核心用SSM又额外加一个Flask关于为什么选SSM而不是Spring Boot肯定有人纠结。我的考虑是这是课程设计或毕业设计场景里非常经典的一组搭配SpringSpringMVCMyBatis三件套的学习资料最全面试被问到的概率也高团队里的同学多多少少都接触过上手成本低。如果放在真实企业内部我会建议直接上Spring Boot但作为学习和交付型项目SSM反而能让人把“配置是怎么工作的”这件事学透部署时也少很多黑盒对答辩也有好处。Flask加进来干什么起初我也想过一套技术栈全干完但做到疫苗提醒和统计报表的时候遇到了现实问题每隔几分钟要扫描一次免疫记录把近7天内到期的宠物筛选出来同时还要更新提醒标记每个月要按医生、按科室、按药品维度导出好几份Excel报表。这种定时任务和文件导出逻辑如果混在核心业务工程里会让代码不断膨胀也给核心事务链路添乱。所以我把这类相对独立的功能拆成了Flask子服务通过共用数据库和HTTP接口来做整合。顺带说下为什么没选FastAPI。FastAPI的异步性能确实好文档也漂亮但在这个场景里根本没有高并发压力Flask的生态更成熟、部署示例更多、遇到问题随便一搜全是答案。技术选型不是越新越好而是在交付周期里找到一个所有人都能维护的组合够用且稳定才是做项目选型的第一原则。2. 核心功能模块与数据库设计先把地基画对2.1 功能模块全景图整个系统的模块我归纳成八块不多不少每一块都对应真实业务角色模块核心功能面向角色用户与权限登录、角色控制、菜单动态渲染所有用户宠物档案主人信息管理、宠物档案增改查、历史病历查询前台、医生挂号就诊挂号、取消、待诊队列、就诊状态流转前台、医生电子病历症状、诊断、医嘱、复诊记录医生处方与收费处方录入、明细计价、收费结算、退费医生、收费员药品进销存药品档案、入库、库存预警、出库扣减药房疫苗提醒免疫记录、到期扫描、提醒消息系统自动前台统计报表就诊量统计、营收统计、药品消耗排行院长/管理员这里要特别说一句权限不能做成摆设。我见过不少管理系统登录之后所有菜单全平铺出来这种系统在真实诊所里根本没法用。我的做法是设计用户表、角色表、菜单表三张基础表再通过两张关联表把用户和角色、角色和菜单关联起来。登录时查出用户的角色再根据角色查出菜单树前端只渲染当前角色可见的菜单。这个设计在答辩时也很加分因为它说明你不是只做了CRUD而是真的考虑到了实际管理里的权限隔离需求。2.2 表结构设计哪些字段一定是必须的数据库设计是整个项目里最值得花时间的地方表结构错了后面写多少代码都是在打补丁。我的核心表有这么几张主人信息表、宠物档案表、挂号表、病历表、处方主表、处方明细表、药品表、用户表。看里层最关键的几张表宠物档案表pet字段类型说明idbigint主键owner_idbigint关联主人表namevarchar(50)宠物名speciesvarchar(20)物种犬、猫、其他breedvarchar(50)品种birth_datedate出生日期weightdecimal(6,2)体重按公斤is_deletedtinyint软删除标记挂号表registration字段类型说明idbigint主键pet_idbigint关联宠物doctor_idbigint关联医生visit_datedatetime就诊时间statustinyint1待诊 2就诊中 3已完成 4已取消处方明细表prescription_item字段类型说明idbigint主键prescription_idbigint关联处方主表drug_idbigint关联药品quantityint数量unit_pricedecimal(10,2)单价下单时冗余存储amountdecimal(10,2)金额小计这里有几个从实战里总结出来的要点。金额字段一律用decimal(10,2)不要用float或者double宠物医疗一针也要几十上百浮点误差在收费环节完全不能容忍。宠物档案一定要加is_deleted软删除医疗记录是连续数据一旦误删整个治疗链路就断了软删除可以随时恢复。处方明细里要把单价冗余存进去因为药品价格调整以后历史处方金额不能跟着变这是财务凭证最基本的“不可变”思路。状态字段用tinyint代码里配上统一的状态枚举类不要在业务代码中到处写魔法数字1、2、3否则后面维护的人会很痛苦。另外还有一张容易忽略的表是药品库存表。我最初只设计了药品主表库存字段直接挂在主表里后来发现入库批次、入库单价、效期管理根本没法做才补了一张入库流水表。所以建议一开始就把药品主表和入库流水分开哪怕是做简化版至少也能应对两个月的数据积累。2.3 从实体类直接生成建表SQL的小技巧当前讨论度很高的一个点是“根据Java实体类生成创建表的SQL语句”我实际项目里就是这么做的。做法是先在Java实体类里把每个字段写清楚尤其是字段注释一定要完整然后用一个小工具把实体类反射解析成建表语句批量生成数据库表。这样维护实体类的时候数据库结构能保持一致省去了大量手写CREATE TABLE的时间也避免了注释和字段类型不一致的问题。我的具体套路是这样写实体类时严格遵循统一规范主键字段标TableId逻辑删除字段标TableLogic其他字段用TableField注明数据库列名和注释。生成SQL的工具读这些注解自动拼接出CREATE TABLE IF NOT EXISTS语句工具本身代码不多能跑就行不追求通用。如果你用的是MyBatis-Plus它的代码生成器可以反向根据表生成实体类但从实体类正向生成建表语句往往需要自己写点工具。我始终坚持以实体类为基准数据库脚本靠工具生成这个习惯后来帮我省了很多维护成本改字段时只改一处刷新下脚本就行。3. 实战开发从挂号到收费的完整链路怎么落地3.1 SSM项目骨架与配置核心后端用Maven管理工程结构按Controller、Service、Mapper、entity分层。SSM的配置有几个翻车高发点我踩过之后总结成三条。第一Spring的扫描包不要和SpringMVC的扫描包重叠Spring只管Service、Mapper这些业务组件SpringMVC只管Controller重叠会导致Service被初始化两遍事务代理失效。第二数据库连接池直接上Druid配置好监控页面后慢SQL和连接情况一目了然调试的时候真的爽。第三MyBatis的全局配置里开启下划线转驼峰映射这样数据库列名和实体类属性名才能自动对应减少大量重复的resultMap编写。启动Tomcat之前我本机装了多个JDK版本因为有的老SSM项目对JDK版本敏感有的新工具链又要求更高版本。这里分享一个多JDK切换的经验尽量不要频繁改系统环境变量JAVA_HOME而是用IDEA的Project Structure给不同项目分配独立SDK再在运行配置里指定JRE这样能同时维护JDK8和JDK11的项目互不干扰。3.2 挂号、病历、开药、收费一条链路这个项目的主业务链路我把它处理成一条事务链。前台挂号时插入挂号记录把宠物状态更新为“就诊中”医生完成问诊后填写病历插入medical_record开药时插入处方主表和处方明细表收费员结算时插入收费记录并把药品库存扣减掉。这条链路任何一个环节失败都要整体回滚不能出现“挂号成功但病历只存了一半”这种脏数据。这里有个容易被忽略的细节就是扣库存。一开始我用的是“先查库存判断库存够不够再update扣减”但多人同时操作时库存会被扣成负数。后来改成一行SQLupdate drug set stock stock - #{quantity} where id #{drugId} and stock #{quantity}执行后受影响行数为0就说明库存不足直接抛业务异常结束事务。这样是用数据库的行锁解决了并发问题不需要引入Redis或分布式锁那些额外复杂度在诊所这种并发量场景下完全够用。接口设计我习惯按资源来命名比如挂号对应POST /api/registration保存病历对应POST /api/medicalRecord开处方对应POST /api/prescription收费结算对应POST /api/payment。响应统一用一个Result对象包装固定三个字段code、message、data。所有业务异常都由全局异常处理器接住返回友好提示而不是把Java异常堆栈直接甩给页面。前端页面我这里用的是JSP渲染配合一套简洁的后台管理模板登录页和主框架是静态布局菜单由后端根据角色动态渲染。写JSP时踩过最大的坑是EL表达式和JSTL标签库的版本冲突后来统一了Servlet 3.1以上版本配套的依赖才解决。如果你不想用JSP也可以换成Vue通过Ajax调后端接口展示效果更现代但工作量会大一些。作为毕设交付JSP方案的好处是每个页面功能都能直接被老师点开看到不需要额外启动一个前端工程。这里还要补充一个我在权限部分的具体实现思路用户登录后后端把该用户的菜单清单一次性返回前端渲染成侧边栏同时后端在每一个Controller接口上做角色校验。权限校验千万不要只在前端做因为接口是可以被直接调用的只隐藏菜单不校验接口等于没设防。3.3 Flask子服务疫苗提醒和报表导出Flask在这里承担的是两个相对独立的辅助任务。第一个是疫苗提醒因为疫苗记录是持续性的主人经常忘记下一次注射时间系统需要定期扫描疫苗记录表找出未来7天内到期的宠物然后生成提醒记录。我使用APScheduler作为定时任务框架每天凌晨跑一次扫描扫描逻辑很简单查询所有免疫记录计算下次免疫日期和当前日期之差在7天以内且没有被标记为已提醒的记录就生成提醒。这里有个经验提醒状态标记不要让Flask直接更新主库的核心业务表更稳妥的做法是Flask调用SSM提供的一个内部HTTP接口由核心系统去完成状态更新这样写操作的入口始终收敛在一套代码里避免两边同时写库造成数据不一致。第二个是统计报表导出。就诊量、营收、药品消耗排行这些数据如果让管理员在页面上慢慢翻体验很差所以我做了报表下载接口从数据库统计汇总后用openpyxl生成Excel文件前端点击按钮直接下载。生成Excel时要注意设置列宽、表头和自动筛选这些细节虽然不起眼但直接关系到使用的人愿不愿意真的把报表用起来。Flask端不需要做登录页因为它是内部服务只对核心系统暴露几个简单接口定位很轻代码量也很小维护起来非常方便。3.4 两个技术栈联调时最容易翻车的三个点这种多技术栈项目联调时最容易出问题。第一个坑是JSON字段命名不一致Java后端习惯用驼峰Python端用字典的键直接取很容易对不上。我的解决办法是确立统一标准所有对外接口的字段名一律用小写下划线由Java端做序列化配置统一输出Python端直接按下划线处理。第二个坑是时间字段格式和时区。Java的LocalDateTime默认序列化出来是数组格式Python端根本没法直接解析数据库连接的serverTimezone没设置好的话查出来还会差8个小时。我在两个服务里统一约定时间全部用字符串格式yyyy-MM-dd HH:mm:ss传输入库前再按统一格式解析项目跑起来之后没再因为时间问题折腾过。第三个坑是字符集。MySQL连接URL上必须加characterEncodingutf8Tomcat的URIEncoding也要设成UTF-8否则中文数据在页面和接口之间传几次就乱码。别再等到数据写脏了再回头排查最省事的办法是一开始就统一编码。4. 部署、调试与常见问题排查实录4.1 本地调试环境的配置清单这个项目要顺畅跑起来环境整理顺序很重要。先装JDK和MavenJDK8就可以然后用IDEA导入后端工程配置好Maven仓库MySQL建好库执行生成的建表脚本和初始化数据。Flask端建议用虚拟环境把依赖固定在requirements.txt里一次pip install -r requirements.txt装完。装好之后先单独确认Flask服务能起来再启动Tomcat最后联调。分段启动的好处是出问题时能第一时间定位是哪个技术栈的事情。端口规划我固定为Tomcat跑8080Flask跑5001两个服务分开端口避免冲突。前端页面通过8080访问页面中需要调用Flask接口的地方通过后端服务内部调用。因为不是浏览器直接跨域访问跨域问题基本不存在。如果以后要把Flask接口直接暴露给前端Ajax调用那就需要单独处理跨域配置。4.2 部署上线war包、gunicorn与进程守护如果要把项目部署到服务器SSM部分我会打成war包放进Tomcat的webapps目录启动而不是用IDEA里集成的Tomcat去跑。原因很简单IDE里的运行方式依赖开发环境换个机器就不好复现而war包部署是标准套路答辩和交付时随便找一台装好Tomcat的服务器就能跑起来。数据库连接信息、账号密码全部放在配置文件里部署时替换成服务器真实值正式交付时还会把敏感信息单独抽出来维护避免源码里直接带密码。Flask部分部署时不要用自带的开发服务器那个只适合作本地调试。我用gunicorn起多进程启动命令大致是gunicorn -w 4 -b 0.0.0.0:5001 app:app4个worker对辅助任务足够。再用systemd做进程守护保证进程挂掉后能自动拉起。这在正式跑业务时特别重要定时任务如果因为进程挂了没跑疫苗提醒全会漏掉这种情况我真实遇到过后来加了进程守护才解决。4.3 常见问题速查表现象原因解决办法中文乱码数据库连接或Tomcat编码未设置连接URL加characterEncodingutf8Tomcat配置URIEncodingUTF-8Service事务不生效Spring和SpringMVC扫描包重叠Service被初始化两遍Spring只扫描service和mapperSpringMVC只扫描controller扣库存出现负数并发更新没加条件用update drug set stockstock-? where id? and stock?影响行数为0则提示库存不足Flask连MySQL报认证错误MySQL8默认caching_sha2_password认证插件冲突创建用户时指定mysql_native_password或做兼容处理Java时间序列化成数组LocalDateTime默认序列化格式问题统一配置Jackson格式为yyyy-MM-dd HH:mm:ss接口500但日志没输出MyBatis SQL日志级别太高或日志配置缺失把mapper包日志级别设为DEBUG部署后页面资源404context-path和请求路径不一致核对Tomcat应用名前端加统一前缀这张表就是我调试阶段挨个踩过的坑汇总可以直接当成排查手册用。再说一句经验之谈遇到问题先看SQL日志再看HTTP状态码再看控制台异常最后才去怀疑配置。排查顺序反了一个小问题能查一下午。4.4 交付项目时源码和文档怎么组织标题里提到的源码、LW、调试文档和讲解我整理了一套目录结构也推荐你这么做。第一类是源码工程Java后端工程、Flask子服务、数据库脚本分别放独立目录每个工程都带README写清楚JDK版本、依赖安装方式、启动步骤。第二类是数据库脚本建表SQL和初始化数据放一个sql目录。第三类是配套文档包括设计文档、需求说明、操作手册、答辩PPT其中设计文档要重点写数据库设计和接口设计老师翻得最多的就是这两块。第四类是调试文档记录主要问题和解决过程也就是前面那张速查表的完整版。有了这四类内容哪怕三个月后自己回来看这个项目也能快速把环境跑起来不只是给老师看更是给自己省时间。至于讲解视频或者讲解录音我的建议是不要照着PPT念按“业务是什么→流程怎么走→代码怎么落→问题怎么解”这个顺序来讲演示时先跑一遍真实流程再切到关键代码位说明设计意图听的人才会相信你是真的懂。最后说一点我个人的体会。做完这套系统最深的感受是技术选型再花哨不如把一条业务链路理顺与其纠结SSM和Flask哪个更好不如先把挂号到收费的每一步状态流转写清楚。如果你后面想在这个项目上继续扩展建议从微信预约、在线问诊、电子发票这几个方向去加这些都是宠物医院真实存在的需求每补一块系统离“真正能用”就更近一步。