Files
x-server-mobile/docs/APPROVAL_NOTIFICATION_ARCHITECTURE.md
T

549 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 审批与消息模块架构文档
**版本**: 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