185 lines
4.2 KiB
Markdown
185 lines
4.2 KiB
Markdown
|
|
# 飞书消息 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
|
|||
|
|
**修复状态**: ✅ 完成
|