2025-09-11 15:00:57 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 数据库工具类 - 使用serverId作为主键的优化架构
|
|
|
|
|
|
*
|
|
|
|
|
|
* 架构设计:
|
|
|
|
|
|
* 1. 使用serverId作为数据库主键,直接对应接口返回的id字段
|
|
|
|
|
|
* 2. 保留原始的id字段,用于存储接口数据的完整性
|
2025-10-23 17:02:33 +08:00
|
|
|
|
* 3. 添加userId字段实现多用户数据隔离
|
|
|
|
|
|
* 4. 统一会话表和联系人表,兼容好友和群聊
|
2025-09-11 15:00:57 +08:00
|
|
|
|
*
|
|
|
|
|
|
* 优势:
|
|
|
|
|
|
* - 直接使用服务器ID作为主键,避免ID冲突
|
2025-10-23 17:02:33 +08:00
|
|
|
|
* - 通过userId实现多用户数据隔离
|
|
|
|
|
|
* - 统一的会话和联系人表结构,兼容好友和群聊
|
|
|
|
|
|
* - 支持复合索引,提高查询性能
|
|
|
|
|
|
* - 支持用户登录记录和自动清理
|
2025-09-11 15:00:57 +08:00
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
import Dexie, { Table } from "dexie";
|
|
|
|
|
|
import {
|
|
|
|
|
|
KfUserListData,
|
|
|
|
|
|
weChatGroup,
|
|
|
|
|
|
ContractData,
|
|
|
|
|
|
MessageListData,
|
|
|
|
|
|
} from "@/pages/pc/ckbox/data";
|
|
|
|
|
|
|
2025-10-23 17:02:33 +08:00
|
|
|
|
// ==================== 用户登录记录 ====================
|
|
|
|
|
|
export interface UserLoginRecord {
|
|
|
|
|
|
serverId: string; // 主键: user_${userId}
|
|
|
|
|
|
userId: number; // 用户ID
|
|
|
|
|
|
lastLoginTime: string; // 最后登录时间
|
|
|
|
|
|
loginCount: number; // 登录次数
|
|
|
|
|
|
createTime: string; // 首次登录时间
|
|
|
|
|
|
lastActiveTime: string; // 最后活跃时间
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 统一会话表(兼容好友和群聊) ====================
|
|
|
|
|
|
export interface ChatSession {
|
|
|
|
|
|
serverId: string; // 主键
|
|
|
|
|
|
userId: number; // 用户ID(数据隔离)
|
|
|
|
|
|
id: number; // 原始ID
|
|
|
|
|
|
type: "friend" | "group"; // 类型:好友或群聊
|
|
|
|
|
|
|
|
|
|
|
|
// 通用字段
|
|
|
|
|
|
wechatAccountId: number; // 所属客服账号
|
|
|
|
|
|
nickname: string; // 显示名称
|
|
|
|
|
|
conRemark?: string; // 备注名
|
|
|
|
|
|
avatar: string; // 头像
|
|
|
|
|
|
content: string; // 最新消息内容
|
|
|
|
|
|
lastUpdateTime: string; // 最后更新时间
|
|
|
|
|
|
config: {
|
|
|
|
|
|
unreadCount: number; // 未读数量
|
|
|
|
|
|
top: boolean; // 是否置顶
|
|
|
|
|
|
};
|
|
|
|
|
|
sortKey: string; // 预计算排序键
|
|
|
|
|
|
|
|
|
|
|
|
// 好友特有字段(type='friend'时有效)
|
|
|
|
|
|
wechatFriendId?: number; // 好友ID
|
|
|
|
|
|
wechatId?: string; // 微信号
|
|
|
|
|
|
alias?: string; // 别名
|
|
|
|
|
|
|
|
|
|
|
|
// 群聊特有字段(type='group'时有效)
|
|
|
|
|
|
chatroomId?: string; // 群聊ID
|
|
|
|
|
|
chatroomOwner?: string; // 群主
|
|
|
|
|
|
selfDisplayName?: string; // 群内昵称
|
|
|
|
|
|
notice?: string; // 群公告
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 统一联系人表(兼容好友和群聊) ====================
|
|
|
|
|
|
export interface Contact {
|
|
|
|
|
|
serverId: string; // 主键
|
|
|
|
|
|
userId: number; // 用户ID(数据隔离)
|
|
|
|
|
|
id: number; // 原始ID
|
|
|
|
|
|
type: "friend" | "group"; // 类型:好友或群聊
|
|
|
|
|
|
|
|
|
|
|
|
// 通用字段
|
|
|
|
|
|
wechatAccountId: number; // 所属客服账号
|
|
|
|
|
|
nickname: string; // 显示名称
|
|
|
|
|
|
conRemark?: string; // 备注名
|
|
|
|
|
|
avatar: string; // 头像
|
|
|
|
|
|
lastUpdateTime: string; // 最后更新时间
|
|
|
|
|
|
config?: any; // 配置信息
|
|
|
|
|
|
sortKey: string; // 预计算排序键
|
|
|
|
|
|
searchKey: string; // 预计算搜索键
|
|
|
|
|
|
|
|
|
|
|
|
// 好友特有字段(type='friend'时有效)
|
|
|
|
|
|
wechatFriendId?: number; // 好友ID
|
|
|
|
|
|
wechatId?: string; // 微信号
|
|
|
|
|
|
alias?: string; // 别名
|
|
|
|
|
|
gender?: number; // 性别
|
|
|
|
|
|
region?: string; // 地区
|
|
|
|
|
|
signature?: string; // 个性签名
|
|
|
|
|
|
phone?: string; // 手机号
|
|
|
|
|
|
quanPin?: string; // 全拼
|
|
|
|
|
|
|
|
|
|
|
|
// 群聊特有字段(type='group'时有效)
|
|
|
|
|
|
chatroomId?: string; // 群聊ID
|
|
|
|
|
|
chatroomOwner?: string; // 群主
|
|
|
|
|
|
selfDisplayName?: string; // 群内昵称
|
|
|
|
|
|
notice?: string; // 群公告
|
|
|
|
|
|
memberCount?: number; // 群成员数量
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 联系人标签映射表 ====================
|
|
|
|
|
|
export interface ContactLabelMap {
|
|
|
|
|
|
serverId: string; // 主键: ${contactId}_${labelId}
|
|
|
|
|
|
userId: number; // 用户ID(数据隔离)
|
|
|
|
|
|
labelId: number; // 标签ID
|
|
|
|
|
|
contactId: number; // 联系人ID
|
|
|
|
|
|
contactType: "friend" | "group"; // 联系人类型
|
|
|
|
|
|
sortKey: string; // 预计算排序键
|
|
|
|
|
|
searchKey: string; // 预计算搜索键
|
|
|
|
|
|
|
|
|
|
|
|
// 列表展示必需字段(轻量)
|
|
|
|
|
|
avatar: string;
|
|
|
|
|
|
nickname: string;
|
|
|
|
|
|
conRemark?: string;
|
|
|
|
|
|
unreadCount: number;
|
|
|
|
|
|
lastUpdateTime: string;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ==================== 保留原有数据类型(向后兼容) ====================
|
2025-09-11 15:00:57 +08:00
|
|
|
|
export interface KfUserWithServerId extends Omit<KfUserListData, "id"> {
|
|
|
|
|
|
serverId: number | string; // 服务器ID作为主键
|
2025-10-23 17:02:33 +08:00
|
|
|
|
userId: number; // 用户ID(数据隔离)
|
2025-09-11 15:00:57 +08:00
|
|
|
|
id?: number; // 接口数据的原始ID字段
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface NewContactListData {
|
|
|
|
|
|
serverId: number | string; // 服务器ID作为主键
|
2025-10-23 17:02:33 +08:00
|
|
|
|
userId: number; // 用户ID(数据隔离)
|
2025-09-11 15:00:57 +08:00
|
|
|
|
id?: number; // 接口数据的原始ID字段
|
|
|
|
|
|
groupName: string;
|
2025-10-23 17:02:33 +08:00
|
|
|
|
contacts: Contact[];
|
|
|
|
|
|
weChatGroup: Contact[];
|
2025-09-11 15:00:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 数据库类
|
|
|
|
|
|
class CunkebaoDatabase extends Dexie {
|
2025-10-23 17:02:33 +08:00
|
|
|
|
// ==================== 保留原有表(向后兼容) ====================
|
2025-09-11 15:00:57 +08:00
|
|
|
|
kfUsers!: Table<KfUserWithServerId>;
|
|
|
|
|
|
weChatGroup!: Table<weChatGroup>;
|
|
|
|
|
|
contracts!: Table<ContractData>;
|
2025-10-23 17:02:33 +08:00
|
|
|
|
newContactList!: Table<NewContactListData>;
|
2025-09-11 15:00:57 +08:00
|
|
|
|
messageList!: Table<MessageListData>;
|
|
|
|
|
|
|
2025-10-23 17:02:33 +08:00
|
|
|
|
// ==================== 新增统一表 ====================
|
|
|
|
|
|
chatSessions!: Table<ChatSession>; // 统一会话表
|
|
|
|
|
|
contactsUnified!: Table<Contact>; // 统一联系人表
|
|
|
|
|
|
contactLabelMap!: Table<ContactLabelMap>; // 联系人标签映射表
|
|
|
|
|
|
userLoginRecords!: Table<UserLoginRecord>; // 用户登录记录表
|
|
|
|
|
|
|
2025-09-11 15:00:57 +08:00
|
|
|
|
constructor() {
|
|
|
|
|
|
super("CunkebaoDatabase");
|
|
|
|
|
|
|
2025-10-23 17:02:33 +08:00
|
|
|
|
// 版本1:使用serverId作为主键的架构(保留原有表)
|
2025-09-11 15:00:57 +08:00
|
|
|
|
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:
|
2025-09-23 11:54:10 +08:00
|
|
|
|
"serverId, id, wechatAccountId, tenantId, accountId, chatroomId, chatroomOwner, conRemark, nickname, chatroomAvatar,wechatChatroomId, groupId, config, notice, selfDisplyName",
|
2025-09-11 15:00:57 +08:00
|
|
|
|
contracts:
|
2025-09-23 11:54:10 +08:00
|
|
|
|
"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, duplicate",
|
2025-10-23 17:02:33 +08:00
|
|
|
|
newContactList: "serverId, id, groupName, contacts",
|
2025-09-11 15:00:57 +08:00
|
|
|
|
messageList:
|
2025-09-23 11:54:10 +08:00
|
|
|
|
"serverId, id, dataType, wechatAccountId, tenantId, accountId, nickname, avatar, groupId, config, labels, wechatId, alias, conRemark, quanPin, gender, region, addFrom, phone, signature, extendFields, city, lastUpdateTime, isPassed, thirdParty, additionalPicture, desc, lastMessageTime, duplicate, chatroomId, chatroomOwner, chatroomAvatar, notice, selfDisplyName",
|
2025-09-11 15:00:57 +08:00
|
|
|
|
});
|
2025-10-23 17:02:33 +08:00
|
|
|
|
|
|
|
|
|
|
// 版本2:添加用户隔离字段到原有表
|
|
|
|
|
|
this.version(2)
|
|
|
|
|
|
.stores({
|
|
|
|
|
|
kfUsers:
|
|
|
|
|
|
"serverId, userId, id, tenantId, wechatId, nickname, alias, avatar, gender, region, signature, bindQQ, bindEmail, bindMobile, createTime, currentDeviceId, isDeleted, deleteTime, groupId, memo, wechatVersion, lastUpdateTime, isOnline",
|
|
|
|
|
|
weChatGroup:
|
|
|
|
|
|
"serverId, userId, id, wechatAccountId, tenantId, accountId, chatroomId, chatroomOwner, conRemark, nickname, chatroomAvatar,wechatChatroomId, groupId, config, notice, selfDisplyName",
|
|
|
|
|
|
contracts:
|
|
|
|
|
|
"serverId, userId, 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, duplicate",
|
|
|
|
|
|
newContactList: "serverId, userId, id, groupName, contacts",
|
|
|
|
|
|
messageList:
|
|
|
|
|
|
"serverId, userId, id, dataType, wechatAccountId, tenantId, accountId, nickname, avatar, groupId, config, labels, wechatId, alias, conRemark, quanPin, gender, region, addFrom, phone, signature, extendFields, city, lastUpdateTime, isPassed, thirdParty, additionalPicture, desc, lastMessageTime, duplicate, chatroomId, chatroomOwner, chatroomAvatar, notice, selfDisplyName",
|
|
|
|
|
|
})
|
|
|
|
|
|
.upgrade(trans => {
|
|
|
|
|
|
// 为现有数据添加默认userId=0(需要手动清理)
|
|
|
|
|
|
return trans
|
|
|
|
|
|
.table("kfUsers")
|
|
|
|
|
|
.toCollection()
|
|
|
|
|
|
.modify(item => {
|
|
|
|
|
|
item.userId = 0;
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 版本3:添加新的统一表和用户登录记录表
|
|
|
|
|
|
this.version(3).stores({
|
|
|
|
|
|
// 保留原有表
|
|
|
|
|
|
kfUsers:
|
|
|
|
|
|
"serverId, userId, id, tenantId, wechatId, nickname, alias, avatar, gender, region, signature, bindQQ, bindEmail, bindMobile, createTime, currentDeviceId, isDeleted, deleteTime, groupId, memo, wechatVersion, lastUpdateTime, isOnline",
|
|
|
|
|
|
weChatGroup:
|
|
|
|
|
|
"serverId, userId, id, wechatAccountId, tenantId, accountId, chatroomId, chatroomOwner, conRemark, nickname, chatroomAvatar,wechatChatroomId, groupId, config, notice, selfDisplyName",
|
|
|
|
|
|
contracts:
|
|
|
|
|
|
"serverId, userId, 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, duplicate",
|
|
|
|
|
|
newContactList: "serverId, userId, id, groupName, contacts",
|
|
|
|
|
|
messageList:
|
|
|
|
|
|
"serverId, userId, id, dataType, wechatAccountId, tenantId, accountId, nickname, avatar, groupId, config, labels, wechatId, alias, conRemark, quanPin, gender, region, addFrom, phone, signature, extendFields, city, lastUpdateTime, isPassed, thirdParty, additionalPicture, desc, lastMessageTime, duplicate, chatroomId, chatroomOwner, chatroomAvatar, notice, selfDisplyName",
|
|
|
|
|
|
|
|
|
|
|
|
// 新增统一表
|
|
|
|
|
|
// 会话表索引:支持按用户、类型、时间、置顶等查询
|
|
|
|
|
|
chatSessions:
|
|
|
|
|
|
"serverId, userId, id, type, wechatAccountId, [userId+type], [userId+wechatAccountId], [userId+lastUpdateTime], sortKey, nickname, conRemark, avatar, content, lastUpdateTime",
|
|
|
|
|
|
|
|
|
|
|
|
// 联系人表索引:支持按用户、类型、标签、搜索等查询
|
|
|
|
|
|
contactsUnified:
|
|
|
|
|
|
"serverId, userId, id, type, wechatAccountId, [userId+type], [userId+wechatAccountId], sortKey, searchKey, nickname, conRemark, avatar, lastUpdateTime",
|
|
|
|
|
|
|
|
|
|
|
|
// 联系人标签映射表索引:支持按用户、标签、联系人、类型查询
|
|
|
|
|
|
contactLabelMap:
|
|
|
|
|
|
"serverId, userId, labelId, contactId, contactType, [userId+labelId], [userId+contactId], [userId+labelId+sortKey], sortKey, searchKey, avatar, nickname, conRemark, unreadCount, lastUpdateTime",
|
|
|
|
|
|
|
|
|
|
|
|
// 用户登录记录表索引:支持按用户ID、登录时间查询
|
|
|
|
|
|
userLoginRecords:
|
|
|
|
|
|
"serverId, userId, lastLoginTime, loginCount, createTime, lastActiveTime",
|
|
|
|
|
|
});
|
2025-09-11 15:00:57 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建数据库实例
|
|
|
|
|
|
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) {
|
2025-09-11 17:35:20 +08:00
|
|
|
|
// console.log("所有数据都已存在,跳过插入");
|
2025-09-11 15:00:57 +08:00
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const processedData = newData.map(item => ({
|
|
|
|
|
|
...item,
|
|
|
|
|
|
serverId: item.id, // 使用接口的id作为serverId主键
|
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
2025-09-18 15:04:06 +08:00
|
|
|
|
operator:
|
|
|
|
|
|
| "equals"
|
|
|
|
|
|
| "notEqual"
|
|
|
|
|
|
| "above"
|
|
|
|
|
|
| "below"
|
|
|
|
|
|
| "aboveOrEqual"
|
|
|
|
|
|
| "belowOrEqual"
|
|
|
|
|
|
| "startsWith"
|
|
|
|
|
|
| "anyOf"
|
|
|
|
|
|
| "notIn"
|
|
|
|
|
|
| "between"
|
|
|
|
|
|
| "contains";
|
2025-09-11 15:00:57 +08:00
|
|
|
|
value: any;
|
2025-09-18 15:04:06 +08:00
|
|
|
|
value2?: any; // 用于 between 操作符
|
2025-09-11 15:00:57 +08:00
|
|
|
|
}[],
|
|
|
|
|
|
): Promise<T[]> {
|
|
|
|
|
|
let collection = this.table.toCollection();
|
|
|
|
|
|
|
|
|
|
|
|
for (const condition of conditions) {
|
2025-09-18 15:04:06 +08:00
|
|
|
|
const { field, operator, value, value2 } = condition;
|
2025-09-11 15:00:57 +08:00
|
|
|
|
collection = collection.and(item => {
|
|
|
|
|
|
const fieldValue = (item as any)[field];
|
|
|
|
|
|
switch (operator) {
|
|
|
|
|
|
case "equals":
|
|
|
|
|
|
return fieldValue === value;
|
2025-09-18 15:04:06 +08:00
|
|
|
|
case "notEqual":
|
|
|
|
|
|
return fieldValue !== value;
|
2025-09-11 15:00:57 +08:00
|
|
|
|
case "above":
|
|
|
|
|
|
return fieldValue > value;
|
|
|
|
|
|
case "below":
|
|
|
|
|
|
return fieldValue < value;
|
2025-09-18 15:04:06 +08:00
|
|
|
|
case "aboveOrEqual":
|
|
|
|
|
|
return fieldValue >= value;
|
|
|
|
|
|
case "belowOrEqual":
|
|
|
|
|
|
return fieldValue <= value;
|
2025-09-11 15:00:57 +08:00
|
|
|
|
case "startsWith":
|
|
|
|
|
|
return (
|
|
|
|
|
|
typeof fieldValue === "string" && fieldValue.startsWith(value)
|
|
|
|
|
|
);
|
2025-09-18 15:04:06 +08:00
|
|
|
|
case "contains":
|
|
|
|
|
|
return typeof fieldValue === "string" && fieldValue.includes(value);
|
|
|
|
|
|
case "anyOf":
|
|
|
|
|
|
return Array.isArray(value) && value.includes(fieldValue);
|
|
|
|
|
|
case "notIn":
|
|
|
|
|
|
return Array.isArray(value) && !value.includes(fieldValue);
|
|
|
|
|
|
case "between":
|
|
|
|
|
|
return fieldValue >= value && fieldValue <= (value2 ?? value);
|
2025-09-11 15:00:57 +08:00
|
|
|
|
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);
|
2025-10-23 17:02:33 +08:00
|
|
|
|
export const newContactListService = new DatabaseService(db.newContactList);
|
2025-09-11 15:00:57 +08:00
|
|
|
|
export const messageListService = new DatabaseService(db.messageList);
|
|
|
|
|
|
|
2025-10-23 17:02:33 +08:00
|
|
|
|
// 新增统一表服务
|
|
|
|
|
|
export const chatSessionService = new DatabaseService(db.chatSessions);
|
|
|
|
|
|
export const contactUnifiedService = new DatabaseService(db.contactsUnified);
|
|
|
|
|
|
export const contactLabelMapService = new DatabaseService(db.contactLabelMap);
|
|
|
|
|
|
export const userLoginRecordService = new DatabaseService(db.userLoginRecords);
|
|
|
|
|
|
|
2025-09-11 15:00:57 +08:00
|
|
|
|
// 默认导出数据库实例
|
|
|
|
|
|
export default db;
|