SpringBoot+Vue智慧物业系统:工单闭环与定时升级实战
2026/9/16 16:13:04 网站建设 项目流程

简介:本资源是一套完整的小区智慧物业管理系统毕业设计项目,面向计算机专业本科生及Java全栈初学者,聚焦物业缴费、报修投诉、房屋管理、公告通知等核心业务场景,提供可直接运行的前后端分离解决方案。压缩包共434个文件,含100个Java后端逻辑类、80个Vue前端页面组件、30个JS工具与请求脚本、28个CSS样式文件及29个SVG图标资源,辅以SQL建表语句、YML配置、MyBatis映射XML等关键工程文件,整体55.62MB,结构清晰、模块解耦度高。已有269人学习下载,适合作为期末大作业或课程设计参考。读者可获得SpringBoot+Vue2双端可调试源码、基于MyBatis-Plus的数据库操作封装、SpringSecurity权限控制实现、Quartz定时任务示例、EasyExcel导入导出功能,以及Fantastic-admin后台框架下的完整UI交互逻辑,涵盖从登录鉴权、数据列表到表单提交的全流程实践代码。

1. 这不是又一个“登录+列表”的毕业设计,而是一套能跑通业主报修、物业派单、工单闭环、费用催缴全链路的 SpringBoot + Vue 实战系统

你下载过太多标着“SpringBoot 毕业设计”的压缩包——解压后发现只有用户管理、角色权限、增删改查三张表,连真实业务场景的影子都没有。但这个小区智慧物业管理系统不同:它内置了SysRepair(报修工单)、SysComplaint(投诉建议)、SysRoom(房屋与楼栋关系)、SysUserInfoData(业主身份与房产绑定)四类核心业务实体,后端用Quartz定时扫描超时未处理工单并自动升级,前端通过Ant-Design-Vuea-table+a-modal实现工单状态流转可视化,连短信通知都封装了SmsUtils.class并预留了对接通道。它面向的是真实小区场景下的三方协作:业主提交诉求 → 物业中心分派 → 维修人员接单 → 完成后拍照上传 → 业主评价闭环。适合需要交付可演示、有业务深度、能讲清技术选型逻辑的本科毕设或课程大作业,尤其适合 Java 后端基础尚可、但缺乏完整项目串联经验的同学——因为它的模块边界清晰、接口契约明确、数据库字段命名规范,你改一个SysRepair.status枚举值,就能立刻看到前端按钮状态和后端定时任务行为同步变化。

2. 后端服务 em_server:从 SpringBoot 主体到 MyBatis-Plus 动态 SQL 的落地细节

2.1 SpringBoot 2.7.x 与 SpringSecurity 5.7.x 的兼容性配置要点

该项目基于 SpringBoot 2.7.x 构建(非 3.x),因此SecurityConfig.class中的配置方式必须匹配 SpringSecurity 5.7.x 的 DSL 风格。关键点在于:不能使用http.authorizeHttpRequests()的 lambda 写法直接链式调用.anyRequest().authenticated(),而必须显式声明authorizeHttpRequestsrequestMatchers规则。否则启动时会抛出java.lang.IllegalStateException: Failed to load ApplicationContext。正确写法如下:

@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeHttpRequests(authz -> authz .requestMatchers("/login", "/register", "/swagger-ui/**", "/v3/api-docs/**").permitAll() .requestMatchers("/admin/**").hasRole("ADMIN") .requestMatchers("/user/**").hasAnyRole("USER", "ADMIN") .anyRequest().authenticated() ); return http.build(); } }

提示:requestMatchers必须放在authorizeHttpRequests内部,且.anyRequest().authenticated()是兜底规则,不可省略。若漏掉/v3/api-docs/**,Swagger UI 将无法加载接口文档;若未对/admin/**/user/**做角色隔离,会导致普通用户也能访问后台管理接口。

2.2 MyBatis-Plus 3.5.x 的代码生成器与实体映射实践

em_server使用 MyBatis-Plus 3.5.x(非 4.x),其@TableName@TableField注解需严格对应数据库表结构。以SysRepair.class为例,其字段映射并非简单驼峰转下划线,而是存在业务语义映射:

@TableName("sys_repair") public class SysRepair implements Serializable { private static final long serialVersionUID = 1L; @TableId(type = IdType.AUTO) private Long id; @TableField("room_id") // 显式指定字段名,因数据库为 room_id 而非 roomId private Long roomId; @TableField("repair_status") // 状态字段为 repair_status,枚举值:0-待处理、1-处理中、2-已完成、3-已关闭 private Integer repairStatus; @TableField("create_time") private LocalDateTime createTime; @TableField(exist = false) // 该字段不存于数据库,用于接收前端传入的房号字符串(如"3栋2单元501") private String roomNo; }
2.2.1 数据库表字段与 Java 实体关键映射对照表
数据库字段名Java 字段名注解说明业务含义
room_idroomId@TableField("room_id")关联sys_room.id,非外键约束,靠业务逻辑维护一致性
repair_statusrepairStatus@TableField("repair_status")整型状态码,前端展示需转换为中文(待处理/处理中/已完成/已关闭)
handle_user_idhandleUserId@TableField("handle_user_id")派单后记录维修人员 ID,为空表示未派单
finish_timefinishTime@TableField("finish_time")LocalDateTime类型,MySQL 对应datetime,非timestamp

注意:SysRoom.classbuildingNo(楼栋号)、unitNo(单元号)、roomNo(房号)三个字段共同构成唯一房屋标识,但数据库未建联合唯一索引——若需防止重复录入,应在SysRoomMapperinsert方法前加select count(*)校验逻辑,而非依赖数据库约束

2.3 Quartz 定时任务在工单超时升级中的具体实现

SysRepair工单的超时升级逻辑由Quartz实现,核心是RepairTimeoutJob.class(虽未在文件列表中显式列出,但SecurityConfig.class引用了@EnableScheduling,且application.yml中配置了spring.quartz.job-store-type=jdbc)。其触发逻辑为:每 5 分钟扫描repair_status = 0(待处理)且create_time超过 2 小时的工单,将其状态更新为4(超时自动升级),并插入一条系统通知记录到sys_notice表。

@Component public class RepairTimeoutJob { @Autowired private SysRepairMapper repairMapper; @Autowired private SysNoticeMapper noticeMapper; @Scheduled(cron = "0 */5 * * * ?") // 每5分钟执行一次 public void checkTimeoutRepairs() { LocalDateTime twoHoursAgo = LocalDateTime.now().minusHours(2); List<SysRepair> timeoutRepairs = repairMapper.selectList( new QueryWrapper<SysRepair>() .eq("repair_status", 0) .lt("create_time", twoHoursAgo) ); for (SysRepair repair : timeoutRepairs) { repair.setRepairStatus(4); // 升级为超时状态 repairMapper.updateById(repair); // 插入系统通知 SysNotice notice = new SysNotice(); notice.setTitle("工单超时提醒"); notice.setContent("报修单【" + repair.getId() + "】已超时2小时,请及时处理"); notice.setCreateTime(LocalDateTime.now()); notice.setTargetType("REPAIR"); notice.setTargetId(repair.getId()); noticeMapper.insert(notice); } } }

提示:cron = "0 */5 * * * ?"表示从秒开始(0秒),每5分钟触发;lt("create_time", twoHoursAgo)使用 MyBatis-Plus 的QueryWrapper构造时间范围查询,避免手写 SQL;repairMapper.updateById()仅更新指定字段,不会覆盖其他值。

3. 前端项目 em_ui:Vue2 + Ant-Design-Vue 的工单状态机驱动交互

3.1 Vue2 生命周期与 Axios 请求拦截的协同设计

em_ui基于 Vue2.6.x(非 Vue3),其请求统一由Axios.js封装,并在main.js中注册全局请求/响应拦截器。关键设计在于:响应拦截器中对401状态码的处理,不是简单跳转登录页,而是先清空本地 token,再重定向到/login?redirect=当前路径,确保用户重新登录后能回到原页面:

// utils/request.js import axios from 'axios' import { message } from 'ant-design-vue' const service = axios.create({ baseURL: '/api', timeout: 10000 }) // 请求拦截器 service.interceptors.request.use( config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }, error => Promise.reject(error) ) // 响应拦截器 service.interceptors.response.use( response => response, error => { if (error.response?.status === 401) { localStorage.removeItem('token') const redirect = encodeURIComponent(window.location.pathname + window.location.search) window.location.href = `/login?redirect=${redirect}` } else if (error.response?.status >= 500) { message.error('服务器异常,请稍后重试') } return Promise.reject(error) } ) export default service

注意:encodeURIComponent对当前路径编码,避免?&导致重定向 URL 解析错误;window.location.href是 Vue2 中最可靠的页面跳转方式,比this.$router.push更底层、更稳定。

3.2 Ant-Design-Vue 表格组件与工单状态流转的双向绑定

工单列表页(views/repair/index.vue)使用<a-table>展示SysRepair数据,其状态列(repair_status)通过customRender渲染为带颜色的状态标签,并支持点击切换状态:

<a-table :columns="columns" :data-source="repairList" row-key="id"> <span slot="repairStatus" slot-scope="text, record"> <a-tag :color="statusColor(record.repairStatus)"> {{ statusText(record.repairStatus) }} </a-tag> <a-dropdown v-if="record.repairStatus !== 2 && record.repairStatus !== 3"> <a class="ant-dropdown-link" @click="e => e.preventDefault()"> 更改状态 <a-icon type="down" /> </a-dropdown> <a-menu slot="overlay"> <a-menu-item v-for="item in statusOptions(record.repairStatus)" :key="item.value" @click="changeStatus(record, item.value)"> {{ item.label }} </a-menu-item> </a-menu> </a-dropdown> </span> </a-table>
3.2.1 状态映射与操作权限控制逻辑
export default { data() { return { statusMap: { 0: { text: '待处理', color: 'blue' }, 1: { text: '处理中', color: 'orange' }, 2: { text: '已完成', color: 'green' }, 3: { text: '已关闭', color: 'red' }, 4: { text: '已升级', color: 'purple' } } } }, methods: { statusText(status) { return this.statusMap[status]?.text || '未知' }, statusColor(status) { return this.statusMap[status]?.color || 'default' }, // 根据当前状态,返回可切换的目标状态选项 statusOptions(currentStatus) { const options = [] if (currentStatus === 0) { options.push({ value: 1, label: '转为处理中' }) options.push({ value: 3, label: '关闭此工单' }) } else if (currentStatus === 1) { options.push({ value: 2, label: '标记为已完成' }) options.push({ value: 3, label: '关闭此工单' }) } return options }, changeStatus(record, newStatus) { this.$confirm({ title: '确认更改状态?', content: `将工单【${record.id}】状态改为【${this.statusText(newStatus)}】`, onOk: () => { this.$http.put(`/repair/${record.id}/status`, { repairStatus: newStatus }) .then(() => { this.$message.success('状态更新成功') this.fetchRepairList() // 重新拉取列表 }) } }) } } }

提示:statusOptions()动态生成下拉菜单项,避免用户将“已完成”工单误切回“待处理”;this.$http.put()调用后端/repair/{id}/status接口,该接口在RepairController.java中定义,接收 JSON Body 并校验新状态是否合法。

4. 数据库初始化与环境适配:从 MySQL 5.7 到达梦/Oracle 的迁移准备

4.1schema.sqldata.sql的结构化拆分策略

项目提供的数据库脚本(database/目录下)包含两个核心文件:schema.sql(建表语句)和data.sql(初始数据)。其设计遵循生产级规范:所有表均使用InnoDB引擎、utf8mb4字符集,并显式声明COMMENT。例如sys_repair表:

CREATE TABLE `sys_repair` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', `room_id` bigint NOT NULL COMMENT '关联房屋ID', `title` varchar(100) NOT NULL COMMENT '报修标题', `content` text COMMENT '报修内容', `repair_status` tinyint NOT NULL DEFAULT '0' COMMENT '状态:0-待处理 1-处理中 2-已完成 3-已关闭 4-已升级', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `finish_time` datetime NULL DEFAULT NULL COMMENT '完成时间', PRIMARY KEY (`id`), KEY `idx_room_id` (`room_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='报修工单表';

注意:DEFAULT CURRENT_TIMESTAMP仅对create_time生效,finish_timeNULL,符合业务逻辑;KEY idx_room_idroom_id字段添加索引,避免按房号查询时全表扫描。

4.2 达梦/Oracle 兼容性改造的三处关键修改

若需将系统迁移到达梦(DM8)或 Oracle 12c+,需修改以下三处(无需重写全部 SQL):

修改点MySQL 原写法达梦/Oracle 改写说明
时间函数NOW()SYSDATE达梦与 Oracle 均支持SYSDATE获取当前时间
自增主键AUTO_INCREMENTGENERATED ALWAYS AS IDENTITY(Oracle 12c+)或IDENTITY(达梦)Oracle 需在建表后单独ALTER TABLE ... ADD CONSTRAINT ... PRIMARY KEY
文本类型textCLOBCLOB可存储更大文本,且 Oracle/达梦 对CLOB的索引和查询优化更成熟

示例达梦兼容建表语句片段:

CREATE TABLE SYS_REPAIR ( ID BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, ROOM_ID BIGINT NOT NULL, TITLE VARCHAR(100) NOT NULL, CONTENT CLOB, REPAIR_STATUS TINYINT DEFAULT 0, CREATE_TIME DATETIME DEFAULT SYSDATE, FINISH_TIME DATETIME ); COMMENT ON COLUMN SYS_REPAIR.REPAIR_STATUS IS '状态:0-待处理 1-处理中 2-已完成 3-已关闭 4-已升级';

提示:达梦默认大小写不敏感,表名建议全大写;Oracle 中VARCHAR2(100)替代VARCHAR(100);所有COMMENT需用COMMENT ON COLUMN单独添加,不能写在建表语句中。

5. 本地运行验证与高频问题排查:从端口冲突到跨域配置的实操清单

5.1 启动前必须检查的 4 项环境配置

检查项默认值修改位置验证方式
后端端口8080em_server/src/main/resources/application.ymlserver.portnetstat -ano | findstr :8080(Windows)或lsof -i :8080(Mac/Linux)
前端代理目标http://localhost:8080em_ui/vue.config.jsdevServer.proxy['/api'].target打开浏览器开发者工具 Network 标签,查看请求 URL 是否为http://localhost:8080/api/xxx
数据库连接jdbc:mysql://localhost:3306/em_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghaiem_server/src/main/resources/application.ymlspring.datasource.url在 MySQL 客户端执行SHOW DATABASES LIKE 'em_db';确认库存在
JWT 密钥em_secret_key_2024em_server/src/main/resources/application.ymljwt.secret启动后访问http://localhost:8080/swagger-ui/index.html,点击任意接口的Authorize按钮,输入Bearer+ 有效 token 测试

提示:若em_server启动失败且报Failed to configure a DataSource,90% 是application.ymlspring.datasource.password为空或密码错误;若em_ui页面空白且控制台报Failed to fetch,优先检查vue.config.js代理配置是否遗漏/api前缀。

5.2 Swagger 接口文档的快速验证路径

项目集成springdoc-openapi-ui(非旧版swagger-springmvc),启动后访问http://localhost:8080/swagger-ui/index.html即可查看完整 API。重点验证以下三个核心接口:

  1. 登录接口POST /login,Body 传{"username":"admin","password":"123456"},成功返回token字段;
  2. 工单列表接口GET /repair?page=1&size=10,响应中records数组应包含至少 1 条测试数据;
  3. 状态更新接口PUT /repair/{id}/status,Path 参数填实际工单 ID,Body 传{"repairStatus":1},返回200 OK且数据库repair_status字段更新。

GET /repair返回空数组,检查data.sql是否已执行(特别是sys_repair表是否有初始数据);若PUT /repair/{id}/status403 Forbidden,确认登录 token 是否有效且请求头Authorization: Bearer xxx已携带。

5.3 一个被忽略但影响演示效果的关键技巧:EasyExcel 导出模板的字段顺序控制

em_server使用EasyExcel实现费用账单导出(SysFeeService.class),但默认导出的 Excel 列顺序与数据库字段顺序一致,不符合财务报表阅读习惯。必须通过@ExcelProperty注解的value属性显式声明中文列名,并用index属性控制顺序

public class FeeExportVo { @ExcelProperty(value = "房号", index = 0) private String roomNo; @ExcelProperty(value = "业主姓名", index = 1) private String ownerName; @ExcelProperty(value = "费用类型", index = 2) private String feeType; @ExcelProperty(value = "应收金额", index = 3) private BigDecimal amount; @ExcelProperty(value = "缴费状态", index = 4) private String status; // 0-未缴 1-已缴 }

注意:index从 0 开始,必须连续无跳跃;value是 Excel 表头文字,与数据库字段名无关;若漏写index,EasyExcel 会按 Java 字段声明顺序排列,导致“费用类型”出现在“房号”之前,演示时易被质疑专业性。

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

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

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

立即咨询