ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Java工程管理系统从0到1:模块设计、权限模型与Spring Boot实战

Java工程管理系统从0到1:模块设计、权限模型与Spring Boot实战 前阵子一个做项目施工的朋友找我说公司想上一套内部工程管理系统市面上产品看了一圈不是太贵就是接口封闭想让我用 Java 帮他们搭一套。这类诉求我遇到过太多次了。工程管理系统这个名词听起来很垂直但真正接触过的人都知道它比普通 OA 或电商后台复杂得多——它管的不是简单的审批流而是围着项目台账、合同、成本、物资、进度、质量安全转的一整条业务链。这篇内容我把这些年做 Java 工程管理系统的经验做一个完整梳理覆盖功能设计、技术选型、权限模型、数据权限、核心表结构、可落地代码和上线后踩过的坑。不管你是准备自研一套工程管理系统还是刚拿到一份工程管理系统源码准备二次开发这篇文章都值得你从头看到尾。1. 先搞清楚工程管理系统到底在管什么1.1 一部系统背后的真实用户画像很多人一上来就谈架构、谈微服务但工程管理系统这种项目第一步根本不该是选技术而是先明确系统是给谁用的。以我接触过的实际场景为例工程管理系统的核心用户不是外部业主而是施工企业的项目部成员和公司管理层。项目经理要的是进度可控、合同台账清晰能随时知道项目当前是赚钱还是亏钱成本员关心目标成本与实际成本的对比材料、分包、设备费用有没有超标质检员和安全员需要的是巡检任务能下发、隐患整改能闭环、复查记录可追踪材料员要的是采购计划、入库出库、库存盘点不能出现现场急用但库里没货系统里却显示一堆库存的情况公司层面的领导不看细节只看几张关键报表项目回款、成本归集、逾期预警。这决定了工程管理系统本质上是一个以项目为主线、以成本控制为核心、以合同和物资为抓手的经营管理系统而不是一个单纯的办公流程工具。1.2 业务主线决定了系统的信息骨架把工程管理的业务周期拆开看信息流是连贯的投标/立项阶段项目信息登记、立项审批、组建项目部开工准备签订收入合同业主合同编制目标成本、项目预算分解、WBS分解、排进度计划施工过程材料采购入库、领用出库归集到具体分部分项劳务分包队伍进场、考勤、计价机械租赁结算过程中发生设计变更或签证收支管理按合同节点申请进度款、登记收款对下游分包/供应商做结算和付款计划完工阶段竣工验收、成本归集完成、结算审批、资料归档。这条主线的信息骨架决定了数据库设计时几乎所有业务表都要有 project_id几乎所有金额都要在合同和成本模块产生联动几乎所有审批流都围绕变更、结算、付款、整改展开。如果系统设计时没有沿着这条主线走而是把每个模块做成信息孤岛后期做成本归集和经营分析时一定痛苦这也是很多工程管理系统最后沦为台账登记工具的根本原因。2. 功能设计模块划分与数据怎么串起来2.1 核心模块清单少一个能跑多一个可能拖垮项目一个标准的 Java 工程管理系统功能模块大致可以分成这么几块模块核心职责数据关键点项目管理项目台账、立项审批、项目成员project 主表所有业务表的根计划进度WBS分解、进度计划、实际进度填报wbs_node 树形结构与成本科目对应合同管理收入/支出合同、结算、变更、收付款合同主表 结算/支付子表成本管理目标成本、实际成本归集、三算对比成本科目体系与物资/劳务/分包联动物资管理需用计划、采购、入库、领用、库存材料和出入库流水领用出库最终进成本设备管理设备台账、租赁、维修项目租用设备费用按月归集分包劳务分包队伍台账、合同、计价、考勤工资劳务费用是成本大头容易漏质量巡检检查计划、问题整改、复查闭环整改状态机超时预警安全巡检隐患登记、整改、复查、重大风险源隐患等级分级逾期自动升级资料管理过程资料、图纸、竣工资料归档文件关联项目和分部分项报表中心项目总览、成本分析、收付款统计多数报表来自聚合 SQL 或定时任务值得强调的是这些模块不是一次全部做完的。项目管理、合同、物资、成本四个模块先跑通质量安全、设备、资料可以二期再做。排期太紧的时候把一个模块做扎实比所有模块做成半成品重要得多。2.2 项目-合同-成本-物资这条主线怎么串联拿一次真实的钢筋采购业务举例某项目需要钢筋材料员在系统里做需用计划审批通过后生成采购订单供应商送货后做入库单项目部领用时做出库单出库单上指定用到哪个 WBS 节点。到月底系统根据出库单的物资金额、分包单位完成的产值自动归集到该项目的实际成本中与目标成本做对比。这个流程里最关键的设计是出库单必须关联 WBS 节点。如果不关联物资数据就只能看到项目整体耗了多少钢却看不到主体结构这个分项花了多少钢成本归集就失去了意义。合同和成本的打通也是同样道理。收入合同的产值确认和收款节点关联到项目的回款台账支出合同的结算单关联到项目应付账款材料出库金额、分包计价金额、设备租赁金额共同构成实际成本。只有源头关联做好了成本分析报表才能直接通过 SQL 聚合出来而不是靠人工导 Excel。2.3 那些看起来必要但建议二期再做的功能我见过不少团队第一期就雄心勃勃要把系统做成包罗万象的平台结果交付日期一拖再拖。有几种功能我建议克制放到二期或三期再做。复杂的可配置审批流引擎Activiti 或 Flowable 功能强大但工程管理系统里绝大多数审批是固定层级施工员 - 项目经理 - 公司部门用一张流程配置表配合硬编码角色完全能撑住。前期直接上工作流引擎光流程维护和表单设计器就能耗掉大量排期。自定义表单和自定义字段让用户随便加字段听起来很灵活实际用起来没人愿意配置还会让统计报表变得很困难。前期固定核心字段后期真的需要扩展再加。与 BIM 的深度集成工程管理系统不需要替代 BIM 软件把文档或模型链接挂在项目资料里即可。真要对接 IFC 数据解析那是另一个专业方向。自动排产调度、AI 成本预测之类的功能一期先别碰等数据积累到一定量级再说。3. 架构选型Java 技术栈与单体/微服务的现实取舍3.1 为什么我默认推荐 Spring Boot 而不是 SSM 或 Spring Cloud如果是新写的工程管理系统我的默认组合是 Spring Boot MyBatis-Plus MySQL Redis Vue 3。先别急着上微服务。先说说为什么不用 SSM。SSM 组合并不算错很多老系统就是这么跑了几年的但 SSM 的 XML 配置实在太繁琐整合 Spring MVC 和 MyBatis 的过程中光是事务配置和数据源配置就能让新人调一两天。Spring Boot 已经把大部分约定打包好了内嵌 Tomcat、自动配置、健康检查都开箱即用开发效率差距是数量级的。那 Spring Cloud 呢不是说微服务不好而是工程管理系统这类企业内部系统的典型特征是用户量少、部署集中、业务复杂但调用链路相对固定。随便算算几百个用户、每天几千次操作单体应用性能完全够。微服务带来的服务拆分、分布式事务、链路追踪、网关鉴权每一层都是额外负担需要专门的运维能力。下表是我在项目评估时常用的判断依据维度Spring Boot 单体Spring Cloud 微服务团队入门成本低一天能跑起来高需要理解注册中心、网关、配置中心开发效率高改动部署快低一次修改跨多个服务运维难度低一个 JAR 包高服务发现、配置更新、容器编排适合场景内部管理系统、中小团队多团队并行、高并发、对外平台工程管理系统属于典型的复杂业务、低并发场景单体应用反而是最务实的答案。3.2 前端、中间件、数据库的组合方案后端确定了 Spring Boot前端我建议 Vue 3 Element Plus Vite。这是目前国内 Java 后端最顺手的组合Element Plus 的表格、表单、树形组件对管理类页面覆盖很全面社区资料也多招人、找人接手都容易。数据库选 MySQL 8.x注意字符集用 utf8mb4排序规则用 utf8mb4_general_ci 或 utf8mb4_0900_ai_ci 都行。Redis 用来做登录 token 缓存、权限菜单缓存、验证码存储工程管理系统并发不高Redis 的负载压力很小。为什么不选前后端不分离的老式 JSP 方案JSP 在多人协作、前端调试、移动端适配方面体验都太差。工程管理系统要对接移动端巡检、领导看板前后端分离是更省力的路线。3.3 微服务不是不能用但要清楚代价有一种情况我会认真考虑微服务公司同时在跑几十个独立项目部每个项目部网络环境隔离数据要汇总到总部或者未来要同时开放给业主、监理、供应商多方登录接口集成需求多。即便如此我也不会直接上 Spring Cloud 全套而是先把单个应用按模块拆分出清晰边界预留好接口。比如把项目主数据合同/成本物资库存设计成可独立部署的模块等量级确实上来了再拆。内部系统一上来就上分布式事务单据、库存这种强一致性场景会在联调阶段让你痛不欲生。4. 权限与数据隔离工程管理系统源码里最难啃的部分4.1 先落地标准 RBAC再谈工程组织做工程管理系统源码最大的坑往往不是业务功能而是权限模型。标准 RBAC 需要以下核心表sys_user用户表sys_role角色表sys_menu菜单/按钮/权限标识表sys_user_role用户-角色关联表sys_role_menu角色-权限关联表sys_org组织架构表公司 - 分公司 - 项目部 - 班组在工程场景里组织架构相对于一般企业在项目部这一层有明显特殊性一个用户可能既在公司总部挂职又同时参与两三个项目部。这种用户如果只挂在组织树的某个节点下跨项目协作时会非常别扭。我的建议是引入岗位 项目成员双维度用户在系统里属于某个组织同时通过项目成员表维护其参与了哪些项目以及在这些项目里的角色项目经理、项目总工、成本员等。菜单权限按角色走数据权限按组织和项目成员身份走。4.2 数据权限项目级隔离的两种实战实现菜单权限解决的是能不能看到这个按钮的问题数据权限解决的是同一张表格里能看哪些行的问题。工程管理系统里数据权限通常分为四个范围仅本人适合查看个人待办、个人填报内容本部门项目部项目成员默认只能看本项目内数据本公司分公司经理、公司成本部可以看所有项目全部系统管理员、集团层面。实现上有两种主流做法。第一种是纯手工方式每个查询方法接收当前用户信息Service 层手动拼接数据权限条件。这种方式直观、好调试但很容易漏——新写一个列表接口时忘掉拼接机密数据就泄露了。第二种是我的实现拦截器方式通过 AOP或 MyBatis 拦截器统一处理。业务代码只写正常查询拦截器自动根据当前用户的数据权限范围改写 SQL追加条件。这种方式一劳永逸所有查询都被强制约束。4.3 MyBatis 拦截器实现数据权限的关键代码与限制我实际项目里用的是 MyBatis-Plus 自带的 DataPermissionInterceptor 多租户插件改造的。核心思路是给业务表增加一个逻辑上的 project_id 过滤条件在查询时自动追加。用一个简单版本说明原理定义一个自定义注解Target({ElementType.METHOD, ElementType.TYPE}) Retention(RetentionPolicy.RUNTIME) public interface DataScope { // 业务表别名 String alias() default ; // 数据范围类型ALL、COMPANY、PROJECT、SELF DataScopeType type() default DataScopeType.PROJECT; }然后在 MyBatis 拦截器里解析 SQL这里用一个极简示意不依赖具体 ORMIntercepts({ Signature(type StatementHandler.class, method prepare, args {Connection.class, Integer.class}) }) public class DataScopeInterceptor implements Interceptor { Override public Object intercept(Invocation invocation) throws Throwable { // 1. 从上下文中拿到当前登录用户 // 2. 根据注解和数据权限范围生成过滤条件 // 3. 改写 BoundSql 的 SQL追加 WHERE project_id IN(...) 或 org_id IN(...) return invocation.proceed(); } }实际生产环境里我不会完全依赖这种通用解析因为工程管理系统的报表 SQL 经常有子查询、UNION、多表关联拦截器写错就会造成数据错误或性能问题。所以我的最终方案是双保险列表查询和详情查询强制走统一 Service 基类基类里自动填充数据权限条件报表、聚合统计、导出等复杂 SQL 由开发人员显式传入 projectIds 参数并在代码评审时重点检查是否调用了权限工具类。在工程管理系统的源码里数据权限和事务边界一样属于不能乱动的核心逻辑。二次开发时最忌讳为了图省事直接关掉数据权限拦截器一出事就是数据安全事故。5. 从零搭一套可运行的工程管理系统源码5.1 技术栈与版本清单如果你准备自己动手搭一套我列一份当前实测比较稳定的版本组合组件推荐版本备注JDK17 LTS新项目不要再停留 JDK 8Spring Boot3.2.x对应 JDK 17兼容性稳定MyBatis-Plus3.5.x单表 CRUD 和分页非常省事MySQL8.0.x生产环境注意装 utf8mb4Redis6.x / 7.x缓存 登录 tokenVue3.4.x使用组合式 APIElement Plus2.x后台管理组件库Maven3.9.x构建打包Nginx1.24前端静态资源 接口反向代理5.2 核心表结构设计至少先建这五张业务表这里给一个最小但能覆盖主流程的表设计以项目表和合同表、物资出入库流水为重点展示。项目主表projectCREATE TABLE project ( id bigint NOT NULL AUTO_INCREMENT, project_code varchar(64) NOT NULL COMMENT 项目编码, project_name varchar(255) NOT NULL COMMENT 项目名称, org_id bigint NOT NULL COMMENT 所属公司/分公司, manager_id bigint DEFAULT NULL COMMENT 项目经理用户ID, status tinyint NOT NULL DEFAULT 0 COMMENT 0立项 1施工中 2停工 3竣工 4已结算, start_date date DEFAULT NULL, end_date date DEFAULT NULL, target_cost decimal(18,2) DEFAULT 0 COMMENT 目标成本单位元, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_org_id (org_id), KEY idx_project_code (project_code) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT项目台账表;合同主表contract_mainCREATE TABLE contract_main ( id bigint NOT NULL AUTO_INCREMENT, contract_no varchar(64) NOT NULL, project_id bigint NOT NULL, contract_type tinyint NOT NULL COMMENT 1收入合同 2支出合同, party_a varchar(255) DEFAULT NULL COMMENT 甲方, party_b varchar(255) DEFAULT NULL COMMENT 乙方, contract_amount decimal(18,2) NOT NULL DEFAULT 0 COMMENT 合同金额, signed_date date DEFAULT NULL, status tinyint NOT NULL DEFAULT 0, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT合同主表;物资出库流水表material_out_stockCREATE TABLE material_out_stock ( id bigint NOT NULL AUTO_INCREMENT, out_no varchar(64) NOT NULL, project_id bigint NOT NULL, wbs_node_id bigint DEFAULT NULL COMMENT 关联WBS节点用于成本归集, material_id bigint NOT NULL, quantity decimal(18,3) NOT NULL, unit_price decimal(18,2) DEFAULT 0, amount decimal(18,2) DEFAULT 0 COMMENT 金额 数量 * 单价, out_date date NOT NULL, operator_id bigint DEFAULT NULL, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_project_wbs (project_id, wbs_node_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT物资出库流水表;权限相关表sys_user, sys_role, sys_menu, sys_user_role, sys_role_menu就是标准的 RBAC 五件套字段不复杂但注意角色和项目之间要有一个关联表比如 sys_user_project用于维护用户-可访问项目列表。表名上建议统一加前缀比如 t_project、t_contract_main能少踩很多保留字冲突。5.3 后端工程结构与项目骨架后端工程目录我用模块化分包但依然是单个 Spring Boot 应用结构如下com.company.ems ├── common // 通用工具、常量、异常处理、统一返回 │ ├── result │ ├── exception │ └── utils ├── framework // 安全认证、权限注解、数据权限拦截器 │ ├── security │ ├── datasource │ └── interceptor ├── modules │ ├── project // 项目管理 │ │ ├── controller │ │ ├── service │ │ ├── mapper │ │ ├── entity │ │ └── dto │ ├── contract // 合同管理 │ ├── material // 物资管理 │ ├── cost // 成本管理 │ ├── quality // 质量巡检 │ └── report // 报表中心 └── system // 用户、角色、菜单、组织这样结构清晰后续如果某个模块要独立成微服务直接按 modules 下的目录边界复制代码即可。5.4 核心代码登录、项目查询、数据权限拦截登录认证逻辑我直接用 Sa-Token 或 Spring Security 都可以工程管理系统为了快我用的是 Sa-Token 简化会话管理。关键配置在 application.yml 里server: port: 8080 servlet: context-path: / spring: datasource: url: jdbc:mysql://localhost:3306/ems?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/Shanghai username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver redis: host: localhost port: 6379 mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: map-underscore-to-camel-case: true global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0一个简单的项目分页查询接口结合数据权限注解RestController RequestMapping(/api/project) public class ProjectController { Resource private ProjectService projectService; SaCheckPermission(project:list) GetMapping(/page) public ResultIPageProjectVO page(ProjectQuery query) { return Result.success(projectService.page(query)); } }Service 层继承 MyBatis-Plus 的 ServiceImpl 后数据权限通过自定义的 BaseService 在查询前填充条件public IPageProjectVO page(ProjectQuery query) { Long uid StpUtil.getLoginIdAsLong(); // 根据用户角色、组织、项目成员关系计算出可见项目 ID 集合 DataScopeContext.setDataScope(userProjectScope(uid)); return baseMapper.selectProjectPage(query, QueryHelper.buildPage(query)); }如果你的系统表结构设计得够规范所有业务模块都带 project_id那项目级数据隔离就是围绕这一个字段做文章简单直接。5.5 前端与后端联调的最小闭环前端我用 Vue 3 Vite。最小闭环就是登录页 首页 项目列表页接口请求通过 Axios 统一加 token// src/api/request.js import axios from axios const request axios.create({ baseURL: /api, timeout: 10000 }) request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) request.interceptors.response.use(res { const data res.data if (data.code ! 200) { // 统一提示错误 return Promise.reject(new Error(data.message)) } return data.data }) export default request项目列表页的核心就是调用分页接口然后把返回数据渲染到表格。Element Plus 的 el-table 封装已经很完善列配置直接映射后端 VO 字段即可。这里给一个建议前后端交接的字段命名统一用 camelCase后端在 DTO 上做 JsonProperty 或开启 MyBatis-Plus 的驼峰映射不要前端转一次、后端再转一次那是纯粹给自己找麻烦。6. 从部署到维护实战中踩过的坑和避坑建议6.1 部署方式与 JVM/时区参数工程管理系统通常是部署在内网服务器我的部署方案是这样后端打 JAR 包前端打包成 dist 静态目录用 Nginx 统一托管。JAR 包启动时有些参数我每次都会带上java -Xms512m -Xmx1024m -Duser.timezoneAsia/Shanghai -jar ems.jar这里有个真实教训服务器默认时区是 UTC导致直连数据库时时间字段写入差了 8 个小时。如果 JDBC 连接串里不指定 serverTimezoneAsia/Shanghai一旦运维把服务器时区改了日志和业务数据会出现诡异的时间偏移。我后来直接在 MySQL 连接串里固定时区服务器层不再依赖默认时区。Docker 部署是另一个建议尤其当一台服务器要同时跑后端和 Redis 时用 docker-compose 统一编排比手工维护进程省心得多。6.2 前端打包路径和反向代理的坑前端 Nginx 配置是这类系统最容易出 bug 的地方。我遇到过一个非常典型的问题Vue 打包时没有配置 base导致上线后资源路径全部指向根目录Nginx 静态目录配的却是 /web 白屏一整天。Nginx 的推荐配置如下server { listen 80; server_name yourserver.com; # 前端静态资源 root /opt/ems/dist; index index.html; # 前端路由 history 模式 location / { 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; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }特别注意 proxy_pass http://127.0.0.1:8080/ 这行末尾的斜杠。如果少写这个斜杠/api/project/page 会被代理成 http://127.0.0.1:8080/api/project/page但你的后端 controller 映射是 /project/page直接 404。这个反斜杠问题排查起来不看配置很难发现。6.3 数据库命名、金额精度和大字段的坑表名、字段名方面project 这个词本身不是 MySQL 保留字但 order、status、desc 这类词很容易踩坑。我的习惯是业务表统一加前缀字段尽量用有业务含义的完整单词组合。金额字段一律用 decimal(18,2)千万不要用 float 或 double。工程领域单笔合同金额动辄几百上千万float 的精度误差在累计和对比时会被放大最后呈现出来的报表总是差几分钱财务同事一遍遍找你对账极其折磨。大字段文件比如合同扫描件、质量图片、验收资料正确的做法是存对象存储或者服务器磁盘路径数据库只存 URL。我见过把 PDF 转成 Base64 直接塞进 MySQL 的骚操作一个合同 20MB连查带传卡到爆炸。6.4 安全基线能上线的硬性要求工程管理系统虽然多为内网使用但安全基线不能省密码绝不能明文存用 BCrypt 加密系统初始密码必须强制修改而且可以做一张用户首次登录强制改密的标记字段接口统一使用 Result 包装类配合 RestControllerAdvice 做全局异常处理避免堆栈信息直接暴露MyBatis 的 SQL 一律用 #{} 传参数禁止字符串拼接 ${}防止 SQL 注入登录接口加验证码防止暴力破解管理端入口不要暴露到公网尽量通过内网访问或加防火墙白名单。这些点每一条都有真实事故案例支撑不是危言耸听。我接手过一个系统上线半年admin 密码还是 admin直接被人扫后台把项目成本数据拖了个遍损失很难挽回。6.5 拿到源码后二次开发的三不要如果你手头已经有一套 Java 工程管理系统源码准备二次开发有几句实在话要讲第一不要上来就直接改代码。先把项目跑起来用测试账号把项目立项、合同、物资出库、成本报表这条主流程走一遍搞清楚数据是怎么从一张表流转到另一张表的。连数据流都没搞清楚就改代码改一处错一处。第二不要动数据权限拦截器和事务边界。这两个是业务系统的命门。有过一个发生在真实项目里的例子开发为了调试方便把数据权限拦截器临时注释掉后来上线忘了开回来一个普通劳务分包商账号能查到全公司的合同结算表。这种事故一次就够伤筋动骨。第三不要随意改表结构尤其是金额字段和状态字段。工程领域有很多约定俗成的业务状态比如合同状态、结算状态、整改状态修改状态字典必须连带检查所有相关的统计 SQL。数据库字段改名更是高危操作后端实体、Mapper XML、前端表格列任何一处漏改都会让你排查半天。工程管理系统这个方向看着低调实际做起来极其考验对业务和技术的综合理解。能落地的系统都不是靠堆功能堆出来的而是靠先把项目主线理清楚、把权限和数据隔离做对、把技术选型控制在团队 hold 得住的范围内。如果你正准备自研或二次开发建议先把立项-合同-物资-成本-报表这条闭环彻底跑通再去考虑加花活。这是我在这个领域踩了无数坑之后最想对同行讲的一句话。
返回列表