docs: 添加审批与消息模块架构文档
This commit is contained in:
@@ -0,0 +1,548 @@
|
|||||||
|
# 审批与消息模块架构文档
|
||||||
|
|
||||||
|
**版本**: 1.0.0
|
||||||
|
**更新时间**: 2026-02-28
|
||||||
|
**状态**: 设计中
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 概述
|
||||||
|
|
||||||
|
系统采用**模块化设计**,审批模块和消息模块是两个独立但可联合使用的核心模块。
|
||||||
|
|
||||||
|
### 设计原则
|
||||||
|
|
||||||
|
1. **模块独立**:各模块可独立部署和使用
|
||||||
|
2. **松耦合**:模块间通过接口通信,不直接依赖
|
||||||
|
3. **可扩展**:支持新增业务场景(如请假、采购、报销等)
|
||||||
|
4. **统一协议**:所有业务场景遵循相同的审批和消息协议
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🏗️ 模块架构
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ 业务场景层 │
|
||||||
|
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||||
|
│ │ 出差审批 │ │ 请假审批 │ │ 采购审批 │ │ 费用报销 │ │
|
||||||
|
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ 审批引擎层(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 |
|
||||||
|
|
||||||
|
### 节点类型
|
||||||
|
|
||||||
|
```php
|
||||||
|
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()` | 判断节点是否完成 |
|
||||||
|
|
||||||
|
### 扩展新业务场景
|
||||||
|
|
||||||
|
**步骤:**
|
||||||
|
|
||||||
|
1. **创建业务模型**(如 `LeaveRequest`)
|
||||||
|
2. **定义流程类型**(`business_type = 'leave'`)
|
||||||
|
3. **实现审批逻辑**
|
||||||
|
- 调用 `ApprovalService::submit()` 创建审批订单
|
||||||
|
- 在 `form_data` 中存储业务数据
|
||||||
|
4. **实现业务逻辑**
|
||||||
|
- 审批通过后的业务处理
|
||||||
|
- 审批拒绝后的业务处理
|
||||||
|
|
||||||
|
**示例:**
|
||||||
|
```php
|
||||||
|
// 请假审批
|
||||||
|
$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. 更新消息(或签/单人场景)
|
||||||
|
- 节点完成后触发
|
||||||
|
- 更新其他审批人的卡片
|
||||||
|
- 移除操作按钮
|
||||||
|
- 记录日志
|
||||||
|
```
|
||||||
|
|
||||||
|
### 扩展新消息类型
|
||||||
|
|
||||||
|
**步骤:**
|
||||||
|
|
||||||
|
1. **定义卡片构建方法**
|
||||||
|
```php
|
||||||
|
private function buildXXXCard(array $params): array
|
||||||
|
{
|
||||||
|
// 返回卡片数据结构
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **添加发送方法**
|
||||||
|
```php
|
||||||
|
public function sendXXXNotification(array $params): bool
|
||||||
|
{
|
||||||
|
// 构建卡片
|
||||||
|
$card = $this->buildXXXCard($params);
|
||||||
|
// 发送消息
|
||||||
|
return $this->sendMessage($feishuUserId, $card);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **添加更新方法**(如需要)
|
||||||
|
```php
|
||||||
|
public function updateXXXCards(...): bool
|
||||||
|
{
|
||||||
|
// 更新逻辑
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔗 模块协作
|
||||||
|
|
||||||
|
### 协作流程
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────┐
|
||||||
|
│ 业务场景(如出差申请) │
|
||||||
|
└─────────────────────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌──────────────────────────────────────┐
|
||||||
|
│ 调用 ApprovalService::submit() │
|
||||||
|
│ - 创建审批订单 │
|
||||||
|
│ - 返回 approval_order │
|
||||||
|
└──────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌──────────────────────────────────────┐
|
||||||
|
│ 调用 FeishuNotificationService │
|
||||||
|
│ - 发送消息给审批人 │
|
||||||
|
│ - 保存消息映射 │
|
||||||
|
│ - 记录日志 │
|
||||||
|
└──────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌──────────────────────────────────────┐
|
||||||
|
│ 用户点击消息卡片操作 │
|
||||||
|
│ - 飞书回调到后端 │
|
||||||
|
│ - 调用 ApprovalService::approve() │
|
||||||
|
└──────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌──────────────────────────────────────┐
|
||||||
|
│ 审批完成后 │
|
||||||
|
│ - 判断是否需要更新其他人的消息 │
|
||||||
|
│ - 或签/单人:调用 updateMessageCards │
|
||||||
|
│ - 会签:等待其他人审批 │
|
||||||
|
└──────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌──────────────────────────────────────┐
|
||||||
|
│ 流程结束 │
|
||||||
|
│ - 通知申请人结果 │
|
||||||
|
└──────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 接口定义
|
||||||
|
|
||||||
|
**审批模块 → 消息模块**
|
||||||
|
|
||||||
|
```php
|
||||||
|
// 发送审批通知
|
||||||
|
$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
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚧 扩展指南
|
||||||
|
|
||||||
|
### 添加新的审批业务场景
|
||||||
|
|
||||||
|
**示例:添加采购审批**
|
||||||
|
|
||||||
|
1. **创建数据库表**
|
||||||
|
```sql
|
||||||
|
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,
|
||||||
|
...
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **创建模型**
|
||||||
|
```php
|
||||||
|
class PurchaseRequest extends Model
|
||||||
|
{
|
||||||
|
public function approvalOrder()
|
||||||
|
{
|
||||||
|
return $this->belongsTo(ApprovalOrder::class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **创建服务**
|
||||||
|
```php
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **创建控制器**
|
||||||
|
```php
|
||||||
|
class PurchaseController
|
||||||
|
{
|
||||||
|
public function submit(Request $request): Response
|
||||||
|
{
|
||||||
|
$result = $this->service->submit($request->post());
|
||||||
|
return $this->success($result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 添加新的消息类型
|
||||||
|
|
||||||
|
**示例:添加催办通知**
|
||||||
|
|
||||||
|
```php
|
||||||
|
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. 流程流转 │
|
||||||
|
│ - 移动到下一节点 或 完成流程 │
|
||||||
|
│ - 通知下一节点审批人 / 通知申请人结果 │
|
||||||
|
└───────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔍 调试与监控
|
||||||
|
|
||||||
|
### 消息日志查询
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 查看最近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` | 错误信息(如有) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 注意事项
|
||||||
|
|
||||||
|
1. **审批人来源**:始终从 `form_data.approvers` 获取,不是从 `node.approvers`
|
||||||
|
2. **消息更新**:仅在或签/单人审批完成时触发
|
||||||
|
3. **防止重复**:审批前检查是否已操作过
|
||||||
|
4. **事务处理**:业务逻辑和审批创建在同一事务中
|
||||||
|
5. **异常处理**:记录所有失败日志,方便排查问题
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 相关文档
|
||||||
|
|
||||||
|
- [数据库结构文档](./DATABASE_SCHEMA.md)
|
||||||
|
- [项目文档](./PROJECT.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**维护人**: 开发团队
|
||||||
|
**最后更新**: 2026-02-28
|
||||||
Reference in New Issue
Block a user