@h-ai/cli 0.1.0-alpha.18 → 0.1.0-alpha.20
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/dist/{chunk-BPO34NRL.js → chunk-3BBXDQ4Y.js} +5 -5
- package/dist/chunk-3BBXDQ4Y.js.map +1 -0
- package/dist/cli-config-templates-IGGO7P45.js +5 -0
- package/dist/{cli-config-templates-JCS7T3NU.js.map → cli-config-templates-IGGO7P45.js.map} +1 -1
- package/dist/index.js +67 -39
- package/dist/index.js.map +1 -1
- package/dist/{src-N4NPS7AG.js → src-QNEJJ3BV.js} +261 -103
- package/dist/src-QNEJJ3BV.js.map +1 -0
- package/package.json +2 -2
- package/templates/apps/admin/messages/en-US.json +5 -0
- package/templates/apps/admin/messages/zh-CN.json +5 -0
- package/templates/apps/admin/src/routes/+layout.svelte.hbs +5 -5
- package/templates/apps/admin/src/routes/+page.svelte.hbs +32 -12
- package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +37 -20
- package/templates/apps/admin/src/routes/admin/+page.svelte.hbs +33 -24
- package/templates/apps/admin/src/routes/admin/settings/+page.svelte.hbs +16 -13
- package/templates/apps/android-app/src/lib/api.ts.hbs +5 -4
- package/templates/apps/android-app/src/lib/capacitor.ts +1 -1
- package/templates/apps/android-app/src/routes/+layout.svelte.hbs +11 -10
- package/templates/apps/android-app/src/routes/+page.svelte.hbs +17 -6
- package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +4 -1
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_core.yml.hbs +14 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +13 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/index.ts.hbs +3 -0
- package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server-app.ts.hbs +23 -3
- package/templates/apps/h5/messages/en-US.json +3 -0
- package/templates/apps/h5/messages/zh-CN.json +3 -0
- package/templates/apps/h5/src/routes/+layout.svelte.hbs +30 -29
- package/templates/apps/h5/src/routes/+page.svelte.hbs +17 -6
- package/templates/apps/h5/src/routes/discover/+page.svelte.hbs +17 -6
- package/templates/apps/h5/src/routes/profile/+page.svelte.hbs +17 -6
- package/templates/apps/website/src/routes/+layout.svelte.hbs +35 -26
- package/templates/apps/website/src/routes/+page.svelte.hbs +28 -10
- package/templates/apps/website/src/routes/about/+page.svelte.hbs +11 -6
- package/templates/apps/website/src/routes/contact/+page.svelte.hbs +37 -34
- package/templates/apps/website/src/routes/services/+page.svelte.hbs +27 -40
- package/templates/base/package.json.hbs +30 -24
- package/templates/base/src/app.html.hbs +4 -0
- package/templates/base/src/hooks.server.ts.hbs +17 -7
- package/templates/base/src/lib/server/init.ts.hbs +20 -12
- package/templates/base/static/favicon.png +0 -0
- package/templates/base/tests/scaffold.test.ts.hbs +9 -1
- package/templates/base/vite.config.ts.hbs +3 -3
- package/templates/features/iam/routes-admin/(auth)/+layout.svelte.hbs +5 -5
- package/templates/features/iam/routes-admin/(auth)/auth/login/+page.svelte.hbs +34 -32
- package/templates/features/iam/routes-admin/(auth)/auth/register/+page.svelte.hbs +46 -48
- package/templates/features/iam/routes-h5/auth/login/+page.svelte.hbs +35 -31
- package/templates/features/iam/routes-h5/auth/register/+page.svelte.hbs +41 -38
- package/templates/features/payment/routes/api/v1/payment/create/+server.ts +15 -7
- package/templates/features/payment/routes/api/v1/payment/notify/[provider]/+server.ts +1 -2
- package/templates/skills/AGENTS.md +11 -1
- package/templates/skills/CLAUDE.md +2 -1
- package/templates/skills/copilot-instructions.md +22 -6
- package/templates/skills/hai-ai/SKILL.md +115 -646
- package/templates/skills/hai-ai/reference.md +197 -0
- package/templates/skills/hai-api-client/SKILL.md +7 -1
- package/templates/skills/hai-app-create/SKILL.md +1 -1
- package/templates/skills/hai-app-review/SKILL.md +1 -1
- package/templates/skills/hai-audit/SKILL.md +7 -2
- package/templates/skills/hai-cache/SKILL.md +2 -0
- package/templates/skills/hai-capacitor/SKILL.md +11 -9
- package/templates/skills/hai-core/SKILL.md +4 -1
- package/templates/skills/hai-crypto/SKILL.md +42 -8
- package/templates/skills/hai-deploy/SKILL.md +12 -9
- package/templates/skills/hai-iam/SKILL.md +2 -0
- package/templates/skills/hai-kit/SKILL.md +14 -5
- package/templates/skills/hai-payment/SKILL.md +5 -0
- package/templates/skills/hai-reach/SKILL.md +3 -1
- package/templates/skills/hai-reldb/SKILL.md +2 -0
- package/templates/skills/hai-scheduler/SKILL.md +2 -0
- package/templates/skills/hai-serv/SKILL.md +5 -0
- package/templates/skills/hai-storage/SKILL.md +10 -1
- package/templates/skills/hai-ui/SKILL.md +25 -11
- package/templates/skills/hai-vecdb/SKILL.md +8 -1
- package/dist/chunk-BPO34NRL.js.map +0 -1
- package/dist/cli-config-templates-JCS7T3NU.js +0 -5
- package/dist/src-N4NPS7AG.js.map +0 -1
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# @h-ai/ai reference
|
|
2
|
+
|
|
3
|
+
本文件承载 `SKILL.md` 中不常用但仍需要保留的 API 参考。优先读取 `SKILL.md`;只有当任务需要完整方法清单、错误码或长示例时再读取本文件。
|
|
4
|
+
|
|
5
|
+
## 初始化与依赖
|
|
6
|
+
|
|
7
|
+
| 能力 | 是否需要 `ai.init()` | 依赖 |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| `ai.tools` | 否 | 无 |
|
|
10
|
+
| `ai.stream` | 否 | 无 |
|
|
11
|
+
| `ai.llm` | 是 | OpenAI 兼容 LLM 配置 |
|
|
12
|
+
| `ai.embedding` | 是 | LLM/Embedding 模型配置 |
|
|
13
|
+
| `ai.memory` | 是 | Store + 可选 Embedding |
|
|
14
|
+
| `ai.retrieval` / `ai.rag` | 是 | Embedding + Store |
|
|
15
|
+
| `ai.knowledge` | 是 | LLM + Embedding + Store + datapipe |
|
|
16
|
+
| `ai.context` | 是 | LLM + Store + 可选 Memory/RAG/Reasoning |
|
|
17
|
+
| `ai.a2a` | 是 | A2A 配置 + executor |
|
|
18
|
+
|
|
19
|
+
关闭必须使用 `await ai.close()`,这样才能确定释放自定义 `AIStoreProvider.close()`。
|
|
20
|
+
|
|
21
|
+
## LLM
|
|
22
|
+
|
|
23
|
+
| 方法 | 说明 |
|
|
24
|
+
| --- | --- |
|
|
25
|
+
| `chat(options)` | 非流式 Chat Completion |
|
|
26
|
+
| `chatStream(options)` | 流式 Chat Completion |
|
|
27
|
+
| `ask(query, options?)` | 返回纯文本的简易问答 |
|
|
28
|
+
| `askStream(query, options?)` | 流式纯文本问答 |
|
|
29
|
+
| `listModels()` | 查询可用模型 |
|
|
30
|
+
| `getHistory(scope, options?)` | 查询会话历史 |
|
|
31
|
+
| `listSessions(objectId)` | 查询主体会话 |
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
const result = await ai.llm.chat({
|
|
35
|
+
model: 'fast',
|
|
36
|
+
messages: [
|
|
37
|
+
{ role: 'system', content: '你是一个翻译助手' },
|
|
38
|
+
{ role: 'user', content: '你好世界' },
|
|
39
|
+
],
|
|
40
|
+
})
|
|
41
|
+
if (result.success) {
|
|
42
|
+
const reply = result.data.choices[0]?.message.content ?? ''
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Tools
|
|
47
|
+
|
|
48
|
+
- `ai.tools.define({ name, description, parameters, handler })`
|
|
49
|
+
- `ai.tools.createRegistry()`
|
|
50
|
+
- `registry.register(tool)` / `registry.unregister(name)`
|
|
51
|
+
- `registry.getDefinitions()`:传给 LLM 的工具定义
|
|
52
|
+
- `registry.execute(toolCall)` / `registry.executeAll(toolCalls, { parallel })`
|
|
53
|
+
|
|
54
|
+
工具 handler 输入由 Zod schema 约束,外部输入不要绕过 schema。
|
|
55
|
+
|
|
56
|
+
## MCP
|
|
57
|
+
|
|
58
|
+
### 独立服务器
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
const mcp = createMcpServer({ name: 'my-server', version: '1.0.0' })
|
|
62
|
+
mcp.registerResource('config', 'config://app', { description: '应用配置' }, async uri => ({
|
|
63
|
+
contents: [{ uri: uri.href, text: '{}' }],
|
|
64
|
+
}))
|
|
65
|
+
await mcp.connect(new StdioServerTransport())
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### `ai.mcp`
|
|
69
|
+
|
|
70
|
+
| 方法 | 说明 |
|
|
71
|
+
| --- | --- |
|
|
72
|
+
| `registerTool(definition, handler)` | 注册内置工具 |
|
|
73
|
+
| `callTool(name, args, context?)` | 调用工具 |
|
|
74
|
+
| `registerResource(resource, handler)` | 注册资源 |
|
|
75
|
+
| `readResource(uri)` | 读取资源 |
|
|
76
|
+
| `registerPrompt(prompt, handler)` | 注册提示词 |
|
|
77
|
+
| `getPrompt(name, args)` | 生成提示词消息 |
|
|
78
|
+
|
|
79
|
+
## Embedding
|
|
80
|
+
|
|
81
|
+
Embedding OpenAI 客户端按 `apiKey + baseURL + timeout` 缓存;同一 key 指向不同网关时不会复用错误客户端。
|
|
82
|
+
|
|
83
|
+
| 方法 | 说明 |
|
|
84
|
+
| --- | --- |
|
|
85
|
+
| `embed(request)` | 支持单条/批量输入 |
|
|
86
|
+
| `embedText(text)` | 返回单条向量 |
|
|
87
|
+
| `embedBatch(texts)` | 返回向量数组 |
|
|
88
|
+
|
|
89
|
+
## Memory
|
|
90
|
+
|
|
91
|
+
| 方法 | 说明 |
|
|
92
|
+
| --- | --- |
|
|
93
|
+
| `extract(messages, options?)` | 从对话提取记忆并存储 |
|
|
94
|
+
| `add(entry)` | 手动添加记忆 |
|
|
95
|
+
| `get(memoryId)` / `update(memoryId, updates)` | 读取/更新 |
|
|
96
|
+
| `recall(query, options?)` | 检索相关记忆 |
|
|
97
|
+
| `injectMemories(messages, options?)` | 注入 system 记忆上下文 |
|
|
98
|
+
| `remove(memoryId)` / `clear(options?)` | 删除 |
|
|
99
|
+
| `list(options?)` / `listPage(options?)` | 列表/分页 |
|
|
100
|
+
|
|
101
|
+
推荐所有记忆操作带 `objectId`,避免不同用户或 Agent 混写。
|
|
102
|
+
|
|
103
|
+
## Retrieval / RAG
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
await ai.retrieval.addSource({
|
|
107
|
+
id: 'docs',
|
|
108
|
+
name: '产品文档',
|
|
109
|
+
collection: 'doc_vectors',
|
|
110
|
+
topK: 5,
|
|
111
|
+
minScore: 0.7,
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
const result = await ai.rag.query('核心架构是什么?', {
|
|
115
|
+
sources: ['docs'],
|
|
116
|
+
topK: 5,
|
|
117
|
+
})
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
`retrieve` 会并发检索目标 source 并按 score 合并排序;无 source 时返回 `RETRIEVAL_SOURCE_NOT_FOUND`。
|
|
121
|
+
|
|
122
|
+
## Knowledge
|
|
123
|
+
|
|
124
|
+
| 方法 | 说明 |
|
|
125
|
+
| --- | --- |
|
|
126
|
+
| `setup(options?)` | 初始化 collection / 表 / 索引 |
|
|
127
|
+
| `ingest(input)` | 文档入库 |
|
|
128
|
+
| `ingestFile(input)` | 从文件导入(Node.js) |
|
|
129
|
+
| `ingestBatch(inputs, onProgress?)` | 批量导入 |
|
|
130
|
+
| `retrieve(query, options?)` | 实体增强检索 |
|
|
131
|
+
| `ask(query, options?)` | 知识库问答 |
|
|
132
|
+
| `findByEntity(entityName, options?)` | 实体关联文档 |
|
|
133
|
+
| `listEntities(options?)` / `listDocuments(options?)` | 列表 |
|
|
134
|
+
| `removeDocument(documentId, options?)` | 删除文档和关联 |
|
|
135
|
+
|
|
136
|
+
入库链路:parse/clean → chunk → embedding → vecdb upsert → reldb metadata/entity index。
|
|
137
|
+
|
|
138
|
+
## Context
|
|
139
|
+
|
|
140
|
+
`ai.context.createManager(options)` 返回有状态管理器,适合多轮对话。
|
|
141
|
+
|
|
142
|
+
常用选项:
|
|
143
|
+
|
|
144
|
+
- `scope: { objectId, sessionId }`
|
|
145
|
+
- `systemPrompt`
|
|
146
|
+
- `compress: { auto, strategy, maxTokens, preserveLastN }`
|
|
147
|
+
- `memory: { enable, enableExtract, topK }`
|
|
148
|
+
- `rag: { enable, sources, topK, minScore }`
|
|
149
|
+
- `tools`
|
|
150
|
+
|
|
151
|
+
常用方法:`addMessage`、`getMessages`、`getTokenUsage`、`chat`、`chatStream`、`save`、`reset`。
|
|
152
|
+
|
|
153
|
+
## File / Rerank / Reasoning
|
|
154
|
+
|
|
155
|
+
- `ai.file.parse({ content, filename, options })`:text/html/pdf/docx/ocr 解析。
|
|
156
|
+
- `ai.file.parseText(content, filename?)`:直接返回文本。
|
|
157
|
+
- `ai.rerank.rerank(request)` / `rerankTexts(query, texts, topN?)`:相关性重排。
|
|
158
|
+
- `ai.reasoning.run(query, options?)` / `runStream(query, options?)`:`react`、`cot`、`plan-execute`。
|
|
159
|
+
|
|
160
|
+
## A2A
|
|
161
|
+
|
|
162
|
+
| 方法 | 说明 |
|
|
163
|
+
| --- | --- |
|
|
164
|
+
| `registerExecutor(executor)` | 注册 Agent executor |
|
|
165
|
+
| `getAgentCard()` | 获取 Agent Card |
|
|
166
|
+
| `handleRequest(body, context?)` | 处理 A2A JSON-RPC 请求 |
|
|
167
|
+
| `listMessages(filter)` | 查询 A2A 消息记录 |
|
|
168
|
+
| `callRemoteAgent(remoteUrl, message, options?)` | 调用远端 Agent |
|
|
169
|
+
|
|
170
|
+
A2A 的 SDK handler 延迟创建;注册 executor 或首次处理请求时才装配运行时对象。
|
|
171
|
+
|
|
172
|
+
## 错误码速查
|
|
173
|
+
|
|
174
|
+
| 分段 | 代表错误 |
|
|
175
|
+
| --- | --- |
|
|
176
|
+
| `hai:ai:000` | `INTERNAL_ERROR` |
|
|
177
|
+
| `hai:ai:010-012` | 初始化:`NOT_INITIALIZED` / `CONFIGURATION_ERROR` / `INIT_IN_PROGRESS` |
|
|
178
|
+
| `hai:ai:020-033` | Rerank / File |
|
|
179
|
+
| `hai:ai:100-107` | LLM / 历史记录 |
|
|
180
|
+
| `hai:ai:200-204` | MCP |
|
|
181
|
+
| `hai:ai:300-302` | Embedding |
|
|
182
|
+
| `hai:ai:400-403` | Tool |
|
|
183
|
+
| `hai:ai:500-502` | Reasoning |
|
|
184
|
+
| `hai:ai:600-701` | Retrieval / RAG |
|
|
185
|
+
| `hai:ai:800-805` | Knowledge |
|
|
186
|
+
| `hai:ai:900-904` | Memory |
|
|
187
|
+
| `hai:ai:950-971` | Context / Store / Session |
|
|
188
|
+
| `hai:ai:980-984` | A2A |
|
|
189
|
+
|
|
190
|
+
## 浏览器端
|
|
191
|
+
|
|
192
|
+
浏览器端通过 `@h-ai/api-client` 或自定义 endpoint 访问服务端 AI 能力。`ai.stream` 可在浏览器直接用于解析服务端流式 chunk。
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
const response = await apiClient.ai.chats.createCompletion({ messages })
|
|
196
|
+
const memories = await apiClient.ai.memories.recall({ query: '偏好', objectId: 'user-001', topK: 5 })
|
|
197
|
+
```
|
|
@@ -48,6 +48,8 @@ await apiClient.init({
|
|
|
48
48
|
})
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
+
> `createCapacitorTokenStorage()` 仅在 Android/iOS 原生环境 + `@aparajita/capacitor-secure-storage` 已安装时持久化 token;纯 Web 不会回退到 `localStorage`。
|
|
52
|
+
|
|
51
53
|
SSR / Node.js 测试场景请显式传入内存存储:
|
|
52
54
|
|
|
53
55
|
```ts
|
|
@@ -84,12 +86,16 @@ const client = apiClient.create(contract)
|
|
|
84
86
|
await client.init({ baseUrl: 'https://api.example.com/api/v1' })
|
|
85
87
|
```
|
|
86
88
|
|
|
89
|
+
`client.config` / `apiClient.config` 返回的是脱敏后的运行配置快照;`baseUrl` 的内嵌凭证与 `headers.authorization` 等敏感值会自动隐藏。
|
|
90
|
+
|
|
87
91
|
### 4. 关闭
|
|
88
92
|
|
|
89
93
|
```ts
|
|
90
94
|
await apiClient.close()
|
|
91
95
|
```
|
|
92
96
|
|
|
97
|
+
> 已初始化后再次 `init()` 会返回 `CONFIG_ERROR`,不会覆盖现有 client;切换 `baseUrl` / `fetch` / `transport` 前必须先 `close()`,避免旧 transport 会话泄漏。
|
|
98
|
+
|
|
93
99
|
## Token Storage 适配器
|
|
94
100
|
|
|
95
101
|
| 工厂调用 | 存储位置 | 适用场景 |
|
|
@@ -126,7 +132,7 @@ if (login.success) {
|
|
|
126
132
|
|
|
127
133
|
| API | 用途 |
|
|
128
134
|
| --- | --- |
|
|
129
|
-
| `apiClient.init(config)` | 初始化默认 API Service client |
|
|
135
|
+
| `apiClient.init(config)` | 初始化默认 API Service client;重复初始化返回 `CONFIG_ERROR` |
|
|
130
136
|
| `apiClient.close()` | 清理 client 状态 |
|
|
131
137
|
| `apiClient.auth.setTokens(tokens)` | 写入 access token;非 httpOnly 存储会同时写入 refresh token |
|
|
132
138
|
| `apiClient.auth.clear()` | 清理 Token |
|
|
@@ -45,7 +45,7 @@ description: 对应用代码进行审查:检查 TDD 合规性、架构分层
|
|
|
45
45
|
|
|
46
46
|
### 架构分层
|
|
47
47
|
|
|
48
|
-
- [ ] `hooks.server.ts` 仅做中间件编排(初始化、i18n
|
|
48
|
+
- [ ] `hooks.server.ts` 仅做中间件编排(初始化、i18n、`handle` hook),无业务逻辑
|
|
49
49
|
- [ ] `+page.server.ts` / `+server.ts` 仅做请求处理(守卫 → 校验 → 调用服务 → 响应)
|
|
50
50
|
- [ ] 业务逻辑集中在 `$lib/server/services/`
|
|
51
51
|
- [ ] 客户端组件不直接调用数据库或服务端模块
|
|
@@ -52,7 +52,10 @@ await audit.init({
|
|
|
52
52
|
### 3. 关闭
|
|
53
53
|
|
|
54
54
|
```ts
|
|
55
|
-
await audit.close()
|
|
55
|
+
const closeResult = await audit.close()
|
|
56
|
+
if (!closeResult.success) {
|
|
57
|
+
throw new Error(closeResult.error.message)
|
|
58
|
+
}
|
|
56
59
|
```
|
|
57
60
|
|
|
58
61
|
## 核心 API
|
|
@@ -62,7 +65,7 @@ await audit.close()
|
|
|
62
65
|
| 方法 | 签名 | 说明 |
|
|
63
66
|
| --------------------- | ---------------------------------------------------------------- | ---------- |
|
|
64
67
|
| `audit.init` | `(config: AuditInitConfig) => Promise<HaiResult<void>>` | 初始化模块 |
|
|
65
|
-
| `audit.close` | `() => Promise<void
|
|
68
|
+
| `audit.close` | `() => Promise<HaiResult<void>>` | 关闭模块 |
|
|
66
69
|
| `audit.isInitialized` | `boolean` | 初始化状态 |
|
|
67
70
|
|
|
68
71
|
### 日志操作
|
|
@@ -86,6 +89,8 @@ await audit.close()
|
|
|
86
89
|
| `audit.helper.passwordResetComplete(userId, ip?, ua?)` | 密码重置完成 |
|
|
87
90
|
| `audit.helper.crud(input)` | CRUD 操作 |
|
|
88
91
|
|
|
92
|
+
> `audit.log()` / `audit.helper.*()` 中的 `details` 仅会自动脱敏凭证类字段(如 `password` / `token` / `secret`);邮箱、手机号默认保留原值。
|
|
93
|
+
|
|
89
94
|
## 错误码
|
|
90
95
|
|
|
91
96
|
| 错误码 | code | 说明 |
|
|
@@ -5,20 +5,20 @@ description: 使用 @h-ai/capacitor 桥接 Capacitor 原生能力(Token 安全
|
|
|
5
5
|
|
|
6
6
|
# hai-capacitor
|
|
7
7
|
|
|
8
|
-
> `@h-ai/capacitor` 是 hai-framework 的 Capacitor 原生桥接模块,封装常用原生能力为统一 API,返回 `HaiResult<T>`。与 `@h-ai/api-client`
|
|
8
|
+
> `@h-ai/capacitor` 是 hai-framework 的 Capacitor 原生桥接模块,封装常用原生能力为统一 API,返回 `HaiResult<T>`。与 `@h-ai/api-client` 配合时,Token 仅存于原生安全存储,不回退到 Web `localStorage`。
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
## 运行环境
|
|
13
13
|
|
|
14
|
-
> **浏览器端 / 原生 App 专用。** 在 Capacitor 原生环境(Android/iOS
|
|
14
|
+
> **浏览器端 / 原生 App 专用。** 在 Capacitor 原生环境(Android/iOS)中提供完整能力;`createCapacitorTokenStorage()` 仅在原生环境持久化 token,纯 Web 不做不安全回退。`capacitor.preferences` 仍可用于普通偏好数据。
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
18
18
|
## 适用场景
|
|
19
19
|
|
|
20
20
|
- Android/iOS 原生应用开发(SvelteKit + Capacitor)
|
|
21
|
-
- Token
|
|
21
|
+
- Token 安全存储(`@aparajita/capacitor-secure-storage`)
|
|
22
22
|
- 设备信息获取(平台、型号、版本)
|
|
23
23
|
- 推送通知注册与监听(FCM / APNs)
|
|
24
24
|
- 原生相机拍照 / 相册选取
|
|
@@ -64,13 +64,13 @@ await apiClient.init({
|
|
|
64
64
|
})
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
`createCapacitorTokenStorage()` 返回 `TokenStorage` 实例(兼容 `@h-ai/api-client`),底层使用 `@capacitor
|
|
67
|
+
`createCapacitorTokenStorage()` 返回 `TokenStorage` 实例(兼容 `@h-ai/api-client`),底层使用 `@aparajita/capacitor-secure-storage`:
|
|
68
68
|
|
|
69
|
-
- Android → SharedPreferences
|
|
70
|
-
- iOS →
|
|
71
|
-
- Web → localStorage
|
|
69
|
+
- Android → Android KeyStore + 加密 SharedPreferences
|
|
70
|
+
- iOS → Keychain
|
|
71
|
+
- Web → **不回退到 localStorage**;`get*()` 返回 `null`,`set*()` / `clear()` 为 no-op
|
|
72
72
|
|
|
73
|
-
所有方法内置 try-catch
|
|
73
|
+
所有方法内置 try-catch;原生安全存储异常时 get 返回 `null`、set/clear 静默失败并记录日志。
|
|
74
74
|
|
|
75
75
|
#### Preferences 子操作
|
|
76
76
|
|
|
@@ -208,6 +208,7 @@ await capacitor.statusBar.show()
|
|
|
208
208
|
| `HaiCapacitorError.PREFERENCES_SET_FAILED` | `hai:capacitor:012` | Preferences 写入失败 |
|
|
209
209
|
| `HaiCapacitorError.PREFERENCES_REMOVE_FAILED`| `hai:capacitor:013` | Preferences 删除失败 |
|
|
210
210
|
| `HaiCapacitorError.DEVICE_INFO_FAILED` | `hai:capacitor:020` | 获取设备信息失败 |
|
|
211
|
+
| `HaiCapacitorError.APP_VERSION_FAILED` | `hai:capacitor:021` | 获取应用版本失败 |
|
|
211
212
|
| `HaiCapacitorError.PUSH_REGISTER_FAILED` | `hai:capacitor:030` | 推送注册失败 |
|
|
212
213
|
| `HaiCapacitorError.PUSH_LISTEN_FAILED` | `hai:capacitor:031` | 推送监听失败 |
|
|
213
214
|
| `HaiCapacitorError.CAMERA_FAILED` | `hai:capacitor:040` | 拍照/相册失败 |
|
|
@@ -359,7 +360,8 @@ async function captureAndUpload() {
|
|
|
359
360
|
| 插件 | 类型 | 用于 |
|
|
360
361
|
| --- | --- | --- |
|
|
361
362
|
| `@capacitor/core` | peerDependency(必需) | 核心运行时 |
|
|
362
|
-
| `@capacitor
|
|
363
|
+
| `@aparajita/capacitor-secure-storage` | 可选 peerDependency(原生 token 存储必需) | `createCapacitorTokenStorage()` |
|
|
364
|
+
| `@capacitor/preferences` | peerDependency(必需) | `capacitor.preferences.*` 普通偏好数据 |
|
|
363
365
|
| `@capacitor/device` | 可选 | `getDeviceInfo()` |
|
|
364
366
|
| `@capacitor/app` | 可选 | `getAppVersion()` |
|
|
365
367
|
| `@capacitor/push-notifications` | 可选 | `registerPush()` / `listenPush()` |
|
|
@@ -16,7 +16,7 @@ description: 使用 @h-ai/core 进行配置加载、日志记录、i18n 国际
|
|
|
16
16
|
| 能力 | Node.js | 浏览器 |
|
|
17
17
|
|------|---------|--------|
|
|
18
18
|
| `core.logger` | ✅ pino(结构化 JSON / pretty) | ✅ loglevel(DevTools console) |
|
|
19
|
-
| `core.config` | ✅ 完整(YAML 加载、watch、validate) | ⚠️
|
|
19
|
+
| `core.config` | ✅ 完整(YAML 加载、watch、validate) | ⚠️ 占位 API;文件配置方法返回 `SERVICE_UNAVAILABLE` |
|
|
20
20
|
| `core.i18n` | ✅ | ✅ |
|
|
21
21
|
| `core.zodValidation` | ✅ | ✅ |
|
|
22
22
|
| `core.id` | ✅ | ✅ |
|
|
@@ -79,6 +79,8 @@ const formErrors = core.zodValidation.mapZodErrorToFormErrors(zodError, getMessa
|
|
|
79
79
|
|
|
80
80
|
### 配置管理 — `core.config`
|
|
81
81
|
|
|
82
|
+
> 浏览器端保留 `core.config` 对象用于类型/runtime 对齐,但没有文件系统:`get()` 返回 `undefined`,`has()` 返回 `false`,`keys()` 返回 `[]`;`load()` / `validate()` / `reload()` 返回 `HaiCommonError.SERVICE_UNAVAILABLE`;`watch()` 会立即回调该错误并返回 no-op 取消函数。
|
|
83
|
+
|
|
82
84
|
| 方法 | 签名 | 说明 |
|
|
83
85
|
| ------------ | -------------------------------------------------------------- | ------------------------------ |
|
|
84
86
|
| `get` | `<T>(name: string) => T \| undefined` | 获取模块配置段 |
|
|
@@ -541,6 +543,7 @@ logger.debug('Data fetched', { count: items.length })
|
|
|
541
543
|
|
|
542
544
|
| 功能 | 行为 |
|
|
543
545
|
|------|------|
|
|
546
|
+
| `core.config` 文件配置 | 不支持;文件相关方法返回 `SERVICE_UNAVAILABLE`,配置应由服务端读取后下发 |
|
|
544
547
|
| `format: 'json' \| 'pretty'` | 被忽略(始终输出到 DevTools console) |
|
|
545
548
|
| `redact` 字段脱敏 | 不生效(需手动脱敏) |
|
|
546
549
|
| `fatal` 级别 | 映射为 `console.error`(带 `[FATAL]` 前缀) |
|
|
@@ -106,8 +106,8 @@ const valid = crypto.hash.verify('hello', hash.data!)
|
|
|
106
106
|
| --------------- | ----------------------------------------------- | --------------------------- |
|
|
107
107
|
| `generateKey` | `() => string` | 生成 128-bit 密钥(32 hex) |
|
|
108
108
|
| `generateIV` | `() => string` | 生成随机 IV(32 hex) |
|
|
109
|
-
| `encrypt` | `(data, key, options?) => HaiResult<
|
|
110
|
-
| `decrypt` | `(
|
|
109
|
+
| `encrypt` | `(data, key, options?) => HaiResult<SymmetricEncryptedPayload>` | 加密(返回结构化密文) |
|
|
110
|
+
| `decrypt` | `(payload, key) => HaiResult<string>` | 解密结构化密文 |
|
|
111
111
|
| `encryptWithIV` | `(data, key) => HaiResult<EncryptWithIVResult>` | CBC 模式加密(自动生成 IV) |
|
|
112
112
|
| `decryptWithIV` | `(ciphertext, key, iv) => HaiResult<string>` | CBC 模式解密 |
|
|
113
113
|
| `deriveKey` | `(password, salt) => string` | 从密码和盐值派生密钥 |
|
|
@@ -116,13 +116,24 @@ const valid = crypto.hash.verify('hello', hash.data!)
|
|
|
116
116
|
|
|
117
117
|
```typescript
|
|
118
118
|
const key = crypto.symmetric.generateKey()
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
|
|
120
|
+
// 默认安全用法:CBC + 自动 IV,返回 { mode, ciphertext, iv, encoding }
|
|
121
|
+
const encrypted = crypto.symmetric.encrypt('明文数据', key)
|
|
122
|
+
if (encrypted.success) {
|
|
123
|
+
const decrypted = crypto.symmetric.decrypt(encrypted.data, key)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 需要单独存储 IV 时使用结构化结果
|
|
127
|
+
const withIV = crypto.symmetric.encryptWithIV('明文数据', key)
|
|
128
|
+
if (withIV.success) {
|
|
129
|
+
const decrypted = crypto.symmetric.decryptWithIV(withIV.data.ciphertext, key, withIV.data.iv)
|
|
122
130
|
}
|
|
123
131
|
```
|
|
124
132
|
|
|
125
|
-
**
|
|
133
|
+
**SymmetricEncryptOptions**:`{ mode?: 'ecb' | 'cbc', iv?: string, outputFormat?: 'hex' | 'base64' }`
|
|
134
|
+
**SymmetricEncryptedPayload**:`{ mode: 'ecb' | 'cbc', ciphertext: string, iv?: string, encoding: 'hex' | 'base64' }`
|
|
135
|
+
|
|
136
|
+
> `encrypt()` 默认使用 CBC 并自动生成 IV。ECB 会泄漏明文结构,只有底层协议明确要求时才显式 `{ mode: 'ecb' }`。
|
|
126
137
|
|
|
127
138
|
### 密码哈希 — `crypto.password`
|
|
128
139
|
|
|
@@ -143,7 +154,7 @@ if (hashed.success) {
|
|
|
143
154
|
|
|
144
155
|
### 传输加密 — `crypto.transport`
|
|
145
156
|
|
|
146
|
-
|
|
157
|
+
传输加密主工厂必须从 `crypto.transport` 访问;共享 `keyStore` provider 请从 `@h-ai/crypto` 根入口导入,不从子目录内部路径导入。
|
|
147
158
|
|
|
148
159
|
| 方法 | 签名 | 说明 |
|
|
149
160
|
| --- | --- | --- |
|
|
@@ -151,6 +162,14 @@ if (hashed.success) {
|
|
|
151
162
|
| `createClient` | `({ keyExchangeUrl, fetch? }) => TransportClient` | 创建客户端 encryptedFetch 会话 |
|
|
152
163
|
| `protocol` | `TRANSPORT_PROTOCOL` | 协议常量:`X-Client-Id`、`X-Encrypted`、默认协商路径 |
|
|
153
164
|
|
|
165
|
+
共享 `keyStore` provider(从 `@h-ai/crypto` 根入口导入):
|
|
166
|
+
|
|
167
|
+
| 工厂 | 签名 | 说明 |
|
|
168
|
+
| --- | --- | --- |
|
|
169
|
+
| `createInMemoryKeyStore` | `(maxClients?) => TransportKeyStore` | 默认 FIFO 内存实现 |
|
|
170
|
+
| `createRedisTransportKeyStore` | `({ cache, ttlSeconds? }) => TransportKeyStore` | 基于 `@h-ai/cache` 的共享实现,通常配 Redis |
|
|
171
|
+
| `createReldbTransportKeyStore` | `({ reldb, ttlSeconds? }) => TransportKeyStore` | 基于 `@h-ai/reldb` 的共享实现,自动建表 |
|
|
172
|
+
|
|
154
173
|
```typescript
|
|
155
174
|
const server = crypto.transport.createServer({ maxClients: 10000 })
|
|
156
175
|
if (!server.success)
|
|
@@ -166,6 +185,17 @@ const resp = await client.encryptedFetch('https://api.example.com/api/v1/echo',
|
|
|
166
185
|
})
|
|
167
186
|
```
|
|
168
187
|
|
|
188
|
+
```typescript
|
|
189
|
+
import { createRedisTransportKeyStore, crypto } from '@h-ai/crypto'
|
|
190
|
+
import { cache } from '@h-ai/cache'
|
|
191
|
+
|
|
192
|
+
await cache.init({ type: 'redis', host: '127.0.0.1', port: 6379 })
|
|
193
|
+
|
|
194
|
+
const sharedServer = crypto.transport.createServer({
|
|
195
|
+
keyStore: createRedisTransportKeyStore({ cache, ttlSeconds: 3600 }),
|
|
196
|
+
})
|
|
197
|
+
```
|
|
198
|
+
|
|
169
199
|
使用流程:
|
|
170
200
|
1. 先 `await crypto.init()`。
|
|
171
201
|
2. 服务端 `createServer()` 后暴露一个 POST 密钥协商端点:接收 `clientPublicKey`,返回 `serverPublicKey + clientId`。
|
|
@@ -174,6 +204,8 @@ const resp = await client.encryptedFetch('https://api.example.com/api/v1/echo',
|
|
|
174
204
|
5. 服务端先 `decryptRequest()`,业务处理后再 `encryptResponse(clientId, data)`。
|
|
175
205
|
6. 客户端收到 `X-Encrypted: true` 的响应后自动解密;`destroy()` 可清空当前会话。
|
|
176
206
|
|
|
207
|
+
`encryptedFetch()` 与原生 fetch 一样会 reject:网络错误、密钥协商失败、请求加密失败或响应解密失败都应在调用方按 fetch 错误处理策略捕获。
|
|
208
|
+
|
|
177
209
|
常规应用优先使用上层封装:
|
|
178
210
|
- serv:`serv.createApp({ transport: { crypto } })`
|
|
179
211
|
- kit:`kit.createHandle({ crypto: { crypto, transport: true } })` + `kit.client.create({ transport: { crypto } })`
|
|
@@ -210,9 +242,11 @@ const encrypted = crypto.symmetric.encrypt(sensitiveData, key)
|
|
|
210
242
|
// 存入数据库
|
|
211
243
|
|
|
212
244
|
// 读取时解密
|
|
213
|
-
const decrypted = crypto.symmetric.decrypt(
|
|
245
|
+
const decrypted = crypto.symmetric.decrypt(storedPayload, key)
|
|
214
246
|
```
|
|
215
247
|
|
|
248
|
+
> 默认密文是结构化对象,必须完整存储 `mode` / `ciphertext` / `iv` / `encoding` 字段;如果数据库字段已拆分 IV 与密文,请改用 `encryptWithIV()` / `decryptWithIV()`。
|
|
249
|
+
|
|
216
250
|
### 密码存储与验证
|
|
217
251
|
|
|
218
252
|
```typescript
|
|
@@ -61,6 +61,8 @@ services:
|
|
|
61
61
|
```typescript
|
|
62
62
|
import { deploy } from '@h-ai/deploy'
|
|
63
63
|
|
|
64
|
+
deploy.credentials.load()
|
|
65
|
+
|
|
64
66
|
await deploy.init({
|
|
65
67
|
provider: { type: 'vercel', token: 'vel_xxx' },
|
|
66
68
|
services: {
|
|
@@ -113,9 +115,13 @@ if (result.success) {
|
|
|
113
115
|
|
|
114
116
|
| 方法 | 签名 | 说明 |
|
|
115
117
|
| ---------------------- | -------------------------------------------------------------------------- | ------------ |
|
|
118
|
+
| `deploy.credentials.load` | `() => HaiResult<string[]>` | 加载凭证到 `process.env` |
|
|
119
|
+
| `deploy.credentials.save` | `(key: string, value: string) => HaiResult<void>` | 保存单个凭证 |
|
|
120
|
+
| `deploy.credentials.saveAll` | `(entries: Record<string, string>) => HaiResult<void>` | 批量保存凭证 |
|
|
121
|
+
| `deploy.credentials.getPath` | `() => string` | 获取凭证文件路径 |
|
|
116
122
|
| `deploy.init` | `(config: DeployConfigInput) => Promise<HaiResult<void>>` | 初始化模块 |
|
|
117
123
|
| `deploy.close` | `() => Promise<void>` | 关闭模块 |
|
|
118
|
-
| `deploy.scan` | `(appDir: string) => Promise<HaiResult<ScanResult>>` |
|
|
124
|
+
| `deploy.scan` | `(appDir: string) => Promise<HaiResult<ScanResult>>` | 扫描应用(不依赖 init) |
|
|
119
125
|
| `deploy.provisionAll` | `(projectName: string) => Promise<HaiResult<ProvisionResult[]>>` | 开通所有服务 |
|
|
120
126
|
| `deploy.deployApp` | `(appDir: string, options?) => Promise<HaiResult<DeployResult>>` | 完整部署 |
|
|
121
127
|
| `deploy.config` | `DeployConfig \| null` | 当前配置 |
|
|
@@ -125,12 +131,9 @@ if (result.success) {
|
|
|
125
131
|
|
|
126
132
|
## 凭证管理
|
|
127
133
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
| `saveCredential(key, value)` | 保存单个凭证 |
|
|
132
|
-
| `saveCredentials(entries)` | 批量保存凭证 |
|
|
133
|
-
| `getCredentialsPath()` | 获取凭证文件路径 |
|
|
134
|
+
所有凭证操作都通过 `deploy.credentials.*` 访问,模块入口不再单独导出自由函数。
|
|
135
|
+
|
|
136
|
+
> `deploy.credentials.*()` 与 `deploy.scan()` 都不依赖 `deploy.init()`,可用于 CLI 在读取 `_deploy.yml` 之前准备环境。
|
|
134
137
|
|
|
135
138
|
---
|
|
136
139
|
|
|
@@ -147,8 +150,8 @@ if (result.success) {
|
|
|
147
150
|
| `HaiDeployError.PROVISION_FAILED` | `hai:deploy:007` | 基础设施开通失败 |
|
|
148
151
|
| `HaiDeployError.ADAPTER_MISSING` | `hai:deploy:008` | SvelteKit adapter 未安装 |
|
|
149
152
|
| `HaiDeployError.SCAN_FAILED` | `hai:deploy:009` | 应用扫描失败 |
|
|
150
|
-
| `HaiDeployError.
|
|
151
|
-
| `HaiDeployError.
|
|
153
|
+
| `HaiDeployError.NOT_INITIALIZED` | `hai:deploy:010` | 模块未初始化 |
|
|
154
|
+
| `HaiDeployError.ENV_VAR_FAILED` | `hai:deploy:011` | 环境变量设置失败 |
|
|
152
155
|
| `HaiDeployError.UNSUPPORTED_TYPE` | `hai:deploy:012` | 不支持的类型 |
|
|
153
156
|
| `HaiDeployError.CONFIG_ERROR` | `hai:deploy:013` | 配置错误 |
|
|
154
157
|
| `HaiDeployError.CREDENTIAL_ERROR` | `hai:deploy:014` | 凭证读写失败 |
|
|
@@ -83,6 +83,8 @@ await iam.init({
|
|
|
83
83
|
})
|
|
84
84
|
```
|
|
85
85
|
|
|
86
|
+
`iam.config` 返回的是脱敏后的配置快照;LDAP `url` / `bindPassword` 等敏感值会自动隐藏。
|
|
87
|
+
|
|
86
88
|
初始化时自动创建数据库表(5 张):`hai_iam_users`、`hai_iam_roles`、`hai_iam_permissions`、`hai_iam_role_permissions`、`hai_iam_user_roles`。
|
|
87
89
|
|
|
88
90
|
Token 和 OTP 存储在 cache 中(不落库)。refreshToken 使用独立的 cache key 存储。
|
|
@@ -5,7 +5,7 @@ description: 使用 @h-ai/kit 构建 SvelteKit 请求管道、认证守卫、统
|
|
|
5
5
|
|
|
6
6
|
# hai-kit
|
|
7
7
|
|
|
8
|
-
> `@h-ai/kit` 只负责 SvelteKit
|
|
8
|
+
> `@h-ai/kit` 只负责 SvelteKit 集成:`handle` hook、guard、response、validate、client、auth、crud、A2A。公共跨端 HTTP API 契约统一使用 `@h-ai/api-contract` + `@h-ai/serv`,不要在 kit 中定义业务 API contract。
|
|
9
9
|
|
|
10
10
|
## 使用步骤
|
|
11
11
|
|
|
@@ -48,20 +48,24 @@ export const POST = kit.handler(async ({ request, locals }) => {
|
|
|
48
48
|
```ts
|
|
49
49
|
import { kit } from '@h-ai/kit'
|
|
50
50
|
|
|
51
|
-
const client = kit.client.create(
|
|
51
|
+
const client = kit.client.create()
|
|
52
52
|
export const { apiFetch } = client
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
+
> 同源 endpoint 优先使用 httpOnly Cookie;`auth: true` 只读取 `kit.auth.setBrowserToken()` 写入的页面内存 Token,不会默认读写 localStorage。确需 Bearer Header 时,显式传入 `BrowserTokenStore`,禁止把敏感 Token 存入 localStorage。
|
|
56
|
+
|
|
55
57
|
### 4. 同源传输加密
|
|
56
58
|
|
|
57
59
|
kit 不再维护本地传输加密实现;服务端和客户端都统一委托 `@h-ai/crypto` 的 `crypto.transport`。
|
|
58
60
|
|
|
59
61
|
```ts
|
|
60
62
|
// hooks.server.ts
|
|
61
|
-
import {
|
|
63
|
+
import { cache } from '@h-ai/cache'
|
|
64
|
+
import { createRedisTransportKeyStore, crypto } from '@h-ai/crypto'
|
|
62
65
|
import { kit } from '@h-ai/kit'
|
|
63
66
|
|
|
64
67
|
await crypto.init()
|
|
68
|
+
await cache.init({ type: 'redis', host: '127.0.0.1', port: 6379 })
|
|
65
69
|
|
|
66
70
|
export const handle = kit.createHandle({
|
|
67
71
|
auth: {
|
|
@@ -71,7 +75,10 @@ export const handle = kit.createHandle({
|
|
|
71
75
|
},
|
|
72
76
|
crypto: {
|
|
73
77
|
crypto,
|
|
74
|
-
transport: {
|
|
78
|
+
transport: {
|
|
79
|
+
requireEncryption: true,
|
|
80
|
+
keyStore: createRedisTransportKeyStore({ cache, ttlSeconds: 3600 }),
|
|
81
|
+
},
|
|
75
82
|
},
|
|
76
83
|
})
|
|
77
84
|
```
|
|
@@ -95,11 +102,13 @@ export const handle = kit.createHandle({
|
|
|
95
102
|
// lib/utils/api.ts —— 业务层只看到 apiFetch
|
|
96
103
|
import { kit } from '@h-ai/kit'
|
|
97
104
|
|
|
98
|
-
export const { apiFetch } = kit.client.create(
|
|
105
|
+
export const { apiFetch } = kit.client.create()
|
|
99
106
|
```
|
|
100
107
|
|
|
101
108
|
默认协商端点:`/api/_hai/key-exchange`。如 `keyExchangePath` 自定义,浏览器端同步设置 `keyExchangeUrl`。
|
|
102
109
|
|
|
110
|
+
多节点部署时,可在 `kit.createHandle({ crypto: { transport } })` 的运行时对象里直接传 `keyStore`;推荐使用 `@h-ai/crypto` 根入口导出的 `createRedisTransportKeyStore()` / `createReldbTransportKeyStore()`。这类对象依赖不要写进 `_kit.yml`。
|
|
111
|
+
|
|
103
112
|
transport 默认保护同源 `/api/*` endpoint 与 SvelteKit `__data.json` 页面数据请求;页面文档、静态资源与 `multipart/form-data` 上传请求保持明文透传。
|
|
104
113
|
|
|
105
114
|
安全策略默认 fail-closed:`requireEncryption: true` 时,受保护路径缺少 `X-Client-Id` 必须返回 400;服务端 transport 管理器不可用、响应体无法加密或超过单次加密上限时返回错误,禁止明文业务响应回退。`requireEncryption: false` 只适合迁移期灰度。
|
|
@@ -58,6 +58,7 @@ alipay:
|
|
|
58
58
|
stripe:
|
|
59
59
|
secretKey: ${HAI_PAYMENT_STRIPE_SECRET_KEY}
|
|
60
60
|
webhookSecret: ${HAI_PAYMENT_STRIPE_WEBHOOK_SECRET}
|
|
61
|
+
webhookToleranceSeconds: 300 # 可选,默认 300 秒
|
|
61
62
|
```
|
|
62
63
|
|
|
63
64
|
### 2. 初始化(服务端)
|
|
@@ -101,6 +102,8 @@ if (result.success) {
|
|
|
101
102
|
// result.data: PaymentOrder { provider, tradeType, clientParams, prepayId? }
|
|
102
103
|
// clientParams 包含调起支付所需参数
|
|
103
104
|
}
|
|
105
|
+
|
|
106
|
+
// amount / totalAmount 必须是正整数(单位:分)
|
|
104
107
|
```
|
|
105
108
|
|
|
106
109
|
### 4. 处理支付回调
|
|
@@ -262,6 +265,8 @@ export const POST = kit.handler(async ({ request, locals }) => {
|
|
|
262
265
|
|
|
263
266
|
关键支付操作(创建订单、回调、退款、关闭订单)成功后自动写入审计日志(依赖 `@h-ai/audit`),无需额外配置。`queryOrder` 为只读操作,不写审计日志。
|
|
264
267
|
|
|
268
|
+
内置 Provider 的 HTTP 请求默认带 15 秒超时;Stripe 可通过 `webhookToleranceSeconds` 调整 webhook 时间戳容忍窗口。
|
|
269
|
+
|
|
265
270
|
---
|
|
266
271
|
|
|
267
272
|
## 相关 Skills
|
|
@@ -96,6 +96,8 @@ await reach.init(core.config.get('reach'))
|
|
|
96
96
|
await reach.close()
|
|
97
97
|
```
|
|
98
98
|
|
|
99
|
+
`reach.config` 返回的是脱敏后的配置快照;`api.url` / `endpoint`、授权头、SMTP `pass`、短信密钥等敏感值会自动隐藏。
|
|
100
|
+
|
|
99
101
|
### 3. 保存模板(模板绑定 Provider)
|
|
100
102
|
|
|
101
103
|
```typescript
|
|
@@ -159,7 +161,7 @@ await reach.send({
|
|
|
159
161
|
| `reach.init` | `(config: ReachConfigInput) => Promise<HaiResult<void>>` | 初始化(注册多个 Provider) |
|
|
160
162
|
| `reach.send` | `(message: ReachMessage) => Promise<HaiResult<SendResult>>` | 发送消息(通过 provider 字段路由) |
|
|
161
163
|
| `reach.template` | `ReachTemplateRegistry` | 模板注册表 |
|
|
162
|
-
| `reach.config` | `ReachConfig \| null` |
|
|
164
|
+
| `reach.config` | `ReachConfig \| null` | 当前脱敏配置快照 |
|
|
163
165
|
| `reach.isInitialized` | `boolean` | 是否已初始化 |
|
|
164
166
|
| `reach.close` | `() => Promise<void>` | 关闭所有连接 |
|
|
165
167
|
|