Spring Boot 3 + Vue 3 物业管理系统全栈实战 在 Java 后端圈子里Spring Boot 3 已经成了绕不开的话题前端这边 Vue 3 的组合式 API 也早就普及。但很多初学者最头疼的问题不是学不会而是不知道怎么串起来。小区物业管理系统这个名字听起来老套但放到 Spring Boot 3 Vue 3 这个技术栈组合下它反而是最适合用来练全栈实战的项目之一业务场景足够贴近现实CRUD 密集又有权限控制、状态流转、前后端联调、环境配置这些硬骨头要啃。这篇文章我会直接按手把手带敲的节奏来写从后端工程初始化、数据库表设计、JWT 认证、日志配置到前端 Vite 项目搭建、核心页面实现、联调部署把每一步的关键细节和踩坑点全展开。这篇教程适合有一定 Java 和前端基础、但还没独立完成过全栈项目的读者也适合想快速把 Spring Boot 3 新特性落地一遍的开发者。1. 项目认知与技术选型为什么是 Spring Boot 3 Vue 31.1 一个老项目的新价值物业管理系统确实是个被写烂了的题目大学课程设计、培训机构项目、毕业设计里到处都是。但你仔细想一下它之所以长盛不衰恰恰是因为它覆盖了一个企业级应用最常见的所有环节登录认证、角色权限、基础数据维护业主、房产、业务流程报修工单从提交到处理到完成、财务记录物业费、停车费、公告发布。这些场景的 CRUD 并不是无脑增删改查每张表之间都有外键关联每个接口都要考虑参数校验、异常处理和权限约束这正好能把 Spring Boot 3 和 Vue 3 的核心知识点全部带出来。还有一个更实际的理由这个项目做好了它可以直接写进简历。比起仿某某电商项目一个你完整经历过设计、开发、部署全流程的物业管理系统在面试的时候反而更能讲出细节——比如你当时怎么设计的表结构为什么用 JWT 不用 Session前端路由守卫怎么控制页面访问权限。面试官问到的点你全都踩过坑这就是它最大的价值。1.2 系统模块划分与业务边界开始敲代码之前我强烈建议你先花半小时把模块边界划清楚。别上来就建工程写接口最后百分之百会返工。一个标准的物业管理系统最小可用版本我建议做这些模块登录认证模块管理员和业主两种角色JWT 签发与校验Spring Security 配置。房产与业主管理小区楼栋、单元、房屋信息维护业主与房屋的绑定关系。费用管理物业费、水电费的账单生成、缴费记录、欠费统计。报修工单业主提交报修、物业人员接单、处理、完成状态流转。公告管理物业发布通知公告业主端可见。我特意砍掉了访客管理、车位管理、投诉建议这些模块不是它们不重要而是对于一个教学性质的项目来说MVP 阶段把这些核心链路跑通比堆功能有意义得多。你后续完全可以在同样的架构上继续加表加接口套路都是一样的。1.3 技术栈全景与版本锁定版本锁死是这种带敲项目最关键的起步动作不然你搜教程的时候Spring Boot 2.7 的写法放到 3.x 上大概率跑不起来。我这里直接给出一份我实际验证过的版本清单技术组件推荐版本说明JDK17Spring Boot 3 强制要求 JDK 17 以上Spring Boot3.2.x稳定版Jakarta EE 规范基础Spring Security6.x随 Spring Boot 3 内置配置写法变化大MyBatis-Plus3.5.x国产增强 ORM适合快速开发MySQL8.0性能和功能都够用Vue3.4组合式 API setup 语法糖Vite5.x新一代前端构建工具Element Plus2.xVue 3 生态最成熟的 UI 组件库Pinia2.x替代 Vuex更简洁的状态管理Axios1.xHTTP 客户端强调几个容易被坑的点Spring Boot 3.x 的包名从javax迁移到了jakarta网上大量旧教程里的import javax.servlet到了 3.x 直接编译不过Spring Security 6 的配置从原来的继承WebSecurityConfigurerAdapter改成了基于SecurityFilterChainBean 的 lambda 风格写法这个不搞清楚光登录认证就能卡你一天。2. 后端动手实录从零搭建 Spring Boot 3 工程2.1 初始化工程与 JDK 17 环境后端工程我建议直接用 start.spring.io 生成骨架别自己在 IDEA 里手动建 Maven 项目再补依赖容易漏。选好 Maven 项目和 Java 17依赖这里只需要先勾上 Spring Web、Spring Security、MySQL Driver、Lombok、ValidationORM 框架我们后面手动加 MyBatis-Plus因为 Spring Initializr 默认没有它的选项。生成后解压用 IDEA 打开先看一眼pom.xml。Spring Boot 3.2.x 的父工程版本号会写清楚你最好确认一下自己本地的 Maven 用的是 JDK 17 编译。这里有个小白最容易忽略的地方IDEA 里就算你把 Project SDK 设成了 17Maven 的maven.compiler.source和maven.compiler.target如果没对上编译出来的字节码版本也可能不对。建议在pom.xml里显式加一下properties java.version17/java.version maven.compiler.source17/maven.compiler.source maven.compiler.target17/maven.compiler.target /properties然后引入 MyBatis-Plus 的 Spring Boot 3 starter。注意MyBatis-Plus 从 3.5.3 开始才提供了专门的mybatis-plus-spring-boot3-starter依赖老版本在 Spring Boot 3 下会报各种奇怪的兼容性错误dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-spring-boot3-starter/artifactId version3.5.5/version /dependency2.2 数据库表设计与代码生成在写接口之前先建表。表设计的好坏直接决定后端代码的复杂度我把核心表的字段梳理出来你自己建库执行即可。数据库名建议用property_system字符集utf8mb4排序规则utf8mb4_unicode_ci。第一张是管理员/用户表我直接叫sys_user字段包括id主键自增、username用户名唯一、password密码BCrypt 加密后存储、real_name真实姓名、role角色ADMIN或OWNER、phone、status状态。第二张是房屋表house_info字段id、building_no楼栋号、unit_no单元号、house_no房号、area面积、owner_id业主 ID外键、status入住状态。第三张业主表owner_info字段id、name、phone、id_card身份证号、house_id关联房屋、create_time。第四张费用表fee_record字段id、house_id、fee_type物业费/水费/电费、amount金额、status未缴/已缴、due_date到期日、pay_time缴费时间。第五张报修工单表repair_order字段id、owner_id报修人、house_id、title、description、status待处理/处理中/已完成、create_time、handler处理人。第六张公告表notice_info字段id、title、content、publish_time。就这六张表你仔细观察一下业主和房屋是双向关联的房屋表有owner_id业主表也有house_id这种冗余在设计上是为了查询方便但写代码的时候要小心别在新增业主和绑定房屋的时候忘记同步更新两边。我实际做的时候是让房屋表成为主导业主表中的house_id主要用于业主端展示自己关联的房子。表的引擎一律 InnoDB主键用BIGINT自增所有业务表都加上create_time和update_time两个datetime字段。索引方面sys_user.username建唯一索引repair_order.owner_id、fee_record.house_id、house_info.owner_id建普通索引。这些都能让后续分页查询少走不少弯路。2.3 JWT 认证与 Spring Security 整合后端最核心的认证流程我单独拿出来讲。Spring Boot 3 内置的是 Spring Security 6配置方式和老版本完全不一样。你如果以前是用extends WebSecurityConfigurerAdapter写法的这里一定要转变思路——现在是用SecurityFilterChainBean 加 lambda 表达式来声明规则。先加 JWT 依赖我用的是io.jsonwebtoken的 jjwt 库0.11.5 版本dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-impl/artifactId version0.11.5/version scoperuntime/scope /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-jackson/artifactId version0.11.5/version scoperuntime/scope /dependencyJWT 工具类JwtUtils核心方法就两个generateToken生成 tokenparseToken解析 token。生成时把用户 ID 和角色塞进 claims过期时间设 24 小时。注意 jjwt 0.11.x 的签名密钥要求至少 256 bit你直接写一个常量字符串也行生产环境务必放到配置中心和加密存储里教学项目写在application.yml里即可。Spring Security 配置类关键代码长这样Configuration EnableWebSecurity public class SecurityConfig { Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http.csrf(csrf - csrf.disable()) .sessionManagement(session - session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth - auth .requestMatchers(/api/auth/login, /api/auth/register).permitAll() .requestMatchers(/api/admin/**).hasRole(ADMIN) .anyRequest().authenticated() ) .exceptionHandling(ex - ex.authenticationEntryPoint(jwtAuthenticationEntryPoint)) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } }三个重点第一csrf.disable()必须写前后端分离项目压根不需要 CSRF Token第二sessionManagement设成STATELESS因为 JWT 本身就是无状态的你不需要服务端保存任何会话第三自定义的JwtAuthenticationFilter要抢在默认认证过滤器之前执行它的作用是从请求头的Authorization字段里把 token 取出来解析后把用户信息放进SecurityContextHolder。登录接口的逻辑不复杂接收用户名密码 - 用BCryptPasswordEncoder校验密码 - 生成 token 返回给前端。这里我强烈建议你用 MyBatis-Plus 的LambdaQueryWrapper查用户不要手写 XML SQL省太多事了。2.4 日志配置logback-spring.xml 和 log4j2 二选一日志这个东西平时没人看出了线上问题恨不得穿越回去补上。Spring Boot 3 默认用的是 Logback内置的spring-boot-starter-logging已经帮我们接好了。但很多讲究一点的项目组会切换成 Log4j2性能更好功能更丰富。这里我两种方案都说一下。如果你继续用默认 Logback我不建议你直接改logback.xml这个名字因为 Spring Boot 官方推荐用logback-spring.xml这个命名允许你在配置里使用springProfile标签做环境区分。我在这个项目里用的是 Logback配置文件放在src/main/resources/logback-spring.xml大致结构如下?xml version1.0 encodingUTF-8? configuration springProfile namedev appender nameCONSOLE classch.qos.logback.core.ConsoleAppender encoder pattern%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n/pattern /encoder /appender root levelINFO appender-ref refCONSOLE/ /root /springProfile springProfile nameprod appender nameFILE classch.qos.logback.core.rolling.RollingFileAppender filelogs/property-system.log/file rollingPolicy classch.qos.logback.core.rolling.TimeBasedRollingPolicy fileNamePatternlogs/property-system.%d{yyyy-MM-dd}.log/fileNamePattern maxHistory30/maxHistory /rollingPolicy encoder pattern%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level [%thread] %logger{36} - %msg%n/pattern /encoder /appender root levelWARN appender-ref refFILE/ /root /springProfile /configuration开发环境打印到控制台生产环境按天滚动写文件并保留 30 天这是最常见的标准配置。如果你想让控制台日志带颜色pattern里用%highlight(%level)和%cyan(%logger{36})包一下就行。如果要切 Log4j2操作也不复杂先从spring-boot-starter-web里排除掉spring-boot-starter-logging再把spring-boot-starter-log4j2加进依赖最后在resources下建立log4j2-spring.xml。这个命名同样是为了让 Spring Boot 自动识别log4j2.xml也可以但优先级会变化。Log4j2 的配置核心是RollingFile加PatternLayout性能确实比 Logback 好一些但在这种 CRUD 为主的系统里感知差异不大选一个你熟练的就好。我再提醒一点日志里永远别打用户的明文密码和身份证号。我之前见过有人为了方便排查在登录接口打印了完整的入参对象这要是数据泄露了锅全在写日志的人身上。3. 前端 Vite Vue 3 开发实录核心页面逐个击破3.1 创建工程Vue 3 安装与 Vite 环境搭建前端工程我用 Vite 创建命令是npm create vitelatest property-web -- --template vue。这里注意 Node.js 版本最好 18Vite 5 对 Node 版本有硬性要求太旧了会直接报错。创建好之后先安装基础依赖我把命令直接贴出来cd property-web npm install vue-router4 pinia axios element-plus sass这里有两个细节。第一个是 SassVue 3 项目里用langscss写样式之前一定要先把sass这个依赖装上Vite 会自动识别并编译 scss 文件不装的话组件跑起来控制台会飘红。热词里搜vue3 安装 scss的人特别多大多数就是漏了这一步。装的时候注意新版本sass包用的是现代 API个别老项目里import的写法可能报废弃警告问题不大但不舒服建议直接用use语法。第二个细节是 Vite 配置请求代理这个属于联调关键点我放到后面专门讲。Element Plus 的引入方式我建议先全局引入省心import ElementPlus from element-plus import element-plus/dist/index.css app.use(ElementPlus)等你的项目大到一定程度再考虑按需自动引入用unplugin-vue-components和unplugin-auto-import两个插件配合搞定。初期全局引入对学习阶段来说完全够用。3.2 布局与路由后台管理界面的骨架物业管理系统的前端是一个典型的中后台管理界面我不会用太多花里胡哨的设计一个干净的布局就够了左侧侧边栏菜单顶部顶栏放用户信息和退出按钮中间内容区域是路由出口router-view。路由表我分成两部分公共路由和动态路由。公共路由只有/login和/register剩下的/home、/owner、/house、/fee、/repair、/notice全部挂在Layout组件下作为子路由。这样实现的效果是访问系统主页时是带侧边栏的整体布局而登录页是独立的无边框页面。路由守卫是前端的重点也是热词里vue3 登录不跳转的高发区。正确的逻辑是在router.beforeEach里判断localStorage.getItem(token)是否存在不存在则跳转到/login存在则放行。同时还要处理一种边界情况用户已经登录了还去访问登录页这属于多余操作直接next({ path: / })重定向回首页。很多人写着写着发现登录成功后点击跳转没反应十有八九是这里逻辑写拧了。router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.path /login) { token ? next(/) : next() } else { token ? next() : next(/login) } })这个写法不是最优的但对你理解路由守卫的运作机制最直接。真实项目里一般会把路由表动态生成用后端返回的菜单数据做筛选这就是另外一个话题了。3.3 业主管理页CRUD 加搜索条件保留登录框架搭完我们来敲第一个完整的核心页面——业主管理。这个页面能带出你后面所有 CRUD 页面的套路认真过一遍后面房屋管理、费用管理就是照猫画虎。template 部分的结构是顶部搜索区输入框 搜索/重置按钮、中间表格区、底部弹窗表单。搜索区用el-form的inline属性el-form :inlinetrue :modelsearchForm submit.prevent el-form-item label业主姓名 el-input v-modelsearchForm.name placeholder请输入业主姓名 clearable / /el-form-item el-form-item el-button typeprimary clickhandleSearch搜索/el-button el-button clickhandleReset重置/el-button /el-form-item /el-form这里有个热词叫vue3 搜索条件保留意思是搜索完翻页、跳转详情后回来搜索条件应该还在。我给你的方案是搜索条件放在回应组件的reactive对象里当路由跳转到别处再返回时因为组件被keep-alive缓存了数据自然还在。但 keep-alive 会带来一个新问题——组件的onMounted在第二次进入时不会重新触发你需要用onActivated钩子来做数据刷新。这两者的配合算是我实际开发里最经常踩的组合坑。表格区直接用el-table绑数据el-table :datatableData border stripe v-loadingloading el-table-column propname label业主姓名 / el-table-column propphone label联系电话 / el-table-column prophouseInfo label关联房屋 / el-table-column label操作 template #default{ row } el-button link typeprimary clickhandleEdit(row)编辑/el-button el-popconfirm title确定删除该业主 confirmhandleDelete(row.id) template #reference el-button link typedanger删除/el-button /template /el-popconfirm /template /el-table-column /el-table删除操作一定要加二次确认弹窗el-popconfirm是 Element Plus 里最简单直接的选择。后端接口设计GET /api/owner/page分页查询参数是pageNum、pageSize、name模糊查询POST /api/owner新增PUT /api/owner更新DELETE /api/owner/{id}删除。这套接口命名风格前后端统一别搞出/api/getOwnerList或者/api/updateOwner这种不伦不类的名字。弹窗表单校验用el-form的rules属性这个也是热词里vue3 rules 日期检验和动态校验的高频区。常见规则对象我简单列一下const rules { name: [{ required: true, message: 请输入业主姓名, trigger: blur }], phone: [ { required: true, message: 请输入联系电话, trigger: blur }, { pattern: /^1[3-9]\d{9}$/, message: 手机号格式不正确, trigger: blur } ] }日期类字段的校验稍微特殊一点你用el-date-picker选完的值是 Date 对象但提交给后端前要格式化成字符串。我看过太多人在这里栽跟头前端校验都过了传给后端却报参数类型错误。解决方式是用dayjs或者直接new Date(value).toISOString().slice(0, 10)建议统一用 dayjs 格式化Element Plus 本身就内置了 dayjs你不用额外装。3.4 报修工单页动态表单与日期校验报修工单模块比业主管理更复杂的地方在于可能存在同一个报修单里有多项维修内容的情况比如这次要换灯泡、修水龙头、检查电路。如果只能用静态表单提交用户得反复填好几遍报修单。我这里的做法是弹窗表单中提供动态添加删除明细行的功能。核心思路是用数组detailList配合v-for渲染表单项每一行都可以单独移除同时提供一个添加行按钮向数组里 push 一个新对象div v-for(item, index) in form.detailList :keyindex el-input v-modelitem.content placeholder维修内容 / el-input-number v-modelitem.amount :min0 placeholder预估费用 / el-button clickremoveDetail(index) :disabledform.detailList.length 1移除/el-button /div el-button clickaddDetail添加维修项/el-button这个功能看起来简单但有一个容易忽略的细节每一行的key千万不要用index因为你删除中间某一行时Vue 的 diff 会复用 DOM导致输入框里的值和数据错位。我当年第一次写动态表单就碰上了这个问题key改成用Date.now() Math.random()生成唯一 ID 后这个问题彻底消失。热词里搜vue3 动态添加删除 form 表单一行数据的人遇到的绝大多数情况就是这个原因。工单表单的状态流转也是一个值得展开的细节。列表页上工单有待处理、处理中、已完成三种状态我用el-tag加不同类型来区分显示切换状态的按钮根据当前状态动态渲染。比如待处理 - 显示接单按钮点击后置为处理中处理中 - 显示完成按钮点击后置为已完成已完成 - 不显示任何操作按钮仅展示状态变化要调用后端接口而不是只在前端改数组这是很多人写前端时容易犯的毛病——刷新页面状态就丢了。接口设计一个PUT /api/repair/{id}/status请求体里带新的状态值即可。3.5 Axios 封装与 Token 注入前后端联通的关键前端页面写得再好看调不动后端接口就是废的。Axios 封装这一节我建议你严格按下面的套路来能少掉很多头发。先建一个utils/request.js创建 axios 实例设置baseURL和超时时间。这里注意baseURL要区分开发环境和生产环境。开发环境用相对路径/api靠 Vite 代理转发到后端生产环境可以也写相对路径然后让 Nginx 统一转发这是最灵活的方式。然后在请求拦截器里带上 tokenconst 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 }) service.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 }, error { if (error.response error.response.status 401) { localStorage.removeItem(token) router.push(/login) } ElMessage.error(error.message || 网络异常) return Promise.reject(error) } )响应拦截器的 401 处理是登录态失效的关键兜底token 过期了自动清除本地 token 并跳回登录页。热词vue3 登录不跳转有两种常见情况一个是前面说的路由守卫逻辑写错另一个就是这里——请求返回 401 但没有跳转用户还以为自己在登录页什么反应都没有。两者都排查过基本能解决九成问题。后端接口统一约定的返回结构是{ code: 200, message: success, data: {...} }所以前端拿到response.data之后先解构检查code。这个结构在后端我用一个ResultT泛型类统一返回前后端约定清楚写起来就不会各搞各的。3.6 Vite 代理与跨域问题环境配置的坑热词里有个vue3 vite dev 局域网打开空白另一个是vue3 访问后端。这两个其实都和 Vite 配置有关。vite.config.js里的server.proxy配置解决的是开发环境下前端向后端发请求的跨域问题export default defineConfig({ server: { host: 0.0.0.0, port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })设置host: 0.0.0.0之后你同一局域网内的手机和另一台电脑就可以通过你的局域网 IP 访问前端页面了。默认情况下 Vite 只监听localhost其他设备访问会直接白屏或者连接拒绝这就是局域网打开空白的最常见原因。关于跨域还有一个后端的兜底配置。在 Spring Boot 里写一个WebMvcConfigurer配置类加CorsRegistry或者在 Spring Security 的过滤器链上加http.cors(cors - cors.configurationSource(corsConfigurationSource()))二选一。我实际开发中只要前端配了 Vite 代理后端其实不需要额外处理 CORS。但如果你哪天直接不通过代理让前端页面里写绝对的http://localhost:8080那就必须后端开 CORS 了。两种方案都能走通但在项目里保持一致我推荐代理方案干净利落。4. 前后端联调、部署与实战避坑4.1 经典问题排查速查表做完整项目流程联调阶段是最折磨人的。我从自己的实战经历和各路踩坑帖里整理了一份高频问题速查表每个问题都是我亲眼见过或者亲身体会过的你按表排查效率会高很多现象可能原因排查思路与修复方式前端请求 404Vite 代理没配置 / 后端接口路径不匹配先看浏览器 Network 面板的请求 URL确认是不是经过代理转发到了正确的http://localhost:8080再看后端RequestMapping路径登录后不跳转路由守卫逻辑问题 / 登录成功后没有执行router.push登录接口返回后先打印 token 是否存进localStorage再检查路由守卫中 token 判断逻辑请求返回 401token 没带 / token 过期 / 白名单路径写错检查前端请求拦截器是否注入了Authorization头检查 Security 配置中.permitAll()路径是否覆盖了登录、注册接口Element Plus 的 upload on-success 不触发使用了http-request自定义上传 / 响应格式不符如果用自定义上传要自己调options.onSuccess()如果没有自定义上传确认后端返回结构是否为{ code, data }格式动态表单行数据错位v-for的key用了index改成业务唯一值如时间戳生成的 ID编译报错Cannot find module sass没装 sass 依赖执行npm install -D sass局域网访问前端白屏server.host没有设为0.0.0.0修改 Vite 配置后重启 dev serverSpring Boot 启动报Failed to configure a DataSource数据库连接配置缺失确认application.yml中spring.datasource.url/username/password是否正确MySQL 服务是否启动我重点展开几个表里没有完全说透的。第一个是 Element Plus Upload 的on-success监听不到这个问题在热词里出现频率很高。如果你用的是默认的上传方式Element Plus 会自行发请求on-success触发条件要求后端返回的 JSON 必须能被解析。但如果你用了http-request属性自定义上传逻辑那么组件完全不会监听后台的响应——你需要在上传逻辑里手动调用options.onSuccess(responseData)才能触发这个回调。这一点文档写得很隐晦坑了不少人。第二个是vxetable 避免 vue3 首屏加载。这个不是物业系统的必选项但如果你觉得 El-Table 在大数据量下卡顿换成 vxe-table 是很常见的选择。首屏加载慢的主要原因是把大型组件库全部全局引入了解决思路是路由懒加载加组件按需引入const OwnerManage () import(/views/owner/OwnerManage.vue)用箭头函数包一层就是懒加载Vue Router 会自动在路由命中时才加载对应组件首屏 JS 体积能小不少。vxe-table 本身也有按需引入插件的机制需要什么功能就引入对应的模块不用的千万别全量注册。第三个是 Spring Boot 3 的接口参数校验。Controller 层如果不在参数上做校验脏数据直接打进数据库后面排查到崩溃。我在Owner实体类上用 Jakarta Validation 的注解NotBlank、Pattern、Email等Controller 接收参数时加Validated注解。同时用RestControllerAdvice写一个全局异常处理器统一捕获MethodArgumentNotValidException返回结构合法的错误提示。记住 Spring Boot 3 这些校验注解的包名也改成了jakarta.validation.*引错包会编译不过。4.2 打包部署前后端分离的上线流程项目开发完总要部署。前后端分离项目的主流方案是两种我都给你过一遍。第一种最简单前端构建产物塞进后端的src/main/resources/static目录。执行npm run build后dist目录里的文件就是纯静态资源把它复制到后端的 static 目录下然后mvn clean package打成单个 jar 包java -jar property-system.jar一条命令启动浏览器访问http://服务器IP:8080就能看到完整的系统。这个方案的优点是部署成本低适合小而美的单体项目缺点是前后端耦合了前端一改版就得重新打后端包。第二种是标准前后端分离部署后端仍以 jar 包形式跑在 8080前端dist目录交给 NginxNginx 配置一个location /指向前端文件location /api反向代理到后端。我用一份最简 Nginx 配置示例server { listen 80; server_name your-domain.com; location / { root /usr/share/nginx/html/property-web; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }try_files $uri $uri/ /index.html;这行非常重要Vue Router 默认是 history 模式前端路由直接访问/owner在刷新时就是一次真实 HTTP 请求如果没有这行配置Nginx 会返回 404。这个坑在部署阶段出现率极高我做过好几次了每次都要检查一遍有没有漏掉。部署完别急着庆祝先用几条命令做冒烟测试。后端看日志有没有启动报错curl http://127.0.0.1:8080/api/...试试接口通不通前端打开页面 F12 控制台看有没有红色的网络请求失败。生产环境配置文件里数据库密码、JWT 密钥这些敏感信息建议通过环境变量注入不要直接硬编码在 yml 文件里不然代码仓库一泄漏生产环境就裸奔了。4.3 从源码理解 Vue 3面试能讲的加分点项目做完以后你如果打算拿它去面试一定要能讲清楚 Vue 3 的底层原理而不是只会写业务。热词里vue3 源码解析、vue3 computed、vue3 面试最经典 6 个问题都是高频考点。我给你提炼几个从源码层面回答的点。Vue 3 的响应式系统是基于Proxy实现的和 Vue 2 的Object.defineProperty完全不同。reactive是用Proxy对对象进行代理拦截 get、set、deleteProperty 等操作完成依赖收集和触发更新。ref则是对基本类型数据的包装内部通过RefImpl类管理一个 value 属性访问.value时触发依赖收集赋值时触发更新。这个设计解决了 Vue 2 里新增属性、删除属性无法被检测到的问题。computed的原理是基于惰性求值和缓存两个机制。它的内部实现了一个ComputedRefImpl有一个_dirty标志位第一次访问时执行 getter 函数算出值后续访问如果依赖没有变化直接返回缓存结果只有依赖的响应式数据发生变化时_dirty才会重新置为 true下次访问才会重新计算。这就解释了为什么在模板中多处使用同一个 computed 属性只会计算一次性能优于调用普通方法。watch和watchEffect是另一个高频考点。watch需要显式指定监听的来源并且可以拿到新值和旧值watchEffect则会自动追踪回调里访问的所有响应式数据。源码层面两者最终都走的是doWatch这个函数只是参数和触发时机有差异。面试官只要听到你能从实现层面答出来印象分会明显不一样。4.4 一个真实的工程化体验从 0 到 1 的沉淀最后说一下我在整个项目开发过程中最真实的体会。做这类管理系统的关键从来不是某个技术点的高深用法而是规范两个字。前端接口路径、后端返回结构、命名风格、异常处理方式、日志规范这些统一了联调效率能翻几倍各写各的代码规模一大就是一场灾难。还有一定要从一开始就学会看控制台的报错信息。很多新手前端一报错就问别人其实浏览器 DevTools 的 Console 面板已经把问题说得明明白白了——是哪一行代码、哪个接口失败、什么类型的错误。后端异常也一样Spring Boot 启动时的堆栈信息是最直接的诊断依据。学会读报错是独立开发者的基本功。我个人在实际操作中的体会是这个项目第二遍、第三遍做的时候速度会快到你想象不到因为你已经把所有的坑都踩过一遍了。你能开始讲出这里的 key 不能用 index要用唯一 ID这里的代理配置漏了 host 字段这里的校验注解包名是 jakarta 不是 javax这类经验就说明它已经真正内化成你自己的技能了。小区物业管理系统确实是个老项目但用 Spring Boot 3 Vue 3 整个技术栈重写一遍的工程化过程含金量依然足够高。这套流程跑通之后迁移到商城、进销存、内容管理都只是业务层面的事架构上你已经站住脚了。