@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.
Files changed (109) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +167 -0
  3. package/dist/chunk-2H7UOFLK.js +10 -0
  4. package/dist/chunk-2H7UOFLK.js.map +1 -0
  5. package/dist/chunk-LANIDJC7.js +659 -0
  6. package/dist/chunk-LANIDJC7.js.map +1 -0
  7. package/dist/chunk-YV7WPUI6.js +206 -0
  8. package/dist/chunk-YV7WPUI6.js.map +1 -0
  9. package/dist/config-templates-S5RI2MC6.js +5 -0
  10. package/dist/config-templates-S5RI2MC6.js.map +1 -0
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.js +1650 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/skill-templates-N5DU5FNX.js +5 -0
  15. package/dist/skill-templates-N5DU5FNX.js.map +1 -0
  16. package/dist/src-XJ6YXNAF.js +15030 -0
  17. package/dist/src-XJ6YXNAF.js.map +1 -0
  18. package/package.json +59 -0
  19. package/templates/README.md +126 -0
  20. package/templates/apps/admin/messages/en-US.json +33 -0
  21. package/templates/apps/admin/messages/zh-CN.json +33 -0
  22. package/templates/apps/admin/src/routes/+layout.svelte.hbs +30 -0
  23. package/templates/apps/admin/src/routes/+page.svelte.hbs +35 -0
  24. package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +44 -0
  25. package/templates/apps/admin/src/routes/admin/+page.svelte.hbs +45 -0
  26. package/templates/apps/admin/src/routes/admin/settings/+page.svelte.hbs +37 -0
  27. package/templates/apps/admin/src/routes/api/health/+server.ts +12 -0
  28. package/templates/apps/android-app/.env.hbs +3 -0
  29. package/templates/apps/android-app/capacitor.config.ts.hbs +12 -0
  30. package/templates/apps/android-app/messages/en-US.json +24 -0
  31. package/templates/apps/android-app/messages/zh-CN.json +24 -0
  32. package/templates/apps/android-app/src/lib/api.ts.hbs +30 -0
  33. package/templates/apps/android-app/src/lib/capacitor.ts +21 -0
  34. package/templates/apps/android-app/src/routes/+layout.svelte.hbs +42 -0
  35. package/templates/apps/android-app/src/routes/+layout.ts +7 -0
  36. package/templates/apps/android-app/src/routes/+page.svelte.hbs +27 -0
  37. package/templates/apps/api/src/routes/+layout.svelte +14 -0
  38. package/templates/apps/api/src/routes/+page.svelte +11 -0
  39. package/templates/apps/api/src/routes/api/v1/health/+server.ts +14 -0
  40. package/templates/apps/api/src/routes/api/v1/items/+server.ts +37 -0
  41. package/templates/apps/api/src/routes/api/v1/items/[id]/+server.ts +30 -0
  42. package/templates/apps/h5/messages/en-US.json +37 -0
  43. package/templates/apps/h5/messages/zh-CN.json +37 -0
  44. package/templates/apps/h5/src/routes/+layout.svelte.hbs +57 -0
  45. package/templates/apps/h5/src/routes/+page.svelte.hbs +27 -0
  46. package/templates/apps/h5/src/routes/discover/+page.svelte.hbs +27 -0
  47. package/templates/apps/h5/src/routes/profile/+page.svelte.hbs +27 -0
  48. package/templates/apps/website/messages/en-US.json +32 -0
  49. package/templates/apps/website/messages/zh-CN.json +32 -0
  50. package/templates/apps/website/src/routes/+layout.svelte.hbs +66 -0
  51. package/templates/apps/website/src/routes/+page.svelte.hbs +33 -0
  52. package/templates/apps/website/src/routes/about/+page.svelte.hbs +27 -0
  53. package/templates/apps/website/src/routes/api/health/+server.ts +12 -0
  54. package/templates/apps/website/src/routes/contact/+page.svelte.hbs +59 -0
  55. package/templates/apps/website/src/routes/services/+page.svelte.hbs +63 -0
  56. package/templates/base/e2e/helpers.ts +49 -0
  57. package/templates/base/package.json.hbs +67 -0
  58. package/templates/base/playwright.config.ts +32 -0
  59. package/templates/base/src/app.css +2 -0
  60. package/templates/base/src/app.d.ts.hbs +18 -0
  61. package/templates/base/src/app.html.hbs +16 -0
  62. package/templates/base/src/hooks.server.ts.hbs +123 -0
  63. package/templates/base/src/lib/server/init.ts.hbs +190 -0
  64. package/templates/base/svelte.config.js.hbs +30 -0
  65. package/templates/base/tsconfig.json +8 -0
  66. package/templates/base/vite.config.ts.hbs +27 -0
  67. package/templates/features/ai/routes/api/chat/+server.ts +26 -0
  68. package/templates/features/datapipe/routes/api/datapipe/pipeline/+server.ts +41 -0
  69. package/templates/features/iam/routes-admin/(auth)/+layout.svelte.hbs +21 -0
  70. package/templates/features/iam/routes-admin/(auth)/auth/login/+page.svelte.hbs +98 -0
  71. package/templates/features/iam/routes-admin/(auth)/auth/register/+page.svelte.hbs +124 -0
  72. package/templates/features/iam/routes-h5/auth/login/+page.svelte.hbs +98 -0
  73. package/templates/features/iam/routes-h5/auth/register/+page.svelte.hbs +114 -0
  74. package/templates/features/iam/routes-shared/api/auth/login/+server.ts +32 -0
  75. package/templates/features/iam/routes-shared/api/auth/logout/+server.ts +15 -0
  76. package/templates/features/iam/routes-shared/api/auth/me/+server.ts +12 -0
  77. package/templates/features/iam/routes-shared/api/auth/register/+server.ts +26 -0
  78. package/templates/features/payment/routes/api/v1/payment/create/+server.ts +30 -0
  79. package/templates/features/payment/routes/api/v1/payment/notify/[provider]/+server.ts +42 -0
  80. package/templates/features/payment/routes/api/v1/payment/query/[orderNo]/+server.ts +25 -0
  81. package/templates/features/storage/routes/api/upload/+server.ts +35 -0
  82. package/templates/features/vecdb/routes/api/vecdb/search/+server.ts +36 -0
  83. package/templates/i18n/project.inlang/settings.json +11 -0
  84. package/templates/skills/AGENTS.md +26 -0
  85. package/templates/skills/CLAUDE.md +12 -0
  86. package/templates/skills/copilot-instructions.md +61 -0
  87. package/templates/skills/hai-ai/SKILL.md +721 -0
  88. package/templates/skills/hai-api-client/SKILL.md +355 -0
  89. package/templates/skills/hai-app-create/SKILL.md +371 -0
  90. package/templates/skills/hai-app-review/SKILL.md +156 -0
  91. package/templates/skills/hai-app-tests/SKILL.md +468 -0
  92. package/templates/skills/hai-audit/SKILL.md +145 -0
  93. package/templates/skills/hai-build/SKILL.md +319 -0
  94. package/templates/skills/hai-cache/SKILL.md +190 -0
  95. package/templates/skills/hai-capacitor/SKILL.md +377 -0
  96. package/templates/skills/hai-core/SKILL.md +484 -0
  97. package/templates/skills/hai-crypto/SKILL.md +198 -0
  98. package/templates/skills/hai-datapipe/SKILL.md +220 -0
  99. package/templates/skills/hai-deploy/SKILL.md +216 -0
  100. package/templates/skills/hai-iam/SKILL.md +361 -0
  101. package/templates/skills/hai-kit/SKILL.md +484 -0
  102. package/templates/skills/hai-payment/SKILL.md +275 -0
  103. package/templates/skills/hai-reach/SKILL.md +315 -0
  104. package/templates/skills/hai-reldb/SKILL.md +465 -0
  105. package/templates/skills/hai-scheduler/SKILL.md +188 -0
  106. package/templates/skills/hai-storage/SKILL.md +276 -0
  107. package/templates/skills/hai-ui/SKILL.md +438 -0
  108. package/templates/skills/hai-vecdb/SKILL.md +279 -0
  109. package/templates/skills/opencode.json +11 -0
@@ -0,0 +1,275 @@
1
+ ---
2
+ name: hai-payment
3
+ description: 使用 @h-ai/payment 接入微信支付、支付宝、Stripe 统一支付;支持创建订单、回调验签、订单查询与退款;当需求涉及支付接入、订单创建、支付回调、退款或客户端调起支付时使用。
4
+ ---
5
+
6
+ # hai-payment
7
+
8
+ > `@h-ai/payment` 是 hai-framework 的统一支付模块,通过 Provider 模式支持微信支付、支付宝、Stripe。服务端处理订单和回调,客户端调起支付。
9
+
10
+ ---
11
+
12
+ ## 运行环境
13
+
14
+ > **服务端 + 浏览器分工模块。** `payment.createOrder` / `payment.handleNotify` / `payment.refund` 等操作在 Node.js 端执行。浏览器端使用 `invokePayment()`(从 `@h-ai/payment/client` 导入)调起支付。
15
+
16
+ ---
17
+
18
+ ## 依赖
19
+
20
+ | 模块 | 用途 | 是否必需 | 初始化要求 |
21
+ | --- | --- | --- | --- |
22
+ | `@h-ai/audit` | 审计日志(支付操作自动审计) | 可选 | 已初始化时自动写入审计日志 |
23
+
24
+ ---
25
+
26
+ ## 适用场景
27
+
28
+ - 接入微信支付(JSAPI / H5 / Native / App)
29
+ - 接入支付宝(H5 / App / PC)
30
+ - 接入 Stripe(Checkout Session)
31
+ - 支付回调验签与通知处理
32
+ - 订单查询与退款
33
+ - 客户端调起支付(Web / H5 / App)
34
+
35
+ ---
36
+
37
+ ## 使用步骤
38
+
39
+ ### 1. 配置
40
+
41
+ ```yaml
42
+ # config/_payment.yml(自行创建)
43
+ wechat:
44
+ appId: ${HAI_PAYMENT_WECHAT_APP_ID}
45
+ mchId: ${HAI_PAYMENT_WECHAT_MCH_ID}
46
+ apiV3Key: ${HAI_PAYMENT_WECHAT_API_V3_KEY}
47
+ privateKey: ${HAI_PAYMENT_WECHAT_PRIVATE_KEY}
48
+ serialNo: ${HAI_PAYMENT_WECHAT_SERIAL_NO}
49
+ platformCert: ${HAI_PAYMENT_WECHAT_PLATFORM_CERT} # 可选,回调验签需要
50
+
51
+ alipay:
52
+ appId: ${HAI_PAYMENT_ALIPAY_APP_ID}
53
+ privateKey: ${HAI_PAYMENT_ALIPAY_PRIVATE_KEY}
54
+ alipayPublicKey: ${HAI_PAYMENT_ALIPAY_PUBLIC_KEY}
55
+ signType: RSA2 # 可选,默认 RSA2
56
+ sandbox: false # 可选,默认 false
57
+
58
+ stripe:
59
+ secretKey: ${HAI_PAYMENT_STRIPE_SECRET_KEY}
60
+ webhookSecret: ${HAI_PAYMENT_STRIPE_WEBHOOK_SECRET}
61
+ ```
62
+
63
+ ### 2. 初始化(服务端)
64
+
65
+ ```typescript
66
+ import { payment } from '@h-ai/payment'
67
+
68
+ const initResult = await payment.init({
69
+ wechat: {
70
+ appId: 'wx1234567890',
71
+ mchId: '1600000000',
72
+ apiV3Key: 'your-api-key-v3',
73
+ privateKey: '-----BEGIN RSA PRIVATE KEY-----...',
74
+ serialNo: 'CERT_SERIAL_NO',
75
+ },
76
+ alipay: {
77
+ appId: '2021000000000000',
78
+ privateKey: '-----BEGIN RSA PRIVATE KEY-----...',
79
+ alipayPublicKey: '-----BEGIN PUBLIC KEY-----...',
80
+ },
81
+ })
82
+ if (!initResult.success) {
83
+ // 处理初始化错误
84
+ }
85
+ ```
86
+
87
+ ### 3. 创建订单
88
+
89
+ ```typescript
90
+ const result = await payment.createOrder('wechat', {
91
+ orderNo: 'ORDER_20250101_001',
92
+ amount: 9900, // 单位:分
93
+ description: '商品名称',
94
+ tradeType: 'jsapi',
95
+ userId: 'user_openid',
96
+ notifyUrl: 'https://example.com/payment/notify/wechat',
97
+ metadata: { source: 'web' },
98
+ })
99
+
100
+ if (result.success) {
101
+ // result.data: PaymentOrder { provider, tradeType, clientParams, prepayId? }
102
+ // clientParams 包含调起支付所需参数
103
+ }
104
+ ```
105
+
106
+ ### 4. 处理支付回调
107
+
108
+ ```typescript
109
+ // API 路由:POST /payment/notify/:provider
110
+ const result = await payment.handleNotify('wechat', {
111
+ body: await request.text(),
112
+ headers: Object.fromEntries(request.headers),
113
+ })
114
+
115
+ if (result.success) {
116
+ // result.data: { orderNo, transactionId, amount, status, paidAt? }
117
+ // 更新订单状态...
118
+ }
119
+ ```
120
+
121
+ ### 5. 查询订单
122
+
123
+ ```typescript
124
+ const result = await payment.queryOrder('wechat', 'ORDER_20250101_001')
125
+
126
+ if (result.success) {
127
+ // result.data.status: 'pending' | 'paid' | 'closed' | 'refunded' | 'failed'
128
+ }
129
+ ```
130
+
131
+ ### 6. 退款
132
+
133
+ ```typescript
134
+ const result = await payment.refund('wechat', {
135
+ orderNo: 'ORDER_20250101_001',
136
+ refundNo: 'REFUND_20250101_001',
137
+ amount: 9900,
138
+ totalAmount: 9900, // 微信退款必填
139
+ reason: '用户退款',
140
+ })
141
+ ```
142
+
143
+ ### 7. 客户端调起支付(浏览器)
144
+
145
+ ```typescript
146
+ import { invokePayment } from '@h-ai/payment/client'
147
+
148
+ // 前端拿到服务端返回的 PaymentOrder 后调起支付
149
+ const payResult = await invokePayment(orderResult.data)
150
+ ```
151
+
152
+ ---
153
+
154
+ ## 核心 API(服务端)
155
+
156
+ | API | 用途 | 返回值 |
157
+ | ------------------------------------------------ | -------- | ----------------------------- |
158
+ | `payment.init(config)` | 初始化 | `HaiResult<void>` |
159
+ | `payment.close()` | 关闭 | `void` |
160
+ | `payment.createOrder(providerName, input)` | 创建订单 | `HaiResult<PaymentOrder>` |
161
+ | `payment.handleNotify(providerName, request)` | 处理回调 | `HaiResult<PaymentNotifyResult>` |
162
+ | `payment.queryOrder(providerName, orderNo)` | 查询订单 | `HaiResult<OrderStatus>` |
163
+ | `payment.refund(providerName, input)` | 退款 | `HaiResult<RefundResult>` |
164
+ | `payment.closeOrder(providerName, orderNo)` | 关闭订单 | `HaiResult<void>` |
165
+ | `payment.getProvider(name)` | 获取 Provider | `PaymentProvider \| undefined` |
166
+ | `payment.registerProvider(provider)` | 注册自定义 Provider | `void` |
167
+
168
+ ## 客户端 API
169
+
170
+ | API | 用途 | 返回值 |
171
+ | ------------------------ | -------- | ----------------------------- |
172
+ | `invokePayment(options)` | 调起支付 | `HaiResult<InvokePaymentResult>` |
173
+
174
+ ---
175
+
176
+ ## 错误码 — `HaiPaymentError`
177
+
178
+ | 错误码 | code | 说明 |
179
+ |--------|------|------|
180
+ | `HaiPaymentError.CREATE_ORDER_FAILED` | `hai:payment:001` | 创建订单失败 |
181
+ | `HaiPaymentError.QUERY_ORDER_FAILED` | `hai:payment:002` | 查询订单失败 |
182
+ | `HaiPaymentError.REFUND_FAILED` | `hai:payment:003` | 退款失败 |
183
+ | `HaiPaymentError.CLOSE_ORDER_FAILED` | `hai:payment:004` | 关闭订单失败 |
184
+ | `HaiPaymentError.NOT_INITIALIZED` | `hai:payment:010` | 模块未初始化 |
185
+ | `HaiPaymentError.SIGN_FAILED` | `hai:payment:020` | 签名失败 |
186
+ | `HaiPaymentError.PROVIDER_NOT_FOUND` | `hai:payment:030` | Provider 未注册 |
187
+ | `HaiPaymentError.INVALID_AMOUNT` | `hai:payment:040` | 金额无效 |
188
+ | `HaiPaymentError.NOTIFY_VERIFY_FAILED` | `hai:payment:050` | 回调验签失败 |
189
+ | `HaiPaymentError.NOTIFY_PARSE_FAILED` | `hai:payment:051` | 回调解析失败 |
190
+ | `HaiPaymentError.INVOKE_WEB_FAILED` | `hai:payment:060` | Web 端调起支付失败 |
191
+ | `HaiPaymentError.INVOKE_APP_FAILED` | `hai:payment:061` | App 端调起支付失败 |
192
+ | `HaiPaymentError.CONFIG_ERROR` | `hai:payment:070` | 配置无效 |
193
+
194
+ ---
195
+
196
+ ## Provider 模式
197
+
198
+ 内置 Provider:
199
+
200
+ - **wechat** — 微信支付 API v3
201
+ - **alipay** — 支付宝开放平台
202
+ - **stripe** — Stripe Checkout Session 模式
203
+
204
+ 自定义 Provider 通过 `payment.registerProvider(provider)` 注册。
205
+
206
+ ### API 契约
207
+
208
+ ```typescript
209
+ import { paymentEndpoints } from '@h-ai/payment/api'
210
+
211
+ // paymentEndpoints.createOrder — POST /payment/create
212
+ // paymentEndpoints.queryOrder — GET /payment/query
213
+ // paymentEndpoints.notifyWechat — POST /payment/notify/wechat
214
+ // paymentEndpoints.notifyAlipay — POST /payment/notify/alipay
215
+ // paymentEndpoints.notifyStripe — POST /payment/notify/stripe
216
+ // paymentEndpoints.refund — POST /payment/refund
217
+ ```
218
+
219
+ ---
220
+
221
+ ## 常见模式
222
+
223
+ ### 服务端路由(SvelteKit)
224
+
225
+ ```typescript
226
+ import { kit } from '@h-ai/kit'
227
+ // src/routes/api/v1/payment/create/+server.ts
228
+ import { payment } from '@h-ai/payment'
229
+
230
+ export const POST = kit.handler(async ({ request, locals }) => {
231
+ const body = await request.json()
232
+ const result = await payment.createOrder(body.provider, {
233
+ orderNo: body.orderNo,
234
+ amount: body.amount,
235
+ description: body.description,
236
+ tradeType: body.tradeType,
237
+ userId: body.userId,
238
+ notifyUrl: body.notifyUrl,
239
+ })
240
+
241
+ if (!result.success) {
242
+ return kit.response.internalError(result.error.message)
243
+ }
244
+
245
+ return kit.response.ok(result.data, locals.requestId)
246
+ })
247
+ ```
248
+
249
+ ### 完整支付流程
250
+
251
+ ```
252
+ 1. 客户端 → 服务端:创建订单(api.call(paymentEndpoints.createOrder, {...}))
253
+ 2. 服务端 → 支付平台:调用 Provider 创建预支付
254
+ 3. 服务端 → 客户端:返回 providerData(含调起参数)
255
+ 4. 客户端:invokePayment(providerData) 调起支付
256
+ 5. 支付平台 → 服务端:异步回调通知
257
+ 6. 服务端:handleNotify() 验签 + 更新订单状态
258
+ 7. 客户端:轮询或回调查询订单状态
259
+ ```
260
+
261
+ ---
262
+
263
+ ## 审计日志
264
+
265
+ 关键支付操作(创建订单、回调、退款、关闭订单)成功后自动写入审计日志(依赖 `@h-ai/audit`),无需额外配置。`queryOrder` 为只读操作,不写审计日志。
266
+
267
+ ---
268
+
269
+ ## 相关 Skills
270
+
271
+ - `hai-kit`:服务端 API 路由与契约处理
272
+ - `hai-api-client`:客户端契约调用
273
+ - `hai-reldb`:订单持久化存储
274
+ - `hai-audit`:审计日志(payment 内部自动调用)
275
+ - `hai-core`:HaiResult 类型、日志、配置
@@ -0,0 +1,315 @@
1
+ ---
2
+ name: hai-reach
3
+ description: 使用 @h-ai/reach 进行邮件、短信和 API 回调发送;当需求涉及用户触达、消息通知、验证码发送、多渠道模板管理时使用。
4
+ ---
5
+
6
+ # hai-reach
7
+
8
+ > `@h-ai/reach` 提供统一的用户触达接口,支持同时注册多个 Provider(SMTP 邮件、短信、API 回调),内置模板引擎与免打扰(DND)机制。
9
+
10
+ ---
11
+
12
+ ## 运行环境
13
+
14
+ > ⚠️ **服务端模块(Node.js only)。** 浏览器端不直接发送邮件/短信,而是通过 API 端点触发服务端 `reach.send()`。
15
+
16
+ ---
17
+
18
+ ## 依赖
19
+
20
+ | 模块 | 用途 | 是否必需 | 初始化要求 |
21
+ | --- | --- | --- | --- |
22
+ | `@h-ai/reldb` | 数据库(发送日志与模板持久化) | 可选 | 已初始化时自动启用持久化 |
23
+ | `@h-ai/cache` | 缓存(DND delay 策略分布式锁) | 可选 | 已初始化时自动启用分布式锁 |
24
+
25
+ ---
26
+
27
+ ## 适用场景
28
+
29
+ - 同时使用邮件、短信、API 回调发送通知
30
+ - 发送邮件通知(注册欢迎、密码重置、告警等)
31
+ - 发送短信验证码或通知
32
+ - 通过 HTTP API 回调触发第三方通知
33
+ - 定义和管理消息模板(模板绑定到具体 Provider,支持配置文件定义)
34
+ - 免打扰时段控制
35
+ - 基于 `HaiReachError` 做错误分支处理
36
+
37
+ ---
38
+
39
+ ## 使用步骤
40
+
41
+ ### 1. 配置
42
+
43
+ ```yaml
44
+ # config/_reach.yml
45
+ providers:
46
+ - name: email
47
+ type: smtp
48
+ host: ${HAI_REACH_SMTP_HOST:smtp.example.com}
49
+ port: ${HAI_REACH_SMTP_PORT:465}
50
+ secure: true
51
+ user: ${HAI_REACH_SMTP_USER:}
52
+ pass: ${HAI_REACH_SMTP_PASS:}
53
+ from: ${HAI_REACH_SMTP_FROM:noreply@example.com}
54
+ - name: sms
55
+ type: aliyun-sms
56
+ accessKeyId: ${HAI_REACH_SMS_ACCESS_KEY:}
57
+ accessKeySecret: ${HAI_REACH_SMS_SECRET_KEY:}
58
+ signName: ${HAI_REACH_SMS_SIGN_NAME:}
59
+ - name: webhook
60
+ type: api
61
+ url: ${HAI_REACH_WEBHOOK_URL:}
62
+
63
+ # 模板(可选,也可通过代码注册)
64
+ templates:
65
+ - name: verification_code
66
+ provider: email
67
+ subject: '验证码: {code}'
68
+ body: '您的验证码是 {code},有效期 {minutes} 分钟。'
69
+ - name: sms_code
70
+ provider: sms
71
+ body: '验证码: {code},{minutes} 分钟内有效。'
72
+
73
+ # 免打扰(可选)
74
+ dnd:
75
+ enabled: true
76
+ strategy: delay # discard(丢弃)或 delay(延时,DND 结束后集中发送)
77
+ start: '22:00'
78
+ end: '08:00'
79
+ ```
80
+
81
+ ### 2. 初始化与关闭
82
+
83
+ ```typescript
84
+ import { cache } from '@h-ai/cache'
85
+ import { core } from '@h-ai/core'
86
+ import { reach } from '@h-ai/reach'
87
+ import { reldb } from '@h-ai/reldb'
88
+
89
+ // 先初始化可选依赖(按需)
90
+ await reldb.init(core.config.get('db')) // 可选,启用发送日志与模板持久化
91
+ await cache.init(core.config.get('cache')) // 可选,启用 DND delay 策略分布式锁
92
+
93
+ // 再初始化触达模块(自动检测已初始化的 reldb/cache 单例)
94
+ await reach.init(core.config.get('reach'))
95
+ // ... 使用触达服务
96
+ await reach.close()
97
+ ```
98
+
99
+ ### 3. 保存模板(模板绑定 Provider)
100
+
101
+ ```typescript
102
+ // 通过代码保存(配置文件中的模板在 init 时自动注册)
103
+ await reach.template.save({
104
+ name: 'verification_code',
105
+ provider: 'email',
106
+ subject: '验证码: {code}',
107
+ body: '您的验证码是 {code},有效期 {minutes} 分钟。',
108
+ })
109
+
110
+ await reach.template.saveBatch([
111
+ { name: 'welcome', provider: 'email', subject: '欢迎 {userName}', body: '亲爱的 {userName},欢迎使用 {appName}!' },
112
+ { name: 'sms_code', provider: 'sms', body: '验证码: {code},{minutes} 分钟内有效。' },
113
+ ])
114
+ ```
115
+
116
+ ### 4. 发送消息
117
+
118
+ ```typescript
119
+ // 使用模板发送邮件(指定 provider)
120
+ const result = await reach.send({
121
+ provider: 'email',
122
+ to: 'user@example.com',
123
+ template: 'verification_code',
124
+ vars: { code: '123456', minutes: '5' },
125
+ })
126
+
127
+ // 直接发送邮件(无模板)
128
+ await reach.send({
129
+ provider: 'email',
130
+ to: 'user@example.com',
131
+ subject: '通知',
132
+ body: '<h1>Hello</h1>',
133
+ })
134
+
135
+ // 发送短信(通过 extra 传递 Provider 特有参数)
136
+ await reach.send({
137
+ provider: 'sms',
138
+ to: '13800138000',
139
+ extra: { templateCode: 'SMS_123456' },
140
+ vars: { code: '654321' },
141
+ })
142
+
143
+ // API 回调
144
+ await reach.send({
145
+ provider: 'webhook',
146
+ to: 'user@example.com',
147
+ body: '{"event":"signup"}',
148
+ })
149
+ ```
150
+
151
+ ---
152
+
153
+ ## 核心 API
154
+
155
+ ### reach 对象
156
+
157
+ | 方法 / 属性 | 签名 | 说明 |
158
+ | --------------------- | -------------------------------------------------------- | ---------------------------------- |
159
+ | `reach.init` | `(config: ReachConfigInput) => Promise<HaiResult<void>>` | 初始化(注册多个 Provider) |
160
+ | `reach.send` | `(message: ReachMessage) => Promise<HaiResult<SendResult>>` | 发送消息(通过 provider 字段路由) |
161
+ | `reach.template` | `ReachTemplateRegistry` | 模板注册表 |
162
+ | `reach.config` | `ReachConfig \| null` | 当前配置 |
163
+ | `reach.isInitialized` | `boolean` | 是否已初始化 |
164
+ | `reach.close` | `() => Promise<void>` | 关闭所有连接 |
165
+
166
+ ### ReachConfigInput
167
+
168
+ ```typescript
169
+ interface ReachConfigInput {
170
+ providers: ProviderConfig[] // 多个 Provider 配置
171
+ templates?: TemplateConfig[] // 通过配置文件定义的模板
172
+ dnd?: {
173
+ enabled: boolean // 是否启用
174
+ strategy: 'discard' | 'delay' // discard 丢弃 / delay 延时发送
175
+ start: string // 开始时间 HH:mm
176
+ end: string // 结束时间 HH:mm
177
+ }
178
+ }
179
+ ```
180
+
181
+ ### ReachMessage
182
+
183
+ | 字段 | 类型 | 必填 | 说明 |
184
+ | ---------- | ------------------------- | ---- | ----------------------------------- |
185
+ | `provider` | `string` | ✅ | 目标 Provider 名称 |
186
+ | `to` | `string` | ✅ | 接收方(邮箱或手机号) |
187
+ | `subject` | `string` | — | 邮件主题(直接发送时) |
188
+ | `body` | `string` | — | 消息正文(直接发送时) |
189
+ | `template` | `string` | — | 模板名称(模板发送时) |
190
+ | `vars` | `Record<string, string>` | — | 模板变量 |
191
+ | `extra` | `Record<string, unknown>` | — | Provider 扩展参数(如短信模板编码) |
192
+
193
+ ### ReachTemplate(模板绑定 Provider)
194
+
195
+ | 字段 | 类型 | 必填 | 说明 |
196
+ | ---------- | -------- | ---- | -------------------- |
197
+ | `name` | `string` | ✅ | 模板名称 |
198
+ | `provider` | `string` | ✅ | 绑定的 Provider 名称 |
199
+ | `subject` | `string` | — | 邮件主题模板 |
200
+ | `body` | `string` | ✅ | 正文模板 |
201
+
202
+ ---
203
+
204
+ ## 错误码
205
+
206
+ | 错误码 | code | 说明 |
207
+ |--------|------|------|
208
+ | `HaiReachError.SEND_FAILED` | `hai:reach:001` | 发送失败 |
209
+ | `HaiReachError.TEMPLATE_NOT_FOUND` | `hai:reach:002` | 模板未找到 |
210
+ | `HaiReachError.TEMPLATE_RENDER_FAILED` | `hai:reach:003` | 模板渲染失败 |
211
+ | `HaiReachError.INVALID_RECIPIENT` | `hai:reach:004` | 无效接收方 |
212
+ | `HaiReachError.PROVIDER_NOT_FOUND` | `hai:reach:005` | Provider 未找到 |
213
+ | `HaiReachError.DND_BLOCKED` | `hai:reach:006` | 免打扰丢弃 |
214
+ | `HaiReachError.DND_DEFERRED` | `hai:reach:007` | 免打扰延时暂存 |
215
+ | `HaiReachError.NOT_INITIALIZED` | `hai:reach:010` | 模块未初始化 |
216
+ | `HaiReachError.UNSUPPORTED_TYPE` | `hai:reach:011` | 不支持的类型 |
217
+ | `HaiReachError.CONFIG_ERROR` | `hai:reach:012` | 配置错误 |
218
+
219
+ ---
220
+
221
+ ## 常见模式
222
+
223
+ ### 多渠道验证码发送
224
+
225
+ ```typescript
226
+ reach.template.save({
227
+ name: 'email_code',
228
+ provider: 'email',
229
+ subject: '验证码: {code}',
230
+ body: '您的验证码是 {code},有效期 {minutes} 分钟。',
231
+ })
232
+
233
+ reach.template.save({
234
+ name: 'sms_code',
235
+ provider: 'sms',
236
+ body: '验证码: {code},{minutes} 分钟内有效。',
237
+ })
238
+
239
+ // 根据用户选择的渠道发送
240
+ async function sendCode(channel: 'email' | 'sms', target: string, code: string) {
241
+ const template = channel === 'email' ? 'email_code' : 'sms_code'
242
+ return reach.send({
243
+ provider: channel,
244
+ to: target,
245
+ template,
246
+ vars: { code, minutes: '5' },
247
+ })
248
+ }
249
+ ```
250
+
251
+ ### 与 IAM 集成(密码重置 / OTP 验证码)
252
+
253
+ ```typescript
254
+ import { iam } from '@h-ai/iam'
255
+ import { reach } from '@h-ai/reach'
256
+
257
+ // 初始化 reach(注册 email 和 sms Provider)
258
+ await reach.init({
259
+ providers: [
260
+ { name: 'email', type: 'smtp', host: 'smtp.example.com', from: 'noreply@example.com' },
261
+ { name: 'sms', type: 'aliyun-sms', accessKeyId: '...', accessKeySecret: '...', signName: '...' },
262
+ ],
263
+ templates: [
264
+ { name: 'password_reset', provider: 'email', subject: 'Password Reset', body: 'Token: {token}, expires: {expiresAt}' },
265
+ { name: 'otp_email', provider: 'email', subject: 'Code: {code}', body: 'Your code is {code}' },
266
+ { name: 'otp_sms', provider: 'sms', body: 'Your code is {code}' },
267
+ ],
268
+ })
269
+
270
+ // 初始化 IAM,使用 reach 发送通知
271
+ await iam.init({
272
+ db,
273
+ cache,
274
+ onPasswordResetRequest: async (user, token, expiresAt) => {
275
+ await reach.send({ provider: 'email', to: user.email ?? '', template: 'password_reset', vars: { token, expiresAt: expiresAt.toISOString() } })
276
+ },
277
+ onOtpSendEmail: async (email, code) => {
278
+ await reach.send({ provider: 'email', to: email, template: 'otp_email', vars: { code } })
279
+ },
280
+ onOtpSendSms: async (phone, code) => {
281
+ await reach.send({ provider: 'sms', to: phone, template: 'otp_sms', vars: { code } })
282
+ },
283
+ })
284
+ ```
285
+
286
+ ### 错误处理
287
+
288
+ ```typescript
289
+ import { HaiReachError } from '@h-ai/reach'
290
+
291
+ const result = await reach.send(message)
292
+ if (!result.success) {
293
+ switch (result.error.code) {
294
+ case HaiReachError.NOT_INITIALIZED.code:
295
+ break
296
+ case HaiReachError.PROVIDER_NOT_FOUND.code:
297
+ break
298
+ case HaiReachError.DND_BLOCKED.code:
299
+ // 免打扰时段(discard 策略),消息已丢弃
300
+ break
301
+ case HaiReachError.TEMPLATE_NOT_FOUND.code:
302
+ break
303
+ case HaiReachError.SEND_FAILED.code:
304
+ break
305
+ }
306
+ }
307
+ ```
308
+
309
+ ---
310
+
311
+ ## 相关 Skills
312
+
313
+ - `hai-core` — 配置加载、日志、HaiResult 模式
314
+ - `hai-reldb` — 数据库操作(存储发送记录等)
315
+ - `hai-cache` — 缓存操作、分布式锁(DND flush 互斥)