
1. 从需求谈起宠物医院管理系统到底要管些什么很多同学拿到Servlet宠物医院管理系统这类题目时第一反应是这不就是个CRUD嘛然后急着打开IDE开始写代码。我见过太多人卡在中途不是因为代码写不出来而是因为根本没想清楚医院管理这四个字背后藏着多少业务细节。这一章咱们先把需求和边界聊透后面写代码才能少走弯路。宠物医院和给人看病的医院在流程上有很多相似之处但又有自己的特殊性。先说核心流程宠物主人带宠物到店前台需要登记宠物信息和主人联系方式然后挂号看诊医生记录病历、开具处方如果宠物需要住院或寄养还要管理笼位和护理记录最后离店时结算费用。听起来不复杂但拆开来看每一个环节都对应着至少一张数据表和一组页面操作。我当时给这个系统划分的核心模块是这样的宠物档案管理记录宠物名称、品种、性别、年龄、体重、疫苗情况、过敏史等客户管理宠物主人的姓名、电话、地址以及名下关联了哪些宠物预约挂号客户提前预约医生和时段前台能够确认或取消接诊与病历管理医生填写诊断信息、症状描述、检查结果、治疗方案药品与处方管理维护药品库存开处方时自动扣减库存住院与寄养管理笼位分配、每日护理记录、住院费用累计收费与统计挂号费、诊疗费、药品费、住院费统一结算并统计每日/每月营收说实话把表格一列出来你就会发现这个系统的信息量已经超过普通课设水平了。所以别急着写代码先把业务流程画清楚哪怕只是用手写画一张流程图都能帮你省下后面调试的时间。我自己的习惯是先确定角色——谁来用这个系统。宠物医院系统通常有三种角色前台接待与收费、医生看诊与病历、管理员系统维护与数据统计。如果还有系统管理员的角色那还要考虑员工账号的管理。角色不同看到的菜单、能操作的按钮就不一样这也是后期做权限控制的基础。从技术实现的角度看Servlet作为服务端核心配合JSP做页面展示是Java课程设计里最经典的分层方案。请求先到Servlet控制器由Servlet调用业务逻辑层再通过DAO访问数据库最后把结果分发到JSP渲染。这种MVC思路虽然老但结构清晰评审老师一眼就能看出你是懂分层的。而且从学习角度来说这个体系把HTTP请求生命周期、请求参数封装、会话管理、JDBC操作这些基本功全练到了性价比非常高。还有一点很多人忽略系统的边界决定了你的工作量。比如是否要做登录验证是否要做分页查询是否要处理并发挂号冲突是否要生成报表。我建议第一次做的同学把范围控制在能完整跑通一个业务闭环就行——从预约到接诊到处方到结算走通一整条链路。在这个基础上加亮点功能比如按品种统计就诊分布、按月份统计营业额比盲目堆功能要有效得多。2. 技术选型复盘为什么用ServletJSP而不是一上来就Spring Boot我见过不少同学做毕业设计直接绕开Servlet用Spring Boot理由是现在企业都在用这个。这话没错但放到课程设计这个场景里就有点本末倒置。Servlet是Java Web的基石Spring Boot再高级底层还是Servlet容器帮你接收HTTP请求。而且直接上手Spring Boot你很容易陷入注解一贴代码全通但完全不知道怎么回事的状态。这个项目最合理的选型组合是JDK 8 Tomcat 8.5/9 Servlet 3.1 JSP 2.3 MySQL 5.7 JDBC。理由我一个个说。JDK 8稳定、兼容性好Tomcat 8.5和9都完美支持网上查资料也最方便。虽然现在Oracle已经停止JDK 8的免费商用更新但学习阶段不受影响IDE和各种老项目兼容性也最好。Tomcat 8.5Servlet 3.1的参考实现容器支持注解方式配置Servlet又可以保留web.xml方式。恰好适合从传统配置过渡到注解配置的学习曲线。Servlet 3.1支持异步处理、注解配置、WebSocket课设完全是够用的。用注解方式可以省掉很多web.xml配置代码但我也强烈建议你要会写web.xml因为你工作中读老项目的时候一定会碰到。JSPJSTL页面渲染配合JSTL标签库避免在JSP里写大量的Java脚本片段这也是被很多企业诟病的问题。保持页面干净的技巧是把数据准备好的任务全交给ServletJSP只负责展示。MySQL 5.7使用广泛教程多Navicat或者DBeaver都能连。它支持InnoDB事务配合JDBC做事务管理能保证挂号、处方这些操作的完整性。连接池组件Druid或者C3P0都行。我用的是Druid因为它自带监控页面你还能在浏览器里看到SQL执行情况方便排查问题。数据库驱动记得选mysql-connector-java 5.1.49版本。如果选了8.x的驱动连接串里必须加useSSLfalseserverTimezoneAsia/Shanghai否则会报时区错误这个坑我后面单独讲。有些同学会纠结要不要引入MyBatis我的建议是课设阶段别引入。原因很实在MyBatis让你不再手写大量JDBC代码但也正因为这样你对事务、预处理语句、结果集映射的理解会弱很多。而这个项目的考核点恰恰就是这些底层能力。你手写一个JDBC封装类几十行代码但在答辩的时候能从头讲到尾这就是加分项。如果你实在觉得手写DAO比较痛苦也可以参考一个折中方案只写一个BaseDAO类用反射处理通用的增删改查子类继承它再补充特定查询。这个方案既能减少重复代码又保留了对JDBC流程的掌控感属于进阶操作。3. 数据库层的核心设计思路六张表是怎么串起整个业务的数据库是整个系统里最重要的一环。表结构设计好了后面写DAO和Servlet就是顺着流水走设计不好后面写一个功能就要改一次表改到你想摔键盘。我把自己沉淀下来的核心表结构拆给你看你可以在自己的项目里直接参考。3.1 客户与宠物一对多的关系客户表存宠物主人的基本信息宠物表关联到客户。这么设计的原因是一个主人可能带两只猫轮流来看病如果每次来都重新录一遍主人信息系统里就会出现大量重复数据。customer表id、name、phone、address、create_timepet表id、customer_id外键、name、type猫/狗/兔等、breed品种、gender、age、weight、vaccine_status疫苗状态、allergy_info过敏史、create_time这里有个细节需要注意年龄字段建议用字符串存储比如3岁2个月因为宠物的年龄精度不需要太高而且不同物种的年龄计算方式不一样。用字符串可以避免后端换算的麻烦展示也直观。3.2 医生与预约时间冲突怎么处理医生表很简单id、name、title职称、phone、department科室、status出诊/停诊。预约表是业务流的起点也是并发冲突最容易出现的地方appointment表id、pet_id、doctor_id、appointment_date、time_slot如上午09:00-09:30、status待确认/已确认/已完成/已取消、remark、create_time时间字段的格式是2025-03-15这样配合time_slot字符串可以简化判断。在代码里检查该医生该时段是否已有预约时只需一条count查询。如果这个时段已经被占了就返回友好提示让客户换一个时间。我遇到过有些同学把预约时间设计成时间戳结果在JSP页面展示时还要自己做格式化徒增工作量。你可以说时间戳更精确但课设项目的展示型需求决定了人看的格式比机器算的格式更重要。3.3 病历与处方一对多的核心链路病历表medical_recordid、pet_id、doctor_id、visit_time、symptom症状描述、diagnosis诊断结果、check_result检查结果、treatment_plan治疗方案、note备注处方表prescriptionid、record_id关联病历、drug_id关联药品、dosage用量、frequency频次、days天数、amount数量这样设计的好处在于一条病历可以开多条处方每次开药都对应到具体的病历医生回看历史时能完整看到上次来是什么问题、开了什么药。这不光是展示需求也是真实医院系统的审计需求。3.4 药品库存与住院寄养药品表drugid、name、specification规格、stock_quantity库存、price、manufacturer、create_time住院寄养表hospitalizationid、pet_id、cage_no笼位、start_time、end_time、daily_fee、status住院中/已出院、care_note、create_time一个笼位同一时间只能有一只宠物这个业务规则可以在代码里校验也可以加唯一索引。我的建议是加一个cage_status字段把它和宠物解耦这样查看哪些笼位空闲时只需要查笼位表不需要去翻住院记录。不过这会多一张表你可以自行权衡。3.5 设计技巧别忘了逻辑删除和时间字段有两件事我建议你从一开始就做所有表都留create_time字段用于列表排序和数据统计不要物理删除核心业务数据而是用status字段做逻辑删除或标记。原因很简单宠物医院系统里病历和处方都是留痕数据删了可能出纠纷。你也别在答辩时被老师问到删除记录是否合适才意识到这个问题。4. 前后端结构怎么组织从目录到Servlet再到JSP的完整链路这个项目的代码组织方式我强烈建议按MVC的分层思想来建包别像一个菜市场一样把所有类堆在同一个包下面。我的目录结构是这样的src ├─ com.pethospital.servlet │ ├─ CustomerServlet.java │ ├─ PetServlet.java │ ├─ AppointmentServlet.java │ ├─ MedicalRecordServlet.java │ ├─ PrescriptionServlet.java │ ├─ DrugServlet.java │ ├─ HospitalizationServlet.java │ ├─ StatisticsServlet.java │ └─ LoginServlet.java ├─ com.pethospital.dao │ ├─ BaseDAO.java │ ├─ CustomerDAO.java │ ├─ PetDAO.java │ └─ ... ├─ com.pethospital.service │ ├─ CustomerService.java │ └─ ... ├─ com.pethospital.entity │ ├─ Customer.java │ ├─ Pet.java │ └─ ... └─ com.pethospital.util ├─ DBUtil.java └─ LoginFilter.java4.1 Servlet层一个Servlet还是多个Servlet初学者最常见的问题是问一个Servlet走天下还是每个功能一个Servlet我实验过两种方案。单Servlet方案把所有请求都集中到一个类里通过action参数分发代码非常紧凑但类会越来越大到后面一个类上千行维护体验很差。多Servlet方案按业务模块拆分每个Servlet只负责自己模块的动作比如PetServlet负责宠物的增删改查里面再用action参数区分list/add/update/delete/view操作。我的建议是规模再小也按业务拆每个业务一个Servlet。拆分并不增加开发量反而让代码路径清晰打分的时候也能看出你具备模块化设计意识。4.2 请求分发模式用action参数统管操作AppointmentServlet的核心结构是这样一个统一入口用action参数判断执行哪个方法WebServlet(/appointment) public class AppointmentServlet extends HttpServlet { private AppointmentService service new AppointmentService(); protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String action request.getParameter(action); if (list.equals(action)) { list(request, response); } else if (addPage.equals(action)) { forwardToAddPage(request, response); } else if (delete.equals(action)) { delete(request, response); } else { list(request, response); } } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String action request.getParameter(action); if (add.equals(action)) { add(request, response); } else if (update.equals(action)) { update(request, response); } else if (finish.equals(action)) { finish(request, response); } else { response.sendRedirect(request.getContextPath() /appointment?actionlist); } } }这里有个小细节doGet用来处理查询、跳转页面的请求doPost处理数据变更请求。语义上更清晰也能避免在JSP里写form methodget导致中文乱码的经典问题。JSP页面放在webapp目录下。我常用的目录划分是webapp ├─ static │ ├─ css │ └─ js ├─ WEB-INF │ ├─ web.xml │ └─ views │ ├─ customer │ │ ├─ list.jsp │ │ ├─ add.jsp │ │ └─ edit.jsp │ ├─ pet │ ├─ appointment │ ├─ medical-record │ └─ login.jspJSP放WEB-INF下面有个好处用户不能直接在浏览器输入URL访问这些页面必须经过Servlet转发。这样既保障了页面安全又强制了数据必须先准备好再渲染不会出现页面直接打开但数据全是空的这种尴尬情况。4.3 写DAO时一定要用预处理语句写DAO层所有SQL必须用PreparedStatement千万别用字符串拼接SQL。这不只是SQL注入的安全问题也是可靠性问题。比如宠物名字可能含有单引号真实存在叫OBrien的例子字符串拼接直接让SQL报错。预处理语句参数化之后彻底避免了这个麻烦。我复用最多的BaseDAO封装了几个方法public class BaseDAO { protected int executeUpdate(String sql, Object... params) throws SQLException { try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { for (int i 0; i params.length; i) { ps.setObject(i 1, params[i]); } return ps.executeUpdate(); } } protected T ListT queryList(String sql, RowMapperT mapper, Object... params) throws SQLException { ListT list new ArrayList(); try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { for (int i 0; i params.length; i) { ps.setObject(i 1, params[i]); } try (ResultSet rs ps.executeQuery()) { while (rs.next()) { list.add(mapper.mapRow(rs)); } } } return list; } }RowMapper是一个函数式接口传入Lambda就能把结果集映射成实体对象。这种方式比完全手写ResultSet→Object的转换要清爽很多又能让你清楚看到每一列的取值逻辑。注意用 try-with-resources 来管理连接连接用完必须归还或关闭否则连接池很快耗尽系统假死。5. 核心功能模块的完整落地过程一条预约的病历闭环现在进入整个项目最核心的部分。这一章我带着你完整走通一个业务闭环客户预约→医生接诊→开处方→扣库存→结算。这是整个系统的主干道主干道通了其他功能都是支线。5.1 预约从表单提交到防止冲突预约功能的表单页有这几个字段宠物下拉框、医生下拉框、日期、时间段、备注。宠物和医生下拉框的数据在跳转到预约页时由AppointmentServlet里的addPage方法查好放到request域里。提交时doPost里add方法先做两个校验一是检查宠物ID和医生ID是否存在二是检查该医生在该日期该时段是否已有预约public boolean checkTimeSlot(int doctorId, String date, String timeSlot) { String sql SELECT COUNT(*) FROM appointment WHERE doctor_id? AND appointment_date? AND time_slot? AND status ! 已取消; // 返回 count 0 表示冲突 }一个容易漏掉的细节是已取消的预约不应该占用时段。如果你写的查询条件没有排除已取消状态那么客户取消预约后别人再也约不了这个时段。这种bug在验收被当场测出来会很影响印象分的。5.2 接诊与病历事务里完成一系列操作医生点击接诊按钮意味着预约状态从已确认变成已完成同时创建一条病历记录。这里有多个数据库操作必须放在一个事务里public void finishAppointment(int appointmentId, String symptom, String diagnosis, String checkResult, String treatmentPlan, ListPrescriptionItem prescriptionItems) { String updateAppointmentSql UPDATE appointment SET status已完成 WHERE id?; String insertRecordSql INSERT INTO medical_record(pet_id, doctor_id, symptom, diagnosis, check_result, treatment_plan) VALUES (?,?,?,?,?,?); Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 执行更新和插入拿到主键 // 循环插入处方明细 // 每插入一条扣减对应药品库存 conn.commit(); } catch (SQLException e) { conn.rollback(); throw e; } finally { conn.setAutoCommit(true); DBUtil.close(conn); } }这一步是很多同学的短板。你要知道处方明细插入成功但库存扣减失败或者病历创建成功但预约状态忘了更新都会造成数据不一致。事务就是来解决这些问题。项目中至少要有这么一处用事务的代码这是答辩时能讲半天的亮点。数据库连接我推荐Druid连接池来管理。把连接配置放到druid.properties文件里DBUtil用静态代码块加载保证整个应用只有一个连接池实例。每次从池里拿连接用完就归还性能非常稳定。5.3 药品扣减与库存下限提醒扣减库存的SQL是UPDATE drug SET stock_quantity stock_quantity - ? WHERE id ? AND stock_quantity ?。注意这里带了stock_quantity ?条件用数据库的条件判断来防止扣成负数。如果影响行数为0说明库存不足整个事务直接回滚。这个写法比先查再减更安全因为你避免了查到库存3、另一线程扣到1、你按3去减这种并发下的超卖问题。课设里可能并发量不大但这个写法体现了工业级的考量答辩时能加分。还有一个小建议库存低于阈值时可以在药品列表页面高亮显示或者在药品页面顶部显示一条提示信息。比如查药品列表时顺便查stock_quantity 10的条目数不为零就在页面上标红提醒。成本不高但非常体现业务敏感度。5.4 收费结算一个页面清晰列出明细结算页根据预约ID或病历ID查出所有相关费用项挂号费从预约记录取或者按固定值、检查费医生接诊时录入、药品费处方明细关联药品价格自动算、住院费按天数乘日费用。最后汇总成总金额。收费完成之后我给预约表加了一个billing_status字段未收费/已收费。接诊完成后状态变成待收费收费完成后变成已收费。这样前台一眼能看出今天还有哪些病人看了病却没交钱。这个字段虽然小但能让整个流程闭环。5.5 页面展示用列表和详情页串起业务列表页是每个模块的标配。预约列表建议显示宠物名、主人电话、医生名、日期、时段、状态。医生名和宠物名通过联表查询出来而不是直接存ID在列表里不然页面上一串数字可读性很差。详情页要能展示病历和处方的完整信息包括药品名、规格、用量、频次和对应费用。最好将预约→病历→处方→结算放在一个时间线上自上而下展示实现一个看诊详情页。这个页面的完成度会远超大部分同学的课设老师的印象分会很明显不一样。6. 环境配置里最容易翻车的几个地方VSCode、Tomcat和中文乱码这一章可以说是整个项目里最烦人的部分。代码写完了结果环境配了两个小时起不来服务这种经历我太熟悉了。而且现在很多同学用VSCode写JavaWeb遇到的坑比用IDEA的多不少。6.1 VSCode写Servlet需要的环境配置如果你用的是VSCode需要以下插件和配置链Java Extension PackJava语言支持全家桶安装Tomcat插件Community Server Connectors 或者 Tomcat for JavaVSCode里新建JavaWeb项目的推荐方式用Maven创建一个war包装项目。因为Servlet需要依赖Tomcat运行所以pom.xml里引入Servlet API但scope必须是provided意思是编译时用、打包时不包含因为Tomcat已经自带了。如果你误用了默认的compile作用域部署后可能出现类冲突项目里两个相同的类名会导致各种诡异报错。一个更省事但必须做得规范的方法是手动地把servlet-api.jar和jsp-api.jar放进项目里。但放在哪、要不要参与打包都要从pom.xml的角度想清楚。如果项目已经用Maven管理了就用依赖的方式把scope配成provided省得麻烦。部署的时候VSCode的Community Server Connectors插件只需要右键项目Run on Server选Tomcat 8.5或9即可。如果启动端口被占用改tomcat-users.xml或启动配置里的端口都行。生产环境跑多个服务时8080被占是非常常见的情况配一个8008或者8010的端口是常规操作。6.2 Tomcat版本要和JDK匹配这里有个特别常见的坑Tomcat 10.1以上版本已经把包名从javax.servlet改成了jakarta.servlet。如果你用的是Tomcat 10写代码时还在用javax.servlet.http.HttpServlet编译不会报错因为编译期依赖的是你导入的jar但运行时就是404或者500错误。这个问题坑了很多人。所以如果你用的是Servlet课设的标准写法就老老实实配Tomcat 8.5或9对应JDK 8。这个组合经过千万人的验证网上任何资料都能对得上。6.3 中文乱码的三大根源乱码是老生常谈但这个项目里80%的报错都和它有关。我总结三个核心解决方案页面提交的POST请求在Servlet的doPost方法第一行写request.setCharacterEncoding(UTF-8)。放在最前面一定要先设置再调用getParameter响应输出乱码response.setContentType(text/html;charsetUTF-8)JSP页面顶部的pageEncoding指令保持UTF-8数据库连接串jdbc:mysql://localhost:3306/pet_hospital?useUnicodetruecharacterEncodingutf8。MySQL数据库本身的字符集也要设置为utf8mb4不然中文存进去直接变成问号为了保证万无一失可以在DBUtil里加一句拿到Connection后执行SET NAMES utf8mb4。这样即使连接串漏配了编码也能兜底。还有一个容易被忽略的点在Tomcat的server.xml里Connector节点默认的URIEncoding在8.5里已经是UTF-8了老版本需要手动加URIEncodingUTF-8。如果你用Tomcat 7这个配置不加会出现GET请求的查询参数乱码。6.4 部署后的目录结构检查清单部署之前花两分钟检查一下项目目录结构是否符合JavaWeb标准项目根目录 ├─ pom.xml如果是Maven └─ src/main/webapp ├─ WEB-INF │ ├─ web.xml │ └─ views ├─ static │ ├─ css │ └─ js └─ index.jsp绝对不要把JSP直接放在根目录当正式页面用所有页面放进WEB-INF下面。静态资源放static目录这样的结构在任何容器里都能顺利部署。我建议每次改动Java代码之后重新发布而不是热部署。Tomcat的旧版本热部署经常导致内存泄漏和奇怪的类加载问题。虽然热部署省时间但反复出问题浪费的时间更多。稳妥方案是改完代码Clean项目重启Tomcat一劳永逸。7. 搜索宠物医院管理系统时你真正该查的是什么很多同学做这个项目时会去网上搜宠物医院管理系统 源码希望能直接下载完整项目。我不反对参考别人的源码但我强烈不建议没有理解就交上去。你交得了一时答辩时老师问预约冲突你怎么处理的答不上来那才是真的翻车。7.1 源码应该怎么用才有价值我的建议是把源码当作参考答案而不是作业答案。拿到一份源码后你应该做三件事第一看它的包结构和表结构设计。别人的表关系是什么样的客户和宠物是一对多还是一对一预约表有没有做状态机把自己当成评委去挑毛病这个学习效果比你闷头写十遍都强。第二看它有没有做权限控制。这个系统有没有角色区分医生和前台看到的菜单是不是不同如果没有你就知道可以自己加一个。比如通过Session存用户的角色在页面顶部用JSTL判断角色来显示不同的菜单项。第三看它有没有处理异常情况。库存不足提示了吗预约时间冲突处理了吗删除有关联记录的宠物时是不是直接报错这些异常处理恰恰是课设拿高分的分水岭。7.2 网上代码常见的通病你一定要避开我看了不少网上的宠物医院管理系统源码问题非常集中。第一项目里所有代码全部堆在Servlet里一个类两三千行完全没有分层你根本看不下去。第二SQL全是字符串拼接甚至有直接拼接参数进SQL的一旦传了引号就报错彻底没眼看。第三没有任何事务处理插入主表和从表之间只要一个失败数据就不一致。第四登录系统形同虚设根本没有过滤器拦截直接在浏览器里输URL就能访问后台页面。你对照一下自己正在写的代码如果上面任何一条命中了都值得花时间重构。课设项目规模不大重构成本完全能接受。7.3 从能跑到有亮点的进阶功能如果你想把自己的项目从能跑提升到有亮点我特别推荐三个方向第一个是数据可视化。用一个简单的统计页面用Chart.js或者ECharts展示每月营收折线图、宠物品种分布饼图、医生接诊量柱状图。这个功能不需要改后端只需要加一个查询接口返回JSON数据前端用Ajax拉取再渲染。但视觉效果提升非常明显打分时的哇效果比纯表格强十倍。第二个是导出入院小结或者就诊记录的PDF。用iText库把病历数据按照固定模板生成一个PDF。这个功能在真实医院系统里是非常常用的课设里加上绝对属于亮点。第三个是简单的消息通知。当客户的预约被确认或者宠物住院状态变更时在系统首页展示一条站内消息。这个实现起来不难一张通知表和一对查询就够了却能明显提升系统的完成度。7.4 学习节奏建议四阶段推进最后分享一下我自己做课设的时间安排特别适合做这类项目第一阶段一天。梳理需求、设计表结构、画页面草图。这个阶段决定了你后面的所有工作量第二阶段两天。搭项目骨架写DAO层和工具类跑通数据库连接。把基础打牢后面才能快第三阶段四天。按模块从简到繁推进。我的顺序是客户→宠物→预约→病历→处方→住院→统计第四阶段两天。美化页面、补异常处理、模拟真人操作流程测试、准备答辩问题整体下来大约两周。如果你只有一周时间也可以压缩但至少要保证先把客户、宠物、预约这三个基础模块跑通因为后面所有功能都建立在它们之上。8. 登录权限、会话管理和数据校验答辩时最容易被追问的三个细节这一章讲三个容易被忽略但特别受评审老师关注的技术细节。很多同学的代码功能全绿但答辩时在安全性和健壮性上丢了分非常可惜。8.1 用Filter实现登录拦截Servlet的标准方案是用Filter做登录拦截而不是在每个Servlet里复制粘贴判断代码。实现方式一个LoginFilter在doFilter里判断Session中是否存在登录用户同时放行登录页面、静态资源其余请求一律重定向到登录页。WebFilter(/*) public class LoginFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) resp; HttpSession session request.getSession(false); String uri request.getRequestURI(); String contextPath request.getContextPath(); boolean isLoginPage uri.endsWith(login.jsp) || uri.contains(/login); boolean isStatic uri.startsWith(contextPath /static/); boolean isLoggedIn session ! null session.getAttribute(user) ! null; if (isLoginPage || isStatic || isLoggedIn) { chain.doFilter(request, response); } else { response.sendRedirect(contextPath /login.jsp); } } }这个Filter一加系统就安全了一大截。注意放行静态资源不然CSS和JS全被拦了页面变成一团乱。8.2 密码存储至少做一次加盐哈希系统里要有管理员账号就涉及密码存储。我建议不要明文存密码至少用加盐的SHA-256哈希存储。用Java自带的MessageDigest就能实现不需要额外引库。加盐就是把一串随机字符串拼到密码后面再哈希即使两张表密码一样哈希结果也完全不同。有一种非常常见的错误是把哈希算法写成一个工具类里的静态方法然后注册时调用一次、登录时调用一次。这个思路没问题但要注意登录校验必须重新拼接同样的盐再做哈希比对盐值建议存在用户表的一列里每行独立生成。8.3 服务端校验和页面校验要一起做前端用JS做表单校验可以提升体验但安全问题只能靠服务端校验兜底。比如年龄字段前端限制输入数字但恶意请求可以绕过前端直接提交。后端一定要重新判断非数字的全部拒绝或者转成默认值。我的做法是在Service层做参数合法性检查不满足的直接抛业务异常由Servlet统一捕获并返回提示。还有一个细节金额运算不要用double。处方和收费里的金额计算看起来只是乘以数量但浮点误差在累计之后会出问题。用BigDecimal并调用setScale(2, RoundingMode.HALF_UP)来保证两位小数。这个点在银行、医院系统中是底线级别的规范写出来非常加分。9. 从课程设计到真实系统这套代码还能往哪些方向演进如果你的目标不只是交差而是希望这个项目能成为简历上的一个真实作品那下面这几个演进方向非常值得花时间做。第一把JDBC换成MyBatis-Plus。代码量瞬间减半但这不意味着之前的JDBC代码白写了。恰恰相反你有了手写JDBC的基础才能理解ORM框架到底帮你做了什么遇到复杂查询时才知道框架的局限在哪里。这个演进方向最适合写在简历的项目技术升级一栏。第二把系统拆分成前后端分离。后端用Servlet写REST接口返回JSON前端用Vue或者原生HTMLAjax做页面交互。这个改动涉及所有页面的重构但做完之后你的项目形态就非常接近企业实际开发模式了面试时能聊的话题也更多。第三引入Redis做缓存和会话管理。比如药品字典、医生列表这种不常变的数据可以缓存到Redis里降低数据库压力。Session也可以抽到Redis里这样重启Tomcat用户不用重新登录。这个演进方向适合作为亮点在面试时讲。我说说真实业务中这套系统还能做什么。真实的宠物医院系统需要有短信通知功能预约前一天给主人发提醒需要对接电子支付扫码结算需要开发小程序端让宠物主人可以查就诊记录、在线挂号。这些都是在课程设计基础上延伸出来的。但你要明白一件事课程设计最重要的是把核心链路走通把基础功打扎实。项目能写得多大永远不是最重要的最重要的是你对自己写下的每一行代码都能解释清楚。我自己回头看这类项目最大的收获其实不是技术而是一种从零搭建一个完整系统的全局感。你会理解数据驱动页面、状态驱动流程、异常兜底设计这些都是工作中真正的核心竞争力。如果让我给一个实操上的建议把整个项目亲手从空文件敲一遍不要复制粘贴任何一段代码。这个过程会逼着你把每一个细节过脑子而不是手指划过就算学会了。做完之后你可以把源码整理好放GitHubREADME里写清楚系统架构、技术栈、启动步骤和演示截图。对课设来说这份输出已经超过九成的人。最后说一个坚持了很多次的小习惯项目里每个Servlet的类注释上写下这个对象负责哪些功能、依赖哪些Service、有哪些注意事项。这段注释在三天后、三周后甚至答辩前夜再看时能帮你快速找回上下文。接手过任何一个稍微老一点的项目你就会知道文档比你想的值钱得多。