Merge branch 'hicccc77:dev' into dev

This commit is contained in:
xuncha
2026-02-19 16:54:43 +08:00
committed by GitHub
10 changed files with 1328 additions and 78 deletions

View File

@@ -87,6 +87,7 @@ export interface Message {
datadesc: string
datatitle?: string
}>
_db_path?: string // 内部字段:记录消息所属数据库路径
}
export interface Contact {
@@ -112,7 +113,7 @@ const emojiDownloading: Map<string, Promise<string | null>> = new Map()
class ChatService {
private configService: ConfigService
private connected = false
private messageCursors: Map<string, { cursor: number; fetched: number; batchSize: number; startTime?: number; endTime?: number; ascending?: boolean }> = new Map()
private messageCursors: Map<string, { cursor: number; fetched: number; batchSize: number; startTime?: number; endTime?: number; ascending?: boolean; bufferedMessages?: any[] }> = new Map()
private readonly messageBatchDefault = 50
private avatarCache: Map<string, ContactCacheEntry>
private readonly avatarCacheTtlMs = 10 * 60 * 1000
@@ -272,6 +273,32 @@ class ChatService {
this.connected = false
}
/**
* 修改消息内容
*/
async updateMessage(sessionId: string, localId: number, createTime: number, newContent: string): Promise<{ success: boolean; error?: string }> {
try {
const connectResult = await this.ensureConnected()
if (!connectResult.success) return { success: false, error: connectResult.error }
return await wcdbService.updateMessage(sessionId, localId, createTime, newContent)
} catch (e) {
return { success: false, error: String(e) }
}
}
/**
* 删除消息
*/
async deleteMessage(sessionId: string, localId: number, createTime: number, dbPathHint?: string): Promise<{ success: boolean; error?: string }> {
try {
const connectResult = await this.ensureConnected()
if (!connectResult.success) return { success: false, error: connectResult.error }
return await wcdbService.deleteMessage(sessionId, localId, createTime, dbPathHint)
} catch (e) {
return { success: false, error: String(e) }
}
}
/**
* 获取会话列表(优化:先返回基础数据,不等待联系人信息加载)
*/
@@ -732,7 +759,7 @@ class ChatService {
// 4. startTime/endTime 改变(视为全新查询)
// 5. ascending 改变
const needNewCursor = !state ||
offset === 0 ||
offset !== state.fetched || // Offset mismatch -> must reset cursor
state.batchSize !== batchSize ||
state.startTime !== startTime ||
state.endTime !== endTime ||
@@ -764,6 +791,7 @@ class ChatService {
// 如果需要跳过消息(offset > 0),逐批获取但不返回
// 注意:仅在 offset === 0 时重建游标最安全;
// 当 startTime/endTime 变化导致重建时offset 应由前端重置为 0
state.bufferedMessages = []
if (offset > 0) {
console.warn(`[ChatService] 新游标需跳过 ${offset} 条消息startTime=${startTime}, endTime=${endTime}`)
let skipped = 0
@@ -780,8 +808,22 @@ class ChatService {
console.warn(`[ChatService] 跳过时数据耗尽: skipped=${skipped}/${offset}`)
return { success: true, messages: [], hasMore: false }
}
skipped += skipBatch.rows.length
state.fetched += skipBatch.rows.length
const count = skipBatch.rows.length
// Check if we overshot the offset
if (skipped + count > offset) {
const keepIndex = offset - skipped
if (keepIndex < count) {
state.bufferedMessages = skipBatch.rows.slice(keepIndex)
}
}
skipped += count
state.fetched += count
// If satisfied offset, break
if (skipped >= offset) break;
if (!skipBatch.hasMore) {
console.warn(`[ChatService] 跳过后无更多数据: skipped=${skipped}/${offset}`)
return { success: true, messages: [], hasMore: false }
@@ -790,13 +832,8 @@ class ChatService {
if (attempts >= maxSkipAttempts) {
console.error(`[ChatService] 跳过消息超过最大尝试次数: attempts=${attempts}`)
}
console.log(`[ChatService] 跳过完成: skipped=${skipped}, fetched=${state.fetched}`)
console.log(`[ChatService] 跳过完成: skipped=${skipped}, fetched=${state.fetched}, buffered=${state.bufferedMessages?.length || 0}`)
}
} else if (state && offset !== state.fetched) {
// offset 与 fetched 不匹配,说明状态不一致
console.warn(`[ChatService] 游标状态不一致: offset=${offset}, fetched=${state.fetched}, 继续使用现有游标`)
// 不重新创建游标,而是继续使用现有游标
// 这样可以避免频繁重建导致的问题
}
// 确保 state 已初始化
@@ -806,19 +843,35 @@ class ChatService {
}
// 获取当前批次的消息
const batch = await wcdbService.fetchMessageBatch(state.cursor)
if (!batch.success) {
console.error('[ChatService] 获取消息批次失败:', batch.error)
return { success: false, error: batch.error || '获取消息失败' }
// Use buffered rows from skip logic if available
let rows: any[] = state.bufferedMessages || []
state.bufferedMessages = undefined // Clear buffer after use
// If buffer is not enough to fill a batch, try to fetch more
// Or if buffer is empty, fetch a batch
if (rows.length < batchSize) {
const nextBatch = await wcdbService.fetchMessageBatch(state.cursor)
if (nextBatch.success && nextBatch.rows) {
rows = rows.concat(nextBatch.rows)
state.fetched += nextBatch.rows.length
} else if (!nextBatch.success) {
console.error('[ChatService] 获取消息批次失败:', nextBatch.error)
// If we have some buffered rows, we can still return them?
// Or fail? Let's return what we have if any, otherwise fail.
if (rows.length === 0) {
return { success: false, error: nextBatch.error || '获取消息失败' }
}
}
}
if (!batch.rows) {
console.error('[ChatService] 获取消息失败: 返回数据为空')
return { success: false, error: '获取消息失败: 返回数据为空' }
// If we have more than limit (due to buffer + full batch), slice it
if (rows.length > limit) {
rows = rows.slice(0, limit)
// Note: We don't adjust state.fetched here because it tracks cursor position.
// Next time offset will catch up or mismatch trigger reset.
}
const rows = batch.rows as Record<string, any>[]
const hasMore = batch.hasMore === true
const hasMore = rows.length > 0 // Simplified hasMore check for now, can be improved
const normalized = this.normalizeMessageOrder(this.mapRowsToMessages(rows))

View File

@@ -27,6 +27,8 @@ export class WcdbCore {
private wcdbCloseAccount: any = null
private wcdbSetMyWxid: any = null
private wcdbFreeString: any = null
private wcdbUpdateMessage: any = null
private wcdbDeleteMessage: any = null
private wcdbGetSessions: any = null
private wcdbGetMessages: any = null
private wcdbGetMessageCount: any = null
@@ -385,6 +387,20 @@ export class WcdbCore {
this.wcdbSetMyWxid = null
}
// wcdb_status wcdb_update_message(wcdb_handle handle, const char* session_id, int64_t local_id, int32_t create_time, const char* new_content, char** out_error)
try {
this.wcdbUpdateMessage = this.lib.func('int32 wcdb_update_message(int64 handle, const char* sessionId, int64 localId, int32 createTime, const char* newContent, _Out_ void** outError)')
} catch {
this.wcdbUpdateMessage = null
}
// wcdb_status wcdb_delete_message(wcdb_handle handle, const char* session_id, int64_t local_id, char** out_error)
try {
this.wcdbDeleteMessage = this.lib.func('int32 wcdb_delete_message(int64 handle, const char* sessionId, int64 localId, int32 createTime, const char* dbPathHint, _Out_ void** outError)')
} catch {
this.wcdbDeleteMessage = null
}
// void wcdb_free_string(char* ptr)
this.wcdbFreeString = this.lib.func('void wcdb_free_string(void* ptr)')
@@ -1774,4 +1790,62 @@ export class WcdbCore {
return { success: false, error: String(e) }
}
}
}
/**
* 修改消息内容
*/
async updateMessage(sessionId: string, localId: number, createTime: number, newContent: string): Promise<{ success: boolean; error?: string }> {
if (!this.initialized || !this.wcdbUpdateMessage) return { success: false, error: 'WCDB Not Initialized or Method Missing' }
if (!this.handle) return { success: false, error: 'Not Connected' }
return new Promise((resolve) => {
try {
const outError = [null as any]
const result = this.wcdbUpdateMessage(this.handle, sessionId, localId, createTime, newContent, outError)
if (result !== 0) {
let errorMsg = 'Unknown Error'
if (outError[0]) {
errorMsg = this.decodeJsonPtr(outError[0]) || 'Unknown Error (Decode Failed)'
}
resolve({ success: false, error: errorMsg })
return
}
resolve({ success: true })
} catch (e) {
resolve({ success: false, error: String(e) })
}
})
}
/**
* 删除消息
*/
async deleteMessage(sessionId: string, localId: number, createTime: number, dbPathHint?: string): Promise<{ success: boolean; error?: string }> {
if (!this.initialized || !this.wcdbDeleteMessage) return { success: false, error: 'WCDB Not Initialized or Method Missing' }
if (!this.handle) return { success: false, error: 'Not Connected' }
return new Promise((resolve) => {
try {
const outError = [null as any]
const result = this.wcdbDeleteMessage(this.handle, sessionId, localId, createTime || 0, dbPathHint || '', outError)
if (result !== 0) {
let errorMsg = 'Unknown Error'
if (outError[0]) {
errorMsg = this.decodeJsonPtr(outError[0]) || 'Unknown Error (Decode Failed)'
}
console.error(`[WcdbCore] deleteMessage fail: code=${result}, error=${errorMsg}`)
resolve({ success: false, error: errorMsg })
return
}
resolve({ success: true })
} catch (e) {
console.error(`[WcdbCore] deleteMessage exception:`, e)
resolve({ success: false, error: String(e) })
}
})
}
}

View File

@@ -431,6 +431,20 @@ export class WcdbService {
return this.callWorker('verifyUser', { message, hwnd })
}
/**
* 修改消息内容
*/
async updateMessage(sessionId: string, localId: number, createTime: number, newContent: string): Promise<{ success: boolean; error?: string }> {
return this.callWorker('updateMessage', { sessionId, localId, createTime, newContent })
}
/**
* 删除消息
*/
async deleteMessage(sessionId: string, localId: number, createTime: number, dbPathHint?: string): Promise<{ success: boolean; error?: string }> {
return this.callWorker('deleteMessage', { sessionId, localId, createTime, dbPathHint })
}
}