审批记录与通知
审批记录与通知
业务用途
审批记录和通知是 BPM 工作流的「审计追踪」和「消息触达」层:
- 审批记录(
BpmApprovalRecord):记录每个审批节点的操作历史(通过/驳回/转办/委派/评论/抄送),形成完整的审批轨迹。 - 通知服务(
NotificationService):在审批各环节自动推送站内通知,包括待办通知、抄送通知、催办通知、审批结果通知、转办/委派通知、任务关注通知。
涉及文件
后端
| 文件 | 说明 |
|---|---|
backend/src/main/java/com/lowcode/controller/bpm/BpmApprovalRecordController.java | 审批记录控制器(/api/bpm/approval) |
backend/src/main/java/com/lowcode/service/impl/bpm/BpmApprovalRecordServiceImpl.java | 审批记录服务 |
backend/src/main/java/com/lowcode/service/NotificationService.java | 通知服务(站内通知) |
backend/src/main/java/com/lowcode/controller/NotificationController.java | 通知控制器(/api/notification) |
backend/src/main/java/com/lowcode/entity/bpm/BpmApprovalRecord.java | 审批记录实体 |
backend/src/main/java/com/lowcode/service/bpm/SimpleFlowEngine.java | 引擎调用通知服务 |
前端
| 文件 | 说明 |
|---|---|
frontend/src/views/bpm/approval.vue | 审批记录页面 |
frontend/src/views/bpm/todo.vue | 待办页(含通知展示) |
数据库表
bpm_approval_record(审批记录表)
CREATE TABLE bpm_approval_record (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
tenant_id BIGINT DEFAULT 1,
instance_id BIGINT NOT NULL, -- 流程实例 ID
task_id BIGINT, -- 任务 ID
sign_id BIGINT, -- 会签任务 ID
node_id VARCHAR(100), -- 节点标识
node_name VARCHAR(200), -- 节点名称
action_type VARCHAR(50) NOT NULL, -- ★ 操作类型(见下表)
operator_id BIGINT NOT NULL, -- 操作人 ID
operator_name VARCHAR(50) NOT NULL, -- 操作人姓名
operator_avatar VARCHAR(500), -- 操作人头像
opinion VARCHAR(1000), -- 审批意见
signature_url VARCHAR(500), -- 签名图片 URL
attachment_urls CLOB, -- 附件 URL(逗号分隔)
duration BIGINT, -- 处理耗时(秒)
result VARCHAR(20), -- 审批结果: approved/rejected
target_user_id BIGINT, -- 目标用户 ID(转办/加签/抄送)
target_user_name VARCHAR(50), -- 目标用户姓名
source_user_id BIGINT, -- 来源用户 ID(从谁转办)
source_user_name VARCHAR(50), -- 来源用户姓名
remark VARCHAR(500), -- 备注
ip_address VARCHAR(50), -- IP 地址
user_agent VARCHAR(500), -- 设备信息
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);action_type 操作类型
| 值 | 含义 |
|---|---|
APPROVE | 同意 |
REJECT | 驳回 |
DELEGATE | 委托 |
TRANSFER | 转办 |
CANCEL | 取消 |
ROLLBACK | 回退 |
ADD_SIGN_BEFORE | 前加签 |
ADD_SIGN_AFTER | 后加签 |
COMMENT | 评论 |
COPY | 抄送 |
sys_notification(通知表)
通知存储在 sys_notification 而非 bpm_notification
NotificationService 使用 sys_notification 表存储所有通知(不限于 BPM)。bpm_notification 表在 SQL_EXTENSION.sql 中定义但当前未使用。
-- sys_notification 表结构(由 NotificationService 使用)
-- 字段: id, user_id, user_name, title, content, type, category,
-- related_id, related_type, status, read_time, create_time, update_time, deleted后端实现
BpmApprovalRecordController 端点一览
基础路径:/api/bpm/approval
| HTTP | 路径 | 参数 | 说明 |
|---|---|---|---|
| GET | /instance/{instanceId} | instanceId | 获取实例的审批记录(审批轨迹) |
| GET | /task/{taskId} | taskId | 获取任务的审批记录 |
| POST | /comment | instanceId, taskId(可选), userId, userName, content, attachmentUrls(可选) | 添加评论 |
| POST | /copy | instanceId, taskId(可选), operatorId, operatorName, targetUserIds, targetUserNames, reason(可选) | 抄送 |
NotificationService 通知类型
public enum NotificationType {
URGENT, // 催办
COPY, // 抄送
APPROVE, // 审批通知(待办/通过)
REJECT, // 驳回通知
TRANSFER, // 转办通知
DELEGATE, // 委托通知
FINISH, // 流程结束通知
COMMENT, // 评论通知
ANNOUNCEMENT // 通知公告
}通知触发时机
通知由 SimpleFlowEngine 在审批各环节自动调用 NotificationService 发送:
| 触发时机 | 调用方法 | 通知类型 | 接收人 |
|---|---|---|---|
| 创建审批任务 | sendTodoNotification | APPROVE | 审批人 |
| 流程启动/终态抄送节点 | sendCopyNotification | COPY | 抄送人列表 |
| 催办 | sendUrgentNotification | URGENT | 当前审批人 |
| 审批通过/驳回 | sendApprovalNotification | APPROVE/REJECT | 发起人 |
| 转办 | sendTransferNotification | TRANSFER | 原审批人 |
| 委托 | sendDelegateNotification | DELEGATE | 原审批人 + 新审批人 |
| 任务完成 | sendTaskFollowNotification | TASK_FOLLOW | 任务关注者 |
sendTodoNotification(待办通知)
创建审批任务时自动发送:
public void sendTodoNotification(Long taskId, String taskName,
Long assigneeId, String assigneeName, String instanceTitle) {
Notification notification = new Notification();
notification.setUserId(assigneeId);
notification.setUserName(assigneeName);
notification.setType(NotificationType.APPROVE.name());
notification.setCategory("bpm");
notification.setRelatedId(taskId);
notification.setRelatedType("task");
notification.setStatus("unread");
notification.setTitle("【待办】您有新的审批任务:" + taskName);
notification.setContent(String.format(
"流程「%s」的任务「%s」需要您审批。\n创建时间:%s",
instanceTitle, taskName,
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))
));
saveNotification(notification);
}调用位置:SimpleFlowEngine.createTask() 方法中,每创建一个任务后调用。
sendCopyNotification(抄送通知)
流程启动时遇到 copy 节点,或流程终态时发送结果抄送:
public void sendCopyNotification(Long instanceId, String instanceTitle,
List<Long> ccUserIds, Long operatorId, String operatorName, String comment) {
for (Long userId : ccUserIds) {
SysUser user = userMapper.selectById(userId);
Notification notification = new Notification();
notification.setUserId(userId);
notification.setType(NotificationType.COPY.name());
notification.setCategory("bpm");
notification.setRelatedId(instanceId);
notification.setRelatedType("instance");
notification.setTitle("【抄送】流程抄送:" + instanceTitle);
notification.setContent(String.format(
"%s 将流程「%s」抄送给您。\n附言:%s\n抄送时间:%s",
operatorName, instanceTitle, comment,
LocalDateTime.now().format(...)
));
saveNotification(notification);
}
}sendUrgentNotification(催办通知)
public void sendUrgentNotification(Long taskId, String taskName,
Long assigneeId, String assigneeName, Long operatorId, String operatorName) {
Notification notification = new Notification();
notification.setUserId(assigneeId);
notification.setType(NotificationType.URGENT.name());
notification.setTitle("【催办】您有待处理任务:" + taskName);
notification.setContent(String.format(
"%s 催促您处理任务「%s」,请尽快处理。\n催办时间:%s",
operatorName, taskName,
LocalDateTime.now().format(...)
));
saveNotification(notification);
}sendApprovalNotification(审批结果通知)
审批通过或驳回时通知发起人:
public void sendApprovalNotification(Long instanceId, String instanceTitle,
Long startUserId, String action, String approverName, String opinion) {
Notification notification = new Notification();
notification.setUserId(startUserId);
notification.setRelatedId(instanceId);
if ("approve".equals(action)) {
notification.setType(NotificationType.APPROVE.name());
notification.setTitle("【审批通过】流程已通过:" + instanceTitle);
notification.setContent(String.format(
"您的流程「%s」已由 %s 审批通过。\n审批意见:%s\n审批时间:%s",
instanceTitle, approverName, opinion, ...
));
} else {
notification.setType(NotificationType.REJECT.name());
notification.setTitle("【审批驳回】流程被驳回:" + instanceTitle);
notification.setContent(String.format(
"您的流程「%s」已被 %s 驳回。\n驳回原因:%s\n驳回时间:%s",
instanceTitle, approverName, opinion, ...
));
}
saveNotification(notification);
}sendTransferNotification(转办通知)
public void sendTransferNotification(Long taskId, String taskName,
Long fromUserId, String fromUserName, String toUserName, String reason) {
Notification notification = new Notification();
notification.setUserId(fromUserId);
notification.setType(NotificationType.TRANSFER.name());
notification.setTitle("【转办】任务被转办:" + taskName);
notification.setContent(String.format(
"您待处理的任务「%s」已被转办给 %s。\n原因:%s\n转办时间:%s",
taskName, toUserName, reason, ...
));
saveNotification(notification);
}sendDelegateNotification(委托通知)
委托时同时通知原审批人和新审批人:
public void sendDelegateNotification(Long taskId, String taskName,
Long fromUserId, String fromUserName, Long toUserId, String toUserName, String reason) {
// 1. 通知原审批人
Notification fromNotification = new Notification();
fromNotification.setUserId(fromUserId);
fromNotification.setTitle("【委托】任务被委托:" + taskName);
fromNotification.setContent("您待处理的任务已被委托给 " + toUserName);
saveNotification(fromNotification);
// 2. 通知新审批人
Notification toNotification = new Notification();
toNotification.setUserId(toUserId);
toNotification.setTitle("【委托】您有新任务:" + taskName);
toNotification.setContent(fromUserName + " 将任务委托给您");
saveNotification(toNotification);
}sendTaskFollowNotification(任务关注通知)
任务被关注且完成时通知关注者:
public void sendTaskFollowNotification(Long taskId, String taskName,
Long userId, String userName, String action) {
Notification notification = new Notification();
notification.setUserId(userId);
notification.setType("TASK_FOLLOW");
notification.setTitle("【关注】您关注的任务已完成:" + taskName);
notification.setContent(String.format(
"您关注的流程任务「%s」已被处理。\n操作:%s\n时间:%s",
taskName, action, ...
));
saveNotification(notification);
}NotificationController 端点一览
基础路径:/api/notification
| HTTP | 路径 | 参数 | 说明 |
|---|---|---|---|
| GET | /list | userId(可选), status(可选), limit(默认 20) | 获取通知列表 |
| GET | /unread-count | userId(可选) | 获取未读通知数量 |
| PUT | /read/{id} | id | 标记单条通知为已读 |
| PUT | /read-all | userId(可选) | 全部标记为已读 |
| DELETE | /{id} | id | 删除通知 |
| POST | /announcement/publish | Body: AnnouncementRequest | 发布公告 |
通知存储实现
private void saveNotification(Notification notification) {
jdbcTemplate.update(
"INSERT INTO sys_notification (user_id, user_name, title, content, type, category, " +
"related_id, related_type, status, read_time, create_time, update_time, deleted) " +
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0)",
notification.getUserId(),
notification.getUserName(),
notification.getTitle(),
notification.getContent(),
notification.getType(),
notification.getCategory(),
notification.getRelatedId(),
notification.getRelatedType(),
notification.getStatus(),
notification.getReadTime(),
notification.getCreateTime(),
notification.getUpdateTime()
);
}通知查询实现
public List<Notification> getUserNotifications(Long userId, String status, int limit) {
StringBuilder sql = new StringBuilder(
"SELECT id, user_id, user_name, title, content, type, category, " +
"related_id, related_type, status, create_time, read_time " +
"FROM sys_notification WHERE deleted = 0 AND user_id = ?");
List<Object> params = new ArrayList<>();
params.add(userId);
if (status != null && !status.isEmpty()) {
sql.append(" AND status = ?");
params.add(status);
}
sql.append(" ORDER BY create_time DESC LIMIT ?");
params.add(limit);
return jdbcTemplate.query(sql.toString(), (rs, rowNum) -> {
// ... 映射结果 ...
}, params.toArray());
}前端实现
approval.vue(审批记录页)
审批记录页面展示流程实例的完整审批轨迹:
- 调用
GET /bpm/approval/instance/{instanceId}获取审批记录列表 - 按时间顺序展示每个操作:操作人头像、操作类型、审批意见、操作时间
- 支持添加评论(调用
POST /bpm/approval/comment) - 支持手动抄送(调用
POST /bpm/approval/copy)
通知展示
前端通过 GET /api/notification/list 获取通知列表,在页面头部展示未读通知数量(GET /api/notification/unread-count)。

通知全链路
SimpleFlowEngine
│
┌─────────────────┼─────────────────┐
│ │ │
startProcess() completeTask() transferTask()
│ │ │
▼ │ │
┌──────────────────┐ │ │
│ 创建 BpmTask │ │ │
│ (pending) │ │ │
└────────┬─────────┘ │ │
│ │ │
▼ ▼ ▼
┌──────────────────────────────────────────────────────┐
│ NotificationService │
│ │
│ sendTodoNotification -> 审批人收到【待办】通知 │
│ sendCopyNotification -> 抄送人收到【抄送】通知 │
│ sendApprovalNotification -> 发起人收到【通过/驳回】 │
│ sendUrgentNotification -> 审批人收到【催办】通知 │
│ sendTransferNotification -> 原审批人收到【转办】通知 │
│ sendDelegateNotification -> 双方收到【委托】通知 │
│ sendTaskFollowNotification -> 关注者收到【完成】通知 │
└──────────────────────────────────────────────────────┘
│
▼
┌──────────────────┐
│ sys_notification │ (站内通知表)
│ status=unread │
└──────────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ NotificationController │
│ GET /api/notification/list <- 通知列表 │
│ GET /api/notification/unread-count <- 未读数量 │
│ PUT /api/notification/read/{id} <- 标记已读 │
│ PUT /api/notification/read-all <- 全部已读 │
└──────────────────────────────────────────────────────┘操作步骤
1. 查看审批记录
# 获取实例的审批轨迹
curl "http://localhost:52856/api/bpm/approval/instance/50"返回:
{
"code": 200,
"data": [
{
"id": 1,
"instanceId": 50,
"taskId": 80,
"nodeName": "部门主管审批",
"actionType": "APPROVE",
"operatorId": 2,
"operatorName": "李四",
"opinion": "同意,金额合理",
"createTime": "2026-08-06T10:30:00"
}
]
}2. 添加评论
curl -X POST "http://localhost:52856/api/bpm/approval/comment" \
-d "instanceId=50&taskId=80&userId=1&userName=张三&content=请注意供应商资质"3. 查看通知
# 获取通知列表
curl "http://localhost:52856/api/notification/list?limit=20"
# 获取未读数量
curl "http://localhost:52856/api/notification/unread-count"4. 标记已读
# 标记单条已读
curl -X PUT "http://localhost:52856/api/notification/read/1"
# 全部标记已读
curl -X PUT "http://localhost:52856/api/notification/read-all?userId=1"常见问题
通知是站内通知,非实时推送
NotificationService 将通知写入 sys_notification 表,前端通过轮询 GET /api/notification/unread-count 获取未读数量。当前未实现 WebSocket 实时推送,如需实时通知需自行扩展。
抄送通知在流程启动和终态各发送一次
SimpleFlowEngine.startProcess() 在遇到 copy 节点时发送抄送通知。checkAndCompleteInstance() 和驳回时也会通过 sendResultCopyNotifications() 再次发送结果抄送(附带「审批通过」或「审批驳回」前缀)。因此同一个抄送节点可能收到两条通知。
通知中的 category 和 relatedType
| 字段 | 值 | 说明 |
|---|---|---|
category | bpm | BPM 相关通知 |
category | announcement | 公告通知 |
relatedType | task | 关联任务 |
relatedType | instance | 关联流程实例 |
relatedType | announcement | 关联公告 |
status | unread / read | 已读状态 |
ccProcess 中的 NotificationService 实例化问题
BpmInstanceController.ccProcess() 方法中 new NotificationService() 直接实例化了服务(而非注入),导致其内部的 SysUserMapper 和 JdbcTemplate 为 null,会抛出 NPE。这是一个已知 bug,应改为 @Autowired 注入。使用抄送功能时建议通过 BpmApprovalRecordController.addCopy 接口代替。
