
装修行业这些年一直在喊“数字化转型”但真正落到中小装修公司头上大多数还在靠Excel表格加微信群在管项目。客户问进度工长口头汇报材料对不上账验收节点全靠人盯——这套路我太熟了因为我前后帮两三家装修公司做过内部管理系统。今天这篇就把其中最通用、也最值得复现的一套方案完整拆出来基于SpringBoot Vue的前后端分离在线装修管理系统Java MySQL MyBatis这套经典技术栈从需求分析、数据库设计到核心功能实现再到实际开发中那些文档里不会写的坑一次性讲透。如果你是正在做毕业设计或简历项目的学生或者刚转行Java后端、想找个完整项目练手的朋友这篇文章可以直接当参考手册用。全文不吹不黑只讲实际操作中验证过的东西——包括哪些表结构设计是可以直接照抄的、哪些代码有坑、哪些功能点面试官最喜欢深挖。1. 装修业务的特殊性这个系统不能只做“通用CRUD”很多新人拿到“装修管理系统”这个题目第一反应就是客户管理、订单管理、员工管理套个增删改查不就完事了真这么做出来懂行的人一眼就知道是玩具项目。装修行业有自己的业务节奏系统设计必须贴合这些特点才站得住脚。装修项目有四个非常鲜明的特征直接决定了表结构和功能模块怎么设计第一个特征是强流程性。一个装修项目从量房、出设计图、报价、签合同到水电改造、泥瓦工进场、木工、油漆、安装、验收每个节点都有先后顺序而且节点之间有依赖关系。水电没验收泥瓦工就不能进场。所以系统里必须有一套“节点状态机”不能简单地用“进行中/已完成”两个状态糊弄。第二个特征是长周期。普通订单可能几天就结束了装修项目短则45天长则半年。这意味着系统里的数据是持续累积的预算要能追加、材料要能补单、变更要能留痕。如果设计表结构时没考虑到“历史轨迹”这个概念后面做追溯功能时必然返工。第三个特征是角色多且数据权限复杂。一套装修系统里的角色至少有客户、销售/家装顾问、设计师、项目经理工长、材料员、财务、老板。客户只能看自己房子的进度设计师管图纸和主材推荐项目经理管施工队和现场老板要看全部项目和整体利润。这已经不是“三个角色三套权限”简单能覆盖的了必须做基于RBAC的数据范围隔离。第四个特征是单价高、变更频繁。装修预算少则十万多则几十万而且中间一定会有增项、减项、主材升级。所以预算模块不能只存一个总额必须支持“每一项都有明细、每一次变更都有记录”。理解了这四点你再去设计系统结构和普通“管理系统”的区别一下就出来了。这也是我在给项目写简历或答辩时最看重的分析维度——有没有真正理解业务从数据结构上就能看出来。2. 技术选型分析SpringBoot Vue这套组合为什么最稳这套系统的技术栈是后端SpringBoot 2.7 MyBatis前端Vue 2 Element UI数据库MySQL 8.0。说实话这不是什么新潮的组合但恰恰是现阶段Java全栈项目里性价比最高、最适合复现和扩展的一套。2.1 后端为什么用SpringBoot而不是SSH或Spring CloudSpringBoot的核心价值在于“约定大于配置”。对于装修管理系统这种体量几十张表、几十个接口、单机部署完全够用用SpringBoot可以把大量时间花在业务逻辑本身而不是XML配置和容器部署上。选2.7版本而不是3.x主要考虑的是生态兼容性。SpringBoot 3.0强制要求JDK 17并且javax命名空间全部迁移到jakarta很多老教程和老代码直接跑不起来。而SpringBoot 2.7配合JDK 8或JDK 11MySQL驱动、MyBatis Starter、Druid连接池这些组件的版本兼容性都已经被验证过无数遍踩坑成本最低。MyBatis和JPAHibernate的选型也是老生常谈了。用MyBatis就是因为装修系统里有大量复杂查询——多表关联、动态条件、统计报表。MyBatis的SQL是你自己写的每个查询的性能和逻辑都在掌控之中虽然开发时手写的代码量多一点但排查问题时那种“SQL一眼就能看明白”的踏实感是JPA自动生成SQL给不了的。2.2 前端为什么用Vue 2 Element UIVue 2虽然官方已经停止维护了但存量项目和二手资料量巨大遇到任何问题基本都能搜到解决方案。Element UI的组件风格非常适合后台管理系统表格、表单、弹窗、步骤条、树形控件这些都是现成的做装修系统的项目进度看板、预算明细表、材料清单几乎不需要自己造轮子。如果你是新手我不建议一上来就上Vue 3 Vite TypeScript Pinia全家桶。不是说这套不好而是技术栈越新遇到问题时的排查链路越长。Vue 2的Options API配合Vuex理解起来直观写出来的代码也稳定。2.3 整体架构的前后端分离部署逻辑系统部署结构是典型的前后端分离前端Nginx托管静态文件后端SpringBoot打成jar包跑在服务器上MySQL单独一台或同机部署都可以。前后端通过JSON交互接口遵循RESTful风格。这种架构有两个直接好处。第一前端和后端可以并行开发接口文档先定好两边同时开工工期能压缩很多。第二后续如果要扩展移动端比如给项目经理配一个安卓端的巡检打卡App后端接口可以无缝复用只需要给前端重新做一套适配。3. 数据库设计是整个系统的灵魂核心表结构和设计思路说实话判断一个管理系统做得专不专业不用看代码直接打开数据库看表结构就一目了然了。装修系统的表设计我推荐按照“客户档案、项目主线、材料与预算、施工与验收、人员组织”五个维度来组织。3.1 客户表与房屋信息表一对一还是分开存客户和房屋信息建议拆成两张表用customer_id关联。为什么因为一个客户可能有多个房屋——自己家一套父母家一套甚至投资房一套。如果在客户表里直接存“房屋地址”字段后面做“按小区统计项目数量”就非常痛苦。客户表核心字段customer_id主键customer_name客户姓名phone联系电话wechat微信号source获客渠道转介绍/线上广告/门店自然到访follow_status跟进状态待联系/已联系/已到店/已签约/已流失created_time创建时间房屋信息表核心字段house_id主键customer_id关联客户community_name小区名称building_no楼栋号unit_no单元号room_no房号area建筑面积house_type户型如三室两厅一卫这里一个重要设计点是customer_id以外的房屋字段是冗余还是关联。我建议直接建一个house_address的冗余字段把“小区楼栋单元房号”拼接存储这样列表页展示和搜索性能远好于每次都用四五个字段拼接。3.2 项目主表装修系统的“中央枢纽”项目表是整个系统当之无愧的核心几乎所有业务数据都直接或间接关联到这张表。关键字段设计如下project_id主键project_no项目编号如ZX20240516001用于线下沟通和合同引用customer_id关联客户house_id关联房屋designer_id关联设计师员工表project_manager_id关联项目经理工长salesman_id关联销售project_status项目状态待量房/设计中/待签约/施工中/已竣工/已售后start_date计划开工日期end_date计划竣工日期actual_start_date实际开工日期actual_end_date实际竣工日期contract_amount合同金额budget_amount预算金额remark备注仔细看这个表它的重点在于“冗余了多个角色外键”。这样设计的目的很简单——所有跟项目相关的查询都可以通过project表一次关联到位不用绕圈子。项目编号用业务前缀 日期 流水号的格式这个格式在打印合同时非常有用也方便人工记忆。project_status这个字段我用的是VARCHAR类型存储状态码而不是直接用中文。这个习惯很重要因为状态码是可枚举的在Java代码里定义常量或枚举类统一管理避免数据库里存着五花八门的“施工中”“施工 中”“施工中 ”这种带空格脏数据。3.3 节点表与进度记录表支撑“施工状态机”装修项目一定要有“节点进度”的概念。我设计的核心表是project_node项目节点表node_id主键project_id关联项目node_name节点名称量房/设计/报价/签约/拆改/水电/泥瓦/木工/油漆/安装/竣工验收plan_start_date计划开始时间plan_end_date计划结束时间actual_start_date实际开始时间actual_end_date实际结束时间node_status节点状态未开始/进行中/待验收/已完成/已延期sort_order排序号为什么加一个sort_order排序字段因为装修节点的顺序虽然相对固定但不同公司流程有差异——有的公司把“主材确认”放在开工前有的放在水电之后。有一个独立的排序字段管理员就可以在后台灵活调整节点顺序而不需要改代码。对应的project_node_log节点操作日志表记录每一次状态变更谁在什么时间把哪个节点从什么状态改成了什么状态附带操作说明和现场照片URL列表。这个表是后面做“项目动态时间线”的数据来源客户端的“项目进度”页面就是从这里取数展示的。3.4 预算与材料表处理好“增项”是精髓装修预算的核心难点在于预算会变。一个项目从签约到竣工预算调整三五次非常正常。所以预算设计我拆成三张表budget_item预算项主表记录每个预算大类和预算项名称如“水电工程-强电改造”“主材-瓷砖-客厅地砖”budget_item_detail预算项明细表记录每个预算项的单价、数量、单位、小计、备注budget_change_log预算变更记录表记录每一次变更的类型增项/减项、变更金额、变更原因、操作人、审批人这里的关键设计是预算项本身不做物理删除。当客户要求取消某一项时逻辑上的做法是把该项的change_type标记为“减项”并把变更记录写入变更日志表。这样你随时可以回答老板的问题“这个项目本来预算是多少后来加了哪些项加了多少钱谁批准的”——这是财务管理的基本底线。材料表material_stock和预算表之间我选择了弱关联。也就是说材料库存表自己管自己的入库出库不会因为预算项的增删而自动变动。这么设计的原因是装修公司通常会批量采购材料比如一次性买50袋水泥材料无法精确对应到某一个预算项。弱关联更符合实际操作。3.5 员工、角色和权限表用RBAC模型但别过度设计员工表sys_user、角色表sys_role、菜单表sys_menu、用户角色关联表sys_user_role、角色菜单关联表sys_role_menu这套经典五表RBAC模型可以直接用。但我强烈建议不要在这个阶段引入Spring Security或者Shiro这类重量级安全框架——就用一个简单的拦截器或AOP切面在Controller层校验登录状态和角色权限就完全够了。原因是装修管理系统的用户量一般在几十到几百之间不存在复杂的OAuth2、SSO需求。引入Spring Security反而增加了配置复杂度而且它的过滤器链和MyBatis的分页插件有时会互相干扰排查起来特别费劲。用拦截器校验权限的做法非常轻量登录成功后把用户ID和角色ID放进Session或Redis自定义一个RequirePermission(project:update)注解标记在Controller方法上拦截器里做匹配判断不通过就返回401。这个方案代码量少、逻辑直观而且面试时反而更好讲清楚原理。4. 后端核心功能实现从登录鉴权到项目进度流转数据库设计好了接下来就是后端代码的落地。这一部分我挑几个最有代表性的功能讲JWT登录鉴权、项目进度状态流转、预算变更记录、以及MyBatis的动态SQL在真实场景中的写法。这些都是同一个项目里反复使用、最值得下功夫的地方。4.1 基于JWT的登录鉴权实现登录鉴权我没有用Session而是用了JWT。原因很简单前后端分离架构下Session跨域处理比较麻烦JWT无状态、扩展性好后续要接App也方便。实现思路是用户提交账号密码后端校验通过后生成TokenToken的payload里放userId、username、roleId这些非敏感信息。Token有效期设为24小时前端拿到后存在localStorage里每次请求在Header里带Authorization: Bearer token。后端写一个JwtInterceptor拦截器用HandlerInterceptor实现在preHandle方法里解析Token校验签名和过期时间。Token解析成功后把用户信息放进ThreadLocal用于线程内传递Controller里直接UserContext.get()取当前登录用户。核心代码大致如下public class JwtUtils { private static final String SECRET your-256-bit-secret-key; private static final long EXPIRE_TIME 24 * 60 * 60 * 1000; public static String generateToken(Long userId, String username, String roleCode) { return Jwts.builder() .claim(userId, userId) .claim(username, username) .claim(roleCode, roleCode) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() EXPIRE_TIME)) .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } public static Claims parseToken(String token) { return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody(); } }这里有个特别容易踩的坑JWT的密钥不能硬编码在代码里。至少要放到application.yml配置文件中用Value注解读取。如果你连这层都不想被说简陋可以配置Jasypt对配置文件里的密钥做加密也就是把your-256-bit-secret-key变成一段密文存到yml里运行时解密。这个做法在面试中聊起来是个不错的亮点。4.2 项目进度状态流转状态机模式避免一堆if-else装修项目的状态流转是有方向性的待量房→设计中→待签约→施工中→已竣工→已售后。这些状态之间有一些是允许跳转的比如设计中可以直接到待签约有一些是非法跳转比如待量房不能直接跳到已竣工。如果每个接口都写一套if-else判断不仅代码冗长而且随着状态增多维护成本会指数级上升。我推荐用简单的状态机模式定义一张“状态流转配置表”或者使用Java枚举来管理允许的流转路径。public enum ProjectStatusFlow { // 当前状态 - 可流转到的下一状态集合 PENDING_MEASURE(Arrays.asList(ProjectStatus.DESIGNING, ProjectStatus.DISCARDED)), DESIGNING(Arrays.asList(ProjectStatus.PENDING_CONTRACT, ProjectStatus.PENDING_MEASURE)), PENDING_CONTRACT(Arrays.asList(ProjectStatus.CONSTRUCTING, ProjectStatus.DESIGNING)), CONSTRUCTING(Arrays.asList(ProjectStatus.COMPLETED, ProjectStatus.PENDING_CONTRACT)), COMPLETED(Arrays.asList(ProjectStatus.AFTER_SALE)), AFTER_SALE(Collections.emptyList()), DISCARDED(Collections.emptyList()); private ListString nextStates; // 省略构造方法和getter }在Service层写状态变更方法时先校验当前状态是否在允许的nextStates列表中不在就抛业务异常。这样既保证了数据的安全性又让代码的可读性非常好。这个设计在后续面试中讲出来比“我用if判断了状态”要高一个层级。4.3 预算变更的完整链路事务与日志缺一不可预算变更的接口是财务相关的核心接口每一步都要严谨。我的实现链路是前端调用POST /api/budget/change接口参数包括项目ID、变更类型增项/减项、变更项名称、单价、数量、原因。后端在BudgetService.changeBudget()方法中做三步操作第一步插入一条budget_change_log记录状态为“待审批”。第二步更新budget_item_detail表中对应预算项的金额如果是新增项则插入新明细。第三步更新project表中的budget_amount字段预算总额同步加减。这个Service方法上标注Transactional(rollbackFor Exception.class)保证三步操作要么全部成功要么全部回滚。审批动作单独一个接口由项目经理或老板角色操作。审批通过时把变更日志的状态更新为“已通过”审批驳回时系统自动回滚预算明细和总额。这块还有一个细节变更金额如果是负数减项库存和已下单材料是否需要联动处理我的建议是系统只做“台账记录”不做自动库存联动。因为实际业务中材料退货涉及物流、破损、供应商政策等线下因素系统强行自动化反而会制造更多麻烦。自动化要适可而止这个度要把握好。4.4 MyBatis动态SQL实战多条件关联查询怎么写才高效动态SQL是MyBatis最核心的能力也是装修系统里最高频使用的技术。比如“项目列表”这个接口查询条件可能有项目状态、客户姓名模糊搜索、小区名称、项目经理ID、计划开工日期区间。这些条件组合起来有十几种可能用JDBC手工拼接SQL会疯掉用MyBatis的where标签则轻松很多。select idselectProjectPage resultTypecom.example.vo.ProjectVO SELECT p.*, c.customer_name, c.phone, h.community_name, u1.real_name AS designer_name, u2.real_name AS manager_name FROM project p LEFT JOIN customer c ON p.customer_id c.customer_id LEFT JOIN house h ON p.house_id h.house_id LEFT JOIN sys_user u1 ON p.designer_id u1.user_id LEFT JOIN sys_user u2 ON p.project_manager_id u2.user_id where if testprojectStatus ! null and projectStatus ! AND p.project_status #{projectStatus} /if if testcustomerName ! null and customerName ! AND c.customer_name LIKE CONCAT(%, #{customerName}, %) /if if testcommunityName ! null and communityName ! AND h.community_name LIKE CONCAT(%, #{communityName}, %) /if if testmanagerId ! null AND p.project_manager_id #{managerId} /if if teststartDate ! null AND p.plan_start_date gt; #{startDate} /if if testendDate ! null AND p.plan_end_date lt; #{endDate} /if /where ORDER BY p.create_time DESC LIMIT #{offset}, #{pageSize} /select这个SQL里有两个经验点值得展开讲。第一用LEFT JOIN而不是INNER JOIN。因为一个项目可能还没分配设计师designer_id为空如果用INNER JOIN这个项目就会被过滤掉。LEFT JOIN可以保证项目记录始终完整展示关联表查不到就为NULL。第二模糊查询尽量使用CONCAT函数而不是直接写LIKE %${customerName}%。${}是字符串拼接存在SQL注入风险CONCAT(%, #{customerName}, %)是参数预编译安全且写法优雅。配合MyBatis的分页插件PageHelper只要在查询前一行PageHelper.startPage(pageNum, pageSize)分页就完成了非常方便。但要注意PageHelper的Page对象和MyBatis的缓存有时会冲突如果你在同一个方法里执行了两次查询而只设置了一次分页第二次查询会被影响。解决办法是每个分页查询单独开一个Service方法不要让其他查询逻辑混进来。5. 前端的关键页面实现项目看板、预算管理和移动端适配后端接口设计清楚了前端的核心页面就顺理成章。装修管理系统的前端页面里最值得好好做的是这三个项目进度看板、预算明细管理和施工现场照片上传。这三个页面直接决定了客户和项目经理每天愿不愿意打开这个系统。5.1 项目看板用步骤条加时间线展示进度项目进度页面我用Element UI的el-steps组件加自研的时间线组件组合实现。顶部是步骤条展示当前项目处于哪个大阶段template div classproject-progress el-steps :activeactiveStep align-center el-step title量房 :iconElIconHouse/el-step el-step title设计 :iconElIconEdit/el-step el-step title签约 :iconElIconDocument/el-step el-step title施工 :iconElIconTools/el-step el-step title竣工 :iconElIconCheck/el-step el-step title售后 :iconElIconService/el-step /el-steps /div /template步骤条下面是节点详细信息的卡片列表。每个卡片展示一个节点的计划时间和实际时间如果节点延期了用红色标签标记“已延期N天”点开卡片可以看到这个节点下的所有操作日志和现场照片。这里的核心交互逻辑是项目经理在手机上点某个节点的“完成”按钮系统会弹出确认框要求填“完工说明”和上传照片提交后后端自动更新节点状态并把操作写入日志表。这个流程要在前端做好体验优化——照片上传用异步上传先传图片拿回URL再随表单一起提交避免用户等太久。5.2 预算管理页金额变动全程留痕预算管理页我建议做成“抬头汇总 明细表格 变更记录抽屉”三部分。抬头汇总展示当前项目的合同金额、预算总额、已变更金额、待审批金额。明细表格是预算项的完整列表每一项都有单价、数量、金额。点“变更记录”弹出抽屉展示这个项目所有的增项减项历史每条记录都带操作人、审批状态、审批人和审批意见。新增变更项的弹窗里需要做一个比较关键的校验逻辑变更后的预算总额不能超过合同总额的某个比例比如10%。超过的话需要额外填写“超预算原因”并且审批人必须是老板角色。这个业务规则看起来简单但正是这些细节让系统在真实业务中真正可用。5.3 移动端适配的取舍装修系统的用户里项目经理、工长、材料员绝大多数时间在工地现场不可能坐在电脑前操作。所以前端必须做移动端适配。我用的方案是同一套Vue项目引入lib-flexible做rem适配同时针对常见页面单独写移动端布局。具体来说桌面端侧边栏菜单在移动端变成底部Tab栏表格在移动端自动切换成卡片列表模式。Element UI的表格组件在手机上体验很差一行数据十几个字段会被挤得没法看所以项目进度页面和预算页在移动端用van-listVant组件重新设计了一套卡片式布局。这个工作量确实不小但这是系统能不能真正被用起来的决定性一步。我在实际项目中第一版只做了PC端结果发现项目经理根本不用系统天天靠微信汇报。后来花了两周补了移动端适配使用率才真正上来。这个教训希望对你有帮助。6. 实际开发中踩过的坑MyBatis缓存、图片存储和权限拦截每个项目都有一些文档里不会写、但实际开发总会撞上的坑。这里整理几个我在这套装修系统里真实遇到、且解决后觉得很有价值的问题。6.1 MyBatis一级缓存引发的事务可见性问题MyBatis的一级缓存是SqlSession级别的默认开启。在Spring集成环境下如果同一个Service方法中连续两次查询同一个用户第二次查询会直接走缓存不查数据库。这在单线程下没问题但如果你在同一个事务里先update了这条数据再查询同一行读到的可能还是旧值。我当时遇到的具体场景是项目经理把节点状态从“施工中”改为“待验收”然后立即查询这个项目的最新进度结果前端拿到的是旧的“施工中”状态。排查了半天最后定位到是MyBatis一级缓存导致的脏读。解决方案有两种第一种最简单粗暴在查询方法的Mapper XML里加flushCachetrue强制每次查询都刷新缓存。缺点是这个配置会禁用该语句的一级缓存性能有轻微影响。第二种在Service层把“写操作”和随后的“读操作”拆到两个方法或者在更新后手动调用SqlSession.clearCache()。这个方法更精细代码上稍微麻烦一点。对于装修管理系统这种并发量不高的系统直接用flushCachetrue完全可以接受。这个坑说出来懂的人会知道你真的做过项目。6.2 装修现场照片怎么存本地文件还是对象存储装修系统里现场照片是高频数据。一个项目从开工到竣工至少会传几百张照片。如果直接用Base64字符串存到MySQL的LONGTEXT字段里数据库很快就会膨胀到几个GB备份恢复越来越慢。我采用的方案是照片文件存本地磁盘数据库只存文件路径URL。具体做法是使用Nginx配置一个静态资源目录比如/data/upload/project/把它映射为http://your-server.com/upload/。SpringBoot接收上传的MultipartFile后按/项目ID/日期/时间戳_随机数.jpg的格式存储。数据库的project_node_log表里photos字段存储JSON数组格式的路径列表如[/upload/project/12/20240516/1715800000123.jpg]。这个方案成本低、实现简单缺点是文件分散在后端机器本地磁盘上如果要做负载均衡多节点部署就需要把存储切换到对象存储如MinIO或阿里云OSS。考虑到这是一个中小型装修公司内部系统单节点部署本地存储完全够用等用户量真的上来再迁移不迟。用JSON数组存路径列表虽然不符合数据库第三范式但实际使用中查询非常高效——你不需要再建一张子表查询照片列表直接从主记录里解析JSON就好。对于这种“低频更新、整体读写”的数据反范式设计是合理的选择。6.3 权限拦截器的坑放行路径和未登录返回码要全局统一前端和后端联调时最常见的报错就是401/403的响应格式不一致。前端希望所有接口统一的响应结构是{code: 401, message: 未登录, data: null}但如果你在拦截器里直接response.getWriter().write(未登录)前端解析JSON就会直接挂掉。我的做法是封装一个ResultT统一响应体拦截器里检测到未登录时也通过Result.error(401, 未登录)输出成标准JSON并设置Content-Type为application/json;charsetUTF-8。同时要注意响应的编码问题——如果不指定字符集中文会变成乱码前后端排查半天找不到原因。另外拦截器的放行路径要设计好/api/auth/login、/api/auth/captcha、/static/**这些是要放行的/api/**下的所有业务接口全部要走鉴权。如果你用了Swagger做接口文档还需要放行/swagger-ui/**、/v3/api-docs/**这些路径否则接口文档页也会被拦截。7. 如果你在准备面试这套系统的十问十答最后这部分专门写给正在准备Java开发岗位面试的朋友。这套装修管理系统项目面试官大概率会围绕几个方向提问技术选型原因、数据库设计、权限方案、接口幂等性、性能优化。提前把这些问题想清楚比背八股文有用得多。问题1为什么用SpringBoot而不用Spring Cloud回答思路系统是单体架构用户量几十人用微服务是过度设计。SpringBoot足够支撑当前业务后续如果业务增长可以按模块拆分但现阶段单体是最稳妥的选择。问题2MyBatis和MyBatis-Plus怎么选回答思路MyBatis-Plus确实简化了单表CRUD的代码量但这个项目里我的核心查询都是多表关联的动态SQLMyBatis-Plus的LambdaQueryWrapper在这种场景下帮助不大。手写MyBatis XML反而让SQL更直观可控。这个回答能体现你没被工具绑架。问题3项目中遇到最难的问题是什么回答思路可以说状态机的实现如何保证项目状态流转的合法性或者预算变更的事务一致性问题。面试官想听的不是“遇到了什么问题”而是“你是如何分析、定位、解决这个问题的过程”——所以要提前把上面提到的MyBatis缓存脏读的排障过程背熟从现象到根因到解决到反思逻辑链完整。问题4数据权限是怎么做的回答思路RBAC是菜单权限数据权限靠SQL拼接。项目经理登录后项目查询语句会自动附加AND project_manager_id 当前用户ID。用一个DataScope注解加AOP切面来实现Mapper的自定义SQL里写死${dataScopeSql}占位符运行时动态替换。这个方案简单有效。问题5项目前端为什么用Vue 2回答思路项目开发时Vue 3生态还不成熟Element UI只支持Vue 2。同时团队对这个技术栈最熟悉风险可控。再加一句如果现在重写会考虑Vue 3 Vite TypeScript但核心业务逻辑不受框架影响。问题6系统部署方案回答思路一台2核4G的云服务器Nginx部署前端静态文件并反向代理/api/到后端8080端口MySQL与后端部署在同一台机器定时用mysqldump备份后端用systemd守护jar包进程日志用logback按天滚动。成本低、维护简单。问题7数据库有哪些索引回答思路除了主键索引所有外键字段customer_id、house_id、designer_id等都加了普通索引project.project_no是唯一索引project_node表的project_id sort_order是联合索引project_node_log表的node_id加索引。字符串字段如phone如果做模糊查询左匹配普通索引会失效所以查询时尽量用右匹配或全文索引但在这个系统里数据量小影响不大。问题8系统安全性做了什么回答思路登录密码用MD5加盐或BCrypt加密存储JWT设置合理过期时间后端拦截器统一校验登录状态和菜单权限SQL注入通过预编译规避MyBatis的#{}XSS攻击通过前端Vue自带的转义机制和后端入参过滤双保险。这些要说出来面试官能感知到你对安全的意识。问题9前端有没有做权限控制回答思路前端用自定义指令v-permission控制按钮级权限。用户登录后后端返回该用户的权限码列表前端根据权限码是否包含project:create来动态渲染添加按钮。但前端权限只是体验优化真正的安全防线必须落在后端——这个原则要向面试官重点强调。问题10如果要上线你还会做哪些优化回答思路接口加Redis缓存热点数据如首页统计信息加接口限流防止恶意请求日志加操作审计给关键操作预算变更、项目状态修改增加短信提醒功能。数据量大了以后给MySQL开慢查询日志并优化大表索引。这些优化方向既实际又接地气。写在最后的一些实话这个系统前前后后我改了差不多两个月从最初简单的增删改查到后来把装修行业的业务流程真正吃透整个过程的收获远比写几百行代码多得多。做管理系统的难点从来不在技术而在于“你是否真的理解你服务的这群人每天是怎么工作的”——项目经理需要什么信息做决策客户关心哪些节点财务要审核什么数据老板想看到哪些统计报表。想清楚这些代码只是水到渠成的事。如果你打算用这套系统作为毕业设计或者简历项目我的建议是不要停留在照着源码敲一遍挑选一两个模块自己重新设计并实现比如把权限模块升级成Spring Security版本或者给预算模块加上Excel导入导出的功能。这样项目才能写进简历的时候底气十足经得起追问。你投入的时间不会白费把这些业务逻辑真正理解清楚之后再去应对真实的工作项目你会发现很多概念都是相通的。