perf: cache media and coalesce realtime sync
This commit is contained in:
@@ -6,6 +6,10 @@ server {
|
||||
root /var/www/im-web;
|
||||
index index.html;
|
||||
client_max_body_size 100m;
|
||||
gzip on;
|
||||
gzip_vary on;
|
||||
gzip_min_length 1024;
|
||||
gzip_types application/json;
|
||||
|
||||
location = / {
|
||||
try_files /index.html =404;
|
||||
@@ -18,6 +22,13 @@ server {
|
||||
add_header Cache-Control "no-store";
|
||||
}
|
||||
|
||||
location = /api/media_proxy.php {
|
||||
include snippets/fastcgi-php.conf;
|
||||
fastcgi_param PHP_ADMIN_VALUE "open_basedir=/var/www/im-web/:/tmp/";
|
||||
fastcgi_pass unix:/run/php/php8.3-fpm.sock;
|
||||
fastcgi_read_timeout 300;
|
||||
}
|
||||
|
||||
location ~ ^/api/.+\.php$ {
|
||||
include snippets/fastcgi-php.conf;
|
||||
fastcgi_param PHP_ADMIN_VALUE "open_basedir=/var/www/im-web/:/tmp/";
|
||||
|
||||
Vendored
+51
-15
@@ -1521,11 +1521,27 @@ function loadRecentConversations(PDO $db, string $userId, int $limit): array
|
||||
return array_slice($conversations, 0, $limit);
|
||||
}
|
||||
|
||||
function messageSyncResult(
|
||||
PDO $db,
|
||||
string $userId,
|
||||
array $messages,
|
||||
bool $includeConversations,
|
||||
int $limit
|
||||
): array {
|
||||
$result = ['messages' => $messages];
|
||||
if ($includeConversations) {
|
||||
$result['conversations'] = loadRecentConversations($db, $userId, $limit);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
$payload = readJsonPayload();
|
||||
$userId = trim((string)($payload['userId'] ?? ''));
|
||||
$target = trim((string)($payload['target'] ?? ''));
|
||||
$limit = clampLimit($payload['limit'] ?? 50, 50, 100);
|
||||
$markRead = payloadBool($payload['markRead'] ?? false);
|
||||
$includeConversations = payloadBool($payload['includeConversations'] ?? false);
|
||||
$conversationType = normalizeConversationType($payload['conversationType'] ?? $payload['type'] ?? 0);
|
||||
|
||||
if ($userId === '') {
|
||||
@@ -1540,28 +1556,48 @@ try {
|
||||
if ($target !== '') {
|
||||
if ($conversationType === 'group') {
|
||||
if (isWebGroupTarget(imAppDb(), $target)) {
|
||||
respond(0, 'success', [
|
||||
'messages' => loadWebGroupMessages(imAppDb(), $userId, $target, $limit, $markRead),
|
||||
]);
|
||||
respond(0, 'success', messageSyncResult(
|
||||
$db,
|
||||
$userId,
|
||||
loadWebGroupMessages(imAppDb(), $userId, $target, $limit, $markRead),
|
||||
$includeConversations,
|
||||
$limit
|
||||
));
|
||||
}
|
||||
if (isNativeGroupTarget($db, $target)) {
|
||||
respond(0, 'success', [
|
||||
'messages' => loadNativeGroupMessages($db, $userId, $target, $limit, $markRead),
|
||||
]);
|
||||
respond(0, 'success', messageSyncResult(
|
||||
$db,
|
||||
$userId,
|
||||
loadNativeGroupMessages($db, $userId, $target, $limit, $markRead),
|
||||
$includeConversations,
|
||||
$limit
|
||||
));
|
||||
}
|
||||
respond(0, 'success', [
|
||||
'messages' => [],
|
||||
]);
|
||||
respond(0, 'success', messageSyncResult(
|
||||
$db,
|
||||
$userId,
|
||||
[],
|
||||
$includeConversations,
|
||||
$limit
|
||||
));
|
||||
}
|
||||
if (isWebGroupTarget(imAppDb(), $target)) {
|
||||
respond(0, 'success', [
|
||||
'messages' => loadWebGroupMessages(imAppDb(), $userId, $target, $limit, $markRead),
|
||||
]);
|
||||
respond(0, 'success', messageSyncResult(
|
||||
$db,
|
||||
$userId,
|
||||
loadWebGroupMessages(imAppDb(), $userId, $target, $limit, $markRead),
|
||||
$includeConversations,
|
||||
$limit
|
||||
));
|
||||
}
|
||||
$targetId = resolveUserId($db, $target);
|
||||
respond(0, 'success', [
|
||||
'messages' => loadConversationMessages($db, $userId, $targetId, $limit, $markRead),
|
||||
]);
|
||||
respond(0, 'success', messageSyncResult(
|
||||
$db,
|
||||
$userId,
|
||||
loadConversationMessages($db, $userId, $targetId, $limit, $markRead),
|
||||
$includeConversations,
|
||||
$limit
|
||||
));
|
||||
}
|
||||
|
||||
respond(0, 'success', [
|
||||
|
||||
Vendored
+1
-1
@@ -1 +1 @@
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Web IM</title><link rel="icon" href="img/5IuOieu9PF8MJtp43kD2kCkdLVjVwDp7.ico"><script src="config/runtime-config.js"></script><script defer="defer" src="js/chunk-vendors.86756863.js"></script><script defer="defer" src="js/app.a89973f7.js"></script><link href="css/app.2c52dabb.css" rel="stylesheet"></head><body><noscript>请启用 JavaScript 后使用 Web IM。</noscript><div id="app"></div></body></html>
|
||||
<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Web IM</title><link rel="icon" href="img/5IuOieu9PF8MJtp43kD2kCkdLVjVwDp7.ico"><script src="config/runtime-config.js"></script><script defer="defer" src="js/chunk-vendors.86756863.js"></script><script defer="defer" src="js/app.631b8b2d.js"></script><link href="css/app.2c52dabb.css" rel="stylesheet"></head><body><noscript>请启用 JavaScript 后使用 Web IM。</noscript><div id="app"></div></body></html>
|
||||
+34
-8
@@ -6281,7 +6281,8 @@ class HttpImClient {
|
||||
target: conversation.target || conversation.raw?.target || conversation.id.replace(/^single-/, ''),
|
||||
limit,
|
||||
conversationType: conversation.type || conversation.raw?.type || 0,
|
||||
markRead: Boolean(options.markRead)
|
||||
markRead: Boolean(options.markRead),
|
||||
includeConversations: Boolean(options.includeConversations)
|
||||
}, {
|
||||
withCredentials: false
|
||||
});
|
||||
@@ -6293,6 +6294,19 @@ class HttpImClient {
|
||||
displayTimestamp: message.displayTimestamp || message.timestamp,
|
||||
conversationId: conversation.id
|
||||
}));
|
||||
if (options.includeConversations && Array.isArray(response.data?.result?.conversations)) {
|
||||
const remoteConversations = response.data.result.conversations.map(remoteConversation => ({
|
||||
...remoteConversation,
|
||||
updatedAt: Number(remoteConversation.updatedAt || Date.now()),
|
||||
avatarText: remoteConversation.avatarText || String(remoteConversation.title || remoteConversation.target || '?').slice(0, 1).toUpperCase(),
|
||||
raw: remoteConversation.raw || {
|
||||
type: 'single',
|
||||
target: remoteConversation.target,
|
||||
line: 0
|
||||
}
|
||||
}));
|
||||
this.emit('conversationsChanged', remoteConversations);
|
||||
}
|
||||
const userId = this.currentUser?.userId || '';
|
||||
const messagesByConversation = readLocalMessages(userId);
|
||||
messagesByConversation[conversation.id] = mergeSyncedMessages(messagesByConversation[conversation.id] || [], messages, conversation.id, userId);
|
||||
@@ -8015,6 +8029,7 @@ const useConversationStore = (0,pinia__WEBPACK_IMPORTED_MODULE_19__/* .defineSto
|
||||
readPresenceEventsBound: false,
|
||||
syncInFlight: false,
|
||||
pendingReadReceiptConversationId: '',
|
||||
pendingConversationSync: false,
|
||||
conversationSyncInFlight: false,
|
||||
messageSyncTimer: null,
|
||||
messageSyncQueuedAt: 0,
|
||||
@@ -8126,6 +8141,7 @@ const useConversationStore = (0,pinia__WEBPACK_IMPORTED_MODULE_19__/* .defineSto
|
||||
this.syncInFlight = false;
|
||||
this.conversationSyncInFlight = false;
|
||||
this.pendingReadReceiptConversationId = '';
|
||||
this.pendingConversationSync = false;
|
||||
this.lastReadSyncByConversation = {};
|
||||
this.activeId = '';
|
||||
this.conversations = [];
|
||||
@@ -8560,6 +8576,9 @@ const useConversationStore = (0,pinia__WEBPACK_IMPORTED_MODULE_19__/* .defineSto
|
||||
if (wantsReadReceipt) {
|
||||
this.pendingReadReceiptConversationId = conversation.id;
|
||||
}
|
||||
if (options.includeConversations) {
|
||||
this.pendingConversationSync = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.syncInFlight = true;
|
||||
@@ -8567,7 +8586,8 @@ const useConversationStore = (0,pinia__WEBPACK_IMPORTED_MODULE_19__/* .defineSto
|
||||
const previousMessages = this.messagesByConversation[syncedConversationId] || [];
|
||||
try {
|
||||
const messages = await _services_imClient__WEBPACK_IMPORTED_MODULE_20__/* .imClient */ .wD.fetchRemoteMessages(conversation, 50, {
|
||||
markRead
|
||||
markRead,
|
||||
includeConversations: Boolean(options.includeConversations)
|
||||
});
|
||||
if (markRead) {
|
||||
this.lastReadSyncByConversation = {
|
||||
@@ -8608,10 +8628,13 @@ const useConversationStore = (0,pinia__WEBPACK_IMPORTED_MODULE_19__/* .defineSto
|
||||
// Keep the local view usable if a polling request misses.
|
||||
} finally {
|
||||
this.syncInFlight = false;
|
||||
if (this.pendingReadReceiptConversationId === this.activeId && canMarkReadNow()) {
|
||||
if ((this.pendingReadReceiptConversationId === this.activeId || this.pendingConversationSync) && canMarkReadNow()) {
|
||||
const includeConversations = this.pendingConversationSync;
|
||||
this.pendingReadReceiptConversationId = '';
|
||||
this.pendingConversationSync = false;
|
||||
this.scheduleActiveMessagesSync({
|
||||
allowReadReceipt: true
|
||||
allowReadReceipt: true,
|
||||
includeConversations
|
||||
}, 700);
|
||||
}
|
||||
}
|
||||
@@ -8627,7 +8650,8 @@ const useConversationStore = (0,pinia__WEBPACK_IMPORTED_MODULE_19__/* .defineSto
|
||||
this.pendingMessageSyncOptions = {
|
||||
...this.pendingMessageSyncOptions,
|
||||
allowReadReceipt: Boolean(this.pendingMessageSyncOptions.allowReadReceipt || options.allowReadReceipt),
|
||||
userAction: Boolean(this.pendingMessageSyncOptions.userAction || options.userAction)
|
||||
userAction: Boolean(this.pendingMessageSyncOptions.userAction || options.userAction),
|
||||
includeConversations: Boolean(this.pendingMessageSyncOptions.includeConversations || options.includeConversations)
|
||||
};
|
||||
const maxWait = 1200;
|
||||
const runAt = Math.min(now + Math.max(0, Number(delay) || 0), this.messageSyncQueuedAt + maxWait);
|
||||
@@ -8664,11 +8688,13 @@ const useConversationStore = (0,pinia__WEBPACK_IMPORTED_MODULE_19__/* .defineSto
|
||||
}
|
||||
window.clearTimeout(this.realtimeSyncTimer);
|
||||
this.realtimeSyncTimer = window.setTimeout(() => {
|
||||
this.refreshRemoteConversations();
|
||||
if (this.activeId) {
|
||||
this.scheduleActiveMessagesSync({
|
||||
allowReadReceipt: canMarkReadNow()
|
||||
allowReadReceipt: canMarkReadNow(),
|
||||
includeConversations: true
|
||||
}, 700);
|
||||
} else {
|
||||
this.refreshRemoteConversations();
|
||||
}
|
||||
}, 500);
|
||||
},
|
||||
@@ -9874,4 +9900,4 @@ const __exports__ = _NavigationRail_vue_vue_type_script_setup_true_lang_js__WEBP
|
||||
/******/
|
||||
/******/ })()
|
||||
;
|
||||
//# sourceMappingURL=app.a89973f7.js.map
|
||||
//# sourceMappingURL=app.631b8b2d.js.map
|
||||
Vendored
+1
File diff suppressed because one or more lines are too long
Vendored
-1
File diff suppressed because one or more lines are too long
+51
-15
@@ -1521,11 +1521,27 @@ function loadRecentConversations(PDO $db, string $userId, int $limit): array
|
||||
return array_slice($conversations, 0, $limit);
|
||||
}
|
||||
|
||||
function messageSyncResult(
|
||||
PDO $db,
|
||||
string $userId,
|
||||
array $messages,
|
||||
bool $includeConversations,
|
||||
int $limit
|
||||
): array {
|
||||
$result = ['messages' => $messages];
|
||||
if ($includeConversations) {
|
||||
$result['conversations'] = loadRecentConversations($db, $userId, $limit);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
$payload = readJsonPayload();
|
||||
$userId = trim((string)($payload['userId'] ?? ''));
|
||||
$target = trim((string)($payload['target'] ?? ''));
|
||||
$limit = clampLimit($payload['limit'] ?? 50, 50, 100);
|
||||
$markRead = payloadBool($payload['markRead'] ?? false);
|
||||
$includeConversations = payloadBool($payload['includeConversations'] ?? false);
|
||||
$conversationType = normalizeConversationType($payload['conversationType'] ?? $payload['type'] ?? 0);
|
||||
|
||||
if ($userId === '') {
|
||||
@@ -1540,28 +1556,48 @@ try {
|
||||
if ($target !== '') {
|
||||
if ($conversationType === 'group') {
|
||||
if (isWebGroupTarget(imAppDb(), $target)) {
|
||||
respond(0, 'success', [
|
||||
'messages' => loadWebGroupMessages(imAppDb(), $userId, $target, $limit, $markRead),
|
||||
]);
|
||||
respond(0, 'success', messageSyncResult(
|
||||
$db,
|
||||
$userId,
|
||||
loadWebGroupMessages(imAppDb(), $userId, $target, $limit, $markRead),
|
||||
$includeConversations,
|
||||
$limit
|
||||
));
|
||||
}
|
||||
if (isNativeGroupTarget($db, $target)) {
|
||||
respond(0, 'success', [
|
||||
'messages' => loadNativeGroupMessages($db, $userId, $target, $limit, $markRead),
|
||||
]);
|
||||
respond(0, 'success', messageSyncResult(
|
||||
$db,
|
||||
$userId,
|
||||
loadNativeGroupMessages($db, $userId, $target, $limit, $markRead),
|
||||
$includeConversations,
|
||||
$limit
|
||||
));
|
||||
}
|
||||
respond(0, 'success', [
|
||||
'messages' => [],
|
||||
]);
|
||||
respond(0, 'success', messageSyncResult(
|
||||
$db,
|
||||
$userId,
|
||||
[],
|
||||
$includeConversations,
|
||||
$limit
|
||||
));
|
||||
}
|
||||
if (isWebGroupTarget(imAppDb(), $target)) {
|
||||
respond(0, 'success', [
|
||||
'messages' => loadWebGroupMessages(imAppDb(), $userId, $target, $limit, $markRead),
|
||||
]);
|
||||
respond(0, 'success', messageSyncResult(
|
||||
$db,
|
||||
$userId,
|
||||
loadWebGroupMessages(imAppDb(), $userId, $target, $limit, $markRead),
|
||||
$includeConversations,
|
||||
$limit
|
||||
));
|
||||
}
|
||||
$targetId = resolveUserId($db, $target);
|
||||
respond(0, 'success', [
|
||||
'messages' => loadConversationMessages($db, $userId, $targetId, $limit, $markRead),
|
||||
]);
|
||||
respond(0, 'success', messageSyncResult(
|
||||
$db,
|
||||
$userId,
|
||||
loadConversationMessages($db, $userId, $targetId, $limit, $markRead),
|
||||
$includeConversations,
|
||||
$limit
|
||||
));
|
||||
}
|
||||
|
||||
respond(0, 'success', [
|
||||
|
||||
@@ -379,7 +379,8 @@ export class HttpImClient {
|
||||
target: conversation.target || conversation.raw?.target || conversation.id.replace(/^single-/, ''),
|
||||
limit,
|
||||
conversationType: conversation.type || conversation.raw?.type || 0,
|
||||
markRead: Boolean(options.markRead)
|
||||
markRead: Boolean(options.markRead),
|
||||
includeConversations: Boolean(options.includeConversations)
|
||||
}, { withCredentials: false });
|
||||
|
||||
if (response.data?.code !== 0) {
|
||||
@@ -391,6 +392,15 @@ export class HttpImClient {
|
||||
displayTimestamp: message.displayTimestamp || message.timestamp,
|
||||
conversationId: conversation.id
|
||||
}));
|
||||
if (options.includeConversations && Array.isArray(response.data?.result?.conversations)) {
|
||||
const remoteConversations = response.data.result.conversations.map(remoteConversation => ({
|
||||
...remoteConversation,
|
||||
updatedAt: Number(remoteConversation.updatedAt || Date.now()),
|
||||
avatarText: remoteConversation.avatarText || String(remoteConversation.title || remoteConversation.target || '?').slice(0, 1).toUpperCase(),
|
||||
raw: remoteConversation.raw || { type: 'single', target: remoteConversation.target, line: 0 }
|
||||
}));
|
||||
this.emit('conversationsChanged', remoteConversations);
|
||||
}
|
||||
const userId = this.currentUser?.userId || '';
|
||||
const messagesByConversation = readLocalMessages(userId);
|
||||
messagesByConversation[conversation.id] = mergeSyncedMessages(messagesByConversation[conversation.id] || [], messages, conversation.id, userId);
|
||||
|
||||
@@ -345,6 +345,7 @@ export const useConversationStore = defineStore('conversations', {
|
||||
readPresenceEventsBound: false,
|
||||
syncInFlight: false,
|
||||
pendingReadReceiptConversationId: '',
|
||||
pendingConversationSync: false,
|
||||
conversationSyncInFlight: false,
|
||||
messageSyncTimer: null,
|
||||
messageSyncQueuedAt: 0,
|
||||
@@ -457,6 +458,7 @@ export const useConversationStore = defineStore('conversations', {
|
||||
this.syncInFlight = false;
|
||||
this.conversationSyncInFlight = false;
|
||||
this.pendingReadReceiptConversationId = '';
|
||||
this.pendingConversationSync = false;
|
||||
this.lastReadSyncByConversation = {};
|
||||
this.activeId = '';
|
||||
this.conversations = [];
|
||||
@@ -887,6 +889,9 @@ export const useConversationStore = defineStore('conversations', {
|
||||
if (wantsReadReceipt) {
|
||||
this.pendingReadReceiptConversationId = conversation.id;
|
||||
}
|
||||
if (options.includeConversations) {
|
||||
this.pendingConversationSync = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
this.syncInFlight = true;
|
||||
@@ -894,7 +899,8 @@ export const useConversationStore = defineStore('conversations', {
|
||||
const previousMessages = this.messagesByConversation[syncedConversationId] || [];
|
||||
try {
|
||||
const messages = await imClient.fetchRemoteMessages(conversation, 50, {
|
||||
markRead
|
||||
markRead,
|
||||
includeConversations: Boolean(options.includeConversations)
|
||||
});
|
||||
if (markRead) {
|
||||
this.lastReadSyncByConversation = {
|
||||
@@ -938,9 +944,17 @@ export const useConversationStore = defineStore('conversations', {
|
||||
// Keep the local view usable if a polling request misses.
|
||||
} finally {
|
||||
this.syncInFlight = false;
|
||||
if (this.pendingReadReceiptConversationId === this.activeId && canMarkReadNow()) {
|
||||
if (
|
||||
(this.pendingReadReceiptConversationId === this.activeId || this.pendingConversationSync)
|
||||
&& canMarkReadNow()
|
||||
) {
|
||||
const includeConversations = this.pendingConversationSync;
|
||||
this.pendingReadReceiptConversationId = '';
|
||||
this.scheduleActiveMessagesSync({ allowReadReceipt: true }, 700);
|
||||
this.pendingConversationSync = false;
|
||||
this.scheduleActiveMessagesSync({
|
||||
allowReadReceipt: true,
|
||||
includeConversations
|
||||
}, 700);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -957,7 +971,10 @@ export const useConversationStore = defineStore('conversations', {
|
||||
allowReadReceipt: Boolean(
|
||||
this.pendingMessageSyncOptions.allowReadReceipt || options.allowReadReceipt
|
||||
),
|
||||
userAction: Boolean(this.pendingMessageSyncOptions.userAction || options.userAction)
|
||||
userAction: Boolean(this.pendingMessageSyncOptions.userAction || options.userAction),
|
||||
includeConversations: Boolean(
|
||||
this.pendingMessageSyncOptions.includeConversations || options.includeConversations
|
||||
)
|
||||
};
|
||||
const maxWait = 1200;
|
||||
const runAt = Math.min(now + Math.max(0, Number(delay) || 0), this.messageSyncQueuedAt + maxWait);
|
||||
@@ -994,9 +1011,13 @@ export const useConversationStore = defineStore('conversations', {
|
||||
}
|
||||
window.clearTimeout(this.realtimeSyncTimer);
|
||||
this.realtimeSyncTimer = window.setTimeout(() => {
|
||||
this.refreshRemoteConversations();
|
||||
if (this.activeId) {
|
||||
this.scheduleActiveMessagesSync({ allowReadReceipt: canMarkReadNow() }, 700);
|
||||
this.scheduleActiveMessagesSync({
|
||||
allowReadReceipt: canMarkReadNow(),
|
||||
includeConversations: true
|
||||
}, 700);
|
||||
} else {
|
||||
this.refreshRemoteConversations();
|
||||
}
|
||||
}, 500);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user