Files
cunkebao_v3/Cunkebao/src/utils/db.ts
超级老白兔 e7c109eab1 refactor(wechat): 优化消息接收处理和数据库结构
- 移除未使用的kfUserService导入
- 为weChatGroup和ContractData接口添加serverId字段
- 重构receivedMsg方法,根据消息类型从数据库获取会话信息
- 简化数据库表结构,移除冗余的WithServerId接口
2025-09-05 14:53:09 +08:00

320 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 数据库工具类 - 使用serverId作为主键的优化架构
*
* 架构设计:
* 1. 使用serverId作为数据库主键直接对应接口返回的id字段
* 2. 保留原始的id字段用于存储接口数据的完整性
* 3. 简化数据处理逻辑避免ID映射的复杂性
*
* 优势:
* - 直接使用服务器ID作为主键避免ID冲突
* - 保持数据的一致性和可追溯性
* - 简化查询逻辑,提高性能
* - 支持重复数据检测和去重
*
* 使用方法:
* - 存储接口数据:使用 createWithServerId() 或 createManyWithServerId()
* - 查询数据:使用 findById(id) 根据原始ID查询或 findByPrimaryKey(serverId) 根据主键查询
* - 批量查询:使用 findByIds([id1, id2, ...]) 根据原始ID批量查询
* - 内部操作serverId作为主键用于数据库内部管理
*
* 示例:
* const serverData = { id: 1001, name: '测试', ... }; // 接口返回的数据
* const serverId = await service.createWithServerId(serverData); // 存储返回serverId
* const data = await service.findById(1001); // 根据原始ID查询用户友好
* const dataByPK = await service.findByPrimaryKey(serverId); // 根据主键查询(内部使用)
*/
import Dexie, { Table } from "dexie";
import {
KfUserListData,
weChatGroup,
ContractData,
MessageListData,
} from "@/pages/pc/ckbox/data";
// 数据类型定义使用serverId作为主键
export interface KfUserWithServerId extends Omit<KfUserListData, "id"> {
serverId: number | string; // 服务器ID作为主键
id?: number; // 接口数据的原始ID字段
}
// 新联系人列表数据接口
export interface NewContactListData {
serverId: number | string; // 服务器ID作为主键
id?: number; // 接口数据的原始ID字段
groupName: string;
contacts: ContractData[];
weChatGroup: weChatGroup[];
}
// 数据库类
class CunkebaoDatabase extends Dexie {
kfUsers!: Table<KfUserWithServerId>;
weChatGroup!: Table<weChatGroup>;
contracts!: Table<ContractData>;
newContractList!: Table<NewContactListData>;
messageList!: Table<MessageListData>;
constructor() {
super("CunkebaoDatabase");
// 版本1使用serverId作为主键的架构
this.version(1).stores({
kfUsers:
"serverId, id, tenantId, wechatId, nickname, alias, avatar, gender, region, signature, bindQQ, bindEmail, bindMobile, createTime, currentDeviceId, isDeleted, deleteTime, groupId, memo, wechatVersion, lastUpdateTime, isOnline",
weChatGroup:
"serverId, id, wechatAccountId, tenantId, accountId, chatroomId, chatroomOwner, conRemark, nickname, chatroomAvatar,wechatChatroomId, groupId, config, unreadCount, notice, selfDisplyName",
contracts:
"serverId, id, wechatAccountId, wechatId, alias, conRemark, nickname, quanPin, avatar, gender, region, addFrom, phone, signature, accountId, extendFields, city, lastUpdateTime, isPassed, tenantId, groupId, thirdParty, additionalPicture, desc, config, lastMessageTime, unreadCount, duplicate",
newContractList: "serverId, id, groupName, contacts",
messageList:
"serverId, id, dataType, wechatAccountId, tenantId, accountId, nickname, avatar, groupId, config, labels, unreadCount, wechatId, alias, conRemark, quanPin, gender, region, addFrom, phone, signature, extendFields, city, lastUpdateTime, isPassed, thirdParty, additionalPicture, desc, lastMessageTime, duplicate, chatroomId, chatroomOwner, chatroomAvatar, notice, selfDisplyName",
});
}
}
// 创建数据库实例
export const db = new CunkebaoDatabase();
// 简单的数据库操作类
export class DatabaseService<T> {
constructor(private table: Table<T>) {}
// 基础 CRUD 操作 - 使用serverId作为主键
async create(data: Omit<T, "serverId">): Promise<string | number> {
return await this.table.add(data as T);
}
// 创建数据(直接使用接口数据)
// 接口数据的id字段直接作为serverId主键原id字段保留
async createWithServerId(data: any): Promise<string | number> {
const dataToInsert = {
...data,
serverId: data.id, // 使用接口的id作为serverId主键
};
return await this.table.add(dataToInsert as T);
}
// 根据原始ID查询用户友好的查询方法
async findById(id: string | number): Promise<T | undefined> {
return await this.table.where("id").equals(id).first();
}
// 根据serverId查询内部主键查询
async findByPrimaryKey(serverId: string | number): Promise<T | undefined> {
return await this.table.get(serverId);
}
async findAll(): Promise<T[]> {
return await this.table.toArray();
}
async update(serverId: string | number, data: Partial<T>): Promise<number> {
return await this.table.update(serverId, data as any);
}
async updateMany(
dataList: { serverId: string | number; data: Partial<T> }[],
): Promise<number> {
return await this.table.bulkUpdate(
dataList.map(item => ({
key: item.serverId,
changes: item.data as any,
})),
);
}
async createMany(
dataList: Omit<T, "serverId">[],
): Promise<(string | number)[]> {
return await this.table.bulkAdd(dataList as T[], { allKeys: true });
}
// 批量创建数据(直接使用接口数据)
// 接口数据的id字段直接作为serverId主键
async createManyWithServerId(dataList: any[]): Promise<(string | number)[]> {
// 检查是否存在重复的serverId
const serverIds = dataList.map(item => item.id);
const existingData = await this.table
.where("serverId")
.anyOf(serverIds)
.toArray();
const existingServerIds = new Set(
existingData.map((item: any) => item.serverId),
);
// 过滤掉已存在的数据
const newData = dataList.filter(item => !existingServerIds.has(item.id));
if (newData.length === 0) {
console.log("所有数据都已存在,跳过插入");
return [];
}
const processedData = newData.map(item => ({
...item,
serverId: item.id, // 使用接口的id作为serverId主键
}));
console.log(
`插入 ${processedData.length} 条新数据,跳过 ${dataList.length - newData.length} 条重复数据`,
);
return await this.table.bulkAdd(processedData as T[], { allKeys: true });
}
async delete(serverId: string | number): Promise<void> {
await this.table.delete(serverId);
}
async clear(): Promise<void> {
await this.table.clear();
}
// 条件查询
async findWhere(field: keyof T, value: any): Promise<T[]> {
return await this.table
.where(field as string)
.equals(value)
.toArray();
}
// 根据服务器ID查询兼容性方法
async findByServerId(serverId: any): Promise<T | undefined> {
return await this.table.get(serverId);
}
// 根据原始ID批量查询
async findByIds(ids: (string | number)[]): Promise<T[]> {
return await this.table.where("id").anyOf(ids).toArray();
}
// 多值查询IN 查询)
async findWhereIn(field: keyof T, values: any[]): Promise<T[]> {
return await this.table
.where(field as string)
.anyOf(values)
.toArray();
}
// 范围查询
async findWhereBetween(field: keyof T, min: any, max: any): Promise<T[]> {
return await this.table
.where(field as string)
.between(min, max)
.toArray();
}
// 模糊查询(以指定字符串开头)
async findWhereStartsWith(field: keyof T, prefix: string): Promise<T[]> {
return await this.table
.where(field as string)
.startsWith(prefix)
.toArray();
}
// 不等于查询
async findWhereNot(field: keyof T, value: any): Promise<T[]> {
return await this.table
.where(field as string)
.notEqual(value)
.toArray();
}
// 大于查询
async findWhereGreaterThan(field: keyof T, value: any): Promise<T[]> {
return await this.table
.where(field as string)
.above(value)
.toArray();
}
// 小于查询
async findWhereLessThan(field: keyof T, value: any): Promise<T[]> {
return await this.table
.where(field as string)
.below(value)
.toArray();
}
// 复合条件查询
async findWhereMultiple(
conditions: {
field: keyof T;
operator: "equals" | "above" | "below" | "startsWith";
value: any;
}[],
): Promise<T[]> {
let collection = this.table.toCollection();
for (const condition of conditions) {
const { field, operator, value } = condition;
collection = collection.and(item => {
const fieldValue = (item as any)[field];
switch (operator) {
case "equals":
return fieldValue === value;
case "above":
return fieldValue > value;
case "below":
return fieldValue < value;
case "startsWith":
return (
typeof fieldValue === "string" && fieldValue.startsWith(value)
);
default:
return true;
}
});
}
return await collection.toArray();
}
// 分页查询
async findWithPagination(
page: number = 1,
limit: number = 10,
): Promise<{ data: T[]; total: number; page: number; limit: number }> {
const offset = (page - 1) * limit;
const total = await this.table.count();
const data = await this.table.offset(offset).limit(limit).toArray();
return { data, total, page, limit };
}
// 排序查询
async findAllSorted(
field: keyof T,
direction: "asc" | "desc" = "asc",
): Promise<T[]> {
const collection = this.table.orderBy(field as string);
return direction === "desc"
? await collection.reverse().toArray()
: await collection.toArray();
}
// 统计
async count(): Promise<number> {
return await this.table.count();
}
// 条件统计
async countWhere(field: keyof T, value: any): Promise<number> {
return await this.table
.where(field as string)
.equals(value)
.count();
}
}
// 创建各表的服务实例
export const kfUserService = new DatabaseService(db.kfUsers);
export const weChatGroupService = new DatabaseService(db.weChatGroup);
export const contractService = new DatabaseService(db.contracts);
export const newContactListService = new DatabaseService(db.newContractList);
export const messageListService = new DatabaseService(db.messageList);
// 默认导出数据库实例
export default db;