news 2026/9/28 5:13:02

SpringBoot2+Vue3智慧社区管理系统实战:从数据库设计到部署上线

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot2+Vue3智慧社区管理系统实战:从数据库设计到部署上线

最近在社区开源平台放了一套智慧社区管理系统的完整源码,技术栈是 SpringBoot2 + Vue3 + MyBatis-Plus + MySQL8.0 这个前后端分离的标准组合,同步附带了数据库脚本、接口文档和部署说明。整套系统并不是那种只为了应付演示的玩具项目,小区档案、房产信息、业主绑定、物业缴费、报修工单、车位管理、公告发布这些业务模块都做了闭环。发出去之后经常有人私信问:这套系统的核心表到底怎么设计的?MyBatis-Plus 的分页和条件构造器在实际项目中怎么用才顺手?MySQL8.0 升级之后有没有遇到连接问题?Vue3 后台管理页面的登录态和权限控制是怎么搭的?这篇文章就把我在开发这套系统过程中踩过的坑、做过取舍的地方、以及真正跑通全流程的关键细节一次性说清楚,写给我自己复盘,也写给打算拿这套系统做毕业设计或者转行练手的朋友。

1. 智慧社区管理系统的业务全貌:先想清楚要管什么

1.1 五个核心模块拆解:从小区档案到物业缴费

在做任何系统之前,我习惯先把业务对象列出来。智慧社区管理系统表面上看是一个"后台管理平台",但真正落到业务层面,它管理的是**物理空间(小区/楼栋/房屋)和住在空间里的人(业主/家属/租户)**之间的关系。整套系统我最终拆成了五个核心模块。

第一个是小区资源档案模块。这里的小区不是简单的名称字段,而是一个树形结构:小区下有楼栋,楼栋下有单元,单元下有房屋。每套房屋有面积、户型、朝向、当前居住状态(自住/出租/空置)。这个树形结构是整个系统的地基,因为后面的缴费、报修、车辆绑定最终都要关联到具体的房屋上。我见过很多初学朋友的数据库设计,直接一张表把所有字段塞进去,结果写后面的业务逻辑时到处 join,维护成本极高。

第二个是业主与住户管理模块。业主和房屋之间是多对多的关系,一个人可以在一个小区拥有多套房产,也可以同时是业主和家属。在数据库层面我用了单独的关联表来维护,而不是在房屋表里直接加一个 owner_id 字段。至于租户,系统里我不做太重的合同管理,只记录一个居住人信息,这个设计思路是:核心目标是让物业知道"这套房子现在谁在住、怎么联系得上",而不是做一套 ERP。

第三个是物业缴费模块。这是整个系统里最容易写乱的部分。一笔物业费的状态流转是:生成账单 → 未缴费 → 已缴费 →(如果逾期)生成滞纳金。我把费用类型分成了物业费、停车费、水费代收、垃圾清运费,用一张表加费用类型字段来处理,而不是每一种费用建一张表。缴费动作落地之后要回写房屋的状态,比如说某套房连续欠费三个月,系统会在业主信息列表里自动打上欠费标红标记。这里我用定时任务处理逾期状态变更,每天早上跑一次,而不是在查询接口里临时算逾期,性能差别很大。

第四个是报修工单模块。业主提交报修 → 物业派单 → 师傅接单 → 维修完成 → 业主评价。这个流程需要一个状态机来控制:待派单、已派单、维修中、已完成、已评价、已取消。比较关键的是派单逻辑,物业管理员的角色可以把工单指派给某个维修师傅,师傅登录系统后只能看到自己的待办工单。我在工单表里设计了 assignee_id 字段,查询时直接按这个字段过滤,不需要额外的授权判断逻辑。

第五个是车辆与通行管理模块。关联到房屋的车辆登记、访客车辆临时放行、车位信息管理。车辆进场出场这部分,我在系统里只做到了登记和记录层面,没有对接硬件的道闸。如果你要接真实的门禁设备,预留了 plate_number 和 entry_time 两个字段,后面做接口对接时把设备回调写入这两列就行。

1.2 技术栈选型复盘:SpringBoot2 + Vue3 的组合逻辑

这套技术栈不是我拍脑袋定的,每层选择都有明确理由。

SpringBoot2 在当前环境下依然是最稳妥的选择。版本我锁在 2.7.x,而不是直接上 SpringBoot3,核心原因是生态兼容性:MyBatis-Plus 和大量企业级中间件对 SpringBoot3 的适配在早期并不完善,而且 Java8 的开发者群体依然庞大,基于 JDK8 的 SpringBoot2 项目拿下来就能跑,不需要额外处理模块化路径和 Jakarta 命名空间变化。SpringBoot 2.7 虽然已经进入维护末期,但对这类管理系统绰绰有余,稳定性经过了太多年验证。

前端选择 Vue3 而不是 Vue2,一方面是因为组合式 API 在组织复杂业务逻辑时确实比 Options API 更清爽。社区管理后台的页面复杂度不低,一个缴费记录页要同时处理搜索条件、表格数据、分页状态、弹窗表单、批量操作,用 setup 函数把所有逻辑收敛在一起,比到处散落 data/methods/computed 要好维护很多。另一方面,Element Plus 已经足够成熟,Vite 的冷启动速度对日常开发体验的提升也是实打实的。

MyBatis-Plus 在这个项目里是胜负手。它和 Spring Data JPA 的核心区别在于:JPA 把实体关系管理做到极致,适合领域驱动设计,但在 SQL 调优和多表关联查询时容易被自动生成的 SQL 坑到;MyBatis-Plus 本质上还是 MyBatis,SQL 写在自己手里,同时又提供了单表 CRUD 的自动封装。社区管理系统的业务大量集中在单表操作上,比如查询某栋楼下的所有房屋、按状态查工单、分页查缴费记录,这些场景用 BaseMapper 内置方法基本覆盖了,真正的复杂统计 SQL 我手动写在 XML 里,质量可控。

MySQL8.0 主要是为了 utf8mb4 字符集和新版窗口函数。社区系统的地址、业主姓名、备注信息里经常出现生僻字和表情符号,utf8mb4 是刚需。窗口函数在统计报表场景里非常好用,比如分析"每栋楼的缴费率排名",一条 ROW_NUMBER() 就能解决,在 MySQL5.7 里写起来要痛苦得多。

2. 后端工程搭建:分层规范、统一响应与权限设计

2.1 后端工程目录的划分思路

很多朋友在搭建 SpringBoot 工程时喜欢把全部代码平铺在几个包下面,短时间看着还行,一旦业务模块变多就开始混乱。我在这套系统里采用了"按技术层横向划分 + 按业务模块纵向分包"的混合结构,既保证了技术的统一性,又让业务边界清晰。

横向的技术层包名是 controller、service、mapper、entity、dto、config、common。纵向的业务模块包则在 controller 和 service 内部再次拆分子包,比如 controller 下拆成 community、repair、payment、car、notice。这样做的好处是:找某个功能的 Controller 只需要先进 controller 包,再点具体业务模块,不会出现几十个 Controller 文件堆在一起的局面。Mapper 层我坚持只放数据库操作,业务逻辑全部下沉到 Service 层,Controller 层只做参数接收、常量校验、响应封装,不写任何 SQL 逻辑。这条红线守住了,代码基本不会烂。

配置文件的组织也容易被忽略。我没有把所有配置塞进一个 application.yml,而是拆成了三个环境:application-dev.yml(本地开发,数据库地址指向本机)、application-prod.yml(部署环境,数据库地址指向服务器)、application-common.yml(通用配置,比如 JWT 过期时间、文件上传大小)。打包时通过 --spring.profiles.active=prod 指定环境,避免每次换环境都去改 yml 里的数据库密码。

2.2 统一响应体、全局异常与分页返回结构

前后端分离项目里,统一的响应格式是最基础也最容易出错的部分。我在项目里定义了一个泛型响应类 R<T>,所有接口返回的都是这个结构。它有四个核心字段:code 表示业务状态码(200 成功,500 系统错误,401 未登录,403 无权限),message 是给前端展示的提示信息,data 是实际数据,timestamp 是时间戳。这样前端 Axios 拦截器里只需要判断 code === 200 就正常处理数据,其他情况统一弹提示,不需要每个接口单独做错误处理。

分页返回结构我单独定义了一个 PageResult 类,包含总记录数 total、当前页数据列表 records、当前页码 current、每页大小 size。这个设计和 MyBatis-Plus 的 IPage 对象是解耦的,Controller 层拿到 IPage 之后转换成 PageResult 返回。为什么要多做一层转换?因为 MyBatis-Plus 的 Page 对象里带了搜索条件和额外属性,直接序列化给前端会暴露太多无意义字段,而且返回结构不够稳定,一旦 MP 版本升级字段变了,前端可能就解析报错了。

全局异常处理我使用了 @RestControllerAdvice 注解。业务异常通过自定义的 BizException 抛出,比如"该房屋已被业主绑定""工单状态不允许取消",统一到这里捕获后转换成标准的 R 响应,状态码可以根据异常类型动态映射。还有一类容易忽略的异常是参数校验异常,我在实体 DTO 上用了 javax.validation 注解,比如 @NotBlank、@Email、@Pattern,校验失败抛出的 MethodArgumentNotValidException 也要在全局异常里接住,否则返回默认的 400 错误结构,前端拿到的是无法直接提示的原始错误对象。

2.3 JWT 登录与接口鉴权的实现要点

社区管理系统的用户角色分三类:系统管理员(超管)、物业人员(包括管理员和维修师傅)、业主。业主登录后只能看到自己的房屋、缴费和报修,物业人员可以看到自己负责范围内的业务数据,超管则拥有一切权限。这里我用 JWT 方案实现无状态登录,登录成功时签发一个 Token,Token 里在 payload 部分封装 userId、username、role 三个字段。出于安全考虑,我在 JWT 配置里设置了两个关键参数:过期时间 7200 秒(两小时),签名密钥足够复杂(至少 32 位随机字符串),防止被暴力破解。

后端拦截器我实现了一个 JwtInterceptor,继承 HandlerInterceptor,在 preHandle 里解析 Token。校验通过后把 userId 存入 request 的 Attribute 中,业务层从 request 里取当前操作用户,而不是把用户信息写在方法的参数列表里传来传去。这个做法的好处是后续在任意 Service 方法里都能获取到当前登录人,比如记录操作日志时直接调用一个静态方法就行。

角色权限我用了一个简单的自定义注解 @RequireRole,标注在 Controller 方法上。拦截器解析完 JWT 之后,检查当前用户角色是否满足注解中声明的角色集合。整套系统规模不大,这种"注解 + 方法级拦截"方案比引入 Spring Security 全家桶要轻量得多,学 Spring Security 的成本要比写这个拦截器高不少。等系统真的需要动态权限、细粒度数据权限的时候再迁移过去不迟。

3. 数据库设计与 MyBatis-Plus 实战:MySQL8.0 的正确打开方式

3.1 核心表结构设计:外键怎么取舍

关于数据库表外键,我的原则是:业务系统里不使用物理外键,全部通过逻辑关联维护。物理外键在数据插入时性能损耗明显,而且社区管理系统后期如果有拆分数据库的需求,物理外键会成为巨大阻碍。比如房屋表和业主表之间的关联,我只在房屋表里保留 owner_id 字段作为逻辑外键,通过定期校验和代码层面约束保证数据一致性,效果完全够用。

核心表的设计我列一张核心结构表来说明:

表名关键字段关联关系说明
communityname, address, area, developer1对多:building小区基础档案
buildingcommunity_id, building_no, floor_count多对1:community楼栋及层数信息
roombuilding_id, unit_no, room_no, area, owner_id多对1:building房屋核心档案
ownerreal_name, mobile, id_card, status多对多:room(room_owner 表)业主信息
paymentroom_id, fee_type, amount, status, deadline多对1:room物业缴费记录
repairroom_id, order_no, description, status, assignee_id多对1:room / user报修工单

所有核心业务表都包含三个公共字段:create_time、update_time、deleted。create_time 和 update_time 由 MyBatis-Plus 的自动填充功能在 insert 和 update 时自动写入,deleted 字段配合逻辑删除功能实现数据安全删除——用户在前台删除一条缴费单时,数据库实际上只执行了 UPDATE deleted=1,这样误删数据还能恢复,系统审计时也能追溯。

3.2 MyBatis-Plus 的自动填充、分页插件与条件构造器

MyBatis-Plus 自动填充是在实体类的 createTime 字段上加 @TableField(fill = FieldFill.INSERT) 注解,然后实现一个 MetaObjectHandler 处理器,在 insertFill 方法里统一写入 LocalDateTime.now()。注意最好用 LocalDateTime 而不是 java.util.Date,因为 MySQL8.0 驱动对 java.time 包的支持更好,JSON 序列化时也能避免时区问题。

分页插件配置我曾经漏掉过一次,导致调用 selectPage 方法时返回的数据是全量而不是真正分页,排查了很久。关键是要在 MybatisPlusConfig 里注入 MybatisPlusInterceptor,并添加 PaginationInnerInterceptor,指定数据库类型为 DbType.MYSQL。

条件构造器 QueryWrapper 是 MyBatis-Plus 用得最多的 API。我不建议在 Service 层直接 new QueryWrapper 写代码,因为条件完全散落在业务代码里,后期 SQL 调优会非常难受。我通常在 Service 层调用一个私有方法构建查询条件,返回 QueryWrapper 对象。比如查询缴费记录时,动态拼接 roomId、feeType、status 和缴费日期区间,核心写法如下:

private QueryWrapper<Payment> buildPaymentQuery(PaymentQueryDTO dto) { QueryWrapper<Payment> wrapper = new QueryWrapper<>(); wrapper.eq(StringUtils.isNotBlank(dto.getRoomId()), "room_id", dto.getRoomId()); wrapper.eq(dto.getFeeType() != null, "fee_type", dto.getFeeType()); wrapper.eq(StringUtils.isNotBlank(dto.getStatus()), "status", dto.getStatus()); if (dto.getStartDate() != null && dto.getEndDate() != null) { wrapper.between("deadline", dto.getStartDate(), dto.getEndDate()); } wrapper.orderByDesc("create_time"); return wrapper; }

注意 eq 方法的第一个条件参数必须是 boolean,前端传来的查询参数为空时就不拼接这个条件,这样才能实现真正的动态查询。还有就是每张表的逻辑删除字段名为 deleted 时,MP 的查询会自动追加 deleted=0 条件,但如果你在 XML 里手写 SQL 或者用了自定义 SQL 片段,这个自动过滤是失效的,必须手动加上条件,这是一个非常容易踩的坑。

批量插入也值得提一下。如果缴费系统月结时需要给上千户业主批量生成账单,逐条 insert 性能太差,MP 提供了 saveBatch 方法。但实测时我发现默认的批量插入语句还是拆成了多条 insert,性能和一条 insert 带多组 values 比差距明显。我的做法是在 XML 里手写 foreach 批量 insert 语句,几千条数据的插入时间能从几十秒降到一两秒以内。有些版本 MP 的 saveBatch 也做了优化,但自己验证一把才是最靠谱的。

3.3 升级 MySQL8.0 之后的三个兼容性问题

这套系统最初开发时我在本机用的是 MySQL5.7,一切正常。切到 MySQL8.0 之后接连遇到三个问题,这里单独列出来。

第一个是认证插件问题。MySQL8.0 默认的认证插件是 caching_sha2_password,而 MySQL5.7 时代常用的客户端连接工具和旧版本驱动只认 mysql_native_password。如果 SpringBoot 项目的驱动版本太老,启动时就会报 Unable to load authentication plugin 错误。解决办法有两个:一是把 mysql-connector-java 升级到 8.0.x 版本,这是治根的方式;二是临时把用户的认证插件改回 mysql_native_password:ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '你的密码';。部署到 Linux 服务器时建议直接用新驱动,不做兼容降级。

第二个是JDBC URL 必须加时区参数。MySQL8.0 对时间类型的处理更严格,连接字符串里如果不加 serverTimezone=Asia/Shanghai 和 useUnicode=true&characterEncoding=utf8,项目启动时就会一直报数据库连接超时或者时间解析错误。经验值是把完整的参数串放在配置文件里:jdbc:mysql://localhost:3306/community?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true。

第三个是utf8mb4 排序规则的变化。MySQL8.0 的默认排序规则是 utf8mb4_0900_ai_ci,这个排序规则在 LIKE 查询和 UNIQUE 索引上的一些行为与老版本的 utf8mb4_general_ci 有细微差异。如果你是从 5.7 导出的数据库,导入 8.0 之后部分表的索引可能变成无效索引。建议建库时直接指定 utf8mb4 的通用排序规则,建库语句我用的是:CREATE DATABASE community CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci。

4. Vue3 前端工程:从登录页到业务页面的完整链路

4.1 Vite 工程初始化与项目结构

前端部分我用 Vite 而不是 Vue CLI 来搭建,理由很直接:Vite 的开发服务器启动速度在项目变大之后优势非常明显,而且 Vite 5 对 Vue3 的支持是官方一等公民,配置要比 Vue CLI 的 webpack 配置简洁太多。

项目初始化命令是 npm create vite@latest community-web -- --template vue,然后手动安装核心依赖:vue-router、pinia、element-plus、axios、sass。Element Plus 我采用全量引入而不是按需自动导入,原因是在后台管理系统这种内部项目里,全量引入的打包体积虽然大了几百 KB,但省去了插件配置的麻烦,开发体验更顺畅。如果以后要优化首屏加载,再改成按需自动导入也不迟。

目录结构我按照前端工程化的标准组织:src/api 按业务模块放接口请求文件,src/views 放页面级组件,src/components 放公共组件,src/router 放路由配置,src/store 放 Pinia 状态管理。还有一个容易忽略的是 src/utils 里放公共工具方法,比如日期格式化、金额保留两位小数、字典映射函数。社区管理系统的很多状态字段是数字字典,比如工单状态 0 是待派单 1 是已派单,我写了一个 getDictLabel 方法统一处理,避免在页面上到处写 if-else。

4.2 登录态管理:路由守卫、Pinia 与 Token

前端登录流程是整个系统的门口,这块设计不好会出现很恶心的体验问题,比如刷新后白屏、直接通过网址访问未授权页面。

我的做法是登录成功后后端返回 Token 和用户基本信息,前端用 Pinia 定义一个 user store,state 里放 token、userInfo、role。Pinia 的持久化我直接写死在 store 的初始化逻辑里:从 localStorage 读取 Token 和用户信息,有就直接放入 state,没有就为空。这里不建议把全部业务缓存都交给 Pinia 的持久化插件,因为用户信息在权限变动时容易留下脏数据,只要持久化 token 和 username 就够了,其余的每次刷新后重新拉取。

路由守卫我同时用了全局前置守卫和动态 meta 字段。router.beforeEach 里做三件事:判断是否存在 Token,不存在则重定向到登录页;存在但用户信息为空时调用接口拉取当前用户信息;最后判断当前路由的 meta.roles 数组是否包含用户角色,不包含就跳转到 403 页面。这里要特别提示的是,路由守卫的跳转逻辑一定要避免死循环,比如重定向到 /login 时如果 /login 页面自身也在守卫里判断 token,就会陷入无限跳转。我的处理是守卫的最开始先判断 to.path 是否为 /login,如果是直接放行。

4.3 业务页面里的三种高频模式

社区管理系统的后台页面看起来多,但拆开来看基本就是三种模式:列表 + 分页 + 搜索、弹窗 + 表单、状态流转操作。把这三个模式写好,其他页面都是套模板。

列表页的核心是这个套路:页面挂载时调用查询接口,关键词变化时重置页码为 1 再重新查询,分页组件的 current-change 事件触发翻页。搜索区我统一用 el-form 的内联模式,查询按钮和重置按钮并排,重置方法里把搜索表单所有字段清空,然后同样重置页码再查一次。

弹窗表单我写了一个通用的 useFormDialog 组合式函数,里面封装了打开弹窗、回显数据、提交前校验、调用新增或更新接口这几个动作。新增和编辑共用一个弹窗组件,区别只在回显时调一次详情接口并给表单赋值。这一步有个细节:编辑回显时别直接拿列表行数据赋值,因为列表返回的字段往往和表单字段不是一一对应的,比如时间字段在列表里是字符串,在表单里需要的是 Date 对象,直接赋值会出现校验失灵。

状态流转操作比如派单、完成工单、退单,我设计成在表格操作列里放下拉按钮组件。根据当前行状态动态显示可用操作,禁用不可用的动作。点击操作后先弹出确认框,再调接口。这里积累了血泪教训:每个操作按钮必须绑定 loading 状态,防止用户手快重复提交,我封装了一个 useActionLoading 方法,按 actionKey 来标记哪个操作正在进行。

4.4 Axios 请求封装与接口联调细节

Axios 封装的核心是拦截器,我设置了三层逻辑。请求拦截器里从 Pinia 中获取 token 并注入 Authorization 头,格式是 Bearer 。前端如果检测到路由正处在登录页就不注入。响应拦截器里统一判断后端返回的 code:200 直接 resolve 返回 data 部分,401 时清除本地 token 并跳转登录页,其他错误码用 Element Plus 的 ElMessage 展示后端返回的 message 字段。

接口文件组织是我比较看重的点。每个业务模块一个 api 文件,例如 src/api/payment.js 里只放缴费相关的请求函数,统一导出。函数命名遵循"方法verb + 资源名称"的规则,比如 getPaymentList、createPayment、updatePayment、deletePayment,这样维护的时候搜索非常方便。和前端联调初期最容易出现的问题是后端返回的字段命名风格不统一,有的字段是 createTime,有的用的下划线 create_time。在这套项目里我们统一强制后端 DTO 使用驼峰命名返回,JSON 里就是驼峰,前端直接消费不用做任何转换。

接口联调阶段我还发现一个问题:SpringBoot 默认返回的时间格式是 UTC 格式的长字符串,前端展示时非常难看。我在后端 yml 里配置了全局的 Jackson 时间序列化格式 spring.jackson.date-format=yyyy-MM-dd HH:mm:ss,同时前端在 axios 请求时也设置了 paramsSerializer,确保时间类型参数能正确传递到后端。如果前后端对时间格式没有统一约定,排查起来会非常头疼。

5. 部署与复盘:让系统真正跑起来的全过程

5.1 环境准备:Linux 安装 MySQL8.0 的实操记录

这套系统最终是部署在一台 2 核 4G 内存的 Linux 服务器上的,用的 CentOS 8 系统。数据库我直接选择了 MySQL8.0,安装时不推荐用官方 MySQL Yum Repository 的方式,网络波动会导致安装中断。稳妥做法是直接用腾讯云环境或本地 rpm 包安装,实际操作步骤是:

先检查系统是否装了旧版本:rpm -qa | grep mysql。然后下载 MySQL8.0 的 RPM 包,安装顺序必须先安装 common、libs、client,最后装 server,顺序反了会提示依赖错误。安装完成后执行 systemctl start mysqld 启动服务,用 grep 'temporary password' /var/log/mysqld.log 找到初始临时密码,登录之后必须先执行 ALTER USER 修改密码,因为 MySQL8.0 强制要求登录后修改临时密码才能执行任何操作。

修改完密码后,我给社区系统创建了独立数据库账号,没有直接用 root:CREATE USER 'community'@'%' IDENTIFIED BY '这里写强密码';。然后授权 GRANT ALL PRIVILEGES ON community.* TO 'community'@'%';。使用独立账号的好处是后续上线后会部署多个系统,每个系统账号只拥有自己数据库的权限,安全级别高很多。

5.2 前后端部署流程

后端部署非常简单,Maven 打包命令我用的是 mvn clean package -Dmaven.test.skip=true,因为测试类里依赖了数据库连接,在构建机上执行测试会浪费时间甚至报错。打出来的 jar 包用 nohup java -jar community-server.jar --spring.profiles.active=prod 启动,注意这里必须要让日志输出重定向到文件:nohup 命令的日志重定向到 nohup.out 不够规范,我写成 java -jar xx.jar > /usr/local/community/logs/app.log 2>&1 &,方便之后用 tail 查看实时日志。

前端部署的核心是 Nginx 静态资源服务。Vite 打包后生成 dist 目录,我把 dist 目录放到 /usr/local/nginx/html/community 下。Nginx 配置里最关键的是 location /api/ 的代理转发:把所有 /api 开头的请求代理到后端服务的地址,同时做了 WebSocket 支持。

location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }

这里要特别提醒一个容易翻车的点:proxy_pass http://127.0.0.1:8080/ 末尾的斜杠非常关键,如果有斜杠,代理时会把 /api 前缀去掉,比如 /api/payment/list 会转成 /payment/list 发给后端;如果没有斜杠,原路径会整体传给后端,此时后端的 Controller 里就必须用 /api 作为前缀。两种写法必须和前后端约定保持一致,否则接口一定 404。

5.3 上线前需要检查的清单与常见坑

上线前我养成了一个固定的检查清单,特意分享出来,因为踩坑踩多了自然就总结出了规律:

第一,检查 SpringBoot 配置文件里的 MySQL 地址是否改成了服务器 IP,数据库账号密码是否和服务器上创建的一致。我遇到过朋友把 dev 环境配置直接打成 prod 包,上线后数据库连到本地导致启动失败。

第二,检查防火墙和服务器安全组是否放行了 8080 和 3306 端口。很多情况下后端 jar 包明明启动了,接口却一直超时,最后查到是安全组没开端口,这个坑看起来低级,但排查起来相当隐蔽。

第三,检查 JWT 密钥是否修改。源码里自带了一套默认密钥,上线前不改就等于门锁形同虚设。我习惯让运维在环境变量里注入 JWT_SECRET,SpringBoot 配置里通过 ${JWT_SECRET} 读取,既保证不泄露到代码仓库,又方便不同环境使用不同密钥。

第四,检查定时任务是否启动。缴费系统里逾期账单的状态变更依赖定时任务,上线后要确认日志里定时任务有正常执行记录,我曾经遇到过一台服务器时区没设对,定时任务在凌晨 4 点才跑,导致白天展示的账单状态一直是昨天的。

第五,检查前端页面是否启用了生产模式。Vite 打包时如果用 npm run dev 启动而不是 build,网站访问性能会差很多,而且浏览器控制台里全是开发模式的警告信息。

第六,检查数据库的初始数据是否正确导入。系统的初始小区、初始楼栋、初始管理员账号都是通过 SQL 脚本导入的,上线时脚本执行顺序错了会导致关联数据缺失,比如房屋表已经有了数据但楼栋表还没有,数据完整性就挂了。

部署完成之后一定要做的自检动作是:先通过 curl 测试后端健康检查接口,确认返回 200,再验证前端登录页是否能正常拉取验证码和提交登录请求,最后模拟一遍核心业务路径——新建小区、添加楼栋、创建房屋、绑定业主、生成缴费单、提交报修单、完成工单。这条主链路走通,系统上线基本就成了。

写到这里,我把这套智慧社区管理系统从业务设计、技术选型、核心模块、数据库设计,一直到前端实现、后端实现和部署上线的完整链路都过了一遍。如果你正在学习 SpringBoot 和 Vue3,最大的建议还是别只盯着教程看,真正用这套系统把代码通读、跑通,然后自己动手改两个模块,比如给缴费模块加一个导出 Excel 功能、给报修模块加一个通知推送,这两个实操做下来,你对这套技术栈的熟悉程度会发生质的飞跃。这套系统的源码和详细文档我都已经整理好放在仓库里了,有需要学习交流的可以直接对照本文里的实现思路去读代码。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 5:12:56

Java程序员AI转型:langchain4j与Spring AI实战指南

先说结论&#xff1a;Java 程序员不需要焦虑“AI 时代 Java 没用了”。从 2026 秋招的岗位分布来看&#xff0c;Java 后端依然是招聘量最大的方向之一&#xff0c;但纯 CRUD 的 Java 岗位正在变少&#xff0c;要求带 AI 能力的 Java 岗位在变多。这场转型的关键不是重学 Python…

作者头像 李华
网站建设 2026/9/28 5:12:50

云帆智能客户管理系统更新说明:V2.6.4

一、功能优化1. 解决docker启动时&#xff0c;3306端口被占用启动不了的问题2. docker部署时&#xff0c;增加了启动状态&#xff0c;访问地址等的说明3. 解决手机端默认账号看不清的问题4.管理员增加客户批量转移功能。管理员增加 客户批量转移功能。做一个单独的管理菜单&…

作者头像 李华
网站建设 2026/9/28 5:12:01

吃鸡对局信号枪博弈复盘:语音信息管理与视频工具链实战

“兄弟&#xff0c;信号枪发一下。”当这句话从敌人嘴里说出来的时候&#xff0c;就意味着你手里的道具已经变成了全场最显眼的目标。很多玩家在这个瞬间会犹豫&#xff1a;给&#xff0c;对方拿到空投后很可能反手一枪&#xff1b;不给&#xff0c;敌人已经开始架枪准备硬抢。…

作者头像 李华
网站建设 2026/9/28 5:09:54

AI大模型应用开发入门指南:小白也能抓住时代红利,转行新机遇!

文章指出&#xff0c;尽管微软等科技巨头在裁员&#xff0c;但英伟达等公司却在积极扩招&#xff0c;尤其是在AI大模型应用开发领域。AI行业正在经历人才结构的“换血”&#xff0c;传统岗位被淘汰&#xff0c;而应用层开发需求激增。对于想转行或学习AI的普通人来说&#xff0…

作者头像 李华
网站建设 2026/9/28 5:09:32

Python音频特征提取与DTW匹配:军乐合奏曲目识别实战

之前整理一段阿穆尔之波国际军乐节开幕式的大合奏现场录音时&#xff0c;遇到了一个很实际的问题&#xff1a;广场上多支军乐团同时齐奏&#xff0c;铜管声、大鼓声、观众掌声混叠在一起&#xff0c;人耳能明显判断出“节奏整齐、气势很足”&#xff0c;但要想准确说出合奏的到…

作者头像 李华
网站建设 2026/9/28 5:09:03

本地部署大模型实战对比:LM Studio 与 Ollama 的选型指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华