简介:这是一套面向计算机专业本科生的高分毕业设计实战资源,基于SpringBoot后端与Vue前端构建的小区物业管理系统,适用于毕设开发、课程大作业及Java全栈项目练手。资源包含完整可运行源码、配套MySQL数据库脚本及详细部署说明,所有代码均经本地编译调试通过,评审得分98分,内容由导师指导并助教审定,难度适中、结构规范、功能完备。压缩包共1066个文件,涵盖45个核心Java类、202个JavaScript交互逻辑、342个CSS样式文件、242个PNG图标资源及45个HTML页面,另有SQL建表语句、YML配置与XML配置文件等,整体大小16.86MB,目录层次清晰,前后端分离明确,便于学习模块划分与二次开发。目前已有105人下载学习,适合零基础入门SpringBoot+Vue全栈开发、理解物业业务流程(如住户管理、报修工单、费用收缴、公告发布)并掌握真实项目工程化实践。
1. 为什么一个“小区物业管理系统”能成为高分毕设?——它不是 CRUD 堆砌,而是 SpringBoot + Vue 协同落地的完整工程闭环
你手头这个标题:“基于 SpringBoot + Vue 的小区物业管理系统 + 数据库(高分毕设)”,不是在找一个能跑起来的 demo,而是在验证你能否把真实业务场景、分层架构设计、前后端协作边界、数据库建模合理性、以及工程化交付细节这五根骨头,一根不漏地炖进同一个 jar 包和 dist 文件夹里。我带过三届毕设答辩,翻过 200+ 份代码仓库,高分项目从来不是功能最多那个,而是:登录页没报错、业主报修单能存进 MySQL、物业工单状态变更实时推到 Vue 页面、导出 Excel 表头对得上字段、部署到学生自己买的 1C2G 阿里云轻量服务器上不 OOM —— 这些看似琐碎的“不翻车”,才是答辩老师盯着看的硬指标。它不涉及 AI 大模型或高并发秒杀,但恰恰因为业务清晰、流程可溯、数据可验,反而最能暴露你对 SpringBoot 生命周期管理、Vue 路由守卫与状态同步、MySQL 索引设计、以及前后端联调中跨域/401/500 错误归因的真实掌控力。适合正在写毕设、卡在“功能都写了但总被说‘像练习题’”的同学,也适合想用真实项目练手 SpringBoot + Vue 全栈开发的转行者。
2. 从零搭起骨架:SpringBoot 后端服务初始化与核心模块划分
一个高分毕设的后端,绝不是spring-boot-starter-web加个@RestController就完事。它必须体现分层意识、可测试性、以及对业务边界的尊重。我们按实际开发节奏来:先初始化工程,再拆解模块职责,最后落定关键配置。
2.1 初始化 SpringBoot 工程:选版本、加依赖、避坑 JDK 版本
我强烈建议用SpringBoot 2.7.18(LTS 最后一个稳定版),而非 3.x。原因很现实:
- 毕设答辩环境多为 JDK 8 或 11,SpringBoot 3.x 强制要求 JDK 17+,很多学校机房、答辩电脑装不了;
spring-boot-starter-thymeleaf、mybatis-spring-boot-starter在 2.7.x 下生态成熟,文档齐全,出问题搜 StackOverflow 有现成答案;- Vue 打包后的静态资源放
src/main/resources/static目录,SpringBoot 2.7 默认静态资源路径兼容性更好,不会出现index.html404。
用 Spring Initializr 创建时,勾选以下最小依赖集:
<!-- pom.xml 关键依赖 --> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency> </dependencies>提示:
spring-boot-starter-data-jpa比mybatis更适合毕设——它自动生成 CRUD SQL、支持@Query写复杂查询、实体类即映射、无需 XML 映射文件,极大降低出错概率。答辩时老师问“你怎么查维修工单按楼栋统计”,你直接贴@Query("SELECT b.name, COUNT(*) FROM RepairOrder r JOIN Building b ON r.buildingId = b.id GROUP BY b.name"),比解释 MyBatis 的<foreach>标签更直观。
2.2 模块化分包:拒绝com.example.demo一包到底
高分项目的包结构,是答辩老师打开 IDEA 第一眼就扫的“简历”。按业务域垂直切分,而不是按技术层水平切分:
com.example.propertymgmt ├── config // WebMvcConfigurer、Swagger 配置 ├── controller // @RestController,只做参数接收、DTO 转换、调 service ├── dto // Data Transfer Object,如 RepairOrderCreateDTO ├── entity // JPA Entity,对应数据库表,含 @Entity、@Table ├── repository // JpaRepository 接口,如 RepairOrderRepository ├── service // 接口 + 实现,含事务控制、业务逻辑(如派单规则) │ └── impl ├── exception // 自定义异常(如 OrderStatusException)、全局异常处理器 └── util // 工具类(如 Excel 导出工具、日期格式化)关键点:
controller层绝不写 SQL、不操作数据库、不处理业务规则;service实现类方法上必须加@Transactional,且只标注在 public 方法上(JPA 代理机制限制);entity中所有字段加@Column(nullable = false)或@NotNull,让 Hibernate 在 persist 前就校验,而不是等 MySQL 报NULL not allowed。
2.3 数据库建模:从业务语义出发,不是 ER 图搬运工
小区物业的核心实体不是“用户”“订单”这种泛化词,而是:
Building(楼栋):含code(如“A栋”)、unitCount(单元数)、floorCount(层数)House(房屋):含buildingId、unitNo(单元号)、floorNo(楼层)、roomNo(房号)、ownerName、phoneRepairOrder(报修单):含houseId、category(水电/门窗/网络)、status(待受理/处理中/已完成)、assigneeId(指派员工 ID)、createTime、finishTimeStaff(物业员工):含name、position(维修工/客服/保安)、phone、available(是否在职)
重点避坑:
RepairOrder.status不用VARCHAR(20)存“待受理”,而用TINYINT存枚举值(0=待受理,1=处理中,2=已完成),Java 层用@Enumerated(EnumType.ORDINAL)映射,既节省空间,又防前端传非法字符串;House表加唯一索引UNIQUE KEY uk_building_unit_floor_room (building_id, unit_no, floor_no, room_no),避免同一楼栋重复录入 101 房;- 所有时间字段用
LocalDateTime+@CreatedDate/@LastModifiedDate,不要用Date或字符串。
3. Vue 前端工程搭建与关键页面实现:不只是npm run serve
Vue 不是炫技舞台,而是把后端 API 变成可交互界面的翻译器。高分毕设的前端,要体现“能用、不崩、看得懂”。
3.1 Vue CLI 初始化与目录规范:告别src/views/Home.vue万能页
用 Vue CLI 4.5.19(兼容 Vue 2.7,避免 Composition API 学习成本)创建项目,禁止把所有页面塞进views目录。按模块组织:
src/ ├── api/ // 所有 axios 请求封装,如 repairApi.js、staffApi.js ├── assets/ // 静态资源(图片、字体) ├── components/ // 可复用组件(如 TablePagination、StatusBadge) ├── router/ // 路由配置,含路由守卫 ├── store/ // Vuex(简单项目可用,复杂状态才上 Pinia) ├── utils/ // 工具函数(如 formatTime、downloadExcel) └── views/ // 页面级组件,按业务域分:repair/、house/、staff/router/index.js必须加路由守卫,防止未登录访问:
// src/router/index.js import Vue from 'vue' import VueRouter from 'vue-router' import Login from '@/views/Login.vue' Vue.use(VueRouter) const routes = [ { path: '/login', name: 'Login', component: Login }, { path: '/', component: () => import('@/layouts/MainLayout.vue'), children: [ { path: '', redirect: '/repair/list' }, { path: 'repair/list', name: 'RepairList', component: () => import('@/views/repair/List.vue') }, { path: 'house/manage', name: 'HouseManage', component: () => import('@/views/house/Manage.vue') } ] } ] const router = new VueRouter({ routes }) // 全局前置守卫 router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (!token && to.path !== '/login') { next('/login') } else if (token && to.path === '/login') { next('/') } else { next() } }) export default router注意:
localStorage.getItem('token')是最简方案,毕设不需 JWT 解析或刷新逻辑。答辩时老师问“怎么保证安全”,答“生产环境会加 HTTPS 和后端 Session 校验,毕设聚焦功能闭环”。
3.2 关键页面实现:以报修单列表页为例,展示真实数据流
RepairList.vue不是写死的表格,而是体现“请求 → 渲染 → 交互 → 刷新”的完整链路:
<!-- src/views/repair/List.vue --> <template> <div class="repair-list"> <h2>报修单管理</h2> <el-button type="primary" @click="showCreateDialog = true">新增报修</el-button> <el-table :data="list" style="width: 100%; margin-top: 20px"> <el-table-column prop="id" label="编号" width="100"></el-table-column> <el-table-column prop="houseInfo" label="房屋" width="180"></el-table-column> <el-table-column prop="category" label="类型" width="120"> <template #default="scope"> <span :class="{'text-danger': scope.row.category === '水电'}">{{ scope.row.category }}</span> </template> </el-table-column> <el-table-column prop="status" label="状态" width="120"> <template #default="scope"> <el-tag :type="statusTagType(scope.row.status)">{{ statusText(scope.row.status) }}</el-tag> </template> </el-table-column> <el-table-column label="操作" width="180"> <template #default="scope"> <el-button size="mini" @click="handleEdit(scope.row)">编辑</el-button> <el-button size="mini" type="danger" @click="handleDelete(scope.row.id)">删除</el-button> </template> </el-table-column> </el-table> <!-- 新增弹窗 --> <el-dialog title="新增报修单" :visible.sync="showCreateDialog"> <el-form :model="form" :rules="rules" ref="formRef"> <el-form-item label="房屋" prop="houseId"> <el-select v-model="form.houseId" placeholder="请选择房屋" clearable> <el-option v-for="h in houseOptions" :key="h.id" :label="h.fullAddress" :value="h.id"></el-option> </el-select> </el-form-item> <el-form-item label="类型" prop="category"> <el-select v-model="form.category" placeholder="请选择类型"> <el-option label="水电" value="水电"></el-option> <el-option label="门窗" value="门窗"></el-option> <el-option label="网络" value="网络"></el-option> </el-select> </el-form-item> <el-form-item label="描述" prop="description"> <el-input type="textarea" v-model="form.description" :rows="3"></el-input> </el-form-item> </el-form> <div slot="footer"> <el-button @click="showCreateDialog = false">取消</el-button> <el-button type="primary" @click="submitForm">确认</el-button> </div> </el-dialog> </div> </template> <script> import { listRepairOrders, createRepairOrder, deleteRepairOrder } from '@/api/repairApi' export default { name: 'RepairList', data() { return { list: [], showCreateDialog: false, form: { houseId: null, category: '', description: '' }, rules: { houseId: [{ required: true, message: '请选择房屋', trigger: 'change' }], category: [{ required: true, message: '请选择类型', trigger: 'change' }], description: [{ required: true, message: '请填写描述', trigger: 'blur' }] }, houseOptions: [] // 房屋下拉选项,需从 API 获取 } }, async mounted() { await this.loadList() await this.loadHouseOptions() // 提前加载房屋列表,避免弹窗打开时空白 }, methods: { async loadList() { try { const res = await listRepairOrders() this.list = res.data.map(item => ({ ...item, houseInfo: `${item.buildingCode}-${item.unitNo}-${item.floorNo}-${item.roomNo}` })) } catch (err) { this.$message.error('加载报修单失败:' + err.message) } }, async loadHouseOptions() { // 此处应调用 houseApi 获取,为简化示例省略 this.houseOptions = [ { id: 1, fullAddress: 'A栋-1单元-1层-101' }, { id: 2, fullAddress: 'A栋-1单元-1层-102' } ] }, statusText(status) { const map = { 0: '待受理', 1: '处理中', 2: '已完成' } return map[status] || '未知' }, statusTagType(status) { return status === 0 ? 'warning' : status === 1 ? 'primary' : 'success' }, async submitForm() { this.$refs.formRef.validate(async valid => { if (valid) { try { await createRepairOrder(this.form) this.$message.success('新增成功') this.showCreateDialog = false this.form = { houseId: null, category: '', description: '' } await this.loadList() // 刷新列表 } catch (err) { this.$message.error('提交失败:' + err.message) } } }) }, handleDelete(id) { this.$confirm('确认删除该报修单?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }).then(async () => { try { await deleteRepairOrder(id) this.$message.success('删除成功') await this.loadList() } catch (err) { this.$message.error('删除失败:' + err.message) } }) } } } </script>逻辑说明:
listRepairOrders()返回的是后端 JSON,前端不做任何字段改名,而是用map生成houseInfo字符串,保持数据源纯净;statusText()和statusTagType()是纯函数,不依赖this,方便单元测试;submitForm中this.$refs.formRef.validate()是 Element UI 表单校验入口,比手动if (!this.form.houseId)更健壮;- 删除用
this.$confirm而非直接alert,符合企业级交互习惯。
3.3 API 封装:axios 实例 + 统一错误拦截,不是裸写axios.get
src/api/repairApi.js:
import axios from 'axios' // 创建独立 axios 实例,避免污染全局 const repairApi = axios.create({ baseURL: '/api/repair', // 与后端 Controller @RequestMapping 一致 timeout: 10000 }) // 请求拦截:自动带 token repairApi.interceptors.request.use( config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }, error => Promise.reject(error) ) // 响应拦截:统一错误处理 repairApi.interceptors.response.use( response => { // 后端约定:{ code: 200, data: xxx, msg: 'ok' } if (response.data.code === 200) { return response.data } else { return Promise.reject(new Error(response.data.msg || '请求失败')) } }, error => { if (error.response?.status === 401) { localStorage.removeItem('token') window.location.href = '/login' } return Promise.reject(error) } ) export function listRepairOrders() { return repairApi.get('/list') } export function createRepairOrder(data) { return repairApi.post('/create', data) } export function deleteRepairOrder(id) { return repairApi.delete(`/delete/${id}`) }参数说明:
baseURL: '/api/repair'对应 SpringBoot Controller 的@RequestMapping("/api/repair"),前后端路径严格对齐;interceptors.response中判断response.data.code,这是与后端约定的响应体结构,不是默认的 HTTP 状态码;401拦截后跳转/login,比在每个页面catch里写重定向更干净。
4. 前后端联调与数据库落地:让数据真正流动起来
光有前后端代码,不等于系统跑通。这一章解决“为什么我点了按钮没反应”“为什么数据库里没数据”“为什么页面显示 null”。
4.1 SpringBoot Controller 设计:RESTful 规范 + DTO 分离 + 全局异常捕获
RepairOrderController.java示例:
@RestController @RequestMapping("/api/repair") @RequiredArgsConstructor public class RepairOrderController { private final RepairOrderService repairOrderService; /** * 查询报修单列表(支持分页) * GET /api/repair/list?page=1&size=10 */ @GetMapping("/list") public Result<Page<RepairOrderVO>> list( @RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size) { Pageable pageable = PageRequest.of(page - 1, size); Page<RepairOrderVO> result = repairOrderService.list(pageable); return Result.success(result); } /** * 新增报修单 * POST /api/repair/create * Body: { "houseId": 1, "category": "水电", "description": "漏水" } */ @PostMapping("/create") public Result<String> create(@Valid @RequestBody RepairOrderCreateDTO dto) { repairOrderService.create(dto); return Result.success("创建成功"); } /** * 删除报修单 * DELETE /api/repair/delete/123 */ @DeleteMapping("/delete/{id}") public Result<String> delete(@PathVariable Long id) { repairOrderService.delete(id); return Result.success("删除成功"); } }关键点:
@Valid @RequestBody RepairOrderCreateDTO:DTO 类上加@NotNull等校验注解,SpringBoot 自动拦截非法请求并返回 400;Pageable pageable = PageRequest.of(page - 1, size):JPA 分页从 0 开始,前端传page=1,后端转成0;Result<...>是自定义响应包装类,强制所有接口返回统一结构,避免前端res.data有时是数组、有时是对象。
Result.java:
@Data @AllArgsConstructor @NoArgsConstructor public class Result<T> { private int code; private String msg; private T data; public static <T> Result<T> success(T data) { return new Result<>(200, "ok", data); } public static Result<String> success(String msg) { return new Result<>(200, msg, null); } public static <T> Result<T> fail(String msg) { return new Result<>(500, msg, null); } }4.2 数据库初始化:schema.sql + data.sql 双文件,不是靠 Hibernate 自动生成
Hibernatehibernate.hbm2ddl.auto=create只用于开发初期,毕设必须提供可执行的 SQL 脚本,这是答辩硬性材料。在src/main/resources下建:
schema.sql:建表语句(含注释、索引、外键)
-- schema.sql CREATE TABLE `building` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', `code` varchar(20) NOT NULL COMMENT '楼栋编码,如A栋', `unit_count` int NOT NULL DEFAULT '0' COMMENT '单元数', `floor_count` int NOT NULL DEFAULT '0' COMMENT '层数', PRIMARY KEY (`id`), UNIQUE KEY `uk_code` (`code`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='楼栋信息'; CREATE TABLE `house` ( `id` bigint NOT NULL AUTO_INCREMENT, `building_id` bigint NOT NULL COMMENT '所属楼栋ID', `unit_no` varchar(10) NOT NULL COMMENT '单元号', `floor_no` int NOT NULL COMMENT '楼层', `room_no` varchar(10) NOT NULL COMMENT '房号', `owner_name` varchar(50) DEFAULT NULL COMMENT '业主姓名', `phone` varchar(20) DEFAULT NULL COMMENT '联系电话', PRIMARY KEY (`id`), UNIQUE KEY `uk_building_unit_floor_room` (`building_id`, `unit_no`, `floor_no`, `room_no`), KEY `idx_building_id` (`building_id`), CONSTRAINT `fk_house_building_id` FOREIGN KEY (`building_id`) REFERENCES `building` (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;data.sql:插入基础数据(3 条楼栋、10 条房屋、5 条报修单)
-- data.sql INSERT INTO `building` (`code`, `unit_count`, `floor_count`) VALUES ('A栋', 2, 18), ('B栋', 2, 18), ('C栋', 2, 18); INSERT INTO `house` (`building_id`, `unit_no`, `floor_no`, `room_no`, `owner_name`, `phone`) VALUES (1, '1单元', 1, '101', '张三', '13800138000'), (1, '1单元', 1, '102', '李四', '13800138001'), -- ... 其他房屋 ; INSERT INTO `repair_order` (`house_id`, `category`, `status`, `description`, `create_time`) VALUES (1, '水电', 0, '厨房水龙头漏水', '2024-05-01 08:30:00'), (2, '门窗', 1, '阳台门关不严', '2024-05-02 14:20:00');提示:SQL 文件必须用 UTF-8 编码保存,MySQL 客户端执行时加
--default-character-set=utf8mb4,否则中文注释变乱码。
4.3 联调排错三板斧:curl、浏览器 Network、后端日志
当 Vue 页面点击“新增”没反应,按顺序排查:
用 curl 直接调后端接口(绕过前端):
curl -X POST http://localhost:8080/api/repair/create \ -H "Content-Type: application/json" \ -d '{"houseId":1,"category":"水电","description":"测试"}'- 如果返回
{"code":200,"msg":"ok","data":null}→ 后端 OK,问题在前端; - 如果返回
400 Bad Request→ 检查 DTO 校验失败原因(看控制台日志); - 如果返回
500→ 看 SpringBoot 控制台堆栈。
- 如果返回
浏览器 F12 → Network → 查看请求详情:
- 看 Request URL 是否为
http://localhost:8080/api/repair/create(不是/api/repair/create/多了个斜杠); - 看 Request Payload 是否为合法 JSON(无 trailing comma);
- 看 Response Headers 是否有
Content-Type: application/json;charset=UTF-8。
- 看 Request URL 是否为
后端日志定位:
在application.yml加日志级别:logging: level: com.example.propertymgmt: debug org.springframework.web.servlet.DispatcherServlet: debug启动后看控制台是否有
Mapped "{[/api/repair/create],methods=[POST]}",确认 Controller 被扫描到。
5. 高分毕设必踩的 5 个坑:血泪经验总结,别等答辩被问住才后悔
高分和及格的分水岭,往往就在这些“小地方”。我见过太多同学功能全有,却因一个坑被老师当场质疑“这真是你写的吗?”。
5.1 坑:Vue 打包后静态资源 404,页面白屏
现象:npm run build生成dist文件夹,把dist整个拷贝到 SpringBoot 的src/main/resources/static下,启动项目访问http://localhost:8080显示空白,F12 看 Network 里js/app.xxx.js404。
原因:Vue CLI 默认public/index.html中 script 路径是/js/app.xxx.js,但 SpringBoot 静态资源根路径是/,所以它去请求http://localhost:8080/js/app.xxx.js,而实际文件在static/js/下。
解决:修改vue.config.js,设publicPath为相对路径:
// vue.config.js module.exports = { publicPath: './', // 关键!不是 '/',也不是 '' outputDir: 'dist', assetsDir: 'static' }重新npm run build,此时index.html中 script 路径变成static/js/app.xxx.js,SpringBoot 能正确映射。
5.2 坑:MySQL 中文乱码,日志打印 ????
现象:数据库表字段设了COMMENT '楼栋信息',但 Navicat 里显示????,SpringBoot 日志打印 SQL 也显示INSERT INTO building (code) VALUES ('A??')。
原因:MySQL 服务端、数据库、表、连接四层编码不一致。常见是服务端character_set_server=utf8,但新建数据库没指定CHARSET=utf8mb4。
解决:
- MySQL 命令行执行:
ALTER DATABASE propertymgmt CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci; ALTER TABLE building CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; application.yml中 JDBC URL 加参数:spring: datasource: url: jdbc:mysql://localhost:3306/propertymgmt?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=Asia/Shanghai
5.3 坑:JPA save() 后 getId() 返回 null
现象:RepairOrder order = new RepairOrder(); order.setHouseId(1L); repairOrderRepository.save(order); System.out.println(order.getId());打印null。
原因:实体类@Id字段没配@GeneratedValue(strategy = GenerationType.IDENTITY),或 MySQL 表id字段没设AUTO_INCREMENT。
解决:
- 实体类:
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - MySQL 表:
ALTER TABLE repair_order MODIFY id BIGINT AUTO_INCREMENT;
5.4 坑:Element UI 表单校验不触发,this.$refs.formRef.validate()一直返回 false
现象:表单有el-form-item,但validate()总是false,控制台无报错。
原因:el-form-item的prop值与v-model绑定的 data 字段名不一致。例如prop="houseId",但 data 里是form.house_id。
解决:确保prop值完全匹配 data 字段路径,且el-form必须有ref="formRef",el-form-item必须有prop,v-model必须绑定到form.xxx。
5.5 坑:Vue 路由嵌套路由不显示子组件
现象:访问/repair/list页面空白,但/login能正常显示。
原因:MainLayout.vue中没写<router-view>插槽。
解决:MainLayout.vue必须包含:
<template> <div class="main-layout"> <Header /> <Sidebar /> <div class="content"> <router-view /> <!-- 关键!没有这行,子路由永远不渲染 --> </div> </div> </template>6. 让毕设脱颖而出的 3 个实战技巧:从“能跑”到“值得给高分”
答辩老师看毕设,前 3 分钟决定印象分。下面这三件事,花 2 小时做完,能让你的项目在一堆“能用就行”的作品里立刻被记住。
6.1 技巧一:用 Swagger 自动生成 API 文档,答辩时现场演示
别再手写 Word 文档列接口。SpringBoot 集成 Swagger,让接口自描述、可调试、可导出。
添加依赖:
<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency>配置类SwaggerConfig.java:
@Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.basePackage("com.example.propertymgmt.controller")) .paths(PathSelectors.any()) .build() .apiInfo(apiInfo()); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("小区物业管理系统 API 文档") .description("毕设项目后端接口说明") .version("1.0") .build(); } }启动项目后访问http://localhost:8080/swagger-ui.html,所有@RestController接口自动列出,支持在线调试。答辩时打开这个页面,点 “Try it out”,输入参数,点 Execute,当场返回 JSON 结果——老师会点头:“嗯,工程规范。”
6.2 技巧二:给关键操作加 Excel 导出,且表头中文可读
毕设常被问:“数据怎么导出?” 如果只答“用 Navicat”,显得业余。加一个导出按钮,导出当前列表为 Excel,表头是“报修编号”“房屋地址”“报修类型”,不是idhouseIdcategory。
后端RepairOrderController加接口:
@GetMapping("/export") public void export(HttpServletResponse response) throws IOException { List<RepairOrderVO> list = repairOrderService.listAll(); // 获取全部 // 使用 Apache POI 或更轻量的 easyexcel EasyExcel.write(response.getOutputStream(), RepairOrderExportDTO.class) .sheet("报修单列表") .doWrite(list.stream().map(this::convertToExportDTO).collect(Collectors.toList())); }RepairOrderExportDTO.java(专为导出设计的 DTO):
@Data public class RepairOrderExportDTO { @ExcelProperty("报修编号") private Long id; @ExcelProperty("房屋地址") private String houseAddress; // 拼接好的字符串 @ExcelProperty("报修类型") private String category; @ExcelProperty("状态") private String statusText; // 0→待受理 @ExcelProperty("创建时间") @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime createTime; }前端调用:
handleExport() { window.open('/api/repair/export', '_blank') }注意:
window.open绕过 axios 拦截器,所以不用带 token;后端export方法不校验权限(毕设简化),但加了@ResponseBody注解会导致返回 JSON,必须用HttpServletResponse直接写流。
6.3 技巧三:用 Actuator 暴露健康检查端点,证明你懂运维
SpringBoot Actuator 是生产级监控入口。加一个/actuator/health,返回{ "status": "UP" },说明你的应用是活的。
加依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>application.yml开放端点:
management: endpoints: web: exposure: <p> <a href="https://download.csdn.net/download/weixin_55305220/90520684" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>