更新
This commit is contained in:
@@ -62,7 +62,7 @@ class WecomPromotionController extends BaseAdminController
|
||||
return $this->run(function () use ($id) {
|
||||
WecomPromotionLogic::deletePool($id, $this->adminId, $this->adminInfo);
|
||||
|
||||
return $this->success('分流方案已删除');
|
||||
return $this->success('分流方案及企业微信官方获客链接已永久删除');
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ namespace app\adminapi\logic\firstvisit;
|
||||
use app\common\service\DataScope\DataScopeService;
|
||||
use app\common\service\qywx\QywxCustomerAcquisitionApiService;
|
||||
use app\common\service\qywx\QywxCustomerAcquisitionLinkService;
|
||||
use app\common\service\qywx\QywxPromotionMemberRange;
|
||||
use app\common\service\qywx\QywxPromotionMemberSchedulerService;
|
||||
use app\common\service\qywx\QywxPromotionRangeSyncService;
|
||||
use app\common\service\qywx\QywxPromotionWeightedRandom;
|
||||
use app\common\service\qywx\QywxPromotionWidgetService;
|
||||
use RuntimeException;
|
||||
use think\facade\Db;
|
||||
@@ -126,11 +126,11 @@ class WecomPromotionLogic
|
||||
$officialLink = $officialLinks[0] ?? null;
|
||||
$memberRules = $memberRulesByPool[(int) $pool['id']] ?? [];
|
||||
$sync = $syncByPool[(int) $pool['id']] ?? [];
|
||||
$remoteUserIds = array_fill_keys((array) ($officialLink['range_userids'] ?? []), true);
|
||||
foreach ($memberRules as &$memberRule) {
|
||||
$memberRule['is_current'] = (int) ($memberRule['id'] ?? 0)
|
||||
=== (int) ($sync['desired_member_id'] ?? 0);
|
||||
$memberRule['is_applied'] = (int) ($memberRule['id'] ?? 0)
|
||||
=== (int) ($sync['applied_member_id'] ?? 0);
|
||||
$memberRule['is_current'] = false;
|
||||
$memberRule['is_applied'] = false;
|
||||
$memberRule['is_in_remote_range'] = isset($remoteUserIds[(string) ($memberRule['userid'] ?? '')]);
|
||||
$memberRule['sync_status'] = (int) ($sync['status'] ?? 0);
|
||||
$memberRule['sync_error'] = (string) ($sync['last_error'] ?? '');
|
||||
}
|
||||
@@ -204,7 +204,7 @@ class WecomPromotionLogic
|
||||
}
|
||||
$members = self::resolveMembers((array) ($params['member_admin_ids'] ?? []), $adminId, $adminInfo);
|
||||
$userIds = array_values(array_column($members, 'userid'));
|
||||
$selectedUserId = self::preferredDispatchUserId($id, $members);
|
||||
$eligibleUserIds = self::eligibleSelectedUserIds($id, $members);
|
||||
$skipVerify = (int) ($params['skip_verify'] ?? 0) === 1 ? 1 : 0;
|
||||
$status = (int) ($params['status'] ?? 1) === 1 ? 1 : 0;
|
||||
$now = time();
|
||||
@@ -227,38 +227,38 @@ class WecomPromotionLogic
|
||||
}
|
||||
|
||||
$api = new QywxCustomerAcquisitionApiService();
|
||||
$payload = [
|
||||
'link_name' => mb_substr($name, 0, 30),
|
||||
// 对外始终是同一个官方直链;当前只激活调度器选中的成员,回调后再切换下一位。
|
||||
'range' => ['user_list' => [$selectedUserId]],
|
||||
'skip_verify' => $skipVerify === 1,
|
||||
];
|
||||
$createdRemote = false;
|
||||
if ($existingLink !== null) {
|
||||
$remoteLinkId = trim((string) ($existingLink['remote_link_id'] ?? ''));
|
||||
$payload['link_id'] = $remoteLinkId;
|
||||
$api->updateLink($payload);
|
||||
} else {
|
||||
$remote = null;
|
||||
if ($existingLink === null) {
|
||||
$payload = [
|
||||
'link_name' => mb_substr($name, 0, 30),
|
||||
// 一个官方直链同时绑定全部当前可用医助,由企业微信原生多人路由直接分配。
|
||||
'range' => ['user_list' => $eligibleUserIds],
|
||||
'skip_verify' => $skipVerify === 1,
|
||||
];
|
||||
$created = $api->createLink($payload);
|
||||
$remoteLinkId = self::extractRemoteLinkId($created);
|
||||
if ($remoteLinkId === '') {
|
||||
throw new RuntimeException('企业微信已创建链接,但接口未返回 link_id,请执行“同步企业微信”确认结果');
|
||||
}
|
||||
$createdRemote = true;
|
||||
}
|
||||
try {
|
||||
$remote = self::normaliseRemoteLink($api->getLink($remoteLinkId), $remoteLinkId);
|
||||
} catch (\Throwable $e) {
|
||||
if ($createdRemote) {
|
||||
try {
|
||||
$remote = self::normaliseRemoteLink($api->getLink($remoteLinkId), $remoteLinkId);
|
||||
if (!QywxPromotionMemberRange::same($remote['range_userids'], $eligibleUserIds)) {
|
||||
throw new RuntimeException('企业微信返回的多人路由成员范围与方案可用医助不一致');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
try {
|
||||
$api->deleteLink($remoteLinkId);
|
||||
} catch (\Throwable) {
|
||||
// 保留详情读取的原始异常;孤立链接仍可通过企业微信同步找回。
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
throw $e;
|
||||
} else {
|
||||
$remoteLinkId = trim((string) ($existingLink['remote_link_id'] ?? ''));
|
||||
}
|
||||
$remoteData = self::remoteColumns($remote, $now);
|
||||
$remoteData = $remote !== null ? self::remoteColumns($remote, $now) : [];
|
||||
|
||||
$linkId = (int) ($existingLink['id'] ?? 0);
|
||||
try {
|
||||
@@ -270,7 +270,8 @@ class WecomPromotionLogic
|
||||
$status,
|
||||
$existingPool,
|
||||
$members,
|
||||
$selectedUserId,
|
||||
$skipVerify,
|
||||
$createdRemote,
|
||||
$now,
|
||||
$adminId
|
||||
): void {
|
||||
@@ -288,8 +289,10 @@ class WecomPromotionLogic
|
||||
|
||||
$linkCommon = [
|
||||
'pool_id' => $id,
|
||||
'name' => mb_substr((string) $poolData['name'], 0, 30),
|
||||
'group_name' => '方案官方链接',
|
||||
'status' => $status,
|
||||
'skip_verify' => $skipVerify,
|
||||
'update_time' => $now,
|
||||
];
|
||||
if ($linkId > 0) {
|
||||
@@ -312,7 +315,9 @@ class WecomPromotionLogic
|
||||
]);
|
||||
}
|
||||
self::persistPoolMembers($id, $members, $now);
|
||||
QywxPromotionMemberSchedulerService::initialisePool($id, $linkId, $selectedUserId);
|
||||
if ($createdRemote) {
|
||||
QywxPromotionMemberSchedulerService::initialisePool($id, $linkId);
|
||||
}
|
||||
|
||||
// 旧多链接只在本地下线,企业微信远端与历史客户归因继续保留。
|
||||
Db::name('qywx_promotion_link')
|
||||
@@ -332,16 +337,30 @@ class WecomPromotionLogic
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$syncError = '';
|
||||
if (!$createdRemote) {
|
||||
QywxPromotionMemberSchedulerService::requestPoolSync($id, $linkId);
|
||||
try {
|
||||
(new QywxPromotionRangeSyncService())->syncPool($id);
|
||||
} catch (\Throwable $e) {
|
||||
// 本地方案和成员规则已保存;后台分钟任务会继续重试最新完整范围。
|
||||
$syncError = $e->getMessage();
|
||||
}
|
||||
}
|
||||
$savedLink = Db::name('qywx_promotion_link')->where('id', $linkId)->find() ?: [];
|
||||
$savedUrl = (string) ($savedLink['wecom_url'] ?? $remote['url'] ?? '');
|
||||
|
||||
return [
|
||||
'id' => $id,
|
||||
'remote_link_id' => (string) ($remote['link_id'] ?? ''),
|
||||
'wecom_url' => (string) ($remote['url'] ?? ''),
|
||||
'remote_link_id' => (string) ($savedLink['remote_link_id'] ?? $remoteLinkId),
|
||||
'wecom_url' => $savedUrl,
|
||||
'main_url' => QywxCustomerAcquisitionLinkService::withCustomerChannel(
|
||||
(string) ($remote['url'] ?? ''),
|
||||
$savedUrl,
|
||||
'zyt_pool:' . $id
|
||||
),
|
||||
'member_userids' => $userIds,
|
||||
'current_userid' => $selectedUserId,
|
||||
'range_userids' => $eligibleUserIds,
|
||||
'sync_error' => $syncError,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -360,13 +379,95 @@ class WecomPromotionLogic
|
||||
return ['id' => $id, 'widget_config' => $config];
|
||||
}
|
||||
|
||||
public static function deletePool(int $id, int $adminId, array $adminInfo): void
|
||||
public static function deletePool(
|
||||
int $id,
|
||||
int $adminId,
|
||||
array $adminInfo,
|
||||
?QywxCustomerAcquisitionApiService $api = null
|
||||
): void
|
||||
{
|
||||
self::assertScopedRow('qywx_promotion_pool', $id, $adminId, $adminInfo);
|
||||
$links = Db::name('qywx_promotion_link')
|
||||
->where('pool_id', $id)
|
||||
->order('id', 'asc')
|
||||
->select()->toArray();
|
||||
$now = time();
|
||||
$remoteLinks = array_values(array_filter($links, static fn (array $link): bool =>
|
||||
trim((string) ($link['remote_link_id'] ?? '')) !== ''
|
||||
&& (int) ($link['remote_status'] ?? 0) !== 2
|
||||
));
|
||||
if ($remoteLinks !== []) {
|
||||
// 使用方案级删除租约阻止双击、重复请求以及回调/分钟任务继续更新远端范围。
|
||||
$deleteToken = bin2hex(random_bytes(16));
|
||||
Db::transaction(function () use ($id, $remoteLinks, $deleteToken, $now): void {
|
||||
$sync = Db::name('qywx_promotion_range_sync')->where('pool_id', $id)->lock(true)->find();
|
||||
if ($sync && (int) ($sync['status'] ?? 0) === 5 && (int) ($sync['lock_until'] ?? 0) > $now) {
|
||||
throw new RuntimeException('该分流方案正在删除,请勿重复提交');
|
||||
}
|
||||
$data = [
|
||||
'promotion_link_id' => (int) ($sync['promotion_link_id'] ?? $remoteLinks[0]['id'] ?? 0),
|
||||
'desired_member_id' => 0,
|
||||
'desired_version' => max(1, (int) ($sync['desired_version'] ?? 0) + 1),
|
||||
'status' => 5,
|
||||
'next_retry' => 0,
|
||||
'lock_token' => $deleteToken,
|
||||
'lock_until' => $now + 300,
|
||||
'last_error' => '分流方案正在删除企业微信官方链接',
|
||||
'update_time' => $now,
|
||||
];
|
||||
if ($sync) {
|
||||
Db::name('qywx_promotion_range_sync')->where('pool_id', $id)->update($data);
|
||||
} else {
|
||||
Db::name('qywx_promotion_range_sync')->insert($data + [
|
||||
'pool_id' => $id,
|
||||
'applied_member_id' => 0,
|
||||
'applied_version' => 0,
|
||||
'attempts' => 0,
|
||||
'create_time' => $now,
|
||||
]);
|
||||
}
|
||||
});
|
||||
$api ??= new QywxCustomerAcquisitionApiService();
|
||||
foreach ($remoteLinks as $link) {
|
||||
$linkId = (int) ($link['id'] ?? 0);
|
||||
$remoteLinkId = trim((string) ($link['remote_link_id'] ?? ''));
|
||||
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(),
|
||||
]);
|
||||
|
||||
throw new RuntimeException($message, 0, $e);
|
||||
}
|
||||
// 多个历史官方链接部分成功时也保存进度,用户重试删除不会再次请求已删除链接。
|
||||
Db::name('qywx_promotion_link')->where('id', $linkId)->update([
|
||||
'status' => 0,
|
||||
'remote_status' => 2,
|
||||
'last_sync_time' => time(),
|
||||
'sync_error' => '',
|
||||
'update_time' => time(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Db::transaction(function () use ($id, $now): void {
|
||||
Db::name('qywx_promotion_pool')->where('id', $id)->update(['delete_time' => $now, 'update_time' => $now]);
|
||||
Db::name('qywx_promotion_link')->where('pool_id', $id)->whereNull('delete_time')->update(['delete_time' => $now, 'update_time' => $now]);
|
||||
Db::name('qywx_promotion_link')->where('pool_id', $id)->whereNull('delete_time')->update([
|
||||
'status' => 0,
|
||||
'delete_time' => $now,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
Db::name('qywx_promotion_pool_member')->where('pool_id', $id)->whereNull('delete_time')->update([
|
||||
'enabled' => 0,
|
||||
'delete_time' => $now,
|
||||
@@ -375,7 +476,9 @@ class WecomPromotionLogic
|
||||
Db::name('qywx_promotion_range_sync')->where('pool_id', $id)->update([
|
||||
'status' => 4,
|
||||
'next_retry' => 0,
|
||||
'last_error' => '分流方案已删除',
|
||||
'lock_token' => '',
|
||||
'lock_until' => 0,
|
||||
'last_error' => '分流方案及企业微信官方链接已删除',
|
||||
'update_time' => $now,
|
||||
]);
|
||||
});
|
||||
@@ -412,7 +515,8 @@ class WecomPromotionLogic
|
||||
Db::transaction(function () use ($id, $enabled, $params, $startAt, $endAt, $now): void {
|
||||
Db::name('qywx_promotion_pool_member')->where('id', $id)->update([
|
||||
'enabled' => $enabled,
|
||||
'weight' => min(100, max(1, (int) ($params['weight'] ?? 1))),
|
||||
// 企业微信原生多人路由不支持逐成员权重;字段固定为 1,仅兼容已部署表结构。
|
||||
'weight' => 1,
|
||||
'daily_limit' => min(1000000, max(0, (int) ($params['daily_limit'] ?? 0))),
|
||||
'active_start' => $startAt,
|
||||
'active_end' => $endAt,
|
||||
@@ -445,7 +549,6 @@ class WecomPromotionLogic
|
||||
return self::saveMember([
|
||||
'id' => $id,
|
||||
'status' => $status,
|
||||
'weight' => (int) ($row['weight'] ?? 1),
|
||||
'daily_limit' => (int) ($row['daily_limit'] ?? 0),
|
||||
'active_start' => (int) ($row['active_start'] ?? 0),
|
||||
'active_end' => (int) ($row['active_end'] ?? 0),
|
||||
@@ -457,18 +560,10 @@ class WecomPromotionLogic
|
||||
{
|
||||
$id = max(0, (int) ($params['id'] ?? 0));
|
||||
$poolId = max(0, (int) ($params['pool_id'] ?? 0));
|
||||
$pool = self::assertScopedRow('qywx_promotion_pool', $poolId, $adminId, $adminInfo);
|
||||
self::assertScopedRow('qywx_promotion_pool', $poolId, $adminId, $adminInfo);
|
||||
$existing = $id > 0 ? self::assertScopedRow('qywx_promotion_link', $id, $adminId, $adminInfo) : null;
|
||||
if ($existing === null) {
|
||||
$officialCount = (int) Db::name('qywx_promotion_link')
|
||||
->where('pool_id', $poolId)
|
||||
->whereNull('delete_time')
|
||||
->where('remote_link_id', '<>', '')
|
||||
->where('remote_status', '<>', 2)
|
||||
->count();
|
||||
if ($officialCount > 0) {
|
||||
throw new RuntimeException('一个分流方案只能有一个官方获客链接,请直接编辑分流方案中的获客成员');
|
||||
}
|
||||
if ($existing === null || trim((string) ($existing['remote_link_id'] ?? '')) !== '') {
|
||||
throw new RuntimeException('官方获客链接由分流方案统一维护,请直接编辑方案中的获客医助');
|
||||
}
|
||||
$name = trim((string) ($params['name'] ?? ''));
|
||||
if ($name === '' || mb_strlen($name) > 80) {
|
||||
@@ -494,66 +589,15 @@ class WecomPromotionLogic
|
||||
'update_time' => $now,
|
||||
];
|
||||
|
||||
// 历史手工链接只维护本地分流规则,不会在企业微信端创建重复链接。
|
||||
if ($existing !== null && trim((string) ($existing['remote_link_id'] ?? '')) === '') {
|
||||
$url = trim((string) ($params['wecom_url'] ?? $existing['wecom_url'] ?? ''));
|
||||
if (!QywxCustomerAcquisitionLinkService::isAllowed($url)) {
|
||||
throw new RuntimeException('历史链接必须是 https://work.weixin.qq.com/ca/... 格式');
|
||||
}
|
||||
$data['wecom_url'] = $url;
|
||||
Db::name('qywx_promotion_link')->where('id', $id)->update($data);
|
||||
|
||||
return ['id' => $id, 'mode' => 'legacy'];
|
||||
// 仅保留历史手工链接的兼容编辑;官方链接只能经 savePool 和版本化同步服务修改。
|
||||
$url = trim((string) ($params['wecom_url'] ?? $existing['wecom_url'] ?? ''));
|
||||
if (!QywxCustomerAcquisitionLinkService::isAllowed($url)) {
|
||||
throw new RuntimeException('历史链接必须是 https://work.weixin.qq.com/ca/... 格式');
|
||||
}
|
||||
$data['wecom_url'] = $url;
|
||||
Db::name('qywx_promotion_link')->where('id', $id)->update($data);
|
||||
|
||||
$userIds = self::resolveMemberUserIds((array) ($params['member_admin_ids'] ?? []), $adminId, $adminInfo);
|
||||
$skipVerify = (int) ($params['skip_verify'] ?? 0) === 1 ? 1 : 0;
|
||||
$payload = [
|
||||
'link_name' => $name,
|
||||
'range' => ['user_list' => $userIds],
|
||||
'skip_verify' => $skipVerify === 1,
|
||||
];
|
||||
|
||||
$api = new QywxCustomerAcquisitionApiService();
|
||||
if ($existing !== null) {
|
||||
$remoteLinkId = trim((string) ($existing['remote_link_id'] ?? ''));
|
||||
$payload['link_id'] = $remoteLinkId;
|
||||
$api->updateLink($payload);
|
||||
} else {
|
||||
$created = $api->createLink($payload);
|
||||
$remoteLinkId = self::extractRemoteLinkId($created);
|
||||
if ($remoteLinkId === '') {
|
||||
throw new RuntimeException('企业微信已创建链接,但接口未返回 link_id,请先执行“同步企业微信”确认结果');
|
||||
}
|
||||
}
|
||||
|
||||
$remote = self::normaliseRemoteLink($api->getLink($remoteLinkId), $remoteLinkId);
|
||||
$data += self::remoteColumns($remote, $now);
|
||||
if ($existing !== null) {
|
||||
Db::name('qywx_promotion_link')->where('id', $id)->update($data);
|
||||
} else {
|
||||
$data += [
|
||||
'owner_admin_id' => (int) ($pool['owner_admin_id'] ?? 0) ?: $adminId,
|
||||
'dept_id' => (int) ($pool['dept_id'] ?? 0) ?: self::primaryDeptId($adminId),
|
||||
'click_count' => 0,
|
||||
'today_count' => 0,
|
||||
'today_date' => null,
|
||||
'last_click_time' => 0,
|
||||
'create_time' => $now,
|
||||
];
|
||||
try {
|
||||
$id = (int) Db::name('qywx_promotion_link')->insertGetId($data);
|
||||
} catch (\Throwable $e) {
|
||||
try {
|
||||
$api->deleteLink($remoteLinkId);
|
||||
} catch (\Throwable) {
|
||||
// 远端补偿失败时保留原始异常,管理员可通过“同步企业微信”找回链接。
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
return ['id' => $id, 'remote_link_id' => $remoteLinkId, 'mode' => 'official'];
|
||||
return ['id' => $id, 'mode' => 'legacy'];
|
||||
}
|
||||
|
||||
/** 验证 CorpID、应用 Secret、可信 IP 与获客助手接口权限。 */
|
||||
@@ -653,7 +697,7 @@ class WecomPromotionLogic
|
||||
return self::remotePublicPayload($remote);
|
||||
}
|
||||
|
||||
/** 永久删除企业微信端获客链接,本地保留审计记录并停止分流。 */
|
||||
/** 官方链接由方案统一删除,避免绕过方案删除租约和同步状态机。 */
|
||||
public static function deleteRemoteLink(int $id, int $adminId, array $adminInfo): void
|
||||
{
|
||||
$row = self::assertScopedRow('qywx_promotion_link', $id, $adminId, $adminInfo);
|
||||
@@ -661,14 +705,8 @@ class WecomPromotionLogic
|
||||
if ($remoteLinkId === '') {
|
||||
throw new RuntimeException('历史手工链接只能从本地移除');
|
||||
}
|
||||
(new QywxCustomerAcquisitionApiService())->deleteLink($remoteLinkId);
|
||||
Db::name('qywx_promotion_link')->where('id', $id)->update([
|
||||
'status' => 0,
|
||||
'remote_status' => 2,
|
||||
'last_sync_time' => time(),
|
||||
'sync_error' => '',
|
||||
'update_time' => time(),
|
||||
]);
|
||||
|
||||
throw new RuntimeException('官方获客链接由分流方案统一维护,请使用“删除分流方案”同时删除企业微信链接');
|
||||
}
|
||||
|
||||
public static function toggleLink(int $id, int $status, int $adminId, array $adminInfo): void
|
||||
@@ -685,7 +723,10 @@ class WecomPromotionLogic
|
||||
|
||||
public static function deleteLink(int $id, int $adminId, array $adminInfo): void
|
||||
{
|
||||
self::assertScopedRow('qywx_promotion_link', $id, $adminId, $adminInfo);
|
||||
$row = self::assertScopedRow('qywx_promotion_link', $id, $adminId, $adminInfo);
|
||||
if (trim((string) ($row['remote_link_id'] ?? '')) !== '') {
|
||||
throw new RuntimeException('官方获客链接不能仅从本地移除,请使用“删除分流方案”同步删除企业微信链接');
|
||||
}
|
||||
Db::name('qywx_promotion_link')->where('id', $id)->update([
|
||||
'delete_time' => time(),
|
||||
'update_time' => time(),
|
||||
@@ -776,76 +817,41 @@ class WecomPromotionLogic
|
||||
return array_values(array_column(self::resolveMembers($adminIds, $adminId, $adminInfo), 'userid'));
|
||||
}
|
||||
|
||||
/** @param list<array{id:int,userid:string}> $members */
|
||||
private static function preferredDispatchUserId(int $poolId, array $members): string
|
||||
/** @param list<array{id:int,userid:string}> $members @return list<string> */
|
||||
private static function eligibleSelectedUserIds(int $poolId, array $members): array
|
||||
{
|
||||
if ($members === []) {
|
||||
throw new RuntimeException('请至少选择一名获客成员');
|
||||
}
|
||||
$selected = [];
|
||||
foreach ($members as $member) {
|
||||
$selected[(string) $member['userid']] = true;
|
||||
}
|
||||
$rules = $poolId > 0
|
||||
? Db::name('qywx_promotion_pool_member')->where('pool_id', $poolId)->whereNull('delete_time')->select()->toArray()
|
||||
: [];
|
||||
$rulesById = [];
|
||||
$rulesByUserId = [];
|
||||
foreach ($rules as $rule) {
|
||||
$rulesById[(int) $rule['id']] = $rule;
|
||||
$rulesByUserId[(string) $rule['userid']] = $rule;
|
||||
}
|
||||
$desiredId = $poolId > 0
|
||||
? (int) (Db::name('qywx_promotion_range_sync')->where('pool_id', $poolId)->value('desired_member_id') ?? 0)
|
||||
: 0;
|
||||
$now = time();
|
||||
$today = date('Y-m-d', $now);
|
||||
if (isset($rulesById[$desiredId])) {
|
||||
$current = $rulesById[$desiredId];
|
||||
if ((string) ($current['today_date'] ?? '') !== $today) {
|
||||
$current['today_count'] = 0;
|
||||
$current['today_date'] = $today;
|
||||
}
|
||||
if (isset($selected[(string) $current['userid']]) && QywxPromotionWeightedRandom::eligible($current, $now)) {
|
||||
return (string) $current['userid'];
|
||||
}
|
||||
}
|
||||
$randomCandidates = [];
|
||||
$candidateUserIds = [];
|
||||
foreach ($members as $index => $member) {
|
||||
$candidates = [];
|
||||
foreach ($members as $member) {
|
||||
$userId = (string) $member['userid'];
|
||||
$rule = $rulesByUserId[$userId] ?? [
|
||||
'enabled' => 1,
|
||||
'weight' => 1,
|
||||
'daily_limit' => 0,
|
||||
'today_count' => 0,
|
||||
'today_date' => $today,
|
||||
'active_start' => 0,
|
||||
'active_end' => 0,
|
||||
];
|
||||
if ((string) ($rule['today_date'] ?? '') !== $today) {
|
||||
$rule['today_count'] = 0;
|
||||
$rule['today_date'] = $today;
|
||||
}
|
||||
$candidateId = $index + 1;
|
||||
$rule['id'] = $candidateId;
|
||||
$randomCandidates[] = $rule;
|
||||
$candidateUserIds[$candidateId] = $userId;
|
||||
$rule['userid'] = $userId;
|
||||
$candidates[] = $rule;
|
||||
}
|
||||
$selection = QywxPromotionWeightedRandom::select($randomCandidates, $today, $now);
|
||||
$selectedCandidateId = (int) ($selection['selected_id'] ?? 0);
|
||||
if ($selectedCandidateId > 0 && isset($candidateUserIds[$selectedCandidateId])) {
|
||||
return $candidateUserIds[$selectedCandidateId];
|
||||
}
|
||||
// 企业微信不接受空范围;仅在所有启用成员恰好达到上限时保留一名,队列会标记“无可用成员”。
|
||||
foreach ($members as $member) {
|
||||
$rule = $rulesByUserId[(string) $member['userid']] ?? null;
|
||||
if ($rule === null || (int) ($rule['enabled'] ?? 1) === 1) {
|
||||
return (string) $member['userid'];
|
||||
}
|
||||
$range = QywxPromotionMemberRange::evaluate($candidates, $today, $now);
|
||||
if ($range['userids'] === []) {
|
||||
throw new RuntimeException('至少需要一名已启用、已生效且未达到今日上限的获客医助');
|
||||
}
|
||||
|
||||
throw new RuntimeException('至少需要启用一名获客医助');
|
||||
return $range['userids'];
|
||||
}
|
||||
|
||||
/** @param list<array{id:int,userid:string}> $members */
|
||||
@@ -918,16 +924,32 @@ class WecomPromotionLogic
|
||||
if ($officialLink === null) {
|
||||
continue;
|
||||
}
|
||||
$firstUserId = '';
|
||||
foreach ((array) ($officialLink['range_userids'] ?? []) as $userIdValue) {
|
||||
$userId = trim((string) $userIdValue);
|
||||
$rangeUserIds = array_values(array_unique(array_filter(array_map(
|
||||
static fn (mixed $value): string => trim((string) $value),
|
||||
(array) ($officialLink['range_userids'] ?? [])
|
||||
), static fn (string $value): bool => $value !== '')));
|
||||
if ($rangeUserIds === []) {
|
||||
continue;
|
||||
}
|
||||
$mappedMembers = [];
|
||||
foreach ($rangeUserIds as $userId) {
|
||||
$mappedAdminId = (int) ($adminIdByUserId[$userId] ?? 0);
|
||||
if ($mappedAdminId <= 0) {
|
||||
// 受限账号或本地缺少映射时禁止部分回填,避免定时任务误删企微中的不可见成员。
|
||||
$mappedMembers = [];
|
||||
break;
|
||||
}
|
||||
$mappedMembers[] = ['userid' => $userId, 'admin_id' => $mappedAdminId];
|
||||
}
|
||||
if (count($mappedMembers) !== count($rangeUserIds)) {
|
||||
continue;
|
||||
}
|
||||
foreach ($mappedMembers as $mappedMember) {
|
||||
$userId = (string) $mappedMember['userid'];
|
||||
$mappedAdminId = (int) $mappedMember['admin_id'];
|
||||
if ($userId === '' || $mappedAdminId <= 0) {
|
||||
continue;
|
||||
}
|
||||
if ($firstUserId === '') {
|
||||
$firstUserId = $userId;
|
||||
}
|
||||
try {
|
||||
Db::name('qywx_promotion_pool_member')->insert([
|
||||
'pool_id' => $poolId,
|
||||
@@ -951,20 +973,8 @@ class WecomPromotionLogic
|
||||
// 并发打开 overview 时唯一键会阻止重复回填。
|
||||
}
|
||||
}
|
||||
if ($firstUserId !== '') {
|
||||
QywxPromotionMemberSchedulerService::initialisePool(
|
||||
$poolId,
|
||||
(int) $officialLink['id'],
|
||||
$firstUserId
|
||||
);
|
||||
// 旧链接可能仍包含多人范围;排队收敛为当前调度成员,官方 URL 本身保持不变。
|
||||
Db::name('qywx_promotion_range_sync')->where('pool_id', $poolId)->update([
|
||||
'status' => 1,
|
||||
'applied_member_id' => 0,
|
||||
'applied_version' => 0,
|
||||
'next_retry' => $now,
|
||||
'update_time' => $now,
|
||||
]);
|
||||
if ($mappedMembers !== []) {
|
||||
QywxPromotionMemberSchedulerService::initialisePool($poolId, (int) $officialLink['id']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user