Files
x-server-mobile/docs/TEST_REPORT_APPROVAL_20260301.md
T
root 7598e363f2 docs: 添加审批流程测试报告 (2026-03-01)
- 测试环境: 生产环境 (45.153.130.80)
- 测试场景: 会签、拒绝、重复操作防护、撤销
- 测试结果: 4个场景全部通过
- 待解决问题: 飞书消息 emoji 编码问题
2026-03-01 09:18:44 +08:00

10 KiB
Raw Blame History

审批流程测试报告

测试日期: 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]
current_nodes = [7]
status = "pending"
订单ID创建成功 订单ID=14 ✅
2 审批人1通过 order_id = 14
approver_id = 2
action = "approve"
状态=pending
已审批=1/2
状态=pending
已审批=1/2
✅
3 审批人2通过 order_id = 14
approver_id = 3
action = "approve"
状态=approved
已审批=2/2
状态=approved
已审批=2/2
✅
4 验证审批记录 order_id = 14
action = "approve"
2条记录 2条记录 ✅

审批记录

ID 1: 陈兰 (ID:2) 在节点 7 审批通过
ID 2: 陈标 (ID:3) 在节点 7 审批通过

测试代码

// 创建订单
$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]
status = "pending"
订单ID创建成功 订单ID=15 ✅
2 审批人1拒绝 order_id = 15
approver_id = 2
action = "reject"
comment = "测试拒绝"
状态=rejected 状态=rejected ✅
3 验证审批记录 order_id = 15
action = "reject"
1条拒绝记录 1条拒绝记录 ✅

审批记录

ID 1: 陈兰 (ID:2) 在节点 7 审批拒绝 - 原因: "测试拒绝"

测试代码

$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]
status = "pending"
订单ID创建成功 订单ID=16 ✅
2 审批人1第一次通过 order_id = 16
approver_id = 2
操作成功 操作成功 ✅
3 审批人1第二次通过 order_id = 16
approver_id = 2
抛出异常 "您已经通过过此申请" ✅

异常信息

Exception: 您已经通过过此申请,无需重复操作

测试代码

$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
status = "pending"
订单ID创建成功 订单ID=17 ✅
2 申请人撤销 order_id = 17
user_id = 1
状态=cancelled 状态=cancelled ✅
3 验证撤销记录 order_id = 17
action = "cancel"
1条撤销记录 1条撤销记录 ✅

审批记录

ID 1: 李佳博 (ID:1) 撤销了申请

测试代码

$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()

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

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           # 消息日志模型

测试执行命令

# 会签流程测试
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 审核状态: 待审核