更新
This commit is contained in:
@@ -434,21 +434,25 @@ class WecomPromotionLogic
|
||||
try {
|
||||
$api->deleteLink($remoteLinkId);
|
||||
} catch (\Throwable $e) {
|
||||
$message = '企业微信官方获客链接删除失败,本地方案已保留:' . $e->getMessage();
|
||||
Db::name('qywx_promotion_link')->where('id', $linkId)->update([
|
||||
'sync_error' => mb_substr($message, 0, 500),
|
||||
'update_time' => time(),
|
||||
]);
|
||||
Db::name('qywx_promotion_range_sync')->where('pool_id', $id)->where('lock_token', $deleteToken)->update([
|
||||
'status' => 4,
|
||||
'next_retry' => 0,
|
||||
'lock_token' => '',
|
||||
'lock_until' => 0,
|
||||
'last_error' => mb_substr($message, 0, 500),
|
||||
'update_time' => time(),
|
||||
]);
|
||||
// 删除是幂等操作:企业微信明确返回 invalid link_id 时,远端目标已不存在
|
||||
// 或已不再属于当前应用,继续完成本地清理;其他错误仍保留方案以便重试。
|
||||
if (!self::isRemoteLinkAlreadyMissing($e)) {
|
||||
$message = '企业微信官方获客链接删除失败,本地方案已保留:' . $e->getMessage();
|
||||
Db::name('qywx_promotion_link')->where('id', $linkId)->update([
|
||||
'sync_error' => mb_substr($message, 0, 500),
|
||||
'update_time' => time(),
|
||||
]);
|
||||
Db::name('qywx_promotion_range_sync')->where('pool_id', $id)->where('lock_token', $deleteToken)->update([
|
||||
'status' => 4,
|
||||
'next_retry' => 0,
|
||||
'lock_token' => '',
|
||||
'lock_until' => 0,
|
||||
'last_error' => mb_substr($message, 0, 500),
|
||||
'update_time' => time(),
|
||||
]);
|
||||
|
||||
throw new RuntimeException($message, 0, $e);
|
||||
throw new RuntimeException($message, 0, $e);
|
||||
}
|
||||
}
|
||||
// 多个历史官方链接部分成功时也保存进度,用户重试删除不会再次请求已删除链接。
|
||||
Db::name('qywx_promotion_link')->where('id', $linkId)->update([
|
||||
@@ -484,6 +488,16 @@ class WecomPromotionLogic
|
||||
});
|
||||
}
|
||||
|
||||
private static function isRemoteLinkAlreadyMissing(\Throwable $error): bool
|
||||
{
|
||||
$message = strtolower($error->getMessage());
|
||||
$isInvalidLinkId = str_contains($message, 'invalid link_id');
|
||||
$isInvalidParameter = (int) $error->getCode() === 40058
|
||||
|| str_contains($message, '[40058]');
|
||||
|
||||
return $isInvalidParameter && $isInvalidLinkId;
|
||||
}
|
||||
|
||||
public static function saveMember(array $params, int $adminId, array $adminInfo): array
|
||||
{
|
||||
self::assertMemberDispatchSchema();
|
||||
|
||||
@@ -187,7 +187,8 @@ class QywxCustomerAcquisitionApiService
|
||||
if ($errcode === 60111) {
|
||||
throw new RuntimeException(
|
||||
'所选医助的企业微信 userid 不存在,或不在获客助手可调用应用的可见范围;'
|
||||
. '请检查后台账号绑定和企业微信应用可见范围。企业微信返回:' . $errorMessage
|
||||
. '请检查后台账号绑定和企业微信应用可见范围。企业微信返回:' . $errorMessage,
|
||||
$errcode
|
||||
);
|
||||
}
|
||||
|
||||
@@ -195,7 +196,7 @@ class QywxCustomerAcquisitionApiService
|
||||
'企业微信获客助手接口失败[%d]:%s',
|
||||
$errcode,
|
||||
$errorMessage
|
||||
));
|
||||
), $errcode);
|
||||
}
|
||||
|
||||
private function accessToken(): string
|
||||
|
||||
@@ -121,4 +121,19 @@ try {
|
||||
$assert(str_contains($invalidUserMessage, 'userid 不存在'), '60111 应返回明确的成员绑定诊断提示');
|
||||
$assert(str_contains($invalidUserMessage, '应用可见范围'), '60111 应提示检查应用可见范围');
|
||||
|
||||
$invalidLinkService = new QywxCustomerAcquisitionApiService(new Client([
|
||||
'base_uri' => 'https://qyapi.weixin.qq.com/',
|
||||
'handler' => HandlerStack::create(new MockHandler([
|
||||
$json(['errcode' => 40058, 'errmsg' => 'invalid link_id']),
|
||||
])),
|
||||
'http_errors' => false,
|
||||
]), static fn (): string => 'mock_token');
|
||||
$invalidLinkCode = 0;
|
||||
try {
|
||||
$invalidLinkService->deleteLink('stale_link_id');
|
||||
} catch (RuntimeException $e) {
|
||||
$invalidLinkCode = $e->getCode();
|
||||
}
|
||||
$assert($invalidLinkCode === 40058, '企业微信错误码应保留在异常 code 中供业务层分类');
|
||||
|
||||
echo "QYWX_CUSTOMER_ACQUISITION_API_TEST_OK\n";
|
||||
|
||||
@@ -16,15 +16,18 @@ final class PromotionDeleteApiFake extends QywxCustomerAcquisitionApiService
|
||||
/** @var list<string> */
|
||||
public array $deleted = [];
|
||||
|
||||
public function __construct(private string $failLinkId = '')
|
||||
{
|
||||
public function __construct(
|
||||
private string $failLinkId = '',
|
||||
private string $failMessage = 'mock remote deletion failed',
|
||||
private int $failCode = 0
|
||||
) {
|
||||
}
|
||||
|
||||
public function deleteLink(string $linkId): void
|
||||
{
|
||||
$this->deleted[] = $linkId;
|
||||
if ($linkId === $this->failLinkId) {
|
||||
throw new RuntimeException('mock remote deletion failed');
|
||||
throw new RuntimeException($this->failMessage, $this->failCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -94,7 +97,11 @@ try {
|
||||
'update_time' => $now,
|
||||
]);
|
||||
|
||||
$firstAttempt = new PromotionDeleteApiFake($secondRemoteId);
|
||||
$firstAttempt = new PromotionDeleteApiFake(
|
||||
$secondRemoteId,
|
||||
'企业微信获客助手接口失败[40058]:invalid range',
|
||||
40058
|
||||
);
|
||||
$message = '';
|
||||
try {
|
||||
WecomPromotionLogic::deletePool($poolId, (int) $admin['id'], $admin, $firstAttempt);
|
||||
@@ -116,6 +123,44 @@ try {
|
||||
$assert((int) Db::name('qywx_promotion_pool_member')->where('id', $memberId)->value('enabled') === 0, '方案删除后成员仍处于启用状态');
|
||||
$sync = Db::name('qywx_promotion_range_sync')->where('pool_id', $poolId)->find();
|
||||
$assert((int) ($sync['status'] ?? 0) === 4 && (string) ($sync['lock_token'] ?? '') === '', '方案删除后同步租约没有终止');
|
||||
|
||||
// 企业微信明确返回 invalid link_id,说明远端目标已不存在或当前应用已无法操作;
|
||||
// 删除应保持幂等并继续清理本地方案,不能让陈旧 link_id 永久阻塞用户。
|
||||
$staleRemoteId = 'contract_stale_' . $suffix;
|
||||
$stalePoolId = (int) Db::name('qywx_promotion_pool')->insertGetId([
|
||||
'name' => '失效链接删除测试',
|
||||
'public_key' => bin2hex(random_bytes(16)),
|
||||
'status' => 1,
|
||||
'owner_admin_id' => (int) $admin['id'],
|
||||
'create_time' => $now,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
$staleLinkId = (int) Db::name('qywx_promotion_link')->insertGetId([
|
||||
'pool_id' => $stalePoolId,
|
||||
'name' => '远端已失效链接',
|
||||
'wecom_url' => 'https://work.weixin.qq.com/ca/' . $staleRemoteId,
|
||||
'remote_link_id' => $staleRemoteId,
|
||||
'remote_status' => 1,
|
||||
'status' => 1,
|
||||
'owner_admin_id' => (int) $admin['id'],
|
||||
'create_time' => $now,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
$staleApi = new PromotionDeleteApiFake(
|
||||
$staleRemoteId,
|
||||
'企业微信获客助手接口失败[40058]:invalid link_id',
|
||||
40058
|
||||
);
|
||||
WecomPromotionLogic::deletePool($stalePoolId, (int) $admin['id'], $admin, $staleApi);
|
||||
$assert($staleApi->deleted === [$staleRemoteId], '失效链接没有请求企业微信删除接口');
|
||||
$assert(
|
||||
Db::name('qywx_promotion_pool')->where('id', $stalePoolId)->whereNotNull('delete_time')->count() === 1,
|
||||
'invalid link_id 阻塞了本地方案删除'
|
||||
);
|
||||
$assert(
|
||||
(int) Db::name('qywx_promotion_link')->where('id', $staleLinkId)->value('remote_status') === 2,
|
||||
'invalid link_id 没有按远端已删除状态收敛'
|
||||
);
|
||||
} finally {
|
||||
Db::rollback();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user