CaiWuXiTong/app/Services/DingTalk/CourierOnboardingClient.php

119 lines
5.3 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Services\DingTalk;
use App\Exceptions\CourierOnboardingDingTalkException;
use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Carbon;
/** 独立的「财务系统」应用,仅负责快递入驻审批。创建请求绝不自动重试。 */
class CourierOnboardingClient
{
private const BASE_URL = 'https://api.dingtalk.com/v1.0';
public function accessToken(): string
{
$settings = config('courier_onboarding.dingtalk');
foreach (['client_id', 'client_secret', 'process_code', 'originator_user_id', 'dept_id'] as $key) {
if (trim((string) ($settings[$key] ?? '')) === '') {
throw new CourierOnboardingDingTalkException('入驻钉钉应用配置不完整,请联系系统管理员。');
}
}
try {
$response = Http::acceptJson()->connectTimeout(3)->timeout(8)
->post(self::BASE_URL . '/oauth2/accessToken', [
'appKey' => $settings['client_id'],
'appSecret' => $settings['client_secret'],
]);
} catch (ConnectionException) {
throw new CourierOnboardingDingTalkException('获取钉钉应用凭证超时,尚未发起审批。');
}
$token = $response->json('accessToken');
if (! $response->successful() || ! is_string($token) || trim($token) === '') {
throw new CourierOnboardingDingTalkException('获取钉钉应用凭证失败,请检查应用凭据和权限。');
}
return $token;
}
public function start(string $token, array $payload): string
{
try {
$response = Http::acceptJson()->connectTimeout(3)->timeout(12)
->withHeaders(['x-acs-dingtalk-access-token' => $token])
->post(self::BASE_URL . '/workflow/processInstances', $payload);
} catch (ConnectionException) {
throw new CourierOnboardingDingTalkException('发起请求超时,请先在钉钉核查是否已生成审批,勿重复发起。', true);
}
if (! $response->successful()) {
// 不保存第三方原始响应,防止请求内容或凭据被带入入驻记录。
$code = preg_replace('/[^a-zA-Z0-9_.-]/', '', (string) $response->json('code', ''));
$uncertain = $response->serverError() || $response->status() === 408;
throw new CourierOnboardingDingTalkException(
'钉钉返回 HTTP ' . $response->status() . ($code !== '' ? '(' . substr($code, 0, 100) . ')' : '')
. '。' . ($uncertain ? '请先核查是否已生成审批。' : '请检查流程配置和发起权限。'),
$uncertain,
);
}
$id = $response->json('instanceId');
if (! is_string($id) || trim($id) === '') {
throw new CourierOnboardingDingTalkException('钉钉响应缺少审批编号,请先核查是否已生成审批。', true);
}
return trim($id);
}
public function instance(string $token, string $instanceId): array
{
try {
$response = Http::acceptJson()->connectTimeout(3)->timeout(8)
->withHeaders(['x-acs-dingtalk-access-token' => $token])
->get(self::BASE_URL . '/workflow/processInstances', ['processInstanceId' => $instanceId]);
} catch (ConnectionException) {
throw new CourierOnboardingDingTalkException('查询钉钉审批超时。');
}
if (! $response->successful() || ! is_array($response->json('result'))) {
throw new CourierOnboardingDingTalkException('无法读取指定的钉钉审批。');
}
return $response->json('result');
}
public function belongsToProcess(string $token, string $instanceId, string $createdAt): bool
{
$created = Carbon::parse($createdAt);
$nextToken = 0;
// 仅查询创建时刻前后一分钟的指定流程与发起人,限制核查范围。
for ($page = 0; $page < 5; $page++) {
$response = Http::acceptJson()->connectTimeout(3)->timeout(8)
->withHeaders(['x-acs-dingtalk-access-token' => $token])
->post(self::BASE_URL . '/workflow/processes/instanceIds/query', [
'processCode' => config('courier_onboarding.dingtalk.process_code'),
'userIds' => [config('courier_onboarding.dingtalk.originator_user_id')],
'startTime' => $created->copy()->subMinute()->getTimestampMs(),
'endTime' => $created->copy()->addMinute()->getTimestampMs(),
'maxResults' => 100, 'nextToken' => $nextToken,
]);
if (! $response->successful() || ! is_array($response->json('result.list'))) {
throw new CourierOnboardingDingTalkException('无法核对审批所属流程,未关联。');
}
if (in_array($instanceId, $response->json('result.list'), true)) {
return true;
}
$next = $response->json('result.nextToken');
if ($next === null || $next === '' || (int) $next === $nextToken) {
return false;
}
$nextToken = (int) $next;
}
return false;
}
}