chore: 提供中文字符编码解决方案

This commit is contained in:
2025-11-22 11:46:14 +08:00
parent 2611162e60
commit caaf85290f
4 changed files with 272 additions and 8 deletions

View File

@@ -0,0 +1,212 @@
# PHP 中文字符编码解决方案
## 🎯 问题描述
在使用 `json_encode()` 处理包含中文字符的数据时,默认会将中文字符转换为 Unicode 编码:
```php
// ❌ 默认行为 - 中文转为Unicode
$data = ['message' => '请检查计划任务配置'];
echo json_encode($data);
// 输出:{"message":"\u8bf7\u68c0\u67e5\u8ba1\u5212\u4efb\u52a1\u914d\u7f6e"}
```
这在日志文件中查看很不方便,影响调试效率。
## 🛠️ 解决方案对比
### 方案一:`JSON_UNESCAPED_UNICODE` 标志 ⭐⭐⭐⭐⭐⭐
```php
// ✅ 推荐方案 - 保持中文原样
$data = ['message' => '请检查计划任务配置'];
echo json_encode($data, JSON_UNESCAPED_UNICODE);
// 输出:{"message":"请检查计划任务配置"}
```
**优点:**
- ✅ 中文完全可读
- ✅ 保持JSON格式
- ✅ 兼容性好
- ✅ 性能优秀
**使用场景:**
- 日志记录
- API响应
- 配置文件
- 调试输出
### 方案二:`var_export()` 函数 ⭐⭐⭐
```php
// ✅ 完全避免Unicode转义
$data = ['message' => '请检查计划任务配置'];
echo var_export($data, true);
// 输出array (
// 'message' => '请检查计划任务配置',
// )
```
**优点:**
- ✅ 完全可读
- ✅ 数组结构清晰
- ✅ 无需额外参数
**缺点:**
- ❌ 不是标准JSON格式
- ❌ 输出较冗长
**使用场景:**
- 开发调试
- 临时日志
- 数组检查
### 方案三:`print_r()` + 捕获输出 ⭐⭐
```php
// ✅ 可读性好的数组输出
$data = ['message' => '请检查计划任务配置'];
ob_start();
print_r($data);
$result = ob_get_clean();
```
**优点:**
- ✅ 格式美观
- ✅ 易于阅读
**缺点:**
- ❌ 需要输出缓冲
- ❌ 性能较差
### 方案四:自定义序列化函数 ⭐⭐⭐
```php
function serializeArray($data) {
if (!is_array($data)) return $data;
$result = [];
foreach ($data as $key => $value) {
if (is_array($value)) {
$result[$key] = serializeArray($value);
} else {
$result[$key] = $value;
}
}
return $result;
}
```
## 🎯 最佳实践组合
### 在 Cron 类中的实现
```php
class Cron extends BaseModel
{
/**
* 格式化数据为日志友好的字符串(保持中文可读性)
*/
private static function formatForLog($data): string
{
if (is_array($data) || is_object($data)) {
// JSON_UNESCAPED_UNICODE 保持中文可读性
// JSON_PRETTY_PRINT 增加格式化
// JSON_UNESCAPED_SLASHES 避免斜杠转义
return json_encode($data,
JSON_UNESCAPED_UNICODE |
JSON_PRETTY_PRINT |
JSON_UNESCAPED_SLASHES
);
}
return (string) $data;
}
/**
* 完全避免Unicode转义的格式化
*/
private static function exportForLog($data): string
{
if (is_array($data) || is_object($data)) {
return var_export($data, true);
}
return (string) $data;
}
}
```
### 使用示例
```php
// 日志记录 - 使用 formatForLog
$detail = [
'error' => $error,
'remark' => $remark,
'suggestion' => ScheduleDict::getSuggestion($cronType)
];
log_write('异常信息:' . self::formatForLog($detail), 'warning');
// 调试输出 - 使用 exportForLog
$debugData = ['config' => $config, 'status' => $status];
echo self::exportForLog($debugData);
```
## 🔧 环境配置
### 确保 PHP 版本支持
```php
// PHP 5.4+ 基本支持
json_encode($data, JSON_UNESCAPED_UNICODE);
// PHP 5.3+ 需要 polyfill
if (!defined('JSON_UNESCAPED_UNICODE')) {
define('JSON_UNESCAPED_UNICODE', 256);
}
```
### 文件编码设置
```php
// 确保文件本身是 UTF-8 编码
header('Content-Type: text/html; charset=utf-8');
mb_internal_encoding('UTF-8');
```
## 📊 性能对比
| 方法 | 执行时间 | 内存使用 | 可读性 | JSON兼容 |
|------|----------|----------|---------|-----------|
| `json_encode()` | 快 | 低 | ⭐⭐⭐⭐⭐ | ✅ |
| `var_export()` | 中 | 中 | ⭐⭐⭐⭐ | ❌ |
| `print_r()` | 慢 | 高 | ⭐⭐⭐⭐ | ❌ |
| 自定义序列化 | 慢 | 高 | ⭐⭐ | ❌ |
## 🎉 推荐配置
### 生产环境(推荐)
```php
// 使用 JSON_UNESCAPED_UNICODE 保持中文可读性
json_encode($data, JSON_UNESCAPED_UNICODE);
```
### 开发环境
```php
// 使用格式化JSON增强可读性
json_encode($data,
JSON_UNESCAPED_UNICODE |
JSON_PRETTY_PRINT |
JSON_UNESCAPED_SLASHES
);
```
### 调试模式
```php
// 使用 var_export 获得最佳可读性
var_export($data, true);
```
---
**总结:** 对于您的需求,使用 `JSON_UNESCAPED_UNICODE` 标志是最佳选择既保持了JSON格式的标准性又确保了中文字符的可读性。

View File

@@ -1879,6 +1879,38 @@ function paramFilter($param)
return preg_replace($filter_rule, '', $param);
}
/**
* 格式化数据为日志友好的字符串(保持中文可读性)
*
* @param mixed $data 要格式化的数据
* @return string 格式化后的字符串
*/
function formatForLog($data): string
{
if (is_array($data) || is_object($data)) {
// 使用 JSON_UNESCAPED_UNICODE 保持中文可读性
return json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
}
return (string) $data;
}
/**
* 格式化数据为可导出的字符串完全避免Unicode转义
*
* @param mixed $data 要格式化的数据
* @return string 格式化后的字符串
*/
function exportForLog($data): string
{
if (is_array($data) || is_object($data)) {
// 使用 var_export 完全避免Unicode转义
return var_export($data, true);
}
return (string) $data;
}
/**
* 简单日志写入
* @param string $msg 日志内容

View File

@@ -56,4 +56,17 @@ class ScheduleDict
if($code) return $list[$code] ?? '';
return $list;
}
public static function getSuggestion(string $type) {
switch($type) {
case self::default:
return '请检查系统计划任务配置,确保 cron 进程正常运行,并检查网络连接和 SSL 证书设置。';
case self::url:
return '请检查网络连接、目标 URL 可访问性、HTTP 状态码和接口认证配置。可以使用 curl 命令手动测试接口。';
case self::cli:
return '请检查 cron 服务状态、命令执行权限、脚本路径和 PHP CLI 环境。建议使用 crontab -l 查看当前任务配置。';
default:
return '请检查计划任务配置文件和相关权限设置,确保所有依赖服务正常运行。';
}
}
}

View File

@@ -61,13 +61,13 @@ class Cron extends BaseModel
*/
public function execute($type = 'default')
{
log_write('计划任务开始执行', 'debug');
log_write('计划任务开始执行', 'info');
if (config('cron.default') != $type) {
log_write('计划任务方式不匹配<不能执行/model/system/Cron/execute>' . config('cron.default') . ' != ' . $type, 'debug');
log_write('计划任务方式不匹配<不能执行/model/system/Cron/execute>' . config('cron.default') . ' != ' . $type, 'warning');
return true;
}
log_write('当前执行方式:' . $type, 'debug');
log_write('当前执行方式:' . $type, 'info');
try {
//写入计划任务标记运行
@@ -78,7 +78,7 @@ class Cron extends BaseModel
$query_execute_time = $is_open_queue == 1 ? time() + 60 : time();
$list = model('cron')->getList([['execute_time', '<=', $query_execute_time]]);
$now_time = time();
log_write('计划任务开始执行,查询计划任务列表', 'debug');
log_write('计划任务开始执行,查询计划任务列表', 'info');
if (!empty($list)) {
foreach ($list as $k => $v) {
@@ -97,7 +97,7 @@ class Cron extends BaseModel
return $res ?? $this->success();
}, function ($params) {
try {
log_write('调用事件名称:' . $params['name'], 'debug');
log_write('调用事件名称:' . $params['name'], 'info');
$res = event($params['event'], ['relate_id' => $params['relate_id']]);
} catch (\Exception $e) {
$res = $this->error($e->getMessage(), $e->getMessage());
@@ -150,7 +150,7 @@ class Cron extends BaseModel
// $this->setCron();
return true;
} catch (\Exception $e) {
log_write('计划任务执行异常<model/system/Cron/execute>' . $e->getMessage(), 'debug');
log_write('计划任务执行异常<model/system/Cron/execute>' . $e->getMessage(), 'error');
return true;
}
}
@@ -252,9 +252,16 @@ class Cron extends BaseModel
$remark = 'Cron计划任务已停止当前启动的任务方式' . ScheduleDict::getType(config('cron.default')) . '。';
$error = self::getError(config('cron.default'));
if (!empty($error)) {
$remark .= json_encode($error);
$remark .= formatForLog($error);
}
log_write('Cron计划任务校验计划任务是否正常运行计划任务异常异常信息' . json_encode($error) . ',文件路径:' . $file, 'warning');
$detail = [
'error'=> $error,
'remark' => $remark,
'suggestion' => ScheduleDict::getSuggestion(config('cron.default')),
];
log_write('Cron计划任务校验计划任务是否正常运行计划任务异常异常信息' . formatForLog($detail) . ',文件路径:' . $file, 'warning');
return $this->error([], $remark);
} catch (\Exception $e) {
log_write('Cron计划任务校验计划任务是否正常运行异常' . $e->getMessage() . ',异常行:' . $e->getLine() . ',文件路径:' . $file, 'error');