业务审批桥
业务审批桥
业务用途
业务审批桥(BizApprovalController)是连接低代码表单数据与 BPM 审批流程的一体化接口。它将「保存业务数据」和「发起审批流程」两步操作合并为一个原子请求,业务方只需调用一个接口即可完成数据提交 + 流程发起。
典型场景:
- 用户填写采购申请表单 -> 点击「提交审批」->
submit/{formId}自动保存数据并发起流程 - 已保存的采购单 -> 点击「提交审批」->
submit/{formId}/{dataId}对已有数据发起流程 - 采购单列表页 -> 批量查询审批状态 ->
status/batch/{formId} - 采购单详情页 -> 查询审批状态 ->
status/{formId}/{dataId}
涉及文件
后端
| 文件 | 说明 |
|---|---|
backend/src/main/java/com/lowcode/controller/BizApprovalController.java | 业务审批桥控制器(/api/biz-approval) |
backend/src/main/java/com/lowcode/service/impl/bpm/BpmModelServiceImpl.java | 模型服务(startProcess 方法) |
backend/src/main/java/com/lowcode/service/bpm/SimpleFlowEngine.java | 引擎(实际执行 startProcess) |
backend/src/main/java/com/lowcode/service/SchemaService.java | 动态表数据服务(insertData / updateData / getDataById) |
backend/src/main/java/com/lowcode/entity/LcForm.java | 表单实体(formCode / tableId / approvalEnabled / processModelId) |
数据库表
BizApprovalController 本身不对应独立数据库表,它操作以下表:
| 表 | 操作 | 说明 |
|---|---|---|
动态业务表(如 erp_purchase_request) | INSERT / UPDATE | 通过 SchemaService 读写业务数据 |
bpm_instance | INSERT | 通过 SimpleFlowEngine.startProcess 创建流程实例 |
bpm_task | INSERT | 引擎自动创建首个审批任务 |
lc_form | SELECT | 查询表单配置(formCode、tableId、processModelId、approvalEnabled) |
后端实现
BizApprovalController 端点一览
基础路径:/api/biz-approval
| HTTP | 路径 | 参数 | 说明 |
|---|---|---|---|
| POST | /submit/{formId} | formId(路径), Body: Map<String, Object> 数据 | 提交新数据 + 发起审批 |
| POST | /submit/{formId}/{dataId} | formId(路径), dataId(路径), Body: Map<String, Object> 数据 | 对已有数据发起审批 |
| GET | /status/{formId}/{dataId} | formId(路径), dataId(路径) | 查询单条数据的审批状态 |
| POST | /status/batch/{formId} | formId(路径), Body: List<Long> dataIds | 批量查询审批状态 |
submit/{formId} -- 提交新数据并发起审批
@PostMapping("/submit/{formId}")
public Result<Map<String, Object>> submitApproval(
@PathVariable Long formId, @RequestBody Map<String, Object> data) {
// 1. 校验表单配置
LcForm form = formMapper.selectById(formId);
if (form == null) {
return Result.error("表单不存在");
}
if (!Boolean.TRUE.equals(form.getApprovalEnabled())) {
return Result.error("当前表单未启用审批");
}
if (form.getProcessModelId() == null) {
return Result.error("当前表单未绑定审批流程");
}
if (form.getTableId() == null) {
return Result.error("当前表单未绑定业务表");
}
// 2. 通过 SchemaService 插入业务数据
Long dataId = schemaService.insertData(form.getTableId(), data);
LcTableMeta table = schemaService.getTableDetail(form.getTableId());
// 3. 构建流程变量
Map<String, Object> variables = new HashMap<>();
variables.put("formId", formId);
variables.put("tableId", form.getTableId());
variables.put("businessId", dataId);
variables.put("businessTable", table.getTableName());
variables.put("businessType", form.getFormCode()); // ★ formCode
variables.put("businessKey", form.getFormCode() + ":" + dataId); // ★ formCode:dataId
variables.put("title", form.getFormName() + "审批");
variables.put("formData", toJson(data));
variables.putAll(data); // 业务数据也作为流程变量传入
// 4. 发起流程
BpmInstance instance = bpmModelService.startProcess(form.getProcessModelId(), variables);
// 5. 返回结果
Map<String, Object> result = new HashMap<>();
result.put("dataId", dataId);
result.put("instanceId", instance.getId());
result.put("status", instance.getStatus());
return Result.success("提交审批成功", result);
}submit/{formId}/{dataId} -- 对已有数据发起审批
@PostMapping("/submit/{formId}/{dataId}")
public Result<Map<String, Object>> submitExistingApproval(
@PathVariable Long formId, @PathVariable Long dataId,
@RequestBody Map<String, Object> data) {
// 1. 校验表单配置(同上)
LcForm form = formMapper.selectById(formId);
// ... 校验 ...
// 2. 检查数据是否存在
Map<String, Object> existingData = schemaService.getDataById(form.getTableId(), dataId);
if (existingData == null) {
return Result.error("业务数据不存在,无法提交审批");
}
// 3. 检查是否已在审批中
Map<String, Object> status = buildApprovalStatus(form, dataId);
if ("running".equals(status.get("status"))) {
return Result.error("该记录已在审批中,不能重复提交");
}
if ("completed".equals(status.get("status"))) {
return Result.error("该记录审批已通过,不能重复提交");
}
// 4. 如果有新数据,更新业务数据
if (data != null && !data.isEmpty()) {
schemaService.updateData(form.getTableId(), dataId, data);
existingData = schemaService.getDataById(form.getTableId(), dataId);
}
// 5. 构建流程变量并发起流程(同上)
Map<String, Object> variables = new HashMap<>();
variables.put("businessType", form.getFormCode());
variables.put("businessKey", form.getFormCode() + ":" + dataId);
// ...
BpmInstance instance = bpmModelService.startProcess(form.getProcessModelId(), variables);
// ...
}status/{formId}/{dataId} -- 查询审批状态
@GetMapping("/status/{formId}/{dataId}")
public Result<Map<String, Object>> getApprovalStatus(
@PathVariable Long formId, @PathVariable Long dataId) {
LcForm form = formMapper.selectById(formId);
if (form == null) {
Map<String, Object> result = new HashMap<>();
result.put("status", "none");
return Result.success(result);
}
return Result.success(buildApprovalStatus(form, dataId));
}buildApprovalStatus -- 实时派生审批状态
private Map<String, Object> buildApprovalStatus(LcForm form, Long dataId) {
Map<String, Object> result = new HashMap<>();
// 通过 businessKey 查找最新的流程实例
BpmInstance instance = bpmInstanceMapper.selectOne(
new LambdaQueryWrapper<BpmInstance>()
.eq(BpmInstance::getBusinessKey, form.getFormCode() + ":" + dataId)
.orderByDesc(BpmInstance::getCreateTime)
.last("LIMIT 1")
);
if (instance == null) {
result.put("status", "none");
return result;
}
result.put("status", instance.getStatus()); // running/completed/rejected/recalled
result.put("instanceId", instance.getId());
result.put("startTime", instance.getStartTime());
result.put("endTime", instance.getEndTime());
// 查找当前待办任务
BpmTask task = bpmTaskMapper.selectOne(
new LambdaQueryWrapper<BpmTask>()
.eq(BpmTask::getInstanceId, instance.getId())
.eq(BpmTask::getStatus, "pending")
.orderByAsc(BpmTask::getCreateTime)
.last("LIMIT 1")
);
if (task != null) {
result.put("taskId", task.getId());
result.put("taskStatus", task.getStatus());
result.put("taskName", task.getTaskName());
}
return result;
}两种审批状态来源
- 实时派生(
buildApprovalStatus):通过businessKey关联bpm_instance表查询。适用于未配置approval_status列的表。 - 回写落库(
BpmBusinessStatusCallback):将状态写入业务表approval_status列。适用于配置了该列的表(如 ERP 单据表)。
两种方式可以共存:buildApprovalStatus 返回流程实例状态,approval_status 列存储业务表状态。
status/batch/{formId} -- 批量查询审批状态
@PostMapping("/status/batch/{formId}")
public Result<Map<Long, Map<String, Object>>> batchApprovalStatus(
@PathVariable Long formId, @RequestBody List<Long> dataIds) {
LcForm form = formMapper.selectById(formId);
Map<Long, Map<String, Object>> result = new HashMap<>();
if (form == null || dataIds == null || dataIds.isEmpty()) {
return Result.success(result);
}
for (Long dataId : dataIds) {
if (dataId != null) {
result.put(dataId, buildApprovalStatus(form, dataId));
}
}
return Result.success(result);
}前端集成
典型调用流程
// 1. 提交审批(新数据)
const response = await request.post(`/biz-approval/submit/${formId}`, formData)
// 返回: { dataId, instanceId, status }
// 2. 查询审批状态(列表页批量查询)
const statusMap = await request.post(`/biz-approval/status/batch/${formId}`, [1, 2, 3])
// 返回: { 1: {status: 'running', instanceId: 10}, 2: {status: 'completed'}, ... }
// 3. 查询单条审批状态(详情页)
const status = await request.get(`/biz-approval/status/${formId}/${dataId}`)
// 返回: { status: 'running', instanceId: 10, taskId: 5, taskName: '主管审批' }
businessKey 格式约定
businessKey = formCode + ":" + dataId| 字段 | 来源 | 示例 |
|---|---|---|
formCode | LcForm.formCode | purchase_request |
dataId | SchemaService.insertData 返回值 | 100 |
businessKey | 拼接 | purchase_request:100 |
businessType | LcForm.formCode | purchase_request |
businessKey 是连接流程实例与业务数据行的唯一标识。BpmBusinessStatusCallback 通过 businessKey 最后一个冒号后的数字解析 dataId,然后 UPDATE <业务表> SET approval_status = ? WHERE id = ?。
操作步骤
1. 配置表单
在表单设计器中:
- 设置
formCode(与业务表名一致,如purchase_request) - 绑定
tableId(指向业务表元数据) - 启用
approvalEnabled = true - 绑定
processModelId(指向已部署的流程模型)
2. 提交新数据并发起审批
curl -X POST http://localhost:52856/api/biz-approval/submit/1 \
-H "Content-Type: application/json" \
-d '{
"supplier_name": "供应商A",
"total_amount": 5000,
"request_date": "2026-08-06",
"items": [
{"name": "笔记本", "qty": 10, "price": 500}
]
}'返回:
{
"code": 200,
"msg": "提交审批成功",
"data": {
"dataId": 100,
"instanceId": 50,
"status": "running"
}
}3. 对已有数据发起审批
curl -X POST http://localhost:52856/api/biz-approval/submit/1/100 \
-H "Content-Type: application/json" \
-d '{"total_amount": 6000}'4. 查询审批状态
# 单条查询
curl "http://localhost:52856/api/biz-approval/status/1/100"
# 批量查询
curl -X POST http://localhost:52856/api/biz-approval/status/batch/1 \
-H "Content-Type: application/json" \
-d '[100, 101, 102]'返回:
{
"code": 200,
"data": {
"status": "running",
"instanceId": 50,
"startTime": "2026-08-06T10:00:00",
"endTime": null,
"taskId": 80,
"taskStatus": "pending",
"taskName": "部门主管审批"
}
}常见问题
表单未启用审批
submit 接口会检查 form.approvalEnabled 是否为 true。如果表单设计器中没有启用审批,会返回错误「当前表单未启用审批」。需要在表单配置中设置 approvalEnabled = true 并绑定 processModelId。
重复提交检查
submit/{formId}/{dataId} 接口会检查该数据是否已有运行中或已完成的流程实例。如果状态为 running 返回「已在审批中,不能重复提交」;如果状态为 completed 返回「审批已通过,不能重复提交」。但 rejected 和 recalled 状态允许重新提交。
审批状态与 approval_status 列的关系
buildApprovalStatus 方法返回的是 bpm_instance.status(running/completed/rejected/recalled),而 BpmBusinessStatusCallback 回写的是映射后的值(IN_PROGRESS/APPROVED/REJECTED/WITHDRAWN)。如果业务表有 approval_status 列,建议直接读该列;如果没有,用 buildApprovalStatus 实时派生。
为什么不用事务包裹数据插入 + 流程发起?
submit/{formId} 方法没有 @Transactional 注解。如果流程发起失败(如审批人未配置),业务数据已经插入不会回滚。这是设计选择:业务数据即使审批失败也保留,用户可以修正后重新提交审批(submit/{formId}/{dataId})。
