十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Spring Boot毕设实战:猪疾病信息查询系统开发全流程解析

Spring Boot毕设实战:猪疾病信息查询系统开发全流程解析 每年到这个时间点私信里问得最多的就是毕设有没有现成的Spring Boot项目说实话网上打着“源码打包送”旗号的资源我下载过不少真正能一次跑起来的没几个就算跑起来了答辩时被老师一句“你这个JpaRepository为什么不用写实现类”问倒的一抓一大把。所以这篇我不打算给你贴一个所谓“完整源码”的下载链接而是以“猪疾病信息查询系统”为例把从选题思路、技术选型、目录规范到核心代码实现的整条链路讲明白。你要做的不是复制粘贴而是把它变成自己能讲清楚的项目。系统本身不复杂就是一个典型的信息管理场景用户输入关键词系统从数据库里把对应的猪病信息查出来管理员在后台维护数据。它非常适合Spring Boot初学者作为毕业设计也适合想正经了解MVC四层架构怎么落地的人。1. 项目整体设计与思路拆解1.1 为什么选“猪疾病信息查询系统”当毕设题目每年选毕设题目的时候大家最怕两件事一是题目太偏参考资料少做着做着卡住二是题目太烂大街和全班几十个人撞车答辩老师看了就烦。猪疾病信息查询系统刚好卡在一个不错的平衡点上。从需求侧看它属于典型的信息管理系统核心操作就是增删改查加搜索技术边界非常清楚。哪怕你之前没做过完整项目也能够在两三周内把主流程写出来。从选题侧看带上“猪疾病”这个垂直场景后整个项目就有了明确的业务故事养殖户需要快速查询猪的常见病了解症状和防治方法管理员需要维护疾病数据。这个业务故事让系统的每个功能模块都显得合理不是硬凑功能。另外这个题目的扩展空间也很大。想做得好一点可以加重疫病预警、按季节统计发病率想做得轻一点只做查询加后台管理也完全够写一篇像样的论文。相比商城、博客这种“做烂了”的题目它更容易给答辩老师留下“这个学生有想法”的印象。1.2 技术选型Spring Boot JPA Thymeleaf还是前后端分离每次带实训我都反复强调一句毕设的技术选型优先选你熟悉且能讲清楚的方案而不是选看起来最酷的方案。这个系统后端主框架用Spring Boot这一点没什么好纠结的。Spring Boot是目前Java后台开发的绝对主流简历上写出来有分量而且它对配置做了大量自动装配对新手极其友好。ORM层我建议用Spring Data JPA而非MyBatis原因有三个第一JPA的Repository接口直接提供findAll、findById、save、deleteById等现成方法CRUD业务几乎不用写SQL开发效率高很多。第二查询方法的命名规则非常直观比如findByDiseaseNameContainingSpring Data JPA会自动解析成模糊查询代码可读性极强。第三实体类用它来做对象关系映射你只要把对应关系标清楚比MyBatis写一堆XML的维护成本低。前端渲染方案我选了Thymeleaf服务端模板而不是Vue做前后端分离。这里不是说Vue不好而是对单人完成的毕设来说服务端渲染能省掉一大堆接口联调工作。你不需要处理跨域、不需要单独搞一套Node环境、不需要纠结Token怎么存写好Controller后直接返回视图名页面数据由模板引擎渲染。数据库用MySQL 5.7或8.0都行。前者更稳定后者对JSON类型支持更好。考虑到毕设只存疾病信息、分类信息和管理员信息数据量很小两者没有实质差别。1.3 系统功能模块划分与角色设计在动手写代码之前先想清楚系统有几种角色、各角色能干什么否则很容易在开发到一半的时候到处加功能把代码结构搞乱。这个系统的用户角色分两类普通用户养殖户、学生、访客进入首页后可以按疾病名称、病原体、症状进行模糊搜索也可以按疾病分类浏览点击进去能看到完整详情。管理员登录后台后可以维护疾病信息、疾病分类包括新增、编辑、删除、上架下架等操作。功能模块拆开来看是四块疾病信息查询模块、疾病分类展示模块、防疫知识库模块、后台管理模块。疾病信息查询是核心中的核心这决定了它不是一个单纯展示静态页面的网站而是真正和数据库交互的动态系统。查询面板支持按关键字搜索也就是热搜词里频繁出现的“模糊查询”。分类展示模块则提供了一个“按图索骥”的路径比如用户不确定病名但怀疑是“细菌性”的就可以直接点进去看这一分类下有哪些病。防疫知识库是系统的加分项不是必需项但建议加上。它能让系统看起来更像一个完整的知识服务平台而不是一个简单的数据表。后台管理模块则承担了数据的日常维护工作。2. 核心细节解析与实操要点2.1 Spring Boot四层架构与目录规范很多同学拿到一份源码第一件事是找启动类第二件事就是找Controller然后一路点进去看页面效果。代码能跑但这不代表你懂了。真正在答辩时能讲清楚的核心是Spring Boot的四层架构。我先说一个典型的请求流转过程用户在浏览器输入一个搜索关键词点击查询请求会先到达ControllerController收到参数后调用Service层的方法Service层做业务逻辑处理比如判断关键词是否为空、是否要做分页然后调用Repository层Repository层通过JPA与数据库交互把结果返回给ServiceService再返回给ControllerController把数据塞进Model后交给Thymeleaf模板渲染成HTML最终显示在用户浏览器上。对应的目录结构我建议按这个规范建com.example.pigdisease ├── PigDiseaseApplication.java // 启动类 ├── controller/ // 控制器接收请求、返回页面 │ ├── DiseaseController.java // 前台查询相关 │ └── AdminController.java // 后台管理相关 ├── service/ // 业务接口 │ └── DiseaseService.java ├── service/impl/ // 业务实现类 │ └── DiseaseServiceImpl.java ├── repository/ // 数据访问层 │ └── DiseaseRepository.java ├── entity/ // 实体类 │ ├── Disease.java │ ├── DiseaseCategory.java │ └── Admin.java ├── config/ // 配置类静态资源映射、拦截器 │ └── WebConfig.java ├── common/ // 公共类返回结果、常量 │ └── Result.java └── resources/ ├── application.yml ├── static/ // 静态资源CSS、JS、图片 ├── templates/ // 模板页面 │ ├── index.html │ ├── detail.html │ └── admin/ └── data.sql // 初始化数据为什么一定要分Controller、Service、Repository这三层有一个很现实的理由答辩时老师几乎必问“你为什么要这样分层”。从技术角度说Controller只负责参数接收和视图转发Service负责业务规则Repository负责数据访问职责单一、互不干扰。将来想更换数据库只需要改Repository层的实现想调整某个业务规则也只动Service层不会牵一发而动全身。从工程角度看这种分层让代码的可读性大幅提升。你一个类不要写超过200行否则期末你自己都懒得看。2.2 JpaRepository到底怎么用这个点值得单独拿出来说因为太多初学者拿到源码后看着那个空空荡荡的接口一脸懵就定义了一个接口继承了JpaRepository怎么就能增删改查了先看代码package com.example.pigdisease.repository; import com.example.pigdisease.entity.Disease; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; Repository public interface DiseaseRepository extends JpaRepositoryDisease, Integer { // 按病名模糊查询 PageDisease findByDiseaseNameContaining(String keyword, Pageable pageable); // 按病名或病原体模糊查询 PageDisease findByDiseaseNameContainingOrPathogenContaining(String nameKeyword, String pathogenKeyword, Pageable pageable); // 按分类ID查询 PageDisease findByCategoryId(Integer categoryId, Pageable pageable); // 按分类ID查询且带关键字 PageDisease findByCategoryIdAndDiseaseNameContaining(Integer categoryId, String keyword, Pageable pageable); long countByCategoryId(Integer categoryId); }JpaRepository是一个Spring Data JPA提供的泛型接口第一个泛型参数是实体类类型第二个是主键类型。它内置了一大堆方法findAll()查询全部、findById(id)按主键查、save(entity)新增或更新、deleteById(id)按主键删除、count()统计数量。这些方法的SQL不用你写Spring Data JPA在启动时会自动生成对应的实现。你可能好奇那我只写一个方法名findByDiseaseNameContaining它是怎么知道我要查哪个字段的机制是方法命名解析findBy是固定关键字后面跟的DiseaseName对应实体类中的diseaseName属性Containing代表like也就是SQL里的like %值%。Spring Data JPA启动时会解析方法名生成JPQL查询语句。这里要注意命名必须和实体属性保持一致一旦写错启动项目时就会报属性名找不到的错误。分页查询也是毕设里高频出现的点。Controller拿到pageNum和pageSizeService层构造PageRequestPageRequest pageRequest PageRequest.of(page - 1, size); PageDisease pageResult diseaseRepository.findByDiseaseNameContaining(keyword, pageRequest);PageRequest.of的第一个参数是页码注意从0开始前端传第1页这里要减1。第二个参数是每页条数。返回的Page对象里getContent()拿当前页数据getTotalElements()拿总记录数getTotalPages()拿总页数后端把这些信息塞到Model里前端循环输出就行。2.3 配置文件与初始化数据Spring Boot的一大卖点就是配置简化但简化不等于不用配置。打开resources下的application.yml几个关键项必须搞清楚。server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/pig_disease?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver jpa: hibernate: ddl-auto: update show-sql: true open-in-view: false servlet: multipart: max-file-size: 10MB max-request-size: 10MB thymeleaf: cache: false数据库连接串里的serverTimezone必配否则会报时区错误characterEncodingutf8防止中文乱码。ddl-auto设置为update意思是实体类和表结构不一致时Hibernate自动更新表结构。开发期用这个很方便但要注意它不会删除旧字段只会新增。如果你对表结构很介意可以设置为none然后手动执行SQL脚本。show-sql: true会在控制台打印Hibernate生成的SQL这可以说是学习JPA的最佳辅助工具建议开发期开着上线前再关掉。open-in-view: false是很多教程不爱提但非常重要的一个配置。默认情况下Spring Boot把Open Session In View设为true会导致Controller层还能懒加载查询关联对象虽然方便但会拉长数据库连接占用时间还可能引发LazyInitializationException。做毕设时建议设为false强制你在Service层把要用的数据查好。初始化数据我习惯用resources下的data.sqlSpring Boot启动时检测到脚本会自动执行。可以预先把十余种常见猪疾病分类好填进去比如猪瘟、猪蓝耳病、猪圆环病毒病、猪丹毒、猪肺疫、副猪嗜血杆菌病、猪链球菌病、仔猪黄白痢、猪气喘病、猪附红细胞体病等。这样系统启动后立刻有内容可看演示效果也好。3. 实操过程与核心环节实现3.1 环境准备和项目骨架搭建不要一上来就写代码先把环境确认一遍。JDK版本根据Spring Boot版本来Spring Boot 2.x配JDK 8或11Spring Boot 3.x要求JDK 17以上。我在这套教程默认用Spring Boot 2.7.18 JDK 8这个组合最稳很多学校机器都是这个配置兼容性最好。IDE推荐IntelliJ IDEA社区版就够用。直接用IDEA新建Spring Initializr项目勾选以下依赖Spring WebSpring Data JPAThymeleafMySQL DriverLombokValidation如果你手里已经有一份源码可以不用从头建项目打开源码后先检查这几项第一Maven仓库是否配置了阿里云镜像第二JDK版本和pom.xml里的java.version是否一致第三数据库是否创建好用户名密码是否对得上。我见过很多“不能运行”的源码十有八九都是这三个问题。pom.xml里常用的依赖和版本可以这样参考parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-jpa/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-thymeleaf/artifactId /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency /dependencies3.2 实体类与数据库表设计在建实体类之前先把表结构想清楚。这就像盖房子要先画图纸不然代码写一半再改表牵连一大堆。核心是疾病表、疾病分类表、管理员表。疾病表Diseasepackage com.example.pigdisease.entity; import lombok.Data; import javax.persistence.*; import java.time.LocalDateTime; Data Entity Table(name disease) public class Disease { Id GeneratedValue(strategy GenerationType.IDENTITY) private Integer id; Column(name disease_name, nullable false, length 128) private String diseaseName; ManyToOne(fetch FetchType.EAGER) JoinColumn(name category_id) private DiseaseCategory category; Column(columnDefinition TEXT) private String pathogen; Column(columnDefinition TEXT) private String symptom; Column(columnDefinition TEXT) private String incidence; Column(columnDefinition TEXT) private String diagnosis; Column(columnDefinition TEXT) private String prevention; Column(name image_url, length 255) private String imageUrl; Column(name create_time) private LocalDateTime createTime; }几个字段的说明diseaseName后面加unique可以唯一约束防止重复录入症状、诊断等字段内容可能很长用TEXT类型category用ManyToOne关联分类表JPA会自动生成外键关系。createTime在保存时手工set一下当前时间也可以在PrePersist里自动处理。DiseaseCategorypackage com.example.pigdisease.entity; import lombok.Data; import javax.persistence.*; import java.util.ArrayList; import java.util.List; Data Entity Table(name disease_category) public class DiseaseCategory { Id GeneratedValue(strategy GenerationType.IDENTITY) private Integer id; Column(name category_name, nullable false, length 64) private String categoryName; Column(columnDefinition TEXT) private String description; OneToMany(mappedBy category) private ListDisease diseases new ArrayList(); }Admin表简单一点id、username、password。密码不要明文存用BCrypt加密虽然毕设可能不在意但这属于安全姿势在论文里写上也是加分项。3.3 核心功能实现模糊搜索、分类查询、分页现在进入最核心的部分我按一个请求的实际链路来写这段代码如果你能理解比记住任何现成源码都管用。Controller层package com.example.pigdisease.controller; import com.example.pigdisease.service.DiseaseService; import com.example.pigdisease.vo.DiseaseQueryVO; import org.springframework.data.domain.Page; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; Controller public class DiseaseController { private final DiseaseService diseaseService; public DiseaseController(DiseaseService diseaseService) { this.diseaseService diseaseService; } GetMapping(/) public String index(RequestParam(defaultValue 1) int page, RequestParam(defaultValue 8) int size, RequestParam(required false) String keyword, RequestParam(required false) Integer categoryId, Model model) { PageDiseaseQueryVO diseases diseaseService.queryDiseases(keyword, categoryId, page, size); model.addAttribute(diseases, diseases); model.addAttribute(keyword, keyword); model.addAttribute(categoryId, categoryId); return index; } GetMapping(/disease/{id}) public String detail(PathVariable Integer id, Model model) { model.addAttribute(disease, diseaseService.findById(id)); return detail; } }Service接口public interface DiseaseService { PageDiseaseQueryVO queryDiseases(String keyword, Integer categoryId, int page, int size); DiseaseQueryVO findById(Integer id); }ServiceImpl实现类package com.example.pigdisease.service.impl; import com.example.pigdisease.entity.Disease; import com.example.pigdisease.repository.DiseaseRepository; import com.example.pigdisease.service.DiseaseService; import com.example.pigdisease.vo.DiseaseQueryVO; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; Service public class DiseaseServiceImpl implements DiseaseService { private final DiseaseRepository diseaseRepository; public DiseaseServiceImpl(DiseaseRepository diseaseRepository) { this.diseaseRepository diseaseRepository; } Override public PageDiseaseQueryVO queryDiseases(String keyword, Integer categoryId, int page, int size) { Pageable pageable PageRequest.of(page - 1, size); PageDisease diseasePage; boolean hasKeyword StringUtils.hasText(keyword); boolean hasCategory categoryId ! null categoryId 0; if (hasKeyword hasCategory) { diseasePage diseaseRepository.findByCategoryIdAndDiseaseNameContaining(categoryId, keyword.trim(), pageable); } else if (hasKeyword) { diseasePage diseaseRepository .findByDiseaseNameContainingOrPathogenContaining(keyword.trim(), keyword.trim(), pageable); } else if (hasCategory) { diseasePage diseaseRepository.findByCategoryId(categoryId, pageable); } else { diseasePage diseaseRepository.findAll(pageable); } return diseasePage.map(this::convertToVO); } }这里有一个非常关键的判断逻辑关键字和分类ID组合查询时还要分情况处理不然单独builder查询条件会写得很乱。上面这个if-else分支把“有关键字有分类”“只有关键字”“只有分类”“都没有”四种情况全cover住逻辑清晰答辩时也好解释。convertToVO是实体转View Object的过程为什么要转而不直接返回实体因为有些字段不需要暴露给前端比如createTime我们想显示但不想把整个实体绑定到页面用VO隔离一层更安全。将来想加个点赞数字段也不会污染实体类。前端index.htmlThymeleaf的循环展示div th:eachdisease : ${diseases.content} h3 a th:href{/disease/ ${disease.id}} th:text${disease.diseaseName}疾病名称/a /h3 p病原体span th:text${disease.pathogen}病原体/span/p p主要症状span th:text${disease.symptom}症状/span/p /div div 共 span th:text${diseases.totalElements}0/span 条记录第 span th:text${diseases.number 1}1/span/span th:text${diseases.totalPages}1/span 页 a th:href{/?page ${diseases.number} keyword ${keyword}}上一页/a a th:href{/?page ${diseases.number 2} keyword ${keyword}}下一页/a /div这段页面代码虽然简单但注意分页参数计算Page对象里number从0开始所以显示页码时加1下一页的链接要把当前页page加2因为后端接收页号是从1开始算的。这个细节非常容易写错一旦写错翻到下一页时数据就对不上了。3.4 文件上传图片的实现疾病信息查询系统里疾病图片是刚需。没有图片的列表页会显得特别空答辩演示时观感也不太好。这里用Spring Boot实现一个最稳妥的图片上传方案。先配置上传目录。我建议把图片存在项目根目录下的upload/文件夹中而不是存到数据库里。数据库只保存相对路径。这样好处是数据库表不会变得臃肿图片替换也方便。配置文件application.yml里加上spring: servlet: multipart: max-file-size: 10MB max-request-size: 10MB app: upload-dir: ./upload/写一个文件上传工具类package com.example.pigdisease.common; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; import java.util.UUID; Component public class FileUploadUtil { Value(${app.upload-dir}) private String uploadDir; public String upload(MultipartFile file) { if (file null || file.isEmpty()) { throw new RuntimeException(文件不能为空); } String originalFilename file.getOriginalFilename(); String ext ; if (originalFilename ! null originalFilename.contains(.)) { ext originalFilename.substring(originalFilename.lastIndexOf(.)); } String newFileName UUID.randomUUID().toString().replace(-, ) ext; File dir new File(uploadDir); if (!dir.exists()) { dir.mkdirs(); } try { file.transferTo(new File(dir, newFileName)); } catch (IOException e) { throw new RuntimeException(文件上传失败, e); } return /upload/ newFileName; } }这里有两个很重要的细节。第一文件名不要用用户上传的原始名容易重名覆盖也容易包含非法字符。我用UUID随机生成新名字这样绝对不会重复。第二扩展名要保留否则图片显示不出来。第三transferTo方法的目录必须提前存在所以要先mkdirs。上传成功后返回的是相对路径/upload/xxx.jpg这个路径还需要在配置类里映射到磁盘目录否则浏览器访问不到。package com.example.pigdisease.config; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.io.File; Configuration public class WebConfig implements WebMvcConfigurer { Value(${app.upload-dir}) private String uploadDir; Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceLocations(file: new File(uploadDir).getAbsolutePath() File.separator); } }这个映射的原理要能讲清楚浏览器访问/upload/xxx.jpg时Spring MVC不会去templates或static里找而是根据addResourceHandler的配置把请求转到本地磁盘file路径下对应的文件上。imageUrl字段里保存的/upload/xxx.jpg前端直接可以在th:src里用。3.5 后台管理模块与登录校验后台管理模块用最简单的方式实现一个管理员登录页一个后台页面里面可以做疾病信息新增、编辑、删除、分类管理。不需要把权限做得多复杂但至少要有登录拦截不然谁都能访问后台管理页就说不过去了。登录拦截的核心代码是一个HandlerInterceptorpackage com.example.pigdisease.config; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; Component public class AdminLoginInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object admin request.getSession().getAttribute(admin); if (admin null) { response.sendRedirect(/admin/login); return false; } return true; } }然后在WebConfig里注册这个拦截器指定拦截路径为/admin/**放行登录页和静态资源Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(adminLoginInterceptor) .addPathPatterns(/admin/**) .excludePathPatterns(/admin/login, /admin/doLogin, /css/**, /js/**); }后台新增疾病信息的Controller就不全部展开了核心就是接收表单参数、处理上传图片、组装Disease实体、调用save保存。这里要提醒一句新增和编辑最好共用一个表单页面通过id是否为空来判断是保存还是更新。这样代码量少操作体验也一致。4. 常见问题与排查技巧实录这部分是我整理的高频问题每一个都是我实际带毕设时见过的真实报错。建议把这张表存下来遇到问题先自己对一遍。现象可能原因解决办法启动报端口被占用8080或其他端口被其他程序占用改配置文件server.port或命令行netstat -ano找出占用进程并结束数据库连接失败URL写错、密码错误、MySQL服务未启动确认MySQL服务已启动检查application.yml的url和密码url里加serverTimezoneAsia/Shanghai依赖下载特别慢或失败未配置Maven镜像访问中央仓库太慢在settings.xml中配置阿里云镜像mirrorOf为central或*上传图片报文件过大Spring Boot默认单文件1MB总请求10MB在application.yml配置spring.servlet.multipart.max-file-size和max-request-size页面中文乱码数据库连接没有指定字符编码在jdbc url里加useUnicodetruecharacterEncodingutf8实体类字段映射失败属性名与表字段名不一致使用Column注解显式指定字段名命名规则保持驼峰转下划线save之后实体的id还是null主键没有配置自增策略GeneratedValue(strategy GenerationType.IDENTITY)静态资源或upload图片404没有配置资源映射或路径不对检查WebConfig的addResourceHandlers配置确认磁盘目录存在懒加载报LazyInitializationExceptionopen-in-viewfalse且关联对象是懒加载在Service层用Transactional包裹查询关联查询用JOIN FETCH不必要时改为EAGER分页显示总页数不对PageRequest页码从0开始前端没做换算后端接收page时做page-1返回给前端时做number1除了这些我再分享几个排查经验。第一看控制台日志比瞎猜效率高得多。Spring Boot的报错信息在大部分情况下已经告诉了你错在哪比如找不到属性、找不到表、端口占用都会明确英文提示。很多同学一遇到报错就截图过来问其实只要耐下心读一遍Exception开头那几行90%的问题自己就能解决。第二JPA的show-sql一定要开着。你可能觉得控制台打印SQL很吵但调试时它的价值太大了。查出来的数据不是你想要的直接看SQL就能判断是查询条件写错了还是关联关系写错了。不用debug光看SQL就能解决一半问题。第三data.sql脚本里的表名要和Entity注解里的表名完全一致。大小写和反引号也要注意MySQL在Windows下对表名大小写不敏感但在Linux下敏感。为了保险统一用小写加下划线命名disease_name、category_id、create_time这种。第四上传文件时绝对路径的问题特别容易翻车。开发环境用相对路径./upload/没问题但打成jar包部署后jar文件所在路径可能和你预期不一样。保险的做法是路径用配置文件维护部署时根据服务器环境单独调整不要在代码里写死一个绝对路径。第五密码加密。管理员密码就算在毕设里也不要明文存。Spring Security里推荐用BCryptPasswordEncoder其实不引入Spring Security也能直接用这个类只需要添加spring-security-crypto依赖。注册时加密存储登录时校验原始密码和加密串是否匹配。这个点在论文里写出来导师会觉得你安全意识不错。5. 答辩要点与扩展思路源码能跑只是基本盘拿到好成绩的关键是你能不能把项目讲明白。根据我带毕设的经验答辩现场老师最常问的问题就三类架构上的“为什么”、代码上的“怎么做”、还有场景上的“能不能扩展”。架构问题最常见的就是Spring Boot为什么不用写一堆XML配置这个要讲清楚自动装配的原理。Spring Boot启动时通过spring.factories或EnableAutoConfiguration加载大量自动配置类根据classpath下的依赖和你的配置决定是否生效比如classpath里有H2数据库驱动就自动配置数据源有Thymeleaf就自动配置模板引擎。JpaRepository为什么不需要写实现类答案是因为Spring Data JPA会在运行时动态生成代理实现。容器启动时扫描Repository接口使用JDK动态代理为每个接口方法生成调用逻辑。实体字段映射则通过方法命名解析完成。代码问题经常问上传的图片存在哪里一定不要只说“存在服务器上”。要说清楚文件存在磁盘upload目录数据库imageUrl字段只保存相对访问路径浏览器通过/upload/资源映射读取文件。文件本身不进数据库这样避免数据库体积膨胀。扩展建议里比较稳妥的方向有三个。一是增加图表统计用ECharts按月份展示疾病发生数量给系统加上“疾病趋势分析”模块。二是引入Redis做热门疾病缓存搜索前先查缓存缓存不命中再查数据库算是高性能优化思路。三是把系统改成API模式把Thymeleaf换成Vue前端后端只提供JSON接口这个改动比较大但如果论文里篇幅不够扩可以把这个当成后续工作来写。扩展功能不要贪多。一个能稳定运行、逻辑清晰、能讲清楚设计和问题的项目价值远高于一堆没跑通的炫酷功能。答辩老师其实不太在乎功能多不多更在乎你是不是真的理解自己写的代码。最后再分享一个人的小经验拿到别人源码后不要直接用IDEA打开就启动先花一天时间把整个项目目录点一遍弄清楚每个目录什么作用再手动在纸上画一遍请求流程从浏览器地址栏输入URL开始经过哪些类、哪些方法直到页面渲染完成。这个过程做完你就已经把源码变成自己的了。以后不管面试还是工作这种从请求链路上理解项目的习惯会让你少踩很多坑。
返回列表