prepare( 'select `_uid` from `t_user` where `_uid` = ? or `_mobile` = ? or `_name` = ? limit 1' ); $statement->execute([$value, $value, $value]); $uid = $statement->fetchColumn(); if (is_string($uid) && $uid !== '') { $resolved[] = $uid; } } return array_values(array_unique($resolved)); } function webGroupConversation(array $group, string $role = 'member'): array { $name = (string)($group['name'] ?? $group['_name'] ?? $group['group_id'] ?? $group['_gid'] ?? ''); $groupId = (string)($group['group_id'] ?? $group['_gid'] ?? ''); $category = (string)($group['category'] ?? 'group'); $title = $name !== '' ? $name : $groupId; $updatedAt = (int)($group['updated_at_ms'] ?? $group['_dt'] ?? webImNowMs()); return [ 'id' => 'group-' . $groupId, 'type' => $category === 'channel' ? 'channel' : 'group', 'target' => $groupId, 'title' => $title, 'subtitle' => $category === 'channel' ? '公告群' : '群组', 'unread' => 0, 'avatarText' => mb_strtoupper(mb_substr($title, 0, 1, 'UTF-8'), 'UTF-8'), 'updatedAt' => $updatedAt, 'role' => $role, 'raw' => [ 'type' => 'group', 'target' => $groupId, 'line' => 0, 'category' => $category, 'role' => $role, ], ]; } function fetchWebGroup(PDO $db, string $groupKey): ?array { $statement = $db->prepare( 'select * from `t_web_groups`' . ' where `deleted` = 0 and (`group_id` = ? or `name` = ?)' . ' order by (`group_id` = ?) desc limit 1' ); $statement->execute([$groupKey, $groupKey, $groupKey]); $group = $statement->fetch(); return is_array($group) ? $group : null; } function fetchWebGroupRole(PDO $db, string $groupId, string $userId): string { $statement = $db->prepare( 'select `role` from `t_web_group_members`' . ' where `group_id` = ? and `user_id` = ? and `status` = 0 limit 1' ); $statement->execute([$groupId, $userId]); $role = $statement->fetchColumn(); return is_string($role) && $role !== '' ? $role : 'member'; } function fetchWebGroupMember(PDO $db, string $groupId, string $userId): array { $statement = $db->prepare( 'select * from `t_web_group_members`' . ' where `group_id` = ? and `user_id` = ? and `status` = 0 limit 1' ); $statement->execute([$groupId, $userId]); $member = $statement->fetch(); return is_array($member) ? $member : []; } function requireWebGroupMember(PDO $db, string $groupId, string $userId): array { $member = fetchWebGroupMember($db, $groupId, $userId); if (!$member) { respond(403, '你不在该群组中'); } return $member; } function requireWebGroupRole(PDO $db, string $groupId, string $userId, array $roles): array { $member = requireWebGroupMember($db, $groupId, $userId); if (!in_array((string)$member['role'], $roles, true)) { respond(403, '没有群组管理权限'); } return $member; } function upsertWebGroupMember(PDO $db, string $groupId, string $userId, string $role): void { $now = webImNowMs(); $statement = $db->prepare( 'insert into `t_web_group_members`' . ' (`group_id`, `user_id`, `role`, `joined_at_ms`, `updated_at_ms`, `status`)' . ' values (?, ?, ?, ?, ?, 0)' . ' on duplicate key update' . ' `role` = if(`role` = "owner", `role`, values(`role`)),' . ' `updated_at_ms` = values(`updated_at_ms`),' . ' `status` = 0' ); $statement->execute([$groupId, $userId, $role, $now, $now]); } function createNativeGroup(string $operator, string $groupId, string $name, string $category, array $members): array { $nativeMembers = []; foreach ($members as $memberId) { $nativeMembers[] = [ 'member_id' => $memberId, 'type' => $memberId === $operator ? 2 : 0, ]; } return requireImSuccess(imAdminPost('/admin/group/create', [ 'operator' => $operator, 'group' => [ 'group_info' => [ 'target_id' => $groupId, 'name' => $name, 'owner' => $operator, 'type' => 0, 'mute' => $category === 'channel' ? 1 : 0, 'join_type' => 0, 'searchable' => 1, 'history_message' => 1, 'extra' => json_encode(['webCategory' => $category], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), ], 'members' => $nativeMembers, ], 'to_lines' => [0], ]), '原生群组创建失败'); } function addNativeGroupMember(string $operator, string $groupId, string $userId): void { requireImSuccess(imAdminPost('/admin/group/member/add', [ 'operator' => $operator, 'group_id' => $groupId, 'members' => [ ['member_id' => $userId, 'type' => 0], ], 'to_lines' => [0], ]), '加入原生群组失败'); } function removeNativeGroupMember(string $operator, string $groupId, array $userIds): void { if (!$userIds) { return; } requireImSuccess(imAdminPost('/admin/group/member/del', [ 'operator' => $operator, 'group_id' => $groupId, 'members' => array_values($userIds), 'to_lines' => [0], ]), '移除原生成员失败'); } function dismissNativeGroup(string $operator, string $groupId): void { requireImSuccess(imAdminPost('/admin/group/del', [ 'operator' => $operator, 'group_id' => $groupId, 'to_lines' => [0], ]), '解散原生群组失败'); } function transferNativeGroup(string $operator, string $groupId, string $newOwner): void { requireImSuccess(imAdminPost('/admin/group/transfer', [ 'operator' => $operator, 'group_id' => $groupId, 'new_owner' => $newOwner, 'to_lines' => [0], ]), '转让原生群组失败'); } function sendWebGroupWelcomeMessage(PDO $appDb, string $groupId, string $senderId, string $welcomeText): void { $text = trim($welcomeText); if ($text === '') { return; } $now = webImNowMs(); $messageUid = webImMessageUid(); $statement = $appDb->prepare( 'insert into `t_web_group_messages`' . ' (`message_uid`, `group_id`, `sender_uid`, `text`, `created_at_ms`, `edited_at_ms`, `recalled`)' . ' values (?, ?, ?, ?, ?, 0, 0)' ); $statement->execute([$messageUid, $groupId, $senderId, $text, $now]); $update = $appDb->prepare('update `t_web_groups` set `updated_at_ms` = ? where `group_id` = ?'); $update->execute([$now, $groupId]); try { requireImSuccess(imAdminPost('/admin/message/send', [ 'sender' => $senderId, 'conv' => [ 'type' => 1, 'target' => $groupId, 'line' => 0, ], 'payload' => [ 'type' => 1, 'searchableContent' => $text, 'pushContent' => $text, 'content' => $text, 'persistFlag' => 3, ], 'userMessage' => true, ]), '欢迎词发送失败'); } catch (Throwable) { } } function createWebGroupAction(PDO $imDb, PDO $appDb, string $userId, array $payload, string $category): array { $name = trim((string)($payload['name'] ?? '')); $announcement = trim((string)($payload['announcement'] ?? '')); $welcomeText = trim((string)($payload['welcomeText'] ?? '')); if ($name === '') { respond(400, $category === 'channel' ? '请输入公告群名称' : '请输入群组名称'); } $memberIds = resolveWebUserIds($imDb, webActionList($payload['memberIds'] ?? [])); $memberIds[] = $userId; $memberIds = array_values(array_unique(array_filter($memberIds))); if ($category === 'group' && count($memberIds) < 2) { respond(400, '请选择至少一位联系人'); } $groupId = 'webg_' . bin2hex(random_bytes(8)); $now = webImNowMs(); $nativeStatus = 'pending'; $nativeGroupId = ''; $nativeError = ''; try { $nativeResult = createNativeGroup($userId, $groupId, $name, $category, $memberIds); $nativeGroupId = (string)($nativeResult['group_id'] ?? $nativeResult['groupId'] ?? $groupId); $nativeStatus = 'ok'; } catch (Throwable $error) { $nativeStatus = 'fallback'; $nativeError = mb_substr($error->getMessage() ?: '原生群组创建失败', 0, 255, 'UTF-8'); } $statement = $appDb->prepare( 'insert into `t_web_groups`' . ' (`group_id`, `category`, `name`, `portrait`, `owner_uid`, `join_mode`, `announcement`,' . ' `welcome_text`, `native_group_id`, `native_status`, `native_error`, `created_by`, `created_at_ms`, `updated_at_ms`, `deleted`)' . ' values (?, ?, ?, "", ?, "open", ?, ?, ?, ?, ?, ?, ?, ?, 0)' ); $statement->execute([ $groupId, $category, $name, $userId, $announcement, $welcomeText, $nativeGroupId, $nativeStatus, $nativeError, $userId, $now, $now, ]); foreach ($memberIds as $memberId) { upsertWebGroupMember($appDb, $groupId, $memberId, $memberId === $userId ? 'owner' : 'member'); } sendWebGroupWelcomeMessage($appDb, $groupId, $userId, $welcomeText); $group = fetchWebGroup($appDb, $groupId); return [ 'group' => $group, 'conversation' => webGroupConversation($group ?: [ 'group_id' => $groupId, 'category' => $category, 'name' => $name, 'updated_at_ms' => $now, ], 'owner'), 'nativeStatus' => $nativeStatus, 'nativeError' => $nativeError, ]; } function fetchWebUserMap(PDO $imDb, array $userIds, string $ownerId = ''): array { $userIds = webActionList($userIds); if (!$userIds) { return []; } $statement = $imDb->prepare( 'select `_uid`, `_name`, `_display_name`, `_mobile`, `_portrait` from `t_user`' . ' where `_uid` in (' . appPlaceholders($userIds) . ')' ); $statement->execute($userIds); $users = []; foreach ($statement->fetchAll() as $row) { $uid = (string)$row['_uid']; $displayName = trim((string)($row['_display_name'] ?? '')) ?: trim((string)($row['_name'] ?? '')) ?: trim((string)($row['_mobile'] ?? '')) ?: $uid; $users[$uid] = [ 'uid' => $uid, 'name' => (string)($row['_name'] ?? ''), 'displayName' => $displayName, 'mobile' => (string)($row['_mobile'] ?? ''), 'portrait' => (string)($row['_portrait'] ?? ''), 'contactAlias' => '', 'friendAlias' => '', 'remarkAlias' => '', 'isContact' => false, ]; } if ($ownerId !== '') { $aliasStatement = $imDb->prepare( 'select `_friend_uid`, `_alias` from `t_friend` where `_uid` = ?' . ' and `_friend_uid` in (' . appPlaceholders($userIds) . ')' ); $aliasStatement->execute(array_merge([$ownerId], $userIds)); foreach ($aliasStatement->fetchAll() as $row) { $friendUid = (string)$row['_friend_uid']; if (!isset($users[$friendUid])) { continue; } $users[$friendUid]['isContact'] = true; $users[$friendUid]['friendAlias'] = trim((string)($row['_alias'] ?? '')); } $remarkMap = fetchWebUserRemarkMap(imAppDb(), $ownerId, $userIds); foreach ($remarkMap as $targetUid => $alias) { if (!isset($users[$targetUid])) { continue; } $users[$targetUid]['remarkAlias'] = $alias; } foreach ($users as $uid => $user) { $friendAlias = trim((string)($user['friendAlias'] ?? '')); $remarkAlias = trim((string)($user['remarkAlias'] ?? '')); $users[$uid]['contactAlias'] = $friendAlias !== '' ? $friendAlias : $remarkAlias; } } return $users; } function currentMuteSetting(PDO $appDb, string $groupId): array { $statement = $appDb->prepare('select * from `t_web_group_mute_rules` where `group_id` = ? limit 1'); $statement->execute([$groupId]); $row = $statement->fetch(); if (!is_array($row)) { return [ 'mode' => 'off', 'scheduleType' => 'once', 'startsAtMs' => 0, 'endsAtMs' => 0, 'dailyStart' => '', 'dailyEnd' => '', 'enabled' => false, ]; } return [ 'mode' => (string)$row['mode'], 'scheduleType' => (string)$row['schedule_type'], 'startsAtMs' => (int)$row['starts_at_ms'], 'endsAtMs' => (int)$row['ends_at_ms'], 'dailyStart' => (string)$row['daily_start'], 'dailyEnd' => (string)$row['daily_end'], 'enabled' => (bool)$row['enabled'], ]; } function groupDetailAction(PDO $imDb, PDO $appDb, string $userId, string $groupId): array { $group = fetchWebGroup($appDb, $groupId); if (!$group) { respond(404, '没有找到该群组'); } $member = fetchWebGroupMember($appDb, $groupId, $userId); if (!$member) { respond(403, '你不在该群组中'); } $ownerId = (string)$group['owner_uid']; $users = fetchWebUserMap($imDb, [$ownerId, $userId], $userId); $owner = $users[$ownerId] ?? ['uid' => $ownerId, 'displayName' => $ownerId, 'contactAlias' => '']; $me = $users[$userId] ?? ['uid' => $userId, 'displayName' => $userId]; $myAlias = (string)($member['alias'] ?? ''); return [ 'group' => [ 'groupId' => (string)$group['group_id'], 'name' => (string)$group['name'], 'category' => (string)$group['category'], 'announcement' => (string)($group['announcement'] ?? ''), 'welcomeText' => (string)($group['welcome_text'] ?? ''), 'ownerUid' => $ownerId, 'ownerName' => (string)($owner['contactAlias'] ?? '') !== '' ? (string)$owner['contactAlias'] : (string)$owner['displayName'], 'myRole' => (string)$member['role'], 'myAlias' => $myAlias, 'myDisplayName' => $myAlias !== '' ? $myAlias : (string)$me['displayName'], 'muteSetting' => currentMuteSetting($appDb, (string)$group['group_id']), ], ]; } function groupMembersAction(PDO $imDb, PDO $appDb, string $userId, string $groupId): array { if (!fetchWebGroupMember($appDb, $groupId, $userId)) { respond(403, '你不在该群组中'); } $statement = $appDb->prepare( 'select `user_id`, `role`, `alias`, `joined_at_ms` from `t_web_group_members`' . ' where `group_id` = ? and `status` = 0 order by (`role` = "owner") desc, `joined_at_ms` asc' ); $statement->execute([$groupId]); $members = $statement->fetchAll(); $users = fetchWebUserMap($imDb, array_column($members, 'user_id'), $userId); return [ 'members' => array_map(static function (array $member) use ($users): array { $uid = (string)$member['user_id']; $user = $users[$uid] ?? [ 'uid' => $uid, 'displayName' => $uid, 'mobile' => '', 'portrait' => '', 'contactAlias' => '', 'friendAlias' => '', 'remarkAlias' => '', 'isContact' => false, ]; $alias = trim((string)($member['alias'] ?? '')); $contactAlias = trim((string)($user['contactAlias'] ?? '')); return [ 'uid' => $uid, 'role' => (string)$member['role'], 'alias' => $alias, 'contactAlias' => $contactAlias, 'friendAlias' => trim((string)($user['friendAlias'] ?? '')), 'remarkAlias' => trim((string)($user['remarkAlias'] ?? '')), 'isContact' => (bool)($user['isContact'] ?? false), 'displayName' => $contactAlias !== '' ? $contactAlias : ($alias !== '' ? $alias : (string)$user['displayName']), 'rawDisplayName' => (string)$user['displayName'], 'groupDisplayName' => $alias, 'mobile' => (string)($user['mobile'] ?? ''), 'portrait' => (string)($user['portrait'] ?? ''), ]; }, $members), ]; } function updateGroupAliasAction(PDO $appDb, string $userId, string $groupId, string $alias): array { if (mb_strlen($alias, 'UTF-8') > 64) { respond(400, '本群昵称过长'); } if (!fetchWebGroupMember($appDb, $groupId, $userId)) { respond(403, '你不在该群组中'); } $now = webImNowMs(); $statement = $appDb->prepare( 'update `t_web_group_members` set `alias` = ?, `updated_at_ms` = ?' . ' where `group_id` = ? and `user_id` = ? and `status` = 0' ); $statement->execute([$alias, $now, $groupId, $userId]); return ['alias' => $alias]; } function leaveGroupAction(PDO $appDb, string $userId, string $groupId): array { $group = fetchWebGroup($appDb, $groupId); if (!$group) { respond(404, '没有找到该群组'); } $member = fetchWebGroupMember($appDb, $groupId, $userId); if (!$member) { respond(403, '你不在该群组中'); } $now = webImNowMs(); $statement = $appDb->prepare( 'update `t_web_group_members` set `status` = 1, `updated_at_ms` = ?' . ' where `group_id` = ? and `user_id` = ?' ); $statement->execute([$now, $groupId, $userId]); $groupUpdate = $appDb->prepare('update `t_web_groups` set `updated_at_ms` = ? where `group_id` = ?'); $groupUpdate->execute([$now, $groupId]); try { requireImSuccess(imAdminPost('/admin/group/member/quit', [ 'operator' => $userId, 'group_id' => $groupId, 'to_lines' => [0], ]), '退出原生群组失败'); } catch (Throwable) { } return ['groupId' => $groupId]; } function dismissGroupAction(PDO $appDb, string $userId, string $groupId): array { $group = fetchWebGroup($appDb, $groupId); if (!$group) { respond(404, '没有找到该群组'); } requireWebGroupRole($appDb, $groupId, $userId, ['owner']); $now = webImNowMs(); $update = $appDb->prepare('update `t_web_groups` set `deleted` = 1, `updated_at_ms` = ? where `group_id` = ?'); $update->execute([$now, $groupId]); $memberUpdate = $appDb->prepare('update `t_web_group_members` set `status` = 1, `updated_at_ms` = ? where `group_id` = ?'); $memberUpdate->execute([$now, $groupId]); try { dismissNativeGroup($userId, $groupId); } catch (Throwable) { } return ['groupId' => $groupId]; } function transferGroupAction(PDO $appDb, string $userId, string $groupId, string $targetUserId): array { if ($targetUserId === '' || $targetUserId === $userId) { respond(400, '请选择新的群主'); } $group = fetchWebGroup($appDb, $groupId); if (!$group) { respond(404, '没有找到该群组'); } requireWebGroupRole($appDb, $groupId, $userId, ['owner']); $target = requireWebGroupMember($appDb, $groupId, $targetUserId); if ((string)$target['role'] === 'owner') { respond(400, '该成员已经是群主'); } $now = webImNowMs(); $appDb->beginTransaction(); try { $groupUpdate = $appDb->prepare('update `t_web_groups` set `owner_uid` = ?, `updated_at_ms` = ? where `group_id` = ?'); $groupUpdate->execute([$targetUserId, $now, $groupId]); $oldOwnerUpdate = $appDb->prepare( 'update `t_web_group_members` set `role` = "member", `updated_at_ms` = ?' . ' where `group_id` = ? and `user_id` = ?' ); $oldOwnerUpdate->execute([$now, $groupId, $userId]); $newOwnerUpdate = $appDb->prepare( 'update `t_web_group_members` set `role` = "owner", `updated_at_ms` = ?' . ' where `group_id` = ? and `user_id` = ?' ); $newOwnerUpdate->execute([$now, $groupId, $targetUserId]); $appDb->commit(); } catch (Throwable $error) { $appDb->rollBack(); throw $error; } try { transferNativeGroup($userId, $groupId, $targetUserId); } catch (Throwable) { } return ['groupId' => $groupId, 'newOwner' => $targetUserId]; } function setGroupAdminsAction(PDO $appDb, string $userId, string $groupId, array $adminIds): array { requireWebGroupRole($appDb, $groupId, $userId, ['owner']); $adminIds = array_values(array_unique(array_filter(array_map('strval', $adminIds), static fn(string $uid): bool => $uid !== $userId))); $now = webImNowMs(); $reset = $appDb->prepare( 'update `t_web_group_members` set `role` = "member", `updated_at_ms` = ?' . ' where `group_id` = ? and `role` = "admin" and `status` = 0' ); $reset->execute([$now, $groupId]); if ($adminIds) { $update = $appDb->prepare( 'update `t_web_group_members` set `role` = "admin", `updated_at_ms` = ?' . ' where `group_id` = ? and `user_id` in (' . appPlaceholders($adminIds) . ')' . ' and `role` <> "owner" and `status` = 0' ); $update->execute(array_merge([$now, $groupId], $adminIds)); } return ['adminIds' => $adminIds]; } function updateGroupProfileAction(PDO $appDb, string $userId, string $groupId, array $payload): array { requireWebGroupRole($appDb, $groupId, $userId, ['owner', 'admin']); $name = trim((string)($payload['name'] ?? '')); $announcement = trim((string)($payload['announcement'] ?? '')); $welcomeText = trim((string)($payload['welcomeText'] ?? '')); if ($name === '') { respond(400, '请输入群名称'); } if (mb_strlen($name, 'UTF-8') > 128) { respond(400, '群名称过长'); } $now = webImNowMs(); $statement = $appDb->prepare( 'update `t_web_groups` set `name` = ?, `announcement` = ?, `welcome_text` = ?, `updated_at_ms` = ?' . ' where `group_id` = ? and `deleted` = 0' ); $statement->execute([$name, $announcement, $welcomeText, $now, $groupId]); return [ 'groupId' => $groupId, 'name' => $name, 'announcement' => $announcement, 'welcomeText' => $welcomeText, 'updatedAt' => $now, ]; } function kickGroupMembersAction(PDO $appDb, string $userId, string $groupId, array $targetIds): array { $operator = requireWebGroupRole($appDb, $groupId, $userId, ['owner', 'admin']); $operatorRole = (string)$operator['role']; $targetIds = array_values(array_unique(array_filter(array_map('strval', $targetIds), static fn(string $uid): bool => $uid !== ''))); $targetIds = array_values(array_filter($targetIds, static fn(string $uid): bool => $uid !== $userId)); if (!$targetIds) { respond(400, '请选择要移除的群员'); } $statement = $appDb->prepare( 'select `user_id`, `role` from `t_web_group_members`' . ' where `group_id` = ? and `user_id` in (' . appPlaceholders($targetIds) . ') and `status` = 0' ); $statement->execute(array_merge([$groupId], $targetIds)); $targets = $statement->fetchAll(); if (!$targets) { respond(400, '没有可移除的群员'); } $allowedIds = []; foreach ($targets as $target) { $role = (string)$target['role']; if ($role === 'owner') { continue; } if ($operatorRole === 'admin' && $role !== 'member') { continue; } $allowedIds[] = (string)$target['user_id']; } if (!$allowedIds) { respond(403, '没有可移除的群员'); } $now = webImNowMs(); $update = $appDb->prepare( 'update `t_web_group_members` set `status` = 2, `updated_at_ms` = ?' . ' where `group_id` = ? and `user_id` in (' . appPlaceholders($allowedIds) . ')' ); $update->execute(array_merge([$now, $groupId], $allowedIds)); try { removeNativeGroupMember($userId, $groupId, $allowedIds); } catch (Throwable) { } return ['removedIds' => $allowedIds]; } function updateMuteSettingAction(PDO $appDb, string $userId, string $groupId, array $payload): array { requireWebGroupRole($appDb, $groupId, $userId, ['owner', 'admin']); $mode = trim((string)($payload['mode'] ?? 'off')); $allowedModes = ['off', 'all_include_admins', 'all_except_admins']; if (!in_array($mode, $allowedModes, true)) { respond(400, '禁言模式不正确'); } $scheduleType = trim((string)($payload['scheduleType'] ?? 'once')); if (!in_array($scheduleType, ['manual', 'once', 'daily'], true)) { respond(400, '禁言时段类型不正确'); } $startsAtMs = max(0, (int)($payload['startsAtMs'] ?? 0)); $endsAtMs = max(0, (int)($payload['endsAtMs'] ?? 0)); $dailyStart = trim((string)($payload['dailyStart'] ?? '')); $dailyEnd = trim((string)($payload['dailyEnd'] ?? '')); $enabled = $mode !== 'off'; if ($enabled && $scheduleType === 'once' && ($startsAtMs <= 0 || $endsAtMs <= $startsAtMs)) { respond(400, '请设置有效的单次禁言时段'); } if ($enabled && $scheduleType === 'daily' && (!preg_match('/^\d{2}:\d{2}$/', $dailyStart) || !preg_match('/^\d{2}:\d{2}$/', $dailyEnd))) { respond(400, '请设置有效的每日禁言时段'); } $now = webImNowMs(); $statement = $appDb->prepare( 'insert into `t_web_group_mute_rules`' . ' (`group_id`, `mode`, `schedule_type`, `starts_at_ms`, `ends_at_ms`, `daily_start`, `daily_end`, `enabled`, `created_by`, `updated_at_ms`)' . ' values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)' . ' on duplicate key update' . ' `mode` = values(`mode`),' . ' `schedule_type` = values(`schedule_type`),' . ' `starts_at_ms` = values(`starts_at_ms`),' . ' `ends_at_ms` = values(`ends_at_ms`),' . ' `daily_start` = values(`daily_start`),' . ' `daily_end` = values(`daily_end`),' . ' `enabled` = values(`enabled`),' . ' `created_by` = values(`created_by`),' . ' `updated_at_ms` = values(`updated_at_ms`)' ); $statement->execute([$groupId, $mode, $scheduleType, $startsAtMs, $endsAtMs, $dailyStart, $dailyEnd, $enabled ? 1 : 0, $userId, $now]); return ['muteSetting' => currentMuteSetting($appDb, $groupId)]; } function joinWebGroupAction(PDO $appDb, string $userId, string $groupKey): array { if ($groupKey === '') { respond(400, '请输入群组ID或群名称'); } $group = fetchWebGroup($appDb, $groupKey); if (!$group) { try { $native = requireImSuccess(imAdminPost('/admin/group/get_info', ['groupId' => $groupKey]), '群组不存在'); $now = webImNowMs(); $statement = $appDb->prepare( 'insert into `t_web_groups`' . ' (`group_id`, `category`, `name`, `portrait`, `owner_uid`, `join_mode`, `announcement`,' . ' `native_group_id`, `native_status`, `created_by`, `created_at_ms`, `updated_at_ms`, `deleted`)' . ' values (?, "group", ?, ?, ?, "open", "", ?, "ok", ?, ?, ?, 0)' . ' on duplicate key update `updated_at_ms` = values(`updated_at_ms`)' ); $statement->execute([ $groupKey, (string)($native['name'] ?? $groupKey), (string)($native['portrait'] ?? ''), (string)($native['owner'] ?? ''), $groupKey, $userId, $now, $now, ]); $group = fetchWebGroup($appDb, $groupKey); } catch (Throwable) { respond(404, '没有找到该群组'); } } $groupId = (string)$group['group_id']; upsertWebGroupMember($appDb, $groupId, $userId, 'member'); try { addNativeGroupMember((string)($group['owner_uid'] ?: $userId), $groupId, $userId); } catch (Throwable $error) { // Web membership is authoritative for this web build; native group sync is best-effort. } $role = fetchWebGroupRole($appDb, $groupId, $userId); return [ 'group' => fetchWebGroup($appDb, $groupId), 'conversation' => webGroupConversation($group, $role), ]; } function sendBroadcastAction(PDO $imDb, PDO $appDb, string $userId, array $payload): array { $text = trim((string)($payload['text'] ?? '')); if ($text === '') { respond(400, '请输入群发内容'); } $targetIds = resolveWebUserIds($imDb, webActionList($payload['targetIds'] ?? [])); $targetIds = array_values(array_filter(array_unique($targetIds), static fn(string $uid): bool => $uid !== $userId)); if (!$targetIds) { respond(400, '请选择群发对象'); } $results = []; foreach ($targetIds as $targetId) { try { $result = requireImSuccess(imAdminPost('/admin/message/send', [ 'sender' => $userId, 'conv' => [ 'type' => 0, 'target' => $targetId, 'line' => 0, ], 'payload' => [ 'type' => 1, 'searchableContent' => $text, 'pushContent' => $text, 'content' => $text, 'persistFlag' => 3, ], 'userMessage' => true, ]), '发送失败'); $results[] = [ 'targetId' => $targetId, 'ok' => true, 'messageUid' => isset($result['messageUid']) ? (string)$result['messageUid'] : '', ]; } catch (Throwable $error) { $results[] = [ 'targetId' => $targetId, 'ok' => false, 'error' => $error->getMessage() ?: '发送失败', ]; } } $successCount = count(array_filter($results, static fn(array $item): bool => !empty($item['ok']))); $failedCount = count($results) - $successCount; $now = webImNowMs(); $statement = $appDb->prepare( 'insert into `t_web_broadcast_logs`' . ' (`sender_uid`, `target_count`, `success_count`, `failed_count`, `text`, `target_json`, `result_json`, `created_at_ms`)' . ' values (?, ?, ?, ?, ?, ?, ?, ?)' ); $statement->execute([ $userId, count($targetIds), $successCount, $failedCount, $text, json_encode($targetIds, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), json_encode($results, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), $now, ]); return [ 'targetCount' => count($targetIds), 'successCount' => $successCount, 'failedCount' => $failedCount, 'results' => $results, ]; } $payload = readJsonPayload(); $action = trim((string)($payload['action'] ?? '')); $userId = trim((string)($payload['userId'] ?? '')); if ($userId === '') { respond(400, '缺少当前用户ID'); } try { ensureWebImAppTables(); $imDb = imDb(); $appDb = imAppDb(); if ($action === 'create_group') { respond(0, 'success', createWebGroupAction($imDb, $appDb, $userId, $payload, 'group')); } if ($action === 'create_channel') { respond(0, 'success', createWebGroupAction($imDb, $appDb, $userId, $payload, 'channel')); } if ($action === 'join_group') { respond(0, 'success', joinWebGroupAction($appDb, $userId, trim((string)($payload['groupKey'] ?? '')))); } if ($action === 'group_detail') { respond(0, 'success', groupDetailAction($imDb, $appDb, $userId, trim((string)($payload['groupId'] ?? '')))); } if ($action === 'group_members') { respond(0, 'success', groupMembersAction($imDb, $appDb, $userId, trim((string)($payload['groupId'] ?? '')))); } if ($action === 'update_group_alias') { respond(0, 'success', updateGroupAliasAction( $appDb, $userId, trim((string)($payload['groupId'] ?? '')), trim((string)($payload['alias'] ?? '')) )); } if ($action === 'leave_group') { respond(0, 'success', leaveGroupAction($appDb, $userId, trim((string)($payload['groupId'] ?? '')))); } if ($action === 'dismiss_group') { respond(0, 'success', dismissGroupAction($appDb, $userId, trim((string)($payload['groupId'] ?? '')))); } if ($action === 'transfer_group') { respond(0, 'success', transferGroupAction( $appDb, $userId, trim((string)($payload['groupId'] ?? '')), trim((string)($payload['targetUserId'] ?? '')) )); } if ($action === 'set_group_admins') { respond(0, 'success', setGroupAdminsAction( $appDb, $userId, trim((string)($payload['groupId'] ?? '')), is_array($payload['adminIds'] ?? null) ? $payload['adminIds'] : [] )); } if ($action === 'update_group_profile') { respond(0, 'success', updateGroupProfileAction( $appDb, $userId, trim((string)($payload['groupId'] ?? '')), $payload )); } if ($action === 'kick_group_members') { respond(0, 'success', kickGroupMembersAction( $appDb, $userId, trim((string)($payload['groupId'] ?? '')), is_array($payload['targetIds'] ?? null) ? $payload['targetIds'] : [] )); } if ($action === 'update_mute_setting') { respond(0, 'success', updateMuteSettingAction( $appDb, $userId, trim((string)($payload['groupId'] ?? '')), $payload )); } if ($action === 'broadcast') { $result = sendBroadcastAction($imDb, $appDb, $userId, $payload); if ((int)$result['successCount'] <= 0) { respond(500, '群发失败', $result); } respond(0, 'success', $result); } respond(400, '未知操作'); } catch (Throwable $error) { respond(500, $error->getMessage() ?: '会话操作失败'); }