拓冰建站拓冰建站
首页 / 资讯中心 / 正文

Java毕业设计实战:基于Spring Boot的宠物医院管理系统开发全流程解析

简介在Java Web开发领域Spring Boot框架因其简化配置、快速启动的特性已成为构建企业级应用的主流选择。其核心原理在于通过自动配置和起步依赖极大降低了传统SSM框架的整合复杂度使开发者能更专注于业务逻辑的实现。这一技术价值在于显著提升了开发效率与项目可维护性尤其适用于需要快速交付的中小型管理系统开发。在应用场景上诸如客户关系管理、库存系统、信息门户等业务关系明确的项目都能从Spring Boot的敏捷开发模式中受益。本文以宠物医院综合信息管理系统为例深入剖析了从需求分析、技术选型到数据库设计与核心功能实现的完整流程其中重点讲解了如何使用MyBatis-Plus高效操作数据库并详细阐述了通过JWT实现安全认证的工程实践为面临Java毕业设计的学生提供了一套可复用的方法论与避坑指南。1. 项目概述一个典型的Java毕业设计如何炼成最近在整理资料翻到了几年前带学生做的一个毕业设计项目名字叫“兽爱医疗专家”。这名字听起来有点意思对吧本质上它是一个面向宠物医院的综合信息管理系统。今天我不打算只丢给你一个压缩包而是想把这个项目从“一个题目”到“一套完整交付物”的全过程掰开揉碎了讲清楚。如果你正卡在Java毕业设计的某个环节或者对如何把课堂上学到的SSM、Spring Boot这些框架真正用到一个有模有样的系统里感到迷茫那这篇分享或许能给你一些实实在在的参考。这个项目包含了毕业设计要求的几乎所有核心产出物可运行的Java源代码、符合规范的毕业论文、用于答辩展示的PPT模板以及配套的数据库脚本。它解决的核心问题是帮助中小型宠物医院实现从客户管理、宠物档案、诊疗记录到药品库存、财务统计的数字化管理把医生从繁琐的纸质记录中解放出来提升运营效率。无论你是计算机、软件工程还是信息管理专业的学生只要你的技术栈是Java Web这一套这个项目的架构和实现思路都具有很强的通用性。接下来我会围绕这个项目详细拆解从需求分析、技术选型、数据库设计、核心功能实现到论文撰写、答辩准备的全流程并附上大量我在指导过程中总结的“踩坑”经验和避雷指南。2. 项目整体设计与技术栈选型思路2.1 需求分析与业务模型构建接到“宠物医院管理系统”这个题目第一步不是急着打开IDE写代码而是要把模糊的需求具体化。我们通过模拟访谈宠物医院经营者梳理出了几个核心业务模块前台业务包括宠物主人的注册登录、宠物档案的建立与查询、预约挂号、诊疗记录的查看与支付。这部分是系统的门面强调用户体验和操作的便捷性。诊疗核心医生工作站涵盖接诊登记、开具处方关联药品库存、记录检查结果、完成病历书写。这是系统的核心业务逻辑最复杂涉及多个实体的状态流转。后台管理包括员工医生、护士、前台管理、药品与耗材的进销存管理、财务收支统计、系统权限控制。这部分保障系统的稳定运行和数据安全。基于这些我们抽象出关键实体用户分角色、宠物、病历、处方、药品、库存、订单等。这里的一个关键设计决策是病历与处方的分离。一份病历记录症状、诊断、检查结果可以对应多张处方药品、处置项目而一张处方又可以关联多种药品。这样设计的好处是结构清晰便于后续的统计查询例如统计某种药品的使用频率。2.2 技术栈选型背后的“为什么”技术选型直接决定了开发效率和项目的可维护性。对于毕业设计而言选型的核心原则是主流、稳定、资料丰富、便于演示。后端Spring Boot MyBatis-Plus。放弃传统的SSH或SSM整合直接使用Spring Boot。理由很简单它简化了配置内置Tomcat让你能通过一个main方法就能启动项目特别适合答辩演示。MyBatis-Plus是对MyBatis的增强其强大的通用Mapper和条件构造器能减少大量简单的CRUD SQL编写把精力集中在业务逻辑上。前端Thymeleaf Bootstrap jQuery。没有选用Vue或React等前后端分离框架。虽然分离是主流但对于毕业设计尤其是单人开发前后端不分离的架构更简单直接。Thymeleaf模板引擎与Spring Boot无缝集成Bootstrap能快速搭建出美观、响应式的界面jQuery处理页面交互足矣。这能让你在有限时间内更专注于功能实现而非架构复杂度。数据库MySQL 5.7。毫无疑问的关系型数据库首选。社区活跃工具丰富如Navicat, Workbench对于宠物医院这种业务关系明确的项目用MySQL建模非常合适。开发工具IntelliJ IDEA Maven。IDEA在Java开发领域的体验优势明显其智能提示和代码分析能帮你避免很多低级错误。Maven进行依赖管理让项目结构标准化。其他关键依赖Lombok通过注解自动生成Getter/Setter、构造方法等让实体类代码非常简洁。Hutool一个国产Java工具类库提供了字符串处理、日期、加密、IO等众多实用方法能极大提升开发效率。PageHelper实现后端分页的利器与MyBatis整合方便避免手动计算分页参数。注意技术选型要与自身能力匹配。如果你对前后端分离和前端框架不熟强行上VueSpring Boot可能会在联调、部署上花费过多时间导致核心功能完成度不高。毕业设计评审首先看的是功能完整性和系统稳定性。3. 数据库设计与核心表结构解析数据库设计是系统的基石设计得好后续开发事半功倍。我们采用PowerDesigner进行概念模型和物理模型设计这里展示几个核心表及其关联。3.1 核心实体表设计用户表 (sys_user)这是系统的入口。我们采用经典的RBAC基于角色的访问控制模型。CREATE TABLE sys_user ( user_id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 用户ID, username varchar(50) NOT NULL COMMENT 用户名, password varchar(100) NOT NULL COMMENT 密码, real_name varchar(50) DEFAULT NULL COMMENT 真实姓名, phone varchar(20) DEFAULT NULL COMMENT 手机号, user_type char(2) DEFAULT 01 COMMENT 用户类型01系统用户 02客户, status char(1) DEFAULT 0 COMMENT 帐号状态0正常 1停用, create_time datetime DEFAULT NULL COMMENT 创建时间, PRIMARY KEY (user_id), UNIQUE KEY uniq_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户信息表;关键点user_type字段区分了后台管理员医生、护士等和前台客户宠物主人。密码字段password长度设为100是为了使用Spring Security的BCryptPasswordEncoder进行加密存储加密后的字符串较长。宠物表 (pet_info)CREATE TABLE pet_info ( pet_id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 宠物ID, pet_name varchar(50) NOT NULL COMMENT 宠物名称, owner_id bigint(20) NOT NULL COMMENT 主人ID关联sys_user, user_type02, species varchar(50) DEFAULT NULL COMMENT 物种狗、猫等, breed varchar(50) DEFAULT NULL COMMENT 品种, birthday date DEFAULT NULL COMMENT 出生日期, weight decimal(5,2) DEFAULT NULL COMMENT 体重kg, avatar varchar(255) DEFAULT NULL COMMENT 头像图片路径, health_notes text COMMENT 健康备注, PRIMARY KEY (pet_id), KEY idx_owner (owner_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT宠物信息表;关键点owner_id外键关联到sys_user表建立了宠物与主人的关系。weight字段使用decimal(5,2)类型精确到小数点后两位适合体重记录。病历主表 (medical_record)CREATE TABLE medical_record ( record_id varchar(32) NOT NULL COMMENT 病历ID使用UUID, pet_id bigint(20) NOT NULL COMMENT 宠物ID, doctor_id bigint(20) NOT NULL COMMENT 接诊医生ID, visit_time datetime NOT NULL COMMENT 就诊时间, symptoms text COMMENT 症状描述, diagnosis text COMMENT 诊断结果, advice text COMMENT 医嘱, create_time datetime DEFAULT NULL COMMENT 创建时间, PRIMARY KEY (record_id), KEY idx_pet (pet_id), KEY idx_doctor (doctor_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT病历主表;关键点record_id没有使用自增ID而是采用UUID。这是因为病历编号有时需要暴露给用户如打印病历使用无规律且唯一的UUID比连续的自增ID更合适。pet_id和doctor_id都是外键。3.2 关联表与业务逻辑表处方表 (prescription)CREATE TABLE prescription ( prescription_id bigint(20) NOT NULL AUTO_INCREMENT, record_id varchar(32) NOT NULL COMMENT 关联的病历ID, drug_id bigint(20) NOT NULL COMMENT 药品ID, quantity int(11) NOT NULL COMMENT 数量, dosage varchar(100) DEFAULT NULL COMMENT 用法用量, frequency varchar(50) DEFAULT NULL COMMENT 用药频率, PRIMARY KEY (prescription_id), KEY idx_record (record_id), KEY idx_drug (drug_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT处方明细表;这张表体现了病历与处方的多对多关系通过中间表prescription实现。一份病历可以有多个处方项一个处方项对应一种药品。药品库存表 (drug_stock)CREATE TABLE drug_stock ( drug_id bigint(20) NOT NULL AUTO_INCREMENT, drug_name varchar(100) NOT NULL COMMENT 药品名称, specification varchar(100) DEFAULT NULL COMMENT 规格, manufacturer varchar(100) DEFAULT NULL COMMENT 生产厂家, stock_quantity int(11) NOT NULL DEFAULT 0 COMMENT 当前库存数量, warning_quantity int(11) NOT NULL DEFAULT 10 COMMENT 库存预警数量, unit_price decimal(10,2) NOT NULL COMMENT 单价, status char(1) DEFAULT 0 COMMENT 状态0正常 1停用, PRIMARY KEY (drug_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT药品库存表;关键点stock_quantity和warning_quantity是实现库存预警功能的基础。在开具处方时需要检查库存是否充足这是一个典型的业务逻辑校验点。实操心得数据库设计的几个坑字符集统一建表时务必显式指定CHARSETutf8mb4而不是依赖数据库默认设置。utf8mb4才是真正的UTF-8能存储emoji等字符避免未来出现乱码。索引不是越多越好在pet_id,owner_id,record_id等经常用于查询和关联的字段上建立索引是必要的。但像status这种只有几个枚举值的字段加索引效果甚微反而影响写入速度。字段注释一定要写这是好习惯。在生成数据库设计文档时这些注释会自动成为字段说明省去大量沟通成本。在MyBatis-Plus中通过注解也能将注释映射到实体类属性上。关于外键在业务系统中我们通常在应用层维护逻辑外键约束而不是在数据库层使用物理外键。物理外键会影响大批量数据操作的性能并且在分库分表时很麻烦。但在毕业设计中使用物理外键可以让表关系更直观且MySQL的InnoDB引擎对其支持良好可以根据情况选择。4. 后端核心功能模块实现详解有了清晰的数据库设计后端开发就是按图索骥。我们采用经典的三层架构Controller - Service - Mapper。4.1 用户认证与权限控制实现这是系统的安全大门。我们采用Spring Security JWTJSON Web Token的方案实现无状态的认证。1. 核心配置类 (SecurityConfig)Configuration EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { Autowired private UserDetailsServiceImpl userDetailsService; Autowired private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter; Bean public PasswordEncoder passwordEncoder() { // 使用BCrypt强哈希加密 return new BCryptPasswordEncoder(); } Override protected void configure(HttpSecurity http) throws Exception { http // 关闭CSRF和Session因为用JWT .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() // 放行登录、注册等接口 .antMatchers(/user/login, /user/register).permitAll() // 静态资源放行 .antMatchers(/css/**, /js/**, /images/**).permitAll() // 其他所有请求都需要认证 .anyRequest().authenticated(); // 在UsernamePasswordAuthenticationFilter之前添加JWT过滤器 http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); } Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } }2. JWT过滤器 (JwtAuthenticationTokenFilter)这个过滤器的任务是解析HTTP请求头中的Token并恢复用户身份。Component public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { Autowired private JwtUtil jwtUtil; Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { // 1. 从请求头中获取token String token request.getHeader(Authorization); if (StringUtils.hasText(token) token.startsWith(Bearer )) { token token.substring(7); // 2. 解析token获取用户名 String username jwtUtil.getUsernameFromToken(token); // 3. 如果用户名不为空且SecurityContext中没有认证信息 if (username ! null SecurityContextHolder.getContext().getAuthentication() null) { // 4. 从数据库或缓存中加载用户详情 UserDetails userDetails this.userDetailsService.loadUserByUsername(username); // 5. 验证token是否有效未过期、用户信息匹配 if (jwtUtil.validateToken(token, userDetails)) { // 6. 创建认证令牌并设置到SecurityContext中 UsernamePasswordAuthenticationToken authenticationToken new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authenticationToken); } } } chain.doFilter(request, response); } }3. 登录接口 (AuthController)RestController RequestMapping(/auth) public class AuthController { Autowired private AuthenticationManager authenticationManager; Autowired private JwtUtil jwtUtil; PostMapping(/login) public Result login(RequestBody LoginRequest loginRequest) { // 1. 使用AuthenticationManager进行认证 Authentication authentication authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()) ); // 2. 认证成功将认证信息存入SecurityContext SecurityContextHolder.getContext().setAuthentication(authentication); // 3. 生成JWT Token String jwt jwtUtil.generateToken(authentication); // 4. 返回Token和用户基本信息 MapString, Object result new HashMap(); result.put(token, jwt); result.put(user, authentication.getPrincipal()); return Result.success(登录成功, result); } }注意事项JWT使用的安全细节Token过期时间在JwtUtil中通过setExpiration设置一个合理的过期时间如2小时。过期后前端需要引导用户重新登录或使用Refresh Token机制。密钥保管用于签名Token的密钥Secret必须足够复杂且不能硬编码在代码中。应该放在配置文件如application.yml中并通过环境变量注入。注销问题JWT是无状态的服务端无法直接让一个已签发的Token失效。常见的解决方案是使用一个短期的Token过期时间并结合一个“黑名单”缓存如Redis来存储需要提前失效的Token。对于毕业设计如果要求不高可以只依赖Token过期。4.2 业务逻辑层病历创建与处方开具这是业务最复杂的部分涉及事务管理和库存校验。Service层实现 (MedicalRecordServiceImpl)Service Transactional(rollbackFor Exception.class) // 声明式事务遇到任何异常都回滚 public class MedicalRecordServiceImpl implements MedicalRecordService { Autowired private MedicalRecordMapper medicalRecordMapper; Autowired private PrescriptionMapper prescriptionMapper; Autowired private DrugStockMapper drugStockMapper; Override public Result createMedicalRecord(MedicalRecordDTO recordDTO) { // 1. DTO数据校验使用Validation注解或手动校验 if (recordDTO.getPetId() null || recordDTO.getDoctorId() null) { return Result.error(宠物和医生信息不能为空); } // 2. 生成病历IDUUID String recordId UUID.randomUUID().toString().replace(-, ); MedicalRecord record new MedicalRecord(); BeanUtils.copyProperties(recordDTO, record); record.setRecordId(recordId); record.setCreateTime(new Date()); // 3. 保存病历主信息 medicalRecordMapper.insert(record); // 4. 处理处方列表核心业务逻辑 ListPrescriptionDTO prescriptionList recordDTO.getPrescriptionList(); if (prescriptionList ! null !prescriptionList.isEmpty()) { for (PrescriptionDTO dto : prescriptionList) { // 4.1 检查药品库存是否充足 DrugStock drug drugStockMapper.selectById(dto.getDrugId()); if (drug null || drug.getStatus().equals(1)) { throw new BusinessException(药品[ dto.getDrugName() ]不存在或已停用); } if (drug.getStockQuantity() dto.getQuantity()) { throw new BusinessException(药品[ drug.getDrugName() ]库存不足当前库存 drug.getStockQuantity()); } // 4.2 扣减库存悲观锁或乐观锁控制并发 int updateCount drugStockMapper.decreaseStock(dto.getDrugId(), dto.getQuantity()); if (updateCount 0) { // 扣减失败可能库存已被其他事务修改 throw new BusinessException(药品库存扣减失败请重试); } // 4.3 保存处方明细 Prescription prescription new Prescription(); BeanUtils.copyProperties(dto, prescription); prescription.setRecordId(recordId); prescriptionMapper.insert(prescription); } } // 5. 记录操作日志可选 // logService.recordLog(...); return Result.success(病历创建成功, recordId); } }关键点解析事务管理Transactional注解确保了“保存病历”和“扣减库存”这两个操作在一个事务中要么全部成功要么全部回滚。这避免了病历创建成功但库存没扣减的数据不一致情况。库存校验与扣减这是一个典型的“读-改-写”并发场景。如果两个医生同时为同一种药品开处方可能造成超卖。这里decreaseStock方法内部使用了数据库的乐观锁或悲观锁机制。乐观锁实现示例在drug_stock表增加一个version版本号字段。UPDATE drug_stock SET stock_quantity stock_quantity - #{quantity}, version version 1 WHERE drug_id #{drugId} AND stock_quantity #{quantity} AND version #{version}执行后检查影响行数updateCount如果为0说明库存不足或版本号不对数据已被修改则抛出异常事务回滚。异常处理我们自定义了BusinessException继承自RuntimeException在事务中抛出会触发事务回滚。前端会收到统一的错误信息。4.3 数据持久层MyBatis-Plus的高效使用MyBatis-Plus极大地简化了DAO层的代码。1. 实体类与Mapper接口Data // Lombok注解自动生成getter/setter等 TableName(medical_record) // 指定表名 public class MedicalRecord { TableId(value record_id, type IdType.INPUT) // 主键非自增 private String recordId; private Long petId; private Long doctorId; private Date visitTime; private String symptoms; private String diagnosis; private String advice; private Date createTime; } // Mapper接口继承BaseMapper即可获得CRUD方法 public interface MedicalRecordMapper extends BaseMapperMedicalRecord { // 可以在此定义自定义的复杂SQL方法 ListMedicalRecord selectByPetIdAndDate(Param(petId) Long petId, Param(startDate) Date startDate, Param(endDate) Date endDate); }2. 自定义SQL的XML映射对于复杂的多表关联查询如“查询某宠物近一年的所有病历及处方详情”需要在MedicalRecordMapper.xml中编写SQL。?xml version1.0 encodingUTF-8? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.shouai.mapper.MedicalRecordMapper select idselectByPetIdAndDate resultMapMedicalRecordWithPrescription SELECT mr.*, p.prescription_id, p.drug_id, p.quantity, p.dosage, p.frequency, ds.drug_name, ds.specification FROM medical_record mr LEFT JOIN prescription p ON mr.record_id p.record_id LEFT JOIN drug_stock ds ON p.drug_id ds.drug_id WHERE mr.pet_id #{petId} AND mr.visit_time BETWEEN #{startDate} AND #{endDate} ORDER BY mr.visit_time DESC /select resultMap idMedicalRecordWithPrescription typecom.shouai.entity.MedicalRecord extendsBaseResultMap !-- 一对多关联处方 -- collection propertyprescriptionList ofTypecom.shouai.entity.Prescription columnPrefixp_ id propertyprescriptionId columnprescription_id/ result propertydrugId columndrug_id/ result propertyquantity columnquantity/ result propertydosage columndosage/ result propertyfrequency columnfrequency/ !-- 处方关联的药品信息一对一 -- association propertydrugStock javaTypecom.shouai.entity.DrugStock id propertydrugId columndrug_id/ result propertydrugName columndrug_name/ result propertyspecification columnspecification/ /association /collection /resultMap /mapper关键点resultMap的灵活使用是MyBatis处理复杂结果集映射的核心。通过collection和association标签可以轻松地将一条SQL查询结果映射成嵌套的Java对象避免在代码中进行繁琐的数据组装。5. 前端页面交互与功能实现前端采用Bootstrap 4构建响应式布局Thymeleaf渲染数据jQuery处理Ajax请求。5.1 病历管理列表页与分页Controller层返回视图和数据Controller RequestMapping(/medical) public class MedicalRecordController { Autowired private MedicalRecordService medicalRecordService; GetMapping(/list) public String list(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, Model model) { // 使用PageHelper分页 PageHelper.startPage(pageNum, pageSize); ListMedicalRecordVO list medicalRecordService.selectRecordList(new MedicalRecordQuery()); PageInfoMedicalRecordVO pageInfo new PageInfo(list); model.addAttribute(pageInfo, pageInfo); return medical/list; // 对应src/main/resources/templates/medical/list.html } }Thymeleaf模板 (list.html) 关键片段table classtable table-hover thead tr th病历号/th th宠物名/th th主人/th th接诊医生/th th就诊时间/th th操作/th /tr /thead tbody tr th:eachrecord : ${pageInfo.list} td th:text${record.recordId}MR20230520001/td td th:text${record.petName}旺财/td td th:text${record.ownerName}张先生/td td th:text${record.doctorName}李医生/td td th:text${#dates.format(record.visitTime, yyyy-MM-dd HH:mm)}2023-05-20 10:30/td td a th:href{/medical/detail/{id}(id${record.recordId})} classbtn btn-sm btn-info查看/a button classbtn btn-sm btn-warning onclickeditRecord(${record.recordId})编辑/button /td /tr /tbody /table !-- 分页组件 -- div th:insert~{fragments/pagination :: pagination(pageInfo${pageInfo}, url${medical/list})}/div分页组件的复用我们将分页UI抽离成一个Thymeleaf片段fragments/pagination.html通过th:insert在任何需要分页的页面引入提高了代码复用率。5.2 使用Ajax实现动态表单与数据提交在创建病历页面处方药品需要动态添加和删除。JavaScript/jQuery 动态添加处方行let prescriptionIndex 0; function addPrescriptionRow() { const rowHtml tr idprescriptionRow${prescriptionIndex} td select classform-control drug-select nameprescriptionList[${prescriptionIndex}].drugId required option value请选择药品/option !-- 药品选项将通过Ajax动态加载 -- /select /td tdinput typenumber classform-control nameprescriptionList[${prescriptionIndex}].quantity min1 required/td tdinput typetext classform-control nameprescriptionList[${prescriptionIndex}].dosage placeholder如一次一片/td tdinput typetext classform-control nameprescriptionList[${prescriptionIndex}].frequency placeholder如一日两次/td tdbutton typebutton classbtn btn-danger btn-sm onclickremoveRow(${prescriptionIndex})删除/button/td /tr; $(#prescriptionTable tbody).append(rowHtml); // 为新增行的药品下拉框加载数据 loadDrugOptions(prescriptionIndex); prescriptionIndex; } // 加载药品选项 function loadDrugOptions(index) { $.ajax({ url: /drug/list/available, type: GET, success: function(data) { if(data.code 200) { let options option value请选择药品/option; $.each(data.data, function(i, drug) { options option value${drug.drugId}${drug.drugName} (${drug.specification}) - 库存:${drug.stockQuantity}/option; }); $(#prescriptionRow${index} .drug-select).html(options); } } }); }表单提交与结果处理$(#medicalRecordForm).submit(function(e) { e.preventDefault(); // 阻止默认表单提交 const formData $(this).serialize(); // 序列化表单数据 $.ajax({ url: /medical/create, type: POST, data: formData, success: function(response) { if(response.code 200) { alert(病历创建成功病历号 response.data); // 跳转到列表页或详情页 window.location.href /medical/list; } else { alert(创建失败 response.msg); } }, error: function(xhr) { alert(请求异常请检查网络或控制台。); console.error(xhr); } }); });前端开发避坑指南Thymeleaf与JavaScript的变量传递在.html文件中Thymeleaf在服务器端渲染而JavaScript在浏览器端运行。不能直接用Thymeleaf表达式给JS变量赋值复杂对象。正确做法是通过th:attr设置>build plugins plugin groupIdorg.springframework.boot/groupId artifactIdspring-boot-maven-plugin/artifactId /plugin /plugins /build在项目根目录下执行Maven命令mvn clean package -DskipTests打包完成后会在target目录下生成一个兽爱医疗专家-0.0.1-SNAPSHOT.jar文件。2. 准备部署包创建一个独立的部署文件夹例如release包含以下内容/release ├── app.jar # 打包好的Spring Boot JAR文件 ├── application-prod.yml # 生产环境配置文件数据库连接等 ├── sql/ # 数据库初始化脚本 │ ├── init_schema.sql # 建表语句 │ └── init_data.sql # 初始数据管理员账号、基础药品等 ├── start.bat # Windows启动脚本 └── start.sh # Linux/Mac启动脚本start.sh脚本内容示例#!/bin/bash # 指定激活的生产环境配置文件 java -jar app.jar --spring.profiles.activeprod3. 一键启动评委只需要安装Java 8或11运行环境。安装MySQL并执行sql/下的脚本创建数据库。修改application-prod.yml中的数据库连接信息。双击start.bat或运行./start.sh。在浏览器访问http://localhost:8080。7.2 开发与部署中的典型问题排查以下是我在指导过程中学生最常遇到的几个问题及解决方案问题现象可能原因排查步骤与解决方案启动报错APPLICATION FAILED TO START1. 配置错误如数据库连接串2. 端口被占用3. 依赖冲突1. 检查application.yml特别是缩进和冒号后的空格。2. 使用netstat -ano | findstr :8080(Win) 或lsof -i:8080(Mac/Linux) 查看端口占用并结束进程或修改server.port。3. 运行mvn dependency:tree查看依赖树排除冲突的传递依赖。页面访问4041. 控制器请求路径映射错误2. 静态资源路径错误3. Thymeleaf模板文件位置不对1. 检查Controller类的RequestMapping和方法上的GetMapping/PostMapping路径。2. Spring Boot默认静态资源在/static,/public等目录下检查文件是否放对位置。3. Thymeleaf模板默认在/templates下且返回的视图名要匹配。数据库连接失败1. MySQL服务未启动2. 连接URL、用户名、密码错误3. 时区或SSL问题1. 确保MySQL服务已运行。2. 仔细核对application.yml中的url,username,password。3. 在JDBC URL中添加时区参数jdbc:mysql://localhost:3306/shouai_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse页面显示乱码1. 数据库、后端、前端字符集不统一2. HTTP请求/响应未设置编码1. 确保数据库、表、字段均为utf8mb4。2. 在Spring Boot配置中设置spring.http.encoding.charsetUTF-8和spring.http.encoding.enabledtrue。3. 在HTML的head中添加meta charsetUTF-8。事务不回滚1. 异常未被抛出或不是RuntimeException2. 方法不是public3. 在同一个类中方法调用导致AOP失效1. 确保方法抛出Exception或在Transactional中指定rollbackFor。2.Transactional只能用于public方法。3. 避免在类内部调用带Transactional的方法可通过从Spring容器中获取代理对象解决。一个真实的踩坑记录有学生在演示时点击“开具处方”按钮后系统卡住最后报错。经排查是因为在DrugStockMapper.xml的decreaseStock更新语句中WHERE条件写成了stock_quantity #{quantity}应该是导致当处方数量等于库存时更新失败而Service层又没有正确处理这种“更新行数为0”的情况导致事务悬挂。解决方法一是修正SQL二是在Service层严格判断updateCount并给出明确的业务异常提示。这个坑告诉我们边界条件的测试如库存刚好够用和细致的异常处理是多么重要。最后我想说完成一个毕业设计就像完成一个小型的产品研发。它考验的不仅仅是你对某项技术的掌握更是你分析问题、设计系统、编码实现、调试排错、文档表达的综合能力。希望这份基于“兽爱医疗专家”项目的超详细拆解能为你点亮一盏灯。当你自己动手把一个个功能模块搭建起来并最终看到系统流畅运行的那一刻那种成就感远比仅仅复制一份代码要强烈得多。如果在实现过程中遇到具体问题不妨多看看日志善用调试工具你会发现大部分难题都能迎刃而解。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门