更新优化

This commit is contained in:
annali168290-ux
2026-10-10 21:20:27 +07:00
parent a5efae932b
commit cf66f936e5
31 changed files with 2752 additions and 850 deletions
+20 -4
View File
@@ -6,6 +6,18 @@ require __DIR__ . '/im_web_bootstrap.php';
const WEB_MEDIA_PROXY_MAX_BYTES = 15 * 1024 * 1024;
const WEB_MEDIA_PROXY_MAX_SECONDS = 25;
function mediaProxyEnv(string $name, string $default = ''): string
{
if (function_exists('webImEnv')) {
return (string)webImEnv($name, $default);
}
$value = getenv($name);
if ($value === false || trim((string)$value) === '') {
return $default;
}
return trim((string)$value);
}
function failMediaProxy(int $status, string $message): never
{
http_response_code($status);
@@ -18,12 +30,13 @@ function failMediaProxy(int $status, string $message): never
function mediaProxyAllowedHosts(): array
{
$configured = webImEnv('JC_WEB_IM_MEDIA_PROXY_HOSTS');
$configured = mediaProxyEnv('JC_WEB_IM_MEDIA_PROXY_HOSTS');
$hosts = array_map(
static fn(string $host): string => strtolower(rtrim(trim($host), '.')),
preg_split('/[,\s]+/', $configured) ?: []
);
$hosts[] = 'zhiyanim.dfpnm.cn';
return array_values(array_unique(array_filter($hosts)));
}
@@ -53,9 +66,12 @@ function validatedMediaProxyUrl(string $url): array
function mediaProxyCacheRoot(): string
{
$root = rtrim(webImEnv('JC_WEB_IM_MEDIA_CACHE_ROOT', '/var/cache/jc-web-im-media'), '/');
$root = rtrim(mediaProxyEnv('JC_WEB_IM_MEDIA_CACHE_ROOT', '/var/cache/jc-web-im-media'), '/');
if ($root === '' || !is_dir($root) || !is_writable($root)) {
failMediaProxy(503, '媒体缓存目录不可用');
$root = rtrim(sys_get_temp_dir(), '/') . '/jc-web-im-media';
if (!is_dir($root) && !@mkdir($root, 0755, true) && !is_dir($root)) {
failMediaProxy(503, '媒体缓存目录不可用');
}
}
return $root;
@@ -63,7 +79,7 @@ function mediaProxyCacheRoot(): string
function mediaProxyPreferredIps(): array
{
$configured = preg_split('/[,\s]+/', webImEnv('JC_WEB_IM_MEDIA_PROXY_IPS')) ?: [];
$configured = preg_split('/[,\s]+/', mediaProxyEnv('JC_WEB_IM_MEDIA_PROXY_IPS')) ?: [];
$ips = [];
foreach ($configured as $ip) {
$ip = trim($ip);
+50 -3
View File
@@ -78,7 +78,7 @@ function fetchActionMessageRow(string $messageUid): array
return is_array($row) ? $row : [];
}
function ensureMessageEditsTable(): void
function ensureMessageEditsTable(): void
{
imDb()->exec(
'create table if not exists `t_web_message_edits` ('
@@ -93,7 +93,40 @@ function ensureMessageEditsTable(): void
. 'key `idx_edited_at_ms` (`edited_at_ms`)'
. ') engine=InnoDB default charset=utf8mb4'
);
}
}
function ensurePinnedMessagesTable(): void
{
imAppDb()->exec(
'create table if not exists `t_web_pinned_messages` ('
. '`user_id` varchar(64) not null,'
. '`conversation_type` varchar(16) not null,'
. '`target_id` varchar(128) not null,'
. '`message_uid` varchar(32) not null,'
. '`updated_at_ms` bigint not null,'
. 'primary key (`user_id`, `conversation_type`, `target_id`)'
. ') engine=InnoDB default charset=utf8mb4'
);
}
function savePinnedMessage(string $userId, string $conversationType, string $targetId, string $messageUid): void
{
ensurePinnedMessagesTable();
$statement = imAppDb()->prepare(
'insert into `t_web_pinned_messages` (`user_id`, `conversation_type`, `target_id`, `message_uid`, `updated_at_ms`)'
. ' values (?, ?, ?, ?, ?) on duplicate key update `message_uid` = values(`message_uid`), `updated_at_ms` = values(`updated_at_ms`)'
);
$statement->execute([$userId, $conversationType, $targetId, $messageUid, nowTimestampMs()]);
}
function removePinnedMessage(string $userId, string $conversationType, string $targetId): void
{
ensurePinnedMessagesTable();
$statement = imAppDb()->prepare(
'delete from `t_web_pinned_messages` where `user_id` = ? and `conversation_type` = ? and `target_id` = ?'
);
$statement->execute([$userId, $conversationType, $targetId]);
}
function editTextMessage(string $operatorId, string $messageUid, string $text): array
{
@@ -222,7 +255,21 @@ if ($action === '' || $messageUid <= 0) {
respond(400, '缺少操作类型或消息ID');
}
try {
try {
if ($action === 'pin' || $action === 'unpin') {
if ($userId === '') respond(400, '缺少操作者ID');
$targetId = trim((string)($payload['target'] ?? ''));
$conversationType = trim((string)($payload['conversationType'] ?? 'single'));
if ($targetId === '') respond(400, '缺少会话ID');
$userId = resolveActionUserId($userId);
if ($action === 'pin') {
savePinnedMessage($userId, $conversationType, $targetId, $messageUidText);
} else {
removePinnedMessage($userId, $conversationType, $targetId);
}
respond(0, 'success', ['messageUid' => $messageUidText, 'action' => $action]);
}
if ($action === 'recall') {
if ($userId === '') {
respond(400, '缺少操作者ID');
+110 -16
View File
@@ -213,6 +213,9 @@ function attachmentFallbackText(?array $attachment): string
if ((string)$attachment['type'] === 'image') {
return '[图片]';
}
if ((string)$attachment['type'] === 'video') {
return '[视频]';
}
return '[文件] ' . ((string)($attachment['name'] ?? '') ?: '文件');
}
@@ -641,6 +644,9 @@ function nativeMediaUrlScore(string $url): int
if (preg_match('/\.(?:jpe?g|png|gif|webp|bmp|avif)$/i', $path)) {
$score += 30;
}
if (preg_match('/\.(?:mp4|mov|m4v|webm|avi|mkv|3gp)$/i', $path)) {
$score += 30;
}
if (strpos($path, 'image') !== false || strpos($path, 'media') !== false || strpos($path, 'upload') !== false) {
$score += 5;
}
@@ -691,11 +697,21 @@ function nativeAttachmentName(string $url, string $type): string
return $name;
}
return $type === 'image' ? '图片' : '文件';
return $type === 'image' ? '图片' : ($type === 'video' ? '视频' : '文件');
}
function nativeAttachmentMime(string $url, string $type): string
{
if ($type === 'video') {
$path = strtolower((string)(parse_url($url, PHP_URL_PATH) ?: ''));
if (preg_match('/\.webm$/', $path)) {
return 'video/webm';
}
if (preg_match('/\.(?:mov|m4v)$/', $path)) {
return 'video/quicktime';
}
return 'video/mp4';
}
if ($type !== 'image') {
return '';
}
@@ -730,7 +746,7 @@ function nativeAttachmentFromRow(array $row, ?array $attachment): ?array
}
$contentType = (int)($row['_content_type'] ?? 0);
if ($contentType !== 3 && $contentType !== 5) {
if (!in_array($contentType, [3, 5, 6], true)) {
return null;
}
@@ -739,7 +755,7 @@ function nativeAttachmentFromRow(array $row, ?array $attachment): ?array
return null;
}
$type = $contentType === 3 ? 'image' : 'file';
$type = $contentType === 3 ? 'image' : ($contentType === 6 ? 'video' : 'file');
return [
'type' => $type,
@@ -767,7 +783,7 @@ function nativeDisplayText(string $searchableText, int $contentType, bool $hasAt
{
$text = trim($searchableText);
if (isNativePlaceholderText($text, $contentType)) {
return '';
$text = '';
}
if ($text === '' && !$isRecalled) {
return nativeMessageFallbackText($contentType, $hasAttachment);
@@ -776,6 +792,29 @@ function nativeDisplayText(string $searchableText, int $contentType, bool $hasAt
return $text;
}
function nativeRedPacketFromText(string $text): ?array
{
$decoded = json_decode(trim($text), true);
if (!is_array($decoded)) {
return null;
}
$packet = $decoded['redPackets'] ?? $decoded['redPacket'] ?? null;
if (!is_array($packet)) {
return null;
}
return [
'id' => (string)($packet['id'] ?? ''),
'senderId' => (string)($packet['userId'] ?? $packet['senderId'] ?? ''),
'target' => (string)($packet['target'] ?? $decoded['target'] ?? ''),
'blessing' => trim((string)($packet['blessing'] ?? '')) ?: '恭喜发财,大吉大利',
'status' => (int)($packet['status'] ?? 0),
'expireTime' => (string)($packet['expireTime'] ?? ''),
'createDate' => (string)($packet['createDate'] ?? ''),
];
}
function nativeMessageFallbackText(int $contentType, bool $hasAttachment): string
{
if ($hasAttachment) {
@@ -784,9 +823,18 @@ function nativeMessageFallbackText(int $contentType, bool $hasAttachment): strin
if ($contentType === 5) {
return '[文件]';
}
if ($contentType === 6) {
return '[视频加载失败]';
}
if ($contentType === 90) {
return '[红包]';
}
if ($contentType === 92) {
return '以上是打招呼的内容';
}
if ($contentType === 93) {
return '你们已经是好友了,可以开始聊天了。';
}
if ($contentType === 105) {
return '[领取了红包]';
}
@@ -892,13 +940,19 @@ function formatNativeGroupMessage(
$messageId = (string)$row['_mid'];
$timestamp = timestampMs((string)($row['_dt'] ?? ''), $messageId);
$contentType = (int)($row['_content_type'] ?? 0);
$redPacket = nativeRedPacketFromText((string)($row['_searchable_key'] ?? ''));
if ($redPacket !== null) {
$contentType = 90;
}
$isRecalled = $contentType === 80;
$direction = $from === $currentUserId ? 'out' : 'in';
$attachment = nativeAttachmentFromRow($row, $attachment);
$messageType = $attachment
? (string)$attachment['type']
: (in_array($contentType, [1, 80, 90, 105], true) ? 'text' : 'unknown');
$text = nativeDisplayText((string)($row['_searchable_key'] ?? ''), $contentType, $attachment !== null, $isRecalled);
: (in_array($contentType, [1, 80, 90, 92, 93, 105], true) ? 'text' : 'unknown');
$text = $redPacket !== null
? '[红包]'
: nativeDisplayText((string)($row['_searchable_key'] ?? ''), $contentType, $attachment !== null, $isRecalled);
return [
'id' => $messageId,
@@ -929,6 +983,7 @@ function formatNativeGroupMessage(
'contentType' => $contentType,
'text' => attachmentDisplayText($text, $attachment),
'attachment' => $attachment,
'redPacket' => $redPacket,
'status' => $isRecalled && $direction === 'out' ? 'recalled' : 'sent',
'deliveryStatus' => $direction === 'out' && !$isRecalled ? 'sent' : '',
'recalled' => $isRecalled,
@@ -1333,6 +1388,10 @@ function formatMessage(
$messageId = (string)$row['_mid'];
$from = (string)$row['_from'];
$contentType = (int)($row['_content_type'] ?? 0);
$redPacket = nativeRedPacketFromText((string)($row['_searchable_key'] ?? ''));
if ($redPacket !== null) {
$contentType = 90;
}
$isRecalled = $contentType === 80;
$text = (string)($row['_searchable_key'] ?? '');
$timestamp = timestampMs((string)($row['_dt'] ?? ''), $messageId);
@@ -1340,8 +1399,10 @@ function formatMessage(
$attachment = nativeAttachmentFromRow($row, $attachment);
$messageType = $attachment
? (string)$attachment['type']
: (in_array($contentType, [1, 80, 90, 105], true) ? 'text' : 'unknown');
$messageText = nativeDisplayText($text, $contentType, $attachment !== null, $isRecalled);
: (in_array($contentType, [1, 80, 90, 92, 93, 105], true) ? 'text' : 'unknown');
$messageText = $redPacket !== null
? '[红包]'
: nativeDisplayText($text, $contentType, $attachment !== null, $isRecalled);
$messageText = attachmentDisplayText($messageText, $attachment);
$editedAt = (int)($editState['editedAt'] ?? 0);
$activityTimestamp = messageActivityTimestamp($timestamp, $editState);
@@ -1358,9 +1419,12 @@ function formatMessage(
'target' => $peerId,
'direction' => $direction,
'type' => $messageType,
'contentType' => $attachment ? ((string)$attachment['type'] === 'image' ? 3 : 5) : $contentType,
'contentType' => $attachment
? ((string)$attachment['type'] === 'image' ? 3 : ((string)$attachment['type'] === 'video' ? 6 : 5))
: $contentType,
'text' => $messageText,
'attachment' => $attachment,
'redPacket' => $redPacket,
'status' => $isRecalled && $direction === 'out' ? 'recalled' : ($direction === 'out' ? $deliveryStatus : 'sent'),
'deliveryStatus' => $deliveryStatus,
'recalled' => $isRecalled,
@@ -1510,7 +1574,16 @@ function loadRecentConversations(PDO $db, string $userId, int $limit): array
'portrait' => (string)($users[$peerId]['_portrait'] ?? ''),
'avatarText' => mb_strtoupper(mb_substr($title, 0, 1, 'UTF-8'), 'UTF-8'),
'updatedAt' => $message ? $message['activityTimestamp'] : timestampMs((string)($userRow['_dt'] ?? ''), $messageId),
'raw' => ['type' => 'single', 'target' => $peerId, 'line' => 0],
'raw' => [
'type' => 'single',
'target' => $peerId,
'line' => 0,
'userId' => (string)($users[$peerId]['_name'] ?? $peerId),
'userUid' => $peerId,
'displayName' => (string)($users[$peerId]['_display_name'] ?? ''),
'mobile' => (string)($users[$peerId]['_mobile'] ?? ''),
'address' => (string)($users[$peerId]['_address'] ?? ''),
],
];
}
@@ -1536,13 +1609,34 @@ function messageSyncResult(
string $userId,
array $messages,
bool $includeConversations,
int $limit
int $limit,
string $targetId = '',
string $conversationType = 'single'
): array {
$result = ['messages' => $messages];
if ($includeConversations) {
$result['conversations'] = loadRecentConversations($db, $userId, $limit);
}
if ($targetId !== '') {
try {
imAppDb()->exec(
'create table if not exists `t_web_pinned_messages` ('
. '`user_id` varchar(64) not null, `conversation_type` varchar(16) not null,'
. '`target_id` varchar(128) not null, `message_uid` varchar(32) not null,'
. '`updated_at_ms` bigint not null, primary key (`user_id`, `conversation_type`, `target_id`)'
. ') engine=InnoDB default charset=utf8mb4'
);
$statement = imAppDb()->prepare(
'select `message_uid` from `t_web_pinned_messages` where `user_id` = ? and `conversation_type` = ? and `target_id` = ? limit 1'
);
$statement->execute([$userId, $conversationType, $targetId]);
$result['pinnedMessageUid'] = (string)($statement->fetchColumn() ?: '');
} catch (Throwable) {
$result['pinnedMessageUid'] = '';
}
}
return $result;
}
@@ -1571,7 +1665,7 @@ try {
$userId,
loadWebGroupMessages(imAppDb(), $userId, $target, $limit, $markRead),
$includeConversations,
$limit
$limit, $target, 'group'
));
}
if (isNativeGroupTarget($db, $target)) {
@@ -1580,7 +1674,7 @@ try {
$userId,
loadNativeGroupMessages($db, $userId, $target, $limit, $markRead),
$includeConversations,
$limit
$limit, $target, 'group'
));
}
respond(0, 'success', messageSyncResult(
@@ -1588,7 +1682,7 @@ try {
$userId,
[],
$includeConversations,
$limit
$limit, $target, 'group'
));
}
if (isWebGroupTarget(imAppDb(), $target)) {
@@ -1597,7 +1691,7 @@ try {
$userId,
loadWebGroupMessages(imAppDb(), $userId, $target, $limit, $markRead),
$includeConversations,
$limit
$limit, $target, 'group'
));
}
$targetId = resolveUserId($db, $target);
@@ -1606,7 +1700,7 @@ try {
$userId,
loadConversationMessages($db, $userId, $targetId, $limit, $markRead),
$includeConversations,
$limit
$limit, $targetId, 'single'
));
}