修正上传的逻辑

develop
JING 2 months ago
parent 25687e8b2c
commit 348de5cb19
  1. 33
      src/factory/object/onelevel/UploadRequest.php
  2. 136
      src/http/impl/HFPayHttpClient.php

@ -66,37 +66,6 @@ class UploadRequest
if(empty($this->data['handle_type'])) { if(empty($this->data['handle_type'])) {
throw new ParamsException('上传类型不能为空', 'handle_type'); throw new ParamsException('上传类型不能为空', 'handle_type');
} }
return $this->data;
// 构建 multipart 数据结构
$multipartData = [
[
'name' => 'attach_file', // 这里的 'file' 是接口定义的文件字段名
'contents' => fopen($this->data['attach_file'], 'r'), // 使用 fopen 打开文件流
],
// 可以同时上传其他文本字段
[
'name' => 'attach_no',
'contents' => $this->data['attach_no'],
],
[
'name' => 'trans_type',
'contents' => $this->data['trans_type'],
],
[
'name' => 'attach_type',
'contents' => $this->data['attach_type'],
],
[
'name' => 'attach_desc',
'contents' => $this->data['attach_desc'],
],
[
'name' => 'handle_type',
'contents' => $this->data['handle_type'],
],
];
return $multipartData;
} }
} }

@ -114,7 +114,61 @@ class HFPayHttpClient implements IHttpClient
*/ */
private ISigner $signClient; private ISigner $signClient;
/**
* 准备 Multipart 数据
* 核心原则:文件流只进 multipart,不进 signData;文本字段两者都进
*/
private function _prepareMultipartData(array $body): array
{
$multipart = [];
$signData = [];
foreach ($body as $key => $value) {
// 1. 资源类型(文件流):仅放入 multipart,绝不参与签名
if (is_resource($value)) {
$multipart[] = [
'name' => $key,
'contents' => $value,
];
continue;
}
// 2. 已是 Guzzle Multipart 格式的数组
if (is_array($value) && isset($value['contents'])) {
$multipart[] = $value;
// 仅当 contents 非资源时才参与签名
if (!is_resource($value['contents'])) {
$signData[$key] = (string)$value['contents'];
}
continue;
}
// 3. 普通数组:转 JSON 字符串
if (is_array($value)) {
$jsonStr = json_encode($value, JSON_UNESCAPED_UNICODE);
$multipart[] = ['name' => $key, 'contents' => $jsonStr];
$signData[$key] = $jsonStr;
continue;
}
// 4. NULL 值:转为空字符串(汇付通常要求空值也参与签名)
if ($value === null) {
$multipart[] = ['name' => $key, 'contents' => ''];
$signData[$key] = '';
continue;
}
// 5. 标量类型:强制转为字符串
$strVal = (string)$value;
$multipart[] = ['name' => $key, 'contents' => $strVal];
$signData[$key] = $strVal;
}
return [
'multipart' => $multipart,
'sign_data' => $signData,
];
}
/** /**
* @param string $method * @param string $method
* @param string $uri * @param string $uri
@ -131,64 +185,70 @@ class HFPayHttpClient implements IHttpClient
{ {
$options = $this->getRequestOptions(); $options = $this->getRequestOptions();
if(!empty($query)) { if (!empty($query)) {
$options['query'] = array_filter($query, fn($v) => $v !== null); $options['query'] = array_filter($query, fn($v) => $v !== null);
} }
//添加固定的参数
//版本号 // ✅ 关键修正:固定参数必须在签名之前注入到 $body 中
if(empty($body['version'])) { // 这样无论是 form/json 还是 multipart,都能保证这些字段参与签名
$body['version'] = $this->config['version'] ?? ''; if (empty($body['version'])) {
$body['version'] = $this->config['version'] ?? self::API_VERSION;
} }
if(empty($body['mer_cust_id'])) { if (empty($body['mer_cust_id'])) {
$body['mer_cust_id'] = $this->config['mid'] ?? ''; $body['mer_cust_id'] = $this->config['mid'] ?? '';
} }
if(empty($body['bg_ret_url']) && !empty($this->config['callback'])) { if (empty($body['bg_ret_url']) && !empty($this->config['callback'])) {
$body['bg_ret_url'] = $this->config['callback']; $body['bg_ret_url'] = $this->config['callback'];
} }
//将内容生成签名 if ($format === 'multipart') {
$sign = $this->signClient->sign($body); // 1. 分离文件流和文本参数
$body['check_value'] = $sign; $processed = $this->_prepareMultipartData($body);
// echo '请求参数 #####'; // 2. 使用纯文本参数生成签名(文件流已被排除)
// print_r($body); $sign = $this->signClient->sign($processed['sign_data']);
// echo '请求参数 end';
if(empty($format)) { // 3. 将签名结果作为普通文本字段追加到 multipart
$format = 'form'; $processed['multipart'][] = [
} 'name' => 'check_value',
switch ($format) { 'contents' => $sign,
case 'json': ];
$options['json'] = array_filter($body, fn($v) => $v !== null);
break; $options['multipart'] = $processed['multipart'];
case 'form': } else {
$options['form_params'] = array_filter($body, fn($v) => $v !== null); // 非 multipart 逻辑保持不变
break; $sign = $this->signClient->sign($body);
case 'body': $body['check_value'] = $sign;
$options['body'] = array_filter($body, fn($v) => $v !== null);
break;
// 新增的分支
case 'multipart':
$options['multipart'] = array_filter($body, fn($v) => $v !== null);
break;
}
switch ($format) {
case 'json':
$options['json'] = array_filter($body, fn($v) => $v !== null);
break;
case 'form':
$options['form_params'] = array_filter($body, fn($v) => $v !== null);
break;
case 'body':
$options['body'] = json_encode(array_filter($body, fn($v) => $v !== null), JSON_UNESCAPED_UNICODE);
break;
}
}
try { try {
$resp = $this->client->request($method, $uri, $options); $resp = $this->client->request($method, $uri, $options);
} catch (GuzzleException $e) { } catch (GuzzleException $e) {
throw new ApiException($e->getMessage()); throw new ApiException($e->getMessage());
} }
$response = Response::make($resp);
if(!$response->isSuccess()) { $response = Response::make($resp);
if (!$response->isSuccess()) {
throw new ApiException('汇付反馈状态不正确', $resp->getStatusCode()); throw new ApiException('汇付反馈状态不正确', $resp->getStatusCode());
} }
//验证数据
$body = $response->getBody(); $respBody = $response->getBody();
if(!$this->signClient->verify($body)) { if (!$this->signClient->verify($respBody)) {
throw new ApiException('签名验证未通过'); throw new ApiException('签名验证未通过');
} }
//解码
$decodeBody = $this->signClient->decode($body['check_value']); $decodeBody = $this->signClient->decode($respBody['check_value']);
ErrCodeHelper::parseError($decodeBody['resp_code'], $decodeBody['resp_desc']); ErrCodeHelper::parseError($decodeBody['resp_code'], $decodeBody['resp_desc']);
return Response::tempInit(200, $decodeBody); return Response::tempInit(200, $decodeBody);
} }

Loading…
Cancel
Save