refactor!: 重新实现最近对话和收藏对话的逻辑 (破坏性变更)

This commit is contained in:
CrescentLeaf
2026-01-25 00:50:14 +08:00
parent 44ada8206d
commit ec527bafc6
5 changed files with 160 additions and 41 deletions

View File

@@ -318,11 +318,11 @@ export default class UserApi extends BaseApi {
const user = User.findById(token.author) as User const user = User.findById(token.author) as User
const recentChats = user.getRecentChats() const recentChats = user.getRecentChats()
const recentChatsList: any[] = [] const recentChatsList: any[] = []
for (const [chatId, content] of recentChats) { for (const {chat_id, content} of recentChats) {
const chat = Chat.findById(chatId) const chat = Chat.findById(chat_id)
recentChatsList.push({ recentChatsList.push({
content, content,
id: chatId, id: chat_id,
title: chat?.getTitle(user) || "未知", title: chat?.getTitle(user) || "未知",
avatar_file_hash: chat?.getAvatarFileHash(user) ? chat?.getAvatarFileHash(user) : undefined avatar_file_hash: chat?.getAvatarFileHash(user) ? chat?.getAvatarFileHash(user) : undefined
}) })

View File

@@ -0,0 +1,10 @@
export default class RecentChatBean {
declare count: number
/** 最近对话所关联的用户 */
declare user_id: string
declare chat_id: string
declare content: string
declare updated_time: number
[key: string]: unknown
}

View File

@@ -11,11 +11,10 @@ import UserBean from './UserBean.ts'
import FileManager from './FileManager.ts' import FileManager from './FileManager.ts'
import { SQLInputValue } from "node:sqlite" import { SQLInputValue } from "node:sqlite"
import ChatPrivate from "./ChatPrivate.ts" import ChatPrivate from "./ChatPrivate.ts"
import Chat from "./Chat.ts"
import ChatBean from "./ChatBean.ts"
import MapJson from "../MapJson.ts"
import DataWrongError from '../api/DataWrongError.ts' import DataWrongError from '../api/DataWrongError.ts'
import UserChatLinker from "./UserChatLinker.ts"; import UserChatLinker from "./UserChatLinker.ts"
import UserFavouriteChatLinker from "./UserFavouriteChatLinker.ts"
import UserRecentChatLinker from "./UserRecentChatLinker.ts"
type UserBeanKey = keyof UserBean type UserBeanKey = keyof UserBean
@@ -38,8 +37,6 @@ export default class User {
/* 用户名 */ username TEXT, /* 用户名 */ username TEXT,
/* 昵称 */ nickname TEXT NOT NULL, /* 昵称 */ nickname TEXT NOT NULL,
/* 头像, 可选 */ avatar_file_hash TEXT, /* 头像, 可选 */ avatar_file_hash TEXT,
/* 对话列表 */ favourite_chats TEXT NOT NULL,
/* 最近对话 */ recent_chats TEXT NOT NULL,
/* 设置 */ settings TEXT NOT NULL /* 设置 */ settings TEXT NOT NULL
); );
`) `)
@@ -69,18 +66,14 @@ export default class User {
username, username,
nickname, nickname,
avatar_file_hash, avatar_file_hash,
favourite_chats,
recent_chats,
settings settings
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);`).run( ) VALUES (?, ?, ?, ?, ?, ?, ?);`).run(
crypto.randomUUID(), crypto.randomUUID(),
password, password,
Date.now(), Date.now(),
userName, userName,
nickName, nickName,
null, null,
'[]',
JSON.stringify(new Map(), MapJson.replacer),
"{}" "{}"
).lastInsertRowid ).lastInsertRowid
)[0] )[0]
@@ -136,37 +129,25 @@ export default class User {
this.setAttr("username", userName) this.setAttr("username", userName)
} }
updateRecentChat(chatId: string, content: string) { updateRecentChat(chatId: string, content: string) {
const map = JSON.parse(this.bean.recent_chats, MapJson.reviver) as Map<string, string> UserRecentChatLinker.updateOrAddRecentChat(this.bean.id, chatId, content)
map.delete(chatId)
map.set(chatId, content)
this.setAttr("recent_chats", JSON.stringify(map, MapJson.replacer))
} }
getRecentChats(): Map<string, string> { getRecentChats() {
try { return UserRecentChatLinker.getUserRecentChatBeans(this.bean.id)
return JSON.parse(this.bean.recent_chats, MapJson.reviver) }
} catch (e) {
console.log(chalk.yellow(`警告: 最近对话列表解析失敗: ${(e as Error).message}`)) getFavouriteChats() {
return new Map() return UserFavouriteChatLinker.getUserFavouriteChats(this.bean.id)
} }
addFavouriteChats(chatIds: string[]) {
chatIds.forEach((v) => UserFavouriteChatLinker.linkUserAndChat(this.bean.id, v))
}
removeFavouriteChats(chatIds: string[]) {
chatIds.forEach((v) => UserFavouriteChatLinker.unlinkUserAndChat(this.bean.id, v))
} }
addFavouriteChat(chatId: string) { addFavouriteChat(chatId: string) {
const ls = this.getFavouriteChats() this.addFavouriteChats([chatId])
if (ls.indexOf(chatId) != -1 || ChatPrivate.getChatIdByUsersId(this.bean.id, this.bean.id) == chatId) return
ls.push(chatId)
this.setAttr("favourite_chats", JSON.stringify(ls))
}
removeFavouriteChats(contacts: string[]) {
const ls = this.getFavouriteChats().filter((v) => !contacts.includes(v))
this.setAttr("favourite_chats", JSON.stringify(ls))
}
getFavouriteChats() {
try {
return [...(JSON.parse(this.bean.favourite_chats) as string[]), ChatPrivate.findOrCreateForPrivate(this, this).bean.id]
} catch (e) {
console.log(chalk.yellow(`警告: 收藏对话解析失败: ${(e as Error).message}`))
return []
}
} }
getAllChatsList() { getAllChatsList() {
return UserChatLinker.getUserChats(this.bean.id) return UserChatLinker.getUserChats(this.bean.id)
} }

View File

@@ -0,0 +1,57 @@
import { DatabaseSync } from "node:sqlite"
import path from 'node:path'
import config from "../config.ts"
import { SQLInputValue } from "node:sqlite"
export default class UserFavouriteChatLinker {
static database: DatabaseSync = this.init()
private static init(): DatabaseSync {
const db: DatabaseSync = new DatabaseSync(path.join(config.data_path, 'UserFavouriteChatLinker.db'))
db.exec(`
CREATE TABLE IF NOT EXISTS UserFavouriteChatLinker (
/* 序号 */ count INTEGER PRIMARY KEY AUTOINCREMENT,
/* 用戶 ID */ user_id TEXT NOT NULL,
/* Chat ID */ chat_id TEXT NOT NULL
);
`)
db.exec(`CREATE INDEX IF NOT EXISTS idx_user_id ON UserFavouriteChatLinker(user_id);`)
return db
}
/**
* 若用户和对话未关联, 则进行关联
*/
static linkUserAndChat(userId: string, chatId: string) {
if (!this.checkUserIsLinkedToChat(userId, chatId))
this.database.prepare(`INSERT INTO UserFavouriteChatLinker (
user_id,
chat_id
) VALUES (?, ?);`).run(
userId,
chatId
)
}
/**
* 解除用户和对话的关联
*/
static unlinkUserAndChat(userId: string, chatId: string) {
this.database.prepare(`DELETE FROM UserFavouriteChatLinker WHERE user_id = ? AND chat_id = ?`).run(userId, chatId)
}
/**
* 检测用户和对话的关联
*/
static checkUserIsLinkedToChat(userId: string, chatId: string) {
return this.findAllByCondition('user_id = ? AND chat_id = ?', userId, chatId).length != 0
}
/**
* 获取用户所有关联的对话
*/
static getUserFavouriteChats(userId: string) {
return this.findAllByCondition('user_id = ?', userId).map((v) => v.chat_id) as string[]
}
protected static findAllByCondition(condition: string, ...args: SQLInputValue[]) {
return this.database.prepare(`SELECT * FROM UserFavouriteChatLinker WHERE ${condition}`).all(...args)
}
}

View File

@@ -0,0 +1,71 @@
import { DatabaseSync } from "node:sqlite"
import path from 'node:path'
import RecentChatBean from './RecentChatBean.ts'
import config from "../config.ts"
import { SQLInputValue } from "node:sqlite"
export default class UserRecentChatLinker {
static database: DatabaseSync = this.init()
private static init(): DatabaseSync {
const db: DatabaseSync = new DatabaseSync(path.join(config.data_path, 'UserRecentChatLinker.db'))
db.exec(`
CREATE TABLE IF NOT EXISTS UserRecentChatLinker (
/* 序号 */ count INTEGER PRIMARY KEY AUTOINCREMENT,
/* 用戶 ID */ user_id TEXT NOT NULL,
/* Chat ID */ chat_id TEXT NOT NULL,
/* Last Message Content */ content TEXT NOT NULL,
/* Last Update Time */ updated_time INT8 NOT NULL
);
`)
db.exec(`CREATE INDEX IF NOT EXISTS idx_user_id ON UserRecentChatLinker(user_id);`)
return db
}
/**
* 若用户和对话未关联, 则进行关联
*/
static updateOrAddRecentChat(userId: string, chatId: string, content: string) {
if (!this.checkUserIsLinkedToChat(userId, chatId))
this.database.prepare(`INSERT INTO UserRecentChatLinker (
user_id,
chat_id,
content,
updated_time
) VALUES (?, ?, ?, ?);`).run(
userId,
chatId,
content,
Date.now()
)
else
this.database.prepare('UPDATE UserRecentChatLinker SET content = ?, updated_time = ? WHERE count = ?').run(
content,
Date.now(),
/* 既然已经 bind 了, 那么就不需要判断了? */
this.findAllByCondition('user_id = ? AND chat_id = ?', userId, chatId)[0].count
)
}
/**
* 解除用户和对话的关联
*/
static removeRecentChat(userId: string, chatId: string) {
this.database.prepare(`DELETE FROM UserRecentChatLinker WHERE user_id = ? AND chat_id = ?`).run(userId, chatId)
}
/**
* 检测用户和对话的关联
*/
static checkUserIsLinkedToChat(userId: string, chatId: string) {
return this.findAllByCondition('user_id = ? AND chat_id = ?', userId, chatId).length != 0
}
/**
* 获取用户所有关联的对话
*/
static getUserRecentChatBeans(userId: string) {
return this.findAllByCondition('user_id = ? ORDER BY updated_time DESC', userId) as unknown as RecentChatBean[]
}
protected static findAllByCondition(condition: string, ...args: SQLInputValue[]) {
return this.database.prepare(`SELECT * FROM UserRecentChatLinker WHERE ${condition}`).all(...args)
}
}