web-dc-api 0.1.21 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1172 -35
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/protobuf-CGYNGjQ9.js +1 -0
- package/dist/dc.min.js +1 -1
- package/dist/esm/chunks/protobuf-zJmoxMGP.js +1 -0
- package/dist/esm/index.js +1 -1
- package/dist/index.d.ts +23 -1
- package/package.json +6 -2
- package/dist/cjs/protobuf-BVBdi7Hh.js +0 -1
- package/dist/esm/chunks/protobuf-CbxDm-Gy.js +0 -1
package/README.md
CHANGED
|
@@ -1,47 +1,1184 @@
|
|
|
1
|
-
|
|
1
|
+
# Web DC API 开发指南
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**web-dc-api** 是一个革命性的去中心化 Web 开发 SDK,让您无需任何服务器即可构建完整的互联网应用。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## 核心特性
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
🚀 **零服务器架构** - 完全去中心化,无需部署后端服务器、数据库或存储服务
|
|
8
|
+
🔐 **自动统一登录** - 内置钱包登录和身份认证,一次接入,全网通用
|
|
9
|
+
💾 **分布式存储** - 用户数据库、键值存储、文件系统全部去中心化
|
|
10
|
+
💬 **内置社交功能** - 评论系统、消息通信,开箱即用
|
|
11
|
+
🤖 **AI 集成** - 原生支持 AI 代理和 MCP Server 调用
|
|
12
|
+
🔧 **免运维** - 无需服务器运维、监控、扩容,专注业务逻辑开发
|
|
13
|
+
🌉 **Web2 到 Web3 的桥梁** - 让传统 Web 开发者无缝过渡到去中心化世界
|
|
14
14
|
|
|
15
|
-
##
|
|
15
|
+
## 适用场景
|
|
16
16
|
|
|
17
|
-
-
|
|
18
|
-
-
|
|
17
|
+
- **社交应用**:博客、论坛、社区、内容平台
|
|
18
|
+
- **协作工具**:笔记、文档、任务管理
|
|
19
|
+
- **数据应用**:个人数据管理、隐私保护应用
|
|
20
|
+
- **AI 应用**:集成 AI 能力的去中心化应用
|
|
21
|
+
- **任何需要后端的 Web 应用**:DC API 就是您的后端
|
|
19
22
|
|
|
20
|
-
##
|
|
23
|
+
## 快速开始 (Quick Start)
|
|
21
24
|
|
|
22
|
-
|
|
25
|
+
以下是一个完整的调用示例,展示了从引入 SDK 到登录、初始化数据库以及使用功能的完整流程。
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
- dc.db.create(dc.dbThreadId, "coll", jsonStr) -> [id, err]
|
|
26
|
-
- dc.db.find(dc.dbThreadId, "coll", jsonQuery) -> [listJson, err] (Sort: {sort: {fieldPath: "x", desc: true}})
|
|
27
|
-
- dc.db.save(dc.dbThreadId, "coll", jsonPatch) -> [err]
|
|
28
|
-
- dc.db.delete(dc.dbThreadId, "coll", id)
|
|
27
|
+
### 1. 基础调用 (Raw SDK)
|
|
29
28
|
|
|
30
|
-
|
|
29
|
+
这是最基础的使用方式,直接引入 `DC` 类进行操作。
|
|
31
30
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
- @Entity({ name: "keyvalue_xxx_pub" }) (公开) 或 keyvalue_xxx (私有)。
|
|
35
|
-
- @Column({ type: "..." })
|
|
36
|
-
- @Index({ name: "idx_x", fields: ["f1", "f2"] }) (支持复合索引)
|
|
37
|
-
- **Service**: 注入 constructor(dc: DC, db: KeyValueDB)。
|
|
38
|
-
- 获取 Store: dc.keyValue.getStore(appId, "topic_name", APPThemeConfig.appThemeAuthor)
|
|
39
|
-
- 初始化 Repo: this.repo = new EntityRepository(EntityClass, dc.keyValue, db)
|
|
40
|
-
- **Repository API**: save(entity), findById(id), findByIndex(key, val), query(cond), deleteById(id)。
|
|
41
|
-
- **注意**: 不支持 SQL。复合索引值必须用 composeCompositeIndexValue([v1, v2]) 生成。
|
|
31
|
+
```typescript
|
|
32
|
+
import { DC } from 'web-dc-api';
|
|
42
33
|
|
|
43
|
-
|
|
34
|
+
// 1. 配置并初始化 DC 实例
|
|
35
|
+
const dc = new DC({
|
|
36
|
+
wssUrl: "wss://dcchain.baybird.cn",
|
|
37
|
+
backWssUrl: "wss://dcchain.baybird.cn",
|
|
38
|
+
appInfo: {
|
|
39
|
+
appId: "your-app-id",
|
|
40
|
+
appName: "Your App Name",
|
|
41
|
+
appVersion: "1.0.0",
|
|
42
|
+
appIcon: "",
|
|
43
|
+
appDesc: ""
|
|
44
|
+
}
|
|
45
|
+
});
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
async function main() {
|
|
48
|
+
// 2. 初始化 DC 实例 (连接节点、启动服务)
|
|
49
|
+
const initSuccess = await dc.init();
|
|
50
|
+
if (!initSuccess) {
|
|
51
|
+
console.error("DC 初始化失败");
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
console.log("DC 初始化成功");
|
|
55
|
+
|
|
56
|
+
// 3. 用户登录 (钱包登录)
|
|
57
|
+
const [accountInfo, loginError] = await dc.auth.accountLoginWithWallet();
|
|
58
|
+
|
|
59
|
+
if (loginError || !accountInfo) {
|
|
60
|
+
console.error("登录失败:", loginError);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
console.log("登录成功, 用户公钥:", dc.publicKey?.string());
|
|
64
|
+
|
|
65
|
+
// 4. 初始化用户数据库
|
|
66
|
+
const collections = [{
|
|
67
|
+
name: 'user_notes',
|
|
68
|
+
schema: {
|
|
69
|
+
type: 'object',
|
|
70
|
+
properties: {
|
|
71
|
+
_id: { type: 'string' },
|
|
72
|
+
content: { type: 'string' },
|
|
73
|
+
create_time: { type: 'number' },
|
|
74
|
+
_mod: { type: 'number' }
|
|
75
|
+
},
|
|
76
|
+
required: ["_id"]
|
|
77
|
+
}
|
|
78
|
+
}];
|
|
79
|
+
|
|
80
|
+
const [, dbError] = await dc.initUserDB(collections, "1.0.0", false);
|
|
81
|
+
if (dbError) {
|
|
82
|
+
console.error("数据库初始化失败:", dbError);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// 5. 使用功能 (例如写入数据)
|
|
87
|
+
const [id, createErr] = await dc.db.create(
|
|
88
|
+
dc.dbThreadId,
|
|
89
|
+
'user_notes',
|
|
90
|
+
JSON.stringify({
|
|
91
|
+
content: "Hello DC World!",
|
|
92
|
+
create_time: Date.now(),
|
|
93
|
+
_mod: Date.now()
|
|
94
|
+
})
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
if (!createErr) {
|
|
98
|
+
console.log("数据写入成功, ID:", id);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
main();
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 2. React 集成 (DCContext)
|
|
106
|
+
|
|
107
|
+
在 React 项目中,建议使用 Context 来管理 DC 实例。你可以将以下代码保存为 `src/contexts/DCContext.tsx`。
|
|
108
|
+
|
|
109
|
+
```tsx
|
|
110
|
+
import React, { createContext, useContext, useState, useCallback, ReactNode } from 'react';
|
|
111
|
+
import { DC } from 'web-dc-api';
|
|
112
|
+
import { AccountInfo } from 'web-dc-api/lib/common/types/types';
|
|
113
|
+
|
|
114
|
+
interface DCStatus {
|
|
115
|
+
accountInfo: AccountInfo | null;
|
|
116
|
+
isReady: boolean;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
interface DCContextType {
|
|
120
|
+
getDC: () => Promise<DC | null>;
|
|
121
|
+
saveAccountInfo: (info: AccountInfo) => void;
|
|
122
|
+
dcStatus: DCStatus;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const DCContext = createContext<DCContextType | null>(null);
|
|
126
|
+
|
|
127
|
+
export const DCProvider = ({ children }: { children: ReactNode }) => {
|
|
128
|
+
const [dcInstance, setDcInstance] = useState<DC | null>(null);
|
|
129
|
+
const [dcStatus, setDcStatus] = useState<DCStatus>({
|
|
130
|
+
accountInfo: null,
|
|
131
|
+
isReady: false
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
const getDC = useCallback(async () => {
|
|
135
|
+
if (dcInstance) return dcInstance;
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
// 初始化配置
|
|
139
|
+
const config = {
|
|
140
|
+
wssUrl: "wss://dcchain.baybird.cn",
|
|
141
|
+
backWssUrl: "wss://dcchain.baybird.cn",
|
|
142
|
+
appInfo: {
|
|
143
|
+
appId: "your-app-id",
|
|
144
|
+
appName: "Your App Name",
|
|
145
|
+
appVersion: "1.0.0",
|
|
146
|
+
appIcon: "",
|
|
147
|
+
appDesc: ""
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const dc = new DC(config);
|
|
152
|
+
|
|
153
|
+
// 初始化 DC 实例
|
|
154
|
+
const initSuccess = await dc.init();
|
|
155
|
+
if (!initSuccess) {
|
|
156
|
+
console.error("DC 初始化失败");
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
setDcInstance(dc);
|
|
161
|
+
return dc;
|
|
162
|
+
} catch (error) {
|
|
163
|
+
console.error("DC init failed:", error);
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
}, [dcInstance]);
|
|
167
|
+
|
|
168
|
+
const saveAccountInfo = useCallback((info: AccountInfo) => {
|
|
169
|
+
setDcStatus(prev => ({
|
|
170
|
+
...prev,
|
|
171
|
+
accountInfo: info,
|
|
172
|
+
isReady: true
|
|
173
|
+
}));
|
|
174
|
+
}, []);
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
<DCContext.Provider value={{ getDC, saveAccountInfo, dcStatus }}>
|
|
178
|
+
{children}
|
|
179
|
+
</DCContext.Provider>
|
|
180
|
+
);
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
export const useDC = () => {
|
|
184
|
+
const context = useContext(DCContext);
|
|
185
|
+
if (!context) {
|
|
186
|
+
throw new Error('useDC must be used within a DCProvider');
|
|
187
|
+
}
|
|
188
|
+
return context;
|
|
189
|
+
};
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### 3. React 组件中使用示例
|
|
193
|
+
|
|
194
|
+
```tsx
|
|
195
|
+
import React from 'react';
|
|
196
|
+
import { useDC } from "./contexts/DCContext";
|
|
197
|
+
|
|
198
|
+
const App = () => {
|
|
199
|
+
const { getDC, saveAccountInfo, dcStatus } = useDC();
|
|
200
|
+
|
|
201
|
+
const handleLogin = async () => {
|
|
202
|
+
// getDC() 会自动处理 dc.init()
|
|
203
|
+
const dc = await getDC();
|
|
204
|
+
if (!dc) return;
|
|
205
|
+
|
|
206
|
+
const [accountInfo, err] = await dc.auth.accountLoginWithWallet();
|
|
207
|
+
if (accountInfo) {
|
|
208
|
+
// 初始化DB...
|
|
209
|
+
// await dc.initUserDB(...)
|
|
210
|
+
|
|
211
|
+
saveAccountInfo(accountInfo);
|
|
212
|
+
}
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
return (
|
|
216
|
+
<button onClick={handleLogin}>Login</button>
|
|
217
|
+
);
|
|
218
|
+
};
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## 1. 初始化与认证 (Auth)
|
|
222
|
+
|
|
223
|
+
### DCAPI 使用示例
|
|
224
|
+
|
|
225
|
+
```tsx
|
|
226
|
+
import { useDC } from "/src/contexts/DCContext.tsx";
|
|
227
|
+
const {getDC} = useDC();
|
|
228
|
+
const dc = await getDC();
|
|
229
|
+
if (!dc) {
|
|
230
|
+
console.error("未获取到有效的DC实例或认证信息");
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### 用户登录验证
|
|
236
|
+
|
|
237
|
+
```javascript
|
|
238
|
+
// 用户登录,登录成功返回的accountInfo信息中包括:1.nftAccount: string类型,用户账号,用于界面显示 2.appAccount: Uint8Array(32)类型,应用账号,用于数据存储的用户标识
|
|
239
|
+
// 这条语句必须放在符合语法的地方
|
|
240
|
+
const { dcStatus, getDC, saveAccountInfo } = useDC();
|
|
241
|
+
const dc = await getDC();
|
|
242
|
+
const [accountInfo, loginError] = await dc.auth.accountLoginWithWallet();
|
|
243
|
+
const publicKeyStr = dc.publicKey.string(); // 获取用户公钥
|
|
244
|
+
// 用户登录成功后,必须马上初始化用户数据库
|
|
245
|
+
const [,err] = await dc.initUserDB(dbCollections, appVersion, false);
|
|
246
|
+
// 用户登录成功后,在dc.initUserDB调用之后,保存用户信息
|
|
247
|
+
saveAccountInfo(accountInfo);
|
|
248
|
+
|
|
249
|
+
// 全局其他地方可以通过以下方式判断是否登录成功
|
|
250
|
+
if (dcStatus.accountInfo && dcStatus.accountInfo.appAccount) {
|
|
251
|
+
//已经登录成功,dcStatus.accountInfo.nftAccount 表示登录的用户名
|
|
252
|
+
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
## 2. 文件模块 (File)
|
|
257
|
+
|
|
258
|
+
```javascript
|
|
259
|
+
// 上传文件并跟踪进度,进度回调参数:status表示状态: 0=成功,1=加密中,2=上传中,3=出错,4=异常,size表示已上传的字节数
|
|
260
|
+
const [cid, error] = await dc.file.addFile(
|
|
261
|
+
file, //File对象
|
|
262
|
+
'加密密钥', // 需要文件加密时,使用对32字节长度字符进行base32加密后的字符串; 不需要文件加密时, 使用空字符串
|
|
263
|
+
(status, size) => console.log(`上传状态:${status} 已上传, ${size} 字节`)
|
|
264
|
+
);
|
|
265
|
+
|
|
266
|
+
// 通过CID等待下载完整文件再返回
|
|
267
|
+
const [fileContent, error] = await dc.file.getFile('file-cid', '解密密钥'); // 解密密钥为文件上传时的加密密钥
|
|
268
|
+
|
|
269
|
+
// 通过流式下载文件,可以显示下载进度
|
|
270
|
+
const [stream, error] = await dc.file.createFileStream('file-cid', '解密密钥'); // 解密密钥为文件上传时的加密密钥
|
|
271
|
+
|
|
272
|
+
// 上传文件夹,进度回调参数:status表示状态: 0=成功,1=加密中,2=上传中,3=出错,4=异常,total表示总文件数,process表示已上传的文件数
|
|
273
|
+
const folderInput = document.getElementById('folderInput') as HTMLInputElement;
|
|
274
|
+
const files = folderInput.files;
|
|
275
|
+
const res = await dc.file.addFolder(
|
|
276
|
+
files, // 文件列表
|
|
277
|
+
"加密密钥", // 需要文件加密时,使用对32字节长度字符进行base32加密后的字符串; 不需要文件加密时, 使用空字符串
|
|
278
|
+
(status: number, total: number, process: number) => console.log(`上传状态: ${status}, 总文件数: ${total}, 进度: ${process} 已上传,`)
|
|
279
|
+
);
|
|
280
|
+
folderCID = res[0];
|
|
281
|
+
|
|
282
|
+
// 获取文件夹文件列表,返回的fileList格式为JSON对象:Array<{Name: string; Type: number; Size: number; Hash: string; Path: string}> type=0文件 type=1文件夹,每个文件内容可以将Hash字段的值作为cid通过 dc.file.getFile 获取,
|
|
283
|
+
const [fileList, error] = await dc.file.getFolderFileList(
|
|
284
|
+
'folder-cid', // 文件夹CID
|
|
285
|
+
false, // 是否重新寻址
|
|
286
|
+
true // 是否递归获取所有子文件夹的文件列表
|
|
287
|
+
);
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
## 3. ThreadDB用户数据库 (db)
|
|
291
|
+
|
|
292
|
+
用户数据库专门用于存储个人数据,支持跨设备同步。每个用户只能访问自己的数据,适合存储用户设置、个人记录等私密信息。
|
|
293
|
+
|
|
294
|
+
```javascript
|
|
295
|
+
// =====第一步:定义数据结构=====
|
|
296
|
+
// 定义数据集合(类似表结构)
|
|
297
|
+
const collections = [
|
|
298
|
+
{
|
|
299
|
+
name: 'user_notes', // 集合名称(如用户笔记)
|
|
300
|
+
schema: {
|
|
301
|
+
type: 'object',
|
|
302
|
+
properties: {
|
|
303
|
+
_id: { type: 'string' }, // 必需字段,系统自动生成
|
|
304
|
+
title: { type: 'string' }, // 笔记标题
|
|
305
|
+
content: { type: 'string' }, // 笔记内容
|
|
306
|
+
create_time: { type: 'number' }, // 创建时间
|
|
307
|
+
_mod: { type: 'number' } // 必需字段,修改时间
|
|
308
|
+
},
|
|
309
|
+
required: ["_id"],
|
|
310
|
+
additionalProperties: true
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
];
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
// =====第二步:初始化数据库=====
|
|
317
|
+
// 初始化用户数据库
|
|
318
|
+
|
|
319
|
+
// 用户登录成功后,必须进行初始化个人数据库,
|
|
320
|
+
const [, dbError] = await dc.initUserDB(collections);
|
|
321
|
+
|
|
322
|
+
if (dbError) {
|
|
323
|
+
console.error('初始化失败:', dbError);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
console.log('初始化成功,ID:', dc.dbThreadId);
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
// =====第三步:操作数据库=====
|
|
330
|
+
// 创建记录
|
|
331
|
+
const noteData = {
|
|
332
|
+
title: '第一条笔记',
|
|
333
|
+
content: '笔记内容',
|
|
334
|
+
create_time: Date.now()
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
const [recordId, createError] = await dc.db.create(
|
|
338
|
+
dc.dbThreadId,
|
|
339
|
+
'user_notes',
|
|
340
|
+
JSON.stringify(noteData) // 不需要传入_id,系统自动生成
|
|
341
|
+
);
|
|
342
|
+
console.log('记录创建成功,ID:', recordId);
|
|
343
|
+
|
|
344
|
+
// 查询记录
|
|
345
|
+
const [results, findError] = await dc.db.find(
|
|
346
|
+
dc.dbThreadId,
|
|
347
|
+
'user_notes',
|
|
348
|
+
JSON.stringify({
|
|
349
|
+
sort: { fieldPath: "create_time", desc: true } // 按创建时间倒序
|
|
350
|
+
})
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
if (results) {
|
|
354
|
+
const noteList = JSON.parse(results);
|
|
355
|
+
console.log('笔记列表:', noteList);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// 更新记录
|
|
359
|
+
const updateData = {
|
|
360
|
+
_id: recordId, // 必须包含记录ID
|
|
361
|
+
title: '更新后的标题',
|
|
362
|
+
content: '更新后的内容',
|
|
363
|
+
create_time: Date.now()
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
await dc.db.save(
|
|
367
|
+
dc.dbThreadId,
|
|
368
|
+
'user_notes',
|
|
369
|
+
JSON.stringify(updateData)
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
// 删除记录
|
|
373
|
+
await dc.db.delete(dc.dbThreadId, 'user_notes', recordId);
|
|
374
|
+
|
|
375
|
+
//=====第四步:查询举例=====
|
|
376
|
+
|
|
377
|
+
// 简单条件查询
|
|
378
|
+
const simpleQuery = {
|
|
379
|
+
condition: "title = '我的笔记'"
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
// 复合条件查询
|
|
383
|
+
const complexQuery = {
|
|
384
|
+
condition: "create_time > 1640995200000",
|
|
385
|
+
ors: [
|
|
386
|
+
{ condition: "title = '重要笔记'" }
|
|
387
|
+
],
|
|
388
|
+
sort: { fieldPath: "create_time", desc: true },
|
|
389
|
+
seek: "分页标记" // 用于分页
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
const [queryResults, queryError] = await dc.db.find(
|
|
393
|
+
dc.dbThreadId,
|
|
394
|
+
'user_notes',
|
|
395
|
+
JSON.stringify(complexQuery)
|
|
396
|
+
);
|
|
397
|
+
|
|
398
|
+
//=====第五步:实际应用=====
|
|
399
|
+
|
|
400
|
+
// 创建用户设置管理
|
|
401
|
+
const saveUserSettings = async (settings) => {
|
|
402
|
+
const [id, error] = await dc.db.create(
|
|
403
|
+
dc.dbThreadId,
|
|
404
|
+
'user_settings',
|
|
405
|
+
JSON.stringify({
|
|
406
|
+
...settings,
|
|
407
|
+
update_time: Date.now()
|
|
408
|
+
})
|
|
409
|
+
);
|
|
410
|
+
return { id, error };
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
// 获取用户最新设置
|
|
414
|
+
const getUserSettings = async () => {
|
|
415
|
+
const [results, error] = await dc.db.find(
|
|
416
|
+
dc.dbThreadId,
|
|
417
|
+
'user_settings',
|
|
418
|
+
JSON.stringify({
|
|
419
|
+
sort: { fieldPath: "update_time", desc: true }
|
|
420
|
+
})
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
if (results) {
|
|
424
|
+
const settings = JSON.parse(results);
|
|
425
|
+
return settings[0]; // 返回最新的设置
|
|
426
|
+
}
|
|
427
|
+
return null;
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
// 使用示例
|
|
431
|
+
await saveUserSettings({ theme: 'dark', language: 'zh-CN' });
|
|
432
|
+
const currentSettings = await getUserSettings();
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
## 4. keyValue DB存储
|
|
436
|
+
|
|
437
|
+
keyValue DB是一个键值对存储系统,类似于Redis或MongoDB等NoSQL数据库。它的特点是:
|
|
438
|
+
|
|
439
|
+
**功能特性:**
|
|
440
|
+
- 主题名称必须以"keyvalue_"开头
|
|
441
|
+
- 支持权限控制,可以设置哪些用户能读取或修改数据
|
|
442
|
+
- 支持索引查询,方便批量获取数据
|
|
443
|
+
|
|
444
|
+
**使用场景对比:**
|
|
445
|
+
- **用户数据库(db)**:专门用于个人数据存储,每个用户只能访问自己的数据
|
|
446
|
+
- **keyValue DB**:适合多用户共享数据的场景,比如:
|
|
447
|
+
- 应用配置信息(所有用户共享)
|
|
448
|
+
- 公告通知(发布给所有用户)
|
|
449
|
+
- 排行榜数据(用户之间可见)
|
|
450
|
+
- 商品信息(多用户浏览)
|
|
451
|
+
|
|
452
|
+
**权限管理:**
|
|
453
|
+
创建公共主题时有特殊限制:
|
|
454
|
+
- 只有应用的初始主题作者(APPThemeConfig.appThemeAuthor)才能创建供全体用户访问的公共主题
|
|
455
|
+
- 其他用户可以创建私有主题或邀请制主题
|
|
456
|
+
- 主题名称必须以"keyvalue_"开头,公共主题必须以"_pub"结尾。
|
|
457
|
+
|
|
458
|
+
简单理解:keyValue DB = 多人共享的数据仓库,ThreadDB用户数据库 (db) = 个人专属的数据柜子
|
|
459
|
+
使用keyValueDB时, 需要注意以下几点:
|
|
460
|
+
|
|
461
|
+
1. 主题名称必须以"keyvalue_"开头,公共主题必须以"_pub"结尾。
|
|
462
|
+
2. 支持权限控制,可以设置哪些用户能读取或修改数据。
|
|
463
|
+
3. 支持索引查询,方便批量获取数据。
|
|
464
|
+
|
|
465
|
+
使用示例:
|
|
466
|
+
|
|
467
|
+
```tsx
|
|
468
|
+
import { APPThemeConfig } from '@config/config.json';
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
//=====第一步:获取或创建数据存储空间=====
|
|
472
|
+
|
|
473
|
+
let kvdb = null;
|
|
474
|
+
let error = null;
|
|
475
|
+
|
|
476
|
+
// 尝试获取已存在的公共数据存储
|
|
477
|
+
[kvdb, error] = await dc.keyValue.getStore(
|
|
478
|
+
dc.appInfo.appId, // 应用ID
|
|
479
|
+
'keyvalue_app_config_pub', //存储主题名称(比如应用配置),必须以"keyvalue_"开头,如果是公共存储则额外的必须以"_pub"结尾
|
|
480
|
+
APPThemeConfig.appThemeAuthor // 主题作者公钥,这里必须为APPThemeConfig.appThemeAuthor,不要更改
|
|
481
|
+
);
|
|
482
|
+
|
|
483
|
+
// 如果存储不存在,则创建新的
|
|
484
|
+
if (!kvdb) {
|
|
485
|
+
// 检查权限:只有应用管理员才能创建公共存储
|
|
486
|
+
if (dc.publicKey.string() === APPThemeConfig.appThemeAuthor) {
|
|
487
|
+
console.log('正在创建公共数据存储...');
|
|
488
|
+
[kvdb, error] = await dc.keyValue.createStore(
|
|
489
|
+
dc.appInfo.appId,
|
|
490
|
+
'keyvalue_app_config_pub', // 主题必须以"keyvalue_"开头,如果是公共存储必须以"_pub"结尾
|
|
491
|
+
50 * 1024 * 1024, // 分配50MB存储空间
|
|
492
|
+
2 // 访问类型:2=公共(所有人可读,写入需授权)
|
|
493
|
+
);
|
|
494
|
+
|
|
495
|
+
if (kvdb) {
|
|
496
|
+
console.log('公共数据存储创建成功!');
|
|
497
|
+
}
|
|
498
|
+
} else {
|
|
499
|
+
console.error('权限不足:只有应用管理员可以创建公共存储');
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
//=====第二步:配置用户访问权限=====
|
|
505
|
+
|
|
506
|
+
// 授权所有用户具有写入权限
|
|
507
|
+
const [authResult, authError] = await dc.keyValue.configAuth(
|
|
508
|
+
kvdb,
|
|
509
|
+
'all', // 'all'表示所有用户,也可以填具体用户的公钥
|
|
510
|
+
3, // 权限级别: 0: 无权限 1: 申请权限 2: 只读 3: 读写 4: 管理员 5: 只写
|
|
511
|
+
'允许所有用户访问应用配置' // 授权说明
|
|
512
|
+
);
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
// =====第三步:存储数据(支持索引查询)=====
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
// 存储应用设置,并设置索引便于查询
|
|
521
|
+
const appSettings = {
|
|
522
|
+
theme: 'dark',
|
|
523
|
+
language: 'zh-CN',
|
|
524
|
+
version: '1.0.0'
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
//设置值,在设置时,为了后续不同纬度快速检索,可以同时设置索引信息,格式为json字符串,数据格式如下:[{key:"indexkey1",type:"string",value:"value"},{key:"indexkey2",type:"number", value:12}]
|
|
528
|
+
const [setSuccess, setError] = await dc.keyValue.set(
|
|
529
|
+
kvdb,
|
|
530
|
+
'app_settings', // 数据的键名
|
|
531
|
+
JSON.stringify(appSettings), // 数据内容(JSON格式)
|
|
532
|
+
JSON.stringify([{key:"type",type:"string",value:"settings"},{key:"theme",type:"string",value:"dark"},{key:"lang",type:"string",value:"zh-CN"}]) // 索引配置,json字符串格式:类型=设置,主题=暗色,语言=中文
|
|
533
|
+
);
|
|
534
|
+
|
|
535
|
+
if (setSuccess) {
|
|
536
|
+
console.log('应用设置保存成功');
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
// 存储用户偏好设置
|
|
540
|
+
const userPrefs = {
|
|
541
|
+
notifications: true,
|
|
542
|
+
autoSave: false,
|
|
543
|
+
userId: 'user123'
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
await dc.keyValue.set(
|
|
547
|
+
kvdb,
|
|
548
|
+
'prefs_user123', //key
|
|
549
|
+
JSON.stringify(userPrefs), //value
|
|
550
|
+
JSON.stringify([{key:"type",type:"string",value:"userprefs"},{key:"userId",type:"string",value:"user123"},{key:"notifications",type:"boolean",value:true}]) //indexs
|
|
551
|
+
);
|
|
552
|
+
|
|
553
|
+
//存储需要用于排名的数据,
|
|
554
|
+
const userRankValue = {
|
|
555
|
+
userpubkey: "bb",
|
|
556
|
+
name:"Alice",
|
|
557
|
+
score: 100
|
|
558
|
+
};
|
|
559
|
+
await dc.keyValue.set(
|
|
560
|
+
kvdb,
|
|
561
|
+
'ranking',
|
|
562
|
+
JSON.stringify(userRankValue),
|
|
563
|
+
JSON.stringify([{key:"index_score",type:"number",value:100}]),
|
|
564
|
+
);
|
|
565
|
+
|
|
566
|
+
//=====第四步:读取数据=====
|
|
567
|
+
|
|
568
|
+
// 方式1: 获取key最近设置的value,如果writerPubkey存在,则获取该用户针对该key设置的最新值,如果省略掉该参数,或者传入空字符串,
|
|
569
|
+
const [value,error] = await dc.keyValue.get(
|
|
570
|
+
kvdb,
|
|
571
|
+
'app_settings', //应用的设置
|
|
572
|
+
writerPubkey //写入者动pubkey,可以省略。如果这里省略,或者输入空字符串,则获取针对该key所有用户写入的value中的最新值
|
|
573
|
+
);
|
|
574
|
+
if (error) {
|
|
575
|
+
console.error('获取应用设置失败:', error);
|
|
576
|
+
} else {
|
|
577
|
+
console.log('应用设置:', JSON.parse(value));
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
// 方式2:通过索引获取排名,获取kvdb中所有key为"ranking",index中score值最高的前10个记录,其中最后一个参数对象的type必须与设置索引时的type一致
|
|
584
|
+
const [rankingList, getError] = await dc.keyValue.getWithIndex(
|
|
585
|
+
kvdb,
|
|
586
|
+
"index_score", //索引名称
|
|
587
|
+
null, //索引值,null表示不限定值
|
|
588
|
+
{
|
|
589
|
+
type:"number",//索引值的类型
|
|
590
|
+
limit: 10, //最多返回10个
|
|
591
|
+
seekKey: '', //起始键
|
|
592
|
+
direction: 0, //获取记录的方向: 0-正序获取记录 1-倒序获取数据
|
|
593
|
+
offset: 0 //起始位移
|
|
594
|
+
});
|
|
595
|
+
if (rankingList) {
|
|
596
|
+
const rankings = JSON.parse(rankingList); //rankingList格式为设置进去的值的数组: '[{"ranking": "{"userpubkey": "bb", "name": "Alice", "score": 100}"}]'
|
|
597
|
+
console.log('当前排行榜:', rankings);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// 方式3:通过索引批量查询
|
|
601
|
+
const [searchResults, searchError] = await dc.keyValue.getWithIndex(
|
|
602
|
+
kvdb,
|
|
603
|
+
'type', // 索引名称
|
|
604
|
+
'userprefs', // 索引值(查找所有用户偏好设置)
|
|
605
|
+
{
|
|
606
|
+
type: "string", // 索引值的类型
|
|
607
|
+
direction: 0,
|
|
608
|
+
limit: 10 , // 最多返回10条结果
|
|
609
|
+
offset: 0 , // 起始位移
|
|
610
|
+
seekKey: '' // 起始键
|
|
611
|
+
}
|
|
612
|
+
);
|
|
613
|
+
|
|
614
|
+
if (searchResults) {
|
|
615
|
+
const userPrefsList = JSON.parse(searchResults);
|
|
616
|
+
console.log('所有用户偏好设置:', userPrefsList);
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
//方式4: 获取指定key下的所有值
|
|
621
|
+
const [allValues, allError] = await dc.keyValue.getValues(
|
|
622
|
+
kvdb,
|
|
623
|
+
'prefs_user123' //所有用户的设置
|
|
624
|
+
);
|
|
625
|
+
if (allError) {
|
|
626
|
+
console.error('获取所有用户设置失败:', allError);
|
|
627
|
+
} else {
|
|
628
|
+
console.log('所有用户设置:', JSON.parse(allValues));
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
//=====实际应用场景示例=====
|
|
632
|
+
|
|
633
|
+
// 场景1:存储商品信息
|
|
634
|
+
const product = {
|
|
635
|
+
id: 'prod001',
|
|
636
|
+
name: 'iPhone 15',
|
|
637
|
+
price: 5999,
|
|
638
|
+
category: 'electronics'
|
|
639
|
+
};
|
|
640
|
+
|
|
641
|
+
await dc.keyValue.set(
|
|
642
|
+
kvdb,
|
|
643
|
+
'product_' + product.id,
|
|
644
|
+
JSON.stringify(product),
|
|
645
|
+
JSON.stringify([{key:"category",type:"string",value:product.category},{key:"price",type:"number",value:product.price},{key:"type",type:"string",value:"product"}]) // 索引:分类=电子产品,价格=5999,类型=商品
|
|
646
|
+
);
|
|
647
|
+
|
|
648
|
+
// 场景2:查询特定分类的商品
|
|
649
|
+
const [electronics, _] = await dc.keyValue.getWithIndex(
|
|
650
|
+
kvdb,
|
|
651
|
+
'category', // 按分类查询
|
|
652
|
+
'electronics', // 查找电子产品
|
|
653
|
+
{
|
|
654
|
+
type: "string",
|
|
655
|
+
limit: 20, // 最多返回20条结果
|
|
656
|
+
offset: 0,
|
|
657
|
+
seekKey: ''
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
const productList = JSON.parse(electronics);
|
|
661
|
+
console.log('电子产品列表:', productList);
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
|
|
665
|
+
## 5. 评论系统 (comment)
|
|
666
|
+
|
|
667
|
+
评论系统 (comment) 是专门为社交互动设计的功能模块,让用户可以发布、查看和管理评论内容。
|
|
668
|
+
|
|
669
|
+
**核心特点:**
|
|
670
|
+
- **时间线存储**:所有评论按发布时间顺序保存,可以按时间线浏览
|
|
671
|
+
- **权限控制**:支持设置谁能发评论、谁能查看评论
|
|
672
|
+
- **主题管理**:可以为不同话题创建独立的评论区
|
|
673
|
+
|
|
674
|
+
**与 keyValue DB 的区别:**
|
|
675
|
+
- **keyValue DB**:像一个文件柜,适合存储配置信息、商品数据等相对固定的内容,且主题名称必须以"keyvalue_"开头
|
|
676
|
+
- **评论系统**:像一个留言板,适合存储用户发言、互动讨论等时序性内容
|
|
677
|
+
|
|
678
|
+
**comment使用规则**
|
|
679
|
+
- - 如果一个生成的代码文件里面包含了对comment主题的创建操作, 必须在文件头部标注使用到的主题名称,方便数据库静态构建.
|
|
680
|
+
格式如下: `/**comment: news_comments */` 每个主题一行,如果有多个主题,请逐行添加
|
|
681
|
+
- 所有涉及到评论系统 (comment)的操作的代码文件都应该放在services目录下
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
- **评论系统**:像一个留言板,适合存储用户发言、互动讨论等时序性内容
|
|
685
|
+
|
|
686
|
+
**适用场景:**
|
|
687
|
+
- 微博、朋友圈(用户动态和评论)
|
|
688
|
+
- 论坛、社区(帖子和回复)
|
|
689
|
+
- 商品评价(买家评论和商家回复)
|
|
690
|
+
- 新闻评论(读者评论和讨论)
|
|
691
|
+
|
|
692
|
+
**灵活的内容处理:**
|
|
693
|
+
评论内容可以根据业务需要自定义格式。比如:
|
|
694
|
+
- 在评论中包含特定ID,后续可以通过这个ID来更新评论状态
|
|
695
|
+
- 支持富文本、图片、链接等多种内容格式
|
|
696
|
+
- 可以实现点赞、回复、转发等社交功能
|
|
697
|
+
|
|
698
|
+
简单理解:keyValue DB = 数据存储柜,comment = 时间线留言板
|
|
699
|
+
|
|
700
|
+
```tsx
|
|
701
|
+
import { APPThemeConfig } from '@config/config.json';
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
//=====第一步:创建评论主题(仅应用管理员可操作)=====
|
|
705
|
+
// 检查权限:只有应用管理员才能创建公共评论主题
|
|
706
|
+
if (dc.publicKey.string() === APPThemeConfig.appThemeAuthor) {
|
|
707
|
+
try {
|
|
708
|
+
console.log('正在创建公共评论主题...');
|
|
709
|
+
|
|
710
|
+
const [status, err] = await dc.comment.addThemeObj(
|
|
711
|
+
'news_comments', // 主题ID(比如新闻评论区)
|
|
712
|
+
0, // 访问类型:0=公开, 1=私密, 2=授权
|
|
713
|
+
50 * 1024 * 1024 // 分配50MB存储空间
|
|
714
|
+
);
|
|
715
|
+
if (status === 0) {
|
|
716
|
+
console.log('评论主题创建成功!');
|
|
717
|
+
}
|
|
718
|
+
} catch(e) {
|
|
719
|
+
console.log('主题已存在,无需重复创建');
|
|
720
|
+
}
|
|
721
|
+
} else {
|
|
722
|
+
console.log('权限不足:只有应用管理员可以创建公共评论主题');
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
//=====第二步:配置用户访问权限=====
|
|
727
|
+
|
|
728
|
+
// 授权所有用户具有评论权限
|
|
729
|
+
const [authStatus, authError] = await dc.comment.configAuth(
|
|
730
|
+
APPThemeConfig.appThemeAuthor, // 主题作者公钥
|
|
731
|
+
'news_comments', // 主题ID
|
|
732
|
+
'all', // 'all'表示所有用户,也可以填具体用户公钥
|
|
733
|
+
3, // 权限级别:与keyValue DB的权限级别定义一致
|
|
734
|
+
'允许所有用户参与评论讨论' // 授权说明
|
|
735
|
+
);
|
|
736
|
+
|
|
737
|
+
|
|
738
|
+
//=====第三步:发布评论=====
|
|
739
|
+
|
|
740
|
+
// 发布普通评论
|
|
741
|
+
const newsContent = {
|
|
742
|
+
newsId: 'news_001',
|
|
743
|
+
title: 'DC平台最新功能发布',
|
|
744
|
+
userComment: '这个功能很棒,期待更多更新!'
|
|
745
|
+
};
|
|
746
|
+
|
|
747
|
+
const [commentId, commentError] = await dc.comment.publishCommentToTheme(
|
|
748
|
+
'news_comments', // 主题ID
|
|
749
|
+
APPThemeConfig.appThemeAuthor, // 主题作者公钥
|
|
750
|
+
0, // 评论类型:0=普通评论, 1=点赞, 2=推荐, 3=踩
|
|
751
|
+
JSON.stringify(newsContent), // 评论内容
|
|
752
|
+
1, // 可见性:0=仅作者可见, 1=公开
|
|
753
|
+
'' // 引用其他评论(空表示不引用)
|
|
754
|
+
);
|
|
755
|
+
|
|
756
|
+
if (commentId) {
|
|
757
|
+
console.log('评论发布成功,ID:', commentId);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// 发布点赞评论
|
|
761
|
+
const [likeId, likeError] = await dc.comment.publishCommentToTheme(
|
|
762
|
+
'news_comments',
|
|
763
|
+
APPThemeConfig.appThemeAuthor, //主题作者公钥,这里必须为APPThemeConfig.appThemeAuthor,不要更改
|
|
764
|
+
1, // 评论类型:1=点赞
|
|
765
|
+
JSON.stringify({
|
|
766
|
+
action: 'like',
|
|
767
|
+
targetComment: commentId,
|
|
768
|
+
userId: dc.publicKey.string()
|
|
769
|
+
}),
|
|
770
|
+
1,
|
|
771
|
+
'1000/'+commentId // 引用刚才的评论,格式: 原评论发布时的区块高度/评论ID
|
|
772
|
+
);
|
|
773
|
+
|
|
774
|
+
//=====第四步:获取评论列表=====
|
|
775
|
+
|
|
776
|
+
// 获取最新的评论列表
|
|
777
|
+
const [comments, commentsError] = await dc.comment.getThemeComments(
|
|
778
|
+
'news_comments', // 主题ID
|
|
779
|
+
APPThemeConfig.appThemeAuthor, //主题作者公钥,这里必须为APPThemeConfig.appThemeAuthor,不要更改
|
|
780
|
+
0, // 起始高度(0表示从最新开始)
|
|
781
|
+
0, // 方向:0=最新优先, 1=最旧优先
|
|
782
|
+
0, // 偏移量(分页用)
|
|
783
|
+
20 // 获取数量限制
|
|
784
|
+
);
|
|
785
|
+
|
|
786
|
+
if (comments && !commentsError) {
|
|
787
|
+
const commentList = JSON.parse(comments);
|
|
788
|
+
console.log('评论列表:', commentList);
|
|
789
|
+
|
|
790
|
+
// 处理评论数据
|
|
791
|
+
commentList.forEach(comment => {
|
|
792
|
+
const content = JSON.parse(comment.content);
|
|
793
|
+
console.log('用户评论: '+ content.userComment);
|
|
794
|
+
console.log('发布时间: '+ new Date(comment.timestamp).toLocaleString()});
|
|
795
|
+
});
|
|
796
|
+
} else {
|
|
797
|
+
console.error('获取评论失败:', commentsError);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
//=====实际应用场景示例=====
|
|
801
|
+
|
|
802
|
+
// 场景1:新闻评论系统
|
|
803
|
+
const publishNewsComment = async (newsId, commentText) => {
|
|
804
|
+
const commentData = {
|
|
805
|
+
type: 'news_comment',
|
|
806
|
+
newsId: newsId,
|
|
807
|
+
comment: commentText,
|
|
808
|
+
timestamp: Date.now(),
|
|
809
|
+
author: dc.publicKey.string()
|
|
810
|
+
};
|
|
811
|
+
|
|
812
|
+
const [id, error] = await dc.comment.publishCommentToTheme(
|
|
813
|
+
'news_comments',
|
|
814
|
+
APPThemeConfig.appThemeAuthor, //主题作者公钥,这里必须为APPThemeConfig.appThemeAuthor,不要更改
|
|
815
|
+
0,
|
|
816
|
+
JSON.stringify(commentData),
|
|
817
|
+
1,
|
|
818
|
+
''
|
|
819
|
+
);
|
|
820
|
+
return { id, error };
|
|
821
|
+
};
|
|
822
|
+
|
|
823
|
+
// 场景2:商品评价系统
|
|
824
|
+
const publishProductReview = async (productId, rating, review) => {
|
|
825
|
+
const reviewData = {
|
|
826
|
+
type: 'product_review',
|
|
827
|
+
productId: productId,
|
|
828
|
+
rating: rating, // 1-5星评分
|
|
829
|
+
review: review,
|
|
830
|
+
timestamp: Date.now(),
|
|
831
|
+
reviewer: dc.publicKey.string()
|
|
832
|
+
};
|
|
833
|
+
|
|
834
|
+
const [id, error] = await dc.comment.publishCommentToTheme(
|
|
835
|
+
'product_reviews',
|
|
836
|
+
APPThemeConfig.appThemeAuthor,//主题作者公钥,这里必须为APPThemeConfig.appThemeAuthor,不要更改
|
|
837
|
+
0,
|
|
838
|
+
JSON.stringify(reviewData),
|
|
839
|
+
1,
|
|
840
|
+
''
|
|
841
|
+
);
|
|
842
|
+
|
|
843
|
+
return { id, error };
|
|
844
|
+
};
|
|
845
|
+
|
|
846
|
+
// 使用示例
|
|
847
|
+
await publishNewsComment('news_001', '这篇文章写得很好!');
|
|
848
|
+
await publishProductReview('prod_001', 5, '商品质量很棒,物流也很快!');
|
|
849
|
+
```
|
|
850
|
+
|
|
851
|
+
|
|
852
|
+
## 6. 消息系统 (message)
|
|
853
|
+
|
|
854
|
+
消息系统 (message) 提供用户之间的私信功能,类似于邮件系统或即时通讯。
|
|
855
|
+
|
|
856
|
+
**核心特点:**
|
|
857
|
+
- **点对点通信**:用户可以直接给其他用户发送私信
|
|
858
|
+
- **收件箱模式**:所有收到的消息都存储在个人收件箱中
|
|
859
|
+
- **简单易用**:只需要知道对方的公钥即可发送消息
|
|
860
|
+
|
|
861
|
+
**适用场景:**
|
|
862
|
+
- 用户之间的私人对话
|
|
863
|
+
- 系统通知和提醒
|
|
864
|
+
- 客服消息和反馈
|
|
865
|
+
- 好友聊天和交流
|
|
866
|
+
|
|
867
|
+
**与其他模块的区别:**
|
|
868
|
+
- **消息系统**:一对一的私密通信,类似微信私聊
|
|
869
|
+
- **评论系统**:公开的讨论区,类似微博评论
|
|
870
|
+
- **用户数据库**:个人数据存储,不涉及通信
|
|
871
|
+
|
|
872
|
+
简单理解:消息系统 = 私人邮箱,comment = 公共留言板
|
|
873
|
+
|
|
874
|
+
使用示例:
|
|
875
|
+
|
|
876
|
+
```javascript
|
|
877
|
+
// =====发送私信给其他用户=====
|
|
878
|
+
|
|
879
|
+
// 发送简单文本消息
|
|
880
|
+
const [status, sendError] = await dc.message.sendMsgToUserBox(
|
|
881
|
+
'anotherUserPublicKey123', // 接收者的公钥(可通过 dc.publicKey.string() 获取)
|
|
882
|
+
'你好,这是一条测试消息!' // 消息内容
|
|
883
|
+
);
|
|
884
|
+
|
|
885
|
+
if (status === 0) {
|
|
886
|
+
console.log('消息发送成功');
|
|
887
|
+
} else {
|
|
888
|
+
console.error('消息发送失败:', sendError);
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// 发送复杂消息(JSON格式)
|
|
892
|
+
const messageData = {
|
|
893
|
+
type: 'notification',
|
|
894
|
+
title: '系统通知',
|
|
895
|
+
content: '您有一个新的订单需要处理',
|
|
896
|
+
timestamp: Date.now(),
|
|
897
|
+
sender: dc.publicKey.string()
|
|
898
|
+
};
|
|
899
|
+
|
|
900
|
+
const [status2, sendError2] = await dc.message.sendMsgToUserBox(
|
|
901
|
+
'receiverPublicKey',
|
|
902
|
+
JSON.stringify(messageData)
|
|
903
|
+
);
|
|
904
|
+
|
|
905
|
+
//=====获取收件箱消息=====
|
|
906
|
+
|
|
907
|
+
// 获取最新的消息列表
|
|
908
|
+
const [messages, getError] = await dc.message.getMsgFromUserBox(20); // 获取最新20条消息
|
|
909
|
+
|
|
910
|
+
if (messages && !getError) {
|
|
911
|
+
console.log('收到的消息:', messages);
|
|
912
|
+
|
|
913
|
+
// 处理消息列表
|
|
914
|
+
messages.forEach((message, index) => {
|
|
915
|
+
console.log('消息 ' + (index + 1) + ': 发送者=' + message.sender + ', 内容=' + message.content + ', 时间=' + new Date(message.timestamp).toLocaleString());
|
|
916
|
+
});
|
|
917
|
+
} else {
|
|
918
|
+
console.error('获取消息失败:', getError);
|
|
919
|
+
}
|
|
920
|
+
|
|
921
|
+
//=====实际应用场景示例=====
|
|
922
|
+
|
|
923
|
+
// 场景1:发送系统通知
|
|
924
|
+
const sendSystemNotification = async (userPublicKey, title, content) => {
|
|
925
|
+
const notification = {
|
|
926
|
+
type: 'system_notification',
|
|
927
|
+
title: title,
|
|
928
|
+
content: content,
|
|
929
|
+
timestamp: Date.now(),
|
|
930
|
+
priority: 'normal'
|
|
931
|
+
};
|
|
932
|
+
|
|
933
|
+
const [status, error] = await dc.message.sendMsgToUserBox(
|
|
934
|
+
userPublicKey,
|
|
935
|
+
JSON.stringify(notification)
|
|
936
|
+
);
|
|
937
|
+
|
|
938
|
+
return { success: status === 0, error };
|
|
939
|
+
};
|
|
940
|
+
|
|
941
|
+
// 场景2:用户聊天功能
|
|
942
|
+
const sendChatMessage = async (friendPublicKey, messageText) => {
|
|
943
|
+
const chatMessage = {
|
|
944
|
+
type: 'chat',
|
|
945
|
+
message: messageText,
|
|
946
|
+
sender: dc.publicKey.string(),
|
|
947
|
+
timestamp: Date.now()
|
|
948
|
+
};
|
|
949
|
+
|
|
950
|
+
const [status, error] = await dc.message.sendMsgToUserBox(
|
|
951
|
+
friendPublicKey,
|
|
952
|
+
JSON.stringify(chatMessage)
|
|
953
|
+
);
|
|
954
|
+
|
|
955
|
+
return { success: status === 0, error };
|
|
956
|
+
};
|
|
957
|
+
|
|
958
|
+
// 场景3:获取并分类处理消息
|
|
959
|
+
const processInboxMessages = async () => {
|
|
960
|
+
const [messages, error] = await dc.message.getMsgFromUserBox(50);
|
|
961
|
+
|
|
962
|
+
if (messages && !error) {
|
|
963
|
+
const notifications = [];
|
|
964
|
+
const chatMessages = [];
|
|
965
|
+
|
|
966
|
+
messages.forEach(msg => {
|
|
967
|
+
try {
|
|
968
|
+
const parsed = JSON.parse(msg.content);
|
|
969
|
+
if (parsed.type === 'system_notification') {
|
|
970
|
+
notifications.push(parsed);
|
|
971
|
+
} else if (parsed.type === 'chat') {
|
|
972
|
+
chatMessages.push(parsed);
|
|
973
|
+
}
|
|
974
|
+
} catch (e) {
|
|
975
|
+
// 处理纯文本消息
|
|
976
|
+
chatMessages.push({
|
|
977
|
+
type: 'text',
|
|
978
|
+
message: msg.content,
|
|
979
|
+
sender: msg.sender,
|
|
980
|
+
timestamp: msg.timestamp
|
|
981
|
+
});
|
|
982
|
+
}
|
|
983
|
+
});
|
|
984
|
+
|
|
985
|
+
console.log('系统通知:', notifications);
|
|
986
|
+
console.log('聊天消息:', chatMessages);
|
|
987
|
+
}
|
|
988
|
+
};
|
|
989
|
+
|
|
990
|
+
// 使用示例
|
|
991
|
+
await sendSystemNotification('userPublicKey123', '订单更新', '您的订单已发货');
|
|
992
|
+
await sendChatMessage('friendPublicKey456', '你好,最近怎么样?');
|
|
993
|
+
await processInboxMessages();
|
|
994
|
+
```
|
|
995
|
+
|
|
996
|
+
|
|
997
|
+
## 7. AI 代理 (aiproxy)
|
|
998
|
+
|
|
999
|
+
### 创建代理配置
|
|
1000
|
+
```javascript
|
|
1001
|
+
// 创建AI代理配置,status=0表示成功
|
|
1002
|
+
const [status, error] = await dc.aiproxy.createProxyConfig(
|
|
1003
|
+
dc.appInfo.appId,
|
|
1004
|
+
'default' // 配置主题名称
|
|
1005
|
+
);
|
|
1006
|
+
```
|
|
1007
|
+
|
|
1008
|
+
### 配置AI服务
|
|
1009
|
+
```javascript
|
|
1010
|
+
|
|
1011
|
+
// 配置AI模型参数
|
|
1012
|
+
const modelConfig = {
|
|
1013
|
+
Model: "deepseek-r1",// 模型名称
|
|
1014
|
+
Temperature: 0.7,
|
|
1015
|
+
MaxTokens: 10000,
|
|
1016
|
+
TopP: 0.9,
|
|
1017
|
+
TopK: 40,
|
|
1018
|
+
StopSequences: []string{},
|
|
1019
|
+
SystemPrompt: "你是一个软件开发专家.",
|
|
1020
|
+
Stream: true, // 启用流模式
|
|
1021
|
+
Tools: []ToolDefinition{},// 工具定义数组
|
|
1022
|
+
Remark: "这是一个AI代理配置"
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
|
|
1026
|
+
const serviceConfig = {
|
|
1027
|
+
service: 'ai代理服务', // 服务名称
|
|
1028
|
+
isAIModel: 0, // 0: AI模型 1: MCPServer
|
|
1029
|
+
apiType: 0, // 模型接口类型
|
|
1030
|
+
authorization: "Bearer your-api-key", // 授权信息
|
|
1031
|
+
endpoint: "https://api.openai.com/v1", // API端点
|
|
1032
|
+
organization: "your-organization", // 组织名称或ID
|
|
1033
|
+
apiVersion: "v1", // api版本号
|
|
1034
|
+
modelConfig: modelConfig, // 模型配置
|
|
1035
|
+
remark: ""
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
//配置AI服务
|
|
1039
|
+
const [success, error] = await dc.aiproxy.configAIProxy(
|
|
1040
|
+
dc.appInfo.appId,
|
|
1041
|
+
dc.publicKey.string(), // 配置作者公钥
|
|
1042
|
+
'default', // 主题
|
|
1043
|
+
'openai-gpt', // 名称
|
|
1044
|
+
serviceConfig
|
|
1045
|
+
);
|
|
1046
|
+
|
|
1047
|
+
if (success) {
|
|
1048
|
+
console.log('AI服务配置成功');
|
|
1049
|
+
}
|
|
1050
|
+
```
|
|
1051
|
+
|
|
1052
|
+
|
|
1053
|
+
### 用户权限管理
|
|
1054
|
+
|
|
1055
|
+
```javascript
|
|
1056
|
+
// 为用户分配AI服务访问权限
|
|
1057
|
+
const authConfig = {
|
|
1058
|
+
maxTokensPerDay: 10000, // 每日最大token数
|
|
1059
|
+
allowedModels: ['gpt-3.5-turbo', 'gpt-4'],
|
|
1060
|
+
rateLimitPerMinute: 10 // 每分钟调用次数限制
|
|
1061
|
+
};
|
|
1062
|
+
|
|
1063
|
+
const authConfig: ProxyCallConfig = {
|
|
1064
|
+
No: 1,
|
|
1065
|
+
Tlim: 1000, // 总次数限制
|
|
1066
|
+
Dlim: 100, // 日限制
|
|
1067
|
+
Wlim: 500, // 周限制
|
|
1068
|
+
Mlim: 2000, // 月限制
|
|
1069
|
+
Ylim: 10000, // 年限制
|
|
1070
|
+
Exp: 12345678 // 过期区块高度
|
|
1071
|
+
};
|
|
1072
|
+
|
|
1073
|
+
const [status, error] = await dc.aiproxy.configAuth(
|
|
1074
|
+
dc.appInfo.appId,
|
|
1075
|
+
dc.publicKey.string(), // 配置作者公钥
|
|
1076
|
+
'default', // 主题
|
|
1077
|
+
'用户', // 被授权的公钥,all表示所有用户
|
|
1078
|
+
3, // 权限级别:3=写入权限
|
|
1079
|
+
authConfig // 授权配置
|
|
1080
|
+
);
|
|
1081
|
+
|
|
1082
|
+
if (status) {
|
|
1083
|
+
console.log('配置成功');
|
|
1084
|
+
}
|
|
1085
|
+
```
|
|
1086
|
+
|
|
1087
|
+
|
|
1088
|
+
### 默认调用配置
|
|
1089
|
+
在AI请求调用前调用
|
|
1090
|
+
|
|
1091
|
+
```javascript
|
|
1092
|
+
// 设置AI调用的默认参数
|
|
1093
|
+
const defaultConfig = {
|
|
1094
|
+
appId: dc.appInfo.appId,
|
|
1095
|
+
themeAuthor: dc.publicKey.string(),
|
|
1096
|
+
configTheme: 'default',
|
|
1097
|
+
serviceName: 'openai-gpt'
|
|
1098
|
+
};
|
|
1099
|
+
const error = await dc.aiproxy.SetAICallConfig(defaultConfig);
|
|
1100
|
+
|
|
1101
|
+
if (!error) {
|
|
1102
|
+
console.log('设置成功');
|
|
1103
|
+
}
|
|
1104
|
+
```
|
|
1105
|
+
|
|
1106
|
+
|
|
1107
|
+
### 执行AI调用
|
|
1108
|
+
|
|
1109
|
+
```javascript
|
|
1110
|
+
|
|
1111
|
+
const requestBody = JSON.stringify({
|
|
1112
|
+
chatMessages: [
|
|
1113
|
+
{
|
|
1114
|
+
role: "user",
|
|
1115
|
+
content: [
|
|
1116
|
+
{
|
|
1117
|
+
type: "text",
|
|
1118
|
+
text: "你好,请介绍一下DC平台"
|
|
1119
|
+
}
|
|
1120
|
+
]
|
|
1121
|
+
}
|
|
1122
|
+
]
|
|
1123
|
+
});
|
|
1124
|
+
|
|
1125
|
+
|
|
1126
|
+
const controller = new AbortController();
|
|
1127
|
+
const context = { signal: controller.signal };
|
|
1128
|
+
|
|
1129
|
+
// 流式响应处理
|
|
1130
|
+
// flag: 0表示开始接收数据, 1:权限不足 2:获取失败 3:关闭连接 4: 其他错误 content: 接收到的数据
|
|
1131
|
+
const handleStreamResponse = (flag, content, error) => {
|
|
1132
|
+
if (error) {
|
|
1133
|
+
console.error('流式响应错误:', error);
|
|
1134
|
+
return;
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
if (flag === 3) {
|
|
1138
|
+
console.log('响应完成');
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
if (flag === 1) {
|
|
1142
|
+
console.error('权限不足');
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
if (flag === 2) {
|
|
1146
|
+
console.error('获取AI服务失败:', content);
|
|
1147
|
+
return;
|
|
1148
|
+
}
|
|
1149
|
+
if (flag === 4) {
|
|
1150
|
+
console.error('错误:', content);
|
|
1151
|
+
return;
|
|
1152
|
+
}
|
|
1153
|
+
document.getElementById('ai-response').innerHTML += content;
|
|
1154
|
+
|
|
1155
|
+
};
|
|
1156
|
+
|
|
1157
|
+
// 执行AI调用
|
|
1158
|
+
const [_, error] = await dc.aiproxy.DoAIProxyCall(
|
|
1159
|
+
context,
|
|
1160
|
+
requestBody,
|
|
1161
|
+
false, // 不强制刷新
|
|
1162
|
+
handleStreamResponse, // 流式响应回调
|
|
1163
|
+
// 以下参数为空时使用默认配置
|
|
1164
|
+
undefined, // appId
|
|
1165
|
+
undefined, // themeAuthor
|
|
1166
|
+
undefined, // configTheme
|
|
1167
|
+
undefined, // serviceName
|
|
1168
|
+
undefined, // headers
|
|
1169
|
+
undefined, // path
|
|
1170
|
+
undefined // model
|
|
1171
|
+
);
|
|
1172
|
+
|
|
1173
|
+
if (error) {
|
|
1174
|
+
console.error('调用失败', error);
|
|
1175
|
+
}
|
|
1176
|
+
```
|
|
1177
|
+
|
|
1178
|
+
## 模块选择指南
|
|
1179
|
+
- **ThreadDB用户数据库(db)**: 个人数据,隐私数据,跨设备同步
|
|
1180
|
+
- **keyValue DB**: 多用户共享数据,应用配置,商品信息,排行榜
|
|
1181
|
+
- **评论系统**: 时间线社交互动,评论回复,点赞功能
|
|
1182
|
+
- **消息系统**: 点对点私密通信,系统通知
|
|
1183
|
+
- **文件模块**: 加密文件存储,支持文件夹管理
|
|
1184
|
+
- **AI代理**: 应用中需要调用AI模型或者MCPServer时,使用AI代理模块进行配置和调用
|