
做了几年企业级管理系统手里攒了不少能直接跑的项目。这次分享的是一个Spring Boot装饰工程管理系统技术栈是SpringBoot后端Vue前端MySQL整套源码拿来就能跑。做装饰公司运营或者接私活的朋友应该能直接用上这套东西里面把工程管理从立项、预算、施工到验收结算的完整链路都串起来了不用再从零去凑功能模块。1. 项目背景与整体设计思路1.1 装饰工程行业的管理痛点在哪装饰工程这个行业跟普通制造业不太一样项目分散、周期短则一两周长则三四个月过程中涉及的角色特别多业主、项目经理、设计师、施工班组、材料供应商、财务。我接触过的多家装饰公司管理方式基本停留在Excel加微信群里发照片的阶段最直接的后果就是数据对不上、进度靠问、成本靠猜。举个例子一个项目进场后材料采购谁负责、什么时候到货、谁来验收这些流转信息如果不集中管理出了问题就只能翻聊天记录。还有预算超支的问题装修增项变更很频繁口头说一句就干了结算的时候没有单据依据利润被吃掉一大块都发现不了。这套系统就是把这些问题用数字化方式收敛起来让每个环节都有记录、可追溯、能统计。1.2 为什么选Spring Boot Vue MySQL这套组合这套技术栈现在是中小型管理系统最成熟的搭配没有之一。后端Spring Boot负责接口和业务逻辑前端Vue负责页面交互MySQL存业务数据。三个东西各自生态成熟招人容易出问题网上一搜全是解决方案。具体到版本选择我用的Spring Boot 2.6.x、Vue 2.6.xElement UI、MySQL 5.7。为什么不上Spring Boot 3和Vue 3不是追新不好而是这套系统要保证稳定性和兼容性。Spring Boot 3要求JDK 17很多公司环境还在用JDK 8Vue 3配套的组件库虽然也成熟了但Element UI的生态在Vue 2里更稳。如果你是个人学习或内部使用2.x版本的资料最多、坑最少跑起来最省心。当然如果你想上3.x代码迁移成本也不高后续可以在扩展里单独说。1.3 前后端分离架构的核心逻辑项目采用标准前后端分离模式前端单独起一个Vue服务默认8080端口通过Axios请求后端接口默认8081端口后端连接MySQL数据库。前端负责渲染和交互后端只暴露JSON格式的RESTful API互不干扰。这个架构最大的好处是部署灵活。开发阶段两个服务分开跑后端端口冲突不影响前端调试上线之后又可以把前端打包成静态文件扔到Nginx里跟后端做反向代理形成一个对外统一入口。系统整体链路我梳理一下Vue前端页面Element UI组件 ↓ Axios请求携带JWT Token Spring Boot后端接口Controller层 ↓ MyBatis-Plus操作 MySQL数据库InnoDB引擎每个请求都会经过JWT校验才能访问受保护的接口未登录状态只能访问登录接口。这个设计保证了系统的安全性也方便后续接入多端比如未来要出小程序或App后端接口完全可以复用。2. 核心功能模块拆解2.1 项目管理模块从立项到竣工的状态流转项目管理是整套系统的地基所有数据最终都挂在项目下面。我设计了一张project_info表字段涵盖了项目编号、项目名称、客户姓名、联系电话、所属区域、项目经理、项目状态、预算金额、开工日期、计划竣工日期、实际竣工日期、项目备注等十几个核心字段。项目状态我把整个生命周期拆成了六个节点待开工、施工中、已停工、待验收、已验收、已竣工。每一个状态变更都要求在页面上操作不能直接改数据库这样能保证操作留痕以后追责有依据。实际开发中状态流转直接影响后面要说的进度款节点所以状态字段的枚举定义一定要前后端统一我采用的方法是后端定义一个枚举类前端做字典表映射两边用同一个value值。2.2 预算与合同管理把增项变更管住做装修管理如果不管预算和合同就是空中楼阁。系统中预算管理这块我拆成了三级结构项目总预算对应合同金额下面挂预算明细项比如水电改造、墙地面工程、木作工程每个明细项可以绑定多个变更记录。变更单是装饰行业特有的东西客户今天加个插座、明天换个瓷砖都要走变更流程。系统里我设计了change_order表记录变更内容、变更金额、发起人、审批状态审批通过后自动累加到合同总金额上。合同模块我做了合同台账和收款记录两个部分收款节点跟项目状态关联开工付30%水电验收付35%竣工验收付30%尾款5%验收合格后一个月内结清。每个收款节点到了系统会在待办事项里给财务人员推提醒。2.3 材料与供应商管理进出库有据可查材料管理这块我一开始没想做太复杂后来跟几个项目经理聊完发现材料损耗和供应商结算是最容易扯皮的地方。所以系统里做了一个基础的材料档案库包含了材料名称、规格型号、单位、参考单价、常用品牌、供应商信息。采购出库的时候从档案库里选材料录入数量、单价、采购日期、经办人自动计算采购金额并关联到对应项目。供应商模块独立出一张表记录供应商名称、联系人、电话、供货类别、合作状态、结算方式。每个供应商可以关联多条采购记录月底对账的时候直接按供应商维度汇总不用再一张张翻单据。另外我加了一个采购预警逻辑当某项目材料采购金额超过项目预算的80%时系统给项目经理和管理员同时发消息提醒防止材料费失控。2.4 施工进度与质量验收现场管理数字化进度管理用WBS任务拆解的思路把一个装饰项目拆成几个大的工程阶段拆除改造、水电工程、泥瓦工程、木作工程、油漆工程、安装工程、竣工验收。每个阶段可以派给具体施工班组填写计划开始时间和计划结束时间实际施工过程中由项目经理扫码或者拍照上传进度照片后台记录实际完成时间。进度模块的精髓在“对比”这两个字上。系统自动用甘特图的样式把计划时间和实际时间放在一起对照展示哪个阶段延期了一眼就看出来。这里我在前端用了ECharts的自定义图实现效果是横向条形图数据库不用单独建表前端根据stage_start_date和stage_end_date字段动态计算性能上完全没问题。质量验收模块则包含了隐蔽工程验收、分项验收和竣工验收三个级别验收单需要填写结论、备注并由验收人和项目经理双人签字确认。2.5 数据统计看板管理层要的一页纸报表管理看板是给老板和总经理看的所以我单独做了一个仪表盘页面集合了四块核心指标在建项目数量、本月合同签约金额、本月回款金额、超预算项目清单。每块指标下面都有趋势图和Top10排名比如签约金额按月份的趋势、回款速率的行业对比、项目毛利排行等。这套统计报表全部用SQL聚合查询实现配合后端定时缓存查询效率还不错。前端用了ECharts的折线图、柱状图和饼图数据量在几千个项目以内完全不需要引入OLAP引擎。如果后面数据量大了再考虑对报表查询做读写分离不影响主体业务。2.6 权限体系多角色数据隔离权限这块我用了经典的RBAC模型基于角色的访问控制设计了用户表、角色表、菜单表、用户角色关联表、角色菜单关联表这五张核心表。角色分了四档系统管理员、老板/总经理、项目经理、财务人员。这里有个细节很多人容易忽略同一个项目经理如果同时管多个项目他登录系统后默认只能看到自己负责的项目数据。但老板角色需要跨项目查看所有数据所以我在数据权限层面做了一层过滤MyBatis-Plus的拦截器里自动拼上project_manager_id 当前用户ID的条件不同角色看到的项目列表完全不同。这一步不做的话项目数据横向泄露的风险很高尤其多项目经理协作的场景。3. 数据库设计与关键表结构3.1 核心表设计说明装饰工程管理系统的数据库一共十几张表我按业务域拆成四组基础数据、项目执行、财务管理、系统权限。核心表结构我列一下表名业务域核心字段说明sys_user系统权限id, username, password, real_name, role_id, status用户账号信息密码用BCrypt加密存储sys_role系统权限id, role_name, role_code, remark角色定义project_info项目执行id, project_no, project_name, customer_name, manager_id, status, budget_amount, start_date, end_date项目主表所有业务都挂在这个表下面budget_item财务管理id, project_id, item_name, budget_amount, actual_amount, change_amount预算明细项change_order财务管理id, project_id, change_type, content, amount, status, create_user变更单记录contract_info财务管理id, project_id, contract_amount, sign_date, payment_node合同签约信息payment_record财务管理id, contract_id, node_name, amount, pay_date, status收款节点记录material_purchase项目执行id, project_id, material_name, spec, quantity, unit_price, supplier_id, purchase_date材料采购记录supplier_info基础数据id, supplier_name, contact, phone, business_scope供应商档案progress_stage项目执行id, project_id, stage_name, plan_start, plan_end, actual_start, actual_end, status施工阶段进度acceptance_record项目执行id, project_id, accept_type, accept_date, conclusion, inspector, manager验收记录material_info基础数据id, material_name, material_spec, unit, reference_price材料档案库3.2 主外键关联设计说明表之间的关联我用了逻辑外键而非数据库物理外键。就是说在Java实体类里定义关联关系但不真正在MySQL里建FOREIGN KEY约束。这样做的原因主要有两个一是物理外键在删除或者更新时容易产生锁竞争影响写入性能二是项目上线后如果需要做数据清洗或分库外键约束会很碍事。逻辑外键把关联关系放到业务层控制灵活度更高代价是需要开发者在代码里保证引用的完整性。不过这里有个注意事项逻辑外键模式下删除操作必须手动检查引用关系。比如要删除一个供应商得先查material_purchase表里有没有关联采购记录有的话提示“该供应商存在采购记录无法删除”或者做逻辑删除用一个deleted字段标记。我两种方式都做了有业务单据的数据禁止物理删除只能标记作废无业务引用的主数据允许物理删除。3.3 时间字段与状态字段的规范时间字段我统一用datetime类型存储格式为yyyy-MM-dd HH:mm:ss。这里要注意时区问题MySQL 5.7默认时区是系统时区如果服务器用的UTC而代码用了东八区时间就会出现8小时偏差。我在application.yml里强制指定了数据库连接参数serverTimezoneAsia/Shanghai同时JVM启动参数也加上-Duser.timezoneGMT8双保险确保时间显示一致。状态字段我用tinyint类型存储0、1、2这样的数字不直接存中文。查询的时候通过字典映射转换成对应的中文文案。这样做的好处是数据库存储空间小、查询快而且状态扩展方便——新增一个状态不用改数据库只改枚举和字典就行。但是坑也在这前后端状态码必须严格对齐如果前端写死了1代表“进行中”后端改了枚举顺序整个页面显示就全乱了。所以我统一把枚举定义在后端前端通过接口拉取字典数据动态渲染避免硬编码。4. 系统运行与部署实操4.1 环境准备清单这套系统要跑起来首先准备好以下环境JDK 1.8及以上推荐8u202版本稳定且兼容性好Maven 3.6.x后端依赖管理Node.js 14.x或16.x前端构建工具Vue 2要求Node版本不能太新18以上容易出兼容问题MySQL 5.7或8.0我用的是5.78.0只要改驱动名和URL参数也能跑IDE推荐IntelliJ IDEA社区版够用加VS Code前端开发数据库可视化工具Navicat或SQLyog环境搭建顺序建议先装JDK和MySQL再装Node和Maven因为后面启动服务依赖前面的基础环境。MySQL安装好之后记得把root密码改掉创建专用的业务账号不要用root直连生产库——这个习惯最好从开发阶段就养成。4.2 数据库初始化步骤第一步是建库。在MySQL里执行一条建库语句CREATE DATABASE decoration_manage DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;数据库编码必须用utf8mb4不要用utf8。utf8mb4能存emoji表情和生僻字而utf8只能存3字节字符遇到特殊符号就会报错。这个坑我踩过客户在备注里粘贴了一个从微信复制过来的特殊字符直接导致插入失败排查了半天。第二步是导入表结构和初始化数据。项目里带了一个sql文件夹里面有两个文件schema.sql负责建表data.sql负责初始化管理员账号和基础字典数据。执行方式可以用Navicat右键运行SQL文件也可以命令行执行mysql -u root -p decoration_manage schema.sql mysql -u root -p decoration_manage data.sql初始化数据里默认有一个管理员账号用户名admin密码admin123BCrypt加密存储。登录系统后第一件事就是改密码这是基本的安全素养。4.3 后端启动步骤与配置文件修改后端项目导入IDEA后等待Maven下载依赖。首次导入可能耗时几分钟建议配置阿里云镜像加速。打开src/main/resources/application.yml重点修改以下几处server: port: 8081 spring: datasource: url: jdbc:mysql://localhost:3306/decoration_manage?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 你的数据库密码 driver-class-name: com.mysql.cj.jdbc.Driver数据库密码改成你自己的。driver-class-name这里我用的是MySQL 8.x的驱动但我连的是5.7数据库这没问题因为MySQL 8的驱动向下兼容5.7。如果你用的是老版本驱动com.mysql.jdbc.Driver也能跑不过建议还是用新驱动。配置改完后找到启动类DecorateApplication.java右键运行。等控制台打出“Started DecorateApplication in x.xxx seconds”就说明后端启动成功了。这时候打开浏览器访问http://localhost:8081/swagger-ui.html能看到接口文档页面说明后端接口层完全正常。4.4 前端启动步骤与开发环境配置前端工程在vue-frontend目录下用VS Code打开。首先安装依赖npm install国内网络环境下npm install大概率会卡在某个包上下载不动解决办法是配置淘宝镜像npm config set registry https://registry.npmmirror.com设置完再执行npm install速度能快好几倍。依赖装完后查看vue.config.js文件里面配置了开发代理devServer: { port: 8080, proxy: { /api: { target: http://localhost:8081, changeOrigin: true, pathRewrite: { ^/api: } } } }这个配置的含义是前端页面发起/api/login请求时开发服务器会把请求转发到http://localhost:8081/login同时把/api前缀去掉。这样前端代码里所有请求路径都写/api/xxx的形式和后端接口路径解耦上线时通过Nginx的location规则再映射一次。启动前端npm run serve浏览器访问http://localhost:8080输入初始化账号密码能正常登录并看到项目列表就说明整套系统跑通了。4.5 生产环境部署的两种形态开发环境跑通只是第一步真正交付给客户用还得考虑部署。我总结了两种常见形态第一种是单机部署适合小型装饰公司。前端执行npm run build生成dist静态文件用Nginx托管同时Nginx配置反向代理转发/api请求到Spring Boot服务。Nginx配置核心片段server { listen 80; server_name your-domain.com; location / { root /opt/decoration/dist; index index.html; try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8081/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }注意try_files这个指令是SPA应用必需的。因为Vue是前端路由刷新页面时如果直接请求一个深层路径比如/project/detail/12Nginx需要在静态目录里找这个文件找不到就返回index.html再交给Vue路由去解析。第二种是前后端分离托管适合有云服务器预算的公司。后端打成jar包用systemd守护运行前端静态文件放到OSS或CDN上MySQL用云数据库RDS备份和监控交给云厂商处理。这种模式虽然多了几十块钱的云服务费用但省去了自己维护数据库和磁盘的麻烦对不懂运维的小公司来说更划算。5. 常见问题与排查技巧5.1 数据库连接失败与中文乱码最常见的启动失败原因就是数据库连不上报错信息一般是Access denied for user或者Communications link failure。遇见这种问题先分三步排查一查MySQL服务有没有启动二查账号密码有没有写错三查IP和端口能不能通。本地开发一般用ping localhost测试MySQL进程云服务器上要确认安全组放行了3306端口。中文乱码问题也要特别注意。系统的前端、后端、数据库三层都要统一字符集。前端HTML里设置meta charsetutf-8后端接口响应头加Content-Type: application/json;charsetUTF-8数据库连接URL里带characterEncodingutf8同时表结构用utf8mb4。三层任何一层没对齐就可能出现页面问号或者接口返回乱码。有次排查了半小时最后发现是数据库表的字符集在初始化的时候被建成了latin1。5.2 前端跨域请求报错开发环境下最常见的前端报错是CORS。浏览器控制台提示“Access to XMLHttpRequest at ‘http://localhost:8081/api/login’ from origin ‘http://localhost:8080’ has been blocked by CORS policy”。虽然前端配置了代理但如果请求路径写错了——比如没走/api开头直接请求了后端地址——代理就不生效浏览器就会跨域。排查这个问题的顺序先看页面上发出的请求URL确认有没有/api前缀再看Network面板里请求的完整地址如果变成http://localhost:8081/api/login而不是http://localhost:8080/api/login说明代理没拦截到。另外后端我统一加了跨域配置类允许来自8080端口的请求双保险之下正常不会触发跨域。这里有个建议跨域问题最终要在上线时消灭Nginx同源部署后就不存在跨域了所以本地开发时的代理配置要跟生产配置一致。5.3 接口返回401或Token过期JWT认证模式下登录成功后会拿到一个Token前端存储在localStorage里每次请求通过拦截器把Token加到请求头Authorization字段。如果登录后操作了一会儿突然返回401多半是Token过期。系统中我把Token有效期默认设为了24小时过期后前端拦截器检测到401会跳转回登录页并清除本地缓存的用户信息。这个机制中用户“被踢下线”体验不太好。我在用户表里加了last_active_time字段做了一个滑动过期逻辑用户只要每两小时内有操作就自动续期超过两小时没操作才要求重新登录。这个方案对办公场景比较友好员工中午去吃饭回来不会被强制下线。5.4 npm安装依赖卡住或构建失败前端依赖安装是整个系统最容易出问题的环节。除了前面说的配镜像源还有个常见坑是Node版本过高。Vue 2项目用的webpack 4对Node 17以上版本有兼容问题会报ERR_OSSL_EVP_UNSUPPORTED错误。解决方法有几种降Node到16.x版本或者升级webpack或者设置环境变量NODE_OPTIONS--openssl-legacy-provider。我个人建议直接用nvm切换Node到16.x最省心。构建时如果内存不足会报JavaScript heap out of memory可以在执行命令时加参数npm run build --max_old_space_size40965.5 表格分页和条件查询的边界情况系统里项目列表、采购记录等模块都做了分页查询。用MyBatis-Plus的Page对象配合条件构造器看起来简单但有个边界情况要特别注意前端传过来的页码和每页条数一定要做校验否则用户手动改一下URL参数把pageNum改成10000后端就会查出全表数据或者返回空页极端情况下拖垮数据库。我的做法是在Controller层统一封装分页参数类对pageNum做了最小值1的限制对pageSize做了最大500的限制。这个经验是从一次真实事故里得来的客户那边有人写了个爬虫循环遍历所有页码直接把数据库连接池打满了服务彻底假死。后来加上限流和参数校验才解决。6. 代码结构优化与后续扩展方向6.1 分层结构解析这套系统后端代码采用标准三层架构Controller层负责接口暴露、Service层处理业务逻辑、Mapper层负责数据库操作。另外我加了DTO和VO的转换层避免数据库实体类直接暴露给前端。比如用户表的密码字段绝对不能出现在接口返回里通过VO把密码剔除后再响应。这一点建议所有做管理系统的人都严格遵守。实体类直接序列化出去轻则泄露字段重则如果有人通过接口爆破找到添加用户的接口直接就给系统塞一个管理员账号等于脱了裤子防守。DTO/VO转换层看着多个类写起来麻烦但安全性提升是实打实的。6.2 可以扩展的方向这套系统后续可以扩展的功能模块还很多。第一个是消息提醒目前是靠站内待办可以对接企业微信或钉钉机器人项目经理一旦有新的验收任务要处理直接推消息到手机。第二个是移动端适配虽然Web页面做了响应式但项目经理在工地现场更多用手机做一个微信小程序版本或者H5版本会实用得多。第三个是成本核算深化把人工费、机械费、材料费三本账拆细结合结算数据形成项目利润分析报表。数据库层面如果要支持多公司多租户在核心业务表上增加tenant_id字段在MyBatis拦截器里做数据隔离改造难度也不高。对于想把这套系统二次开发接单的朋友这几个扩展点都能用来跟客户沟通展示增量价值。6.3 代码维护的实操建议分享几个我维护这套系统积累的小技巧。第一数据库变更必须同步更新文档我在项目里维护了一个CHANGELOG.md每次有表结构改动就记录一段时间后回看帮助巨大。第二接口返回对象统一使用统一的ResultT包装类包含code、message、data三个字段前端根据code判断业务状态而不是根据HTTP状态码这样业务错误和系统错误可以区分处理。第三核心业务操作要记录操作日志谁在什么时候改了什么项目的主要字段以后审计能查得一清二楚。最后代码里的魔法值一定要用常量类或者枚举替代比如项目状态那一串数字如果散落在代码各处后面维护的人读代码会非常痛苦。我把这套系统在我本地从零到跑通的完整流程跑了不止二十遍每一步都记录在案按照上面写的步骤操作基本不会卡壳。有一次给一个完全没接触过Spring Boot的同事做演示我提供了一份环境准备文档让他按顺序操作半小时后他能自己启动前端看到登录页。所以别担心手生照着流程来就行遇到问题先看控制台日志日志是最诚实的。这套系统在我实际交付的项目里已经跑了快两年中途因为客户需求扩展过几次功能整体框架一直没有大改。当初选型时没跟着潮流上微服务、上容器化现在看来是对的——团队几个人维护一套单体系统反而效率最高开发和部署都省心。技术选型这东西从来不是越新越好适配团队现状和业务场景才是王道。