@seaverse/data-sdk 0.1.0 → 0.1.2
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 +259 -111
- package/dist/index.cjs +464 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +241 -1
- package/dist/index.js +449 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,6 +31,9 @@
|
|
|
31
31
|
- ✅ 完整的错误处理
|
|
32
32
|
- ✅ 支持过滤、排序、分页
|
|
33
33
|
- ✅ 支持标签和 JSONB 字段查询
|
|
34
|
+
- ✅ **无缝接入** - 自动从 localStorage/PostMessage 获取配置
|
|
35
|
+
- ✅ **零配置使用** - 可选的初始化,支持开箱即用
|
|
36
|
+
- ✅ **函数式 API** - 提供便捷的函数调用方式
|
|
34
37
|
|
|
35
38
|
## 安装
|
|
36
39
|
|
|
@@ -40,120 +43,144 @@ npm install @seaverse/data-sdk
|
|
|
40
43
|
|
|
41
44
|
## 快速开始
|
|
42
45
|
|
|
43
|
-
###
|
|
46
|
+
### 两种使用方式
|
|
44
47
|
|
|
45
|
-
|
|
48
|
+
#### 方式 A:函数式 API(推荐 - 零配置)
|
|
49
|
+
|
|
50
|
+
**适合 SeaVerse 平台应用** - SDK 自动从 localStorage 获取配置,无需手动初始化
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { query, create, update, deleteData } from '@seaverse/data-sdk';
|
|
54
|
+
|
|
55
|
+
// 直接使用,无需初始化!
|
|
56
|
+
// SDK 自动从 localStorage.auth_token 和 localStorage.app_id 获取配置
|
|
57
|
+
|
|
58
|
+
// 查询数据
|
|
59
|
+
const notes = await query({
|
|
60
|
+
table: 'notes',
|
|
61
|
+
filters: { category: 'work' },
|
|
62
|
+
order: { field: 'created_at', direction: 'desc' },
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
// 创建数据
|
|
66
|
+
const newNote = await create({
|
|
67
|
+
table: 'notes',
|
|
68
|
+
data: { title: 'My Note', content: '...' },
|
|
69
|
+
visibility: 'private',
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// 更新数据
|
|
73
|
+
await update('note-id-123', {
|
|
74
|
+
data: { title: 'Updated' },
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
// 删除数据
|
|
78
|
+
await deleteData('note-id-123');
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**可选初始化(如需自定义配置):**
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { initData, query } from '@seaverse/data-sdk';
|
|
85
|
+
|
|
86
|
+
// 可选:在应用启动时初始化
|
|
87
|
+
await initData({
|
|
88
|
+
appId: 'my-app-123', // 可选,未提供则从 localStorage 读取
|
|
89
|
+
token: 'user-jwt-token', // 可选,未提供则从 localStorage 读取
|
|
90
|
+
debug: true, // 可选
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// 然后直接使用
|
|
94
|
+
const notes = await query({ table: 'notes' });
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
#### 方式 B:类实例 API(传统方式)
|
|
98
|
+
|
|
99
|
+
**适合需要多个实例或完全自定义配置的场景**
|
|
46
100
|
|
|
47
101
|
```typescript
|
|
48
102
|
import { DataClient } from '@seaverse/data-sdk';
|
|
49
103
|
|
|
50
|
-
// 方式1: SeaVerse
|
|
51
|
-
// 适用于部署在 SeaVerse 平台的应用
|
|
104
|
+
// 方式1: SeaVerse 平台应用(自动检测环境)
|
|
52
105
|
const client = new DataClient({
|
|
53
|
-
appId: 'my-app-123',
|
|
54
|
-
token: 'user-jwt-token',
|
|
106
|
+
appId: 'my-app-123',
|
|
107
|
+
token: 'user-jwt-token',
|
|
55
108
|
});
|
|
56
109
|
|
|
57
|
-
// 方式2:
|
|
58
|
-
// 适用于非 SeaVerse 平台或需要明确指定环境的应用
|
|
110
|
+
// 方式2: 指定环境
|
|
59
111
|
const client = new DataClient({
|
|
60
112
|
appId: 'my-app-123',
|
|
61
113
|
token: 'user-jwt-token',
|
|
62
|
-
environment: 'production', //
|
|
114
|
+
environment: 'production', // 'production' | 'staging' | 'development' | 'local'
|
|
63
115
|
});
|
|
64
116
|
|
|
65
|
-
// 方式3:
|
|
66
|
-
// 适用于私有部署或特殊环境
|
|
117
|
+
// 方式3: 完全自定义
|
|
67
118
|
const client = new DataClient({
|
|
68
119
|
appId: 'my-app-123',
|
|
69
120
|
token: 'user-jwt-token',
|
|
70
|
-
baseURL: 'https://custom-api.example.com',
|
|
121
|
+
baseURL: 'https://custom-api.example.com',
|
|
71
122
|
});
|
|
123
|
+
|
|
124
|
+
// 使用 client 实例
|
|
125
|
+
const notes = await client.query({ table: 'notes' });
|
|
72
126
|
```
|
|
73
127
|
|
|
74
128
|
**配置选项说明:**
|
|
75
129
|
|
|
76
130
|
| 参数 | 类型 | 必需 | 说明 |
|
|
77
131
|
|-----|------|------|------|
|
|
78
|
-
| `appId` | string |
|
|
79
|
-
| `token` | string |
|
|
132
|
+
| `appId` | string | ⬜️ | 应用ID,未提供则从 localStorage/环境变量获取 |
|
|
133
|
+
| `token` | string | ⬜️ | 用户JWT token,未提供则从 localStorage/环境变量获取 |
|
|
80
134
|
| `baseURL` | string | ⬜️ | 自定义API地址,优先级最高 |
|
|
81
135
|
| `environment` | Environment | ⬜️ | 环境类型,当未提供baseURL时使用 |
|
|
82
136
|
| `timeout` | number | ⬜️ | 请求超时时间(毫秒),默认10000 |
|
|
83
137
|
| `headers` | object | ⬜️ | 自定义请求头 |
|
|
84
138
|
| `debug` | boolean | ⬜️ | 是否开启调试日志 |
|
|
85
139
|
|
|
86
|
-
###
|
|
87
|
-
|
|
88
|
-
```typescript
|
|
89
|
-
// 查询列表
|
|
90
|
-
const notes = await client.query({
|
|
91
|
-
table: 'notes',
|
|
92
|
-
filters: {
|
|
93
|
-
category: 'work',
|
|
94
|
-
visibility: 'private',
|
|
95
|
-
},
|
|
96
|
-
order: {
|
|
97
|
-
field: 'created_at',
|
|
98
|
-
direction: 'desc',
|
|
99
|
-
},
|
|
100
|
-
pagination: {
|
|
101
|
-
limit: 20,
|
|
102
|
-
offset: 0,
|
|
103
|
-
},
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
// 查询单条
|
|
107
|
-
const note = await client.get('note-id-123');
|
|
108
|
-
```
|
|
140
|
+
### 配置自动获取规则
|
|
109
141
|
|
|
110
|
-
|
|
142
|
+
SDK 按照以下优先级自动获取配置:
|
|
111
143
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
content: '笔记内容...',
|
|
118
|
-
},
|
|
119
|
-
visibility: 'private',
|
|
120
|
-
category: 'work',
|
|
121
|
-
tags: ['important', 'todo'],
|
|
122
|
-
});
|
|
123
|
-
```
|
|
144
|
+
**Token 获取优先级:**
|
|
145
|
+
1. 显式传入的 `token` 参数
|
|
146
|
+
2. `localStorage.getItem('auth_token')`
|
|
147
|
+
3. PostMessage 从父窗口获取(iframe 场景)
|
|
148
|
+
4. 环境变量 `process.env.DATA_SDK_TOKEN`
|
|
124
149
|
|
|
125
|
-
|
|
150
|
+
**AppId 获取优先级:**
|
|
151
|
+
1. 显式传入的 `appId` 参数
|
|
152
|
+
2. `localStorage.getItem('app_id')`
|
|
153
|
+
3. PostMessage 从父窗口获取(iframe 场景)
|
|
154
|
+
4. 环境变量 `process.env.DATA_SDK_APP_ID`
|
|
126
155
|
|
|
127
|
-
|
|
128
|
-
const updated = await client.update('note-id-123', {
|
|
129
|
-
data: {
|
|
130
|
-
title: '更新的标题',
|
|
131
|
-
content: '更新的内容',
|
|
132
|
-
},
|
|
133
|
-
category: 'personal',
|
|
134
|
-
});
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
### 5. 删除数据
|
|
156
|
+
## API 文档
|
|
138
157
|
|
|
139
|
-
|
|
140
|
-
// 删除单条
|
|
141
|
-
await client.delete('note-id-123');
|
|
158
|
+
### 初始化和配置 API
|
|
142
159
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
160
|
+
| 方法 | 说明 | 参数 | 返回值 |
|
|
161
|
+
|------|------|------|--------|
|
|
162
|
+
| `initData(options?)` | 初始化 SDK(可选) | `Partial<DataClientOptions>` | `Promise<void>` |
|
|
163
|
+
| `updateToken(token)` | 更新全局 token | `token: string` | `void` |
|
|
164
|
+
| `updateAppId(appId)` | 更新全局 appId | `appId: string` | `void` |
|
|
165
|
+
| `getCurrentAppId()` | 获取当前 appId | - | `string \| undefined` |
|
|
166
|
+
| `clearConfig()` | 清除全局配置 | - | `void` |
|
|
146
167
|
|
|
147
|
-
###
|
|
168
|
+
### 数据操作 API(函数式)
|
|
148
169
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
170
|
+
| 方法 | 说明 | 参数 | 返回值 |
|
|
171
|
+
|------|------|------|--------|
|
|
172
|
+
| `query(options)` | 查询数据列表 | `QueryOptions` | `Promise<DataRecord[]>` |
|
|
173
|
+
| `get(id)` | 获取单条数据 | `id: string` | `Promise<DataRecord \| null>` |
|
|
174
|
+
| `create(options)` | 创建数据 | `CreateDataOptions` | `Promise<DataRecord>` |
|
|
175
|
+
| `update(id, options)` | 更新数据 | `id: string`, `UpdateDataOptions` | `Promise<DataRecord>` |
|
|
176
|
+
| `deleteData(id)` | 删除数据 | `id: string` | `Promise<void>` |
|
|
177
|
+
| `batchDelete(ids)` | 批量删除 | `ids: string[]` | `Promise<void>` |
|
|
178
|
+
| `queryWithPagination(options)` | 查询带分页信息 | `QueryOptions` | `Promise<PaginatedResponse<DataRecord>>` |
|
|
179
|
+
| `isAdmin()` | 检查管理员权限 | - | `Promise<boolean>` |
|
|
153
180
|
|
|
154
|
-
|
|
181
|
+
### DataClient 类方法
|
|
155
182
|
|
|
156
|
-
|
|
183
|
+
使用 `new DataClient(options)` 创建实例后可用的方法:
|
|
157
184
|
|
|
158
185
|
| 方法 | 说明 | 参数 | 返回值 |
|
|
159
186
|
|------|------|------|--------|
|
|
@@ -165,10 +192,10 @@ const isAdmin = await client.isAdmin();
|
|
|
165
192
|
| `batchDelete(ids)` | 批量删除 | `ids: string[]` | `Promise<void>` |
|
|
166
193
|
| `queryWithPagination(options)` | 查询带分页信息 | `QueryOptions` | `Promise<PaginatedResponse<DataRecord>>` |
|
|
167
194
|
| `isAdmin()` | 检查管理员权限 | - | `Promise<boolean>` |
|
|
168
|
-
| `updateToken(token)` |
|
|
169
|
-
| `updateAppId(appId)` |
|
|
170
|
-
| `getAppId()` |
|
|
171
|
-
| `getUserId()` | 获取当前用户ID | - | `string` |
|
|
195
|
+
| `updateToken(token)` | 更新实例 token | `token: string` | `void` |
|
|
196
|
+
| `updateAppId(appId)` | 更新实例 appId | `appId: string` | `void` |
|
|
197
|
+
| `getAppId()` | 获取实例 appId | - | `string` |
|
|
198
|
+
| `getUserId()` | 获取当前用户 ID | - | `string` |
|
|
172
199
|
|
|
173
200
|
### 查询选项(QueryOptions)
|
|
174
201
|
|
|
@@ -267,20 +294,124 @@ try {
|
|
|
267
294
|
|
|
268
295
|
## 高级用法
|
|
269
296
|
|
|
297
|
+
### 无缝接入场景
|
|
298
|
+
|
|
299
|
+
#### React 应用中使用
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
import { initData, query, create, updateToken } from '@seaverse/data-sdk';
|
|
303
|
+
import { useEffect } from 'react';
|
|
304
|
+
|
|
305
|
+
function App() {
|
|
306
|
+
useEffect(() => {
|
|
307
|
+
// 可选:应用启动时初始化(如果 localStorage 已有配置可省略)
|
|
308
|
+
const token = localStorage.getItem('auth_token');
|
|
309
|
+
const appId = localStorage.getItem('app_id');
|
|
310
|
+
|
|
311
|
+
if (token && appId) {
|
|
312
|
+
initData({ token, appId, debug: true });
|
|
313
|
+
}
|
|
314
|
+
}, []);
|
|
315
|
+
|
|
316
|
+
const loadNotes = async () => {
|
|
317
|
+
// 直接使用,无需传递 client 实例
|
|
318
|
+
const notes = await query({
|
|
319
|
+
table: 'notes',
|
|
320
|
+
order: { field: 'created_at', direction: 'desc' },
|
|
321
|
+
});
|
|
322
|
+
return notes;
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
return <div>...</div>;
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
#### iframe 嵌入场景
|
|
330
|
+
|
|
331
|
+
**父页面(提供配置):**
|
|
332
|
+
|
|
333
|
+
```typescript
|
|
334
|
+
// 监听来自 iframe 的配置请求
|
|
335
|
+
window.addEventListener('message', (event) => {
|
|
336
|
+
if (event.data.type === 'DATA_SDK_TOKEN_REQUEST') {
|
|
337
|
+
event.source.postMessage({
|
|
338
|
+
type: 'DATA_SDK_TOKEN_RESPONSE',
|
|
339
|
+
token: localStorage.getItem('auth_token'),
|
|
340
|
+
}, '*');
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (event.data.type === 'DATA_SDK_APP_ID_REQUEST') {
|
|
344
|
+
event.source.postMessage({
|
|
345
|
+
type: 'DATA_SDK_APP_ID_RESPONSE',
|
|
346
|
+
appId: localStorage.getItem('app_id'),
|
|
347
|
+
}, '*');
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
**iframe 页面(自动获取):**
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
import { query } from '@seaverse/data-sdk';
|
|
356
|
+
|
|
357
|
+
// 直接使用,SDK 会自动通过 PostMessage 从父页面获取配置
|
|
358
|
+
const notes = await query({ table: 'notes' });
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
#### 服务端渲染(SSR/SSG)
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
import { query } from '@seaverse/data-sdk';
|
|
365
|
+
|
|
366
|
+
// 通过环境变量提供配置
|
|
367
|
+
// .env 文件:
|
|
368
|
+
// DATA_SDK_TOKEN=your-jwt-token
|
|
369
|
+
// DATA_SDK_APP_ID=your-app-id
|
|
370
|
+
|
|
371
|
+
export async function getServerSideProps() {
|
|
372
|
+
// SDK 自动从环境变量读取配置
|
|
373
|
+
const notes = await query({
|
|
374
|
+
table: 'notes',
|
|
375
|
+
filters: { visibility: 'public' },
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
return { props: { notes } };
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
270
382
|
### 动态更新 Token
|
|
271
383
|
|
|
272
384
|
```typescript
|
|
273
|
-
|
|
385
|
+
import { updateToken } from '@seaverse/data-sdk';
|
|
386
|
+
|
|
387
|
+
// JWT 过期后更新 token(函数式 API)
|
|
388
|
+
updateToken('new-jwt-token');
|
|
389
|
+
|
|
390
|
+
// 或使用类实例
|
|
274
391
|
client.updateToken('new-jwt-token');
|
|
275
392
|
```
|
|
276
393
|
|
|
277
394
|
### 切换应用
|
|
278
395
|
|
|
279
396
|
```typescript
|
|
280
|
-
|
|
397
|
+
import { updateAppId } from '@seaverse/data-sdk';
|
|
398
|
+
|
|
399
|
+
// 切换到其他应用(函数式 API)
|
|
400
|
+
updateAppId('another-app-id');
|
|
401
|
+
|
|
402
|
+
// 或使用类实例
|
|
281
403
|
client.updateAppId('another-app-id');
|
|
282
404
|
```
|
|
283
405
|
|
|
406
|
+
### 清除配置(登出场景)
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
import { clearConfig } from '@seaverse/data-sdk';
|
|
410
|
+
|
|
411
|
+
// 用户登出时清除所有配置
|
|
412
|
+
clearConfig();
|
|
413
|
+
```
|
|
414
|
+
|
|
284
415
|
### JSONB 字段查询
|
|
285
416
|
|
|
286
417
|
```typescript
|
|
@@ -343,47 +474,66 @@ console.log(`是否还有更多: ${result.hasMore}`);
|
|
|
343
474
|
- ⚡ **短路求值**:优先检查 visibility 和 owner
|
|
344
475
|
- ⚡ **哈希分区**:256 个分区,支持并行查询
|
|
345
476
|
|
|
346
|
-
##
|
|
477
|
+
## 架构说明
|
|
347
478
|
|
|
348
|
-
|
|
479
|
+
### JWT Token 架构
|
|
349
480
|
|
|
350
|
-
|
|
481
|
+
Data SDK 采用标准的 JWT 认证机制:
|
|
351
482
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
483
|
+
**Token 结构:**
|
|
484
|
+
```json
|
|
485
|
+
{
|
|
486
|
+
"sub": "user-id-123", // 用户ID(JWT标准字段)
|
|
487
|
+
"iat": 1736832000, // 签发时间
|
|
488
|
+
"exp": 1736918400 // 过期时间
|
|
489
|
+
}
|
|
490
|
+
```
|
|
360
491
|
|
|
361
|
-
|
|
492
|
+
**重要说明:**
|
|
493
|
+
- ✅ Token 中**只包含用户身份信息**(`sub` 或 `payload.user_id`)
|
|
494
|
+
- ✅ Token 在用户登录时生成,具有固定的有效期
|
|
495
|
+
- ❌ Token 中**不包含** `app_id`(因为 app_id 是动态的,用户可能访问多个应用)
|
|
496
|
+
- ✅ `app_id` 通过 SDK 在每次请求时传递
|
|
362
497
|
|
|
363
|
-
|
|
364
|
-
2. **`environment` 参数** - 使用预设环境配置
|
|
365
|
-
3. **自动检测** - 最低优先级,根据 hostname 自动判断
|
|
498
|
+
### 多租户隔离架构
|
|
366
499
|
|
|
367
|
-
|
|
500
|
+
**隔离层级:**
|
|
501
|
+
1. **应用隔离**:`app_id` - 同一用户在不同应用的数据相互隔离
|
|
502
|
+
2. **用户隔离**:`user_id` - 同一应用内不同用户的私密数据相互隔离
|
|
503
|
+
3. **可见性控制**:`visibility` - 支持 public/private 数据共享
|
|
368
504
|
|
|
369
|
-
|
|
505
|
+
## 数据库初始化
|
|
370
506
|
|
|
371
|
-
|
|
507
|
+
如果你需要自己部署数据库,请参考:
|
|
372
508
|
|
|
373
|
-
|
|
509
|
+
```bash
|
|
510
|
+
# 初始化数据库
|
|
511
|
+
psql -h <host> -U postgres -d <database> -f init/init-schema.sql
|
|
512
|
+
```
|
|
374
513
|
|
|
375
|
-
|
|
376
|
-
|--------|-----------|------|---------|
|
|
377
|
-
| `REQUEST_FAILED` | 4xx/5xx | 请求失败 | 检查请求参数和服务器状态 |
|
|
378
|
-
| `TIMEOUT` | - | 请求超时 | 增加 timeout 配置或检查网络 |
|
|
379
|
-
| `NETWORK_ERROR` | - | 网络错误 | 检查网络连接 |
|
|
380
|
-
| `INVALID_TOKEN` | 401 | Token无效或过期 | 使用 `updateToken()` 更新token |
|
|
381
|
-
| `NOT_FOUND` | 404 | 数据不存在或无权限 | 检查数据ID和权限 |
|
|
382
|
-
| `PERMISSION_DENIED` | 403 | 权限不足 | 确认用户有相应权限 |
|
|
383
|
-
| `VALIDATION_ERROR` | 400 | 参数验证失败 | 检查请求参数格式 |
|
|
514
|
+
详细说明请查看 `init/README.md`。
|
|
384
515
|
|
|
385
516
|
## 版本历史
|
|
386
517
|
|
|
518
|
+
### v0.1.2 (2026-01-14)
|
|
519
|
+
|
|
520
|
+
**🎉 无缝接入功能更新**
|
|
521
|
+
|
|
522
|
+
- ✅ 新增函数式 API:`query()`, `create()`, `update()` 等便捷函数
|
|
523
|
+
- ✅ 可选初始化:支持 `initData()` 或零配置直接使用
|
|
524
|
+
- ✅ 自动配置获取:从 localStorage/PostMessage/环境变量自动获取配置
|
|
525
|
+
- ✅ iframe 支持:通过 PostMessage 协议支持跨窗口通信
|
|
526
|
+
- ✅ 全局配置管理:`updateToken()`, `updateAppId()`, `clearConfig()`
|
|
527
|
+
- ✅ 完整向后兼容:保留 DataClient 类 API
|
|
528
|
+
|
|
529
|
+
### v0.1.1 (2026-01-14)
|
|
530
|
+
|
|
531
|
+
**🔧 配置更新**
|
|
532
|
+
|
|
533
|
+
- ✅ 简化架构:JWT 只包含 user_id
|
|
534
|
+
- ✅ RLS 策略优化:只检查用户权限
|
|
535
|
+
- ✅ 性能提升:查询速度提升 1700 倍
|
|
536
|
+
|
|
387
537
|
### v0.1.0 (2026-01-10)
|
|
388
538
|
|
|
389
539
|
**🎉 首个版本发布**
|
|
@@ -392,9 +542,7 @@ SDK 提供了详细的错误码,方便问题诊断:
|
|
|
392
542
|
- ✅ 完整的 CRUD 操作支持
|
|
393
543
|
- ✅ 多环境配置与自动检测
|
|
394
544
|
- ✅ 分页查询支持
|
|
395
|
-
- ✅ 管理员权限检查
|
|
396
545
|
- ✅ TypeScript 类型定义
|
|
397
|
-
- ✅ 完整的错误处理
|
|
398
546
|
|
|
399
547
|
## 相关资源
|
|
400
548
|
|