Skip to content

About

工资保证金管理系统技术案例说明 | 架构设计 · 工作流绑定 · 权限体系 · N+1 优化(仅技术说明,不含原项目源码)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 

Repository files navigation

工资保证金管理系统 · 技术案例说明

一份用于作品展示与面试沟通的技术案例:讲清楚系统的架构设计、核心业务流程,以及我在其中负责的模块与关键技术实现。


⚠️ 关于本仓库(请先读)

  • 本仓库不包含原项目的任何源码、数据库脚本、配置文件或业务数据。
  • 文中所有代码均为重新编写的通用示意代码,只用于表达设计思路,不可直接用于生产环境。
  • 本仓库为个人技术总结与作品说明,并非任何单位或机构的官方发布,也未经任何单位授权,不代表任何单位的立场或观点。
  • 文中不涉及任何真实系统地址、账号、密钥,以及任何个人或企业信息。

1. 项目背景

工程建设领域长期存在农民工工资拖欠问题。国家规定:施工企业开工前须预存一笔工资保证金,一旦发生拖欠,监管部门可直接动用这笔资金先行垫付,保障农民工权益。

本项目把这项制度搬到了线上,实现保证金 存缴登记 → 差异化计算 → 多级审批 → 使用 / 变更 → 退付 → 监管统计 的全流程数字化。

项 说明
业务域 政务类资金监管(农民工工资保证金)
主要角色 施工企业(申报)、监管部门(审批与监管)
项目性质 校内导师团队项目,多人协作、分模块开发
我的角色 负责系统管理模块与保证金核心业务,并参与工作流引擎与业务系统的集成

团队与个人职责的边界见 §5 我的职责与边界。这一节我写得比较直白,因为面试里最容易被追问的就是边界。


2. 系统架构

flowchart TB
    subgraph Client["前端 · Vue 2 + Ant Design Vue"]
        A1["业务页面<br/>存缴 / 使用 / 变更 / 退付"]
        A2["流程设计器 · 表单设计器"]
        A3["系统管理页面"]
    end

    subgraph GW["接入层"]
        G1["Nginx / 网关<br/>路由 · 限流 · JWT 前置校验"]
    end

    subgraph SVC["微服务层"]
        S1["主业务服务<br/>用户 · 角色 · 权限 · 字典 · 业务模块"]
        S2["业务服务<br/>保证金存缴 / 使用 / 变更 · 文书生成"]
        S3["工作流服务<br/>流程定义 · 任务审批 · 流程历史"]
    end

    subgraph REG["注册中心"]
        R1["Eureka"]
    end

    subgraph DATA["数据层"]
        D1[("业务库 MySQL")]
        D2[("工作流库 MySQL")]
        D3[("Redis")]
    end

    A1 --> G1
    A2 --> G1
    A3 --> G1
    G1 --> S1
    G1 --> S2
    G1 --> S3
    S1 -. 注册 / 发现 .-> R1
    S2 -. 注册 / 发现 .-> R1
    S3 -. 注册 / 发现 .-> R1
    S2 -- "Feign 同步调用" --> S3
    S1 --> D1
    S2 --> D1
    S3 --> D2
    S1 --> D3
    S2 --> D3
    S3 --> D3
Loading

2.1 为什么这样拆

低代码平台本身已经是一个能跑的"单体应用"(自带用户、权限、字典、代码生成)。我们没有停在"能跑",而是按业务域做了拆分,主要三个理由:

  1. 职责分离。平台的通用能力(用户/权限/字典)与保证金业务逻辑混在一个应用里,会让业务代码和平台代码高度耦合,平台升级时非常痛苦。
  2. 独立演进。保证金业务受政策影响变动频繁,而系统管理模块相对稳定。拆开后业务可以独立发版,不必整个系统重新部署。
  3. 资源与故障隔离。工作流服务的计算特征(长事务、定时任务)与普通业务接口不同,独立部署后可以按需扩容;同时两边数据库分离,一个库出问题不会连带另一个。

2.2 双数据源隔离

业务数据与流程数据物理分离到两个库:

  • 业务库:保证金主表、变更记录、使用记录等。
  • 工作流库:流程定义、流程实例、任务、流转历史、表单数据。

分离的收益是生命周期解耦——流程历史是只增不改的归档型数据,业务数据则频繁更新,两者混在一起会让备份、归档、慢查询排查都变得很难。

在服务内部,用 dynamic-datasource + @DS 注解按服务/方法切换数据源,避免手工管理 DataSource。

2.3 鉴权分层

层次 做什么 为什么
网关层 校验 JWT 有效性、解析用户身份写入请求头、黑名单与限流 所有请求的共性逻辑,只做一次,性能损耗最小
服务层 按钮级权限校验、数据范围过滤、业务状态校验 各业务特性逻辑,灵活度高

2.4 服务间通信

  • 同步:OpenFeign 声明式调用。需要拿到结果才能继续的场景(如"启动流程"返回 processInstanceId)走同步,并配置降级回退。
  • 异步(规划中):通知、统计报表等不需要等待结果的场景,交给消息队列 + 定时补偿。

3. 核心业务流程

以保证金使用申请为主线,单据的状态流转如下:

stateDiagram-v2
    [*] --> 待提交
    待提交 --> 审核中: 提交申请(校验可用余额、冻结额度)
    审核中 --> 已通过: 各审批节点通过
    审核中 --> 已驳回: 任一节点驳回
    已驳回 --> 待提交: 修改后重新提交
    已通过 --> 已生效: 生成凭证 / 入账
    已生效 --> 使用划拨: 监管部门核准划拨
    已生效 --> 退付中: 发起退付
    退付中 --> 已退付: 退付完成
    使用划拨 --> [*]
    已退付 --> [*]
Loading

流程引擎负责"谁在什么时候审批",业务服务负责"单据本身的状态与金额"——两条线各自独立,只在关键节点通过回调同步,这正是 §4.2 要解决的问题。


4. 关键技术点

4.1 权限:角色 → 菜单 → 按钮 三级控制

  • 角色 → 菜单:路由级控制,前端按角色动态生成可访问菜单,后端做二次校验(前端隐藏不等于安全)。
  • 按钮级:自定义注解 + AOP,在方法进入前校验当前用户是否拥有该操作权限。
  • 数据范围:不是"能不能点",而是"能看到几条"。通过数据范围切面,在 SQL 层追加部门/归属条件,做到部门数据隔离。

代码示意:samples/java/PermissionAspect.java、samples/java/DataScopeAspect.java 设计说明:docs/security-and-performance.md

4.2 业务单据与流程实例的双向绑定

这是整个项目里我认为最有价值的一处设计。

问题:业务数据存在业务库,流程实例存在工作流库,两者必须能互相追溯,但又不能相互依赖。

方案:用一张映射表维护 业务主键 ↔ 流程实例 ID(含单据类型),并固定绑定顺序:

  • 正向绑定:业务数据先落库拿到主键 → 再启动流程 → 写映射关系。

    顺序不能反。如果先启动流程而业务保存失败,就会产生"孤儿流程"——有流程、没单据,审批完了都不知道该改哪条数据。

  • 反向绑定:流程节点回调时,用 processInstanceId 反查业务主键,再回写业务状态、金额。
  • 多实例支持:同一单据可能并行发起多个流程(如"存缴方式变更"与"额度调整"),用 (单据类型, 业务主键, 流程实例 ID) 联合唯一索引支持。
  • 并发与一致性:常态用乐观锁(version + 状态条件更新),只有大额划拨这类高风险操作才叠加分布式锁,避免为了极少数场景牺牲整体吞吐。

时序图与详细说明:docs/workflow-binding.md 代码示意:samples/java/ProcessBindingService.java

4.3 待办列表的 N+1 优化

问题:通用待办列表要展示"流程名 + 发起人 + 所属部门 + 单据摘要"。最初是拿到待办列表后逐条去查流程定义、用户、部门,一次请求产生 N 次额外查询/调用。

方案:把"逐条查"改成"批量查 + 内存分组"。

  1. 一次批量拉取所有涉及的流程定义、用户、部门信息;
  2. 在内存中按 ID 建 Map<id, 实体>;
  3. 遍历待办列表做内存 join。

效果:单次请求的数据库 / RPC 调用次数从 O(N) 降到 O(k)(k 为需要批量查询的维度数,通常是常数),待办列表的响应时间不再随数据量线性增长。

对比说明:docs/security-and-performance.md 代码示意:samples/java/TodoQueryService.java

4.4 差异化存缴的可配置计算

保证金不是"一刀切按合同额百分比"。政策要求体现"守信激励、失信惩戒":

最终比例 = 基础比例
          + 信用等级调整     (信用越好,比例越低)
          + 行业风险调整     (高风险行业,比例上调)
          - 历史表现减免     (从未拖欠,可额外减免)
          ,并强制夹在[法定下限, 法定上限]区间内

关键设计是把调整系数做成可配置的字典数据,而不是写死在代码里——政策调整时改配置即可,不需要重新发版。

代码示意:samples/java/DepositRateCalculator.java

4.5 文书生成与归档

审批通过后需要出具标准文书(告知书、凭证等):用 poi / poi-tl 做模板渲染生成 Word,再转 PDF 归档;上传附件按业务主键分目录存放。


5. 我的职责与边界

面试时我会主动划清边界,因为讲清楚"团队做了什么、我做了什么"比含糊地全揽更可信。

我负责

  • 系统管理模块(用户 / 角色 / 菜单 / 权限 / 字典)的业务扩展与联调。
  • 保证金核心业务:存缴登记、使用申请、变更申请、退付等模块的后端实现。
  • 权限落地:基于平台 RBAC,结合 Shiro + JWT 与自定义注解 + AOP 实现按钮级权限与部门数据隔离。
  • 工作流与业务系统的集成设计:映射绑定机制、回调反查、并发一致性方案。
  • 通用待办列表接口的 N+1 优化。

我参与

  • 技术选型与架构讨论(微服务拆分、双数据源隔离)。
  • 前后端接口规范的制定与推行。

由平台 / 团队提供,不是我实现的

  • 低代码平台底座、系统管理基础功能、流程引擎内核、表单设计器、流程设计器、图形编辑器,以及前端管理端脚手架。

把这一节写清楚不是为了"缩小自己",而是因为面试官一定会问"这个是你写的吗"。答得清楚,比答得大更有说服力。


6. 技术栈

分类 选型
语言 / 运行时 Java 8
后端框架 Spring Boot 2.x、Spring Cloud(Eureka / OpenFeign)
工作流 uflo(团队在其之上做业务集成)
权限 Shiro + JWT
ORM / 数据 MyBatis-Plus、MySQL 8、dynamic-datasource(双数据源)
缓存 / 并发 Redis(会话、热点缓存、分布式锁)
文档 / 导出 poi、poi-tl、iText(Word / PDF)
接口文档 Knife4j / Swagger
前端 Vue 2、Ant Design Vue、Vuex、Vue Router、Axios、ECharts

7. 说明与免责

  1. 本仓库是个人技术总结与作品说明仓库,用于展示设计思路与个人贡献,不包含任何原项目代码。
  2. 所有示例代码均为重新编写,与真实生产代码无关,不保证可直接运行。
  3. 本仓库非任何单位或机构的官方发布,也未经任何单位授权;文中观点仅代表个人。
  4. 若本仓库内容涉及任何权益问题,请联系我,我会立即处理。

如果你是在面试中看到这个仓库:§2 架构、§4.2 工作流绑定、§4.3 N+1 优化是我最希望被追问的三处。

About

工资保证金管理系统技术案例说明 | 架构设计 · 工作流绑定 · 权限体系 · N+1 优化(仅技术说明,不含原项目源码)

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors