一份用于作品展示与面试沟通的技术案例:讲清楚系统的架构设计、核心业务流程,以及我在其中负责的模块与关键技术实现。
- 本仓库不包含原项目的任何源码、数据库脚本、配置文件或业务数据。
- 文中所有代码均为重新编写的通用示意代码,只用于表达设计思路,不可直接用于生产环境。
- 本仓库为个人技术总结与作品说明,并非任何单位或机构的官方发布,也未经任何单位授权,不代表任何单位的立场或观点。
- 文中不涉及任何真实系统地址、账号、密钥,以及任何个人或企业信息。
工程建设领域长期存在农民工工资拖欠问题。国家规定:施工企业开工前须预存一笔工资保证金,一旦发生拖欠,监管部门可直接动用这笔资金先行垫付,保障农民工权益。
本项目把这项制度搬到了线上,实现保证金 存缴登记 → 差异化计算 → 多级审批 → 使用 / 变更 → 退付 → 监管统计 的全流程数字化。
| 项 | 说明 |
|---|---|
| 业务域 | 政务类资金监管(农民工工资保证金) |
| 主要角色 | 施工企业(申报)、监管部门(审批与监管) |
| 项目性质 | 校内导师团队项目,多人协作、分模块开发 |
| 我的角色 | 负责系统管理模块与保证金核心业务,并参与工作流引擎与业务系统的集成 |
团队与个人职责的边界见 §5 我的职责与边界。这一节我写得比较直白,因为面试里最容易被追问的就是边界。
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
低代码平台本身已经是一个能跑的"单体应用"(自带用户、权限、字典、代码生成)。我们没有停在"能跑",而是按业务域做了拆分,主要三个理由:
- 职责分离。平台的通用能力(用户/权限/字典)与保证金业务逻辑混在一个应用里,会让业务代码和平台代码高度耦合,平台升级时非常痛苦。
- 独立演进。保证金业务受政策影响变动频繁,而系统管理模块相对稳定。拆开后业务可以独立发版,不必整个系统重新部署。
- 资源与故障隔离。工作流服务的计算特征(长事务、定时任务)与普通业务接口不同,独立部署后可以按需扩容;同时两边数据库分离,一个库出问题不会连带另一个。
业务数据与流程数据物理分离到两个库:
- 业务库:保证金主表、变更记录、使用记录等。
- 工作流库:流程定义、流程实例、任务、流转历史、表单数据。
分离的收益是生命周期解耦——流程历史是只增不改的归档型数据,业务数据则频繁更新,两者混在一起会让备份、归档、慢查询排查都变得很难。
在服务内部,用 dynamic-datasource + @DS 注解按服务/方法切换数据源,避免手工管理 DataSource。
| 层次 | 做什么 | 为什么 |
|---|---|---|
| 网关层 | 校验 JWT 有效性、解析用户身份写入请求头、黑名单与限流 | 所有请求的共性逻辑,只做一次,性能损耗最小 |
| 服务层 | 按钮级权限校验、数据范围过滤、业务状态校验 | 各业务特性逻辑,灵活度高 |
- 同步:
OpenFeign声明式调用。需要拿到结果才能继续的场景(如"启动流程"返回processInstanceId)走同步,并配置降级回退。 - 异步(规划中):通知、统计报表等不需要等待结果的场景,交给消息队列 + 定时补偿。
以保证金使用申请为主线,单据的状态流转如下:
stateDiagram-v2
[*] --> 待提交
待提交 --> 审核中: 提交申请(校验可用余额、冻结额度)
审核中 --> 已通过: 各审批节点通过
审核中 --> 已驳回: 任一节点驳回
已驳回 --> 待提交: 修改后重新提交
已通过 --> 已生效: 生成凭证 / 入账
已生效 --> 使用划拨: 监管部门核准划拨
已生效 --> 退付中: 发起退付
退付中 --> 已退付: 退付完成
使用划拨 --> [*]
已退付 --> [*]
流程引擎负责"谁在什么时候审批",业务服务负责"单据本身的状态与金额"——两条线各自独立,只在关键节点通过回调同步,这正是 §4.2 要解决的问题。
- 角色 → 菜单:路由级控制,前端按角色动态生成可访问菜单,后端做二次校验(前端隐藏不等于安全)。
- 按钮级:自定义注解 + AOP,在方法进入前校验当前用户是否拥有该操作权限。
- 数据范围:不是"能不能点",而是"能看到几条"。通过数据范围切面,在 SQL 层追加部门/归属条件,做到部门数据隔离。
代码示意:
samples/java/PermissionAspect.java、samples/java/DataScopeAspect.java设计说明:docs/security-and-performance.md
这是整个项目里我认为最有价值的一处设计。
问题:业务数据存在业务库,流程实例存在工作流库,两者必须能互相追溯,但又不能相互依赖。
方案:用一张映射表维护 业务主键 ↔ 流程实例 ID(含单据类型),并固定绑定顺序:
- 正向绑定:业务数据先落库拿到主键 → 再启动流程 → 写映射关系。
顺序不能反。如果先启动流程而业务保存失败,就会产生"孤儿流程"——有流程、没单据,审批完了都不知道该改哪条数据。
- 反向绑定:流程节点回调时,用
processInstanceId反查业务主键,再回写业务状态、金额。 - 多实例支持:同一单据可能并行发起多个流程(如"存缴方式变更"与"额度调整"),用
(单据类型, 业务主键, 流程实例 ID)联合唯一索引支持。 - 并发与一致性:常态用乐观锁(
version+ 状态条件更新),只有大额划拨这类高风险操作才叠加分布式锁,避免为了极少数场景牺牲整体吞吐。
时序图与详细说明:
docs/workflow-binding.md代码示意:samples/java/ProcessBindingService.java
问题:通用待办列表要展示"流程名 + 发起人 + 所属部门 + 单据摘要"。最初是拿到待办列表后逐条去查流程定义、用户、部门,一次请求产生 N 次额外查询/调用。
方案:把"逐条查"改成"批量查 + 内存分组"。
- 一次批量拉取所有涉及的流程定义、用户、部门信息;
- 在内存中按 ID 建
Map<id, 实体>; - 遍历待办列表做内存 join。
效果:单次请求的数据库 / RPC 调用次数从 O(N) 降到 O(k)(k 为需要批量查询的维度数,通常是常数),待办列表的响应时间不再随数据量线性增长。
对比说明:
docs/security-and-performance.md代码示意:samples/java/TodoQueryService.java
保证金不是"一刀切按合同额百分比"。政策要求体现"守信激励、失信惩戒":
最终比例 = 基础比例
+ 信用等级调整 (信用越好,比例越低)
+ 行业风险调整 (高风险行业,比例上调)
- 历史表现减免 (从未拖欠,可额外减免)
,并强制夹在[法定下限, 法定上限]区间内
关键设计是把调整系数做成可配置的字典数据,而不是写死在代码里——政策调整时改配置即可,不需要重新发版。
审批通过后需要出具标准文书(告知书、凭证等):用 poi / poi-tl 做模板渲染生成 Word,再转 PDF 归档;上传附件按业务主键分目录存放。
面试时我会主动划清边界,因为讲清楚"团队做了什么、我做了什么"比含糊地全揽更可信。
我负责
- 系统管理模块(用户 / 角色 / 菜单 / 权限 / 字典)的业务扩展与联调。
- 保证金核心业务:存缴登记、使用申请、变更申请、退付等模块的后端实现。
- 权限落地:基于平台 RBAC,结合 Shiro + JWT 与自定义注解 + AOP 实现按钮级权限与部门数据隔离。
- 工作流与业务系统的集成设计:映射绑定机制、回调反查、并发一致性方案。
- 通用待办列表接口的 N+1 优化。
我参与
- 技术选型与架构讨论(微服务拆分、双数据源隔离)。
- 前后端接口规范的制定与推行。
由平台 / 团队提供,不是我实现的
- 低代码平台底座、系统管理基础功能、流程引擎内核、表单设计器、流程设计器、图形编辑器,以及前端管理端脚手架。
把这一节写清楚不是为了"缩小自己",而是因为面试官一定会问"这个是你写的吗"。答得清楚,比答得大更有说服力。
| 分类 | 选型 |
|---|---|
| 语言 / 运行时 | 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 |
- 本仓库是个人技术总结与作品说明仓库,用于展示设计思路与个人贡献,不包含任何原项目代码。
- 所有示例代码均为重新编写,与真实生产代码无关,不保证可直接运行。
- 本仓库非任何单位或机构的官方发布,也未经任何单位授权;文中观点仅代表个人。
- 若本仓库内容涉及任何权益问题,请联系我,我会立即处理。
如果你是在面试中看到这个仓库:§2 架构、§4.2 工作流绑定、§4.3 N+1 优化是我最希望被追问的三处。