217 lines
5.7 KiB
Markdown
217 lines
5.7 KiB
Markdown
|
|
|
|||
|
|
# DBManager (SQLite 封装工具类)
|
|||
|
|
|
|||
|
|
`DBManager` 是基于 `plus.sqlite` 的 Promise 化封装,旨在简化 App 端本地数据库的操作。它支持自动建表、自动创建索引、以及 JSON 字段的自动序列化/反序列化。
|
|||
|
|
|
|||
|
|
#✨ 核心特性
|
|||
|
|
* **开箱即用**:`open()` 时自动检测并创建数据库、表结构和索引。
|
|||
|
|
* **配置灵活**:通过 `DBSchema` 接口纯配置化定义表结构。
|
|||
|
|
* **JSON 友好**:配置 `jsonFields` 后,无需手动 `JSON.stringify/parse`,直接存取对象。
|
|||
|
|
* **Promise 封装**:支持 `async/await`,告别回调地狱。
|
|||
|
|
* **批量操作**:内置事务支持,提升批量插入性能。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ⚙️ 配置说明 (DBSchema)
|
|||
|
|
|
|||
|
|
在使用前,你需要定义一个 `DBSchema` 对象。
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
export interface DBSchema {
|
|||
|
|
/** 数据库逻辑名称 (如: 'chat_db') */
|
|||
|
|
dbName: string;
|
|||
|
|
/** 数据库文件路径 (推荐: '_doc/xxx.db') */
|
|||
|
|
dbPath: string;
|
|||
|
|
/** 表名 */
|
|||
|
|
tableName: string;
|
|||
|
|
/**
|
|||
|
|
* 建表列定义 (标准 SQL 语法)
|
|||
|
|
* 必须在此处定义 PRIMARY KEY
|
|||
|
|
*/
|
|||
|
|
columns: string;
|
|||
|
|
/**
|
|||
|
|
* 需要创建索引的字段列表
|
|||
|
|
* 支持单字段: ['timestamp']
|
|||
|
|
* 支持组合索引: ['user_id, timestamp']
|
|||
|
|
*/
|
|||
|
|
indexes?: string[];
|
|||
|
|
/**
|
|||
|
|
* 需要自动 JSON 序列化的字段名
|
|||
|
|
* 对应的 columns 类型建议为 TEXT
|
|||
|
|
*/
|
|||
|
|
jsonFields?: string[];
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 📝 填表案例 (Schema Examples)
|
|||
|
|
|
|||
|
|
以下是几种常见业务场景的配置写法:
|
|||
|
|
|
|||
|
|
### 场景一:聊天记录表 (标准场景)
|
|||
|
|
* **需求**:使用 UUID 字符串作为主键,存储消息内容,其中 `media` 和 `extra` 字段存储复杂的 JSON 对象,需要按会话 ID 和时间查询。
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
const ChatSchema: DBSchema = {
|
|||
|
|
dbName: 'im_core',
|
|||
|
|
dbPath: '_doc/im_core.db',
|
|||
|
|
tableName: 'messages',
|
|||
|
|
|
|||
|
|
// 定义列:注意 media 和 extra 定义为 TEXT 以存储 JSON 字符串
|
|||
|
|
columns: `
|
|||
|
|
guid TEXT PRIMARY KEY,
|
|||
|
|
session_id TEXT,
|
|||
|
|
sender_id TEXT,
|
|||
|
|
content TEXT,
|
|||
|
|
msg_type INTEGER,
|
|||
|
|
media TEXT,
|
|||
|
|
extra TEXT,
|
|||
|
|
is_read INTEGER,
|
|||
|
|
timestamp INTEGER
|
|||
|
|
`,
|
|||
|
|
|
|||
|
|
// 定义索引:加快查询速度
|
|||
|
|
indexes: [
|
|||
|
|
'timestamp', // 按时间排序
|
|||
|
|
'session_id, timestamp' // 获取某个会话的历史记录 (组合索引)
|
|||
|
|
],
|
|||
|
|
|
|||
|
|
// 自动转换:存入时自动 stringify,取出时自动 parse
|
|||
|
|
jsonFields: ['media', 'extra']
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 场景二:待办事项表 (自增主键)
|
|||
|
|
* **需求**:简单的任务列表,ID 需要自动递增。
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
const TodoSchema: DBSchema = {
|
|||
|
|
dbName: 'todo_app',
|
|||
|
|
dbPath: '_doc/todo.db',
|
|||
|
|
tableName: 'tasks',
|
|||
|
|
|
|||
|
|
// 使用 SQLite 的自增语法
|
|||
|
|
columns: `
|
|||
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|||
|
|
title TEXT,
|
|||
|
|
is_completed INTEGER,
|
|||
|
|
created_at INTEGER
|
|||
|
|
`,
|
|||
|
|
|
|||
|
|
indexes: ['is_completed']
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 场景三:用户关系表 (联合主键)
|
|||
|
|
* **需求**:存储群组成员关系,一个用户在一个群里只能有一条记录(`user_id` + `group_id` 唯一)。
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
const MemberSchema: DBSchema = {
|
|||
|
|
dbName: 'relation_db',
|
|||
|
|
dbPath: '_doc/relation.db',
|
|||
|
|
tableName: 'group_members',
|
|||
|
|
|
|||
|
|
// 在末尾定义联合主键
|
|||
|
|
columns: `
|
|||
|
|
group_id TEXT,
|
|||
|
|
user_id TEXT,
|
|||
|
|
nickname TEXT,
|
|||
|
|
role TEXT,
|
|||
|
|
join_time INTEGER,
|
|||
|
|
PRIMARY KEY (group_id, user_id)
|
|||
|
|
`,
|
|||
|
|
|
|||
|
|
indexes: ['user_id'] // group_id 已经在主键索引里了,不需要单独建
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 🚀 快速开始
|
|||
|
|
### 1. 初始化
|
|||
|
|
|
|||
|
|
推荐在 Store 或 Service 层创建一个单例实例。
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
import { DBManager, DBSchema } from './utils/DBManager';
|
|||
|
|
|
|||
|
|
// 1. 定义配置
|
|||
|
|
const config: DBSchema = { /* 参考上面的案例 */ };
|
|||
|
|
|
|||
|
|
// 2. 创建实例
|
|||
|
|
export const ChatDB = new DBManager(config);
|
|||
|
|
|
|||
|
|
// 3. 打开数据库 (通常在 App 启动或登录后调用)
|
|||
|
|
await ChatDB.open();
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 2. 插入/更新数据 (Upsert)
|
|||
|
|
|
|||
|
|
使用 `Upsert` 方法,如果主键存在则更新,不存在则插入。
|
|||
|
|
**注意**:可以直接传入包含对象的 JSON 字段,无需手动转换。
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
const msg = {
|
|||
|
|
guid: 'msg_1001',
|
|||
|
|
session_id: 'sess_a',
|
|||
|
|
content: 'Hello World',
|
|||
|
|
timestamp: 1679000000,
|
|||
|
|
is_read: true, // 会自动转为 1
|
|||
|
|
// 这里的 media 对象会自动转为字符串存入数据库
|
|||
|
|
media: {
|
|||
|
|
type: 'image',
|
|||
|
|
url: 'https://xxx.com/img.png',
|
|||
|
|
width: 100
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
await ChatDB.Upsert(msg);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3. 查询数据 (Find)
|
|||
|
|
|
|||
|
|
支持 `where`、`orderBy`、`limit`、`offset`。
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
const history = await ChatDB.find({
|
|||
|
|
where: {
|
|||
|
|
session_id: 'sess_a',
|
|||
|
|
// 支持简单的比较操作符 (需在 key 中包含空格)
|
|||
|
|
'timestamp <': 1679999999
|
|||
|
|
},
|
|||
|
|
orderBy: 'timestamp DESC',
|
|||
|
|
limit: 20
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
// history[0].media 会自动被还原为对象
|
|||
|
|
console.log(history[0].media.url);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4. 复杂查询 (Raw SQL)
|
|||
|
|
|
|||
|
|
如果内置方法无法满足需求(如模糊搜索),可执行原生 SQL。
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// 模糊搜索示例
|
|||
|
|
const keyword = '测试';
|
|||
|
|
// 注意手动处理 SQL 转义,防止单引号报错
|
|||
|
|
const safeKeyword = keyword.replace(/'/g, "''");
|
|||
|
|
|
|||
|
|
const sql = `
|
|||
|
|
SELECT * FROM messages
|
|||
|
|
WHERE content LIKE '%${safeKeyword}%'
|
|||
|
|
ORDER BY timestamp DESC
|
|||
|
|
`;
|
|||
|
|
|
|||
|
|
const res = await ChatDB.queryRaw(sql);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## ⚠️ 注意事项
|
|||
|
|
|
|||
|
|
1. **修改表结构**:SQLite 不支持直接修改列类型或删除列。如果修改了 `DBSchema` 的 `columns`,通常需要卸载 App 或手动迁移数据(`ALTER TABLE ADD COLUMN` 可通过 `executeSql` 手动执行)。
|
|||
|
|
2. **布尔值**:SQLite 没有 Boolean 类型,本工具会自动将 `true/false` 转换为 `1/0` 存储。
|
|||
|
|
3. **单引号转义**:在使用 `queryRaw` 手动拼接 SQL 时,务必使用 `.replace(/'/g, "''")` 处理字符串参数,防止 SQL 报错或注入。
|