SpringBoot+Vue+MySQL智慧社区管理系统:核心设计与部署指南
2026/9/17 2:55:46 网站建设 项目流程

简介:基于Java+SpringBoot+Vue+MySQL的智慧社区管理系统毕业设计源码,面向高校计算机专业学生的毕设、课设与期末大作业场景,覆盖居民信息管理、物业通知、服务预约、访客登记、车辆管理、费用缴纳等核心模块,同时界面美观、操作简单、管理便捷,系统可在校园、小区、公寓等多类社区场景中实际应用。压缩包共748个文件,约22.57MB,包含203个Java后端源码、141个Vue前端页面、1个SQL数据库脚本,以及XML配置、bat启动/构建脚本、图标样式等辅助资源;项目基于IntelliJ IDEA、MySQL 8.0、Maven、Navicat环境,已调试通过,导入即可运行。目前已有143人学习下载,除了完整源码与数据库脚本,还附带run.bat、build.bat等自动化脚本和前端图片、字体样式,整体结构清晰,能帮助快速理解SpringBoot+Vue前后端分离项目的分层实现与社区管理业务逻辑,可在此基础上二次开发,整体既能作为高分毕设模板,也可作为课程设计或期末作业的完整参考。

1. 智慧社区管理系统:从毕设标题看一套能落地的 SpringBoot + Vue 全栈模板

把「基于 java+springboot+vue+mysql 的智慧社区管理系统」这个标题拆开看,它其实是毕业设计里最典型的一类单体全栈项目:前端 Vue 负责页面交互,后端 SpringBoot 提供接口,MySQL 存业务数据。真正做过这类项目的人会告诉你,难点从来不在增删改查,而在三件事:业主、物业、管理员共用一套登录入口时权限怎么隔离;房产、报修、缴费、访客这些表怎么关联才不乱;以及前端上传的图片、视频流后端到底怎么吐给浏览器。这篇文章按我平时搭这类系统的顺序来讲,先定表结构,再写后端认证,再套前端页面,最后落到部署和验收技巧上。新手能照着把环境跑起来,熟手可以重点看权限模型和 m3u8 播放这两节的取舍。

2. 先定数据结构:MySQL 表设计与存储过程,别让权限模型拖垮联表

2.1 房产、业主、访客、报修、缴费五张核心表怎么关联

智慧社区的核心业务线是「人 - 房 - 事」。人指业主和访客,房指楼栋单元房间,事指报修、缴费、投诉这些动作。我一般先画一张最简单的 ER 草图:房产表房间挂在楼栋下,业主表通过 owner_id 和房产表关联,报修表和缴费表都带 owner_id 作为外键,访客表单独记录来访时间与离开时间。先别急着加一堆冗余字段,五张表能跑通,再回头补索引。

CREATE TABLE building ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(20) NOT NULL COMMENT '楼栋名,如 3 栋', address VARCHAR(100) DEFAULT NULL ) COMMENT '楼栋表'; CREATE TABLE room ( id BIGINT PRIMARY KEY AUTO_INCREMENT, building_id BIGINT NOT NULL, room_no VARCHAR(20) NOT NULL, owner_id BIGINT DEFAULT NULL, KEY idx_building (building_id) ) COMMENT '房间表'; CREATE TABLE owner ( id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(30) NOT NULL, phone VARCHAR(20) NOT NULL, id_card VARCHAR(18) DEFAULT NULL, wechat VARCHAR(50) DEFAULT NULL ) COMMENT '业主表'; CREATE TABLE repair ( id BIGINT PRIMARY KEY AUTO_INCREMENT, owner_id BIGINT NOT NULL, type VARCHAR(20) COMMENT '水/电/门禁/其他', description VARCHAR(500), status TINYINT DEFAULT 0 COMMENT '0待派单 1处理中 2已完成', create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) COMMENT '报修表'; CREATE TABLE bill ( id BIGINT PRIMARY KEY AUTO_INCREMENT, owner_id BIGINT NOT NULL, item_type VARCHAR(20) COMMENT '物业费/停车费/水费', amount DECIMAL(10,2) NOT NULL, status TINYINT DEFAULT 0 COMMENT '0未缴 1已缴', pay_time DATETIME DEFAULT NULL ) COMMENT '缴费表';

这段 SQL 里有几个细节值得说明。room 表里owner_id DEFAULT NULL是刻意的,因为空房状态必须能表达,不能用 0 占位。bill 的amountDECIMAL(10,2)而不是 FLOAT,财务金额用浮点类型会在累计时出现精度偏差,这点在答辩时经常被追问。create_time用了DEFAULT CURRENT_TIMESTAMP,插入时少写一个字段。真正的业务查询通常是「某业主的未缴账单」「某楼栋的待处理报修」,所以idx_building这种单列索引先加上,等数据量过万再评估联合索引。

2.2 用 RBAC 五表模型管理管理员、物业与业主权限

毕设里最常见的错误写法是给用户表加一个role字段,用 0、1、2 区分角色,然后在每个 Controller 里 if 判断。两个角色时还能撑住,一旦出现「物业经理能看全部报修,普通物业只能看自己片区的报修」这种需求,硬编码就崩了。RBAC 模型是更稳的底子,核心是五张表:用户表、角色表、权限表、用户角色关联表、角色权限关联表。

CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(30) NOT NULL UNIQUE, password VARCHAR(100) NOT NULL COMMENT 'BCrypt 加密', real_name VARCHAR(30), status TINYINT DEFAULT 1 ); CREATE TABLE sys_role ( id BIGINT PRIMARY KEY AUTO_INCREMENT, role_code VARCHAR(20) NOT NULL UNIQUE COMMENT 'ADMIN/MANAGER/OWNER', role_name VARCHAR(30) NOT NULL ); CREATE TABLE sys_permission ( id BIGINT PRIMARY KEY AUTO_INCREMENT, perm_code VARCHAR(50) NOT NULL UNIQUE COMMENT '如 repair:handle', perm_name VARCHAR(30), type TINYINT DEFAULT 1 COMMENT '1菜单 2按钮' ); CREATE TABLE sys_user_role ( user_id BIGINT NOT NULL, role_id BIGINT NOT NULL, PRIMARY KEY (user_id, role_id) ); CREATE TABLE sys_role_permission ( role_id BIGINT NOT NULL, permission_id BIGINT NOT NULL, PRIMARY KEY (role_id, permission_id) );

初始化数据按固定套路写:先插三条角色,再插权限码,最后用关联表把「管理员拥有全部权限」「物业拥有报修处理、访客登记权限」「业主拥有报修提交、缴费查询权限」配好。id 可以写死,因为这是种子数据,不会变。这套模型的好处是权限判断从「用户是什么角色」变成「用户拥有什么权限」,前端按钮级别的v-permission指令和后端拦截器可以共用同一套权限码。

2.2.1 权限码设计要按「资源:操作」命名

权限码不要叫repair1repair2这种无意义名字,用repair:listrepair:handlebill:exportvisitor:register这种「资源:操作」格式。前端判断hasPermission('repair:handle')决定派单按钮显不显示,后端拦截器校验同样的码。命名的一致性是这套方案唯一需要自律的地方,建议把权限码写进一个 Java 常量类,前后端都从接口文档里复制。

2.3 存储过程:AS 关键字那点坑与 MySQL 8 的写法

搜索热词里 mysql 存储过程出现频率很高,在智慧社区里最合适的场景是月底批量生成缴费账单。Java 代码循环 insert 一千条没问题,但放在存储过程里更接近业务语义,也方便在 Navicat 里直接测试。

DROP PROCEDURE IF EXISTS generate_monthly_bill; DELIMITER // CREATE PROCEDURE generate_monthly_bill(IN bill_month VARCHAR(7), IN amount DECIMAL(10,2)) BEGIN DECLARE done INT DEFAULT 0; DECLARE v_owner_id BIGINT; DECLARE cur CURSOR FOR SELECT id FROM owner WHERE status = 1; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1; OPEN cur; read_loop: LOOP FETCH cur INTO v_owner_id; IF done = 1 THEN LEAVE read_loop; END IF; INSERT INTO bill (owner_id, item_type, amount, status, bill_month) VALUES (v_owner_id, '物业费', amount, 0, bill_month); END LOOP; CLOSE cur; END // DELIMITER ;

写这段时有三个坑值得记住。第一,DELIMITER //必须要有,否则 MySQL 客户端碰到分号就认为语句结束了。第二,游标循环结束条件用CONTINUE HANDLER FOR NOT FOUND,这不是可选项,没有它存储过程会死循环。第三,表里如果有重复的业主,需要先SELECT DISTINCT id。调用方式是CALL generate_monthly_bill('2024-06', 120.00),参数bill_monthVARCHAR(7)而不是 DATE,因为 '2024-06' 这种格式用字符串存更直接。

3. SpringBoot 后端:认证链、接口分层与登录会话实现

3.1 登录鉴权用 JWT + 拦截器还是 Security?

Spring Security 功能全但学习曲线陡,毕业设计里更常见、也更稳妥的是 JWT + HandlerInterceptor 这套轻量组合。JWT 天然适合前后端分离:登录接口校验用户名密码后生成 token,前端存在 localStorage,每次请求在请求头带X-Token,后端拦截器解析 token、把用户信息放进 ThreadLocal。相比 Session 方案,后端不需要维护会话状态,接口给小程序或 App 复用也方便。

// JwtUtil.java 核心方法 public class JwtUtil { private static final SecretKey KEY = Keys.hmacShaKeyFor( "community-secret-key-2024".getBytes(StandardCharsets.UTF_8)); public static String generateToken(Long userId, String roleCode) { return Jwts.builder() .setSubject(String.valueOf(userId)) .claim("role", roleCode) .setExpiration(new Date(System.currentTimeMillis() + 7L * 24 * 3600 * 1000)) .signWith(KEY, SignatureAlgorithm.HS256) .compact(); } public static Claims parseToken(String token) { return Jwts.parserBuilder().setSigningKey(KEY).build() .parseClaimsJws(token).getBody(); } }

Keys.hmacShaKeyFor对密钥长度有要求,HS256 算法下密钥至少 32 字节,community-secret-key-2024刚好满足。过期时间设 7 天是折中方案,太短业主天天重新登录,太长token 泄露风险高。真实项目里密钥要放到配置文件的密文配置项里,不要硬编码。拦截器里放行/api/auth/login和静态资源路径,其余接口统一走 token 校验,校验失败返回 401 JSON。

3.2 按 Controller-Service-Mapper 拆包,报警功能给数组接口留好扩展位

后端工程结构我习惯按业务模块拆 controller/service/mapper 三层,而不是按技术层拆包。controller下建authroomrepairbillvisitor五个子包,每个包里放对应的 Controller。Service 层只做业务编排,事务注解加在 Service 实现类上。统一的返回结构是Result<T>,包含codemsgdata三个字段,前端 axios 拦截器只认这个结构。


接口方法说明权限码
/api/auth/loginPOST登录,返回 token公开
/api/owner/roomsGET当前业主的房间列表room:list
/api/repairsPOST提交报修repair:add
/api/repairs/{id}/handlePUT物业处理报修repair:handle
/api/bills/pendingGET未缴账单bill:list
/api/visitorsPOST访客登记visitor:register

这个表里值得注意的设计是「提交报修」和「处理报修」分开接口、分开权限码。业主能提交但不能处理,物业能处理但不能替业主提交。Controller 里不写业务判断,只在方法上标注需要的权限码,由拦截器统一校验。

3.2.1 前端启动参数与端口冲突排查

SpringBoot 默认端口 8080,最常见的问题就是端口被占用。排查命令是netstat -ano | findstr 8080,拿到 PID 后到任务管理器结束进程。开发环境我习惯在application.yml里显式配置端口和数据库连接参数。

server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/community?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: root hikari: maximum-pool-size: 10 minimum-idle: 5 servlet: multipart: max-file-size: 50MB max-request-size: 50MB mybatis-plus: mapper-locations: classpath:mapper/*.xml configuration: map-underscore-to-camel-case: true

serverTimezone=Asia/Shanghai不加,MySQL 8 会报时区错误。maximum-pool-size设 10 对毕设负载足够了,线程池开太大会占用数据库连接数。map-underscore-to-camel-case开启后,数据库的create_time自动映射成createTime,不用每个字段写 @TableField。max-file-size设 50MB 是为了后文要讲的前端上传视频流预留空间。

3.3 登录接口的业务逻辑细节

登录接口不要只做密码比对。正确流程是:先查用户是否存在且状态正常,再用BCryptPasswordEncoder.matches()比对密码,最后生成 token 并记录登录日志。日志表是加分项,答辩时可以说「通过登录日志实现了异常登录告警」。密码千万不要明文存储,BCryptPasswordEncoder是 Spring Security 里的工具类,单独引进来用就行。

4. Vue 前端工程:从 npm run dev 到路由和播放器

4.1 初始化 Vue 项目并安装依赖

智慧社区前端我一般选 Vue2 + Element UI 的组合,原因很现实:Element UI 的表格、表单、对话框组件在后台管理页面里开箱即用,网上的教程和踩坑记录也最多。如果选 Vue3 搭配 Element Plus 也不是不行,但部分老教程的写法要手动改成 Composition API。

npm install -g @vue/cli vue create community-admin cd community-admin npm install element-ui axios vue-router@3 hls.js npm run dev

vue-router@3这个版本号必须指定,Vue2 项目装 vue-router 4 会报错,因为 vue-router 4 只支持 Vue3。hls.js后面播放监控视频流要用,提前装好。npm 安装慢或者报错时,优先检查 Node 版本,Vue CLI 5 要求 Node 12 以上,推荐 Node 14 或 16 LTS 版本。运行npm run dev后浏览器访问http://localhost:8080,如果端口被占用,CLI 会自动加 1 变成 8081。

4.2 路由参数与页面鉴权该如何写

页面权限控制在 Vue 里分两层:路由守卫控制能不能进页面,按钮指令控制能不能看到操作按钮。路由守卫写在router/index.js里。

router.beforeEach((to, from, next) => { const token = localStorage.getItem('token'); if (to.path === '/login') { next(); } else if (!token) { next('/login'); } else { next(); } });

这段逻辑只判断了有没有 token,没判断 token 是否过期。实际开发中可以在 axios 响应拦截器里加一个 401 状态码的统一跳转,token 过期后后端返回 401,前端清空 localStorage 并跳转到登录页。路由传参有两种方式:this.$router.push({ path: '/repair/detail', query: { id: 1 } })后用this.$route.query.id接收;或者this.$router.push({ name: 'RepairDetail', params: { id: 1 } })后用this.$route.params.id接收。query 方式刷新页面参数还在,params 方式刷新就丢了,详情页传 id 建议用 query。

4.3 用 hls.js 在 Vue 里播放 m3u8 监控流

热搜词「vue 播放 m3u8」在智慧社区里对应的是监控摄像头视频回放。摄像头 RTSP 流不能直接被浏览器播放,需要转成 HLS 协议输出 m3u8 索引文件和 ts 分片。Chrome 不原生支持 m3u8,hls.js 库解决了这个问题。

<template> <div> <video ref="videoEl" controls autoplay muted style="width: 100%"></video> </div> </template> <script> import Hls from 'hls.js'; export default { name: 'VideoPlayer', props: { src: { type: String, required: true } }, mounted() { this.playVideo(); }, methods: { playVideo() { const video = this.$refs.videoEl; if (Hls.isSupported()) { const hls = new Hls(); hls.loadSource(this.src); hls.attachMedia(video); this.hls = hls; } else if (video.canPlayType('application/vnd.apple.mpegurl')) { video.src = this.src; } } }, beforeDestroy() { if (this.hls) { this.hls.destroy(); } } }; </script>

代码里有两个关键点:Hls.isSupported()判断浏览器是否支持 MSE,主流 Chrome、Edge、Firefox 都支持;video.canPlayType('application/vnd.apple.mpegurl')是 Safari 的原生 HLS 兜底方案,因为 Safari 自带 HLS 播放能力,不需要 hls.js 介入。组件销毁时调用hls.destroy()释放资源,否则页面切换后视频流还占着带宽。

4.3.1 直播地址与原生 video 标签的区别

如果是摄像头实时直播,src 直接指向http://服务器IP:8080/live/xxx.m3u8就行。如果是回放,后端接口通常返回{ url: '/record/20240601/xxx.m3u8' },前端拿到后拼接完整地址再传给组件。注意 m3u8 里的 ts 分片路径如果是相对的(比如../segment/001.ts),前端请求时会基于当前页面地址解析,容易 404。解决办法是让后端保证 m3u8 文件里的分片地址是完整可访问的 URL,或者把分片也放在同一个静态资源目录下。

5. 部署与验收:用一键脚本把前端产物和 jar 放到同一台服务器

前端开发完npm run build会生成dist目录,后端mvn clean package生成 jar 包。最省事的部署方式是让 Nginx 直接托管前端静态文件,同时反向代理后端接口,这样前端请求/api/xxx不会跨域。

cd /opt/community # 前端构建 cd community-admin npm run build cp -r dist/* /usr/share/nginx/html/community/ # 后端构建 cd ../server mvn clean package -DskipTests nohup java -jar target/community-server.jar \ --server.port=8081 \ --spring.profiles.active=prod \ > app.log 2>&1 &

-DskipTests跳过测试打包,避免测试用例失败导致部署中断。nohup让 jar 包在 SSH 断开后继续运行,日志输出到app.log--spring.profiles.active=prod指定生产环境配置,数据库密码等敏感信息放在application-prod.yml里,不要提交到 Git。

Nginx 配置里加两个 location,一个托静态文件,一个转发 API:

location /api/ { proxy_pass http://127.0.0.1:8081; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /upload/ { alias /data/community/upload/; expires 7d; }

proxy_pass http://127.0.0.1:8081;后面的分号不能丢,并且不要写路径。/upload/映射的是后端上传文件的存储目录,图片和视频文件直接由 Nginx 返回,不经过 Java 应用,减少 Tomcat 压力。

最后验证接口是否通畅,用 curl 测登录再测业务接口:

curl -X POST http://服务器IP/api/auth/login \ -H "Content-Type: application/json" \ -d '{"username":"admin","password":"123456"}'

拿到返回的 token 后再测试需要鉴权的接口,把 token 放进请求头。前端浏览器打开页面,开发者工具 Network 面板里看/api/开头的请求是否都带上了X-Token请求头。如果登录成功但业务接口 401,优先检查拦截器是否放行了/api/auth/login;如果前端能打开但图片 404,检查/upload/的 alias 目录权限是否为 nginx 用户可读。部署完记得把 MySQL 的 root 密码从默认值改掉,同时确认application-prod.yml里的数据库地址不是 localhost,否则后端连不上数据库会直接启动失败。

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

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

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

立即咨询