*/ public static function getOptions(): array { $rows = QywxMediaChannel::where('status', 1) ->field('channel_code, channel_name') ->order('channel_name asc, id asc') ->select() ->toArray(); return array_map(static fn (array $row): array => [ 'code' => (string) ($row['channel_code'] ?? ''), 'name' => (string) ($row['channel_name'] ?? ''), ], $rows); } public static function getDefaultCode(): string { return (string) QywxMediaChannel::where('status', 1) ->order('id asc') ->value('channel_code'); } public static function isValidCode(string $channelCode): bool { $channelCode = trim($channelCode); if ($channelCode === '') { return false; } return QywxMediaChannel::where('status', 1) ->where('channel_code', $channelCode) ->count() > 0; } public static function normalizeStatsCode(string $channelCode): string { return self::isValidCode($channelCode) ? trim($channelCode) : ''; } /** * @return array|null */ public static function getChannelByCode(string $channelCode): ?array { $channelCode = trim($channelCode); if ($channelCode === '') { return null; } $row = QywxMediaChannel::where('status', 1) ->where('channel_code', $channelCode) ->find(); return $row ? $row->toArray() : null; } public static function getNameByCode(string $channelCode): string { $channel = self::getChannelByCode($channelCode); return (string) ($channel['channel_name'] ?? ''); } /** * 挂号老数据渠道:doctor_appointment.channels 对应 dict_data(type_value=channels) 的 value。 * * @param array|null $channel * @return int[] */ public static function getLegacyAppointmentChannelValues(?array $channel): array { if ($channel === null) { return []; } $names = array_values(array_unique(array_filter([ trim((string) ($channel['channel_name'] ?? '')), trim((string) ($channel['source_tag_name'] ?? '')), ]))); if ($names === []) { return []; } $rows = Db::name('dict_data') ->where('type_value', 'channels') ->whereIn('name', $names) ->column('value'); return array_values(array_filter(array_map('intval', $rows), static fn (int $value): bool => $value > 0)); } /** * @param array|null $channel */ public static function applyFollowUsersChannelFilter(Query $query, string $field, ?array $channel): void { if ($channel === null) { return; } $patterns = self::buildLikePatterns($channel); if ($patterns === []) { $query->whereRaw('1 = 0'); return; } $segments = []; $bindings = []; foreach ($patterns as $pattern) { $segments[] = $field . ' LIKE ?'; $bindings[] = $pattern; } $query->whereRaw('(' . implode(' OR ', $segments) . ')', $bindings); } /** * @return array{scanned_contacts: int, discovered_tags: int, inserted_or_updated: int} */ public static function scanFromContacts(int $batchSize = 200): array { $lastId = 0; $scannedContacts = 0; $discoveredTags = []; $upserted = 0; $now = time(); while (true) { $rows = QywxExternalContact::where('id', '>', $lastId) ->field('id, follow_users') ->order('id asc') ->limit($batchSize) ->select() ->toArray(); if ($rows === []) { break; } foreach ($rows as $row) { $lastId = (int) ($row['id'] ?? 0); if ($lastId <= 0) { continue; } $scannedContacts++; $followUsers = json_decode((string) ($row['follow_users'] ?? '[]'), true); $followUsers = is_array($followUsers) ? $followUsers : []; foreach (self::extractTagsFromFollowUsers($followUsers) as $tag) { $tagKey = self::buildTagUniqKey($tag['source_tag_id'], $tag['source_tag_name']); if ($tagKey === '' || isset($discoveredTags[$tagKey])) { continue; } $discoveredTags[$tagKey] = true; $channelCode = self::buildChannelCode($tag['source_tag_id'], $tag['source_tag_name']); $channelName = $tag['source_tag_name'] !== '' ? $tag['source_tag_name'] : $tag['source_tag_id']; $rowData = [ 'channel_code' => $channelCode, 'channel_name' => $channelName, 'source_tag_id' => $tag['source_tag_id'], 'source_tag_name' => $tag['source_tag_name'], 'source_group_name' => $tag['source_group_name'], 'tag_uniq_key' => $tagKey, 'status' => 1, 'last_seen_time' => $now, 'create_time' => $now, 'update_time' => $now, ]; Db::name('qywx_media_channel')->duplicate([ 'channel_name', 'source_tag_id', 'source_tag_name', 'source_group_name', 'status', 'last_seen_time', 'update_time', ])->insert($rowData); $upserted++; } } } return [ 'scanned_contacts' => $scannedContacts, 'discovered_tags' => count($discoveredTags), 'inserted_or_updated' => $upserted, ]; } /** * @param array $channel * @return string[] */ private static function buildLikePatterns(array $channel): array { $patterns = []; $tagId = trim((string) ($channel['source_tag_id'] ?? '')); $tagName = trim((string) ($channel['source_tag_name'] ?? '')); if ($tagId !== '') { $escapedTagId = addcslashes($tagId, '%_\\'); $patterns[] = '%"tag_id":"' . $escapedTagId . '"%'; $patterns[] = '%"id":"' . $escapedTagId . '"%'; } if ($tagName !== '') { $escapedTagName = addcslashes($tagName, '%_\\'); $patterns[] = '%"name":"' . $escapedTagName . '"%'; $patterns[] = '%"tag_name":"' . $escapedTagName . '"%'; } return array_values(array_unique($patterns)); } /** * @param array $followUsers * @return array */ private static function extractTagsFromFollowUsers(array $followUsers): array { $tags = []; foreach ($followUsers as $followUser) { if (!is_array($followUser)) { continue; } $rawTags = $followUser['tags'] ?? []; if (!is_array($rawTags)) { continue; } foreach ($rawTags as $tag) { if (!is_array($tag)) { continue; } $tagId = trim((string) ($tag['tag_id'] ?? $tag['id'] ?? '')); $tagName = trim((string) ($tag['name'] ?? $tag['tag_name'] ?? '')); $groupName = trim((string) ($tag['group_name'] ?? '')); $uniqKey = self::buildTagUniqKey($tagId, $tagName); if ($uniqKey === '') { continue; } $tags[$uniqKey] = [ 'source_tag_id' => $tagId, 'source_tag_name' => $tagName, 'source_group_name' => $groupName, ]; } } return array_values($tags); } private static function buildTagUniqKey(string $tagId, string $tagName): string { $tagId = trim($tagId); $tagName = trim($tagName); if ($tagId !== '') { return 'tag_id:' . $tagId; } if ($tagName !== '') { return 'tag_name:' . md5(mb_strtolower($tagName, 'UTF-8')); } return ''; } private static function buildChannelCode(string $tagId, string $tagName): string { $tagId = trim($tagId); if ($tagId !== '') { return 'tag_' . preg_replace('/[^A-Za-z0-9_\-]/', '_', $tagId); } $normalizedName = trim(mb_strtolower($tagName, 'UTF-8')); return 'tagname_' . substr(md5($normalizedName), 0, 16); } }