316 lines
9.0 KiB
Plaintext
316 lines
9.0 KiB
Plaintext
|
|
# 服务器响应警告问题分析
|
|||
|
|
|
|||
|
|
## 问题描述
|
|||
|
|
|
|||
|
|
从日志中可以看到,API响应中混入了PHP警告信息:
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
<br />
|
|||
|
|
<b>Warning</b>: fopen(/var/www/wy/log.dat): failed to open stream: No such file or directory in <b>/home/renjianbo/saars/wy/wy/wy/server/application/Interface/libraries/Api/Goods/AddGoodsInfo.php</b> on line <b>85</b><br />
|
|||
|
|
<br />
|
|||
|
|
<b>Warning</b>: fwrite() expects parameter 1 to be resource, bool given in <b>/home/renjianbo/saars/wy/wy/wy/server/application/Interface/libraries/Api/Goods/AddGoodsInfo.php</b> on line <b>86</b><br />
|
|||
|
|
<br />
|
|||
|
|
<b>Warning</b>: fclose() expects parameter 1 to be resource, bool given in <b>/home/renjianbo/saars/wy/wy/wy/server/application/Interface/libraries/Api/Goods/AddGoodsInfo.php</b> on line <b>87</b><br />
|
|||
|
|
{
|
|||
|
|
"status": 0,
|
|||
|
|
"msg": "添加成功",
|
|||
|
|
"data": {...}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 问题原因
|
|||
|
|
|
|||
|
|
### 服务器端问题
|
|||
|
|
1. **日志文件路径不存在**
|
|||
|
|
- 服务器尝试打开日志文件:`/var/www/wy/log.dat`
|
|||
|
|
- 文件或目录不存在,导致 `fopen()` 返回 `false`
|
|||
|
|
|
|||
|
|
2. **错误处理不当**
|
|||
|
|
- PHP代码没有检查 `fopen()` 的返回值
|
|||
|
|
- 直接对 `false` 值调用 `fwrite()` 和 `fclose()`
|
|||
|
|
- PHP警告被输出到HTTP响应中
|
|||
|
|
|
|||
|
|
3. **错误输出配置**
|
|||
|
|
- PHP的 `display_errors` 可能被设置为 `On`
|
|||
|
|
- 导致警告信息输出到响应体中
|
|||
|
|
|
|||
|
|
### 影响分析
|
|||
|
|
|
|||
|
|
#### ✅ 当前状态
|
|||
|
|
- **业务功能正常**:虽然响应中有警告,但JSON数据仍然成功解析
|
|||
|
|
- **状态码正确**:`status: 0` 表示操作成功
|
|||
|
|
- **数据完整**:`data` 字段包含完整的业务数据
|
|||
|
|
|
|||
|
|
#### ⚠️ 潜在风险
|
|||
|
|
1. **JSON解析失败风险**
|
|||
|
|
- 如果警告信息在JSON之前,可能导致Gson解析失败
|
|||
|
|
- 某些严格的JSON解析器可能无法处理混入HTML的响应
|
|||
|
|
|
|||
|
|
2. **响应体污染**
|
|||
|
|
- 响应体包含非JSON内容,增加解析复杂度
|
|||
|
|
- 可能在某些情况下导致解析异常
|
|||
|
|
|
|||
|
|
3. **日志记录失败**
|
|||
|
|
- 服务器端日志无法正常记录
|
|||
|
|
- 影响问题排查和监控
|
|||
|
|
|
|||
|
|
## 解决方案
|
|||
|
|
|
|||
|
|
### 方案一:服务器端修复(推荐)
|
|||
|
|
|
|||
|
|
#### 1. 修复日志文件路径
|
|||
|
|
在服务器端 `AddGoodsInfo.php` 文件中:
|
|||
|
|
|
|||
|
|
```php
|
|||
|
|
// 修改前(第85行)
|
|||
|
|
$logFile = fopen('/var/www/wy/log.dat', 'a');
|
|||
|
|
|
|||
|
|
// 修改后
|
|||
|
|
$logDir = '/var/www/wy/';
|
|||
|
|
$logFile = $logDir . 'log.dat';
|
|||
|
|
|
|||
|
|
// 确保目录存在
|
|||
|
|
if (!is_dir($logDir)) {
|
|||
|
|
mkdir($logDir, 0755, true);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查文件是否可写
|
|||
|
|
$handle = @fopen($logFile, 'a');
|
|||
|
|
if ($handle === false) {
|
|||
|
|
// 记录到系统日志或使用error_log
|
|||
|
|
error_log("无法打开日志文件: $logFile");
|
|||
|
|
// 不输出警告到响应
|
|||
|
|
} else {
|
|||
|
|
// 正常写入日志
|
|||
|
|
fwrite($handle, $logContent);
|
|||
|
|
fclose($handle);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2. 关闭错误输出
|
|||
|
|
在PHP配置或代码中:
|
|||
|
|
```php
|
|||
|
|
// 关闭错误显示(生产环境)
|
|||
|
|
ini_set('display_errors', 0);
|
|||
|
|
ini_set('log_errors', 1);
|
|||
|
|
ini_set('error_log', '/var/log/php_errors.log');
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3. 使用异常处理
|
|||
|
|
```php
|
|||
|
|
try {
|
|||
|
|
$logFile = fopen('/var/www/wy/log.dat', 'a');
|
|||
|
|
if ($logFile === false) {
|
|||
|
|
throw new Exception('无法打开日志文件');
|
|||
|
|
}
|
|||
|
|
fwrite($logFile, $logContent);
|
|||
|
|
fclose($logFile);
|
|||
|
|
} catch (Exception $e) {
|
|||
|
|
// 记录到系统日志,不输出到响应
|
|||
|
|
error_log($e->getMessage());
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 方案二:客户端容错处理(临时方案)
|
|||
|
|
|
|||
|
|
如果暂时无法修改服务器端,可以在客户端添加响应清理:
|
|||
|
|
|
|||
|
|
#### 1. 创建自定义Gson Converter
|
|||
|
|
|
|||
|
|
创建文件:`app/src/main/java/http/CleanResponseConverter.java`
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package http;
|
|||
|
|
|
|||
|
|
import com.google.gson.Gson;
|
|||
|
|
import com.google.gson.TypeAdapter;
|
|||
|
|
import okhttp3.ResponseBody;
|
|||
|
|
import retrofit2.Converter;
|
|||
|
|
|
|||
|
|
import java.io.IOException;
|
|||
|
|
import java.nio.charset.Charset;
|
|||
|
|
|
|||
|
|
import okio.Buffer;
|
|||
|
|
import okio.BufferedSource;
|
|||
|
|
|
|||
|
|
public class CleanResponseConverter<T> implements Converter<ResponseBody, T> {
|
|||
|
|
private final Gson gson;
|
|||
|
|
private final TypeAdapter<T> adapter;
|
|||
|
|
|
|||
|
|
CleanResponseConverter(Gson gson, TypeAdapter<T> adapter) {
|
|||
|
|
this.gson = gson;
|
|||
|
|
this.adapter = adapter;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@Override
|
|||
|
|
public T convert(ResponseBody value) throws IOException {
|
|||
|
|
BufferedSource bufferedSource = value.source();
|
|||
|
|
bufferedSource.request(Long.MAX_VALUE);
|
|||
|
|
Buffer buffer = bufferedSource.buffer();
|
|||
|
|
String responseString = buffer.clone().readString(Charset.forName("UTF-8"));
|
|||
|
|
|
|||
|
|
// 清理PHP警告和HTML标签
|
|||
|
|
responseString = cleanResponse(responseString);
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
return adapter.fromJson(responseString);
|
|||
|
|
} finally {
|
|||
|
|
value.close();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 清理响应中的PHP警告和HTML标签
|
|||
|
|
*/
|
|||
|
|
private String cleanResponse(String response) {
|
|||
|
|
if (response == null) {
|
|||
|
|
return "";
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 移除PHP警告(<br />和<b>Warning</b>标签)
|
|||
|
|
response = response.replaceAll("<br\\s*/?>", "");
|
|||
|
|
response = response.replaceAll("<b>Warning</b>", "");
|
|||
|
|
response = response.replaceAll("</b>", "");
|
|||
|
|
response = response.replaceAll("<b>", "");
|
|||
|
|
|
|||
|
|
// 查找JSON开始位置(第一个{或[)
|
|||
|
|
int jsonStart = -1;
|
|||
|
|
for (int i = 0; i < response.length(); i++) {
|
|||
|
|
char c = response.charAt(i);
|
|||
|
|
if (c == '{' || c == '[') {
|
|||
|
|
jsonStart = i;
|
|||
|
|
break;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 如果找到JSON开始位置,只保留JSON部分
|
|||
|
|
if (jsonStart > 0) {
|
|||
|
|
response = response.substring(jsonStart);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 移除JSON结束后的所有内容
|
|||
|
|
int jsonEnd = response.lastIndexOf('}');
|
|||
|
|
if (jsonEnd > 0 && response.trim().endsWith("}")) {
|
|||
|
|
// JSON以}结尾,保留
|
|||
|
|
} else if (jsonEnd > 0) {
|
|||
|
|
response = response.substring(0, jsonEnd + 1);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return response.trim();
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 2. 创建Converter Factory
|
|||
|
|
|
|||
|
|
创建文件:`app/src/main/java/http/CleanResponseConverterFactory.java`
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
package http;
|
|||
|
|
|
|||
|
|
import com.google.gson.Gson;
|
|||
|
|
import com.google.gson.TypeAdapter;
|
|||
|
|
import com.google.gson.reflect.TypeToken;
|
|||
|
|
import retrofit2.Converter;
|
|||
|
|
import retrofit2.Converter.Factory;
|
|||
|
|
|
|||
|
|
import java.lang.annotation.Annotation;
|
|||
|
|
import java.lang.reflect.Type;
|
|||
|
|
|
|||
|
|
public class CleanResponseConverterFactory extends Factory {
|
|||
|
|
private final Gson gson;
|
|||
|
|
|
|||
|
|
public CleanResponseConverterFactory(Gson gson) {
|
|||
|
|
this.gson = gson;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
@Override
|
|||
|
|
public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, retrofit2.Retrofit retrofit) {
|
|||
|
|
TypeAdapter<?> adapter = gson.getAdapter(TypeToken.get(type));
|
|||
|
|
return new CleanResponseConverter<>(gson, (TypeAdapter<Object>) adapter);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
#### 3. 修改RetrofitServiceManager
|
|||
|
|
|
|||
|
|
在 `app/src/main/java/http/RetrofitServiceManager.java` 中:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// 修改前
|
|||
|
|
.addConverterFactory(GsonConverterFactory.create())
|
|||
|
|
|
|||
|
|
// 修改后
|
|||
|
|
.addConverterFactory(new CleanResponseConverterFactory(new Gson()))
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 问题定位
|
|||
|
|
|
|||
|
|
### 服务器端文件位置
|
|||
|
|
- **文件路径**: `/home/renjianbo/saars/wy/wy/wy/server/application/Interface/libraries/Api/Goods/AddGoodsInfo.php`
|
|||
|
|
- **问题行数**: 第85-87行
|
|||
|
|
|
|||
|
|
### 相关API接口
|
|||
|
|
- **接口**: `AddGoodsInfo` (添加商品信息)
|
|||
|
|
- **请求URL**: `http://101.43.95.130:8030/api/`
|
|||
|
|
- **请求参数**:
|
|||
|
|
- `app`: "Goods"
|
|||
|
|
- `class`: "AddGoodsInfo"
|
|||
|
|
- `sign`: MD5签名
|
|||
|
|
|
|||
|
|
## 测试验证
|
|||
|
|
|
|||
|
|
### 验证服务器端修复
|
|||
|
|
1. 检查日志文件是否存在:`ls -la /var/www/wy/log.dat`
|
|||
|
|
2. 检查目录权限:`ls -ld /var/www/wy/`
|
|||
|
|
3. 测试API响应是否干净(无PHP警告)
|
|||
|
|
|
|||
|
|
### 验证客户端修复
|
|||
|
|
1. 查看日志中响应体是否包含警告
|
|||
|
|
2. 确认JSON解析是否正常
|
|||
|
|
3. 测试业务功能是否正常
|
|||
|
|
|
|||
|
|
## 建议优先级
|
|||
|
|
|
|||
|
|
### 🔴 高优先级(立即处理)
|
|||
|
|
1. **服务器端修复日志文件路径问题**
|
|||
|
|
- 创建目录或修复路径
|
|||
|
|
- 添加错误检查
|
|||
|
|
|
|||
|
|
2. **关闭PHP错误输出到响应**
|
|||
|
|
- 设置 `display_errors = 0`
|
|||
|
|
- 使用 `error_log` 记录错误
|
|||
|
|
|
|||
|
|
### 🟡 中优先级(短期处理)
|
|||
|
|
1. **添加客户端响应清理**
|
|||
|
|
- 实现自定义Converter
|
|||
|
|
- 清理响应中的HTML标签
|
|||
|
|
|
|||
|
|
2. **完善错误处理机制**
|
|||
|
|
- 添加响应验证
|
|||
|
|
- 添加异常处理
|
|||
|
|
|
|||
|
|
### 🟢 低优先级(长期优化)
|
|||
|
|
1. **统一日志记录机制**
|
|||
|
|
- 使用统一的日志服务
|
|||
|
|
- 添加日志轮转
|
|||
|
|
|
|||
|
|
2. **API响应标准化**
|
|||
|
|
- 确保所有API响应格式统一
|
|||
|
|
- 添加响应验证
|
|||
|
|
|
|||
|
|
## 总结
|
|||
|
|
|
|||
|
|
当前问题虽然不影响业务功能,但存在潜在风险。建议:
|
|||
|
|
|
|||
|
|
1. **优先修复服务器端**:从根本上解决问题
|
|||
|
|
2. **客户端添加容错**:作为临时保护措施
|
|||
|
|
3. **完善监控机制**:及时发现类似问题
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
**问题发现时间**: 2026-01-09 15:40:30
|
|||
|
|
**影响范围**: AddGoodsInfo API接口
|
|||
|
|
**严重程度**: 中等(功能正常,但响应格式不规范)
|
|||
|
|
|