21 KiB
21 KiB
审批与消息模块架构文档
版本: 1.0.0 更新时间: 2026-02-28 状态: 设计中
📋 概述
系统采用模块化设计,审批模块和消息模块是两个独立但可联合使用的核心模块。
设计原则
- 模块独立:各模块可独立部署和使用
- 松耦合:模块间通过接口通信,不直接依赖
- 可扩展:支持新增业务场景(如请假、采购、报销等)
- 统一协议:所有业务场景遵循相同的审批和消息协议
🏗️ 模块架构
┌─────────────────────────────────────────────────────────────┐
│ 业务场景层 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 出差审批 │ │ 请假审批 │ │ 采购审批 │ │ 费用报销 │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 审批引擎层(ApprovalService) │
│ - 流程管理 - 节点管理 - 订单管理 - 记录管理 │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 消息服务层(FeishuNotificationService) │
│ - 消息发送 - 卡片构建 - 消息更新 - 日志记录 │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 飞书平台 │
│ - 消息推送 - 卡片交互 - 回调处理 │
└─────────────────────────────────────────────────────────────┘
🔧 审批模块(Approval Module)
核心模型
| 模型 | 说明 | 关键字段 |
|---|---|---|
ApprovalFlow |
审批流程模板 | business_type, name, status |
ApprovalNode |
审批节点 | type, approvers, order |
ApprovalOrder |
审批订单实例 | business_type, business_id, form_data, current_nodes, status |
ApprovalRecord |
审批操作记录 | order_id, node_id, approver_id, action |
节点类型
const TYPE_APPROVER = 'approver'; // 单人审批:1人通过即可
const TYPE_AND_APPROVE = 'and_approve'; // 会签:所有人通过
const TYPE_OR_APPROVE = 'or_approve'; // 或签:1人通过即可
const TYPE_NOTIFY = 'notify'; // 通知节点:无需审批
审批状态流转
draft(草稿)
↓ submit
pending(审批中)
↓
├─→ approved(已通过)
├─→ rejected(已拒绝)
└─→ cancelled(已撤销)
核心方法
| 方法 | 说明 |
|---|---|
submit() |
提交审批 |
approve() |
审批通过 |
reject() |
审批拒绝 |
cancel() |
撤销申请 |
isNodeCompleted() |
判断节点是否完成 |
扩展新业务场景
步骤:
- 创建业务模型(如
LeaveRequest) - 定义流程类型(
business_type = 'leave') - 实现审批逻辑
- 调用
ApprovalService::submit()创建审批订单 - 在
form_data中存储业务数据
- 调用
- 实现业务逻辑
- 审批通过后的业务处理
- 审批拒绝后的业务处理
示例:
// 请假审批
$approvalOrder = $approvalService->submit([
'business_type' => 'leave',
'business_id' => $leaveRequest->id,
'title' => '请假申请 - ' . $user->name,
'applicant_id' => $user->id,
'flow_id' => $flow->id,
'form_data' => [
'reason' => $leaveRequest->reason,
'start_date' => $leaveRequest->start_date,
'end_date' => $leaveRequest->end_date,
'approvers' => $selectedApprovers,
'cc_users' => $selectedCcUsers,
],
]);
📨 消息模块(Notification Module)
核心模型
| 模型 | 说明 | 关键字段 |
|---|---|---|
FeishuMessage |
消息映射表 | order_id, node_id, user_id, message_id, status |
FeishuMessageLog |
消息日志表 | action, order_id, message_id, status, request_data, response_data |
消息类型
| 类型 | 场景 | 卡片样式 |
|---|---|---|
| 审批通知 | 发送给审批人 | 带通过/拒绝按钮 |
| 系统通知 | 发送给抄送人 | 带查看详情按钮 |
| 文本通知 | 自审批场景 | 纯文本,无按钮 |
| 结果通知 | 审批结果通知 | 显示操作结果 |
核心方法
| 方法 | 说明 |
|---|---|
sendApprovalNotification() |
发送审批通知(带操作按钮) |
sendSystemNotification() |
发送系统通知(只读) |
updateMessageCards() |
更新消息卡片(移除按钮) |
buildApprovalCard() |
构建审批卡片 |
buildSystemCard() |
构建系统通知卡片 |
消息生命周期
1. 发送消息
- 调用飞书 API
- 获取 message_id
- 保存映射到 feishu_message
- 记录日志到 feishu_message_log
2. 用户操作
- 点击卡片按钮
- 飞书回调处理
- 执行审批逻辑
3. 更新消息(或签/单人场景)
- 节点完成后触发
- 更新其他审批人的卡片
- 移除操作按钮
- 记录日志
扩展新消息类型
步骤:
- 定义卡片构建方法
private function buildXXXCard(array $params): array
{
// 返回卡片数据结构
}
- 添加发送方法
public function sendXXXNotification(array $params): bool
{
// 构建卡片
$card = $this->buildXXXCard($params);
// 发送消息
return $this->sendMessage($feishuUserId, $card);
}
- 添加更新方法(如需要)
public function updateXXXCards(...): bool
{
// 更新逻辑
}
🔗 模块协作
协作流程
┌─────────────────────────────────────────────────────────────┐
│ 业务场景(如出差申请) │
└─────────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ 调用 ApprovalService::submit() │
│ - 创建审批订单 │
│ - 返回 approval_order │
└──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ 调用 FeishuNotificationService │
│ - 发送消息给审批人 │
│ - 保存消息映射 │
│ - 记录日志 │
└──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ 用户点击消息卡片操作 │
│ - 飞书回调到后端 │
│ - 调用 ApprovalService::approve() │
└──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ 审批完成后 │
│ - 判断是否需要更新其他人的消息 │
│ - 或签/单人:调用 updateMessageCards │
│ - 会签:等待其他人审批 │
└──────────────────────────────────────┘
│
▼
┌──────────────────────────────────────┐
│ 流程结束 │
│ - 通知申请人结果 │
└──────────────────────────────────────┘
接口定义
审批模块 → 消息模块
// 发送审批通知
$notificationService->sendApprovalNotification([
'user_id' => $approverId,
'applicant_id' => $applicantId,
'title' => '新的审批待办',
'content' => '...',
'url' => $detailUrl,
'order_id' => $orderId,
'node_id' => $nodeId,
]);
// 更新消息卡片(或签/单人场景)
$notificationService->updateMessageCards(
$orderId,
$nodeId,
$excludeUserId,
'approved',
$operatorName
);
🚧 扩展指南
添加新的审批业务场景
示例:添加采购审批
- 创建数据库表
CREATE TABLE purchase_request (
id bigint unsigned PRIMARY KEY,
user_id bigint unsigned NOT NULL,
reason varchar(500),
amount decimal(10,2),
status enum('draft', 'pending', 'approved', 'rejected'),
approval_order_id bigint unsigned,
...
);
- 创建模型
class PurchaseRequest extends Model
{
public function approvalOrder()
{
return $this->belongsTo(ApprovalOrder::class);
}
}
- 创建服务
class PurchaseService
{
private $approvalService;
public function submit(array $data): PurchaseRequest
{
return DB::transaction(function () use ($data) {
// 创建采购申请
$purchase = PurchaseRequest::create($data);
// 创建审批订单
$approvalOrder = $this->approvalService->submit([
'business_type' => 'purchase',
'business_id' => $purchase->id,
'title' => '采购申请 - ' . $data['item_name'],
'applicant_id' => $data['user_id'],
'flow_id' => $this->getFlowId(),
'form_data' => [
'item_name' => $data['item_name'],
'amount' => $data['amount'],
'approvers' => $data['approvers'],
'cc_users' => $data['cc_users'],
],
]);
// 关联审批订单
$purchase->approval_order_id = $approvalOrder->id;
$purchase->status = 'pending';
$purchase->save();
// 发送通知
$this->sendNotifications($purchase, $approvalOrder);
return $purchase;
});
}
}
- 创建控制器
class PurchaseController
{
public function submit(Request $request): Response
{
$result = $this->service->submit($request->post());
return $this->success($result);
}
}
添加新的消息类型
示例:添加催办通知
class FeishuNotificationService
{
/**
* 发送催办通知
*/
public function sendReminderNotification(array $params): bool
{
$userId = $params['user_id'];
$card = $this->buildReminderCard($params);
// 保存消息映射(如需要)
if (isset($params['order_id']) && isset($params['node_id'])) {
\app\model\FeishuMessage::saveMessage(
$params['order_id'],
$params['node_id'],
$userId,
$messageId
);
}
return $this->sendMessage($feishuUserId, $card);
}
private function buildReminderCard(array $params): array
{
return [
'header' => [
'title' => ['content' => '⏰ 审批催办', 'tag' => 'plain_text'],
'template' => 'orange',
],
'elements' => [
[
'tag' => 'div',
'text' => [
'content' => $params['content'],
'tag' => 'lark_md',
],
],
[
'tag' => 'action',
'actions' => [
[
'tag' => 'button',
'text' => ['content' => '立即处理', 'tag' => 'plain_text'],
'type' => 'default',
'url' => $params['url'],
],
],
],
],
];
}
}
📊 数据流向图
完整审批流程数据流
用户提交申请
↓
┌───────────────────────────────────────────────────────┐
│ 1. 业务表(business_trip) │
│ - 创建业务记录 │
│ - status = 'draft' │
└───────────────────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────────────┐
│ 2. 审批订单(approval_order) │
│ - business_type = 'business_trip' │
│ - business_id = 业务记录ID │
│ - form_data = { │
│ approvers: [用户选择的审批人], │
│ cc_users: [用户选择的抄送人], │
│ ...业务数据 │
│ } │
│ - current_nodes = [第一个节点ID] │
│ - status = 'pending' │
└───────────────────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────────────┐
│ 3. 消息映射(feishu_message) │
│ - order_id + node_id + user_id → message_id │
│ - 每个审批人一条记录 │
└───────────────────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────────────┐
│ 4. 飞书平台 │
│ - 发送卡片消息给审批人 │
└───────────────────────────────────────────────────────┘
↓
审批人操作
↓
┌───────────────────────────────────────────────────────┐
│ 5. 审批记录(approval_record) │
│ - order_id, node_id, approver_id │
│ - action = 'approve' 或 'reject' │
└───────────────────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────────────┐
│ 6. 节点完成判断 │
│ - 检查 form_data.approvers(实际选择的审批人) │
│ - 节点类型决定完成条件 │
│ - 或签/单人:立即完成,触发消息更新 │
│ - 会签:等待所有人 │
└───────────────────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────────────┐
│ 7. 消息更新(或签/单人场景) │
│ - 更新其他审批人的消息卡片 │
│ - 移除操作按钮 │
│ - 标记消息状态 = 'updated' │
└───────────────────────────────────────────────────────┘
↓
┌───────────────────────────────────────────────────────┐
│ 8. 流程流转 │
│ - 移动到下一节点 或 完成流程 │
│ - 通知下一节点审批人 / 通知申请人结果 │
└───────────────────────────────────────────────────────┘
🔍 调试与监控
消息日志查询
# 查看最近50条日志
curl "http://localhost:8787/debug/message-logs?debug=1&limit=50"
# 查看指定订单的日志
curl "http://localhost:8787/debug/message-logs?debug=1&order_id=123"
# 直接查询数据库
docker exec 1Panel-mysql-409s mysql -uroot -pmysql_xEsdPa xserver -e "
SELECT * FROM feishu_message_log
WHERE order_id = 123
ORDER BY created_at DESC;
"
日志字段说明
| 字段 | 说明 |
|---|---|
action |
send(发送)、update(更新)、update_card(更新卡片) |
status |
success(成功)、failed(失败) |
request_data |
发送的数据(JSON) |
response_data |
飞书返回的数据(JSON) |
error_message |
错误信息(如有) |
📝 注意事项
- 审批人来源:始终从
form_data.approvers获取,不是从node.approvers - 消息更新:仅在或签/单人审批完成时触发
- 防止重复:审批前检查是否已操作过
- 事务处理:业务逻辑和审批创建在同一事务中
- 异常处理:记录所有失败日志,方便排查问题
📚 相关文档
维护人: 开发团队 最后更新: 2026-02-28