Spring Boot 3 + Vue 3 社区物业管理系统:数据库、前后端与部署全实战 📅 发布时间:2026/9/5 18:24:40 👁 浏览次数: 社区物业管理系统是 Java 全栈练手、毕业设计和课程设计中常见且很有代表性的选题。它覆盖了楼栋房屋、业主档案、物业费、报修工单、投诉建议、停车位、公告通知等真实业务数据结构不复杂但业务状态多、角色权限清晰非常适合用 Spring Boot 3 Vue 3 MySQL 完整走一遍前后端分离开发流程。这套系统的核心看点可以拆成三块后端用 Spring Boot 3底层是 Spring Framework 6默认要求 JDK 17工程结构和接口写法都明显比老 SSM 时代干净前端用 Vue 3 Vite组合式 API 写业务逻辑、Element Plus 出后台 UI开发期有热更新构建产物可以直接交给 Nginx数据层选 MySQL 8配合 InnoDB 和 utf8mb4能稳妥处理物业费台账、报修单这类高频读写数据。文章后面会按一条完整的可复现路线展开先列能力和模块再设计 MySQL 表然后分别搭建 Spring Boot 3 后端与 Vue 3 前端最后做接口联调、Nginx 部署和常见问题排查。如果你准备用这个项目做毕业设计或 Java 全栈入门可以直接拿下面的工程结构、建表脚本和示例代码作为起点把最小可用版本跑起来后再逐步加功能。1. 社区物业管理系统核心能力速览维度说明项目定位Java 全栈物业业务管理系统前后端分离架构后端技术栈Spring Boot 3、MyBatis-Plus、Spring Security Crypto、JWT前端技术栈Vue 3、Vite、Vue Router、Pinia、Element Plus数据库MySQL 8.0InnoDB utf8mb4运行环境JDK 17、Maven 3.8、Node.js 18核心模块房产档案、业主管理、物业收费、报修管理、投诉建议、公告通知、系统管理权限模型管理员、物业人员、业主三类角色JWT 校验登录状态接口风格RESTful API统一 JSON 返回结构典型流程管理员生成费用账单业主在线报修并跟踪状态物业人员处理并回访部署形态前端构建后由 Nginx 托管/api反向代理到后端服务2. 功能模块设计与权限模型2.1 核心业务模块一个可交付的社区物业管理系统业务上至少要覆盖“房产—业主—费用—服务”这条主线。这里列出一套比较通用的模块划分项目开发时可以直接参考。模块核心功能涉及数据房产档案管理楼栋、单元、房屋、建筑面积、入住状态tb_building、tb_house业主管理维护业主姓名、手机号、证件信息、房屋绑定关系tb_owner收费管理设置收费项生成月度账单登记缴费记录tb_fee_item、tb_fee_record报修管理业主提交报修单物业派单、处理、回访tb_repair_order投诉建议记录投诉内容流转处理并反馈结果tb_complaint公告通知发布小区通知、停水停电信息tb_notice系统管理账号、角色、菜单、操作日志管理tb_user、tb_role具体项目里模块数量可以缩减但费用账单和报修工单建议保留。这两个模块最能体现业务逻辑比如费用有“未缴/已缴”状态报修单有“待派单/处理中/已完成/已回访”状态前后端联调时用来测试状态变化很有效。2.2 角色与权限模型物业管理系统一般有三种典型角色系统管理员管理账号和基础数据配置收费项目和角色。物业人员处理业主报修、登记缴费、发布公告核对房屋和业主数据。业主查看名下房产、查询物业费账单、提交报修和投诉但只能看自己的数据。如果目标是快速跑通毕业设计JWT 拦截器是最轻量的方案如果希望工程化程度更高再用 Spring Security 的过滤器链。需要注意“业主只能访问本户数据”这条规则不能只靠前端隐藏按钮后端接口必须按当前登录用户的房屋关联做数据过滤否则换个请求参数就能看到别人家信息。这里还要提一句合规边界业主的身份证号、手机号、房产信息都属于个人敏感数据。项目里不建议明文存完整证件号接口返回时要脱敏开发调试统一使用假数据不要拿真实小区业主信息去测试。3. 开发环境准备与版本选型本地开发环境建议按以下组合准备这是 Spring Boot 3 Vue 3 组合目前比较主流的配置软件建议版本用途JDK17 或 21Spring Boot 3 最低要求 JDK 17Maven3.8后端依赖管理和打包MySQL8.0业务数据存储Node.js18 LTS 或 20 LTS前端构建工具链IDEIntelliJ IDEA / VS Code后端和前端开发打开命令行先确认这几个命令能正常输出版本java -version mvn -version node -v npm -v mysql --version如果本机还没有 MySQL用 Docker 起一个 MySQL 8 是最省事的不污染本机环境docker run -d --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD123456 \ -e MYSQL_DATABASEcommunity_property \ -v mysql8-data:/var/lib/mysql \ mysql:8.0启动后用 Navicat、DataGrip 或命令行连接时地址要填127.0.0.1:3306不要直接填localhost。Linux 下localhost有时会被解析成 socket 连接出现ERROR 2002 (HY000): Cant connect to local MySQL server through socket这类问题。JDBC 连接串里也统一使用127.0.0.1能避开这个坑。4. 数据库设计与建表脚本数据库设计不需要一步到位但表结构要能支撑“房产—业主—费用”这条主线。建议先建 5 张核心表后续再加报修单和公告表。表名说明tb_user系统登录账号区分角色tb_building楼栋信息tb_house房屋信息关联楼栋和业主tb_fee_record物业费账单记录每笔应收和缴费状态tb_repair_order业主报修单建库建表脚本如下。数据库名固定为community_property字符集统一使用utf8mb4物业端的备注信息、业主姓名都可能包含中文和特殊字符utf8mb4 是必要选择。CREATE DATABASE IF NOT EXISTS community_property DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE community_property; CREATE TABLE tb_user ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 用户ID, username VARCHAR(64) NOT NULL COMMENT 登录账号, password VARCHAR(128) NOT NULL COMMENT BCrypt加密后的密码, real_name VARCHAR(64) NOT NULL COMMENT 姓名, role VARCHAR(20) NOT NULL COMMENT 角色ADMIN/PROPERTY/OWNER, phone VARCHAR(20) DEFAULT NULL COMMENT 手机号, status TINYINT NOT NULL DEFAULT 1 COMMENT 1启用 0禁用, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINE InnoDB COMMENT 系统用户表; CREATE TABLE tb_building ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 楼栋ID, building_name VARCHAR(64) NOT NULL COMMENT 楼栋名称如1号楼, total_floors INT NOT NULL DEFAULT 1 COMMENT 总楼层数, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINE InnoDB COMMENT 楼栋表; CREATE TABLE tb_house ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 房屋ID, building_id BIGINT NOT NULL COMMENT 所属楼栋ID, unit_no VARCHAR(20) NOT NULL DEFAULT 1单元 COMMENT 单元, room_no VARCHAR(20) NOT NULL COMMENT 房号, area DECIMAL(10,2) NOT NULL COMMENT 建筑面积平方米, owner_id BIGINT DEFAULT NULL COMMENT 业主ID, status TINYINT NOT NULL DEFAULT 0 COMMENT 0空置 1入住, PRIMARY KEY (id), UNIQUE KEY uk_building_room (building_id, unit_no, room_no) ) ENGINE InnoDB COMMENT 房屋表; CREATE TABLE tb_fee_record ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 账单ID, house_id BIGINT NOT NULL COMMENT 房屋ID, fee_item VARCHAR(32) NOT NULL COMMENT 收费项目物业费/停车费, amount DECIMAL(10,2) NOT NULL COMMENT 应收金额, bill_month VARCHAR(7) NOT NULL COMMENT 账单月份如2025-01, status TINYINT NOT NULL DEFAULT 0 COMMENT 0未缴 1已缴, pay_time DATETIME DEFAULT NULL COMMENT 缴费时间, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_house_month (house_id, bill_month) ) ENGINE InnoDB COMMENT 费用账单表; CREATE TABLE tb_repair_order ( id BIGINT NOT NULL AUTO_INCREMENT COMMENT 报修单ID, house_id BIGINT NOT NULL COMMENT 报修房屋ID, content VARCHAR(500) NOT NULL COMMENT 报修内容, contact_phone VARCHAR(20) DEFAULT NULL COMMENT 联系电话, status TINYINT NOT NULL DEFAULT 0 COMMENT 0待派单 1处理中 2已完成 3已回访, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, finish_time DATETIME DEFAULT NULL COMMENT 完成时间, PRIMARY KEY (id) ) ENGINE InnoDB COMMENT 报修单;建表时注意几个细节第一MySQL 8 不再推荐用int(11)这样的显示宽度写法主键直接BIGINT即可第二费用金额用DECIMAL(10,2)不要用FLOAT或DOUBLE否则累计时会出现浮点误差第三业务表的主键可以用自增BIGINT但涉及业主身份证这类外部敏感标识时不要直接当主键用。账号表中的密码必须用 BCrypt 哈希存储不能明文入库。初始账号可以这样插入密码字段需要先替换成运行时生成的真实 BCrypt 值INSERT INTO tb_user (username, password, real_name, role) VALUES (admin, 替换为BCrypt加密结果, 系统管理员, ADMIN);5. Spring Boot 3 后端搭建与核心代码5.1 创建工程与 Maven 依赖后端工程可以通过 IDEA 的 Spring Initializr 创建也可以直接访问 start.spring.io 生成。选型时 Java 版本选 17Spring Boot 版本选 3.x依赖先勾选 Spring Web、MySQL Driver。为了少写模板代码工程里再加入 MyBatis-Plus 和 Lombok。pom.xml的关键依赖如下parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version3.3.x/version relativePath/ /parent properties java.version17/java.version mybatis-plus.version3.5.5/mybatis-plus.version /properties dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-spring-boot3-starter/artifactId version${mybatis-plus.version}/version /dependency dependency groupIdorg.springframework.security/groupId artifactIdspring-security-crypto/artifactId /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependencies需要说明的是Spring Boot 3 对应的 MyBatis-Plus 依赖是mybatis-plus-spring-boot3-starter而不是老的mybatis-plus-boot-starter。如果你从旧项目升级这一步最容易踩坑。上面版本号只是示例实际开发时以 Maven Central 上可用的最新 3.5.x 为准。5.2 application.yml 配置数据库连接配置写在src/main/resources/application.yml里。MySQL 8 的驱动类是com.mysql.cj.jdbc.DriverJDBC 连接串建议显式指定时区、编码和allowPublicKeyRetrievaltrue否则高版本 MySQL 可能出现公钥检索报错。server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/community_property?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: 123456 mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0如果后端端口 8080 已经被占用把server.port改成 8081 再启动即可。map-underscore-to-camel-case用来把数据库的house_id自动映射成实体的houseId这是 MyBatis 系列的标准配置。5.3 启动类与分页配置启动类上加MapperScanMyBatis-Plus 就能自动扫描到 Mapper 接口。分页功能需要注册一个拦截器否则selectPage不会生效SpringBootApplication MapperScan(com.example.property.mapper) public class PropertyApplication { public static void main(String[] args) { SpringApplication.run(PropertyApplication.class, args); } }Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }5.4 统一返回结构与登录接口后端接口建议统一返回结构前端 Axios 拦截器只处理一种数据格式联调会省很多事。这里用 JDK 17 的 record 写一个最简Resultpublic record ResultT(int code, String message, T data) { public static T ResultT ok(T data) { return new Result(200, ok, data); } public static T ResultT fail(String message) { return new Result(500, message, null); } }登录功能不直接用明文密码比较用 BCrypt 校验。核心流程是通过用户名查用户判断密码是否匹配匹配后签发 JWTService RequiredArgsConstructor public class AuthService { private final UserMapper userMapper; private final PasswordEncoder passwordEncoder; private final JwtUtil jwtUtil; public String login(String username, String password) { User user userMapper.selectOne( Wrappers.UserlambdaQuery() .eq(User::getUsername, username)); if (user null || !passwordEncoder.matches(password, user.getPassword())) { throw new BusinessException(账号或密码错误); } return jwtUtil.createToken(user.getId(), user.getRole()); } }控制器只负责接收参数并返回结果RestController RequestMapping(/api/auth) RequiredArgsConstructor public class AuthController { private final AuthService authService; PostMapping(/login) public ResultString login(RequestBody LoginVO vo) { return Result.ok(authService.login(vo.getUsername(), vo.getPassword())); } }BCrypt 的PasswordEncoder也需要注册成 Bean。因为这里没有引入完整 Spring Security所以单独加一个配置类即可Configuration public class SecurityBeanConfig { Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }5.5 费用账单查询接口账单模块是物业系统里最有代表性的 CRUD 场景。用 MyBatis-Plus 的LambdaQueryWrapper可以避免手写 XML代码可读性也更高。下面这个 Service 查询所有未缴账单并按月份排序Service RequiredArgsConstructor public class FeeRecordService extends ServiceImplFeeRecordMapper, FeeRecord { public PageFeeRecord pageUnpaid(int current, int size) { LambdaQueryWrapperFeeRecord wrapper Wrappers.lambdaQuery(FeeRecord.class) .eq(FeeRecord::getStatus, 0) .orderByAsc(FeeRecord::getBillMonth); return page(new Page(current, size), wrapper); } }RestController RequestMapping(/api/fee) RequiredArgsConstructor public class FeeRecordController { private final FeeRecordService feeRecordService; GetMapping(/unpaid) public ResultPageFeeRecord unpaid(RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer size) { return Result.ok(feeRecordService.pageUnpaid(page, size)); } }正式项目里这条查询不能直接返回全部未缴账单必须根据登录人的角色做数据范围限制。管理员可以看小区全部数据业主只能查询house_id等于自己名下房屋的账单。这个数据权限校验要放在 Service 层不能信任前端传参。6. Vue 3 前端搭建与页面实现6.1 创建 Vite 工程前端使用 Vite 官方模板创建 Vue 3 项目工程名可以叫community-property-webnpm create vitelatest community-property-web -- --template vue cd community-property-web npm install然后安装路由、状态管理、HTTP 请求库和 UI 组件库npm install vue-router pinia axios element-plus element-plus/icons-vue安装完成后把npm run dev跑起来默认访问端口是 5173。如果 5173 被占用Vite 会自动换到 5174终端里会明确提示访问地址。6.2 封装 Axios 请求前端所有请求都走 Axios统一处理 Token 和错误提示。建议在src/api/request.js里做一次封装import axios from axios import { ElMessage } from element-plus const request axios.create({ baseURL: /api, timeout: 15000 }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) request.interceptors.response.use( response { const res response.data if (res.code ! 200) { ElMessage.error(res.message || 请求失败) return Promise.reject(new Error(res.message)) } return res.data }, error { if (error.response error.response.status 401) { localStorage.removeItem(token) window.location.href /login } ElMessage.error(网络请求异常) return Promise.reject(error) } ) export default request6.3 开发代理配置后端接口是http://localhost:8080前端开发服务器是http://localhost:5173两者不是同一个端口直接请求会触发跨域。解决方式很简单在 Vite 配置文件里加代理把/api开头的请求转发给后端。vite.config.js内容如下import { defineConfig } from vite import vue from vitejs/plugin-vue export default defineConfig({ plugins: [vue()], server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })配置完成后前端代码里请求/api/fee/unpaid实际会代理到http://localhost:8080/api/fee/unpaid。这样开发期不需要在后端写 CORS 配置生产环境统一交给 Nginx 处理比较干净。6.4 登录页面和费用账单页面登录页面用 Element Plus 的表单组件实现提交后把 JWT 存到localStorage再跳转到首页。用一个简单版式说明组件结构template el-form refformRef :modelform label-width80px el-form-item label账号 el-input v-modelform.username / /el-form-item el-form-item label密码 el-input v-modelform.password typepassword show-password / /el-form-item el-button typeprimary clickhandleLogin登录/el-button /el-form /template script setup import { reactive, ref } from vue import { useRouter } from vue-router import { ElMessage } from element-plus import request from ../api/request const router useRouter() const form reactive({ username: , password: }) const handleLogin async () { const token await request.post(/auth/login, form) localStorage.setItem(token, token) ElMessage.success(登录成功) router.push(/dashboard) } /script费用账单列表页用el-table展示未缴数据再配一个分页组件。整体结构就是onMounted时调用接口把返回的records塞给表格分页变化时重新请求。Vue 3 组合式 API 下要注意的是接口调用函数统一放在src/api/目录不要在组件里直接拼axios地址这样后面换接口 BaseURL 或者增加拦截逻辑时只改一个文件。7. 接口联调与功能测试7.1 后端启动开发期启动后端有两种方式。第一种是 Maven 直接运行mvn spring-boot:run第二种是先打包再运行适合多次重启验证mvn clean package -DskipTests java -jar target/community-property-0.0.1-SNAPSHOT.jar后端启动成功后控制台会打印 Spring Boot 的启动日志和端口信息。如果application.yml里的数据库账号密码对不上启动阶段就会抛出连接失败异常先回数据库确认再继续。7.2 登录接口测试先用 curl 验证登录接口是否正常。请求体是 JSON这里使用前面设计的/api/auth/logincurl -X POST http://localhost:8080/api/auth/login \ -H Content-Type: application/json \ -d {username:admin,password:123456}正常响应结构如下{ code: 200, message: ok, data: eyJhbGciOiJIUzI1NiJ9... }返回的data就是 JWT Token。如果返回500排查顺序是先看数据库里有没有对应的 admin 用户再看用户表密码是不是 BCrypt 格式。如果密码是明文BCrypt 校验必然失败。7.3 鉴权接口测试前端登录后会把 Token 放在Authorization请求头里后端拦截器校验通过后才允许访问业务接口。测试时手动携带 Tokencurl -X GET http://localhost:8080/api/fee/unpaid?page1size10 \ -H Authorization: Bearer 登录返回的Token不携带 Token 直接访问时接口应该返回 401。这样一个简单的测试就能验证拦截器和数据传输链路是通的。7.4 前端联调验证后端保持运行再启动前端npm run dev浏览器访问http://localhost:5173用管理员账号登录进入费用账单页面。验证点有三个第一登录成功后页面能正常跳转刷新页面后路由守卫能根据 Token 放行第二费用列表能加载出数据库里已有的账单数据分页切换后 URL 参数发生变化第三直接退出登录再手动访问业务页面会被重定向回登录页。如果页面能打开但表格没数据先不要怀疑前端直接访问后端的/api/fee/unpaid看 SQL 日志。MyBatis-Plus 的