@h-ai/cli 0.1.0-alpha.10
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/LICENSE +202 -0
- package/README.md +167 -0
- package/dist/chunk-2H7UOFLK.js +10 -0
- package/dist/chunk-2H7UOFLK.js.map +1 -0
- package/dist/chunk-LANIDJC7.js +659 -0
- package/dist/chunk-LANIDJC7.js.map +1 -0
- package/dist/chunk-YV7WPUI6.js +206 -0
- package/dist/chunk-YV7WPUI6.js.map +1 -0
- package/dist/config-templates-S5RI2MC6.js +5 -0
- package/dist/config-templates-S5RI2MC6.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1650 -0
- package/dist/index.js.map +1 -0
- package/dist/skill-templates-N5DU5FNX.js +5 -0
- package/dist/skill-templates-N5DU5FNX.js.map +1 -0
- package/dist/src-XJ6YXNAF.js +15030 -0
- package/dist/src-XJ6YXNAF.js.map +1 -0
- package/package.json +59 -0
- package/templates/README.md +126 -0
- package/templates/apps/admin/messages/en-US.json +33 -0
- package/templates/apps/admin/messages/zh-CN.json +33 -0
- package/templates/apps/admin/src/routes/+layout.svelte.hbs +30 -0
- package/templates/apps/admin/src/routes/+page.svelte.hbs +35 -0
- package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +44 -0
- package/templates/apps/admin/src/routes/admin/+page.svelte.hbs +45 -0
- package/templates/apps/admin/src/routes/admin/settings/+page.svelte.hbs +37 -0
- package/templates/apps/admin/src/routes/api/health/+server.ts +12 -0
- package/templates/apps/android-app/.env.hbs +3 -0
- package/templates/apps/android-app/capacitor.config.ts.hbs +12 -0
- package/templates/apps/android-app/messages/en-US.json +24 -0
- package/templates/apps/android-app/messages/zh-CN.json +24 -0
- package/templates/apps/android-app/src/lib/api.ts.hbs +30 -0
- package/templates/apps/android-app/src/lib/capacitor.ts +21 -0
- package/templates/apps/android-app/src/routes/+layout.svelte.hbs +42 -0
- package/templates/apps/android-app/src/routes/+layout.ts +7 -0
- package/templates/apps/android-app/src/routes/+page.svelte.hbs +27 -0
- package/templates/apps/api/src/routes/+layout.svelte +14 -0
- package/templates/apps/api/src/routes/+page.svelte +11 -0
- package/templates/apps/api/src/routes/api/v1/health/+server.ts +14 -0
- package/templates/apps/api/src/routes/api/v1/items/+server.ts +37 -0
- package/templates/apps/api/src/routes/api/v1/items/[id]/+server.ts +30 -0
- package/templates/apps/h5/messages/en-US.json +37 -0
- package/templates/apps/h5/messages/zh-CN.json +37 -0
- package/templates/apps/h5/src/routes/+layout.svelte.hbs +57 -0
- package/templates/apps/h5/src/routes/+page.svelte.hbs +27 -0
- package/templates/apps/h5/src/routes/discover/+page.svelte.hbs +27 -0
- package/templates/apps/h5/src/routes/profile/+page.svelte.hbs +27 -0
- package/templates/apps/website/messages/en-US.json +32 -0
- package/templates/apps/website/messages/zh-CN.json +32 -0
- package/templates/apps/website/src/routes/+layout.svelte.hbs +66 -0
- package/templates/apps/website/src/routes/+page.svelte.hbs +33 -0
- package/templates/apps/website/src/routes/about/+page.svelte.hbs +27 -0
- package/templates/apps/website/src/routes/api/health/+server.ts +12 -0
- package/templates/apps/website/src/routes/contact/+page.svelte.hbs +59 -0
- package/templates/apps/website/src/routes/services/+page.svelte.hbs +63 -0
- package/templates/base/e2e/helpers.ts +49 -0
- package/templates/base/package.json.hbs +67 -0
- package/templates/base/playwright.config.ts +32 -0
- package/templates/base/src/app.css +2 -0
- package/templates/base/src/app.d.ts.hbs +18 -0
- package/templates/base/src/app.html.hbs +16 -0
- package/templates/base/src/hooks.server.ts.hbs +123 -0
- package/templates/base/src/lib/server/init.ts.hbs +190 -0
- package/templates/base/svelte.config.js.hbs +30 -0
- package/templates/base/tsconfig.json +8 -0
- package/templates/base/vite.config.ts.hbs +27 -0
- package/templates/features/ai/routes/api/chat/+server.ts +26 -0
- package/templates/features/datapipe/routes/api/datapipe/pipeline/+server.ts +41 -0
- package/templates/features/iam/routes-admin/(auth)/+layout.svelte.hbs +21 -0
- package/templates/features/iam/routes-admin/(auth)/auth/login/+page.svelte.hbs +98 -0
- package/templates/features/iam/routes-admin/(auth)/auth/register/+page.svelte.hbs +124 -0
- package/templates/features/iam/routes-h5/auth/login/+page.svelte.hbs +98 -0
- package/templates/features/iam/routes-h5/auth/register/+page.svelte.hbs +114 -0
- package/templates/features/iam/routes-shared/api/auth/login/+server.ts +32 -0
- package/templates/features/iam/routes-shared/api/auth/logout/+server.ts +15 -0
- package/templates/features/iam/routes-shared/api/auth/me/+server.ts +12 -0
- package/templates/features/iam/routes-shared/api/auth/register/+server.ts +26 -0
- package/templates/features/payment/routes/api/v1/payment/create/+server.ts +30 -0
- package/templates/features/payment/routes/api/v1/payment/notify/[provider]/+server.ts +42 -0
- package/templates/features/payment/routes/api/v1/payment/query/[orderNo]/+server.ts +25 -0
- package/templates/features/storage/routes/api/upload/+server.ts +35 -0
- package/templates/features/vecdb/routes/api/vecdb/search/+server.ts +36 -0
- package/templates/i18n/project.inlang/settings.json +11 -0
- package/templates/skills/AGENTS.md +26 -0
- package/templates/skills/CLAUDE.md +12 -0
- package/templates/skills/copilot-instructions.md +61 -0
- package/templates/skills/hai-ai/SKILL.md +721 -0
- package/templates/skills/hai-api-client/SKILL.md +355 -0
- package/templates/skills/hai-app-create/SKILL.md +371 -0
- package/templates/skills/hai-app-review/SKILL.md +156 -0
- package/templates/skills/hai-app-tests/SKILL.md +468 -0
- package/templates/skills/hai-audit/SKILL.md +145 -0
- package/templates/skills/hai-build/SKILL.md +319 -0
- package/templates/skills/hai-cache/SKILL.md +190 -0
- package/templates/skills/hai-capacitor/SKILL.md +377 -0
- package/templates/skills/hai-core/SKILL.md +484 -0
- package/templates/skills/hai-crypto/SKILL.md +198 -0
- package/templates/skills/hai-datapipe/SKILL.md +220 -0
- package/templates/skills/hai-deploy/SKILL.md +216 -0
- package/templates/skills/hai-iam/SKILL.md +361 -0
- package/templates/skills/hai-kit/SKILL.md +484 -0
- package/templates/skills/hai-payment/SKILL.md +275 -0
- package/templates/skills/hai-reach/SKILL.md +315 -0
- package/templates/skills/hai-reldb/SKILL.md +465 -0
- package/templates/skills/hai-scheduler/SKILL.md +188 -0
- package/templates/skills/hai-storage/SKILL.md +276 -0
- package/templates/skills/hai-ui/SKILL.md +438 -0
- package/templates/skills/hai-vecdb/SKILL.md +279 -0
- package/templates/skills/opencode.json +11 -0
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hai-core
|
|
3
|
+
description: 使用 @h-ai/core 进行配置加载、日志记录、i18n 国际化、HaiResult 错误处理与模块生命周期管理;当需求涉及 core.init、core.logger、core.config、core.i18n、ok/err 或模块初始化模式时使用。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# hai-core
|
|
7
|
+
|
|
8
|
+
> `@h-ai/core` 是 hai-framework 的基础模块,提供配置管理、结构化日志、国际化、HaiResult 错误模型与模块生命周期工具。所有其他模块均依赖 core。
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 运行环境
|
|
13
|
+
|
|
14
|
+
**Node.js + 浏览器双端可用。** 构建工具自动选择正确入口(`package.json` exports 条件导出)。
|
|
15
|
+
|
|
16
|
+
| 能力 | Node.js | 浏览器 |
|
|
17
|
+
|------|---------|--------|
|
|
18
|
+
| `core.logger` | ✅ pino(结构化 JSON / pretty) | ✅ loglevel(DevTools console) |
|
|
19
|
+
| `core.config` | ✅ 完整(YAML 加载、watch、validate) | ⚠️ 仅支持 `core.init({ ... })` 传入配置 |
|
|
20
|
+
| `core.i18n` | ✅ | ✅ |
|
|
21
|
+
| `core.id` | ✅ | ✅ |
|
|
22
|
+
| 工具函数 | ✅ | ✅ |
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## 适用场景
|
|
27
|
+
|
|
28
|
+
- 项目初始化与配置加载
|
|
29
|
+
- 使用 `core.logger` 记录日志
|
|
30
|
+
- 使用 `core.config` 读取/校验模块配置
|
|
31
|
+
- 使用 `core.i18n` 管理多语言
|
|
32
|
+
- 使用 `ok()` / `err()` 构建 HaiResult 类型返回值
|
|
33
|
+
- 创建模块级 logger 或 i18n 消息获取器
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## 使用步骤
|
|
38
|
+
|
|
39
|
+
### 1. 初始化
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { core } from '@h-ai/core'
|
|
43
|
+
|
|
44
|
+
// 自动加载 config/ 目录下的所有 _*.yml 和 *.yml
|
|
45
|
+
core.init({ configDir: './config' })
|
|
46
|
+
|
|
47
|
+
// 也可指定日志级别和配置监听
|
|
48
|
+
core.init({ configDir: './config', watchConfig: true })
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 2. 使用功能
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// 日志
|
|
55
|
+
core.logger.info('Server started', { port: 3000 })
|
|
56
|
+
|
|
57
|
+
// 配置
|
|
58
|
+
const dbConfig = core.config.get('db')
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 核心 API
|
|
64
|
+
|
|
65
|
+
### 生命周期
|
|
66
|
+
|
|
67
|
+
| 方法 | 签名 | 说明 |
|
|
68
|
+
| ----------- | --------------------------------- | ----------------------------------------- |
|
|
69
|
+
| `core.init` | `(options?: CoreOptions) => void` | 初始化(同步,加载配置目录中的所有 YAML) |
|
|
70
|
+
|
|
71
|
+
> `CoreOptions`:`{ logging?: Partial<LoggingConfig>, configDir?: string, watchConfig?: boolean }`
|
|
72
|
+
>
|
|
73
|
+
> 注意:core 没有 `close()` 方法,初始化后持续可用。
|
|
74
|
+
|
|
75
|
+
### 配置管理 — `core.config`
|
|
76
|
+
|
|
77
|
+
| 方法 | 签名 | 说明 |
|
|
78
|
+
| ------------ | -------------------------------------------------------------- | ------------------------------ |
|
|
79
|
+
| `get` | `<T>(name: string) => T \| undefined` | 获取模块配置段 |
|
|
80
|
+
| `getOrThrow` | `<T>(name: string) => T` | 获取配置(未加载时抛异常) |
|
|
81
|
+
| `load` | `<T>(name, filePath, schema?) => HaiResult<T>` | 手动加载配置文件 |
|
|
82
|
+
| `validate` | `<T>(name: string, schema: ZodType) => HaiResult<T>` | 用 Zod Schema 校验已加载的配置 |
|
|
83
|
+
| `has` | `(name: string) => boolean` | 检查配置段是否存在 |
|
|
84
|
+
| `reload` | `(name: string) => HaiResult<unknown>` | 重新从磁盘加载配置 |
|
|
85
|
+
| `clear` | `(name?: string) => void` | 清除配置缓存 |
|
|
86
|
+
| `keys` | `() => string[]` | 获取所有已加载的配置名称 |
|
|
87
|
+
| `watch` | `<T>(name, callback) => () => void` | 监听配置文件变更并自动重载 |
|
|
88
|
+
| `unwatch` | `(name?: string) => void` | 停止配置文件监听 |
|
|
89
|
+
| `isWatching` | `(name: string) => boolean` | 检查是否正在监听某个配置 |
|
|
90
|
+
|
|
91
|
+
**`watch()` 回调处理**:
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
// watch 回调签名:(config: T | null, error?: HaiError) => void
|
|
95
|
+
// - 成功重载时:传入新配置,error 为 undefined
|
|
96
|
+
// - 重载失败时:config 为 null,error 为错误详情(包含 code 和 message)
|
|
97
|
+
|
|
98
|
+
const unwatch = core.config.watch('db', (cfg, error) => {
|
|
99
|
+
if (error) {
|
|
100
|
+
// 处理重载失败(如文件解析错误),不应更新本地使用的配置
|
|
101
|
+
core.logger.error('Config reload failed', {
|
|
102
|
+
name: 'db',
|
|
103
|
+
code: error.code,
|
|
104
|
+
message: error.message,
|
|
105
|
+
})
|
|
106
|
+
return
|
|
107
|
+
}
|
|
108
|
+
// 使用新配置
|
|
109
|
+
// 注意:此时 core.config.get('db') 已经指向新数据
|
|
110
|
+
core.logger.info('Config updated', { db: cfg })
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
// 调用 unwatch() 停止监听
|
|
114
|
+
unwatch()
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
配置文件格式(YAML,支持环境变量插值):
|
|
118
|
+
|
|
119
|
+
- `${VAR}` — 读取 `process.env.VAR`;缺失则返回 `HaiConfigError.CONFIG_ENV_VAR_MISSING` 错误
|
|
120
|
+
- `${VAR:default}` — 读取 `process.env.VAR`;缺失则使用默认值
|
|
121
|
+
- **类型还原**:整个值恰好是单个变量表达式时,结果还原为原生类型(number / boolean 等);混合文本始终为字符串
|
|
122
|
+
|
|
123
|
+
```yaml
|
|
124
|
+
# config/_core.yml
|
|
125
|
+
app:
|
|
126
|
+
name: ${HAI_APP_NAME:my-app} # → string
|
|
127
|
+
env: ${HAI_ENV:development} # → string
|
|
128
|
+
log:
|
|
129
|
+
level: ${HAI_LOG_LEVEL:info} # → string
|
|
130
|
+
format: ${HAI_LOG_FORMAT:pretty} # → string,pretty | json
|
|
131
|
+
feature:
|
|
132
|
+
debug: ${DEBUG:false} # → boolean false(类型还原)
|
|
133
|
+
maxRetries: ${MAX_RETRIES:3} # → number 3(类型还原)
|
|
134
|
+
url: http://${HOST}:${PORT} # → string(混合文本,不还原)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### 日志 — `core.logger`
|
|
138
|
+
|
|
139
|
+
`core.logger` 既是默认 Logger 实例,也是日志管理的统一入口。
|
|
140
|
+
|
|
141
|
+
**日志记录**(实例方法):`trace` / `debug` / `info` / `warn` / `error` / `fatal`
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
core.logger.info('User created', { userId: '123' })
|
|
145
|
+
core.logger.error('Failed to connect', { error })
|
|
146
|
+
core.logger.debug('Processing item', { id, step: 'validation' })
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**管理方法**:
|
|
150
|
+
|
|
151
|
+
| 方法 | 签名 | 说明 |
|
|
152
|
+
| ----------- | -------------------------------------- | ------------------------ |
|
|
153
|
+
| `create` | `(options?: LoggerOptions) => Logger` | 创建新 Logger 实例 |
|
|
154
|
+
| `child` | `(context: Record<string, unknown>) => Logger` | 创建携带固定上下文的子 Logger |
|
|
155
|
+
| `configure` | `(config: Partial<LoggingConfig>) => void` | 配置全局日志选项(级别、格式等) |
|
|
156
|
+
| `setLevel` | `(level: LogLevel) => void` | 设置全局日志级别 |
|
|
157
|
+
| `getLevel` | `() => LogLevel` | 获取当前全局日志级别 |
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
// 创建模块级 logger
|
|
161
|
+
const logger = core.logger.create({ name: 'my-module' })
|
|
162
|
+
logger.info('Module initialized')
|
|
163
|
+
|
|
164
|
+
// 创建携带固定上下文的子 logger(自动合并到每条日志)
|
|
165
|
+
const reqLogger = core.logger.child({ requestId: 'req-001' })
|
|
166
|
+
reqLogger.info('Request started') // 日志中自动包含 requestId
|
|
167
|
+
|
|
168
|
+
// 配置输出格式和级别
|
|
169
|
+
core.logger.configure({
|
|
170
|
+
level: 'warn',
|
|
171
|
+
format: 'json',
|
|
172
|
+
redact: ['password', 'token'], // 脱敏字段
|
|
173
|
+
}) // Node.js 支持
|
|
174
|
+
core.logger.setLevel('debug')
|
|
175
|
+
const level = core.logger.getLevel() // 'debug'
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**日志级别规范**:
|
|
179
|
+
|
|
180
|
+
| 级别 | 适用场景 |
|
|
181
|
+
| ------- | ---------------------------------- |
|
|
182
|
+
| `trace` | 循环内详细调试数据 |
|
|
183
|
+
| `debug` | 函数进入、参数概要、读操作 |
|
|
184
|
+
| `info` | 业务事件(初始化完成、写操作成功) |
|
|
185
|
+
| `warn` | 异常但可恢复(校验失败、重试) |
|
|
186
|
+
| `error` | 操作失败(需人工排查) |
|
|
187
|
+
| `fatal` | 致命错误(服务无法继续) |
|
|
188
|
+
|
|
189
|
+
### 国际化 — `core.i18n`
|
|
190
|
+
|
|
191
|
+
| 方法 | 签名 | 说明 |
|
|
192
|
+
| --------------------- | --------------------------------------------------------------------- | -------------- |
|
|
193
|
+
| `getGlobalLocale` | `() => string` | 获取当前语言 |
|
|
194
|
+
| `setGlobalLocale` | `(locale: string) => void` | 设置全局语言 |
|
|
195
|
+
| `createMessageGetter` | `(messages: Record<string, Record<string, string>>) => MessageGetter` | 创建消息获取器 |
|
|
196
|
+
|
|
197
|
+
创建模块 i18n:
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
import enUS from '../messages/en-US.json'
|
|
201
|
+
import zhCN from '../messages/zh-CN.json'
|
|
202
|
+
|
|
203
|
+
const m = core.i18n.createMessageGetter({
|
|
204
|
+
'zh-CN': zhCN,
|
|
205
|
+
'en-US': enUS,
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
// 使用
|
|
209
|
+
m('user_created') // "用户已创建"
|
|
210
|
+
m('welcome', { params: { name: '张三' } }) // "欢迎,张三"
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
### ID 生成 — `core.id`
|
|
214
|
+
|
|
215
|
+
| 方法 | 签名 | 说明 |
|
|
216
|
+
| -------------- | --------------------------------------- | ----------------------------- |
|
|
217
|
+
| `generate` | `(size?: number) => string` | nanoid(默认 21 字符) |
|
|
218
|
+
| `short` | `() => string` | 10 字符短 ID |
|
|
219
|
+
| `withPrefix` | `(prefix: string, length?: number) => string` | 带前缀的 nanoid |
|
|
220
|
+
| `trace` | `() => string` | `trace-` 前缀 ID |
|
|
221
|
+
| `request` | `() => string` | `req-` 前缀 ID |
|
|
222
|
+
| `uuid` | `() => string` | UUID v4 |
|
|
223
|
+
| `isValidUUID` | `(value: string) => boolean` | 校验 UUID 格式 |
|
|
224
|
+
| `isValidNanoId`| `(value: string, size?: number) => boolean` | 校验 nanoid 格式 |
|
|
225
|
+
|
|
226
|
+
```typescript
|
|
227
|
+
const id = core.id.generate() // 'V1StGXR_Z5j3eK4uA9b8c'
|
|
228
|
+
const shortId = core.id.short() // 10 字符
|
|
229
|
+
const prefixed = core.id.withPrefix('order_') // 'order_V1StGXR...'
|
|
230
|
+
const traceId = core.id.trace() // 'trace-V1StGXR...'
|
|
231
|
+
const reqId = core.id.request() // 'req-V1StGXR...'
|
|
232
|
+
const uuid = core.id.uuid() // UUID v4
|
|
233
|
+
|
|
234
|
+
core.id.isValidUUID('f47ac10b-...') // true/false
|
|
235
|
+
core.id.isValidNanoId('abc123', 6) // true/false
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### 工具函数
|
|
239
|
+
|
|
240
|
+
#### 类型检查 — `core.typeUtils`
|
|
241
|
+
|
|
242
|
+
`isDefined` / `isObject` / `isFunction` / `isPromise` / `isString` / `isNumber` / `isBoolean` / `isArray`
|
|
243
|
+
|
|
244
|
+
#### 对象操作 — `core.object`
|
|
245
|
+
|
|
246
|
+
`deepClone` / `deepMerge` / `pick` / `omit` / `keys` / `values` / `entries` / `fromEntries`
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
core.object.deepMerge(a, b, c) // 递归合并(防原型污染)
|
|
250
|
+
core.object.pick(obj, ['a', 'b'])
|
|
251
|
+
core.object.omit(obj, ['c', 'd'])
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
#### 字符串操作 — `core.string`
|
|
255
|
+
|
|
256
|
+
`capitalize` / `kebabCase` / `camelCase` / `snakeCase` / `pascalCase` / `truncate` / `trim` / `isBlank` / `isNotBlank` / `padStart` / `padEnd` / `constantTimeEqual`
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
core.string.capitalize('hello') // 'Hello'
|
|
260
|
+
core.string.kebabCase('helloWorld') // 'hello-world'
|
|
261
|
+
core.string.constantTimeEqual(a, b) // 防时序攻击比较
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
#### 数组操作 — `core.array`
|
|
265
|
+
|
|
266
|
+
`unique` / `chunk` / `groupBy` / `first` / `last` / `flatten` / `compact` / `shuffle` / `intersection` / `difference`
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
core.array.unique([1, 1, 2]) // [1, 2]
|
|
270
|
+
core.array.chunk([1, 2, 3, 4], 2) // [[1,2], [3,4]]
|
|
271
|
+
core.array.groupBy(items, item => item.type) // Record<string, T[]>
|
|
272
|
+
core.array.intersection([1, 2], [2, 3]) // [2]
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
#### 异步操作 — `core.async`
|
|
276
|
+
|
|
277
|
+
`delay` / `withTimeout` / `retry` / `parallel` / `serial` / `debounce` / `throttle`
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
await core.async.delay(1000)
|
|
281
|
+
await core.async.withTimeout(fetch('/api'), 5000)
|
|
282
|
+
await core.async.retry(fn, { maxRetries: 3, delay: 1000 })
|
|
283
|
+
await core.async.parallel(items, processFn, 5) // 并发限制
|
|
284
|
+
const debouncedFn = core.async.debounce(fn, 300)
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
#### 时间操作 — `core.time`
|
|
288
|
+
|
|
289
|
+
`formatDate` / `timeAgo` / `now` / `nowSeconds` / `parseDate` / `isValidDate` / `addDays` / `addHours` / `startOfDay` / `endOfDay`
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
core.time.formatDate(new Date(), 'YYYY-MM-DD HH:mm:ss')
|
|
293
|
+
core.time.timeAgo(new Date(Date.now() - 60000)) // '1 分钟前'
|
|
294
|
+
core.time.addDays(date, 7)
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### HaiResult 模型与错误处理
|
|
298
|
+
|
|
299
|
+
所有 hai 模块操作返回 `HaiResult<T>` 类型,强制处理成功/失败分支:
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
import { ok, err, HaiCommonError } from '@h-ai/core'
|
|
303
|
+
|
|
304
|
+
// 成功
|
|
305
|
+
return ok(data)
|
|
306
|
+
|
|
307
|
+
// 失败
|
|
308
|
+
return err(HaiCommonError.NOT_FOUND, 'Resource not found')
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
**使用模式**:
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
const result = await someOperation()
|
|
315
|
+
|
|
316
|
+
// 模式1:直接检查
|
|
317
|
+
if (!result.success) {
|
|
318
|
+
core.logger.error('Operation failed', { error: result.error })
|
|
319
|
+
return result // 直接透传错误
|
|
320
|
+
}
|
|
321
|
+
const data = result.data
|
|
322
|
+
|
|
323
|
+
// 模式2:提前返回
|
|
324
|
+
if (!result.success)
|
|
325
|
+
return result
|
|
326
|
+
// 后续代码可安全使用 result.data
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## 错误码
|
|
330
|
+
|
|
331
|
+
Core 模块定义了两组标准错误码:
|
|
332
|
+
|
|
333
|
+
### `HaiCommonError`(通用错误)
|
|
334
|
+
|
|
335
|
+
| 错误码 | code | 说明 |
|
|
336
|
+
|--------|------|------|
|
|
337
|
+
| `HaiCommonError.NOT_INITIALIZED` | `hai:common:001` | 模块未初始化 |
|
|
338
|
+
| `HaiCommonError.INIT_FAILED` | `hai:common:002` | 初始化失败 |
|
|
339
|
+
| `HaiCommonError.INIT_IN_PROGRESS` | `hai:common:004` | 正在初始化中 |
|
|
340
|
+
| `HaiCommonError.UNAUTHORIZED` | `hai:common:100` | 未认证 |
|
|
341
|
+
| `HaiCommonError.FORBIDDEN` | `hai:common:101` | 无权限 |
|
|
342
|
+
| `HaiCommonError.TOKEN_EXPIRED` | `hai:common:102` | Token 已过期 |
|
|
343
|
+
| `HaiCommonError.TOKEN_INVALID` | `hai:common:103` | Token 无效 |
|
|
344
|
+
| `HaiCommonError.VALIDATION_ERROR` | `hai:common:200` | 校验失败 |
|
|
345
|
+
| `HaiCommonError.INVALID_REQUEST` | `hai:common:201` | 请求无效 |
|
|
346
|
+
| `HaiCommonError.PARAMETER_MISSING` | `hai:common:202` | 参数缺失 |
|
|
347
|
+
| `HaiCommonError.NOT_FOUND` | `hai:common:300` | 资源不存在 |
|
|
348
|
+
| `HaiCommonError.ALREADY_EXISTS` | `hai:common:301` | 资源已存在 |
|
|
349
|
+
| `HaiCommonError.CONFLICT` | `hai:common:302` | 冲突 |
|
|
350
|
+
| `HaiCommonError.API_ERROR` | `hai:common:400` | API 错误 |
|
|
351
|
+
| `HaiCommonError.NETWORK_ERROR` | `hai:common:401` | 网络错误 |
|
|
352
|
+
| `HaiCommonError.TIMEOUT` | `hai:common:402` | 超时 |
|
|
353
|
+
| `HaiCommonError.SERVICE_UNAVAILABLE` | `hai:common:403` | 服务不可用 |
|
|
354
|
+
| `HaiCommonError.INTERNAL_ERROR` | `hai:common:500` | 内部错误 |
|
|
355
|
+
| `HaiCommonError.DATABASE_ERROR` | `hai:common:501` | 数据库错误 |
|
|
356
|
+
| `HaiCommonError.UNKNOWN_ERROR` | `hai:common:599` | 未知错误 |
|
|
357
|
+
|
|
358
|
+
**使用示例**:
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
import { HaiCommonError, err } from '@h-ai/core'
|
|
362
|
+
|
|
363
|
+
// 创建错误实例
|
|
364
|
+
err(HaiCommonError.NOT_FOUND, 'User not found')
|
|
365
|
+
err(HaiCommonError.VALIDATION_ERROR, 'Invalid email', validationErrors)
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### `HaiConfigError`(配置错误)
|
|
369
|
+
|
|
370
|
+
| 错误码 | code | 说明 |
|
|
371
|
+
|--------|------|------|
|
|
372
|
+
| `HaiConfigError.CONFIG_FILE_NOT_FOUND` | `hai:core:010` | 配置文件不存在 |
|
|
373
|
+
| `HaiConfigError.CONFIG_PARSE_ERROR` | `hai:core:011` | YAML 解析失败 |
|
|
374
|
+
| `HaiConfigError.CONFIG_VALIDATION_ERROR` | `hai:core:012` | Schema 校验失败 |
|
|
375
|
+
| `HaiConfigError.CONFIG_ENV_VAR_MISSING` | `hai:core:013` | 必需环境变量缺失 |
|
|
376
|
+
| `HaiConfigError.CONFIG_NOT_LOADED` | `hai:core:014` | 配置未加载 |
|
|
377
|
+
|
|
378
|
+
**使用场景**:
|
|
379
|
+
|
|
380
|
+
```typescript
|
|
381
|
+
import { core, HaiConfigError } from '@h-ai/core'
|
|
382
|
+
|
|
383
|
+
const result = core.config.load('db', './config/db.yml', schema)
|
|
384
|
+
if (!result.success) {
|
|
385
|
+
if (result.error.code === HaiConfigError.CONFIG_FILE_NOT_FOUND.code) {
|
|
386
|
+
core.logger.error('Config file missing')
|
|
387
|
+
}
|
|
388
|
+
else if (result.error.code === HaiConfigError.CONFIG_VALIDATION_ERROR.code) {
|
|
389
|
+
core.logger.error('Config validation failed', { issues: result.error.cause })
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
## 常见模式
|
|
395
|
+
|
|
396
|
+
### 服务端初始化(SvelteKit hooks.server.ts)
|
|
397
|
+
|
|
398
|
+
```typescript
|
|
399
|
+
import { initModules } from '$lib/server/init'
|
|
400
|
+
|
|
401
|
+
// init.ts
|
|
402
|
+
import { core } from '@h-ai/core'
|
|
403
|
+
|
|
404
|
+
const appHandle = kit.createHandle({ /* ... */ })
|
|
405
|
+
|
|
406
|
+
export const handle = kit.sequence(appHandle)
|
|
407
|
+
|
|
408
|
+
let initialized = false
|
|
409
|
+
export async function initModules() {
|
|
410
|
+
if (initialized)
|
|
411
|
+
return
|
|
412
|
+
core.init({ configDir: './config' })
|
|
413
|
+
// ... 其他模块 init
|
|
414
|
+
initialized = true
|
|
415
|
+
}
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### 配置校验最佳实践
|
|
419
|
+
|
|
420
|
+
```typescript
|
|
421
|
+
import { z } from 'zod'
|
|
422
|
+
|
|
423
|
+
const MyConfigSchema = z.object({
|
|
424
|
+
apiKey: z.string().min(1),
|
|
425
|
+
timeout: z.number().positive().default(5000),
|
|
426
|
+
})
|
|
427
|
+
|
|
428
|
+
// 模块初始化时校验
|
|
429
|
+
const result = core.config.validate('myModule', MyConfigSchema)
|
|
430
|
+
if (!result.success) {
|
|
431
|
+
core.logger.error('Invalid config', { error: result.error })
|
|
432
|
+
return result
|
|
433
|
+
}
|
|
434
|
+
const config = result.data // 类型安全
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
## 相关 Skills
|
|
440
|
+
|
|
441
|
+
- `hai-build`:项目整体架构与技能导航
|
|
442
|
+
- `hai-kit`:SvelteKit 集成层(Handle/Guard/Middleware)
|
|
443
|
+
- `hai-reldb` / `hai-cache` / `hai-iam`:使用 core.config 读取配置的下游模块
|
|
444
|
+
|
|
445
|
+
---
|
|
446
|
+
|
|
447
|
+
## 浏览器侧使用
|
|
448
|
+
|
|
449
|
+
> **禁止在应用中直接引入 `loglevel`。** `core.logger` 在浏览器端自动使用 loglevel 作为后端,API 与 Node.js 完全一致。
|
|
450
|
+
|
|
451
|
+
### 初始化
|
|
452
|
+
|
|
453
|
+
浏览器端无文件系统,不需要 `configDir`。在 `+layout.svelte` 或应用入口调用:
|
|
454
|
+
|
|
455
|
+
```typescript
|
|
456
|
+
import { core } from '@h-ai/core'
|
|
457
|
+
|
|
458
|
+
// 浏览器端:仅传入 logging 配置即可
|
|
459
|
+
core.init({ logging: { level: 'info' } })
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
### 日志记录
|
|
463
|
+
|
|
464
|
+
```typescript
|
|
465
|
+
// 与 Node.js 完全一致的 API
|
|
466
|
+
core.logger.info('Page loaded', { route: '/home' })
|
|
467
|
+
core.logger.error('API call failed', { url, status })
|
|
468
|
+
|
|
469
|
+
// 创建模块级 logger
|
|
470
|
+
const logger = core.logger.create({ name: 'my-page' })
|
|
471
|
+
logger.debug('Data fetched', { count: items.length })
|
|
472
|
+
|
|
473
|
+
// ❌ 禁止直接引用底层日志库
|
|
474
|
+
// import log from 'loglevel' ← 不要这样做
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### 浏览器侧限制
|
|
478
|
+
|
|
479
|
+
| 功能 | 行为 |
|
|
480
|
+
|------|------|
|
|
481
|
+
| `format: 'json' \| 'pretty'` | 被忽略(始终输出到 DevTools console) |
|
|
482
|
+
| `redact` 字段脱敏 | 不生效(需手动脱敏) |
|
|
483
|
+
| `fatal` 级别 | 映射为 `console.error`(带 `[FATAL]` 前缀) |
|
|
484
|
+
| context 输出 | JSON 字符串追加到消息末尾(非结构化对象) |
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: hai-crypto
|
|
3
|
+
description: 使用 @h-ai/crypto 进行加密(非对称/哈希/对称)与密码哈希;当需求涉及加密、解密、签名、验签、哈希、密码存储或密钥管理时使用。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# hai-crypto
|
|
7
|
+
|
|
8
|
+
> `@h-ai/crypto` 提供非对称加密、哈希、对称加密与密码哈希能力,支持 Node.js 与浏览器双端。
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## 运行环境
|
|
13
|
+
|
|
14
|
+
**Node.js + 浏览器双端可用。** 浏览器端通过 `crypto.init()` 初始化(无需配置参数)。
|
|
15
|
+
|
|
16
|
+
| 能力 | Node.js | 浏览器 | 说明 |
|
|
17
|
+
|------|---------|--------|------|
|
|
18
|
+
| `crypto.asymmetric` 非对称加密 | ✅ | ✅ | SM2 加解密、签名验签 |
|
|
19
|
+
| `crypto.hash` 哈希 | ✅ | ✅ | SM3 哈希、HMAC |
|
|
20
|
+
| `crypto.symmetric` 对称加密 | ✅ | ✅ | SM4 ECB/CBC |
|
|
21
|
+
| `crypto.password` 密码哈希 | ✅ | ❌ | 依赖 Node.js crypto(服务端专用) |
|
|
22
|
+
|
|
23
|
+
浏览器端主要用于 kit 传输加密场景(`kit.client.create({ transport: { crypto } })`),一般不需要直接调用 crypto API。
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 适用场景
|
|
28
|
+
|
|
29
|
+
- 非对称加密/解密、签名/验签、密钥生成
|
|
30
|
+
- 哈希计算与 HMAC
|
|
31
|
+
- 对称加密/解密(ECB/CBC 模式)
|
|
32
|
+
- 密码存储与验证(加盐迭代哈希)
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## 使用步骤
|
|
37
|
+
|
|
38
|
+
### 1. 初始化与关闭
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { crypto } from '@h-ai/crypto'
|
|
42
|
+
|
|
43
|
+
await crypto.init()
|
|
44
|
+
|
|
45
|
+
// 使用后关闭
|
|
46
|
+
await crypto.close()
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## 核心 API
|
|
52
|
+
|
|
53
|
+
### 非对称加密 — `crypto.asymmetric`
|
|
54
|
+
|
|
55
|
+
| 方法 | 签名 | 说明 |
|
|
56
|
+
| ------------------- | ----------------------------------------------------------- | ------------ |
|
|
57
|
+
| `generateKeyPair` | `() => HaiResult<KeyPair>` | 生成密钥对 |
|
|
58
|
+
| `encrypt` | `(data, publicKey, options?) => HaiResult<string>` | 公钥加密 |
|
|
59
|
+
| `decrypt` | `(ciphertext, privateKey, options?) => HaiResult<string>` | 私钥解密 |
|
|
60
|
+
| `sign` | `(data, privateKey, options?) => HaiResult<string>` | 私钥签名 |
|
|
61
|
+
| `verify` | `(data, signature, publicKey, options?) => HaiResult<boolean>` | 公钥验签 |
|
|
62
|
+
| `isValidPublicKey` | `(key: string) => boolean` | 校验公钥格式 |
|
|
63
|
+
| `isValidPrivateKey` | `(key: string) => boolean` | 校验私钥格式 |
|
|
64
|
+
|
|
65
|
+
```typescript
|
|
66
|
+
const keyPair = crypto.asymmetric.generateKeyPair()
|
|
67
|
+
if (keyPair.success) {
|
|
68
|
+
const { publicKey, privateKey } = keyPair.data
|
|
69
|
+
|
|
70
|
+
const encrypted = crypto.asymmetric.encrypt('敏感数据', publicKey)
|
|
71
|
+
if (encrypted.success) {
|
|
72
|
+
const decrypted = crypto.asymmetric.decrypt(encrypted.data, privateKey)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const signature = crypto.asymmetric.sign('待签名数据', privateKey)
|
|
76
|
+
if (signature.success) {
|
|
77
|
+
const valid = crypto.asymmetric.verify('待签名数据', signature.data, publicKey)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**AsymmetricEncryptOptions**:`{ cipherMode?: 0 | 1, outputFormat?: 'hex' | 'base64' }`
|
|
83
|
+
**SignOptions**:`{ hash?: boolean, userId?: string }`
|
|
84
|
+
|
|
85
|
+
### 哈希 — `crypto.hash`
|
|
86
|
+
|
|
87
|
+
| 方法 | 签名 | 说明 |
|
|
88
|
+
| -------- | ---------------------------------------------------------- | -------- |
|
|
89
|
+
| `hash` | `(data: string \| Uint8Array, options?) => HaiResult<string>` | 哈希 |
|
|
90
|
+
| `hmac` | `(data, key) => HaiResult<string>` | HMAC |
|
|
91
|
+
| `verify` | `(data, expectedHash) => HaiResult<boolean>` | 验证哈希 |
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
const hash = crypto.hash.hash('hello')
|
|
95
|
+
const hmac = crypto.hash.hmac('hello', 'secret-key')
|
|
96
|
+
const valid = crypto.hash.verify('hello', hash.data!)
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
**HashOptions**:`{ inputEncoding?: 'utf8' | 'hex' }`
|
|
100
|
+
|
|
101
|
+
### 对称加密 — `crypto.symmetric`
|
|
102
|
+
|
|
103
|
+
| 方法 | 签名 | 说明 |
|
|
104
|
+
| --------------- | ----------------------------------------------- | --------------------------- |
|
|
105
|
+
| `generateKey` | `() => string` | 生成 128-bit 密钥(32 hex) |
|
|
106
|
+
| `generateIV` | `() => string` | 生成随机 IV(32 hex) |
|
|
107
|
+
| `encrypt` | `(data, key, options?) => HaiResult<string>` | 加密 |
|
|
108
|
+
| `decrypt` | `(ciphertext, key, options?) => HaiResult<string>` | 解密 |
|
|
109
|
+
| `encryptWithIV` | `(data, key) => HaiResult<EncryptWithIVResult>` | CBC 模式加密(自动生成 IV) |
|
|
110
|
+
| `decryptWithIV` | `(ciphertext, key, iv) => HaiResult<string>` | CBC 模式解密 |
|
|
111
|
+
| `deriveKey` | `(password, salt) => string` | 从密码和盐值派生密钥 |
|
|
112
|
+
| `isValidKey` | `(key: string) => boolean` | 校验密钥格式 |
|
|
113
|
+
| `isValidIV` | `(iv: string) => boolean` | 校验 IV 格式 |
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
const key = crypto.symmetric.generateKey()
|
|
117
|
+
const result = crypto.symmetric.encryptWithIV('明文数据', key)
|
|
118
|
+
if (result.success) {
|
|
119
|
+
const decrypted = crypto.symmetric.decryptWithIV(result.data.ciphertext, key, result.data.iv)
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**SymmetricOptions**:`{ mode?: 'ecb' | 'cbc', iv?: string, inputEncoding?: 'utf8' | 'hex', outputFormat?: 'hex' | 'base64' }`
|
|
124
|
+
|
|
125
|
+
### 密码哈希 — `crypto.password`
|
|
126
|
+
|
|
127
|
+
| 方法 | 签名 | 说明 |
|
|
128
|
+
| -------- | --------------------------------------- | -------- |
|
|
129
|
+
| `hash` | `(password, config?) => HaiResult<string>` | 密码哈希 |
|
|
130
|
+
| `verify` | `(password, hash) => HaiResult<boolean>` | 验证密码 |
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
const hashed = crypto.password.hash('MyPassword123')
|
|
134
|
+
if (hashed.success) {
|
|
135
|
+
const valid = crypto.password.verify('MyPassword123', hashed.data)
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
哈希格式:`$hai$<iterations>$<salt>$<hash>`
|
|
140
|
+
**PasswordConfig**:`{ saltLength?: number, iterations?: number }`
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## 错误码 — `HaiCryptoError`
|
|
145
|
+
|
|
146
|
+
| 错误码 | code | 说明 |
|
|
147
|
+
|--------|------|------|
|
|
148
|
+
| `HaiCryptoError.INVALID_INPUT` | `hai:crypto:002` | 无效输入 |
|
|
149
|
+
| `HaiCryptoError.INVALID_KEY` | `hai:crypto:003` | 无效密钥 |
|
|
150
|
+
| `HaiCryptoError.NOT_INITIALIZED` | `hai:crypto:010` | 未初始化 |
|
|
151
|
+
| `HaiCryptoError.INIT_FAILED` | `hai:crypto:011` | 初始化失败 |
|
|
152
|
+
| `HaiCryptoError.KEY_GENERATION_FAILED` | `hai:crypto:020` | 密钥生成失败 |
|
|
153
|
+
| `HaiCryptoError.ENCRYPTION_FAILED` | `hai:crypto:021` | 加密失败 |
|
|
154
|
+
| `HaiCryptoError.DECRYPTION_FAILED` | `hai:crypto:022` | 解密失败 |
|
|
155
|
+
| `HaiCryptoError.SIGN_FAILED` | `hai:crypto:023` | 签名失败 |
|
|
156
|
+
| `HaiCryptoError.VERIFY_FAILED` | `hai:crypto:024` | 验签失败 |
|
|
157
|
+
| `HaiCryptoError.HASH_FAILED` | `hai:crypto:040` | 哈希计算失败 |
|
|
158
|
+
| `HaiCryptoError.HMAC_FAILED` | `hai:crypto:041` | HMAC 计算失败 |
|
|
159
|
+
| `HaiCryptoError.INVALID_IV` | `hai:crypto:060` | 无效 IV |
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## 常见模式
|
|
164
|
+
|
|
165
|
+
### 数据加密存储
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
const key = process.env.HAI_CRYPTO_DATA_KEY!
|
|
169
|
+
const encrypted = crypto.symmetric.encrypt(sensitiveData, key)
|
|
170
|
+
// 存入数据库
|
|
171
|
+
|
|
172
|
+
// 读取时解密
|
|
173
|
+
const decrypted = crypto.symmetric.decrypt(storedData, key)
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### 密码存储与验证
|
|
177
|
+
|
|
178
|
+
```typescript
|
|
179
|
+
// 注册时哈希存储
|
|
180
|
+
const hashResult = crypto.password.hash(userPassword)
|
|
181
|
+
if (hashResult.success) {
|
|
182
|
+
// 存储 hashResult.data 到数据库
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// 登录时验证
|
|
186
|
+
const verifyResult = crypto.password.verify(inputPassword, storedHash)
|
|
187
|
+
if (verifyResult.success && verifyResult.data) {
|
|
188
|
+
// 密码匹配
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## 相关 Skills
|
|
195
|
+
|
|
196
|
+
- `hai-core`:配置与 HaiResult 模型
|
|
197
|
+
- `hai-iam`:密码哈希(内部自动调用 crypto.password)
|
|
198
|
+
- `hai-kit`:SvelteKit 集成(传输加密)
|