简介这是一份基于Java技术的小区物业管理系统设计与实现文档适合计算机相关专业学生、毕业设计人员及对物业管理信息化感兴趣的开发者。文档以小区物业为业务场景围绕报修管理、房屋管理、收费管理、停车位管理、投诉管理和用户管理等核心模块展开完整呈现了从需求分析、可行性分析到系统流程与功能设计的全过程可作为系统开发或论文撰写的参考。资源共1个docx文件压缩包约1.55MB文档内包含中英文摘要、目录、绪论、开发环境介绍、系统分析等章节并涉及Java语言、JDK、Eclipse开发工具和MySQL数据库等技术选型。目前已有60人学习/下载。读者通过这份文档可理清物业管理系统的模块划分、数据库设计思路与界面交互方案同时了解经典信息管理系统的技术选型及实现路径为毕业设计、课程设计或实际项目的前期规划提供直接借鉴。1. 从选题到上线Java小区物业管理系统到底在解决什么问题如果你正在找“基于java的小区物业管理系统设计与实现”的完整方案多半是被毕业设计、课程设计或者手头一个真实的小区项目卡住了。这个题目看起来只是一个普通的增删改查系统但真正动手后你会发现物业费按月生成账单怎么做账期、一套房换了三任业主历史账目怎么归属、Excel 导出的收费报表为什么总是对不平这些才是物业管理系统和“图书管理”这类课设最大的区别。这篇文章把一个可落地的 Spring Boot MyBatis-Plus MySQL 方案拆开讲透从模块边界、表结构设计、核心代码实现到本地部署和五个高频踩坑点。适合在校生做毕设也适合刚入行、想看看真实业务边界的 Java 工程师。2. 系统架构与模块划分先把边界画清楚再动手写代码2.1 技术栈选型为什么是 Spring Boot MyBatis-Plus 而不是 JSP/Servlet现在搜到的 Java 岗位要求和课程设计案例源码几乎都是 Spring Boot 系。“spring boot mybatis 的 java 开源”项目也远比 JSP 时代的教程多。JSP Servlet JDBC 不是不能做而是会把大量时间花在手动封装 JDBC、控制请求转发上这些和物业管理业务本身没有关系。用 Spring Boot 内嵌 Tomcat一个java -jar就能起服务对新手友好得多。MyBatis-Plus 的取舍要讲清楚单表 CRUD 时几乎不用写 SQLBaseMapper 自带 selectById、insert、updateById分页插件注册一个 Bean 就完事。多表关联查询还是要写 XML但整体开发效率仍然比原生 MyBatis 高。如果坚持原生 MyBatis一个实体字段映射要写一大堆 resultMap物业系统里房产、业主、账单、流水这些表动辄十几个字段纯手工映射纯粹是给自己挖坑。视图层看你的交付目标。后端渲染用 Thymeleaf开发速度最快适合单人完成整个毕设前后端分离用 Vue3 Element Plus适合想顺带展示前端能力的同学。物业管理系统是典型的内部系统并发和部署要求都不高Thymeleaf 是首选。如果你不熟悉 npm 那套别在毕设里强行前后端分离光一个 vite 配置和跨域问题就能耗掉两三天。Thymeleaf Bootstrap 完全可以把楼栋管理、收费台账这些页面做得像模像样。2.2 功能模块边界收费、报修、车位、公告四大核心域的划分把功能堆在一个 Controller 里是这类课设最常见的翻车点。一个像样的小区物业管理系统功能可以分成五个域房产域管楼栋、单元、房屋、车位业主域管住户信息和入住状态收费域管账单、缴费、欠费统计服务域管报修工单和公告权限域管管理员角色和菜单。五个域之间通过 Service 互相调用Controller 只做参数接收和结果封装。模块边界的核心规则一个业务动作只进一个 Service。比如“业主缴费”Controller 调用 FeeService.pay()FeeService 内部校验账单状态、写支付流水、更新账单状态这套逻辑绝不能散落在 Controller 里。很多人用 MyBatis-Plus 就直接在 Controller 里 selectById updateById 搞定当时觉得快等要做“欠费业主批量催缴”时发现业务逻辑没法复用只能复制粘贴这是血泪经验。收费域要独立出来因为它是整个系统的核心。报修和公告可以做得粗糙一点甚至用通用 CRUD 带过但收费域涉及房产状态、收费项目、账期、流水四个维度耦合度很高。比如“生成当月所有已入住房屋的物业费账单”要遍历 house 表、读取 fee_item 单价、批量插入 fee_bill跨了三个实体只有放在 FeeService 里才能被“月度结算”“欠费统计”“催缴通知”反复调用。车位收费不想单独建账的话可以把车位建模成一种特殊类型的 house走同一套计费流程这是项目管理里比较常见的做法。2.3 工程目录结构一个可维护的 Maven 项目应该长什么样community-property/ ├── pom.xml └── src/main/java/com/community/property/ ├── CommunityPropertyApplication.java ├── common/ # Result统一返回、BizException、JwtUtil、常量类 ├── config/ # MybatisPlusConfig、CorsConfig、MetaObjectHandler ├── controller/ # 接收HTTP请求只做参数校验和结果封装 ├── entity/ # 与表结构一一对应的实体类 ├── mapper/ # MyBatis-Plus的Mapper接口复杂SQL在这里加XML ├── service/ │ ├── fee/ # 收费域 │ ├── house/ # 房产域 │ ├── owner/ # 业主域 │ └── service/ # 报修与公告 └── util/这个结构的关键在 common 和 config 两个包。common 下的 Result 对象让所有接口返回格式一致比如{code: 0, message: ok, data: ...}前端不管做模板渲染还是对接接口都能统一处理。BizException 配合全局异常处理器让业务校验失败返回明确提示而不是一异常就 500 加一堆堆栈。config 下的 MyBatis-Plus 分页插件几乎每个查询接口都要用MetaObjectHandler 负责 create_time、update_time 的自动填充这两块是 MyBatis-Plus 项目的标准配置。实体类和表结构保持字段同名驼峰转下划线由map-underscore-to-camel-case处理MyBatis-Plus 默认就开着。mapper.xml 里只放真正的多表查询比如“按楼栋查所有欠费房屋”。为了少写 resultMapXML 里尽量用列别名映射实体字段。service 按域拆包而不是按 controller/service/mapper 三层堆平改动时能少翻很多文件。另外把 Application 启动类放在最外层包保证SpringBootApplication能扫到所有子包的组件。3. 数据库设计是物业系统的地基核心表结构与字段参数3.1 房产与业主的关系设计一栋楼、一套房、一户人的建模房产和业主的关系是物业系统的第一张数据模型。一栋楼有多个单元一个单元有多个房屋房屋归业主所有或租住。最偷懒的做法是直接给 house 表加一个 owner_id 字段一个业主多套房时重复记录业主信息业主换房时直接覆盖 owner_id历史归属全丢。后面做缴费统计时根本说不清“这套房在某个时间段该谁交钱”。常见做法是把房产建模成 building、house 两级业主单独一张 owner 表再用 house 表里的冗余字段 owner_id 表示当前归属。owner_id 冗余不是错的关键要让它含义清晰它表示“当前业主”只用于日常查询展示。真正要保留历史就再建一张 house_owner 关系表记录入住时间和搬出时间费用账单按账期落库后历史归属自然就锁在账单里了关系表只是辅助追溯。字段设计上Java 数据类型要和 MySQL 类型对齐。house 表的 id 用 Long 对应 BIGINTunit_no、room_no 用 Integer 对应 INT面积用 BigDecimal 对应 DECIMAL(10,2)面积涉及乘法和统计浮点类型绝对不要用。Java 八大基本类型里的 int、double 看着省事但 MyBatis-Plus 自动填充和 Null 值处理都会更麻烦实体类里统一用包装类型比如 Integer、Long、BigDecimal。3.2 费用表的账期与流水设计物业费为什么不能只存一条记录物业费和其他系统最大的区别在于账期概念。物业费是按月度或季度生成的每个房屋每个月都有一条应收记录交没交、交了多少钱、什么时候交的都要能查。很多课设把“应交物业费”设计成 house 表里的一个字段金额一变就 update结果就是完全看不出这套房欠了哪几个月的钱。正确设计是两张表fee_bill 账单表和 payment_record 缴费流水表。fee_bill 每条记录对应一个房屋一个账期的应收款字段包含 house_id、收费项目 item_id、period_start、period_end、amount、status。payment_record 记录每次真实缴费一个账单可以部分缴、多次缴也可以全额一次缴清。账单状态和支付流水分离之后欠费统计就是一条SELECT COUNT(*), SUM(amount) FROM fee_bill WHERE status 0按楼栋分组一查就出来。这里要理解一个关键点账单是“账”流水是“钱”。账单生成时不产生任何资金变动只是确认“该交多少”缴费动作才产生流水同时更新账单状态。把这两件事混在一起比如在缴费时直接改 house 的欠费金额字段后面对账就会对不上。物业收费报表不平绝大多数都是因为这个设计没做对。3.3 建表 SQL 与关键索引实体类与 DDL 的映射边界CREATE TABLE building ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 主键, name VARCHAR(64) NOT NULL COMMENT 楼栋名称如1号楼, address VARCHAR(255) COMMENT 楼栋地址, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_name (name) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT楼栋表; CREATE TABLE owner ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT 主键, name VARCHAR(32) NOT NULL COMMENT 业主姓名, phone VARCHAR(20) COMMENT 联系电话, id_card VARCHAR(18) COMMENT 身份证号, gender TINYINT COMMENT 0未知 1男 2女, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_phone (phone) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT业主表; CREATE TABLE house ( id BIGINT AUTO_INCREMENT PRIMARY KEY, building_id BIGINT NOT NULL COMMENT 所属楼栋id, unit_no INT NOT NULL COMMENT 单元号, room_no INT NOT NULL COMMENT 房号, area DECIMAL(10,2) NOT NULL COMMENT 建筑面积用于计费, status TINYINT NOT NULL DEFAULT 0 COMMENT 0空置 1入住 2出租, owner_id BIGINT DEFAULT NULL COMMENT 当前业主id冗余字段, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_building_room (building_id, unit_no, room_no), KEY idx_owner (owner_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT房屋表; CREATE TABLE fee_bill ( id BIGINT AUTO_INCREMENT PRIMARY KEY, house_id BIGINT NOT NULL COMMENT 房屋id, item_id BIGINT NOT NULL COMMENT 收费项目id如物业费, period_start DATE NOT NULL COMMENT 账期开始日期, period_end DATE NOT NULL COMMENT 账期结束日期, amount DECIMAL(10,2) NOT NULL COMMENT 应收金额, status TINYINT NOT NULL DEFAULT 0 COMMENT 0未缴 1已缴 9作废, pay_time DATETIME DEFAULT NULL COMMENT 实缴时间, operator_id BIGINT DEFAULT NULL COMMENT 操作管理员id, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_house_status (house_id, status), KEY idx_period (period_start, period_end) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT费用账单表; CREATE TABLE payment_record ( id BIGINT AUTO_INCREMENT PRIMARY KEY, bill_id BIGINT NOT NULL COMMENT 账单id, pay_type TINYINT NOT NULL DEFAULT 0 COMMENT 0现金 1微信 2支付宝, pay_amount DECIMAL(10,2) NOT NULL COMMENT 实缴金额, pay_time DATETIME NOT NULL COMMENT 缴费时间, operator_id BIGINT COMMENT 操作管理员id, remark VARCHAR(255) COMMENT 备注, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_bill (bill_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT缴费流水表;索引设计上fee_bill 最核心的是idx_house_status (house_id, status)因为“查某套房所有未缴账单”是最频繁的操作联合索引让这条件查询直接走索引。period_start、period_end 单独建索引服务“按月统计应收实收”的报表场景。payment_record 的 bill_id 索引保证按账单查流水时不全表扫描。网上那个“根据 java 实体类生成创建表的 SQL 语句”的热词本质上是反射读取 TableName、TableField 注解再拼接 DDLMyBatis-Plus 官方并没有内置这套能力。与其引入不成熟的生成工具不如让实体类和建表 SQL 放一起维护字段顺序对齐反而少踩坑。实体类里用TableName(fee_bill)显式声明表名TableId(type IdType.AUTO)声明自增主键TableField(fill FieldFill.INSERT)声明自动填充字段这套注解和 DDL 对照着看改结构时基本不会漏。4. 用 Spring Boot 把核心模块跑起来登录鉴权与费用管理的实现4.1 JWT 登录鉴权的完整链路工具类、拦截器与放行规则JWT 是课设里最常见的登录方案因为它天然适配前后端分离后端不用存 Session。它的缺点是 token 吊销麻烦、密钥管理要自己操心但对物业系统这种内部管理工具完全够用。这里选 jjwt 0.11.5 版本注意 0.9.1 在 JDK 9 上会因 javax.xml.bind 报错0.11.5 没有这个问题。public class JwtUtil { private static final String SECRET community-property-secret-please-change; private static final long EXPIRE_MILLIS 24 * 60 * 60 * 1000L; private static final Key KEY Keys.hmacShaKeyFor(SECRET.getBytes(StandardCharsets.UTF_8)); public static String createToken(Long userId, String username) { return Jwts.builder() .setSubject(username) .claim(userId, userId) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() EXPIRE_MILLIS)) .signWith(KEY, SignatureAlgorithm.HS256) .compact(); } public static Claims parseToken(String token) { return Jwts.parserBuilder() .setSigningKey(KEY) .build() .parseClaimsJws(token) .getBody(); } }这里有几个参数值得说。SECRET 是签名密钥HS256 算法要求密钥至少 32 字节否则运行时直接抛 WeakKeyException这算是一个隐藏的版本坑。EXPIRE_MILLIS 设为 24 小时管理员登录状态不需要太长缩短 token 有效期能降低 token 泄露风险。claim 里塞 userId 和 username业务代码里需要当前操作人时从 token 取不需要再查库。token 生成后由登录接口返回给前端前端存 localStorage 或 cookie之后每次请求在 Authorization 头带上。登录链路还要加一个拦截器或过滤器。定义一个 HandlerInterceptor在 preHandle 里放行/api/auth/login其他路径都从 header 取 token 再解析。解析失败就返回 401不放行。这里注意只从request.getHeader(Authorization)取去掉Bearer前缀再解析。拦截器注册到 WebMvcConfigurer 里时要指定路径规则常见的写法是addPathPatterns(/api/**)excludePathPatterns(/api/auth/login)。4.2 缴费服务的事务控制账单生成与缴费接口账单生成是一个典型的批量任务遍历所有已入住房屋读取收费项目单价按面积计算金额批量插入 fee_bill。缴费则是单条事务校验账单状态、插入流水、更新账单。Java 怎么保证数据一致性是 java 面试题里常被追问的点在这个缴费接口里答案就是事务加条件更新。Service public class FeeService { Resource private FeeBillMapper feeBillMapper; Resource private PaymentRecordMapper paymentRecordMapper; Transactional(rollbackFor Exception.class) public PayResult pay(Long billId, BigDecimal payAmount, Long operatorId) { FeeBill bill feeBillMapper.selectById(billId); if (bill null) { throw new BizException(账单不存在); } if (bill.getStatus() ! 0) { throw new BizException(账单已缴费请勿重复操作); } // 用条件更新兜住并发只有 status0 时才能更新成功 int updated feeBillMapper.update(null, new LambdaUpdateWrapperFeeBill() .eq(FeeBill::getId, billId) .eq(FeeBill::getStatus, 0) .set(FeeBill::getStatus, 1) .set(FeeBill::getPayTime, LocalDateTime.now()) .set(FeeBill::getOperatorId, operatorId)); if (updated 0) { throw new BizException(账单状态已变化请刷新后重试); } PaymentRecord record new PaymentRecord(); record.setBillId(billId); record.setPayType(0); record.setPayAmount(payAmount); record.setPayTime(LocalDateTime.now()); record.setOperatorId(operatorId); paymentRecordMapper.insert(record); return new PayResult(billId, record.getId()); } }写这段时容易犯的错是在事务里先selectById判断状态再updateById两个动作之间如果另一个请求先改了状态就会重复缴费。这里的条件更新eq(FeeBill::getStatus, 0)直接把状态作为更新条件affected rows 为 0 说明状态已经变化从数据库层面挡住并发重复。MySQL 默认隔离级别可重复读下配合行锁这条 update 就是最朴素的乐观锁。注意Transactional(rollbackFor Exception.class)必须写默认只回滚 RuntimeException如果自定义异常继承 Exception不加 rollbackFor 会导致事务不生效。4.3 MyBatis-Plus 分页与动态查询欠费列表这样写最省事分页是管理系统的标配功能MyBatis-Plus 的分页原理是拦截器改写 SQL 为SELECT COUNT(*)加LIMIT。注册拦截器是配置层面必须做的一步漏了分页查询会直接返回全表数据。Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); PaginationInnerInterceptor pagination new PaginationInnerInterceptor(DbType.MYSQL); pagination.setMaxLimit(500L); interceptor.addInnerInterceptor(pagination); return interceptor; } }setMaxLimit(500L)是防止前端乱传 size 参数把全表拉出来。分页插件之后Service 层用 LambdaQueryWrapper 搭动态查询条件无参的条件不拼进 SQLpublic PageResultFeeBillVO pageFeeBill(Long houseId, Integer status, int page, int size) { LambdaQueryWrapperFeeBill wrapper new LambdaQueryWrapper(); wrapper.eq(houseId ! null, FeeBill::getHouseId, houseId) .eq(status ! null, FeeBill::getStatus, status) .orderByDesc(FeeBill::getPeriodStart); PageFeeBill p new Page(page, size); feeBillMapper.selectPage(p, wrapper); return new PageResult(p.getRecords(), p.getTotal()); }eq的第一个参数是布尔值true 才拼接这个条件。这种写法比 if 拼 SQL 字符串干净得多也是 MyBatis-Plus 日常开发里最常用的技巧。多表查询则放 XML 里更直观比如“按楼栋查欠费房屋”要连 house 和 owner用注解写会很乱放 mapper XML 里可以清晰看到 JOIN 结构还能用if做可选条件。分页插件对自定义 XML 的分页同样生效只要 mapper 接口方法第一个参数传PageT对象即可。5. 本地部署与常见问题排查从 JDK 配置到启动成功的五个踩坑记录5.1 环境版本搭配JDK、Spring Boot、MySQL 的兼容边界Java 环境配置是这类项目的第一道坎。很多新手在本地装了两个 JDKIDEA 里项目用的 JDK 8命令行里 java -version 却是 JDK 17Maven 编译时又取的是 IDEA 配置最后启动报一堆莫名其妙的错误。这里给一个比较稳的搭配照着配能少走弯路组件建议版本注意事项JDK1.88u202不要用 17 跑 Spring Boot 2.7 之前的项目Spring Boot2.7.x3.x 要求 JDK 17课设选 2.7 最稳MySQL8.0驱动用 mysql-connector-j 8.0.x注意时区Maven3.6 以上IDEA 自带可用命令行要配环境变量构建方式mvn clean package本地跑也建议先打包再启动配套的 pom.xml 里 Spring Boot 版本选 2.7.18MyBatis-Plus 建议 3.5.xmysql-connector-j 8.0.33。这些版本都在生产环境验证过互相之间没有明显的兼容问题。# 检查当前命令行 JDK java -version # 打包 mvn clean package -DskipTests # 启动并指定开发环境配置 java -jar target/community-property-0.0.1-SNAPSHOT.jar --spring.profiles.activedev如果本地跑不起来第一步先确认java -version和 Maven 用的 JDK 是同一个。IDEA 里能跑而命令行不能跑十有八九是 IDEA 的 Project SDK 和 JAVA_HOME 不一致。5.2 踩坑一JDK 版本不一致打包成功却启动失败现象mvn clean package正常但java -jar启动几秒就退出日志里有UnsupportedClassVersionError或者NoSuchMethodError。原因Maven 编译用的 JDK 和运行 JAR 的 JVM 版本不一致。比如 Maven 用 JDK 17 编译出 class 版本 61运行环境是 JDK 8JVM 直接拒绝加载。NoSuchMethodError则是 Spring Boot 3.x 的包在 JDK 8 上跑方法签名对不上两种情况本质都是版本错配。解决在 pom.xml 的 maven-compiler-plugin 里强制指定 source 和 target 为 1.8同时保证 JAVA_HOME 指向 JDK 8。命令行依次执行echo %JAVA_HOME%Windows或echo $JAVA_HOMELinux/macOS确认路径再到 IDEA 的 Project Structure 里把 Project SDK 和 Maven Runner 的 JRE 都改成同一个。5.3 踩坑二MySQL 8 连接时的时区、SSL 与 public key 问题现象应用启动时数据源初始化报Communications link failure或者The server time zone value Öйú±ê׼ʱ¼ä is unrecognized。原因MySQL 8 的 JDBC 驱动对时区和 SSL 的默认行为比 5.x 严格。数据库服务器时区不是标准时区名时会报错SSL 握手在某些网络环境下还会失败。网上那些老教程教你连 SQL Server 2008 的连接串照搬过来在 MySQL 8 上大概率翻车。解决JDBC URL 显式关闭 SSL、指定时区、开启 public key 检索application.yml 里这样配spring: datasource: url: jdbc:mysql://localhost:3306/community_property?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8allowPublicKeyRetrievaltrue username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.DriverallowPublicKeyRetrievaltrue是 MySQL 8 的 caching_sha2_password 认证场景下首次连接需要不加会报Public Key Retrieval is not allowed。characterEncodingutf8保证中文不乱码useSSLfalse在本地开发环境关闭加密连接减少握手耗时。5.4 踩坑三MyBatis-Plus 自动填充失效create_time 写不进去现象插入记录成功但表里 create_time、update_time 是 NULL。数据字典里明明配了自动填充怎么没生效。原因自动填充不是只加个依赖就有的需要同时具备两个条件。实体类字段上要有TableField(fill FieldFill.INSERT)注解Spring 容器里要有一个实现 MetaObjectHandler 的组件。缺任何一个MyBatis-Plus 都不会触发填充逻辑。解决实体类和配置组件都补齐。// 实体类字段 TableField(fill FieldFill.INSERT) private LocalDateTime createTime; TableField(fill FieldFill.INSERT_UPDATE) private LocalDateTime updateTime;Component public class MyMetaObjectHandler implements MetaObjectHandler { Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, createTime, LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } }注意 strictInsertFill 里字段名写的是实体属性名 createTime不是数据库列名 create_time。写错会导致静默失败不报错但不填充这种玄学问题最耗时。另外 fill 策略别写成 FieldFill.DEFAULT那是关闭填充。5.5 踩坑四CORS 跨域配置与拦截器冲突预检请求被拦现象前端用 Vue 开发服务器调用后端接口浏览器控制台报No Access-Control-Allow-Origin header或者预检请求 OPTIONS 返回 403。直接请求还发现登录接口能通带 token 的接口不通。原因跨域配置是 Spring 层面处理的但自定义拦截器也会拦截 OPTIONS 预检请求。前端发 POST、PUT 请求时浏览器先发 OPTIONS 试探拦截器一看没带 token 直接返回 401预检没过请求就发不出去。解决注册一个 CorsFilter 处理跨域同时拦截器里对 OPTIONS 方法直接放行。Bean public CorsFilter corsFilter() { CorsConfiguration config new CorsConfiguration(); config.addAllowedOriginPattern(*); config.addAllowedHeader(*); config.addAllowedMethod(*); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration(/**, config); return new CorsFilter(source); }// 拦截器 preHandle 里加这段 if (HttpMethod.OPTIONS.equals(request.getMethod())) { return true; }我这里有过一次印象很深的排查CORS 配置写了拦截器也放行了 OPTIONS但带 token 的请求还是 401。最后发现是前端代码把 token 放在了自定义 headerX-Token里而后端 CORS 配置的 allowedHeader 里没加X-Token预检时告诉浏览器这个头不被允许。所以 allowedHeader 要么精确列全要么直接*。5.6 踩坑五打包后读不到配置文件与 mapper.xml现象IDEA 里能正常导出 Excel、能访问模板文件但java -jar部署后报FileNotFoundException或者读不到 resources 下的 mapper XML。原因代码里用了new File(templates/xxx.xlsx)这种方式读相对路径。本地运行的工作目录是项目根目录能碰巧读到文件打成 JAR 后 resources 内容在 classpath 里不再是文件系统路径File 自然找不到。mapper XML 同理如果 pom.xml 里没把 resources 目录作为资源打包target 里就缺 XML。解决读 resources 下的文件统一用 classpath 方式。ClassPathResource resource new ClassPathResource(templates/import-template.xlsx); InputStream in resource.getInputStream();同时确认 pom.xml 里 resources 配置把src/main/resources包含进去以及 mapper XML 如果放在src/main/java下需要额外配置 Maven 把**/*.xml也打包。变形考法是在启动命令里用--spring.config.locationfile:/opt/config/application.yml指定外部配置这个能满足部署时改数据库密码的需求比重新打包快得多。6. 从能跑到能用交付前要补的细节与验证方法代码能跑通只是第一步要让这套物业管理系统真正能交付还有几个细节必须补。首先是初始化数据至少内置一个管理员账号、几栋楼的楼栋和房屋数据、收费项目数据。没有初始化数据评审时连登录进去都看不到东西第一印象直接打折扣。初始化可以用 CommandLineRunner 在启动时检查管理员表为空就插入默认账号密码不要用明文存 BCrypt 加密后的结果。其次是密码存储和日志记录。登录模块里明文比对密码是这类项目最常见的硬伤只需要引入 spring-security-crypto 依赖用 BCryptPasswordEncoder 的 encode 和 matches 两个方法就够不需要把整个 Spring Security 引进来。收费相关的操作要记录操作人payment_record 里的 operator_id 就是从 JWT 的 claim 里解析出来的答辩时被问“这笔钱是谁收的”就能答上来。最后是一张验证清单。交付前按这个表逐项过一遍比临时点一遍页面靠谱得多验证点操作预期结果登录正确和错误密码各试一次正确进首页错误提示用户名或密码错误生成账单选择 1 号楼点击生成当月账单生成的账单数等于该楼已入住房屋数缴费对未缴账单执行缴费账单变已缴流水表多一条记录欠费查询按楼栋查欠费列表只出现 status0 的账单金额合计正确重复缴费对已缴账单再点缴费提示“账单已缴费”不产生新流水报修闭环业主报修、管理员派单、完工回访状态从待派单到处理中到已完成我最早做这个题目时只把增删改查跑通就觉得自己做完了结果被问“一套房欠了半年物业费你怎么统计”当场卡住。后来才意识到物业系统的灵魂是账期、流水和状态流转界面反而最不重要。这套方案按“先表结构、再事务、后查询”的顺序做下来哪怕要往小区门禁、停车收费这些方向扩展底子也还是稳的。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?