feat(db): 添加Dexie数据库支持并重构数据存储结构
添加Dexie作为IndexedDB封装库,实现本地数据存储功能 重构数据接口定义和存储模块结构,优化类型定义 统一数据接口文件位置,增强代码可维护性
This commit is contained in:
446
Cunkebao/src/utils/db.ts
Normal file
446
Cunkebao/src/utils/db.ts
Normal file
@@ -0,0 +1,446 @@
|
||||
import Dexie, { Table } from "dexie";
|
||||
import { KfUserListData, GroupData, ContractData } from "@/pages/pc/ckbox/data";
|
||||
|
||||
// 定义数据库表结构接口
|
||||
export interface BaseEntity {
|
||||
id?: number;
|
||||
createdAt?: Date;
|
||||
updatedAt?: Date;
|
||||
}
|
||||
|
||||
export interface User extends BaseEntity {
|
||||
name: string;
|
||||
email: string;
|
||||
avatar?: string;
|
||||
status: "active" | "inactive";
|
||||
}
|
||||
|
||||
export interface Message extends BaseEntity {
|
||||
userId: number;
|
||||
content: string;
|
||||
type: "text" | "image" | "file";
|
||||
isRead: boolean;
|
||||
}
|
||||
|
||||
export interface ChatRoom extends BaseEntity {
|
||||
name: string;
|
||||
description?: string;
|
||||
memberIds: number[];
|
||||
lastMessageAt?: Date;
|
||||
}
|
||||
|
||||
export interface Setting extends BaseEntity {
|
||||
key: string;
|
||||
value: any;
|
||||
category: string;
|
||||
}
|
||||
|
||||
// 数据库类
|
||||
class AppDatabase extends Dexie {
|
||||
users!: Table<User>;
|
||||
messages!: Table<Message>;
|
||||
chatRooms!: Table<ChatRoom>;
|
||||
settings!: Table<Setting>;
|
||||
|
||||
constructor() {
|
||||
super("CunkebaoDatabase");
|
||||
|
||||
this.version(1).stores({
|
||||
users: "++id, name, email, status, createdAt",
|
||||
messages: "++id, userId, type, isRead, createdAt",
|
||||
chatRooms: "++id, name, lastMessageAt, createdAt",
|
||||
settings: "++id, key, category, createdAt",
|
||||
});
|
||||
|
||||
// 自动添加时间戳
|
||||
this.users.hook("creating", (primKey, obj, trans) => {
|
||||
obj.createdAt = new Date();
|
||||
obj.updatedAt = new Date();
|
||||
});
|
||||
|
||||
this.users.hook("updating", (modifications, primKey, obj, trans) => {
|
||||
modifications.updatedAt = new Date();
|
||||
});
|
||||
|
||||
this.messages.hook("creating", (primKey, obj, trans) => {
|
||||
obj.createdAt = new Date();
|
||||
obj.updatedAt = new Date();
|
||||
});
|
||||
|
||||
this.chatRooms.hook("creating", (primKey, obj, trans) => {
|
||||
obj.createdAt = new Date();
|
||||
obj.updatedAt = new Date();
|
||||
});
|
||||
|
||||
this.settings.hook("creating", (primKey, obj, trans) => {
|
||||
obj.createdAt = new Date();
|
||||
obj.updatedAt = new Date();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 创建数据库实例
|
||||
export const db = new AppDatabase();
|
||||
|
||||
// 通用数据库操作类
|
||||
export class DatabaseService<T extends BaseEntity> {
|
||||
constructor(private table: Table<T>) {}
|
||||
|
||||
// 基础 CRUD 操作
|
||||
async create(
|
||||
data: Omit<T, "id" | "createdAt" | "updatedAt">,
|
||||
): Promise<number> {
|
||||
return await this.table.add(data as T);
|
||||
}
|
||||
|
||||
async createMany(
|
||||
dataList: Omit<T, "id" | "createdAt" | "updatedAt">[],
|
||||
): Promise<number[]> {
|
||||
return await this.table.bulkAdd(dataList as T[], { allKeys: true });
|
||||
}
|
||||
|
||||
async findById(id: number): Promise<T | undefined> {
|
||||
return await this.table.get(id);
|
||||
}
|
||||
|
||||
async findAll(): Promise<T[]> {
|
||||
return await this.table.toArray();
|
||||
}
|
||||
|
||||
async findByIds(ids: number[]): Promise<T[]> {
|
||||
return await this.table.where("id").anyOf(ids).toArray();
|
||||
}
|
||||
|
||||
async update(
|
||||
id: number,
|
||||
data: Partial<Omit<T, "id" | "createdAt">>,
|
||||
): Promise<number> {
|
||||
return await this.table.update(id, data);
|
||||
}
|
||||
|
||||
async updateMany(
|
||||
updates: { id: number; data: Partial<Omit<T, "id" | "createdAt">> }[],
|
||||
): Promise<number> {
|
||||
return await this.table.bulkUpdate(
|
||||
updates.map(u => ({ key: u.id, changes: u.data })),
|
||||
);
|
||||
}
|
||||
|
||||
async delete(id: number): Promise<void> {
|
||||
await this.table.delete(id);
|
||||
}
|
||||
|
||||
async deleteMany(ids: number[]): Promise<void> {
|
||||
await this.table.bulkDelete(ids);
|
||||
}
|
||||
|
||||
async deleteAll(): Promise<void> {
|
||||
await this.table.clear();
|
||||
}
|
||||
|
||||
// 分页查询
|
||||
async paginate(
|
||||
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 findWhere(field: keyof T, value: any): Promise<T[]> {
|
||||
return await this.table
|
||||
.where(field as string)
|
||||
.equals(value)
|
||||
.toArray();
|
||||
}
|
||||
|
||||
async findWhereIn(field: keyof T, values: any[]): Promise<T[]> {
|
||||
return await this.table
|
||||
.where(field as string)
|
||||
.anyOf(values)
|
||||
.toArray();
|
||||
}
|
||||
|
||||
async findWhereNot(field: keyof T, value: any): Promise<T[]> {
|
||||
return await this.table
|
||||
.where(field as string)
|
||||
.notEqual(value)
|
||||
.toArray();
|
||||
}
|
||||
|
||||
// 排序查询
|
||||
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 search(field: keyof T, keyword: string): Promise<T[]> {
|
||||
return await this.table
|
||||
.where(field as string)
|
||||
.startsWithIgnoreCase(keyword)
|
||||
.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();
|
||||
}
|
||||
|
||||
// 存在性检查
|
||||
async exists(id: number): Promise<boolean> {
|
||||
const item = await this.table.get(id);
|
||||
return !!item;
|
||||
}
|
||||
|
||||
async existsWhere(field: keyof T, value: any): Promise<boolean> {
|
||||
const count = await this.table
|
||||
.where(field as string)
|
||||
.equals(value)
|
||||
.count();
|
||||
return count > 0;
|
||||
}
|
||||
}
|
||||
|
||||
// 创建各表的服务实例
|
||||
export const userService = new DatabaseService(db.users);
|
||||
export const messageService = new DatabaseService(db.messages);
|
||||
export const chatRoomService = new DatabaseService(db.chatRooms);
|
||||
export const settingService = new DatabaseService(db.settings);
|
||||
|
||||
// 专门的业务方法
|
||||
export class UserService extends DatabaseService<User> {
|
||||
constructor() {
|
||||
super(db.users);
|
||||
}
|
||||
|
||||
async findByEmail(email: string): Promise<User | undefined> {
|
||||
return await db.users.where("email").equals(email).first();
|
||||
}
|
||||
|
||||
async findActiveUsers(): Promise<User[]> {
|
||||
return await db.users.where("status").equals("active").toArray();
|
||||
}
|
||||
|
||||
async searchByName(name: string): Promise<User[]> {
|
||||
return await db.users.where("name").startsWithIgnoreCase(name).toArray();
|
||||
}
|
||||
|
||||
async updateStatus(
|
||||
id: number,
|
||||
status: "active" | "inactive",
|
||||
): Promise<number> {
|
||||
return await this.update(id, { status });
|
||||
}
|
||||
}
|
||||
|
||||
export class MessageService extends DatabaseService<Message> {
|
||||
constructor() {
|
||||
super(db.messages);
|
||||
}
|
||||
|
||||
async findByUserId(userId: number): Promise<Message[]> {
|
||||
return await db.messages.where("userId").equals(userId).toArray();
|
||||
}
|
||||
|
||||
async findUnreadMessages(): Promise<Message[]> {
|
||||
return await db.messages.where("isRead").equals(false).toArray();
|
||||
}
|
||||
|
||||
async markAsRead(id: number): Promise<number> {
|
||||
return await this.update(id, { isRead: true });
|
||||
}
|
||||
|
||||
async markAllAsRead(userId: number): Promise<number> {
|
||||
const messages = await db.messages
|
||||
.where("userId")
|
||||
.equals(userId)
|
||||
.and(msg => !msg.isRead)
|
||||
.toArray();
|
||||
const updates = messages.map(msg => ({
|
||||
id: msg.id!,
|
||||
data: { isRead: true },
|
||||
}));
|
||||
return await this.updateMany(updates);
|
||||
}
|
||||
|
||||
async getRecentMessages(limit: number = 50): Promise<Message[]> {
|
||||
return await db.messages
|
||||
.orderBy("createdAt")
|
||||
.reverse()
|
||||
.limit(limit)
|
||||
.toArray();
|
||||
}
|
||||
}
|
||||
|
||||
export class SettingService extends DatabaseService<Setting> {
|
||||
constructor() {
|
||||
super(db.settings);
|
||||
}
|
||||
|
||||
async getSetting(key: string): Promise<any> {
|
||||
const setting = await db.settings.where("key").equals(key).first();
|
||||
return setting?.value;
|
||||
}
|
||||
|
||||
async setSetting(
|
||||
key: string,
|
||||
value: any,
|
||||
category: string = "general",
|
||||
): Promise<number> {
|
||||
const existing = await db.settings.where("key").equals(key).first();
|
||||
if (existing) {
|
||||
return await this.update(existing.id!, { value });
|
||||
} else {
|
||||
return await this.create({ key, value, category });
|
||||
}
|
||||
}
|
||||
|
||||
async getSettingsByCategory(category: string): Promise<Setting[]> {
|
||||
return await db.settings.where("category").equals(category).toArray();
|
||||
}
|
||||
|
||||
async deleteSetting(key: string): Promise<void> {
|
||||
await db.settings.where("key").equals(key).delete();
|
||||
}
|
||||
}
|
||||
|
||||
// 数据库工具类
|
||||
export class DatabaseUtils {
|
||||
// 数据导出
|
||||
static async exportData(): Promise<string> {
|
||||
const data = {
|
||||
users: await db.users.toArray(),
|
||||
messages: await db.messages.toArray(),
|
||||
chatRooms: await db.chatRooms.toArray(),
|
||||
settings: await db.settings.toArray(),
|
||||
exportedAt: new Date().toISOString(),
|
||||
};
|
||||
return JSON.stringify(data, null, 2);
|
||||
}
|
||||
|
||||
// 数据导入
|
||||
static async importData(jsonData: string): Promise<void> {
|
||||
try {
|
||||
const data = JSON.parse(jsonData);
|
||||
|
||||
await db.transaction(
|
||||
"rw",
|
||||
[db.users, db.messages, db.chatRooms, db.settings],
|
||||
async () => {
|
||||
if (data.users) await db.users.bulkPut(data.users);
|
||||
if (data.messages) await db.messages.bulkPut(data.messages);
|
||||
if (data.chatRooms) await db.chatRooms.bulkPut(data.chatRooms);
|
||||
if (data.settings) await db.settings.bulkPut(data.settings);
|
||||
},
|
||||
);
|
||||
} catch (error) {
|
||||
throw new Error("导入数据失败: " + error);
|
||||
}
|
||||
}
|
||||
|
||||
// 清空所有数据
|
||||
static async clearAllData(): Promise<void> {
|
||||
await db.transaction(
|
||||
"rw",
|
||||
[db.users, db.messages, db.chatRooms, db.settings],
|
||||
async () => {
|
||||
await db.users.clear();
|
||||
await db.messages.clear();
|
||||
await db.chatRooms.clear();
|
||||
await db.settings.clear();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// 获取数据库统计信息
|
||||
static async getStats(): Promise<{
|
||||
users: number;
|
||||
messages: number;
|
||||
chatRooms: number;
|
||||
settings: number;
|
||||
totalSize: number;
|
||||
}> {
|
||||
const [users, messages, chatRooms, settings] = await Promise.all([
|
||||
db.users.count(),
|
||||
db.messages.count(),
|
||||
db.chatRooms.count(),
|
||||
db.settings.count(),
|
||||
]);
|
||||
|
||||
// 估算数据库大小(简单估算)
|
||||
const totalSize = users + messages + chatRooms + settings;
|
||||
|
||||
return { users, messages, chatRooms, settings, totalSize };
|
||||
}
|
||||
|
||||
// 数据库健康检查
|
||||
static async healthCheck(): Promise<{
|
||||
status: "healthy" | "error";
|
||||
message: string;
|
||||
}> {
|
||||
try {
|
||||
await db.users.limit(1).toArray();
|
||||
return { status: "healthy", message: "数据库连接正常" };
|
||||
} catch (error) {
|
||||
return { status: "error", message: "数据库连接异常: " + error };
|
||||
}
|
||||
}
|
||||
|
||||
// 数据备份到文件
|
||||
static async backupToFile(): Promise<void> {
|
||||
const data = await this.exportData();
|
||||
const blob = new Blob([data], { type: "application/json" });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
const a = document.createElement("a");
|
||||
a.href = url;
|
||||
a.download = `cunkebao-backup-${new Date().toISOString().split("T")[0]}.json`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
document.body.removeChild(a);
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
|
||||
// 从文件恢复数据
|
||||
static async restoreFromFile(file: File): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = async e => {
|
||||
try {
|
||||
const jsonData = e.target?.result as string;
|
||||
await this.importData(jsonData);
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
};
|
||||
reader.onerror = () => reject(new Error("文件读取失败"));
|
||||
reader.readAsText(file);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 创建业务服务实例
|
||||
export const userBusinessService = new UserService();
|
||||
export const messageBusinessService = new MessageService();
|
||||
export const settingBusinessService = new SettingService();
|
||||
|
||||
// 默认导出数据库实例
|
||||
export default db;
|
||||
Reference in New Issue
Block a user