做装饰工程项目管理的系统很多老牌软件公司都停留在单体架构时代界面老旧、流程僵硬项目经理宁可自己拉Excel表格也不愿意用。而新团队上来就搞微服务分布式对一家中小型装饰公司来说根本就是杀鸡用牛刀维护成本高得吓人。我这次用SpringBootVue这套主流组合配合MySQL和MyBatis完整做了一个装饰工程管理系统从需求梳理到数据库设计再到前后端联调整个过程踩了不少坑也沉淀了不少经验这篇文章就从头到尾掰开揉碎讲一遍。这套系统不是那种只能跑demo的玩具项目而是真正把装饰工程公司的核心业务链路打通了项目立项、合同签订、材料采购、施工进度、验收结算每个环节都有对应管理功能。角色权限也做了细分老板、项目经理、材料员、财务各看各的数据互不干扰。如果你正在学SpringBoot或者Vue想找个真实业务场景练手或者你所在的公司正缺一套轻量级的工程管理工具这篇文章都能给你一个完整可复现的参考。1. 项目定位与核心需求拆解1.1 装饰工程行业的管理痛点在哪做装饰工程管理系统之前得先搞清楚一个事这个行业的痛点和常规的电商、OA系统完全不一样。装饰工程的核心是“项目制 多方协同”一个项目从签合同到最终验收中间涉及业主、项目经理、设计师、施工队、材料供应商、财务至少六方角色。传统管理模式是微信群里报进度、Excel表格管材料、口头汇报定变更。项目少的时候还能靠人肉记忆撑着项目一多就全乱了材料采购和施工进度脱节项目经理不知道库房还有多少瓷砖变更签证没有留痕结算时业主不认账工地上的隐蔽工程验收记录找不到了后期返工互相扯皮。这套管理系统的设计目标就是把这些散落的业务数据收拢到一个流程里。项目立项后自动生成唯一的项目编号合同挂到项目下材料采购单关联到具体施工节点进度汇报按周更新验收记录拍照上传留痕。数据一旦集中了老板看板、项目经理排期、财务对账就都有了依据。1.2 角色权限与业务闭环设计系统设计时我划分了四个角色管理员一般是老板或者副总、项目经理、材料员、财务。管理员拥有全部权限可以创建项目、分配项目经理、查看所有数据报表项目经理负责项目进度汇报、施工任务派发、材料需求申请材料员负责供应商管理、采购单创建和入库确认财务负责合同收款记录和项目成本核算。业务闭环是这套系统能不能真正用起来的关键。我设计的主流程是管理员创建项目并关联合同合同审核通过后自动生成施工计划模板项目经理根据模板创建装修任务每个任务可以关联材料需求单材料员收到需求单后创建采购单采购单入库后库存自动更新财务在项目关键节点录入收款记录最终的毛利报表由系统自动汇总合同金额、采购成本和其他费用。这样的设计避免了“功能一堆但流程走不通”的尴尬。权限隔离不是生硬地藏菜单而是在数据层面做过滤比如项目经理登录后只能看到自己负责的项目材料员只能看到待处理的采购申请改别人数据这种事从接口层面就拦死了。1.3 功能模块清单梳理系统的功能模块按照业务自然划分成六大块项目管理含项目CRUD和状态流转、合同管理含收付款记录、施工任务管理含进度百分比和验收记录、材料管理含供应商、采购单、库存、人员管理含账号分配和角色权限、统计报表含项目毛利和材料消耗汇总。这里要特别说一个容易被新手忽略的点模块划分要跟着业务流程走而不是跟着数据表走。比如“供应商”这个实体它既可以挂在材料管理下也可以单独做成一个模块。我最终把它放在材料管理下面因为供应商的日常操作场景就是采购询价和材料管理天然绑定。强行拆开反而会让用户使用时要频繁切换页面体验很割裂。2. 技术选型与前后端架构设计2.1 SpringBoot Vue这套组合的选型理由选技术栈这事很多人的第一反应是“哪个火选哪个”但实际做项目时匹配业务复杂度才是第一原则。装饰工程管理系统最核心的是业务流程管理不是高并发秒杀也不是海量数据存储所以技术方案要满足三个条件开发效率高、上手门槛低、部署维护简单。SpringBoot解决的是后端开发效率问题。它把Spring生态里那些繁琐的Bean配置全自动装配了内置Tomcat打包成jar直接就能跑。加上Spring Security或者拦截器做鉴权MyBatis做数据访问一套组合下来代码量比传统的SSH架构少一半以上。Vue解决的是前端开发体验问题。它最香的点在于组件化开发和响应式数据绑定。页面上一个底部导航栏、一个状态标签、一个表格组件都是可复用的Vue组件改一处全局生效。表单数据用data绑定之后用户输入和页面状态自动同步不用像JQuery时代那样手动操作DOM。前端用Vue2配Element UI后端用SpringBoot 2.7数据库MySQL 8.0前后端通过RESTful JSON接口交互。这套组合网上的资料极其丰富遇到问题随便一搜就能找到答案对于成长中的团队来说这就是最大的隐形优势。2.2 前后端分离架构带来的效率提升前后端分离这个词喊了好多年但很多人没理解它到底解决什么问题。既然要落地这套系统我就说说我真实的感受。以前写Session JSP那种模式前端页面嵌在后端项目里改个按钮样式要重启服务器。前后端分离后前端项目和后端项目完全独立部署前端开发只需要Mock数据就能把页面写出来后端开发只需要用Postman测试接口。两边并行开发联调时再对准接口文档工期至少缩短三分之一。这套系统里后端只负责提供JSON数据接口前端的路由由Vue Router管理页面跳转不经过服务器。登录鉴权用的是JWTJSON Web Token后端签发token前端存到localStorage里每次请求带在Authorization头里后端过滤器校验token并提取用户信息。这套模式天然适合前后端分离也不存在Session同步的问题。2.3 MyBatis在工程类项目中的独特优势MyBatis在Java圈子里的地位很奇怪一方面有很多人喜欢另一方面也有不少人觉得它太底层、不如JPA好用。但用在工程管理系统这种“业务关联复杂、查询条件灵活”的场景MyBatis反而是最优解。装饰工程项目的数据查询往往不是单表操作。比如“项目列表页”要展示项目名称、负责人、合同金额、累计收款、当前进度这些数据分布在project、contract、project_progress三张表里还要带条件筛选。用MyBatis写SQL可以精确控制每一行查询关联查询、子查询、动态SQL都信手拈来。而用JPA虽然也能实现但遇到复杂统计SQL时要么绕来绕去要么就得写原生的Query注解反而没有MyBatis直接来得痛快。MyBatis的另一个优势是动态SQL。项目列表的筛选条件是动态的可以选择只看“施工中”的项目也可以加上“2024年开工”的时间范围还可以根据项目经理过滤。这种组合查询用MyBatis的if标签拼条件比Java代码里一堆if (xxx ! null) { sql and ... }要优雅太多而且不容易出SQL语法错误。3. 数据库设计与核心表结构3.1 核心数据表设计与关联关系数据库设计是整系统的地基地基歪了后面业务代码写起来全是病。我画表结构花的时间比写代码的时间还多反复调整了三次最终定位的关键表有八张sys_user用户、sys_role角色、project项目、contract合同、project_task施工任务、material材料、purchase_order采购单、purchase_item采购明细。用户和项目之间是多对多关系一个项目经理可以管多个项目一个项目在特殊情况下也可以有多个负责人所以中间加了一层project_manager_rel表。项目表和合同表是一对一关系一个项目只能有一份主合同。合同表单独拆出来的原因是合同字段比较独立包含合同编号、签约日期、合同金额、付款条件放在项目表里会让项目表过于臃肿。施工任务表是连接项目进度和材料消耗的关键节点。任务表里有关联的项目ID、任务名称、计划开始时间、实际开始时间、完成度百分比。材料需求单挂在任务下面采购单又关联材料需求单形成了一条完整的业务链。3.2 关键字段设计与状态机约束有几个字段的设计思路值得单独拎出来说说。项目状态字段我用的不是Integer而是一个字符串类型的枚举值“未开始”“施工中”“已停工”“验收中”“已完工”。字符串枚举在代码里可读性强也不容易出现魔数配合状态校验器来控制状态流转。比如只有“施工中”的项目才能新建施工任务“已完工”的项目不允许再添加采购单这种业务约束写在Service层而不是Controller层避免前端绕过校验直接调接口。金额字段全部使用decimal(10,2)不能图省事用float。工程款的精度问题在结算时会放大float的二进制存储误差在累计多笔后会产生“对不上账”的问题。decimal类型存的是字符串形式的小数精度可控财务人员查账时也安心。所有业务表都加了create_time、update_time、deleted这三个基础字段。deleted字段是逻辑删除的标志因为工程管理系统里的数据有审计追溯需求项目经理误删了一条采购记录如果物理删了后续对账就找不到痕迹了。逻辑删除只标记不删除查询时统一加where deleted 0条件。3.3 索引设计与性能优化数据量还不到写复杂索引优化的程度但基础索引必不可少。外键字段一定要建立索引project_id在任务表、合同表、采购单表里频繁作为查询条件不建索引的话全表扫描是必然的。其次status字段也建了普通索引因为列表页最常用的筛选项就是状态。有个当时踩过的坑分享下在联表查询时MyBatis的resultMap里配置了级联查询导致一条项目详情查询要额外执行三四条SQL。后来我把详情页的SQL改成了JOIN关联查询一条SQL查出所有关联数据耗时直接从200多毫秒降到了30毫秒。对中小型系统来说减少SQL执行条数比技术的微优化来得更实在。4. 后端接口设计与核心业务实现4.1 统一接口规范与异常处理后端接口的规范决定了前端开发效率。我设计了一套统一的响应结构ResponseResult包含三个字段code业务状态码、message提示信息、data业务数据。成功时code为200业务规则校验不通过时code为4001系统异常时code为5000。这套结构的好处是前端Axios拦截器可以集中处理。接口返回200时正常渲染data返回4001时直接弹出message提示返回5000时统一跳转到错误页。不用每个接口单独做错误判断节省大量重复代码。异常处理这里我给个建议不要只依赖全局RestControllerAdvice兜底业务里一定要主动抛自定义业务异常。比如创建采购单时如果材料库存不足直接在Service层抛一个BusinessExceptionmessage写成“瓷砖库存不足剩余xx箱”。这样的提示信息最准确用户在页面上看到就知道怎么处理不会一脸懵地看“系统错误”。4.2 登录鉴权与权限控制实战登录接口的设计流程是用户提交用户名密码后端用BCrypt加密算法校验密码通过后生成JWT tokentoken里封装了用户ID和角色ID有效期设置为2小时。JWT token的生成用到了java-jwt库核心代码大概长这样Algorithm algorithm Algorithm.HMAC256(secretKey); String token JWT.create() .withClaim(userId, user.getId()) .withClaim(roleId, user.getRoleId()) .withExpiresAt(new Date(System.currentTimeMillis() 2 * 60 * 60 * 1000)) .sign(algorithm);token生成后返回给前端前端存储到localStorage并放入Axios请求拦截器。后端用一个拦截器JwtInterceptor统一校验每个请求的token校验失败返回401状态码前端Axios响应拦截器捕获401后跳转到登录页。权限控制这块我采用的是细粒度的接口权限而非简单的角色判断。比如项目经理能调用“创建施工任务”接口材料员不能。我在接口上加了自定义注解RequiresPermission(project:task:add)拦截器里先解析token中的角色ID再到数据库查这个角色的权限列表判断是否包含所需权限。这套方案比硬编码if (roleId 2)的方式可维护性强太多新增角色和调整权限只需要改数据库不用改代码。4.3 项目进度与材料联动模块实现这个模块是整个系统的业务亮点也是技术实现上最值得讲的。施工任务完成后系统要自动判断该项目下所有任务的完成情况如果全部完成则自动将项目状态更新为“验收中”。这个逻辑如果放在前端做用户刷新页面后状态就会错乱所以必须放在后端事务里。实现方式是项目级联任务完成度统计方法事务里先更新任务状态再查询该项目下“未完成”的任务数量如果为0则更新项目状态Transactional(rollbackFor Exception.class) public void completeTask(Long taskId) { ProjectTask task projectTaskMapper.selectById(taskId); task.setStatus(已完成); task.setFinishTime(new Date()); projectTaskMapper.updateById(task); int remainingTasks projectTaskMapper.countUnfinished(task.getProjectId()); if (remainingTasks 0) { Project project projectMapper.selectById(task.getProjectId()); project.setStatus(验收中); projectMapper.updateById(project); } }材料联动的场景是项目经理创建任务时填写材料需求单材料员根据需求单采购采购入库后系统自动扣减库存。库存逻辑需要注意一个并发问题两个材料员同时处理同一份采购单会导致库存重复扣减。解决办法是在采购单上加上版本号字段version更新时执行update stock set version version 1 where id ? and version ?影响行数为0就说明数据被其他人改过需要重试或报错。5. 前端Vue实现与交互细节打磨5.1 前端工程结构与核心配置前端工程用的是Vue CLI脚手架创建的标准结构目录划分比较常规api目录统一放接口请求定义router目录配置路由表views目录放页面组件components目录放公共组件utils目录放Axios实例和公共工具函数。Axios实例的封装是整个前端工程的基础核心配置在api/request.js里。除了配置基础URL和超时时间关键是请求拦截器和响应拦截器的编写。请求拦截器负责从localStorage获取token并加到Authorization头响应拦截器负责判断HTTP状态码和业务code401跳转登录页业务错误弹Message提示。service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] token } return config })路由守卫用Vue Router的beforeEach钩子实现判断进入的目标路由是否需要登录需要的话检查是否有token没有就重定向到登录页并且带上redirect参数登录成功后自动跳回原页面。router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.meta.requiresAuth !token) { next({ path: /login, query: { redirect: to.fullPath } }) } else { next() } })5.2 Element UI组件与业务页面实现思路页面整体用的是Element UI的布局组件侧边栏菜单根据用户角色动态生成。管理员和项目经理看到的菜单不完全一样材料员登录后菜单直接少了几项。这里用了Element UI的el-menu和Vue Router的路由meta关联来实现。项目列表页是核心页面参考了常见的管理后台风格顶部筛选区、中间表格区、底部操作区。筛选区放了项目状态下拉框、时间选择器、关键词输入框和搜索按钮。表格列包括项目名称、项目地点、负责人、合同金额、状态标签、创建时间、操作按钮。状态标签用el-tag根据枚举值显示不同颜色让用户一眼能看出项目处于哪个阶段。表单页的设计需要注意细节。创建项目时合同金额输入框要做千分位格式化用户输入数字后失焦时自动带上逗号分隔。编辑项目时负责人下拉框的数据要实时从后端拉取用户列表。提交表单前要做前端基础校验必填项、金额格式、日期范围校验通过后才发起请求。5.3 状态流转与页面的联动交互状态流转在项目列表页和任务详情页都能触发。项目经理在自己的任务列表里点击“完成”按钮弹窗确认后调用后端接口。接口返回成功后前端要刷新项目详情页的任务进度条。进度条我用了Element UI的el-progress组件百分比通过计算任务完成数占总任务数的比例得到。细节上有个体验问题值得注意新增任务表单完成后前端要手动把新任务数据追加进当前页面列表而不是reload整个路由刷新页面。reload会丢失当前筛选条件和页码用户还得重新设置一遍体验很差。正确做法是调用列表接口重新拉少量数据或者本地push进数组后重新计算进度条。交互提示上遇到逻辑删除的情况时要给用户明确的按钮。删除项目时确认弹窗文案写明“项目删除后将无法恢复”避免误操作。因为逻辑删除后列表页看不到但数据仍在数据库里如果用户想恢复还要走后台操作前端体验要尽量把这个流程想清楚。6. 环境搭建、部署与常见问题排查6.1 本地开发环境和启动步骤把代码从仓库clone下来之后第一步不是直接启动后端而是先准备环境和配置文件。后端是Spring Boot标准的Maven工程需要JDK 1.8、Maven 3.6、MySQL 5.7。配置文件里修改数据库连接账号密码初始化数据库的SQL脚本执行后项目里还配置了首次启动自动建表的选项spring.jpa.hibernate.ddl-autoupdate但从正规项目的角度讲我还是建议用SQL脚本迁移避免自动建表导致的字段遗漏。前端是标准的Node工程需要Node.js 14和npm。依赖安装时建议用npm install初始化如果依赖拉不下来就检查registry源切换成国内镜像源后一般都能解决。开发模式下前端启动在8080端口后端启动在8081端口两者之间通过后端的cors配置允许跨域访问。生产部署时再前端的Vite base路径配置和后端Spring Boot的静态资源映射然后统一放到一个Web服务下。下面给一个启动步骤清单新建MySQL数据库编码utf8mb4执行项目根目录下的init.sql脚本后端配置application.yml填入数据库账号密码通过Maven打包成jar后端执行java -jar decoration-system.jar启动确认8081端口监听前端配置.env文件设置VUE_APP_BASE_URLhttp://localhost:8081/api前端执行npm run serve启动浏览器访问8080端口6.2 常见问题排查与解决实录开发过程中遇到的最典型问题是跨域前端访问后端接口报错blocked by CORS policy。排查思路是先确认是跨域问题还是接口404问题浏览器Network面板看请求是否发出去了。跨域解决方案用了CrossOrigin注解加在Controller类上但这只解决了开发环境的问题生产部署时还是要把两个服务部署到同一域下用Nginx做前端静态资源的反向代理并转发API请求。第二个常见问题是数据库连接失败。后端启动时报Access denied for user rootlocalhost几乎可以肯定是application.yml里的密码不对或者权限不对。排查时用命令行先试一下mysql -uroot -p能不能连上再核对配置文件。第三个常见问题是数据库初始化脚本的版本不一致多张表没有建立外键关系导致联表查询时结果为空。所以初始化顺序要明确先建主表用户、项目、合同再建关联表任务、采购单最后插入初始化数据外键级联删除要从子表开始删。第四个高频问题是前端接口请求报401。这里有个坑用户登录成功拿到token后发请求时config.headers[Authorization]没有带上或者baseURL配置错误。排查时先在浏览器控制台打印token是否存入了localStorage再看请求头有没有带上正确字段。如果你在实际开发中也遇到过接口返回正常但页面渲染数据为空的情况那大概率是前端字段名和后端字段名不一致。比如后端的projectName字段前端写成project_nameJSON返回后数据匹配不上。这种情况没有捷径就是多看接口文档多对齐字段名后端接口写好后可以先导出Swagger文档前端照着字段名做渲染就稳了。6.3 系统上线前要注意的几个细节上线前还有一个特别容易被忽略的点默认账号的密码太简单。系统初始化时管理员账号密码是admin/123456如果上线不修改等于给安全留下了隐患。我在系统里加了首次登录强制改密的逻辑管理员用自己的账号密码登录后系统检查到密码强度不满足要求会强制跳转到修改密码页面改完才能继续操作。生产环境的数据库连接串要配置连接池参数。Spring Boot默认的HikariCP池是够用的但数据库连接超时和最大连接数要调到合理的值。我在配置里将maximum-pool-size设为20connection-timeout设为30000ms这样能保证多人同时使用系统时不会因为连接数不够导致卡顿。部署时万一遇到端口占用可以先查一下端口是否被其他程序占用再杀掉进程重新启动不需要急着改代码。系统上线后最好配置一个监控页面监听系统健康状态出问题能第一时间发现问题这也是工程管理系统在运维上比普通项目更重要的环节。7. 从开发到落地的个人项目体会7.1 从开发到落地的几个关键认知这套装饰工程管理系统从需求调研到正式投入使用前后花了大概一个半月。回头复盘最深的体会是一个系统能不能用起来技术只占三分业务逻辑梳理和操作体验占七分。最开始交付第一版时项目经理反馈操作流程和线下习惯对不上材料员觉得每次开采购单要填写太多字段。于是产品细节做了调整采购单增加复制上一条功能、项目列表增加导出Excel、进度汇报支持手机端拍照上传。这些改动看着不起眼但恰恰是这些不起眼的小功能让用户愿意天天用。技术层面来说我是建议把SpringBootVueMySQLMyBatis这套技术栈吃透再考虑其他框架的。装饰工程管理系统这个业务正好卡在“单块应用足够搞定、微服务反而带来负担”的甜蜜点上用这套技术做表单密集、状态流转多、权限要求细的管理系统效率真的拉满。MyBatis让你能精确控制每一条SQL行为Vue让你能把交互细节做得细腻SpringBoot让后端开发像搭积木一样顺畅。这个系统后续如果想继续迭代还可以扩展的方向不少比如接入工作流引擎让审批流程更灵活增加项目维度的成本核算和利润分析报表抑或是加一个大屏看板把老板关心的月度产值、回款率、材料消耗趋势可视化展示。核心就是把数据沉淀好后续扩展就很容易接上。如果你也在做类似的管理系统我的建议是先把核心业务流程捋清楚再动手写代码业务梳理清楚了技术选型和代码结构都水到渠成。遇到问题也不要慌这套组合在互联网上已经被问烂了搜一搜肯定有答案。最后分享一个我在写这套系统时的体会代码不是越花哨越好能跑上生产环境的代码比任何炫技的实现都值钱。