多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Spring Boot流浪动物救助平台实战:状态机与审核流程设计

Spring Boot流浪动物救助平台实战:状态机与审核流程设计 简介一份基于Java的流浪动物救助平台设计与实现毕业设计文档面向计算机相关专业学生及Java全栈开发初学者。文档完整呈现了平台从选题背景、系统分析、功能设计到技术实现的全部过程围绕流浪动物信息展示、志愿者风采、在线领养申请、线上募捐等核心模块展开给出了基于SpringBoot、Vue、MySQL及Eclipse等工具的详细实现方案。包内共1个docx文件大小1.48MB内容为完整毕业设计论文包含摘要、目录、正文、结论及参考文献结构规范可直接参考。已有299人学习浏览适合用于毕业设计选题参考、论文模板借鉴或项目开发思路梳理。通过阅读这份文档读者可以掌握流浪动物救助平台的需求分析、数据库设计、前后端交互逻辑等关键技术包括流浪动物信息发布管理、领养申请审核流程、线上募捐功能实现同时也能了解Java体系在公益性信息共享平台中的落地实践为类似系统开发提供完整参照。1. 流浪动物救助平台是什么表面是爱心网站本质是审核状态机把Java课程设计题目清单翻到最后几页流浪动物救助平台通常都会挂在里面。第一眼看上去它像一个“爱心信息站”真正动手后才发现难点全在后台审核流程动物发布要审核、领养申请要审核、救助记录要关联用户任何一个状态没流转对这个平台就退化成只能浏览的静态页面。拆开讲它就是一套基于Spring Boot的多角色Java Web系统前台做展示与申请后台做审核与数据管理。适合正在找java课程设计案例源码参考的从业者也适合想用一个完整业务练手Spring Boot的初学者。下面这套方案从选型、建表到接口和页面基本可以直接照抄。2. 模块拆分与技术选型先定边界再用8张表撑起救助平台2.1 技术栈怎么选Spring Boot MyBatis-Plus Thymeleaf的原因Spring Boot MyBatis-Plus Thymeleaf Bootstrap MySQL是这类题目最常见也最稳的一套组合。为什么不是SSH现在新项目很少碰Struts和Hibernate维护成本高答辩也容易被动。为什么不是前后端分离一个人同时维护Vue工程和Spring Boot工程联调、跨域、Token过期这些事会把工期吃掉大半而流浪动物救助平台根本不需要那么重的交互。为什么不是Spring Cloud这个题目业务量级远没到微服务单体应用足够。模板引擎选Thymeleaf因为Spring Boot默认集成不用单独配JSP那套环境UI层面用Bootstrap能把后台表格和前台卡片快速做整齐。如果你想把项目写进简历技术栈后面再硬加Redis缓存和RabbitMQ削峰会很突兀单机事务在这个场景下足够讲清楚。选MyBatis-Plus的理由很现实分页、条件构造器、逻辑删除都是开箱即用省掉大量重复SQL。java基础扎实的同学可以完全手写MyBatis XML但课程设计这种两到四周工期的题目把省下来的时间拿去做业务闭环更划算。另外这套组合对新手也友好出问题时社区案例多基本搜得到同类报错。2.2 功能模块与角色划分整个平台按两个端来拆就够了角色分普通用户和管理员。未登录访客可以浏览前台列表和详情注册登录后用户能提交领养申请、上报救助线索、登记捐赠、报名志愿者活动。管理员负责动物信息审核、领养审核、救助受理、公告发布和用户管理。核心链路可以画成一条线发现流浪动物 → 用户或管理员登记 → 管理员审核通过 → 前台公开展示 → 用户提交领养申请 → 管理员审核 → 动物状态变为“已领养”。这条链路走通系统主体就完成了。剩下的是公告、捐赠、救助记录、评论这些外围模块用于撑起页面丰富度同时也能把数据库表设计得更有层次答辩时也好解释。2.3 数据库设计8张表把业务边界定死表结构我给出可直接执行的版本。用户表建议叫sys_user不要直接用user后者在MySQL里是保留字建表时常要加反引号对新手是额外麻烦改名最省事。CREATE TABLE sys_user ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(100) NOT NULL COMMENT BCrypt加密后的密码, phone VARCHAR(20) DEFAULT NULL, role TINYINT NOT NULL DEFAULT 0 COMMENT 0普通用户 1管理员, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; CREATE TABLE animal ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL COMMENT 动物昵称, species VARCHAR(20) NOT NULL COMMENT 猫或狗, age VARCHAR(20) DEFAULT NULL COMMENT 年龄描述如2个月, gender TINYINT DEFAULT 0 COMMENT 0未知 1公 2母, health_status VARCHAR(200) DEFAULT NULL COMMENT 健康状况, location VARCHAR(100) DEFAULT NULL COMMENT 发现位置, description TEXT COMMENT 详细描述, photo VARCHAR(200) DEFAULT NULL COMMENT 图片相对路径, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待审核 1已发布 2已领养 3已下架, create_by INT NOT NULL COMMENT 发布人id, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT流浪动物表; CREATE TABLE adoption_application ( id INT AUTO_INCREMENT PRIMARY KEY, animal_id INT NOT NULL COMMENT 申请领养的动物, user_id INT NOT NULL COMMENT 申请人, reason VARCHAR(500) COMMENT 申请理由, phone VARCHAR(20), address VARCHAR(100) COMMENT 居住地址, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待审核 1通过 2拒绝, apply_time DATETIME DEFAULT CURRENT_TIMESTAMP, audit_time DATETIME DEFAULT NULL, audit_remark VARCHAR(200) COMMENT 审核备注, KEY idx_animal (animal_id), KEY idx_user (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT领养申请表; CREATE TABLE rescue_record ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL COMMENT 上报人, animal_id INT DEFAULT NULL COMMENT 关联动物id可为空, rescue_time DATETIME COMMENT 发现时间, location VARCHAR(100) COMMENT 发现地点, description TEXT, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待受理 1已受理 2已完成, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT救助记录表; CREATE TABLE donation_record ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, amount DECIMAL(10,2) DEFAULT NULL COMMENT 现金金额, item_name VARCHAR(100) DEFAULT NULL COMMENT 物资名称, remark VARCHAR(200), donate_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT捐赠记录表; CREATE TABLE volunteer_activity ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(100) NOT NULL, content TEXT, activity_time DATETIME, location VARCHAR(100), max_people INT DEFAULT 20, current_people INT DEFAULT 0, status TINYINT DEFAULT 0 COMMENT 0招募中 1已结束, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT志愿活动表; CREATE TABLE announcement ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(100) NOT NULL, content TEXT, publisher VARCHAR(50), publish_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT公告表; CREATE TABLE comment ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, target_type TINYINT COMMENT 0动物 1活动, target_id INT NOT NULL, content VARCHAR(500), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT评论表;几个字段设计上的考虑。animal.photo只存相对路径不要存base64否则数据库会迅速膨胀页面加载也慢。animal.status是全局最重要的字段前台列表只查status 1的数据待审核的动物绝不会出现在访客面前。adoption_application.status和animal.status要分开两者联动是业务闭环的关键点后面代码里再展开。金额字段用DECIMAL不用FLOAT或DOUBLE精度问题在java基础里经常被问这里就顺手避开。rescue_record.animal_id允许为空因为用户可能先上报救助线索管理员后补关联动物档案。这块属于业务边界设计答辩时能说清楚理由比多写十个接口更有说服力。3. 用Spring Boot把后端骨架跑起来实体、Mapper与核心接口3.1 初始化项目与依赖清单用Spring Initializr生成工程时包名建议用com.example.animalgroupId和artifactId随意但包结构要清晰。下面这个依赖清单是精简版版本号以你本地Spring Boot父工程为准不要硬贴网上的版本号容易冲突。dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-thymeleaf/artifactId /dependencyspring-boot-starter-web负责Controller和内置Tomcatmybatis-plus-boot-starter提供MyBatis-Plus能力mysql-connector-java是MySQL驱动thymeleaf负责服务端页面渲染。还需要引入Spring Boot的校验依赖和LombokLombok可以省掉getter/setter不用的话就手动生成。application.yml里的核心配置如下spring: datasource: url: jdbc:mysql://localhost:3306/animal_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver thymeleaf: cache: false servlet: multipart: max-file-size: 10MB max-request-size: 20MB mybatis-plus: configuration: map-underscore-to-camel-case: trueserverTimezoneAsia/Shanghai解决的是MySQL驱动连接后时间差8小时的问题这个是老坑了不配的话查出来的DATETIME会偏移。thymeleaf.cachefalse是开发阶段必须开的否则改页面要重启服务才生效。multipart限制上传大小图片一般10MB以内足够。map-underscore-to-camel-casetrue让create_by自动映射到createByMyBatis-Plus默认开启所以这里的配置其实是冗余的但写出来能让新手看懂意图。3.2 实体类与通用分页接口Animal实体类直接对应animal表用TableName指定表名防止类名和表名不一致时查错表。Data TableName(animal) public class Animal { TableId(type IdType.AUTO) private Long id; private String name; private String species; private String age; private Integer gender; private String healthStatus; private String location; private String description; private String photo; private Integer status; private Long createBy; private LocalDateTime createTime; private LocalDateTime updateTime; }TableId(type IdType.AUTO)对应数据库自增主键。字段全部走驼峰映射不用写XML。注意photo字段在数据库里叫photo没有下划线映射没歧义。createBy对应create_by靠MyBatis-Plus自动驼峰转换完成。Mapper层直接继承BaseMapper单表CRUD和条件查询全部省掉Mapper public interface AnimalMapper extends BaseMapperAnimal { }后台分页接口是管理端最常用的入口。管理员看所有动物列表按status过滤按创建时间倒序RestController RequestMapping(/api/admin/animal) public class AdminAnimalController { Autowired private AnimalMapper animalMapper; GetMapping(/page) public ResultIPageAnimal page( RequestParam(defaultValue 1) int page, RequestParam(defaultValue 10) int size, RequestParam(required false) Integer status) { PageAnimal p new Page(page, size); LambdaQueryWrapperAnimal wrapper new LambdaQueryWrapper(); wrapper.eq(status ! null, Animal::getStatus, status) .orderByDesc(Animal::getCreateTime); return Result.ok(animalMapper.selectPage(p, wrapper)); } }LambdaQueryWrapper里的eq(status ! null, ...)是MyBatis-Plus条件构造器的典型写法第一个参数是布尔表达式为false时这个条件不参与拼SQL所以status传空就能查全部。Page对象必须作为第一个参数传给selectPage返回值里有records、total、current、size前端分页条直接复用这几个字段。这里的Result是项目统一返回对象字段一般包含code、message、data用你自己的包装类也行关键是统一。3.3 领养审核状态机用事务保证数据一致性这是整个项目中最值得写进简历的Service方法。管理员审核一条领养申请时必须同时做两件事把申请状态改成通过或拒绝如果通过把对应动物的状态改成“已领养”。这两步必须在一个事务里完成否则会出现申请已通过但动物还在前台挂着的脏数据。Service public class AdoptionServiceImpl implements AdoptionService { Autowired private AdoptionApplicationMapper applicationMapper; Autowired private AnimalMapper animalMapper; Override Transactional(rollbackFor Exception.class) public void auditAdoption(Long applicationId, Integer auditResult, String auditRemark) { // 1. 查出申请记录状态必须是待审核 AdoptionApplication app applicationMapper.selectById(applicationId); if (app null || !app.getStatus().equals(0)) { throw new BusinessException(申请不存在或已被处理过); } // 2. 更新申请状态 app.setStatus(auditResult); app.setAuditRemark(auditRemark); app.setAuditTime(LocalDateTime.now()); applicationMapper.updateById(app); // 3. 审核通过时联动修改动物状态变成已领养 if (auditResult.equals(1)) { Animal animal animalMapper.selectById(app.getAnimalId()); if (animal null || !animal.getStatus().equals(1)) { throw new BusinessException(该动物当前不在可领养状态); } animal.setStatus(2); animalMapper.updateById(animal); } } }逻辑分三步第一步先查申请并校验状态为0这是防重复处理的关键。第二步更新申请记录把审核结果和备注写进去。第三步是核心联动只有审核通过时才改动物状态拒绝时动物保持“已发布”用户可以另选别的动物继续申请。Transactional(rollbackFor Exception.class)必须写默认情况下事务只回滚RuntimeException如果业务层抛出的是自定义BusinessException不指定rollbackFor会出现事务不生效的诡异问题。这就是常被问到的典型问题java怎么保证数据一致性在这个场景下的答案就是三步走——先查状态、再快照更新、最后联动更新外层用事务兜底。面试官接着问“数据库层面还能怎么兜”答案就是唯一索引或乐观锁后面避坑章节会写。4. 把前后端流程串起来从发表、审核到领养申请闭环4.1 前台首页只展示已审核通过的动物前台首页的核心逻辑就一条SQLwhere status 1 order by create_time desc。待审核和已领养的动物都不该出现在这里用Thymeleaf渲染一个分页列表div classcontainer div classrow th:eacha : ${page.records} div classcol-md-4 card img th:src${a.photo} classcard-img-top alt动物照片/ div classcard-body h5 th:text${a.name}动物名/h5 p th:text${a.species / a.age}猫 / 2个月/p p th:text${a.healthStatus}健康状况/p a th:href{/animal/detail/ ${a.id}} classbtn btn-primary查看详情/a /div /div /div /divController里把PageAnimal放进Model之后Thymeleaf会自动把IPage接口暴露成page.records、page.current、page.pages。前台查询时只设置wrapper.eq(Animal::getStatus, 1)再orderByDesc(Animal::getCreateTime)代码逻辑和后台分页接口几乎一致区别只在一个条件。这里不需要额外写原生的分页SQL避免重复劳动。图片地址直接th:src${a.photo}展示这要求上传图片后返回的路径是/upload/xxx.jpg这种可由浏览器直接访问的URL不是磁盘绝对路径更不是base64字符串。关于图片访问的坑第5章专门讲。4.2 领养申请表单提交与后端防重复校验详情页底部放一个领养申请表单需要提交animalId、申请理由、联系电话和居住地址。这段是纯静态表单不做异步简单可靠form th:action{/api/adoption/apply} methodpost input typehidden nameanimalId th:value${animal.id}/ div classform-group label申请理由/label textarea namereason classform-control rows3/textarea /div div classform-group label联系电话/label input typetext namephone classform-control/ /div div classform-group label居住地址/label input typetext nameaddress classform-control/ /div button typesubmit classbtn btn-success提交领养申请/button /form后端接收这个请求时要做的校验不是表单非空那么简单而是必须自己从库里读动物状态再判断当前用户有没有申请过。不要信任前端传过来的任何状态字段PostMapping(/api/adoption/apply) Transactional public Result? apply(AdoptionApplication app, RequestParam Long userId) { // 1. 动物必须是已发布状态 Animal animal animalMapper.selectById(app.getAnimalId()); if (animal null || animal.getStatus() ! 1) { return Result.fail(该动物当前不可领养); } // 2. 同一个用户对同一动物只能有一条申请 Long count applicationMapper.selectCount( new LambdaQueryWrapperAdoptionApplication() .eq(AdoptionApplication::getAnimalId, app.getAnimalId()) .eq(AdoptionApplication::getUserId, userId)); if (count 0) { return Result.fail(你已经申请过这只动物了请等待审核); } // 3. 初始化申请状态为待审核 app.setUserId(userId); app.setStatus(0); app.setApplyTime(LocalDateTime.now()); applicationMapper.insert(app); return Result.ok(); }注意这里顺序很重要先查动物状态再查申请记录最后插入新申请。查动物状态是为了防止管理员已经审核通过、动物已经变成已领养用户还在前台看到详情页。查重复申请则是应对双击提交或者用户反复刷新提交。第三步把status显式初始化为0即使前端恶意传了其他值也没用后端只认自己赋值。这段逻辑写完前台用户侧的核心闭环就完成了。剩下的救助上报、捐赠登记本质上都是同一个套路关联用户、初始化状态、插入记录只是表和字段不一样。4.3 后台管理审核列表与图片上传后台管理端和前台的区别主要在视图层。管理员打开审核页默认看到status0的动物列表每条记录后面两个按钮“通过”和“拒绝”。通过后动物状态变为1前台立刻可见。拒绝时填一个备注用户那边能收到反馈。图片上传是后台发布动物的必选项工具类写法如下PostMapping(/api/admin/animal/upload) public Result? upload(RequestParam(file) MultipartFile file) { String originalFilename file.getOriginalFilename(); String suffix originalFilename.substring(originalFilename.lastIndexOf(.)); String fileName UUID.randomUUID() suffix; String uploadDir System.getProperty(user.dir) /upload; File dir new File(uploadDir); if (!dir.exists()) { dir.mkdirs(); } File saveFile new File(dir, fileName); file.transferTo(saveFile); return Result.ok(/upload/ fileName); }生成文件名时必须拼一个UUID不能用用户上传的原始文件名否则不同用户传一个a.jpg就会互相覆盖。System.getProperty(user.dir)取的是项目运行目录本地开发时是工程根目录transferTo如果目标目录不存在会直接抛异常所以先mkdirs()。返回给前端的路径是/upload/xxx.jpg这个路径要靠静态资源映射才能访问否则上传成功但图片永远加载不出来下一个坑。5. 平台开发避坑5个常见Java项目翻车现场与排查手段5.1 图片明明传上去了页面就是不显示现象upload接口返回成功数据库里也存了/upload/xxx.jpg但img标签破图。浏览器直接访问localhost:8080/upload/xxx.jpg返回404。原因Spring Boot默认只把classpath:/static/和classpath:/public/等目录映射成静态资源你写的/upload是项目根目录下的物理文件夹不在自动映射范围内。解决自己加一个资源映射配置Configuration public class WebMvcConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceLocations(file: System.getProperty(user.dir) /upload/); } }addResourceHandler(/upload/**)是浏览器访问的URL前缀addResourceLocations(file: ...)指向物理目录注意结尾必须加/否则映射不生效。这是个非常经典的Spring Boot资源映射坑网上案例很多但如果没人提醒第一次遇到会查半天。5.2 同一个用户对同一只动物提交了多条领养申请现象用户疯狂点击“提交领养申请”按钮后台出现多条同一用户的申请记录管理员审核时不知道以哪条为准。原因前端只做了按钮置灰后端的重复校验有并发漏洞。用户两次请求同时到达后端两个线程都查出来count 0然后同时插入重复数据就产生了。解决后端在数据表上直接加唯一索引让重复数据从根本上插不进去ALTER TABLE adoption_application ADD UNIQUE KEY uk_animal_user (animal_id, user_id);加完索引后重复插入会抛DuplicateKeyException在Controller里catch这个异常统一返回“你已申请过”。但这会连带一个问题一旦申请被拒绝用户就不能再申请同一只动物了。如果业务上允许拒绝后再次申请就把唯一索引改成uk_animal_user_status (animal_id, user_id, status)利用status2表示被拒绝允许用户重新提交一条status0的新申请。5.3 MyBatis-Plus分页查出来总是全表数据现象调selectPage后records里一次性返回几十上百条total值正确但分页完全无效。原因MyBatis-Plus的分页需要单独配置PaginationInnerInterceptor拦截器不配置的话Page对象只是摆设SQL不会拼接LIMIT。解决在启动类或配置类里注册分页插件Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); PaginationInnerInterceptor pagination new PaginationInnerInterceptor(DbType.MYSQL); pagination.setMaxLimit(100L); interceptor.addInnerInterceptor(pagination); return interceptor; } }setMaxLimit(100L)是安全上限防止前端传一个size999999把整个表打出来。这个配置属于“新手必踩老手必查”因为不配置时项目能跑、接口能通、数据能返回看起来一切正常只有翻页时才发现问题。5.4 前端传“2024-05-12”后端LocalDateTime直接报错现象领养申请里的applyTime字段前端传的是2024-05-12后端用LocalDateTime接收请求直接400日志提示无法反序列化。原因LocalDateTime默认期望格式是yyyy-MM-ddTHH:mm:ss纯日期字符串缺了时间部分Spring自带的消息转换器不认识这种紧凑格式。解决前端传2024-05-12 00:00:00或者后端把这个字段类型改成LocalDate。实体里如果只是记录申请日期用LocalDate最省心序列化和反序列化都默认支持yyyy-MM-dd。如果要保留具体时间就在字段上加JsonFormat(pattern yyyy-MM-dd HH:mm:ss)并保证前端按这个格式传值。这个问题的本质是java基础里的类型与格式转换面试问日期处理时经常被拿出来当引子。5.5 启动失败端口占用与Mapper扫描不到现象APPLICATION FAILED TO START日志里写着Port 8080 was already in use或者启动后调用接口报Invalid bound statement (not found)。原因端口被占用通常是本地起了第二个Tomcat实例、或者IDEA端口没释放Mapper扫描不到通常是启动类没加MapperScan或者Mapper接口上没有Mapper注解。解决端口占用时临时换端口启动不用改配置文件直接加启动参数java -jar animal-platform.jar --server.port8081Mapper扫描问题分两种情况处理。启动类上统一加MapperScan(com.example.animal.mapper)整个包下的Mapper全部注册或者每个Mapper接口上单独加Mapper。推荐前者写一次就不用再管。这类问题日志里的Caused by通常会直接点明原因不要只看最上面的红色大字往下翻三行找Caused by才是排查思路。6. 从打包到演示让救助平台在验收现场一次跑过的三个技巧6.1 用一条演示脚本把核心流程完整串一遍验收演示最怕临时手点数据状态前后对不上。建议把演示数据直接写成data.sql让JPA或手动初始化器在启动时自动灌入。一条典型的演示链路是一个管理员账号、一个普通用户账号、一只待审核的猫、一只已发布的狗、一条已提交的领养申请。演示时先登录管理员审核猫再去前台看狗提交领养申请后切回管理员审核最终回到前台看到“已领养”状态。演示数据的状态字段必须和脚本顺序匹配否则会出现“点审核说已处理过”的尴尬场面。时间字段用固定的2024-01-01 00:00:00不要用NOW()避免多次启动后数据时间不一致影响截图和讲解。6.2 一条命令打出可运行的jar包本地开发跑mvn spring-boot:run没问题但验收现场往往要换一台机器最好提前打包成可执行jarmvn clean package -DskipTests java -jar target/animal-platform-0.0.1-SNAPSHOT.jar-DskipTests跳过测试避免因为测试类缺失导致打包失败。打包前确认application.yml里数据库地址、账号密码已经换成验收环境的配置用户名密码不要硬编码在代码里。如果现场端口被占按第5章的办法加--server.port参数即可。这里还需要提前确认本机java -version环境正常java环境变量配置不对的话jar包双击和命令行都无法启动。6.3 给验收加分的展示顺序演示时不要从头像和注册页面开始时间不够时先走主链路再补外围模块。推荐顺序前台首页展示已通过审核的动物→ 用户登录 → 提交领养申请 → 右上角提示“待审核”→切换到管理员账号 → 审核通过 → 切换回前台该动物状态变为“已领养”。这条链路每走一步都对应一张表和一个状态字段讲起来干净利落导师也能顺着这个顺序追问。加分项是在管理员首页放一个简单的统计看板按species统计动物数量用ECharts柱状图渲染。后端接口就是GROUP BY species一行SQL前端图表30分钟能调完。如果担心实时数据渲染翻车可以先用静态JSON兜底演示时再切到动态接口。我自己的习惯是永远先做静态兜底再做动态接入两套数据源切换用if判断线上永远走动态演示现场万一接口报错也有退路。这算是我做演示项目这么多年攒下来的一个习惯哪里都可能出问题演示现场永远是备份优先。希望帮到你。本文还有配套的精品资源点击获取
返回列表