@h-ai/cli 0.1.0-alpha.65 → 0.1.0-alpha.67
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.en.md +66 -251
- package/README.ja.md +67 -252
- package/README.md +66 -251
- package/dist/{chunk-47V5OT22.js → chunk-7DSURTJ4.js} +32 -115
- package/dist/chunk-7DSURTJ4.js.map +1 -0
- package/dist/{chunk-IPG3QNYK.js → chunk-AOQ4N657.js} +212 -8
- package/dist/chunk-AOQ4N657.js.map +1 -0
- package/dist/cli-config-templates-6AHNUGBN.js +5 -0
- package/dist/{cli-config-templates-SJ2MBIR7.js.map → cli-config-templates-6AHNUGBN.js.map} +1 -1
- package/dist/{cli-skill-templates-A5VIR5J6.js → cli-skill-templates-FC3QRKDO.js} +3 -3
- package/dist/{cli-skill-templates-A5VIR5J6.js.map → cli-skill-templates-FC3QRKDO.js.map} +1 -1
- package/dist/index.js +644 -391
- package/dist/index.js.map +1 -1
- package/dist/{src-6PPBKQ6Q.js → src-2V6VP4I5.js} +972 -520
- package/dist/src-2V6VP4I5.js.map +1 -0
- package/package.json +8 -8
- package/templates/README.md +48 -145
- package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +1 -1
- package/templates/apps/api/.dockerignore.hbs +18 -12
- package/templates/apps/api/.gitignore.hbs +9 -1
- package/templates/apps/api/apps/{{projectName}}-contract/README.md.hbs +23 -2
- package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +28 -7
- package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +1 -3
- package/templates/apps/api/hai.build.json.hbs +1 -0
- package/templates/apps/api/images/app.Dockerfile.hbs +35 -0
- package/templates/apps/api/package.json.hbs +10 -8
- package/templates/apps/fullstack/.dockerignore.hbs +18 -14
- package/templates/apps/fullstack/.gitignore.hbs +9 -1
- package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +23 -8
- package/templates/apps/fullstack/apps/{{projectName}}-app/index.html.hbs +1 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/package.json.hbs +0 -2
- package/templates/apps/fullstack/apps/{{projectName}}-app/public/runtime-config.js.hbs +4 -0
- package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +3 -3
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +25 -10
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/package.json.hbs +2 -3
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/src/lib.rs.hbs +73 -2
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/src-tauri/tauri.conf.json.hbs +2 -2
- package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +2 -2
- package/templates/apps/fullstack/apps/{{projectName}}-miniapp/README.md.hbs +16 -6
- package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +24 -10
- package/templates/apps/fullstack/apps/{{projectName}}-web/package.json.hbs +0 -2
- package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +3 -3
- package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +12 -1
- package/templates/apps/fullstack/hai.build.json.hbs +7 -0
- package/templates/apps/fullstack/images/app.Dockerfile.hbs +36 -0
- package/templates/apps/fullstack/images/web.Dockerfile.hbs +34 -0
- package/templates/apps/fullstack/package.json.hbs +7 -12
- package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +23 -2
- package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +28 -7
- package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +0 -1
- package/templates/apps/fullstack/packages/{{projectName}}-shared/.gitignore.hbs +7 -0
- package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +33 -12
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +2 -2
- package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +3 -3
- package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +2 -2
- package/templates/apps/h5/src/routes/+layout.svelte.hbs +2 -2
- package/templates/apps/mobile-app/index.html.hbs +1 -0
- package/templates/apps/mobile-app/public/runtime-config.js +1 -0
- package/templates/apps/mobile-app/src/App.svelte.hbs +2 -2
- package/templates/apps/mobile-app/src/lib/api.ts.hbs +2 -1
- package/templates/apps/mobile-app/src/lib/app-config.ts.hbs +3 -2
- package/templates/apps/mobile-app/src/lib/pages/home.svelte.hbs +2 -2
- package/templates/apps/website/src/routes/+layout.svelte.hbs +3 -3
- package/templates/apps/website/src/routes/services/+page.svelte.hbs +1 -1
- package/templates/base/.dockerignore.hbs +18 -13
- package/templates/base/.gitignore.hbs +9 -1
- package/templates/base/e2e/app.spec.ts.hbs +10 -0
- package/templates/base/hai.build.json.hbs +1 -0
- package/templates/base/images/app.Dockerfile.hbs +64 -0
- package/templates/base/{docker → images/web}/nginx.conf.hbs +6 -0
- package/templates/base/package.json.hbs +20 -19
- package/templates/base/svelte.config.js.hbs +2 -0
- package/templates/base/vite.config.ts.hbs +9 -3
- package/templates/shared/.gitattributes +11 -0
- package/templates/shared/container-runtime-config.sh +30 -0
- package/templates/shared/run-e2e.mjs +65 -0
- package/templates/shared/vite-runtime-config.ts +54 -0
- package/templates/skills/AGENTS.md +3 -3
- package/templates/skills/bridges/admin/AGENTS.md +1 -1
- package/templates/skills/bridges/api/AGENTS.md +1 -1
- package/templates/skills/bridges/fullstack/AGENTS.md.hbs +1 -1
- package/templates/skills/bridges/h5/AGENTS.md +1 -1
- package/templates/skills/bridges/mobile-app/AGENTS.md +1 -1
- package/templates/skills/bridges/website/AGENTS.md +1 -1
- package/templates/skills/hai-api-client/SKILL.md +14 -161
- package/templates/skills/hai-api-client/reference.md +166 -0
- package/templates/skills/hai-api-contract/SKILL.md +15 -273
- package/templates/skills/hai-api-contract/reference.md +278 -0
- package/templates/skills/hai-app-create/SKILL.md +2 -2
- package/templates/skills/hai-app-create/development.md +6 -4
- package/templates/skills/hai-app-create/reference.md +5 -4
- package/templates/skills/hai-app-review/SKILL.md +1 -1
- package/templates/skills/hai-app-tests/SKILL.md +4 -3
- package/templates/skills/hai-app-tests/reference.md +2 -2
- package/templates/skills/hai-app-tests/validation.md +31 -0
- package/templates/skills/hai-audit/SKILL.md +17 -142
- package/templates/skills/hai-audit/reference.md +145 -0
- package/templates/skills/hai-build/SKILL.md +24 -3
- package/templates/skills/hai-build/reference.md +10 -9
- package/templates/skills/hai-cache/SKILL.md +15 -182
- package/templates/skills/hai-cache/reference.md +187 -0
- package/templates/skills/hai-capacitor/SKILL.md +4 -3
- package/templates/skills/hai-capacitor/reference.md +6 -4
- package/templates/skills/hai-ci/SKILL.md +2 -0
- package/templates/skills/hai-core/SKILL.md +3 -3
- package/templates/skills/hai-crypto/SKILL.md +16 -252
- package/templates/skills/hai-crypto/reference.md +256 -0
- package/templates/skills/hai-datapipe/SKILL.md +15 -196
- package/templates/skills/hai-datapipe/reference.md +202 -0
- package/templates/skills/hai-deploy/SKILL.md +28 -271
- package/templates/skills/hai-deploy/middleware.md +6 -18
- package/templates/skills/hai-framework-sync/SKILL.md +2 -2
- package/templates/skills/hai-framework-sync/sync.md +1 -1
- package/templates/skills/hai-iam/SKILL.md +1 -3
- package/templates/skills/hai-kit/SKILL.md +12 -167
- package/templates/skills/hai-kit/reference.md +172 -0
- package/templates/skills/hai-payment/SKILL.md +19 -251
- package/templates/skills/hai-payment/reference.md +256 -0
- package/templates/skills/hai-pr-review/SKILL.md +4 -4
- package/templates/skills/hai-reach/SKILL.md +2 -2
- package/templates/skills/hai-reldb/SKILL.md +1 -1
- package/templates/skills/hai-scheduler/SKILL.md +15 -195
- package/templates/skills/hai-scheduler/reference.md +202 -0
- package/templates/skills/hai-serv/SKILL.md +2 -2
- package/templates/skills/hai-spec-implement/SKILL.md +1 -1
- package/templates/skills/hai-spec-implement/workflow.md +3 -3
- package/templates/skills/hai-storage/SKILL.md +17 -238
- package/templates/skills/hai-storage/reference.md +243 -0
- package/templates/skills/hai-ui/SKILL.md +1 -1
- package/templates/skills/hai-ui/reference.md +11 -5
- package/templates/skills/hai-vecdb/SKILL.md +2 -2
- package/dist/chunk-47V5OT22.js.map +0 -1
- package/dist/chunk-IPG3QNYK.js.map +0 -1
- package/dist/cli-config-templates-SJ2MBIR7.js +0 -5
- package/dist/src-6PPBKQ6Q.js.map +0 -1
- package/templates/apps/api/Dockerfile.hbs +0 -48
- package/templates/apps/fullstack/Dockerfile.hbs +0 -69
- package/templates/base/Dockerfile.hbs +0 -79
- /package/templates/apps/fullstack/{docker → images/web}/19-runtime-config.envsh.hbs +0 -0
- /package/templates/apps/fullstack/{docker → images/web}/nginx.conf.hbs +0 -0
- /package/templates/apps/fullstack/{docker → images/web}/runtime-config.js.template.hbs +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: hai-cache
|
|
3
|
-
description: "
|
|
3
|
+
description: "实现或排查 Memory/Redis 缓存、TTL、集合与分布式锁,处理过期、回源和并发边界。"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# hai-cache
|
|
@@ -15,188 +15,21 @@ description: "使用 @h-ai/cache 的 Memory/Redis 缓存、TTL、集合、排行
|
|
|
15
15
|
| 输出 | 缓存和锁操作的 HaiResult,获锁 data 为 boolean |
|
|
16
16
|
| 限制 | 仅服务端;Memory 不跨节点。release/extend 携带本次 owner,避免锁过期后误操作新持有者。 |
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
## 使用路径
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
1. 先确定单进程 Memory 还是共享 Redis;配置校验与 init 成功后才使用。cache 不依赖 reldb。
|
|
21
|
+
2. 根据 KV/Hash/List/Set/ZSet 选择公开操作,明确 TTL、空值和回源失败的业务含义。
|
|
22
|
+
3. 每次获锁生成唯一 owner,检查 success 与 data;续期/释放复用该 owner,丢锁后停止受保护工作。
|
|
23
|
+
4. 验证过期、连接失败、并发与锁过期后的旧任务;最后 close。
|
|
21
24
|
|
|
22
|
-
##
|
|
25
|
+
## 按需参考
|
|
23
26
|
|
|
24
|
-
|
|
27
|
+
按本次操作读取对应小节;以已安装版本的公开类型核验签名,示例中的业务变量由应用提供。
|
|
25
28
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
33
|
-
- 分布式锁(多节点部署互斥控制)
|
|
34
|
-
|
|
35
|
-
## 使用步骤
|
|
36
|
-
|
|
37
|
-
### 1. 配置
|
|
38
|
-
|
|
39
|
-
```yaml
|
|
40
|
-
# config/_cache.yml
|
|
41
|
-
type: memory # memory | redis
|
|
42
|
-
# Redis 配置(type=redis 时生效):
|
|
43
|
-
# host: localhost
|
|
44
|
-
# port: 6379
|
|
45
|
-
# password: ''
|
|
46
|
-
# db: 0
|
|
47
|
-
# keyPrefix: 'hai:'
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
约定环境变量优先于 YAML,例如 `HAI_CACHE_TYPE`、`HAI_CACHE_HOST`、
|
|
51
|
-
`HAI_CACHE_KEYPREFIX`。
|
|
52
|
-
|
|
53
|
-
### 2. 初始化与关闭
|
|
54
|
-
|
|
55
|
-
```typescript
|
|
56
|
-
import { cache } from '@h-ai/cache'
|
|
57
|
-
|
|
58
|
-
await cache.init(core.config.get('cache'))
|
|
59
|
-
// 使用后关闭
|
|
60
|
-
await cache.close()
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
`cache.config` 返回的是脱敏后的配置快照;Redis `password` / `url` 等敏感值不会原样暴露给日志或调试输出。
|
|
64
|
-
|
|
65
|
-
## 核心 API
|
|
66
|
-
|
|
67
|
-
### KV 操作(`cache.kv`)
|
|
68
|
-
|
|
69
|
-
| 方法 | 说明 |
|
|
70
|
-
| ----------------------------------- | -------------------- |
|
|
71
|
-
| `get / set / del / exists` | 基础读写与存在性判断 |
|
|
72
|
-
| `expire / expireAt / ttl / persist` | TTL 管理 |
|
|
73
|
-
| `incr / incrBy / decr / decrBy` | 计数器操作 |
|
|
74
|
-
| `mget / mset` | 批量读写 |
|
|
75
|
-
| `scan / keys / type` | Key 检索与类型判断 |
|
|
76
|
-
|
|
77
|
-
```typescript
|
|
78
|
-
await cache.kv.set('user:123', { name: '张三' }, { ex: 3600 })
|
|
79
|
-
const result = await cache.kv.get<{ name: string }>('user:123')
|
|
80
|
-
if (result.success && result.data) {
|
|
81
|
-
// result.data.name === '张三'
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
await cache.kv.del('user:123')
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### Hash/List/Set/ZSet(`cache.hash/list/set_/zset`)
|
|
88
|
-
|
|
89
|
-
- `cache.hash`:对象字段读写(如用户 profile 局部更新)
|
|
90
|
-
- `cache.list`:队列/消息顺序处理
|
|
91
|
-
- `cache.set_`:去重集合(成员关系、权限集合)
|
|
92
|
-
- `cache.zset`:分数排序(排行榜、权重调度)
|
|
93
|
-
|
|
94
|
-
```typescript
|
|
95
|
-
await cache.hash.hset('profile:1', { nickname: 'alice' })
|
|
96
|
-
await cache.list.lpush('queue:jobs', 'job-1', 'job-2')
|
|
97
|
-
await cache.set_.sadd('role:admin:perms', 'user.read', 'user.write')
|
|
98
|
-
await cache.zset.zadd('rank:daily', { member: 'u1', score: 100 })
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
### 分布式锁(`cache.lock`)
|
|
102
|
-
|
|
103
|
-
| 方法 | 说明 |
|
|
104
|
-
| ---------- | ------------------------------------------ |
|
|
105
|
-
| `acquire` | 尝试获锁(SET NX EX),返回 true/false |
|
|
106
|
-
| `release` | 释放锁(支持 owner 验证,防止误释放) |
|
|
107
|
-
| `isLocked` | 检查锁是否被持有 |
|
|
108
|
-
| `extend` | 续期锁 TTL(支持 owner 验证) |
|
|
109
|
-
|
|
110
|
-
```typescript
|
|
111
|
-
// 每次尝试生成唯一 owner,后续释放/续期复用同一个值
|
|
112
|
-
const owner = core.id.generate()
|
|
113
|
-
const acquired = await cache.lock.acquire('my-lock', { ttl: 30, owner })
|
|
114
|
-
if (acquired.success && acquired.data) {
|
|
115
|
-
try {
|
|
116
|
-
// 长任务仅在仍持有锁时续期;失败或 data=false 时停止依赖锁的后续操作
|
|
117
|
-
const extended = await cache.lock.extend('my-lock', 60, owner)
|
|
118
|
-
if (!extended.success || !extended.data) {
|
|
119
|
-
logger.warn('Lock extension failed; protected work cancelled')
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
// 受保护的操作
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
finally {
|
|
126
|
-
await cache.lock.release('my-lock', owner)
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// 检查
|
|
131
|
-
const locked = await cache.lock.isLocked('my-lock')
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
**最佳实践:**
|
|
135
|
-
|
|
136
|
-
- `owner` 每次获锁尝试唯一,释放/续期复用该值;仅用 nodeId 会使同节点旧任务误释放过期后重新获得的锁
|
|
137
|
-
- 释放锁时传入 `owner` 防止误释放他人锁
|
|
138
|
-
- Memory 后端适合开发/测试;Redis 后端用 Lua 脚本保证 release/extend 原子性
|
|
139
|
-
|
|
140
|
-
## 错误码 — `HaiCacheError`
|
|
141
|
-
|
|
142
|
-
| 错误码 | code | 说明 |
|
|
143
|
-
|--------|------|------|
|
|
144
|
-
| `HaiCacheError.CONNECTION_FAILED` | `hai:cache:001` | 连接失败 |
|
|
145
|
-
| `HaiCacheError.OPERATION_FAILED` | `hai:cache:002` | 操作失败 |
|
|
146
|
-
| `HaiCacheError.SERIALIZATION_FAILED` | `hai:cache:003` | 序列化失败 |
|
|
147
|
-
| `HaiCacheError.DESERIALIZATION_FAILED` | `hai:cache:004` | 反序列化失败 |
|
|
148
|
-
| `HaiCacheError.KEY_NOT_FOUND` | `hai:cache:005` | 键不存在 |
|
|
149
|
-
| `HaiCacheError.TIMEOUT` | `hai:cache:006` | 超时 |
|
|
150
|
-
| `HaiCacheError.NOT_INITIALIZED` | `hai:cache:010` | 未初始化 |
|
|
151
|
-
| `HaiCacheError.UNSUPPORTED_TYPE` | `hai:cache:011` | 不支持的缓存类型 |
|
|
152
|
-
| `HaiCacheError.CONFIG_ERROR` | `hai:cache:012` | 配置错误 |
|
|
153
|
-
|
|
154
|
-
## 常见模式
|
|
155
|
-
|
|
156
|
-
### 缓存命中与回源(未缓存空值,不防穿透)
|
|
157
|
-
|
|
158
|
-
```typescript
|
|
159
|
-
async function getUserCached(userId: string) {
|
|
160
|
-
const cached = await cache.kv.get<User>(`user:${userId}`)
|
|
161
|
-
if (cached.success && cached.data)
|
|
162
|
-
return cached.data
|
|
163
|
-
|
|
164
|
-
const user = await userRepo.findById(userId)
|
|
165
|
-
if (user.success && user.data) {
|
|
166
|
-
await cache.kv.set(`user:${userId}`, user.data, { ex: 3600 })
|
|
167
|
-
return user.data
|
|
168
|
-
}
|
|
169
|
-
return null
|
|
170
|
-
}
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
### 分布式锁保护共享资源
|
|
174
|
-
|
|
175
|
-
```typescript
|
|
176
|
-
const lockKey = 'batch:import'
|
|
177
|
-
const owner = core.id.generate()
|
|
178
|
-
const acquired = await cache.lock.acquire(lockKey, { ttl: 60, owner })
|
|
179
|
-
if (acquired.success && acquired.data) {
|
|
180
|
-
try {
|
|
181
|
-
await runBatchImport()
|
|
182
|
-
}
|
|
183
|
-
finally {
|
|
184
|
-
await cache.lock.release(lockKey, owner)
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
else if (acquired.success) {
|
|
188
|
-
logger.info('Another node is running the import')
|
|
189
|
-
}
|
|
190
|
-
else {
|
|
191
|
-
logger.error('Lock acquisition failed', { code: acquired.error.code })
|
|
192
|
-
}
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
## 相关 Skills
|
|
196
|
-
|
|
197
|
-
- `hai-build`:模块初始化顺序(cache 在 db 之后、iam 之前)
|
|
198
|
-
- `hai-core`:配置与 HaiResult 模型
|
|
199
|
-
- `hai-iam`:会话存储与权限缓存(底层使用 cache)
|
|
200
|
-
- `hai-scheduler`:定时任务分布式锁(底层使用 cache.lock)
|
|
201
|
-
- `hai-reach`:消息发送互斥锁(底层使用 cache.lock)
|
|
202
|
-
- `hai-kit`:SvelteKit 集成层
|
|
29
|
+
- [运行环境](reference.md#运行环境)
|
|
30
|
+
- [适用场景](reference.md#适用场景)
|
|
31
|
+
- [使用步骤](reference.md#使用步骤)
|
|
32
|
+
- [核心 API](reference.md#核心-api)
|
|
33
|
+
- [错误码 — `HaiCacheError`](reference.md#错误码--haicacheerror)
|
|
34
|
+
- [常见模式](reference.md#常见模式)
|
|
35
|
+
- [相关 Skills](reference.md#相关-skills)
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# hai-cache 参考
|
|
2
|
+
|
|
3
|
+
> `@h-ai/cache` 提供统一缓存接口,支持 Memory 与 Redis 后端,包含 KV / Hash / List / Set / ZSet / 分布式锁 六类操作。
|
|
4
|
+
|
|
5
|
+
内存 KV 在到期时间点即失效。整数计数器拒绝 null/boolean/array、非整数增量和安全整数溢出,失败不修改原值;Hash 的 `__proto__` / `constructor` 名称按普通字段保留。
|
|
6
|
+
|
|
7
|
+
## 运行环境
|
|
8
|
+
|
|
9
|
+
> ⚠️ **服务端模块(Node.js only)。** 浏览器端无需直接操作缓存,由服务端模块(如 IAM、Scheduler)内部使用。
|
|
10
|
+
|
|
11
|
+
## 适用场景
|
|
12
|
+
|
|
13
|
+
- 缓存热点数据减少数据库查询
|
|
14
|
+
- 会话存储(配合 IAM)
|
|
15
|
+
- 分布式缓存(Redis)与本地测试缓存(Memory)
|
|
16
|
+
- 集合操作(权限缓存、标签集合)
|
|
17
|
+
- 排行榜(ZSet)与队列(List)
|
|
18
|
+
- 分布式锁(多节点部署互斥控制)
|
|
19
|
+
|
|
20
|
+
## 使用步骤
|
|
21
|
+
|
|
22
|
+
### 1. 配置
|
|
23
|
+
|
|
24
|
+
```yaml
|
|
25
|
+
# config/_cache.yml
|
|
26
|
+
type: memory # memory | redis
|
|
27
|
+
# Redis 配置(type=redis 时生效):
|
|
28
|
+
# host: localhost
|
|
29
|
+
# port: 6379
|
|
30
|
+
# password: ''
|
|
31
|
+
# db: 0
|
|
32
|
+
# keyPrefix: 'hai:'
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
约定环境变量优先于 YAML,例如 `HAI_CACHE_TYPE`、`HAI_CACHE_HOST`、
|
|
36
|
+
`HAI_CACHE_KEYPREFIX`。
|
|
37
|
+
|
|
38
|
+
### 2. 初始化与关闭
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { cache } from '@h-ai/cache'
|
|
42
|
+
|
|
43
|
+
await cache.init(core.config.get('cache'))
|
|
44
|
+
// 使用后关闭
|
|
45
|
+
await cache.close()
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
`cache.config` 返回的是脱敏后的配置快照;Redis `password` / `url` 等敏感值不会原样暴露给日志或调试输出。
|
|
49
|
+
|
|
50
|
+
## 核心 API
|
|
51
|
+
|
|
52
|
+
### KV 操作(`cache.kv`)
|
|
53
|
+
|
|
54
|
+
| 方法 | 说明 |
|
|
55
|
+
| ----------------------------------- | -------------------- |
|
|
56
|
+
| `get / set / del / exists` | 基础读写与存在性判断 |
|
|
57
|
+
| `expire / expireAt / ttl / persist` | TTL 管理 |
|
|
58
|
+
| `incr / incrBy / decr / decrBy` | 计数器操作 |
|
|
59
|
+
| `mget / mset` | 批量读写 |
|
|
60
|
+
| `scan / keys / type` | Key 检索与类型判断 |
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
await cache.kv.set('user:123', { name: '张三' }, { ex: 3600 })
|
|
64
|
+
const result = await cache.kv.get<{ name: string }>('user:123')
|
|
65
|
+
if (result.success && result.data) {
|
|
66
|
+
// result.data.name === '张三'
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
await cache.kv.del('user:123')
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Hash/List/Set/ZSet(`cache.hash/list/set_/zset`)
|
|
73
|
+
|
|
74
|
+
- `cache.hash`:对象字段读写(如用户 profile 局部更新)
|
|
75
|
+
- `cache.list`:队列/消息顺序处理
|
|
76
|
+
- `cache.set_`:去重集合(成员关系、权限集合)
|
|
77
|
+
- `cache.zset`:分数排序(排行榜、权重调度)
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
await cache.hash.hset('profile:1', { nickname: 'alice' })
|
|
81
|
+
await cache.list.lpush('queue:jobs', 'job-1', 'job-2')
|
|
82
|
+
await cache.set_.sadd('role:admin:perms', 'user.read', 'user.write')
|
|
83
|
+
await cache.zset.zadd('rank:daily', { member: 'u1', score: 100 })
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 分布式锁(`cache.lock`)
|
|
87
|
+
|
|
88
|
+
| 方法 | 说明 |
|
|
89
|
+
| ---------- | ------------------------------------------ |
|
|
90
|
+
| `acquire` | 尝试获锁(SET NX EX),返回 true/false |
|
|
91
|
+
| `release` | 释放锁(支持 owner 验证,防止误释放) |
|
|
92
|
+
| `isLocked` | 检查锁是否被持有 |
|
|
93
|
+
| `extend` | 续期锁 TTL(支持 owner 验证) |
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
// 每次尝试生成唯一 owner,后续释放/续期复用同一个值
|
|
97
|
+
const owner = core.id.generate()
|
|
98
|
+
const acquired = await cache.lock.acquire('my-lock', { ttl: 30, owner })
|
|
99
|
+
if (acquired.success && acquired.data) {
|
|
100
|
+
try {
|
|
101
|
+
// 长任务仅在仍持有锁时续期;失败或 data=false 时停止依赖锁的后续操作
|
|
102
|
+
const extended = await cache.lock.extend('my-lock', 60, owner)
|
|
103
|
+
if (!extended.success || !extended.data) {
|
|
104
|
+
logger.warn('Lock extension failed; protected work cancelled')
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
// 受保护的操作
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
await cache.lock.release('my-lock', owner)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 检查
|
|
116
|
+
const locked = await cache.lock.isLocked('my-lock')
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**最佳实践:**
|
|
120
|
+
|
|
121
|
+
- `owner` 每次获锁尝试唯一,释放/续期复用该值;仅用 nodeId 会使同节点旧任务误释放过期后重新获得的锁
|
|
122
|
+
- 释放锁时传入 `owner` 防止误释放他人锁
|
|
123
|
+
- Memory 后端适合开发/测试;Redis 后端用 Lua 脚本保证 release/extend 原子性
|
|
124
|
+
|
|
125
|
+
## 错误码 — `HaiCacheError`
|
|
126
|
+
|
|
127
|
+
| 错误码 | code | 说明 |
|
|
128
|
+
|--------|------|------|
|
|
129
|
+
| `HaiCacheError.CONNECTION_FAILED` | `hai:cache:001` | 连接失败 |
|
|
130
|
+
| `HaiCacheError.OPERATION_FAILED` | `hai:cache:002` | 操作失败 |
|
|
131
|
+
| `HaiCacheError.SERIALIZATION_FAILED` | `hai:cache:003` | 序列化失败 |
|
|
132
|
+
| `HaiCacheError.DESERIALIZATION_FAILED` | `hai:cache:004` | 反序列化失败 |
|
|
133
|
+
| `HaiCacheError.KEY_NOT_FOUND` | `hai:cache:005` | 键不存在 |
|
|
134
|
+
| `HaiCacheError.TIMEOUT` | `hai:cache:006` | 超时 |
|
|
135
|
+
| `HaiCacheError.NOT_INITIALIZED` | `hai:cache:010` | 未初始化 |
|
|
136
|
+
| `HaiCacheError.UNSUPPORTED_TYPE` | `hai:cache:011` | 不支持的缓存类型 |
|
|
137
|
+
| `HaiCacheError.CONFIG_ERROR` | `hai:cache:012` | 配置错误 |
|
|
138
|
+
|
|
139
|
+
## 常见模式
|
|
140
|
+
|
|
141
|
+
### 缓存命中与回源(未缓存空值,不防穿透)
|
|
142
|
+
|
|
143
|
+
```typescript
|
|
144
|
+
async function getUserCached(userId: string) {
|
|
145
|
+
const cached = await cache.kv.get<User>(`user:${userId}`)
|
|
146
|
+
if (cached.success && cached.data)
|
|
147
|
+
return cached.data
|
|
148
|
+
|
|
149
|
+
const user = await userRepo.findById(userId)
|
|
150
|
+
if (user.success && user.data) {
|
|
151
|
+
await cache.kv.set(`user:${userId}`, user.data, { ex: 3600 })
|
|
152
|
+
return user.data
|
|
153
|
+
}
|
|
154
|
+
return null
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### 分布式锁保护共享资源
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
const lockKey = 'batch:import'
|
|
162
|
+
const owner = core.id.generate()
|
|
163
|
+
const acquired = await cache.lock.acquire(lockKey, { ttl: 60, owner })
|
|
164
|
+
if (acquired.success && acquired.data) {
|
|
165
|
+
try {
|
|
166
|
+
await runBatchImport()
|
|
167
|
+
}
|
|
168
|
+
finally {
|
|
169
|
+
await cache.lock.release(lockKey, owner)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
else if (acquired.success) {
|
|
173
|
+
logger.info('Another node is running the import')
|
|
174
|
+
}
|
|
175
|
+
else {
|
|
176
|
+
logger.error('Lock acquisition failed', { code: acquired.error.code })
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## 相关 Skills
|
|
181
|
+
|
|
182
|
+
- `hai-build`:模块初始化顺序(cache 在 iam 之前;cache 自身不依赖 reldb)
|
|
183
|
+
- `hai-core`:配置与 HaiResult 模型
|
|
184
|
+
- `hai-iam`:会话存储与权限缓存(底层使用 cache)
|
|
185
|
+
- `hai-scheduler`:定时任务分布式锁(底层使用 cache.lock)
|
|
186
|
+
- `hai-reach`:消息发送互斥锁(底层使用 cache.lock)
|
|
187
|
+
- `hai-kit`:SvelteKit 集成层
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: hai-capacitor
|
|
3
|
-
description: "
|
|
3
|
+
description: "在 Android/iOS 应用中接入 @h-ai/capacitor 设备、相机、推送与安全 Token 存储,处理 Web/原生差异。"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# hai-capacitor
|
|
@@ -13,16 +13,17 @@ description: "使用 @h-ai/capacitor 接入原生设备、相机、推送、状
|
|
|
13
13
|
| 适用场景 | Android/iOS 原生壳集成与平台差异处理 |
|
|
14
14
|
| 输入 | Capacitor 配置、原生插件、设备权限和平台状态 |
|
|
15
15
|
| 输出 | 原生操作结果及 TokenStorage 适配器 |
|
|
16
|
-
| 限制 | 区分浏览器与 Android/iOS;安全存储需原生插件,Web
|
|
16
|
+
| 限制 | 区分浏览器与 Android/iOS;安全存储需原生插件,Web 使用 httpOnly Cookie/内存认证,不降级到 localStorage。H5 验证不能代替真机验证。 |
|
|
17
17
|
|
|
18
18
|
## 使用路径
|
|
19
19
|
|
|
20
20
|
- 先判断平台和插件可用性,复用已有 SPA 入口;原生 UI 变化验证权限拒绝、取消和返回路径。
|
|
21
21
|
- TokenStorage 在原生端使用安全插件;Web 下不伪造持久化成功。
|
|
22
|
+
- 原生打包前确认公开 API 地址的运行配置入口;fullstack App 使用 `public/runtime-config.js`,构建不会读取部署环境文件。这里只放公开地址,不能放 Token 或密钥。
|
|
22
23
|
|
|
23
24
|
## 按需参考
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
按任务读取 [reference.md](reference.md) 的对应小节;签名以当前源码或安装版本的公开类型为准,不默认加载全部资料。示例中的业务变量由应用提供。
|
|
26
27
|
|
|
27
28
|
- 运行环境
|
|
28
29
|
- 适用场景
|
|
@@ -42,12 +42,14 @@ await capacitor.close()
|
|
|
42
42
|
|
|
43
43
|
### 2. Token 安全存储(与 api-client 配合)
|
|
44
44
|
|
|
45
|
+
先复用应用已有的 API 初始化入口。示例中的 `apiBase` 由应用解析公开运行配置,已经包含 API 前缀,不重复拼接 `/api/v1`。Web 部署可通过 `runtime-config.js` 覆盖地址;原生包在复制静态资源前设置公开配置。
|
|
46
|
+
|
|
45
47
|
```typescript
|
|
46
48
|
import { apiClient } from '@h-ai/api-client'
|
|
47
49
|
import { createCapacitorTokenStorage } from '@h-ai/capacitor'
|
|
48
50
|
|
|
49
51
|
await apiClient.init({
|
|
50
|
-
baseUrl:
|
|
52
|
+
baseUrl: apiBase,
|
|
51
53
|
auth: {
|
|
52
54
|
storage: createCapacitorTokenStorage(),
|
|
53
55
|
},
|
|
@@ -236,7 +238,7 @@ export async function initCapacitor() {
|
|
|
236
238
|
|
|
237
239
|
### SPA 模式配置(必需)
|
|
238
240
|
|
|
239
|
-
Capacitor
|
|
241
|
+
Capacitor 原生壳读取 `webDir` 指定的静态产物。纯 Vite 模板默认为 `dist`;SvelteKit 项目需配置静态适配器,输出可能是 `build`。构建器、容器与 `webDir` 必须一致,以下是 Vite 示例:
|
|
240
242
|
|
|
241
243
|
```typescript
|
|
242
244
|
// capacitor.config.ts
|
|
@@ -258,9 +260,9 @@ export default config
|
|
|
258
260
|
import { apiClient } from '@h-ai/api-client'
|
|
259
261
|
import { createCapacitorTokenStorage } from '@h-ai/capacitor'
|
|
260
262
|
|
|
261
|
-
export async function initApi() {
|
|
263
|
+
export async function initApi(apiBase: string) {
|
|
262
264
|
return apiClient.init({
|
|
263
|
-
baseUrl:
|
|
265
|
+
baseUrl: apiBase,
|
|
264
266
|
auth: {
|
|
265
267
|
storage: createCapacitorTokenStorage(),
|
|
266
268
|
},
|
|
@@ -52,6 +52,8 @@ pnpm test
|
|
|
52
52
|
|
|
53
53
|
从当前 package.json 确认实际 scripts,使用声明的最低 Node 版本验证并与 engines/README 同步;PowerShell 使用 `pnpm.cmd`。测试内容由 `hai-app-tests` 定义;本技能决定门禁如何稳定执行。
|
|
54
54
|
|
|
55
|
+
AI 本地验收和排查按 [分轮验证与集中修复](../hai-app-tests/validation.md) 收集所有可执行结果,再集中修复;普通失败不立即从头重跑。独立 CI job 可继续收集结果,真实前置失败则阻塞其依赖 job;必需检查与发布步骤仍须全部成功,不以继续收集为由设置 `continue-on-error` 或放行失败。
|
|
56
|
+
|
|
55
57
|
lint/typecheck 只能执行已配置的规则。新增开发规范时区分可静态检测项(如显式 any/console.log)与必须行为验收项(如资源越权、事务/幂等、失败结果、生命周期清理),后者在适用单元/集成/E2E 中验证。禁止通过关闭规则、添加无依据忽略或把 `|| true` 加到门禁来“统一风格”。
|
|
56
58
|
|
|
57
59
|
### E2E 策略
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: hai-core
|
|
3
|
-
description: "
|
|
3
|
+
description: "配置 hai 应用启动、YAML/环境变量、日志、i18n 与 HaiResult;排查配置覆盖和错误本地化。"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# hai-core
|
|
@@ -18,7 +18,7 @@ description: "使用 @h-ai/core 配置、日志、i18n、ID、Zod 错误本地
|
|
|
18
18
|
## 使用路径
|
|
19
19
|
|
|
20
20
|
- Node 启动用 core.init({ configDir }) 加载配置;勿传 logging 覆盖 _core.yml 中的日志设置,除非任务明确要求。
|
|
21
|
-
- 应用名称、版本、环境、默认语言和日志设置统一从 `_core.yml` 经 core.config 获取,日志使用 core.logger;UI 不另维护硬编码副本。SvelteKit layout / fullstack app.info 仅下发公开字段白名单;独立 Vite mobile-app 在 Node
|
|
21
|
+
- 应用名称、版本、环境、默认语言和日志设置统一从 `_core.yml` 经 core.config 获取,日志使用 core.logger;UI 不另维护硬编码副本。SvelteKit layout / fullstack app.info 仅下发公开字段白名单;独立 Vite mobile-app 在 Node 构建时解析并注入公开默认值,修改这些编译默认值需要重新构建。静态 Web 的公开 API 地址等通过 `runtime-config.js` 覆盖时无需重建镜像,原生包在复制静态资源前准备配置入口。浏览器不加载 YAML,不接收完整模块配置、密钥或日志脱敏上下文。
|
|
22
22
|
- `HAI_<配置名>_<路径>` 可整体覆盖根/对象/数组;父节点优先,未覆盖时才遍历已有叶子。camelCase 不拆词,约定变量优先于显式插值。
|
|
23
23
|
- 环境文件采用六类:`.env.example` 完整列出字段用途、默认值、枚举和范围;`.env` 为公共基础;`.env.dev`、`.env.test`、`.env.prod` 为公共环境覆盖,这五类均提交 Git。`.env.local` 为本机私有配置,不提交 Git。优先级为进程环境 > `.env.local` > 环境文件 > `.env`;Vite 在 Node 配置阶段调用 `core.config.loadEnvFiles({ mode, envDir })` 并检查 success 后注入进程,Node 脚本按相同顺序传入 `--env-file`。`core.init` 不自行读取 dotenv。
|
|
24
24
|
- 配置 getter 与 ID/logger 方法不是 HaiResult;validate/load 等须检查 success。
|
|
@@ -26,7 +26,7 @@ description: "使用 @h-ai/core 配置、日志、i18n、ID、Zod 错误本地
|
|
|
26
26
|
|
|
27
27
|
## 按需参考
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
按任务读取 [reference.md](reference.md) 的对应小节;签名以当前源码或安装版本的公开类型为准,不默认加载全部资料。示例中的业务变量由应用提供。
|
|
30
30
|
|
|
31
31
|
- 运行环境
|
|
32
32
|
- 适用场景
|