
做智慧社区这种全栈项目我估计你已经看过不下几十个“xx管理系统源码”了。但说实话市面上大量项目要么是SSM老古董套个半残前端要么就是后端塞满业务但前端用JSP硬撑真正能做到SpringBootVue3MyBatis前后端分离、能直接跑起来还能写进简历的真不多。这篇就以这个“智慧社区”项目为主线把技术选型的逻辑、数据库怎么设计、接口怎么落、前端怎么接、最后怎么打包部署一条线全部拆开讲清楚。先交代一下适用人群这个项目最适合两类人一是准备毕业设计的计算机专业学生二是想找Java后端或全栈岗位、手里缺一个能讲清楚的项目经验的初学者。你如果已经能把SpringBoot的自动配置说个大概知道Vue3的组合式API和Pinia是干嘛的那看这篇会非常顺畅。如果还在起步阶段文中每个关键点我也会补上“为什么这么做”的底层逻辑按步骤走也能把项目搭出来。1. 技术选型与整体架构拆解1.1 为什么是SpringBootVue3MyBatisMySQL这套组合先说结论这套组合在2026年的Java全栈项目里依然是性价比最高、容错率最低的选择没有之一。后端用SpringBoot看中的不是它“快”而是它的生态完整度和岗位匹配度。现在你去搜Java相关的工作要求十个里有八个写着“熟悉SpringBoot”它已经是Java服务端的事实标准。SpringBoot的自动配置把过去SpringMVC那套繁琐的XML配置全部吞掉了你只需要在application.yml里写几个关键参数一个能跑的Web服务就起来了。这对做毕设或者个人项目的人来说省下来的时间可以全部砸在业务逻辑上。前端这块Vue3已经是绝对的主流。Vue2在2023年底就停止了官方维护新项目再上Vue2属于给自己挖坑。Vue3的组合式APIComposition API带来的好处是逻辑复用变得非常干净同一个功能相关的ref、computed、watch、方法可以放在一起而不是像Vue2的选项式API那样按data、methods、computed分开摊大饼。配合script setup语法糖写起来的手感已经非常接近React的函数组件但模板上手门槛又比JSX低得多。MyBatis作为持久层框架优点就两个字灵活。它不像JPA/Hibernate那样给你套一层厚厚的对象关系映射而是把SQL控制权完全交回到你手里。智慧社区这种项目里有大量多表关联查询、条件动态拼装比如按楼栋单元状态组合筛选业主列表用MyBatis的动态SQL写起来极其顺手。而且国内绝大多数Java岗位面试都会问MyBatis的缓存机制、#{}和${}的区别你项目里用了它面试时就有话可说。MySQL则是毫无争议的默认选项开源、免费、社区资料多到泛滥装个5.7或者8.0都行。智慧社区的数据量级撑死也就百万级MySQL配合合适的索引和分页完全没压力。1.2 前后端分离到底“分离”了什么很多新手对前后端分离的理解就是“前端一个文件夹后端一个文件夹”这是最大的误解。前后端分离的核心是职责分离 接口契约分离前端只负责页面渲染、用户交互、状态管理通过HTTP请求调用后端接口获取数据。后端只负责业务逻辑、数据持久化、权限校验返回JSON数据完全不关心数据最终长成什么HTML。两边通过一份接口文档或者Swagger页面约定请求路径、请求参数、响应格式各自独立开发、独立部署。这样做的好处往小了说是开发效率高——前端不用等你后端写完才能动工你可以先用Mock数据把页面全部搭好后端接口写完直接替换baseURL就能联调。往大了说前端和后端可以分别部署在不同的服务器上前端用Nginx托管静态文件后端跑在独立的Tomcat/内嵌容器里流量压力可以分开处理。如果以后要做App同一套后端接口可以直接复用给移动端——我见过不少公司就是这么干的后端接口一套PC网页、H5、小程序三端共用。1.3 智慧社区项目的模块边界怎么划拿到“智慧社区”这个需求第一步不是写代码而是把业务模块拆出来。拆模块的标准是每个模块要能回答“谁在用、解决什么问题、需要哪些数据”。结合我做过的小区物业类项目核心模块通常这样划分模块使用角色核心功能业主管理物业管理员业主信息的增删改查、按楼栋/单元筛选、入住状态管理房屋管理物业管理员楼栋、单元、房号三级结构维护房屋与业主关联报修管理业主、物业业主提交报修单、上传图片、物业派单处理、状态流转缴费管理业主、物业物业费/停车费账单生成、在线缴费记录、欠费提醒公告通知物业管理员公告发布、置顶、按小区推送车位管理物业管理员车位信息维护、车位与业主绑定关系访客登记安保人员访客信息登记、进出记录系统管理超级管理员用户管理、角色管理、菜单权限分配这套模块划分的逻辑是以“人—房—钱—事”为主线。业主和房屋是基础数据缴费是资金流报修和访客是日常事务流公告是信息流系统管理是所有系统的底座。你做的时候不需要一次性全做挑四五个核心模块就能支撑起一个完整的演示流程——我后面给出的表结构也优先围绕“业主—房屋—账单—报修”这条链路来设计。2. 后端工程落地SpringBootMyBatis的骨架搭建2.1 项目初始化和分层规范后端工程建议直接用Spring Initializr生成start.spring.io或者用IDEA自带的Spring Initializr。关键依赖勾选Spring Web、MyBatis Framework、MySQL Driver、Lombok。版本上不用刻意追新SpringBoot 2.7.x或者3.x都可以但要注意如果你的JDK是8就别选SpringBoot 3.x了——3.0起强制要求JDK 17这是个很常见的坑。工程建好之后分包结构建议这样com.example.community ├── controller // 接口层接收请求、参数校验、返回结果 ├── service // 业务层核心逻辑处理接口实现类 ├── mapper // 数据访问层MyBatis的Mapper接口 ├── entity // 实体类对应数据库表 ├── dto // 数据传输对象接收前端参数、返回前端结果 ├── config // 配置类跨域、拦截器、Knife4j等 ├── common // 公共类统一返回结果、异常处理、工具类 ├── interceptor // 拦截器登录鉴权这个分层方式是基于实际业务反复打磨后的结果。核心原则就一条Controller不写业务逻辑Service不写SQLMapper只做数据访问。很多新手图省事直接在Controller里写userMapper.selectById()短期内是快但一旦项目变复杂你会发现自己改一个逻辑要翻遍所有Controller那感觉就是灾难。规范分层看起来多写了几个类但每个类的职责单一清晰后面扩展功能、排查问题都会快得多。2.2 统一返回结果和全局异常处理必须做前后端分离项目里前后端必须约定一个统一的接口返回格式。我推荐用最经典的{ code: 200, message: 操作成功, data: { } }前端对所有接口都按这个结构解析code为200时取data渲染页面非200时弹出message提示。这比直接返回裸数据要舒服得多——一旦后端报错前端能拿到明确的错误信息而不是解析出一个undefined然后页面白屏。对应的后端代码很简单定义一个泛型类Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(操作成功); result.setData(data); return result; } public static T ResultT error(String message) { ResultT result new Result(); result.setCode(500); result.setMessage(message); return result; } }全局异常处理用RestControllerAdvice把校验异常、业务异常、数据库异常统一拦截转成上面的Result格式返回。这里有个我踩过的坑如果异常不统一处理SpringBoot默认会把异常堆栈直接抛给前端不仅信息冗余、不友好还会暴露内部实现细节比如SQL错误里带着表名和字段名做项目或者答辩的时候都很扣分。2.3 MyBatis的XML映射与动态SQL实战这套项目里我强烈建议用XML方式写SQL而不是用注解。原因有两个第一智慧社区这种业务有大量动态条件查询XML里if标签拼条件比注解里的Select拼字符串强太多了第二面试官问到MyBatis十有八九会问你动态SQL和XML映射的细节你在项目里用过XML回答起来底气完全不一样。举一个业主管理的典型查询按业主姓名、手机号、楼栋号、入住状态筛选同时分页。Mapper接口定义public interface OwnerMapper { ListOwnerVO selectOwnerPage(Param(name) String name, Param(phone) String phone, Param(building) String building, Param(status) Integer status, Param(offset) int offset, Param(size) int size); long countOwner(Param(name) String name, Param(phone) String phone, Param(building) String building, Param(status) Integer status); }对应的XMLselect idselectOwnerPage resultTypecom.example.community.vo.OwnerVO SELECT o.id, o.name, o.phone, o.id_card, o.status, h.building_no, h.unit_no, h.room_no FROM owner o LEFT JOIN house h ON o.house_id h.id where if testname ! null and name ! AND o.name LIKE CONCAT(%, #{name}, %) /if if testphone ! null and phone ! AND o.phone LIKE CONCAT(%, #{phone}, %) /if if testbuilding ! null and building ! AND h.building_no #{building} /if if teststatus ! null AND o.status #{status} /if /where ORDER BY o.create_time DESC LIMIT #{offset}, #{size} /select注意几个细节where标签会自动去掉第一个多余的AND比手动写WHERE 11干净得多模糊查询别用%${name}%会有SQL注入风险要用CONCAT(%, #{name}, %)分页参数offset和size在接口方法上用Param指定名字XML里才能用#{offset}直接引用。表名和字段名都是下划线命名返回对象如果希望直接自动映射到驼峰格式比如building_no对应buildingNo需要在application.yml里加一行配置mybatis: configuration: map-underscore-to-camel-case: true这个配置省掉大量resultMap手写工作强烈建议一开始就加上。3. 数据库设计智慧社区的核心表结构3.1 表结构设计总览与设计准则数据库设计是这类项目的根基表建得烂后面每一个查询都在还债。设计原则就一条按业务实体建模而不是按页面建模。别因为页面上需要一个“业主列表带房屋信息”就把业主字段和房屋字段揉进一张表。正确做法是业主一张表、房屋一张表查询时关联。这样以后房屋换业主、业主买多套房数据模型都不用动。核心表建议这么设计我给出建表SQL的关键部分-- 业主表 CREATE TABLE owner ( id bigint NOT NULL AUTO_INCREMENT, name varchar(50) NOT NULL COMMENT 业主姓名, phone varchar(20) NOT NULL COMMENT 手机号, id_card varchar(18) DEFAULT NULL COMMENT 身份证号, house_id bigint DEFAULT NULL COMMENT 关联房屋ID, status tinyint NOT NULL DEFAULT 1 COMMENT 状态0迁出 1入住 2未入住, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_phone (phone), KEY idx_house_id (house_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT业主信息表; -- 房屋表 CREATE TABLE house ( id bigint NOT NULL AUTO_INCREMENT, building_no varchar(10) NOT NULL COMMENT 楼栋号, unit_no varchar(10) DEFAULT NULL COMMENT 单元号, room_no varchar(20) NOT NULL COMMENT 房号, area decimal(10,2) DEFAULT NULL COMMENT 建筑面积, owner_id bigint DEFAULT NULL COMMENT 当前业主ID, PRIMARY KEY (id), UNIQUE KEY uk_building_unit_room (building_no, unit_no, room_no) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT房屋信息表; -- 账单表 CREATE TABLE bill ( id bigint NOT NULL AUTO_INCREMENT, owner_id bigint NOT NULL COMMENT 业主ID, house_id bigint NOT NULL COMMENT 房屋ID, bill_type varchar(20) NOT NULL COMMENT 账单类型物业费/停车费/水费, amount decimal(10,2) NOT NULL COMMENT 金额, period varchar(20) DEFAULT NULL COMMENT 账期2026-01, status tinyint NOT NULL DEFAULT 0 COMMENT 状态0未缴 1已缴, pay_time datetime DEFAULT NULL COMMENT 缴费时间, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_owner_id (owner_id), KEY idx_house_id (house_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT缴费账单表; -- 报修表 CREATE TABLE repair ( id bigint NOT NULL AUTO_INCREMENT, owner_id bigint NOT NULL COMMENT 报修业主ID, content varchar(500) NOT NULL COMMENT 报修内容, images varchar(1000) DEFAULT NULL COMMENT 图片路径逗号分隔, status tinyint NOT NULL DEFAULT 0 COMMENT 状态0待处理 1处理中 2已完成 3已取消, create_time datetime DEFAULT CURRENT_TIMESTAMP, handle_time datetime DEFAULT NULL COMMENT 处理时间, PRIMARY KEY (id), KEY idx_owner_id (owner_id), KEY idx_status (status) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT报修工单表;3.2 几个关键的字段设计细节第一统一的ID策略。直接用数据库自增bigint就好。别在毕设项目里引入雪花算法或者MyBatis-Plus的ASSIGN_ID那是分布式场景下才需要的东西单库单表自增ID性能完全够用而且肉眼排查数据时自增ID最直观。第二状态字段用tinyint而不是varchar。比如业主状态0/1/2、账单状态0/1存数字的好处是查询快、存储小、便于做条件筛选。前端展示时再通过枚举或者字典翻译成“已入住”“未入住”这种文案。如果你前后端都在自己手里甚至可以约定好后端直接返回翻译好的状态名省得前端每个字段都做映射。第三时间字段统一datetime并设置默认值。create_time用DEFAULT CURRENT_TIMESTAMP、update_time加ON UPDATE CURRENT_TIMESTAMP这样插入和更新时你连代码都不用写数据库自动维护少写很多重复代码。第四逻辑外键但物理上不建外键约束。表里保留house_id、owner_id这种关联字段但不要真去建FOREIGN KEY约束。原因很实际智慧社区这种管理系统删除操作比较频繁比如测试数据要清理物理外键会挡住删除而且影响写入性能。关联完整性靠代码和接口层保证就够了。3.3 初始化数据的准备技巧项目演示的时候最尴尬的场面就是数据库里没数据页面空空如也。所以建完表之后一定要准备一份初始化SQL造一些看起来真实的数据。造数有几个技巧名字就用“张三丰”“李秀英”这种常用名手机号用13x开头的虚拟号段楼栋就1栋、2栋、3栋各造几户。报修数据把状态分布开既有待处理的也有处理中的和完成的这样演示时所有页面都有内容可看。账单数据要造一些已缴的、一些未缴的而且未缴的要有最近的账期比如当前月份这样你演示“欠费催缴”功能时才有素材。一份好的初始化数据能让你的项目答辩效果提升一个档次这句话我说在前头。4. 前后端接口联动从JWT鉴权到Vue3页面4.1 登录鉴权方案JWT还是Session智慧社区项目里有业主端和物业端必须做登录鉴权。现在主流方案就两个Session和JWT。我的意见很明确用JWT但只是简单的JWT不引入Spring Security。为什么不引入Spring Security因为它的概念体系对初学者太重了——过滤器链、UserDetailsService、SecurityContext、各种放行配置你光搞明白这套东西就要好几天而且它的版本配置在SpringBoot 2.7和3.x之间又不一致一套配置写错就是全线报错。智慧社区的权限粒度做到“登录才能访问接口管理员才能访问管理接口”这个级别自己写个拦截器也就十几行代码。JWT流程如下用户提交用户名密码后端校验通过后生成JWT返回前端。JWT里只放用户ID、用户名、角色这些非敏感信息不要放密码。前端拿到JWT存在localStorage后续每次请求在Authorization请求头带上Bearer token。后端拦截器校验JWT签名和有效期通过则放行并把用户信息放入请求上下文。后端生成JWT用jjwt库引入依赖后核心代码大致长这样// 生成token String token Jwts.builder() .setSubject(String.valueOf(user.getId())) .claim(username, user.getUsername()) .claim(role, user.getRole()) .setExpiration(new Date(System.currentTimeMillis() 86400000)) // 24小时有效期 .signWith(SignatureAlgorithm.HS256, secretKey) .compact();拦截器里解析并校验public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String header request.getHeader(Authorization); if (header null || !header.startsWith(Bearer )) { response.setStatus(401); return false; } String token header.substring(7); try { Claims claims Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody(); request.setAttribute(userId, claims.getSubject()); request.setAttribute(role, claims.get(role)); return true; } catch (Exception e) { response.setStatus(401); return false; } }这里有个实操细节secretKey要用足够长的字符串至少32个字符。不然jjwt在解析时会因为密钥强度不够直接抛异常这个错误信息写得比较隐晦我第一次用的时候调了半天才反应过来。4.2 跨域问题与接口联调配置前后端分离开发模式下前端跑在localhost:5173Vite默认端口后端跑在localhost:8080端口不同必然产生跨域。Vite自带的代理功能是开发环境最优解不需要后端动任何代码。在vite.config.js里配置export default defineConfig({ server: { port: 5173, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } })这样前端请求/api/owner/list时Vite会把它转发到http://localhost:8080/api/owner/list浏览器看到的请求是同源的就不会有跨域报错。生产部署时如果前端用Nginx托管、后端是独立服务也同样用Nginx的location /api做反向代理思路完全一致。另一个方案是后端加全局跨域配置用CrossOrigin或者CorsFilter。但这个方法只适合开发阶段临时用生产环境不建议轻易开放跨域——API暴露给任何域名等于关掉了浏览器同源策略这道防线。优先用代理方案这是行业里的基本共识。4.3 Vue3前端工程搭建和页面骨架前端工程用Vite创建命令就一句npm create vitelatest community-web -- --template vue项目创建后先装路由、状态管理和UI组件库npm install vue-router4 pinia element-plus axios sassElement Plus是这套项目的UI基座它的表格、表单、弹窗、菜单组件都很成熟做管理后台可以省一大半样式工作。装完之后在main.js里全局注册组件——注意Element Plus的图标是按需引入的第一次用全量引入element-plus/icons-vue可能体积大一点但对开发体验更友好打包时可以做优化先跑起来要紧。路由设计需要用到嵌套路由后台管理页面的典型结构是一个左侧菜单右侧内容区菜单对应父路由不同页面是子路由const routes [ { path: /login, component: Login }, { path: /layout, component: Layout, redirect: /dashboard, children: [ { path: dashboard, component: Dashboard }, { path: owner, component: OwnerList }, { path: house, component: HouseList }, { path: bill, component: BillList }, { path: repair, component: RepairList } ] } ]路由守卫用beforeEach判断有没有token、要去的页面是不是登录页逻辑很简单但很关键router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.path ! /login !token) { next(/login) } else { next() } })4.4 Axios封装统一请求头和错误拦截前端调用后端接口必须要封装一个Axios实例。如果每个页面都直接axios.get(/api/xxx)你后续要加token、要统一处理401超时就得改几十个文件。封装之后就一个文件要动这就是工程化思维。import axios from axios const request axios.create({ baseURL: /api, timeout: 10000 }) // 请求拦截器自动带上token request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) // 响应拦截器统一处理业务code和HTTP错误 request.interceptors.response.use( response { const res response.data if (res.code 200) { return res } ElMessage.error(res.message) return Promise.reject(new Error(res.message)) }, error { if (error.response error.response.status 401) { localStorage.removeItem(token) router.push(/login) ElMessage.error(登录已过期请重新登录) } else { ElMessage.error(网络请求异常) } return Promise.reject(error) } )封装好之后页面里就能非常简洁地调用const listData ref([]) const loading ref(false) const fetchList async () { loading.value true try { const res await request.get(/owner/list, { params: queryParams }) listData.value res.data.records total.value res.data.total } finally { loading.value false } }4.5 页面落地一个完整的业主管理模块业主管理模块是最能体现前后端联动的模块建议第一个做。页面结构是顶部条件搜索区姓名、手机号、楼栋号 数据表格 分页器 新增/编辑弹窗。用script setup组织逻辑时把“搜索条件”定义成一个响应式对象查询、重置、分页变化都复用同一个fetchList方法const queryParams reactive({ name: , phone: , building: , pageNum: 1, pageSize: 10 }) const handleQuery () { queryParams.pageNum 1 fetchList() } const handleReset () { queryParams.name queryParams.phone queryParams.building queryParams.pageNum 1 fetchList() }表格列渲染时要注意状态字段的处理。后端返回的status是数字直接展示出来用户看不懂要么在表格列里用formatter转换要么用标签组件配合枚举对象const statusMap { 0: { label: 迁出, type: info }, 1: { label: 入住, type: success }, 2: { label: 未入住, type: warning } }然后在表格列里这样绑定:formatter(row) statusMap[row.status]?.label || 未知。新增和编辑共用一个弹窗组件关键点就是区分isEdit状态新增时表单留空编辑时用当前行数据回填。提交成功后关弹窗重新拉列表提示成功三步一气呵成。这块代码逻辑不难但它是所有管理模块的模板做顺了这个房屋、账单、报修都是复制粘贴微调的节奏。5. 部署上线从开发环境到生产环境5.1 前端打包与SpringBoot结合的两种方式项目做完了要演示、要交给导师检查这时候面临部署问题。前后端分离的部署方式无非两种分开部署和合并部署。分开部署是标准生产模式前端打包后丢给Nginx托管后端打成Jar包直接java -jar跑。这种模式的好处是前后端完全独立扩容互不影响但需要你有一台服务器或者演示时开两个进程端口。合并部署是毕设演示的省事方案Vue项目构建后生成的dist目录直接把里面的文件复制到SpringBoot的src/main/resources/static目录下重新打包后一个Jar包全搞定。访问时后端同时承担静态文件服务和接口服务。这样你只需要跑一个进程无论拷到哪里给别人演示都方便。但有个细节要注意开发时Vite代理的/api前缀生产部署时如果前端静态资源和后端接口在同一个应用中前端里的请求路径必须保持/api前缀并且后端Controller的RequestMapping也要带/api前缀这样请求/api/xxx才能正确路由到接口而不会和静态文件冲突。如果走Nginx部署配置参考server { listen 80; server_name your-domain.com; location / { root /usr/share/nginx/html; index index.html; try_files $uri $uri/ /index.html; # 前端路由是history模式必须加这句 } 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模式的前提少了它刷新页面就会404。5.2 打包命令与构建配置前端打包命令npm run build构建产物在dist目录下。如果你要把前端合并进SpringBoot别忘了检查vite.config.js里的base选项——如果部署在根路径保持默认就行如果部署在某个子路径比如/admin就得设置base: /admin/否则资源路径会全部404。后端打包前把application.yml里数据库连接、JWT密钥等环境相关配置再核对一遍。多环境配置建议用spring.profiles.active机制拆成application-dev.yml和application-prod.yml开发环境连本地数据库生产环境连接服务器数据库切换只需改一个激活项。打包命令mvn clean package -DskipTests启动java -jar target/community-server.jar --spring.profiles.activeprod5.3 文档和演示脚本准备这个建议很少人提但实战价值很高。项目做完后写一份演示脚本对和写剧本一样把系统核心操作流程串起来管理员登录展示首页数据看板。进入业主管理搜索“张三”展示查询结果。新增一个业主关联房屋演示弹窗表单。进入缴费管理选一个欠费账单标记已缴。以业主身份登录提交一个报修单再切回管理员处理工单。把这份脚本练熟是答辩或面试时展示项目的关键。很多人口头描述项目时逻辑混乱但跟着脚本一步步操作语言自然清晰。这份脚本我建议写到项目的README里既方便自己也方便导师或面试官快速了解系统。6. 高频报错排查与避坑清单6.1 联调阶段的经典报错前后端联调是问题最多的阶段我把最常见的问题按出现频率排个序数据库连接报错。常见两种一是Access denied for user rootlocalhost说明用户名密码错了检查application.yml里的username和password二是Public Key Retrieval is not allowed这是MySQL 8.0的加密插件导致JDBC连接串上加allowPublicKeyRetrievaltrueuseSSLfalse就解决。端口被占用。SpringBoot默认8080端口被其他进程占了启动直接失败。命令行执行netstat -ano | findstr 8080找到PID然后taskkill /F /PID pid杀掉或者临时换端口server.port8081。前端请求404。后端接口能直接访问但前端请求404时先看请求URL对不对、/api路径有没有对上。还有一个隐藏坑SpringBoot的RequestMapping路径如果和前端baseURL拼接后多了一个斜杠或少了一个斜杠都可能404排查时直接把完整的请求URL复制到浏览器或者Postman里测试最快。跨域(CORS)报错。浏览器控制台出现Access to XMLHttpRequest at ... has been blocked by CORS policy开发阶段用Vite代理解决别在后端乱加CrossOrigin理由前面说过。日期格式问题。后端返回2026-01-01T10:30:00这种带T的格式前端表格渲染出来不太好看。统一在application.yml配置spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8这里要特别注意时区配置。如果不设置time-zone: GMT8中国用户看到的时间会差8小时这在缴费账单这种对时间敏感的业务里是非常容易暴露的错误。6.2 业务逻辑层的隐藏坑删除数据时外键关联是本项目最大的坑。比如要删除一条房屋记录但该房屋下还有业主和账单直接删会破坏数据完整性。业务上必须做校验删除前先查关联表有没有数据有就提示“该房屋下存在业主/账单无法删除”。这才是真实系统该有的行为也值得写进你的项目亮点里。分页查询的总数问题。很多人只写了分页列表查询忘了写count查询导致前端分页组件显示的总条数永远是0。MyBatis里必须配套一个count方法并且where条件要和列表查询完全一致。如果你用PageHelper注意它会在本地线程里持有分页参数用完之后要及时清理否则下一个查询可能会被错误分页。并发和数据一致性问题。比如两个管理员同时处理同一张报修单后提交的覆盖了先提交的。智慧社区项目里最常用的处理方式表里加一个version字段更新时SET status #{newStatus} WHERE id #{id} AND version #{oldVersion}受影响行数为0说明被其他人改过了提示用户刷新重试。这个优化虽然简单但面试时提到乐观锁控制并发分量一下子就起来了。6.3 前端页面的常见问题表格数据不显示。优先检查接口返回的数据结构。我之前遇到过前端代码写res.data.records但后端返回的是res.data.list字段对不上页面渲染不出任何内容。这类问题用浏览器的Network面板看响应JSON一眼就能定位比在页面代码里瞎猜快得多。刷新404。Vue Router用了history模式前端部署后直接刷新子页面比如刷新/owner出现404。这就是Nginx配置里try_files的问题按前面的配置加好就解决。如果你把前端合并进SpringBoot则要确保后端没有拦截/owner这种无/api前缀的路径否则会被JWT拦截器挡住。Element Plus表格操作列宽度不够。表格里放了编辑、删除、详情三个按钮时操作列宽度至少要设140px否则按钮会换行或者溢出。这算个小细节但演示时按钮挤在一起确实很拉低观感。7. 这套源码改造成简历项目的思路项目跑通只是第一步能把它讲出彩更重要。我建议你在熟悉代码之后往这几个方向做一点深挖面试时能讲的东西会完全不同第一数据权限。现在是所有物业管理员都能看到所有业主能不能改成“A管理员只能看A号楼的数据”这就涉及在查询SQL里加数据范围过滤是一个很典型的数据权限设计点。第二缓存优化。业主列表、楼栋信息这些变化不频繁的数据能不能用Cacheable做Redis缓存引入Redis能聊的话题立刻变多缓存穿透、缓存击穿、缓存雪崩每个都是Java岗位的高频面试题。第三消息通知。报修状态变化时怎么通知业主可以引入WebSocket实时推送或者用RabbitMQ做异步消息。这个点一加项目从“CRUD管理系统”直接升级成“有事件驱动架构的系统”。第四文件上传改造。报修图片现在存在本地磁盘能不能上传到对象存储服务改造后业务逻辑不变但扩展性和面试话题又是质的提升。我个人在实际操作中的体会是一个项目能走多远不取决于它用了多新的技术而取决于你是否能把每一层逻辑讲清楚。智慧社区这套项目最大的价值就是它麻雀虽小五脏俱全从数据库设计到权限控制从联调调试到打包部署完整覆盖了一个真实Web系统从零到一的全过程。你把它吃透一遍之后举一反三再做任何管理系统类项目都会发现骨架是完全一样的只不过换了一套业务皮。最后再分享一个小技巧做这类项目时坚持每天在项目仓库里提交代码并且提交信息写清楚比如feat: 完成业主列表分页查询。这不仅是给自己留后路而且面试时GitHub主页上的提交记录本身就是一种“持续开发和代码规范意识”的证明。很多技术过硬的候选人都是栽在这类不起眼的工程习惯上。