☰
基于Spring Boot的流浪动物管理系统:数据库设计、权限控制与避坑指南
2026/9/25 1:53:54 网站建设 项目流程

简介:基于 Spring Boot 的流浪动物管理系统毕业设计资源,面向计算机专业学生和初级 Java 开发者,适用于课程设计或毕业设计参考。系统分为管理员与用户两大子系统,覆盖用户管理、动物种类管理、流浪动物管理、救助站管理、爱心募捐、动物找家、领养申请、交流论坛等核心模块,界面简洁,操作流程清晰。

资源包共 612 个文件,约 29.44MB,包含 Java 源码、Vue 前端页面、MySQL 数据库脚本、Word 文档和答辩 PPT。主要文件类型有 Java、Vue、JavaScript、XML、CSS,并搭配 SVG 图标、JPG/PNG 图片以及 SQL 初始化脚本,同时附有 Maven 配置和批处理命令,便于在 IDEA 或 Eclipse 中快速运行。已有 129 人学习下载,可帮助毕业设计或期末项目展示的同学减少从零搭建的重复工作。

读者可获得前后端分离的实现思路、数据库表设计、核心接口调用方式以及后台管理模块写法;通过研读源码与配套文档,能快速理解领养、募捐、救助站管理等业务流程,并在此基础上扩展或二次开发,为答辩 PPT 和设计文档撰写提供完整素材。

1. 基于Spring Boot的流浪动物管理系统:为什么这个老题目至今仍是课设首选

“流浪动物管理系统”在Java课程设计里属于出现频率极高的题目,毫不夸张地说,每年都有大量学生靠它完成答辩。这个题目的价值不只在“动物”这个业务本身,而在于技术栈组合非常经典:Spring Boot + MySQL + 后端管理后台,用户与管理员双角色,再加上“领养申请—审核—回访”这条完整业务流,正好把增删改查之外的状态流转和权限控制也覆盖了。对还没接触过企业级项目的学生来说,这是性价比最高的起步题目;对需要快速交付的开发者,也能靠它半天搭出可演示的完整闭环。下面按我平时做这类系统“先设计表、再写代码、最后排错”的顺序,把整套方案拆开讲。

2. 先建表还是先写代码:流浪动物系统的业务模型与五张核心表

拿到题目先别急着打开IDE新建项目,第一件事是把业务规则理清。很多课程设计翻车,不是死在代码上,而是死在表结构设计不合理,写到一半发现字段不够用,或者状态流转对不上。

2.1 角色与领养流程:把业务规则翻译成表字段

这个系统的角色只有两个:普通用户和管理员。普通用户注册后可以浏览动物列表、查看详情、提交领养申请、查看自己的申请进度;管理员负责发布动物信息、审核领养申请、下架不合适的动物、录入回访记录。不引入独立的角色表,在用户表里加一个role字段就够了——课程设计项目用角色表属于过度设计,答辩时反而容易给自己挖坑。

业务规则要定死在设计阶段,最核心的一条是状态流转:动物信息有“可领养 / 已领养 / 已下架”三种状态,领养申请有“待审核 / 已通过 / 已拒绝”三种状态。当管理员把一条申请审核通过时,该动物状态必须同步从“可领养”改成“已领养”。这个联动逻辑写在Service层,不写在数据库触发器里,原因后面讲代码时会说明。

建议画出状态流转图再落到字段:用户表、动物表、领养申请表是必须的三张,回访记录表和公告表按题目要求补充。回访记录表解决的是“领养之后有没有后续跟踪”这个加分项,公告表则是很多评分标准里“系统信息发布功能”的落点。推荐五张核心表,工作量适中且故事完整。

2.2 五张核心表DDL:字段类型、状态枚举与索引取舍

建表脚本我习惯用MySQL 5.7或8.0都兼容的写法,字符集统一用utf8mb4,而不是utf8——流浪动物描述里经常出现表情符号和特殊字符,utf8会直接报错或存成乱码。下面是可直接执行的完整DDL。

CREATE DATABASE IF NOT EXISTS stray_animal DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE stray_animal; CREATE TABLE `user` ( `id` INT NOT NULL AUTO_INCREMENT COMMENT '主键', `username` VARCHAR(50) NOT NULL COMMENT '登录名', `password` VARCHAR(64) NOT NULL COMMENT '密码,演示项目存MD5', `real_name` VARCHAR(50) DEFAULT NULL COMMENT '真实姓名', `phone` VARCHAR(20) DEFAULT NULL COMMENT '联系电话', `role` TINYINT NOT NULL DEFAULT 0 COMMENT '角色:0普通用户 1管理员', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB COMMENT='用户表'; CREATE TABLE `animal` ( `id` INT NOT NULL AUTO_INCREMENT COMMENT '主键', `name` VARCHAR(50) NOT NULL COMMENT '动物昵称', `species` VARCHAR(50) NOT NULL COMMENT '品种,如 中华田园猫/金毛', `age_month` INT DEFAULT NULL COMMENT '月龄,用于列表按年龄排序', `gender` TINYINT DEFAULT 0 COMMENT '性别:0未知 1公 2母', `health` VARCHAR(200) DEFAULT NULL COMMENT '健康状况描述', `image` VARCHAR(255) DEFAULT NULL COMMENT '图片路径,不存二进制', `status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0可领养 1已领养 2已下架', `description` TEXT COMMENT '详细描述', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '入库时间', `update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), KEY `idx_status` (`status`) ) ENGINE=InnoDB COMMENT='流浪动物表'; CREATE TABLE `adoption` ( `id` INT NOT NULL AUTO_INCREMENT COMMENT '主键', `user_id` INT NOT NULL COMMENT '申请人ID', `animal_id` INT NOT NULL COMMENT '动物ID', `reason` VARCHAR(500) NOT NULL COMMENT '领养理由', `address` VARCHAR(255) DEFAULT NULL COMMENT '居住地址', `phone` VARCHAR(20) DEFAULT NULL COMMENT '联系电话,冗余用户手机号方便审核', `status` TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0待审核 1已通过 2已拒绝', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '申请时间', `audit_time` DATETIME DEFAULT NULL COMMENT '审核时间', PRIMARY KEY (`id`), KEY `idx_user` (`user_id`), KEY `idx_animal` (`animal_id`) ) ENGINE=InnoDB COMMENT='领养申请表'; CREATE TABLE `visit_record` ( `id` INT NOT NULL AUTO_INCREMENT COMMENT '主键', `adoption_id` INT NOT NULL COMMENT '关联的领养申请ID', `content` VARCHAR(500) NOT NULL COMMENT '回访内容', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '回访时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB COMMENT='回访记录表'; CREATE TABLE `notice` ( `id` INT NOT NULL AUTO_INCREMENT COMMENT '主键', `title` VARCHAR(100) NOT NULL COMMENT '公告标题', `content` TEXT COMMENT '公告内容', `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '发布时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB COMMENT='公告表';

这套DDL里有几个选型细节值得展开。状态字段一律用TINYINT,不用VARCHAR存“可领养/已领养”这种中文,一是占空间小,二是代码里比较和扩展枚举方便,页面上再映射成中文文案,这是课程设计答辩时老师喜欢听的“设计考量”。age_month用整数存月龄而不是存字符串“2岁”,原因很简单:列表页如果要做“按年龄从小到大排序”或“筛选幼年动物”,字符串字段完全没法高效排序,这个字段算是个小伏笔,后面统计功能也会用到。

user表名在MySQL里属于保留字相关的敏感词,所以我用反引号包住了。实际开发里我更习惯把表名改成sys_user,避免以后写SQL时每处都要加反引号,你也可以直接在DDL里把表名改掉,注意同步改代码里的@TableName注解就行。图片字段只存相对路径,不存BLOB二进制,这是所有管理系统项目的通用约定:数据库存/images/animal/20240601_xxx.jpg这样的路径,真正的文件放在项目的static目录下,前后端分离部署时再换成对象存储的URL。

外键问题上,我一般不建议在DDL里写物理外键。领养申请表里的user_id和animal_id保持逻辑关联,通过索引和代码层校验保证一致性。实际场景中物理外键经常导致删除操作被约束卡死,课程设计演示“删除动物”功能时特别容易触发这个报错。

3. 用Spring Boot把核心CRUD落成代码:依赖配置、三层分层与拦截器

数据库设计完成后进入编码阶段。这个项目采用Spring Boot 2.7.x + MyBatis-Plus + MySQL的组合,是目前课设项目最主流的配置。如果你本机装的是JDK 8,千万别手滑去创建Spring Boot 3.x项目——3.x强制要求JDK 17,启动直接失败,这种版本不兼容问题几乎是每年课程设计最常见的开头。

3.1 pom.xml与application.yml:先让项目能启动

创建项目时用IDEA的Spring Initializr,语言选Java,类型选Maven,依赖勾选Spring Web、MySQL Driver、Lombok。MyBatis-Plus需要手动加到pom里,因为官方Initializr不提供这个选项。

<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.5</version> </dependency>

核心依赖就这一条需要手动添加,其余保持IDEA生成的原样。注意别同时引入mybatis-plus-boot-starter和pagehelper-spring-boot-starter,这两个分页插件放一起会产生Bean冲突,具体现象在避坑章节再展开。

配置文件是项目能不能跑起来的关键,我给的这份经过了多次课设项目验证:

server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/stray_animal?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true username: root password: 你的数据库密码 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai mybatis-plus: mapper-locations: classpath:mapper/**/*.xml type-aliases-package: com.example.strayanimal.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

连接串里的serverTimezone=Asia/Shanghai必须写,MySQL 8.0的驱动默认使用UTC时区,不指定的话Java里的new Date()存入数据库会差8小时,查出来时间对不上。useSSL=false是关掉SSL握手,本地开发不加这个参数有时会告警甚至连接失败。allowPublicKeyRetrieval=true是MySQL 8.0的驱动连接时的常见要求,不写会出现Public Key Retrieval is not allowed错误。这套参数属于连接配置里的血泪经验,每个都是实际踩过坑才加进去的。

数据源不需要额外配置连接池。Spring Boot 2.x默认集成HikariCP,这已经是最好的连接池之一,不需要再引入Druid,除非答辩时需要展示监控页面。log-impl配置成StdOutImpl可以在控制台看到每一条SQL语句,排查问题时比盲猜字段映射效率高得多。

3.2 Controller-Service-Mapper:一套能直接抄的分层代码

实体类映射动物表,注意用Lombok的@Data注解减少getter/setter代码量。@TableName注解指定表名,@TableId指定主键策略,AUTO对应数据库自增。

@Data @TableName("animal") public class Animal { @TableId(type = IdType.AUTO) private Integer id; private String name; private String species; private Integer ageMonth; private Integer gender; private String health; private String image; private Integer status; private String description; private Date createTime; private Date updateTime; }

字段命名用驼峰,数据库列名用下划线,mybatis-plus.configuration.map-underscore-to-camel-case这行配置就是负责把age_month自动映射到ageMonth,不用写一堆@TableField注解。

Mapper接口只需继承BaseMapper,CRUD方法全部现成:

@Mapper public interface AnimalMapper extends BaseMapper<Animal> { }

Service层同样套MyBatis-Plus的模板,接口继承IService,实现类继承ServiceImpl。这是MyBatis-Plus最省事的标准写法,不需要手写增删改查的实现。

public interface AnimalService extends IService<Animal> { } @Service public class AnimalServiceImpl extends ServiceImpl<AnimalMapper, Animal> implements AnimalService { }

Controller是接口的入口,代码风格直接决定前端联调是否顺畅。以分页查询动物列表为例:

@RestController @RequestMapping("/api/animal") public class AnimalController { private final AnimalService animalService; public AnimalController(AnimalService animalService) { this.animalService = animalService; } @GetMapping("/page") public Result<IPage<Animal>> page( @RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, @RequestParam(required = false) Integer status) { Page<Animal> page = new Page<>(pageNum, pageSize); LambdaQueryWrapper<Animal> wrapper = new LambdaQueryWrapper<>(); if (status != null) { wrapper.eq(Animal::getStatus, status); } wrapper.orderByDesc(Animal::getCreateTime); return Result.ok(animalService.page(page, wrapper)); } }

这里有几个习惯值得模仿。构造器注入比直接@Autowired字段更好,写单元测试时能方便地替换依赖,答辩被问到依赖注入时可以答出这个区别。分页参数pageNum和pageSize给了默认值,前端即使漏传参数也不会报错。LambdaQueryWrapper是MyBatis-Plus推荐的条件构造方式,用方法引用代替字符串列名,编译期就能发现拼写错误,同时天然防止SQL注入。按status筛选是列表页最高频的需求,前端传status=0只看可领养的动物,管理员传空看全部。Result.ok()是统一返回结构,下一章会说具体定义。

3.3 登录拦截器:十分钟替系统补上权限控制

课程设计评分标准里“权限控制”通常是明确的要求,不用Spring Security是因为对纯后端模板项目来说太重。一个HandlerInterceptor加一个WebMvcConfigurer就能实现大部分需求。

public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { Object user = request.getSession().getAttribute("loginUser"); if (user == null) { response.setContentType("application/json;charset=utf-8"); response.getWriter().write("{\"code\":401,\"msg\":\"未登录或登录已过期\"}"); return false; } return true; } }

注册拦截器时关键在放行规则的配置:

@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LoginInterceptor()) .addPathPatterns("/**") .excludePathPatterns( "/api/user/login", "/api/user/register", "/error", "/static/**", "/templates/**" ); } }

管理员接口的权限校验更简单,不用单独写第二套拦截器。用户登录时把role字段放进Session,管理员接口的Controller方法里加一行判断:如果不是管理员就返回Result.error("无权限")。课程设计层面这就够了,比为每个角色写一套拦截规则更容易维护。如果你的系统用前后端分离,Session方案会受限,需要改成JWT或者把Token放在请求头里由拦截器解析——这个改动比较大,建议课设还是用Session方案,省去大量跨域和Token刷新问题。

4. 数据库初始化到前后端联调:从SQL脚本到页面展示的完整链路

压缩包里四个交付部分的配合方式通常是:源码用IDEA打开、数据库脚本导入MySQL、课程设计文档作为系统说明和设计过程记录、PPT用于答辩展示。整套跑通的关键在数据库初始化和接口联调这两个环节,几乎所有“项目跑不起来”的问题都出在这一步。

4.1 初始化脚本与连接配置:导入数据、配置好时区

把第二章的DDL在Navicat或命令行执行完成后,还需要插入几条基础数据。管理员账户一定要初始化,否则系统登录入口形同虚设。下面的SQL插入了演示管理员和几条示例动物数据。

INSERT INTO `user` (username, password, real_name, role) VALUES ('admin', 'e10adc3949ba59abbe56e057f20f883e', '管理员', 1), ('zhangsan', 'e10adc3949ba59abbe56e057f20f883e', '张三', 0); INSERT INTO `animal` (name, species, age_month, gender, health, status, description) VALUES ('小花', '中华田园猫', 6, 2, '已驱虫,未绝育', 0, '亲人,喜欢蹭人'), ('旺财', '金毛寻回犬', 18, 1, '已打疫苗,性格温顺', 0, '适合有院子的家庭'), ('雪球', '比熊犬', 10, 2, '后腿轻微受伤恢复中', 1, '需要定期复查');

注意password字段存的是123456的MD5值,e10adc3949ba59abbe56e057f20f883e是常见的MD5值。演示项目用MD5可以接受,但需要心里有数这是不安全的做法——不加盐的MD5很容易被彩虹表反查。答辩如果被问到这个问题,能答出“生产环境应该用BCrypt加盐哈希”就足够了。如果不想在代码里写MD5工具类,也可以偷懒用登录时明文比对,不过那在答辩时容易被当成设计缺陷深挖。

示例动物数据里的status字段特意让三条记录覆盖了不同状态,这样列表页筛选功能演示时不用临时改数据库。导入完成后,用SELECT * FROM animal;确认三条数据都在,再启动Spring Boot项目。控制台出现“Tomcat started on port(s): 8080”只代表应用起来了,必须再打开浏览器访问http://localhost:8080/api/animal/page看到JSON返回,才说明数据源和Mapper映射都正常。

4.2 接口联调与统一返回结构:少改前端的三点约定

前后端联调最容易出现的情况是:前端拿到数据后不知道data里是什么结构,后台返回错误时前端无法区分是参数问题还是业务问题。解决办法是定义一套统一的Result返回体,让所有接口都返回同一个JSON结构。

@Data public class Result<T> { private Integer code; private String msg; private T data; public static <T> Result<T> ok(T data) { Result<T> result = new Result<>(); result.code = 200; result.msg = "成功"; result.data = data; return result; } public static <T> Result<T> error(String msg) { Result<T> result = new Result<>(); result.code = 500; result.msg = msg; return result; } public static <T> Result<T> error(Integer code, String msg) { Result<T> result = new Result<>(); result.code = code; result.msg = msg; return result; } }

前后端围绕这套返回结构达成三个约定。第一,code为200时业务成功,data里是数据本身;非200时data为null,前端直接弹msg提示。第二,分页接口的data固定是MyBatis-Plus的IPage结构,包含records(当前页数据)、total(总条数)、current(当前页码)、size(每页条数),前端渲染表格和分页组件时直接取这些字段。第三,登录失效统一返回code=401,前端Ajax全局拦截这个状态码跳转到登录页,而不是每个页面单独判断。

后端接口就绪后,前端用jQuery的Ajax即可完成联调,不需要引入Axios或Vue全家桶。课设系统的页面我通常用Bootstrap搭一个简单后台模板,列表页的请求长这样:

$.ajax({ url: '/api/animal/page', data: { pageNum: 1, pageSize: 10, status: 0 }, type: 'GET', success: function (res) { if (res.code === 200) { renderList(res.data.records); renderPagination(res.data.total); } else { alert(res.msg); } }, error: function () { alert('网络请求失败'); } });

这里给successCallback里的第一个判断条件写了res.code === 200,而不是写成if (res.data)——很多新手在这里判断数据是否存在,但后端返回错误时msg有值而data为null,用数据是否存在判断会导致报错信息展示不出来。前端拿到records后遍历渲染就行了,我一般会在前端代码里写一个renderList函数,用模板字符串拼接表格行,全程不依赖复杂框架,浏览器打开就是一个能正常交互的页面。

5. 课程设计避坑指南:从连不上库到分页失效的五个翻车现场

这一章集中写最常踩的五个坑,每个都是实际发生过的问题,按“现象→原因→解决”的顺序记录。

5.1 数据库连接失败:同样是localhost,为什么别人连得上你连不上

现象:项目启动时报错Communications link failure或者Access denied for user,页面访问时直接白屏加错误堆栈。

原因分两类。一类是MySQL服务没启动,或端口不是默认的3306。另一类是密码错误但报错信息有误导性,Access denied并不一定代表密码错,也可能是用户名的host限制——root@localhost只允许本机连接,而连接串里写了远程地址。实际排查时最坑的是MySQL 8.0和5.7的驱动差异,8.0必须用com.mysql.cj.jdbc.Driver,5.7用com.mysql.jdbc.Driver,写错直接ClassNotFound。

解决:分三步排查——命令行执行mysql -u root -p确认MySQL能连上;检查pom里MySQL Connector版本和本地MySQL版本是否兼容;确认连接串里的serverTimezone和allowPublicKeyRetrieval两个参数都写了。这几步做完,90%的连接问题都能定位。

5.2 Mapper接口扫描不到:启动失败的黑匣子在哪

现象:项目启动时报Invalid bound statement (not found),或者提示Mapper method 'selectById' not found,但代码看起来没有任何问题。

原因通常是两个:Mapper接口没有加@Mapper注解,或者加了注解但接口所在的包没有被Spring Boot的启动类扫描到。另一个容易忽略的点是mybatis-plus.mapper-locations配置了classpath:mapper/**/*.xml,但resources/mapper目录下没有XML文件——这个配置项找不到资源时不会报编译错,只会在启动时静默失败。

解决:给每个Mapper接口加@Mapper注解,或者在启动类上加@MapperScan("com.example.strayanimal.mapper")指定扫描包,两种方式选一种。非要用XML写自定义SQL,确认resources/mapper目录下文件后缀是.xml且文件名与接口方法对应。其实MyBatis-Plus的BaseMapper已经覆盖了绝大多数单表CRUD,不需要写XML也能完成这个项目,自定义SQL只在复杂动态查询时才用。

5.3 接口返回的时间变成一串数字:JSON序列化参数要显式配置

现象:前端拿到createTime字段显示为“1717234560000”,而不是“2024-06-01 12:00:00”。

原因:Spring Boot默认用Jackson序列化Date类型,默认输出时间戳格式。虽然配置文件里写了spring.jackson.date-format,但有些版本的Spring Boot对这个配置在LocalDateTime类型上不生效——实体类里用的是java.util.Date则生效,换了Java 8的LocalDateTime则格式不生效。

解决:统一实体类里的时间类型。要么全部用Date,配合spring.jackson.date-format配置;要么全部用LocalDateTime,在字段上加@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")注解。混用两种类型是格式错乱最常见的根源,这个坑在答辩前最好自查一遍。

5.4 控制台中文乱码:三个位置都要统一编码

现象:控制台日志正常打印SQL,但SQL里的中文参数显示为“???”,页面上从数据库查出来的中文也是乱码,数据库表里存的数据本身是好的。

原因:从MySQL返回数据经过JDBC连接,乱码通常发生在连接串缺少characterEncoding=utf8。但项目里还有另外两个乱码源:IDE的控制台编码不是UTF-8,以及MySQL数据库建库时字符集用了默认的latin1,导致数据在写入时就已经坏了。前两个是配置问题,第三个是建库时的问题,如果是第三个,光改连接串救不回来,必须重新建库。

解决:三处统一——连接串带useUnicode=true&characterEncoding=utf8;IDEA的Settings→Editor→File Encodings三处全设UTF-8;建库SQL显式指定DEFAULT CHARACTER SET utf8mb4。已经乱掉的数据只能清掉重建,没有后悔药。

5.5 分页插件冲突:MyBatis-Plus和PageHelper别同时引入

现象:项目启动时报Error creating bean with name '...',或者分页查询返回的数据不分页、total始终为0。

原因:mybatis-plus-boot-starter自带分页功能,通过PaginationInnerInterceptor实现;pagehelper-spring-boot-starter也向MyBatis注册了拦截器。两个插件同时存在时,MyBatis的拦截器链路发生冲突,轻则分页失效,重则启动失败。

解决:这个项目用MyBatis-Plus就彻底别碰PageHelper。分页直接用Page<T>对象加IService.page()方法,不需要额外配置。如果学校强制要求PageHelper,那就反过来把MyBatis-Plus换成纯MyBatis加PageHelper的组合,不要试图让两个插件共存。另外,MyBatis-Plus的分页拦截器需要配置:

@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }

不配置这个拦截器,page()方法虽然不报错,但查出来的records是全部数据而不是当前页数据——这个现象同样被很多人当成“分页失效”来排查,其实只是少了上面的配置类。

6. 从能跑到能答辩:三个加分项让系统不只是CRUD

基础功能跑通之后,如果想在答辩时让老师觉得“这个系统有设计”,不需要大改,加三个小而实用的功能就够了。

第一个是首页统计看板,管理员登录后第一眼看到的是数据概览,而不是直接跳列表。统计SQL用一条查出来,比写循环统计代码优雅得多:

SELECT COUNT(*) AS total_animal, SUM(CASE WHEN status = 0 THEN 1 ELSE 0 END) AS adoptable, SUM(CASE WHEN status = 1 THEN 1 ELSE 0 END) AS adopted, SUM(CASE WHEN status = 2 THEN 1 ELSE 0 END) AS off_shelf FROM animal;

这个查询返回一行数据,四个字段分别对应动物总数、可领养数、已领养数、已下架数。再配合一条按月统计申请量的SQL,就是完整的看板数据来源。答辩时能讲出“统计查询下推到数据库层,用聚合函数一次取结果,而不是查全表后Java里数”,这句话在老师耳朵里比再说一遍三层架构有分量得多。

第二个是操作日志。不用引入AOP做花哨的切面日志,用拦截器或者直接在每个更新接口里加一行日志记录就够展示。最简单的方式是建一张operation_log表,管理员下架动物、审核通过领养申请时,调用记录方法写入操作人、操作类型、操作时间。之前设计的audit_time字段在审核接口里顺带更新,既完成需求又验证了表设计的预判性。

第三个是领养申请的审核去重。这条规则实现起来不过几行代码,但体现了“业务约束在Service层”的设计思想:

long pendingCount = adoptionService.count( new LambdaQueryWrapper<Adoption>() .eq(Adoption::getUserId, userId) .eq(Adoption::getStatus, 0)); if (pendingCount > 0) { return Result.error("你还有待审核的申请,请等待审核结果"); }

在提交领养申请前校验,保证同一用户同时只有一条待审核申请,避免恶意重复提交。这段代码放在Service而不是Controller里,是因为业务规则属于服务层职责,Controller只负责参数接收和结果返回。答辩时如果老师问“为什么不直接在数据库加唯一约束”,你可以答:唯一约束无法表达“状态为0的唯一”,跨行的条件校验适合放在应用层,这也是为什么我们用逻辑关联而不是物理外键的原因之一。

关于部署因为篇幅所限不再展开,但建议答辩前把整个流程按“清空数据库→重新导入SQL脚本→启动项目→走一遍管理员和用户两个角色”完整跑通一次,确认文档里的步骤和截图与操作过程一致。我做课程设计时养成的习惯是,答辩前一天什么都不改,只复盘这套流程,因为演示环节翻车九成不是功能不存在,而是数据和环境不一致导致的黑匣子问题。

希望今天这套从建表到答辩的思路,能帮你在拿到类似题目时不再对着空白IDEA发愁。技术方向本身不复杂,复杂的是把每个环节的边界和坑都提前摸清——这恰恰是课程设计最值得你投入时间的地方。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询