372 lines
10 KiB
Markdown
372 lines
10 KiB
Markdown
|
|
# 审批流程测试报告
|
|||
|
|
|
|||
|
|
**测试日期**: 2026-03-01
|
|||
|
|
**测试版本**: feature/debug-feishu-message (commit: 1e2d337)
|
|||
|
|
**测试环境**: 生产环境 (45.153.130.80)
|
|||
|
|
**测试人员**: Claude
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📋 测试环境配置
|
|||
|
|
|
|||
|
|
### 服务器信息
|
|||
|
|
```
|
|||
|
|
IP: 45.153.130.80
|
|||
|
|
域名: uniservice-mobile.hnxinyouhe.com
|
|||
|
|
后端: x-server-backend-prod (PHP 8.5.3)
|
|||
|
|
数据库: 1Panel-mysql-XyxS (MySQL)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 节点配置
|
|||
|
|
```
|
|||
|
|
节点ID: 7
|
|||
|
|
节点名称: 李佳博
|
|||
|
|
节点类型: and_approve (会签)
|
|||
|
|
所属流程: 2 (出差审批)
|
|||
|
|
节点顺序: 0
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 测试用户
|
|||
|
|
```
|
|||
|
|
申请人: 李佳博 (ID: 1)
|
|||
|
|
审批人:
|
|||
|
|
- 陈兰 (ID: 2)
|
|||
|
|
- 陈标 (ID: 3)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🧪 测试场景
|
|||
|
|
|
|||
|
|
### 场景1: 会签流程 - 所有人通过
|
|||
|
|
|
|||
|
|
#### 流程图
|
|||
|
|
```
|
|||
|
|
提交申请 → 审批人1通过 → 审批人2通过 → 订单完成
|
|||
|
|
↓ ↓(pending) ↓(approved)
|
|||
|
|
pending 1/2 已通过 2/2 已通过
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 测试步骤
|
|||
|
|
|
|||
|
|
| 步骤 | 操作 | 参数 | 预期结果 | 实际结果 | 状态 |
|
|||
|
|
|------|------|------|----------|----------|------|
|
|||
|
|
| 1 | 创建审批订单 | `form_data.approvers = [2, 3]`<br>`current_nodes = [7]`<br>`status = "pending"` | 订单ID创建成功 | 订单ID=14 | ✅ |
|
|||
|
|
| 2 | 审批人1通过 | `order_id = 14`<br>`approver_id = 2`<br>`action = "approve"` | 状态=pending<br>已审批=1/2 | 状态=pending<br>已审批=1/2 | ✅ |
|
|||
|
|
| 3 | 审批人2通过 | `order_id = 14`<br>`approver_id = 3`<br>`action = "approve"` | 状态=approved<br>已审批=2/2 | 状态=approved<br>已审批=2/2 | ✅ |
|
|||
|
|
| 4 | 验证审批记录 | `order_id = 14`<br>`action = "approve"` | 2条记录 | 2条记录 | ✅ |
|
|||
|
|
|
|||
|
|
#### 审批记录
|
|||
|
|
```
|
|||
|
|
ID 1: 陈兰 (ID:2) 在节点 7 审批通过
|
|||
|
|
ID 2: 陈标 (ID:3) 在节点 7 审批通过
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 测试代码
|
|||
|
|
```php
|
|||
|
|
// 创建订单
|
|||
|
|
$order = ApprovalOrder::create([
|
|||
|
|
'business_type' => 'business_trip',
|
|||
|
|
'business_id' => 100,
|
|||
|
|
'title' => '会签测试',
|
|||
|
|
'applicant_id' => 1,
|
|||
|
|
'status' => 'pending',
|
|||
|
|
'current_nodes' => [7],
|
|||
|
|
'flow_id' => 2,
|
|||
|
|
'form_data' => ['approvers' => [2, 3]],
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
// 审批人1通过
|
|||
|
|
$service->approve(14, 2);
|
|||
|
|
|
|||
|
|
// 审批人2通过
|
|||
|
|
$service->approve(14, 3);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 场景2: 拒绝流程 - 任一人拒绝
|
|||
|
|
|
|||
|
|
#### 流程图
|
|||
|
|
```
|
|||
|
|
提交申请 → 审批人1拒绝 → 订单结束
|
|||
|
|
↓ ↓(rejected)
|
|||
|
|
pending 流程终止
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 测试步骤
|
|||
|
|
|
|||
|
|
| 步骤 | 操作 | 参数 | 预期结果 | 实际结果 | 状态 |
|
|||
|
|
|------|------|------|----------|----------|------|
|
|||
|
|
| 1 | 创建审批订单 | `form_data.approvers = [2, 3]`<br>`status = "pending"` | 订单ID创建成功 | 订单ID=15 | ✅ |
|
|||
|
|
| 2 | 审批人1拒绝 | `order_id = 15`<br>`approver_id = 2`<br>`action = "reject"`<br>`comment = "测试拒绝"` | 状态=rejected | 状态=rejected | ✅ |
|
|||
|
|
| 3 | 验证审批记录 | `order_id = 15`<br>`action = "reject"` | 1条拒绝记录 | 1条拒绝记录 | ✅ |
|
|||
|
|
|
|||
|
|
#### 审批记录
|
|||
|
|
```
|
|||
|
|
ID 1: 陈兰 (ID:2) 在节点 7 审批拒绝 - 原因: "测试拒绝"
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 测试代码
|
|||
|
|
```php
|
|||
|
|
$order = ApprovalOrder::create([
|
|||
|
|
'business_type' => 'business_trip',
|
|||
|
|
'business_id' => 101,
|
|||
|
|
'title' => '拒绝测试',
|
|||
|
|
'applicant_id' => 1,
|
|||
|
|
'status' => 'pending',
|
|||
|
|
'current_nodes' => [7],
|
|||
|
|
'flow_id' => 2,
|
|||
|
|
'form_data' => ['approvers' => [2, 3]],
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
// 审批人1拒绝
|
|||
|
|
$service->reject(15, 2, '测试拒绝');
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 场景3: 重复操作防护 - 同一人不能重复审批
|
|||
|
|
|
|||
|
|
#### 流程图
|
|||
|
|
```
|
|||
|
|
提交申请 → 审批人1通过 → 审批人1再次通过
|
|||
|
|
↓ ↓(success) ↓(异常)
|
|||
|
|
pending 记录已创建 抛出异常
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 测试步骤
|
|||
|
|
|
|||
|
|
| 步骤 | 操作 | 参数 | 预期结果 | 实际结果 | 状态 |
|
|||
|
|
|------|------|------|----------|----------|------|
|
|||
|
|
| 1 | 创建审批订单 | `form_data.approvers = [2, 3]`<br>`status = "pending"` | 订单ID创建成功 | 订单ID=16 | ✅ |
|
|||
|
|
| 2 | 审批人1第一次通过 | `order_id = 16`<br>`approver_id = 2` | 操作成功 | 操作成功 | ✅ |
|
|||
|
|
| 3 | 审批人1第二次通过 | `order_id = 16`<br>`approver_id = 2` | 抛出异常 | "您已经通过过此申请" | ✅ |
|
|||
|
|
|
|||
|
|
#### 异常信息
|
|||
|
|
```
|
|||
|
|
Exception: 您已经通过过此申请,无需重复操作
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 测试代码
|
|||
|
|
```php
|
|||
|
|
$order = ApprovalOrder::create([
|
|||
|
|
'business_type' => 'business_trip',
|
|||
|
|
'business_id' => 102,
|
|||
|
|
'title' => '重复操作测试',
|
|||
|
|
'applicant_id' => 1,
|
|||
|
|
'status' => 'pending',
|
|||
|
|
'current_nodes' => [7],
|
|||
|
|
'flow_id' => 2,
|
|||
|
|
'form_data' => ['approvers' => [2, 3]],
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
$service->approve(16, 2); // 第一次 - 成功
|
|||
|
|
$service->approve(16, 2); // 第二次 - 异常
|
|||
|
|
} catch (Exception $e) {
|
|||
|
|
// 捕获重复操作异常
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
### 场景4: 撤销流程 - 申请人可撤销
|
|||
|
|
|
|||
|
|
#### 流程图
|
|||
|
|
```
|
|||
|
|
提交申请 → 申请人撤销 → 订单结束
|
|||
|
|
↓ ↓(cancelled)
|
|||
|
|
pending 流程终止
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 测试步骤
|
|||
|
|
|
|||
|
|
| 步骤 | 操作 | 参数 | 预期结果 | 实际结果 | 状态 |
|
|||
|
|
|------|------|------|----------|----------|------|
|
|||
|
|
| 1 | 创建审批订单 | `applicant_id = 1`<br>`status = "pending"` | 订单ID创建成功 | 订单ID=17 | ✅ |
|
|||
|
|
| 2 | 申请人撤销 | `order_id = 17`<br>`user_id = 1` | 状态=cancelled | 状态=cancelled | ✅ |
|
|||
|
|
| 3 | 验证撤销记录 | `order_id = 17`<br>`action = "cancel"` | 1条撤销记录 | 1条撤销记录 | ✅ |
|
|||
|
|
|
|||
|
|
#### 审批记录
|
|||
|
|
```
|
|||
|
|
ID 1: 李佳博 (ID:1) 撤销了申请
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 测试代码
|
|||
|
|
```php
|
|||
|
|
$order = ApprovalOrder::create([
|
|||
|
|
'business_type' => 'business_trip',
|
|||
|
|
'business_id' => 103,
|
|||
|
|
'title' => '撤销测试',
|
|||
|
|
'applicant_id' => 1,
|
|||
|
|
'status' => 'pending',
|
|||
|
|
'current_nodes' => [7],
|
|||
|
|
'flow_id' => 2,
|
|||
|
|
'form_data' => ['approvers' => [2, 3]],
|
|||
|
|
]);
|
|||
|
|
|
|||
|
|
// 申请人撤销
|
|||
|
|
$service->cancel(17, 1);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📊 核心逻辑验证
|
|||
|
|
|
|||
|
|
### 节点完成判断逻辑
|
|||
|
|
|
|||
|
|
**代码位置**: `ApprovalService::isNodeCompleted()`
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
private function isNodeCompleted(int $orderId, int $nodeId): bool
|
|||
|
|
{
|
|||
|
|
$node = ApprovalNode::findOrFail($nodeId);
|
|||
|
|
$records = ApprovalRecord::where('order_id', $orderId)
|
|||
|
|
->where('node_id', $nodeId)
|
|||
|
|
->where('action', 'approve')
|
|||
|
|
->get();
|
|||
|
|
|
|||
|
|
// 从 form_data 获取实际审批人列表
|
|||
|
|
$order = ApprovalOrder::find($orderId);
|
|||
|
|
$formData = $order->form_data ?? [];
|
|||
|
|
$approvers = $formData['approvers'] ?? [];
|
|||
|
|
|
|||
|
|
if (!empty($approvers) && is_array($approvers[0] ?? null) && isset($approvers[0]['id'])) {
|
|||
|
|
$approverIds = array_column($approvers, 'id');
|
|||
|
|
} else {
|
|||
|
|
$approverIds = $approvers;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($node->type === ApprovalNode::TYPE_AND_APPROVE) {
|
|||
|
|
// 会签:需要所有人通过
|
|||
|
|
return count($records) >= count($approverIds);
|
|||
|
|
} elseif ($node->type === ApprovalNode::TYPE_OR_APPROVE) {
|
|||
|
|
// 或签:任一人通过即可
|
|||
|
|
return count($records) >= 1;
|
|||
|
|
} else {
|
|||
|
|
// 单人审批:需要一人通过
|
|||
|
|
return count($records) >= 1;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**验证结果**: ✅ 通过
|
|||
|
|
|
|||
|
|
| 节点类型 | 完成条件 | 测试结果 |
|
|||
|
|
|----------|----------|----------|
|
|||
|
|
| and_approve (会签) | 已审批人数 >= 总审批人数 | ✅ |
|
|||
|
|
| or_approve (或签) | 已审批人数 >= 1 | ⏳ 未测试 |
|
|||
|
|
| approver (单人) | 已审批人数 >= 1 | ⏳ 未测试 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🔔 消息发送功能
|
|||
|
|
|
|||
|
|
### 当前状态
|
|||
|
|
- ⚠️ **飞书消息发送失败** (emoji 编码问题)
|
|||
|
|
|
|||
|
|
### 错误信息
|
|||
|
|
```
|
|||
|
|
ErrCode: 200621;
|
|||
|
|
ErrMsg: parse card json err, please check whether the card json is correct
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 原因分析
|
|||
|
|
PHP 的 `json_encode()` 默认将 emoji 编码成 Unicode 转义序列:
|
|||
|
|
- `🔔` → `\ud83d\udd14`
|
|||
|
|
- `📋` → `\ud83d\udccb`
|
|||
|
|
|
|||
|
|
飞书 API 不接受这种编码格式。
|
|||
|
|
|
|||
|
|
### 待修复项
|
|||
|
|
1. 修改 `FeishuNotificationService` 的 JSON 编码选项
|
|||
|
|
2. 使用 `JSON_UNESCAPED_UNICODE` 标志
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🗄️ 数据库表状态
|
|||
|
|
|
|||
|
|
### feishu_message_log
|
|||
|
|
```sql
|
|||
|
|
CREATE TABLE IF NOT EXISTS feishu_message_log (
|
|||
|
|
id bigint unsigned NOT NULL AUTO_INCREMENT,
|
|||
|
|
action varchar(20) NOT NULL COMMENT '操作类型:send=发送, update=更新, update_card=更新卡片',
|
|||
|
|
order_id bigint unsigned NOT NULL COMMENT '审批订单ID',
|
|||
|
|
node_id bigint unsigned DEFAULT NULL COMMENT '审批节点ID',
|
|||
|
|
user_id bigint unsigned DEFAULT NULL COMMENT '用户ID',
|
|||
|
|
message_id varchar(64) DEFAULT NULL COMMENT '飞书消息ID',
|
|||
|
|
status varchar(10) NOT NULL COMMENT '状态:success=成功, failed=失败',
|
|||
|
|
request_data text DEFAULT NULL COMMENT '请求数据(JSON)',
|
|||
|
|
response_data text DEFAULT NULL COMMENT '响应数据(JSON)',
|
|||
|
|
error_message text DEFAULT NULL COMMENT '错误信息',
|
|||
|
|
created_at timestamp NULL DEFAULT CURRENT_TIMESTAMP,
|
|||
|
|
updated_at timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|||
|
|
PRIMARY KEY (id),
|
|||
|
|
KEY idx_order_id (order_id),
|
|||
|
|
KEY idx_action (action),
|
|||
|
|
KEY idx_status (status),
|
|||
|
|
KEY idx_created_at (created_at)
|
|||
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='飞书消息日志表';
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**状态**: ✅ 已创建并包含 `updated_at` 字段
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ✅ 测试结论
|
|||
|
|
|
|||
|
|
### 通过的测试
|
|||
|
|
1. ✅ 会签流程 - 所有人通过后订单完成
|
|||
|
|
2. ✅ 拒绝流程 - 任一人拒绝后订单结束
|
|||
|
|
3. ✅ 重复操作防护 - 同一人不能重复审批
|
|||
|
|
4. ✅ 撤销流程 - 申请人可撤销申请
|
|||
|
|
5. ✅ 审批记录正确保存
|
|||
|
|
6. ✅ 节点完成判断逻辑正确
|
|||
|
|
|
|||
|
|
### 待解决的问题
|
|||
|
|
1. ⚠️ 飞书消息发送失败 (emoji 编码问题)
|
|||
|
|
2. ⏳ 或签 (or_approve) 场景未测试
|
|||
|
|
3. ⏳ 单人审批 (approver) 场景未测试
|
|||
|
|
4. ⏳ 多节点流转未测试
|
|||
|
|
|
|||
|
|
### 建议
|
|||
|
|
1. 优先修复飞书消息 emoji 编码问题
|
|||
|
|
2. 补充或签和单人审批场景测试
|
|||
|
|
3. 测试多节点流转场景
|
|||
|
|
4. 测试消息卡片更新功能(或签/单人审批完成后)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📝 附录
|
|||
|
|
|
|||
|
|
### 测试文件位置
|
|||
|
|
```
|
|||
|
|
/www/x-server-mobile/docs/TEST_REPORT_APPROVAL_20260301.md
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 相关代码文件
|
|||
|
|
```
|
|||
|
|
backend/app/service/ApprovalService.php # 审批服务
|
|||
|
|
backend/app/service/FeishuNotificationService.php # 飞书通知服务
|
|||
|
|
backend/app/model/ApprovalOrder.php # 审批订单模型
|
|||
|
|
backend/app/model/ApprovalRecord.php # 审批记录模型
|
|||
|
|
backend/app/model/ApprovalNode.php # 审批节点模型
|
|||
|
|
backend/app/model/FeishuMessageLog.php # 消息日志模型
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 测试执行命令
|
|||
|
|
```bash
|
|||
|
|
# 会签流程测试
|
|||
|
|
docker exec x-server-backend-prod php /app/test_approval_logic.php
|
|||
|
|
|
|||
|
|
# 全场景测试
|
|||
|
|
docker exec x-server-backend-prod php /app/test_approval_all.php
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**报告生成时间**: 2026-03-01 09:18:00
|
|||
|
|
**测试执行者**: Claude Code
|
|||
|
|
**审核状态**: 待审核
|