- 移除未使用的kfUserService导入 - 为weChatGroup和ContractData接口添加serverId字段 - 重构receivedMsg方法,根据消息类型从数据库获取会话信息 - 简化数据库表结构,移除冗余的WithServerId接口
320 lines
10 KiB
TypeScript
320 lines
10 KiB
TypeScript
/**
|
||
* 数据库工具类 - 使用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;
|