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 recentChats = user.getRecentChats()
const recentChatsList: any[] = []
for (const [chatId, content] of recentChats) {
const chat = Chat.findById(chatId)
for (const {chat_id, content} of recentChats) {
const chat = Chat.findById(chat_id)
recentChatsList.push({
content,
id: chatId,
id: chat_id,
title: chat?.getTitle(user) || "未知",
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 { SQLInputValue } from "node:sqlite"
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 UserChatLinker from "./UserChatLinker.ts";
import UserChatLinker from "./UserChatLinker.ts"
import UserFavouriteChatLinker from "./UserFavouriteChatLinker.ts"
import UserRecentChatLinker from "./UserRecentChatLinker.ts"
type UserBeanKey = keyof UserBean
@@ -38,8 +37,6 @@ export default class User {
/* 用户名 */ username TEXT,
/* 昵称 */ nickname TEXT NOT NULL,
/* 头像, 可选 */ avatar_file_hash TEXT,
/* 对话列表 */ favourite_chats TEXT NOT NULL,
/* 最近对话 */ recent_chats TEXT NOT NULL,
/* 设置 */ settings TEXT NOT NULL
);
`)
@@ -69,18 +66,14 @@ export default class User {
username,
nickname,
avatar_file_hash,
favourite_chats,
recent_chats,
settings
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);`).run(
) VALUES (?, ?, ?, ?, ?, ?, ?);`).run(
crypto.randomUUID(),
password,
Date.now(),
userName,
nickName,
null,
'[]',
JSON.stringify(new Map(), MapJson.replacer),
"{}"
).lastInsertRowid
)[0]
@@ -136,37 +129,25 @@ export default class User {
this.setAttr("username", userName)
}
updateRecentChat(chatId: string, content: string) {
const map = JSON.parse(this.bean.recent_chats, MapJson.reviver) as Map<string, string>
map.delete(chatId)
map.set(chatId, content)
this.setAttr("recent_chats", JSON.stringify(map, MapJson.replacer))
UserRecentChatLinker.updateOrAddRecentChat(this.bean.id, chatId, content)
}
getRecentChats(): Map<string, string> {
try {
return JSON.parse(this.bean.recent_chats, MapJson.reviver)
} catch (e) {
console.log(chalk.yellow(`警告: 最近对话列表解析失敗: ${(e as Error).message}`))
return new Map()
getRecentChats() {
return UserRecentChatLinker.getUserRecentChatBeans(this.bean.id)
}
getFavouriteChats() {
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) {
const ls = this.getFavouriteChats()
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 []
}
this.addFavouriteChats([chatId])
}
getAllChatsList() {
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)
}
}