diff --git a/app/Console/Commands/VerifyQccEnterprisePackInfo.php b/app/Console/Commands/VerifyQccEnterprisePackInfo.php index e0c68c7..924179b 100644 --- a/app/Console/Commands/VerifyQccEnterprisePackInfo.php +++ b/app/Console/Commands/VerifyQccEnterprisePackInfo.php @@ -15,9 +15,10 @@ class VerifyQccEnterprisePackInfo extends Command protected $signature = 'qcc:verify-pack-info {company_id : 候选、已占额或待人工确认企业的本地企业 ID} {--timeout=60 : 请求超时时间(秒,正整数)} + {--request : 额外输出本次请求地址、请求头和请求体(不含 authKey)} {--raw : 兼容旧用法,默认即输出完整响应}'; - protected $description = '诊断元禾 packInfo 接口,按 code=200 判断成功,仅输出响应,不修改企业状态'; + protected $description = '诊断元禾 packInfo 接口,按 code=200 判断成功,默认仅输出响应,不修改企业状态'; public function handle(YuanheRepository $yuanheRepository): int { @@ -51,7 +52,8 @@ class VerifyQccEnterprisePackInfo extends Command 'enterpriseName' => $company->company_name, ], $timeout); } catch (Throwable $exception) { - // 非 JSON 响应保留原文;没有响应时仅输出错误,不打印签名或请求头。 + $this->printRequest($yuanheRepository); + // 非 JSON 响应保留原文;没有响应时输出错误。 $result = $exception instanceof YuanhePackInfoException && $exception->rawResponse !== null ? $exception->rawResponse : $exception->getMessage(); @@ -59,6 +61,7 @@ class VerifyQccEnterprisePackInfo extends Command return self::FAILURE; } + $this->printRequest($yuanheRepository); $this->output->writeln( json_encode($response, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT), OutputInterface::OUTPUT_RAW @@ -66,4 +69,21 @@ class VerifyQccEnterprisePackInfo extends Command return in_array($response['code'] ?? null, [200, '200'], true) ? self::SUCCESS : self::FAILURE; } + + private function printRequest(YuanheRepository $repository): void + { + if (!$this->option('request')) { + return; + } + + $this->output->writeln('请求信息(不含 authKey):', OutputInterface::OUTPUT_RAW); + $this->output->writeln( + $repository->lastRequest + ? json_encode($repository->lastRequest, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) + : '未生成请求信息。', + OutputInterface::OUTPUT_RAW + ); + $this->output->writeln('返回结果:', OutputInterface::OUTPUT_RAW); + } + } diff --git a/docs/company-news.md b/docs/company-news.md index 4e3e068..a652bc6 100644 --- a/docs/company-news.md +++ b/docs/company-news.md @@ -131,3 +131,5 @@ npx vue-cli-service test:unit tests/unit/components/CompanyNews.spec.js --runInB ## 企业户不占额状态 企业户候选列表固定要求企业至少关联一名 `users.is_schoolmate=1` 的未删除学员,其余学员和课程筛选继续叠加。`company_qcc_accounts.status=non_occupying` 表示不占额企业,区别于没有企业户记录的未纳入。后台可将未纳入且有信用代码的企业批量标记为不占额;不会覆盖已有企业户记录,也不调用外部接口。不占额企业不进入手动或自动新闻采集名单。状态字段为字符串,本次无需新增数据库迁移。 + +诊断时可增加 `--request` 输出本次实际请求的 method、url、headers 和 json 请求体(包含动态 timestamp/token,不包含签名密钥 authKey),成功、超时和非 JSON 响应均支持。例如:`php artisan qcc:verify-pack-info 675 --timeout=120 --raw --request`。不加该参数时仍仅输出响应或错误,不修改企业状态、不导入新闻。 diff --git a/tests/Feature/VerifyQccEnterprisePackInfoTest.php b/tests/Feature/VerifyQccEnterprisePackInfoTest.php index c43d5b0..91223cb 100644 --- a/tests/Feature/VerifyQccEnterprisePackInfoTest.php +++ b/tests/Feature/VerifyQccEnterprisePackInfoTest.php @@ -109,4 +109,40 @@ class VerifyQccEnterprisePackInfoTest extends TestCase ['selected', ['--timeout' => 'abc']], ]; } + + /** @dataProvider requestCases */ + public function test_request_details_are_from_actual_call_even_on_failure(?string $failure): void + { + $this->company('unknown'); + $before = DB::table('company_qcc_accounts')->get()->toJson(); + $request = [ + 'method' => 'POST', 'url' => 'https://example.test/gateway/openapi/businessCollege/enterprise/packInfo', + 'headers' => ['Content-Type' => 'application/json', 'customerId' => 'test-customer', 'timestamp' => '1234567890000', 'token' => 'test-signature'], + 'json' => ['creditCode' => 'test-credit', 'enterpriseName' => '测试企业'], + ]; + $mock = Mockery::mock(YuanheRepository::class); + $mock->authKey = 'secret-not-for-output'; + $mock->shouldReceive('enterprisePackInfo')->once()->with($request['json'], 120) + ->andReturnUsing(function () use ($mock, $request, $failure) { + $mock->lastRequest = $request; + if ($failure !== null) { + throw new YuanhePackInfoException('请求超时', null, null, $failure === 'timeout' ? null : $failure); + } + return ['code' => 200]; + }); + $this->app->instance(YuanheRepository::class, $mock); + $exit = Artisan::call('qcc:verify-pack-info', ['company_id' => 333, '--timeout' => '120', '--raw' => true, '--request' => true]); + $this->assertSame($failure === null ? 0 : 1, $exit); + $expected = "请求信息(不含 authKey):\n".json_encode($request, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)."\n返回结果:\n"; + $expected .= $failure === null ? json_encode(['code' => 200], JSON_PRETTY_PRINT) : ($failure === 'timeout' ? '请求超时' : $failure); + $this->assertSame($expected.PHP_EOL, Artisan::output()); + $this->assertStringNotContainsString('secret-not-for-output', Artisan::output()); + $this->assertSame($before, DB::table('company_qcc_accounts')->get()->toJson()); + } + + public static function requestCases(): array + { + return [[null], ['timeout'], ['504 Gateway Time-out']]; + } + }