# 飞书消息 Emoji 编码修复报告 **修复日期**: 2026-03-01 **修复分支**: feature/debug-feishu-message **问题**: 飞书 API 拒绝接受包含 emoji 的消息卡片 --- ## 问题描述 ### 错误信息 ``` ErrCode: 200621; ErrMsg: parse card json err, please check whether the card json is correct ``` ### 根本原因 **双重 JSON 编码问题**: 1. **第一次编码**:`FeishuNotificationService` 先进行 JSON 编码 ```php $content = json_encode(['card' => $card]); ``` 2. **第二次编码**:`HttpClient::httpPost()` 对整个数据再次编码 ```php curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); ``` 3. **结果**:Emoji 被转义成 Unicode 序列 - `🔔` → `\ud83d\udd14` - `📋` → `\ud83d\udccb` - `✅` → `\u2705` 4. **飞书 API**:不接受这种编码格式,返回 200621 错误 --- ## 修复方案 ### 1. HttpClient 统一编码 **文件**: `backend/app/util/HttpClient.php:92` 添加 `JSON_UNESCAPED_UNICODE` 标志: ```php public static function httpPost( string $url, array $data = [], array $headers = [], int $timeout = 0 ): array { // ... // 使用 JSON_UNESCAPED_UNICODE 避免 emoji 被转义 curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data, JSON_UNESCAPED_UNICODE)); // ... } ``` ### 2. FeishuNotificationService 直接传数组 **文件**: `backend/app/service/FeishuNotificationService.php:574` **修改前**: ```php $content = json_encode(['card' => $card]); $response = HttpClient::httpPost( "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=user_id", [ 'receive_id' => $feishuUserId, 'msg_type' => 'interactive', 'content' => $content, // 已编码的字符串 ], ['Authorization: Bearer ' . $token] ); ``` **修改后**: ```php $response = HttpClient::httpPost( "https://open.feishu.cn/open-apis/im/v1/messages?receive_id_type=user_id", [ 'receive_id' => $feishuUserId, 'msg_type' => 'interactive', 'content' => ['card' => $card], // 直接传递数组 ], ['Authorization: Bearer ' . $token] ); ``` ### 3. 同样修复 updateMessageCards 方法 **文件**: `backend/app/service/FeishuNotificationService.php:715` ```php 'content' => ['card' => $updatedCard], // 直接传递数组 ``` --- ## 修复验证 ### JSON 编码测试结果 ```bash $ docker exec x-server-backend php /app/test_emoji_fix.php ``` **测试1: JSON 编码测试** - ✅ PASS: Emoji 保持原样 **测试3: 编码方式对比** ``` 默认编码: {"emoji":"\ud83d\udd14 \u901a\u77e5"} 修复编码: {"emoji":"🔔 通知"} ``` - ✅ PASS: 修复成功 --- ## 影响范围 ### 修复的方法 1. `FeishuNotificationService::sendMessage()` - 发送审批通知消息 2. `FeishuNotificationService::updateMessageCards()` - 更新消息卡片 ### 修复的消息类型 1. 审批通知消息 2. 系统通知消息 3. 结果通知消息 --- ## 部署状态 | 环境 | 状态 | 说明 | |------|------|------| | 开发环境 | ✅ 已部署 | feature/debug-feishu-message 分支 | | 生产环境 | ✅ 已部署 | 45.153.130.80 | --- ## 相关文件 | 文件 | 修改内容 | |------|----------| | `backend/app/util/HttpClient.php` | 添加 `JSON_UNESCAPED_UNICODE` 标志 | | `backend/app/service/FeishuNotificationService.php` | 直接传递数组,不再预先编码 | --- ## 技术说明 ### JSON_UNESCAPED_UNICODE 标志 PHP 的 `json_encode()` 默认会将非 ASCII 字符(包括 emoji)转义成 Unicode 序列。 使用 `JSON_UNESCAPED_UNICODE` 标志后: - Unicode 字符保持原样输出 - Emoji 字符保持原样输出 - 飞书 API 能正确解析 ### 示例对比 ```php $data = ['emoji' => '🔔 通知']; // 默认编码 json_encode($data); // {"emoji":"\ud83d\udd14 \u901a\u77e5"} // 使用 JSON_UNESCAPED_UNICODE json_encode($data, JSON_UNESCAPED_UNICODE); // {"emoji":"🔔 通知"} ``` --- ## 待验证 1. ⏳ 实际发送飞书消息验证(需要有效的 tenant_access_token 和 user_id) 2. ⏳ 验证所有消息类型(审批通知、系统通知、结果通知) 3. ⏳ 验证消息卡片更新功能 --- **报告生成时间**: 2026-03-01 **修复状态**: ✅ 完成