IcbcQueue.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. <?php
  2. namespace app\command;
  3. use think\console\Command;
  4. use think\console\Input;
  5. use think\console\Output;
  6. use think\Db;
  7. /**
  8. * 工行积分队列处理命令
  9. * 命令行运行: php think icbc_queue
  10. */
  11. class IcbcQueue extends Command
  12. {
  13. protected function configure()
  14. {
  15. $this->setName('icbc_queue')
  16. ->setDescription('处理工行积分推送队列');
  17. }
  18. protected function execute(Input $input, Output $output)
  19. {
  20. $output->writeln("开始处理工行积分队列...");
  21. // 获取待处理的任务
  22. $tasks = Db::name('icbc_queue')
  23. ->where('status', 0)
  24. ->where('retry_count', '<', 3)
  25. ->limit(50)
  26. ->select();
  27. if (empty($tasks)) {
  28. $output->writeln("没有待处理的任务");
  29. return;
  30. }
  31. $output->writeln("找到 " . count($tasks) . " 个待处理任务");
  32. foreach ($tasks as $task) {
  33. try {
  34. // 更新状态为处理中
  35. Db::name('icbc_queue')->where('id', $task['id'])->update([
  36. 'status' => 1,
  37. 'updatetime' => time()
  38. ]);
  39. $output->writeln("处理任务 ID: {$task['id']}, 手机号: {$task['mobile_phone']}, 积分: {$task['integral_value']}");
  40. // 调用工行接口
  41. $result = $this->callIcbcApi($task, $output);
  42. // 先检查返回值是否为数组
  43. if (!is_array($result)) {
  44. $output->writeln("✗ 任务 {$task['id']} 返回值异常: " . var_export($result, true));
  45. // 返回值异常,标记为失败
  46. $retry_count = $task['retry_count'] + 1;
  47. $status = $retry_count >= 3 ? 3 : 0;
  48. Db::name('icbc_queue')->where('id', $task['id'])->update([
  49. 'status' => $status,
  50. 'retry_count' => $retry_count,
  51. 'result' => json_encode(['error' => '返回值不是数组', 'raw' => var_export($result, true)], JSON_UNESCAPED_UNICODE),
  52. 'error_msg' => '接口返回值格式错误',
  53. 'updatetime' => time()
  54. ]);
  55. continue;
  56. }
  57. // 根据结果更新状态
  58. if (isset($result['return_code']) && $result['return_code'] == 0) {
  59. // 成功
  60. Db::name('icbc_queue')->where('id', $task['id'])->update([
  61. 'status' => 2,
  62. 'result' => json_encode($result, JSON_UNESCAPED_UNICODE),
  63. 'updatetime' => time()
  64. ]);
  65. $output->writeln("✓ 任务 {$task['id']} 处理成功");
  66. } else {
  67. // 失败,增加重试次数
  68. $retry_count = $task['retry_count'] + 1;
  69. $status = $retry_count >= 3 ? 3 : 0;
  70. $error_msg = isset($result['return_msg']) ? $result['return_msg'] : '未知错误';
  71. Db::name('icbc_queue')->where('id', $task['id'])->update([
  72. 'status' => $status,
  73. 'retry_count' => $retry_count,
  74. 'result' => json_encode($result, JSON_UNESCAPED_UNICODE),
  75. 'error_msg' => $error_msg,
  76. 'updatetime' => time()
  77. ]);
  78. $output->writeln("✗ 任务 {$task['id']} 处理失败: " . $error_msg);
  79. }
  80. } catch (\Exception $e) {
  81. // 异常处理
  82. Db::name('icbc_queue')->where('id', $task['id'])->update([
  83. 'status' => 0,
  84. 'retry_count' => $task['retry_count'] + 1,
  85. 'error_msg' => $e->getMessage(),
  86. 'updatetime' => time()
  87. ]);
  88. $output->writeln("✗ 任务 {$task['id']} 异常: " . $e->getMessage());
  89. }
  90. // 避免请求过快
  91. usleep(100000); // 0.1秒
  92. }
  93. $output->writeln("队列处理完成");
  94. }
  95. /**
  96. * 调用工行接口
  97. */
  98. private function callIcbcApi($task, $output)
  99. {
  100. include_once EXTEND_PATH . 'icbc/DefaultIcbcClient.php';
  101. // 生成16位唯一序列号
  102. $fSeqNo = time() . str_pad(mt_rand(0, 999999), 6, '0', STR_PAD_LEFT);
  103. // 构建业务参数 (PHP 8+ 兼容)
  104. $biz_content = [
  105. 'fSeqNo' => $fSeqNo,
  106. 'corpCode' => 'xingfulishequ',
  107. 'mobilePhone' => $task['mobile_phone'] ?? '',
  108. 'integralValue' => (string)($task['integral_value'] ?? 0),
  109. 'integralType' => $task['integral_type'] ?? '',
  110. 'remark1' => $task['nickname'] ?? '',
  111. ];
  112. // 工行RSA密钥
  113. $public_key = 'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCwFgHD4kzEVPdOj03ctKM7KV+16bWZ5BMNgvEeuEQwfQYkRVwI9HFOGkwNTMn5hiJXHnlXYCX+zp5r6R52MY0O7BsTCLT7aHaxsANsvI9ABGx3OaTVlPB59M6GPbJh0uXvio0m1r/lTW3Z60RU6Q3oid/rNhP3CiNgg0W6O3AGqwIDAQAB';
  114. $private_key = 'MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCR8/ZvKPAdZzsyvapySvztQm56s1N59ynKMOWpUbK4c5MNWMl+q3dvsp+UiSAx2TAeGkYfW3W6nO/3Y0hAZki99fmuzpPujEeHhs79HNwGZYQjN71Vck2JeflTq8jpL+9/up0Kz2nbwtZDMKTTEgbfNeO24gV1bmvD2kQ9j66RIyuXSDwQbVbQfl6LiqKoJm3rbtsbwX1Ytc0/Szslyor1VdPZWnHDMm3m78Hqu7X3vL6K7fAW/4FVbeKV7vWjvyiTQfETmuADqMdsqV5YeqUZWE/Dnzg+6JV/3L9nJ8f+/mPlh8k1h1oW035GrADKFKf1M2ujKKeHICnj7qKCdBeNAgMBAAECggEAHkh+U2YtHAy1Tbvox7ojbJ8iCTd4FJBiDV/D5zPaX0crtdM8S5oMOBLZ5ZnmIjGsODK/ZfY2ITg62huxfBs88J0+5zRZoV9d4BLqk74PMQyTNDN2h2omCGZUgzXbg/a8PMZdm0aZ8k0k4+AN8vWEk3+89c9Dzq/QkFyTWCqdz+Mp9NkcOjA255kj5/D1q9Zj0x9VcmKg3oTtrKL/dkspUxVaMKXcpo6J4AszC05tT3N0SNLhcq51I6B9QdbYsYCkP9whPNYIl/y4dN4QnNEivQzA5/ltr5DDQZc7Xke1+SpJN+ylBOBJ8yvNoDnuxx0xEWuNJ5bUzyri/DNjZRKNQQKBgQDKkxn5R4Q3rChI/KVIXHMecTs9fXN5pbnA8UKU6ZeTl+wHffxwRieHZJJp1nDcQ5i8YXCtqEJcJGFn3T0rEjVCJ/RVHclFO+TjYaK9HtyNJLPrvKmAzjCzV1yPfe4qmpgJddzZI7Vii2uFgklixvhkoY74hwSJtvLPTO0p0xrYdQKBgQC4cgfpCMLYlaHQGz+dAEf4IewQpwBGn7XShCpiXGFciGZZEIzvJMzXP7yo8pDMHQbB4kQsTRRG2fcdHGWI5VQGHPqG5O0tUueKGUlSg7j8Y/Pp8ZrBGSrlilAf17I/u9MC7Xe2ZRVGNgPDYAyjMEGmClI0n2+aN6b4CFVBjYmfuQKBgEKO9KDIE7QrF41rnW7aGWTuNVWty2wzvIWdf4/n9EqlRwLrLS9CjahZrhWiRLDKcPusVFZqi2s09OAoe/mT4PXcpNX2lHPwCvN+1/allje10HvrIBJXLP8v/BSVftR2uO+azzZ1GhrHzksulKgk0eZWguA7lI0fFEZycxYj65UlAoGAD9p1RZlkLfuGgf2llRgOF4zK3o+MHYXiuep0PioUkECFE4ixpGh0Vtf6nkbjHTgteYK6O1iQsppPfCgRrheQBkp9WhTZMfkbP6p2u+nof4ET2PrUQ16naj1eL655erLpKypADORZVMSVxDhAPdKLAfuHH1DI5ed8qXsF4PGKb7kCgYEAm/d+daT6YsbHDZlJ/J9Q8rRkKmiqj43NGQSHKg6Z6BEDibm8wRmj3Itu1N6XVChuaH+ekJzvUnZ/q1nyYzGvy6bOHYn3ziF9aH7wuhcRZ4qARmKDnzTBLg2QXBK1+400O3LJ+sAH/yuH/Y4hzRE6YMxBQpdYfnlJcIFVimAsT1w=';
  115. // 构建通用请求参数
  116. $data = [
  117. 'app_id' => '10000000000004096993',
  118. 'msg_id' => createUniqueNo('msg', time()),
  119. 'format' => 'json',
  120. 'charset' => 'UTF-8',
  121. 'encrypt_type' => 'AES',
  122. 'sign_type' => 'RSA2',
  123. ];
  124. try {
  125. // 创建工行客户端
  126. $client = new \DefaultIcbcClient(
  127. $data['app_id'],
  128. $private_key,
  129. $data['sign_type'],
  130. $data['charset'],
  131. $data['format'],
  132. $public_key,
  133. '', '', '', ''
  134. );
  135. // 构建请求参数
  136. $request = [
  137. 'serviceUrl' => 'https://gw.dccnet.com.cn:8084/api/mybank/farm/farmplatf/updateVillagerIntegral/V1',
  138. 'method' => 'POST',
  139. 'isNeedEncrypt' => false,
  140. 'extraParams' => null,
  141. 'biz_content' => $biz_content,
  142. ];
  143. // 发送请求
  144. $response = $client->execute($request, $data['msg_id'], '');
  145. // 记录原始响应(用于调试)
  146. $output->writeln(" 原始响应长度: " . strlen($response));
  147. // 检查响应
  148. if (empty($response)) {
  149. return [
  150. 'return_code' => '-1',
  151. 'return_msg' => '接口无响应',
  152. 'raw_response' => $response
  153. ];
  154. }
  155. // 清理响应内容:提取 JSON 部分
  156. // 工行接口可能返回调试信息,需要清理
  157. $clean_response = $this->cleanResponse($response);
  158. // 如果清理后的响应与原始不同,记录
  159. if ($clean_response !== $response) {
  160. $output->writeln(" ⚠ 响应包含非JSON内容,已清理");
  161. }
  162. // 解析响应
  163. $result = json_decode($clean_response, true);
  164. if (json_last_error() !== JSON_ERROR_NONE) {
  165. $output->writeln(" ✗ JSON解析失败: " . json_last_error_msg());
  166. return [
  167. 'return_code' => '-2',
  168. 'return_msg' => 'JSON解析失败: ' . json_last_error_msg(),
  169. 'raw_response' => $response,
  170. 'clean_response' => $clean_response
  171. ];
  172. }
  173. // 写入日志
  174. if (isset($result['return_code'])) {
  175. $this->writeLog($result['return_code'] == 0, $result, $biz_content);
  176. }
  177. return $result;
  178. } catch (\Exception $e) {
  179. return ['return_code' => '-99', 'return_msg' => '异常: ' . $e->getMessage()];
  180. }
  181. }
  182. /**
  183. * 写入日志
  184. */
  185. private function writeLog($is_success, $result, $request_data)
  186. {
  187. $log_dir = RUNTIME_PATH . 'icbc_log/';
  188. if (!is_dir($log_dir)) {
  189. mkdir($log_dir, 0755, true);
  190. }
  191. $filename = $is_success ? 'suc.txt' : 'failicbc.txt';
  192. $filepath = $log_dir . $filename;
  193. $log_content = [
  194. '时间' => date('Y-m-d H:i:s'),
  195. '请求数据' => $request_data,
  196. '返回数据' => $result,
  197. '分隔线' => str_repeat('-', 80)
  198. ];
  199. $log_text = "\n" . json_encode($log_content, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT) . "\n";
  200. file_put_contents($filepath, $log_text, FILE_APPEND);
  201. }
  202. /**
  203. * 清理响应内容,提取纯JSON
  204. * @param string $response 原始响应
  205. * @return string 清理后的JSON字符串
  206. */
  207. private function cleanResponse($response)
  208. {
  209. // 1. 去除首尾空白
  210. $response = trim($response);
  211. // 2. 查找第一个 { 或 [(JSON开始)
  212. $json_start = -1;
  213. $first_brace = strpos($response, '{');
  214. $first_bracket = strpos($response, '[');
  215. if ($first_brace !== false && $first_bracket !== false) {
  216. $json_start = min($first_brace, $first_bracket);
  217. } elseif ($first_brace !== false) {
  218. $json_start = $first_brace;
  219. } elseif ($first_bracket !== false) {
  220. $json_start = $first_bracket;
  221. }
  222. // 3. 查找最后一个 } 或 ](JSON结束)
  223. $json_end = -1;
  224. $last_brace = strrpos($response, '}');
  225. $last_bracket = strrpos($response, ']');
  226. if ($last_brace !== false && $last_bracket !== false) {
  227. $json_end = max($last_brace, $last_bracket);
  228. } elseif ($last_brace !== false) {
  229. $json_end = $last_brace;
  230. } elseif ($last_bracket !== false) {
  231. $json_end = $last_bracket;
  232. }
  233. // 4. 提取JSON部分
  234. if ($json_start !== -1 && $json_end !== -1 && $json_end > $json_start) {
  235. return substr($response, $json_start, $json_end - $json_start + 1);
  236. }
  237. // 5. 如果没找到JSON结构,返回原始内容
  238. return $response;
  239. }
  240. }