refactor: query session

This commit is contained in:
digua
2026-02-09 22:37:33 +08:00
parent 7175e6e5dc
commit 2d6c4d085a
3 changed files with 195 additions and 183 deletions

View File

@@ -5,11 +5,9 @@
import Database from 'better-sqlite3'
import * as fs from 'fs'
import * as path from 'path'
import {
openDatabase,
closeDatabase,
getDbDir,
getDbPath,
buildTimeFilter,
buildSystemMessageFilter,
@@ -422,185 +420,6 @@ export function getMemberNameHistory(sessionId: string, memberId: number): any[]
return rows
}
// ==================== 会话管理 ====================
interface DbMeta {
name: string
platform: string
type: string
imported_at: number
group_id: string | null
group_avatar: string | null
}
/**
* 获取私聊对方成员的头像
* 逻辑参考 private-chat/index.vue 的 otherMemberAvatar
*/
function getPrivateChatMemberAvatar(db: Database.Database, sessionName: string, ownerId: string | null): string | null {
// 获取所有非系统消息成员(按消息数排序)
const members = db
.prepare(
`SELECT
m.platform_id as platformId,
COALESCE(m.group_nickname, m.account_name, m.platform_id) as name,
m.avatar
FROM member m
WHERE COALESCE(m.account_name, '') != '系统消息'
ORDER BY (SELECT COUNT(*) FROM message WHERE sender_id = m.id) DESC`
)
.all() as Array<{ platformId: string; name: string; avatar: string | null }>
if (members.length === 0) return null
// 1. 优先排除 ownerId找到另一成员的头像
if (ownerId) {
const other = members.find((m) => m.platformId !== ownerId)
if (other?.avatar) return other.avatar
}
// 2. 尝试匹配会话名称(私聊名称通常是对方昵称)
const sameName = members.find((m) => m.name === sessionName)
if (sameName?.avatar) return sameName.avatar
// 3. 如果只有两个成员且有 ownerId取另一个即使没有头像也返回 null
// 如果没有 ownerId返回第一个有头像的成员
const firstWithAvatar = members.find((m) => m.avatar)
return firstWithAvatar?.avatar || null
}
/**
* 获取所有会话列表
*/
export function getAllSessions(): any[] {
const dbDir = getDbDir()
if (!fs.existsSync(dbDir)) {
return []
}
const sessions: any[] = []
const files = fs.readdirSync(dbDir).filter((f) => f.endsWith('.db'))
for (const file of files) {
const sessionId = file.replace('.db', '')
const dbPath = path.join(dbDir, file)
try {
const db = new Database(dbPath)
db.pragma('journal_mode = WAL')
// 跳过非聊天会话数据库(例如内部索引库)
const requiredTableCount = db
.prepare(
"SELECT COUNT(*) as cnt FROM sqlite_master WHERE type='table' AND name IN ('meta', 'member', 'message')"
)
.get() as { cnt: number }
if (requiredTableCount.cnt !== 3) {
db.close()
continue
}
const meta = db.prepare('SELECT * FROM meta LIMIT 1').get() as DbMeta | undefined
if (meta) {
const messageCount = (
db
.prepare(
`SELECT COUNT(*) as count
FROM message msg
JOIN member m ON msg.sender_id = m.id
WHERE COALESCE(m.account_name, '') != '系统消息'`
)
.get() as { count: number }
).count
const memberCount = (
db
.prepare(
`SELECT COUNT(*) as count
FROM member
WHERE COALESCE(account_name, '') != '系统消息'`
)
.get() as { count: number }
).count
// 私聊:获取对方成员头像
let memberAvatar: string | null = null
if (meta.type === 'private') {
memberAvatar = getPrivateChatMemberAvatar(db, meta.name, meta.owner_id)
}
sessions.push({
id: sessionId,
name: meta.name,
platform: meta.platform,
type: meta.type,
importedAt: meta.imported_at,
messageCount,
memberCount,
dbPath,
groupId: meta.group_id || null,
groupAvatar: meta.group_avatar || null,
ownerId: meta.owner_id || null,
memberAvatar, // 私聊对方头像
})
}
db.close()
} catch (error) {
console.error(`[Worker] Failed to read database ${file}:`, error)
}
}
return sessions.sort((a, b) => b.importedAt - a.importedAt)
}
/**
* 获取单个会话信息
*/
export function getSession(sessionId: string): any | null {
const db = openDatabase(sessionId)
if (!db) return null
const meta = db.prepare('SELECT * FROM meta LIMIT 1').get() as DbMeta | undefined
if (!meta) return null
const messageCount = (
db
.prepare(
`SELECT COUNT(*) as count
FROM message msg
JOIN member m ON msg.sender_id = m.id
WHERE COALESCE(m.account_name, '') != '系统消息'`
)
.get() as { count: number }
).count
const memberCount = (
db
.prepare(
`SELECT COUNT(*) as count
FROM member
WHERE COALESCE(account_name, '') != '系统消息'`
)
.get() as { count: number }
).count
return {
id: sessionId,
name: meta.name,
platform: meta.platform,
type: meta.type,
importedAt: meta.imported_at,
messageCount,
memberCount,
dbPath: getDbPath(sessionId),
groupId: meta.group_id || null,
groupAvatar: meta.group_avatar || null,
ownerId: meta.owner_id || null,
}
}
// ==================== 成员管理 ====================
/**

View File

@@ -16,8 +16,6 @@ export {
getMessageTypeDistribution,
getTimeRange,
getMemberNameHistory,
getAllSessions,
getSession,
// 成员管理
getMembers,
getMembersPaginated,
@@ -25,6 +23,9 @@ export {
deleteMember,
} from './basic'
// 会话管理(会话列表与基础信息)
export { getAllSessions, getSession } from './sessions'
// 成员分页类型
export type { MembersPaginationParams, MembersPaginatedResult } from './basic'

View File

@@ -0,0 +1,192 @@
/**
* 会话管理查询模块
* 负责会话列表与单会话基础信息查询
*/
import Database from 'better-sqlite3'
import * as fs from 'fs'
import * as path from 'path'
import { openDatabase, getDbDir, getDbPath } from '../core'
interface DbMeta {
name: string
platform: string
type: string
imported_at: number
group_id: string | null
group_avatar: string | null
owner_id: string | null
}
/**
* 判断是否为聊天会话数据库
* 通过核心三表meta/member/message存在性快速识别
*/
function isChatSessionDb(db: Database.Database): boolean {
const requiredTableCount = db
.prepare("SELECT COUNT(*) as cnt FROM sqlite_master WHERE type='table' AND name IN ('meta', 'member', 'message')")
.get() as { cnt: number }
return requiredTableCount.cnt === 3
}
/**
* 获取私聊对方成员的头像
* 逻辑参考 private-chat/index.vue 的 otherMemberAvatar
*/
function getPrivateChatMemberAvatar(db: Database.Database, sessionName: string, ownerId: string | null): string | null {
// 获取所有非系统消息成员(按消息数排序)
const members = db
.prepare(
`SELECT
m.platform_id as platformId,
COALESCE(m.group_nickname, m.account_name, m.platform_id) as name,
m.avatar
FROM member m
WHERE COALESCE(m.account_name, '') != '系统消息'
ORDER BY (SELECT COUNT(*) FROM message WHERE sender_id = m.id) DESC`
)
.all() as Array<{ platformId: string; name: string; avatar: string | null }>
if (members.length === 0) return null
// 1. 优先排除 ownerId找到另一成员的头像
if (ownerId) {
const other = members.find((m) => m.platformId !== ownerId)
if (other?.avatar) return other.avatar
}
// 2. 尝试匹配会话名称(私聊名称通常是对方昵称)
const sameName = members.find((m) => m.name === sessionName)
if (sameName?.avatar) return sameName.avatar
// 3. 如果只有两个成员且有 ownerId取另一个即使没有头像也返回 null
// 如果没有 ownerId返回第一个有头像的成员
const firstWithAvatar = members.find((m) => m.avatar)
return firstWithAvatar?.avatar || null
}
/**
* 获取所有会话列表
*/
export function getAllSessions(): any[] {
const dbDir = getDbDir()
if (!fs.existsSync(dbDir)) {
return []
}
const sessions: any[] = []
const files = fs.readdirSync(dbDir).filter((f) => f.endsWith('.db'))
for (const file of files) {
const sessionId = file.replace('.db', '')
const dbPath = path.join(dbDir, file)
try {
const db = new Database(dbPath)
db.pragma('journal_mode = WAL')
// 跳过非聊天会话数据库(例如内部索引库)
if (!isChatSessionDb(db)) {
db.close()
continue
}
const meta = db.prepare('SELECT * FROM meta LIMIT 1').get() as DbMeta | undefined
if (meta) {
const messageCount = (
db
.prepare(
`SELECT COUNT(*) as count
FROM message msg
JOIN member m ON msg.sender_id = m.id
WHERE COALESCE(m.account_name, '') != '系统消息'`
)
.get() as { count: number }
).count
const memberCount = (
db
.prepare(
`SELECT COUNT(*) as count
FROM member
WHERE COALESCE(account_name, '') != '系统消息'`
)
.get() as { count: number }
).count
// 私聊:获取对方成员头像
let memberAvatar: string | null = null
if (meta.type === 'private') {
memberAvatar = getPrivateChatMemberAvatar(db, meta.name, meta.owner_id)
}
sessions.push({
id: sessionId,
name: meta.name,
platform: meta.platform,
type: meta.type,
importedAt: meta.imported_at,
messageCount,
memberCount,
dbPath,
groupId: meta.group_id || null,
groupAvatar: meta.group_avatar || null,
ownerId: meta.owner_id || null,
memberAvatar, // 私聊对方头像
})
}
db.close()
} catch (error) {
console.error(`[Worker] Failed to read database ${file}:`, error)
}
}
return sessions.sort((a, b) => b.importedAt - a.importedAt)
}
/**
* 获取单个会话信息
*/
export function getSession(sessionId: string): any | null {
const db = openDatabase(sessionId)
if (!db) return null
const meta = db.prepare('SELECT * FROM meta LIMIT 1').get() as DbMeta | undefined
if (!meta) return null
const messageCount = (
db
.prepare(
`SELECT COUNT(*) as count
FROM message msg
JOIN member m ON msg.sender_id = m.id
WHERE COALESCE(m.account_name, '') != '系统消息'`
)
.get() as { count: number }
).count
const memberCount = (
db
.prepare(
`SELECT COUNT(*) as count
FROM member
WHERE COALESCE(account_name, '') != '系统消息'`
)
.get() as { count: number }
).count
return {
id: sessionId,
name: meta.name,
platform: meta.platform,
type: meta.type,
importedAt: meta.imported_at,
messageCount,
memberCount,
dbPath: getDbPath(sessionId),
groupId: meta.group_id || null,
groupAvatar: meta.group_avatar || null,
ownerId: meta.owner_id || null,
}
}