@usethink/cf-core 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -0
- package/dist/features/anti-abuse/index.d.ts +62 -0
- package/dist/features/anti-abuse/index.d.ts.map +1 -0
- package/dist/features/anti-abuse/index.js +139 -0
- package/dist/features/anti-abuse/index.js.map +1 -0
- package/dist/features/email/index.d.ts +68 -0
- package/dist/features/email/index.d.ts.map +1 -0
- package/dist/features/email/index.js +120 -0
- package/dist/features/email/index.js.map +1 -0
- package/dist/features/payment/fetch-utils.d.ts +17 -0
- package/dist/features/payment/fetch-utils.d.ts.map +1 -0
- package/dist/features/payment/fetch-utils.js +56 -0
- package/dist/features/payment/fetch-utils.js.map +1 -0
- package/dist/features/payment/index.d.ts +20 -0
- package/dist/features/payment/index.d.ts.map +1 -0
- package/dist/features/payment/index.js +21 -0
- package/dist/features/payment/index.js.map +1 -0
- package/dist/features/payment/providers/alipay.d.ts +30 -0
- package/dist/features/payment/providers/alipay.d.ts.map +1 -0
- package/dist/features/payment/providers/alipay.js +149 -0
- package/dist/features/payment/providers/alipay.js.map +1 -0
- package/dist/features/payment/providers/stripe.d.ts +34 -0
- package/dist/features/payment/providers/stripe.d.ts.map +1 -0
- package/dist/features/payment/providers/stripe.js +168 -0
- package/dist/features/payment/providers/stripe.js.map +1 -0
- package/dist/features/payment/providers/trc20.d.ts +24 -0
- package/dist/features/payment/providers/trc20.d.ts.map +1 -0
- package/dist/features/payment/providers/trc20.js +96 -0
- package/dist/features/payment/providers/trc20.js.map +1 -0
- package/dist/features/payment/registry.d.ts +43 -0
- package/dist/features/payment/registry.d.ts.map +1 -0
- package/dist/features/payment/registry.js +65 -0
- package/dist/features/payment/registry.js.map +1 -0
- package/dist/features/payment/types.d.ts +72 -0
- package/dist/features/payment/types.d.ts.map +1 -0
- package/dist/features/payment/types.js +8 -0
- package/dist/features/payment/types.js.map +1 -0
- package/dist/features/thompson-router/index.d.ts +101 -0
- package/dist/features/thompson-router/index.d.ts.map +1 -0
- package/dist/features/thompson-router/index.js +186 -0
- package/dist/features/thompson-router/index.js.map +1 -0
- package/dist/features/webhook/index.d.ts +76 -0
- package/dist/features/webhook/index.d.ts.map +1 -0
- package/dist/features/webhook/index.js +127 -0
- package/dist/features/webhook/index.js.map +1 -0
- package/dist/src/audit.d.ts +45 -0
- package/dist/src/audit.d.ts.map +1 -0
- package/dist/src/audit.js +40 -0
- package/dist/src/audit.js.map +1 -0
- package/dist/src/auth/jwt.d.ts +33 -0
- package/dist/src/auth/jwt.d.ts.map +1 -0
- package/dist/src/auth/jwt.js +87 -0
- package/dist/src/auth/jwt.js.map +1 -0
- package/dist/src/auth/password.d.ts +26 -0
- package/dist/src/auth/password.d.ts.map +1 -0
- package/dist/src/auth/password.js +52 -0
- package/dist/src/auth/password.js.map +1 -0
- package/dist/src/bootstrap.d.ts +74 -0
- package/dist/src/bootstrap.d.ts.map +1 -0
- package/dist/src/bootstrap.js +231 -0
- package/dist/src/bootstrap.js.map +1 -0
- package/dist/src/cache.d.ts +52 -0
- package/dist/src/cache.d.ts.map +1 -0
- package/dist/src/cache.js +76 -0
- package/dist/src/cache.js.map +1 -0
- package/dist/src/config.d.ts +83 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +96 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/crypto.d.ts +33 -0
- package/dist/src/crypto.d.ts.map +1 -0
- package/dist/src/crypto.js +87 -0
- package/dist/src/crypto.js.map +1 -0
- package/dist/src/db/connection.d.ts +53 -0
- package/dist/src/db/connection.d.ts.map +1 -0
- package/dist/src/db/connection.js +104 -0
- package/dist/src/db/connection.js.map +1 -0
- package/dist/src/db/index.d.ts +6 -0
- package/dist/src/db/index.d.ts.map +1 -0
- package/dist/src/db/index.js +6 -0
- package/dist/src/db/index.js.map +1 -0
- package/dist/src/db/schema.d.ts +649 -0
- package/dist/src/db/schema.d.ts.map +1 -0
- package/dist/src/db/schema.js +76 -0
- package/dist/src/db/schema.js.map +1 -0
- package/dist/src/error.d.ts +47 -0
- package/dist/src/error.d.ts.map +1 -0
- package/dist/src/error.js +94 -0
- package/dist/src/error.js.map +1 -0
- package/dist/src/http.d.ts +83 -0
- package/dist/src/http.d.ts.map +1 -0
- package/dist/src/http.js +116 -0
- package/dist/src/http.js.map +1 -0
- package/dist/src/idempotency.d.ts +78 -0
- package/dist/src/idempotency.d.ts.map +1 -0
- package/dist/src/idempotency.js +84 -0
- package/dist/src/idempotency.js.map +1 -0
- package/dist/src/index.d.ts +31 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +45 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/logger.d.ts +31 -0
- package/dist/src/logger.d.ts.map +1 -0
- package/dist/src/logger.js +45 -0
- package/dist/src/logger.js.map +1 -0
- package/dist/src/middleware/admin-auth.d.ts +38 -0
- package/dist/src/middleware/admin-auth.d.ts.map +1 -0
- package/dist/src/middleware/admin-auth.js +55 -0
- package/dist/src/middleware/admin-auth.js.map +1 -0
- package/dist/src/middleware/api-key-auth.d.ts +42 -0
- package/dist/src/middleware/api-key-auth.d.ts.map +1 -0
- package/dist/src/middleware/api-key-auth.js +104 -0
- package/dist/src/middleware/api-key-auth.js.map +1 -0
- package/dist/src/middleware/index.d.ts +3 -0
- package/dist/src/middleware/index.d.ts.map +1 -0
- package/dist/src/middleware/index.js +3 -0
- package/dist/src/middleware/index.js.map +1 -0
- package/dist/src/rate-limit.d.ts +54 -0
- package/dist/src/rate-limit.d.ts.map +1 -0
- package/dist/src/rate-limit.js +134 -0
- package/dist/src/rate-limit.js.map +1 -0
- package/dist/src/security.d.ts +78 -0
- package/dist/src/security.d.ts.map +1 -0
- package/dist/src/security.js +175 -0
- package/dist/src/security.js.map +1 -0
- package/dist/src/types.d.ts +64 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +8 -0
- package/dist/src/types.js.map +1 -0
- package/features/anti-abuse/index.ts +180 -0
- package/features/anti-abuse/tests/index.test.ts +50 -0
- package/features/email/index.ts +172 -0
- package/features/email/tests/index.test.ts +44 -0
- package/features/payment/fetch-utils.ts +65 -0
- package/features/payment/index.ts +39 -0
- package/features/payment/providers/alipay.ts +171 -0
- package/features/payment/providers/stripe.ts +192 -0
- package/features/payment/providers/trc20.ts +115 -0
- package/features/payment/registry.ts +87 -0
- package/features/payment/tests/index.test.ts +506 -0
- package/features/payment/types.ts +93 -0
- package/features/telegram-miniapp/index.ts +109 -0
- package/features/telegram-miniapp/tests/index.test.ts +11 -0
- package/features/thompson-router/index.ts +243 -0
- package/features/thompson-router/tests/index.test.ts +93 -0
- package/features/webhook/index.ts +183 -0
- package/features/webhook/tests/index.test.ts +21 -0
- package/package.json +202 -0
- package/src/audit.ts +70 -0
- package/src/auth/jwt.ts +114 -0
- package/src/auth/password.ts +75 -0
- package/src/bootstrap.ts +322 -0
- package/src/cache.ts +78 -0
- package/src/config.ts +134 -0
- package/src/crypto.ts +106 -0
- package/src/db/connection.ts +127 -0
- package/src/db/index.ts +6 -0
- package/src/db/schema.ts +90 -0
- package/src/error.ts +125 -0
- package/src/http.ts +150 -0
- package/src/idempotency.ts +127 -0
- package/src/index.ts +85 -0
- package/src/logger.ts +63 -0
- package/src/middleware/admin-auth.ts +71 -0
- package/src/middleware/api-key-auth.ts +164 -0
- package/src/middleware/index.ts +2 -0
- package/src/rate-limit.ts +167 -0
- package/src/security.ts +219 -0
- package/src/types.ts +70 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @usethink/cf-core — 公共数据库 Schema
|
|
3
|
+
*
|
|
4
|
+
* 所有项目共享的 Drizzle ORM 表定义。
|
|
5
|
+
* 各项目在此基础上添加自己的业务表。
|
|
6
|
+
*
|
|
7
|
+
* 公共表清单:
|
|
8
|
+
* 1. systemConfig — 系统配置(KV 存储,热生效)
|
|
9
|
+
* 2. adminAuditLogs — 管理员审计日志
|
|
10
|
+
* 3. rateLimitWindows — 限流计数窗口(DB 版限流)
|
|
11
|
+
* 4. idempotencyKeys — 幂等键(防重复提交)
|
|
12
|
+
* 5. apiKeys — API Key 认证(可选启用)
|
|
13
|
+
*
|
|
14
|
+
* 来源:eshop/xtools/vcode 三项目 schema.ts 中公共部分提取
|
|
15
|
+
*/
|
|
16
|
+
import { integer, primaryKey, sqliteTable, text } from "drizzle-orm/sqlite-core";
|
|
17
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
18
|
+
// 1. 系统配置表(KV 存储,热生效)
|
|
19
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
20
|
+
export const systemConfig = sqliteTable("system_config", {
|
|
21
|
+
key: text("key").primaryKey(),
|
|
22
|
+
value: text("value").default("").notNull(),
|
|
23
|
+
updatedAt: text("updated_at").default("").notNull(),
|
|
24
|
+
});
|
|
25
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
26
|
+
// 2. 管理员审计日志表
|
|
27
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
28
|
+
export const adminAuditLogs = sqliteTable("admin_audit_logs", {
|
|
29
|
+
id: text("id").primaryKey(),
|
|
30
|
+
action: text("action").notNull(),
|
|
31
|
+
targetType: text("target_type").default("").notNull(),
|
|
32
|
+
targetId: text("target_id").default("").notNull(),
|
|
33
|
+
metadataJson: text("metadata_json").default("{}").notNull(),
|
|
34
|
+
ipHash: text("ip_hash").default("").notNull(),
|
|
35
|
+
createdAt: text("created_at").default("").notNull(),
|
|
36
|
+
});
|
|
37
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
38
|
+
// 3. 限流窗口表(DB 版限流 — 原子 upsert)
|
|
39
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
40
|
+
export const rateLimitWindows = sqliteTable("rate_limit_windows", {
|
|
41
|
+
action: text("action").notNull(),
|
|
42
|
+
ipHash: text("ip_hash").notNull(),
|
|
43
|
+
windowStart: integer("window_start").notNull(),
|
|
44
|
+
requestCount: integer("request_count").default(0).notNull(),
|
|
45
|
+
}, (table) => ({
|
|
46
|
+
pk: primaryKey({ columns: [table.action, table.ipHash, table.windowStart] }),
|
|
47
|
+
}));
|
|
48
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
49
|
+
// 4. 幂等键表(防重复提交)
|
|
50
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
51
|
+
export const idempotencyKeys = sqliteTable("idempotency_keys", {
|
|
52
|
+
key: text("key").notNull(),
|
|
53
|
+
action: text("action").notNull(),
|
|
54
|
+
resourceId: text("resource_id").notNull(),
|
|
55
|
+
responseJson: text("response_json").notNull(),
|
|
56
|
+
createdAt: text("created_at").default("").notNull(),
|
|
57
|
+
});
|
|
58
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
59
|
+
// 5. API Key 表(可选启用)
|
|
60
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
61
|
+
export const apiKeys = sqliteTable("api_keys", {
|
|
62
|
+
id: text("id").primaryKey(),
|
|
63
|
+
name: text("name").notNull(),
|
|
64
|
+
keyHash: text("key_hash").notNull(),
|
|
65
|
+
userId: text("user_id").default("").notNull(),
|
|
66
|
+
tier: text("tier").default("free").notNull(),
|
|
67
|
+
enabled: integer("enabled").default(1).notNull(),
|
|
68
|
+
monthlyQuota: integer("monthly_quota").default(0).notNull(),
|
|
69
|
+
monthlyUsage: integer("monthly_usage").default(0).notNull(),
|
|
70
|
+
monthlyResetAt: text("monthly_reset_at"),
|
|
71
|
+
lastUsedAt: text("last_used_at"),
|
|
72
|
+
createdAt: text("created_at").default("").notNull(),
|
|
73
|
+
updatedAt: text("updated_at").default("").notNull(),
|
|
74
|
+
expiresAt: text("expires_at"),
|
|
75
|
+
});
|
|
76
|
+
//# sourceMappingURL=schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/db/schema.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 错误分类与指数退避重试
|
|
3
|
+
*
|
|
4
|
+
* 区分可重试 / 不可重试 / 需延迟重试的错误类型,
|
|
5
|
+
* 配合指数退避(exponential backoff + jitter)减少无效重试。
|
|
6
|
+
*
|
|
7
|
+
* 来源:xtools src/lib/error-classifier.ts
|
|
8
|
+
*/
|
|
9
|
+
export declare enum ErrorType {
|
|
10
|
+
/** 网络超时、5xx 服务器错误 → 可重试 */
|
|
11
|
+
TRANSIENT = "transient",
|
|
12
|
+
/** 4xx 认证失败、invalid_grant → 不可重试 */
|
|
13
|
+
PERMANENT = "permanent",
|
|
14
|
+
/** 429 Too Many Requests → 延迟重试 */
|
|
15
|
+
RATE_LIMIT = "rate_limit"
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 分类错误类型
|
|
19
|
+
*/
|
|
20
|
+
export declare function classifyError(error: unknown): ErrorType;
|
|
21
|
+
export interface RetryOptions {
|
|
22
|
+
/** 最大重试次数(默认 3) */
|
|
23
|
+
maxRetries?: number;
|
|
24
|
+
/** 基础延迟毫秒(默认 1000) */
|
|
25
|
+
baseDelayMs?: number;
|
|
26
|
+
/** 限流延迟倍数(默认 5) */
|
|
27
|
+
rateLimitMultiplier?: number;
|
|
28
|
+
/** 最大延迟毫秒(默认 30000) */
|
|
29
|
+
maxDelayMs?: number;
|
|
30
|
+
/** 重试回调(用于日志) */
|
|
31
|
+
onRetry?: (attempt: number, error: Error, delayMs: number) => void;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 带指数退避的重试执行器
|
|
35
|
+
*
|
|
36
|
+
* - TRANSIENT: 指数退避(1s → 2s → 4s)
|
|
37
|
+
* - RATE_LIMIT: 更长延迟(5s → 10s → 20s)
|
|
38
|
+
* - PERMANENT: 立即抛出,不重试
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* const result = await retryWithBackoff(
|
|
42
|
+
* () => fetchExternalApi(),
|
|
43
|
+
* { maxRetries: 3, onRetry: (n, err, ms) => console.warn(`重试 #${n}: ${err.message} (${ms}ms)`) }
|
|
44
|
+
* );
|
|
45
|
+
*/
|
|
46
|
+
export declare function retryWithBackoff<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
|
|
47
|
+
//# sourceMappingURL=error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.d.ts","sourceRoot":"","sources":["../../src/error.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,oBAAY,SAAS;IACnB,2BAA2B;IAC3B,SAAS,cAAc;IACvB,oCAAoC;IACpC,SAAS,cAAc;IACvB,mCAAmC;IACnC,UAAU,eAAe;CAC1B;AAsBD;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,SAAS,CAUvD;AAED,MAAM,WAAW,YAAY;IAC3B,mBAAmB;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sBAAsB;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mBAAmB;IACnB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,uBAAuB;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB;IACjB,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CACpE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,gBAAgB,CAAC,CAAC,EACtC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,CAAC,CAAC,CA0CZ"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 错误分类与指数退避重试
|
|
3
|
+
*
|
|
4
|
+
* 区分可重试 / 不可重试 / 需延迟重试的错误类型,
|
|
5
|
+
* 配合指数退避(exponential backoff + jitter)减少无效重试。
|
|
6
|
+
*
|
|
7
|
+
* 来源:xtools src/lib/error-classifier.ts
|
|
8
|
+
*/
|
|
9
|
+
export var ErrorType;
|
|
10
|
+
(function (ErrorType) {
|
|
11
|
+
/** 网络超时、5xx 服务器错误 → 可重试 */
|
|
12
|
+
ErrorType["TRANSIENT"] = "transient";
|
|
13
|
+
/** 4xx 认证失败、invalid_grant → 不可重试 */
|
|
14
|
+
ErrorType["PERMANENT"] = "permanent";
|
|
15
|
+
/** 429 Too Many Requests → 延迟重试 */
|
|
16
|
+
ErrorType["RATE_LIMIT"] = "rate_limit";
|
|
17
|
+
})(ErrorType || (ErrorType = {}));
|
|
18
|
+
const PERMANENT_KEYWORDS = [
|
|
19
|
+
"invalid_grant",
|
|
20
|
+
"invalid_client",
|
|
21
|
+
"unauthorized",
|
|
22
|
+
"interaction_required",
|
|
23
|
+
"access_denied",
|
|
24
|
+
"invalid_token",
|
|
25
|
+
"401",
|
|
26
|
+
"403",
|
|
27
|
+
"account_disabled",
|
|
28
|
+
"consent_required",
|
|
29
|
+
];
|
|
30
|
+
const RATE_LIMIT_KEYWORDS = [
|
|
31
|
+
"429",
|
|
32
|
+
"too many requests",
|
|
33
|
+
"rate limit",
|
|
34
|
+
"throttl",
|
|
35
|
+
];
|
|
36
|
+
/**
|
|
37
|
+
* 分类错误类型
|
|
38
|
+
*/
|
|
39
|
+
export function classifyError(error) {
|
|
40
|
+
const message = (error instanceof Error ? error.message : String(error)).toLowerCase();
|
|
41
|
+
if (RATE_LIMIT_KEYWORDS.some((kw) => message.includes(kw))) {
|
|
42
|
+
return ErrorType.RATE_LIMIT;
|
|
43
|
+
}
|
|
44
|
+
if (PERMANENT_KEYWORDS.some((kw) => message.includes(kw))) {
|
|
45
|
+
return ErrorType.PERMANENT;
|
|
46
|
+
}
|
|
47
|
+
return ErrorType.TRANSIENT;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 带指数退避的重试执行器
|
|
51
|
+
*
|
|
52
|
+
* - TRANSIENT: 指数退避(1s → 2s → 4s)
|
|
53
|
+
* - RATE_LIMIT: 更长延迟(5s → 10s → 20s)
|
|
54
|
+
* - PERMANENT: 立即抛出,不重试
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* const result = await retryWithBackoff(
|
|
58
|
+
* () => fetchExternalApi(),
|
|
59
|
+
* { maxRetries: 3, onRetry: (n, err, ms) => console.warn(`重试 #${n}: ${err.message} (${ms}ms)`) }
|
|
60
|
+
* );
|
|
61
|
+
*/
|
|
62
|
+
export async function retryWithBackoff(fn, options = {}) {
|
|
63
|
+
const { maxRetries = 3, baseDelayMs = 1000, rateLimitMultiplier = 5, maxDelayMs = 30_000, onRetry, } = options;
|
|
64
|
+
let lastError;
|
|
65
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
66
|
+
try {
|
|
67
|
+
return await fn();
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
lastError = error;
|
|
71
|
+
const errorType = classifyError(error);
|
|
72
|
+
if (errorType === ErrorType.PERMANENT) {
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
if (attempt >= maxRetries) {
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
let delayMs;
|
|
79
|
+
if (errorType === ErrorType.RATE_LIMIT) {
|
|
80
|
+
delayMs = Math.min(baseDelayMs * rateLimitMultiplier * Math.pow(2, attempt), maxDelayMs);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
delayMs = Math.min(baseDelayMs * Math.pow(2, attempt), maxDelayMs);
|
|
84
|
+
}
|
|
85
|
+
// 随机抖动(±20%),避免雷群效应
|
|
86
|
+
const jitter = delayMs * 0.2 * (Math.random() * 2 - 1);
|
|
87
|
+
delayMs = Math.round(delayMs + jitter);
|
|
88
|
+
onRetry?.(attempt + 1, error, delayMs);
|
|
89
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
throw lastError;
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.js","sourceRoot":"","sources":["../../src/error.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP 响应工具模块
|
|
3
|
+
*
|
|
4
|
+
* 提供统一的 ok/fail 响应格式,以及常用的请求解析工具。
|
|
5
|
+
* 泛型设计:兼容任意 Hono AppEnv 类型,无需绑定特定项目。
|
|
6
|
+
*
|
|
7
|
+
* 来源:eshop/xtools/vcode 三项目 lib/http.ts 合并
|
|
8
|
+
*/
|
|
9
|
+
import type { Context } from "hono";
|
|
10
|
+
import type { ContentfulStatusCode } from "hono/utils/http-status";
|
|
11
|
+
import type { RateLimitResult } from "./types";
|
|
12
|
+
/**
|
|
13
|
+
* 成功响应 — 统一格式 { ok: true, ...data }
|
|
14
|
+
*/
|
|
15
|
+
export declare function ok(c: Context<any>, data: Record<string, unknown>, status?: ContentfulStatusCode): Response & import("hono").TypedResponse<{
|
|
16
|
+
[x: string]: import("hono/utils/types").JSONValue;
|
|
17
|
+
}, 500 | 100 | 102 | 103 | 200 | 201 | 202 | 203 | 206 | 207 | 208 | 226 | 300 | 301 | 302 | 303 | 305 | 306 | 307 | 308 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 421 | 422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 451 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511 | -1, "json">;
|
|
18
|
+
/**
|
|
19
|
+
* 失败响应 — 统一格式 { ok: false, error: message }
|
|
20
|
+
*/
|
|
21
|
+
export declare function fail(c: Context<any>, message: string, status?: number, details?: unknown): Response & import("hono").TypedResponse<{
|
|
22
|
+
[x: string]: import("hono/utils/types").JSONValue;
|
|
23
|
+
}, 500 | 100 | 102 | 103 | 200 | 201 | 202 | 203 | 206 | 207 | 208 | 226 | 300 | 301 | 302 | 303 | 305 | 306 | 307 | 308 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 421 | 422 | 423 | 424 | 425 | 426 | 428 | 429 | 431 | 451 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511 | -1, "json">;
|
|
24
|
+
/**
|
|
25
|
+
* 限流失败响应 — 429 + 标准 RateLimit 头
|
|
26
|
+
*
|
|
27
|
+
* 返回标准 HTTP 429 响应,包含以下头信息:
|
|
28
|
+
* - Retry-After: 秒数(建议客户端等待时间)
|
|
29
|
+
* - X-RateLimit-Limit: 窗口内允许的最大请求数
|
|
30
|
+
* - X-RateLimit-Remaining: 窗口内剩余请求数(0)
|
|
31
|
+
* - X-RateLimit-Reset: 窗口重置的 Unix 时间戳
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* const result = await rateLimiter.check(key, limit, windowMs);
|
|
36
|
+
* if (!result.ok) {
|
|
37
|
+
* return failRateLimit(c, result, limit);
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export declare function failRateLimit(c: Context<any>, result: RateLimitResult, limit: number): Response & import("hono").TypedResponse<{
|
|
42
|
+
ok: false;
|
|
43
|
+
error: string;
|
|
44
|
+
}, 429, "json">;
|
|
45
|
+
/**
|
|
46
|
+
* 获取站点域名 — 优先使用环境变量 APP_ORIGIN,降级使用请求 URL
|
|
47
|
+
*/
|
|
48
|
+
export declare function getOrigin<E extends {
|
|
49
|
+
Bindings: {
|
|
50
|
+
APP_ORIGIN?: string;
|
|
51
|
+
};
|
|
52
|
+
}>(c: Context<E>): string;
|
|
53
|
+
/**
|
|
54
|
+
* 安全读取 JSON body — 解析失败返回 undefined(避免非 JSON 请求体导致 400)
|
|
55
|
+
*
|
|
56
|
+
* 支持泛型,让调用方可以明确 JSON 形状,避免下游 zod safeParse 的 unknown 类型报错。
|
|
57
|
+
* 默认类型为 unknown,保持向后兼容。
|
|
58
|
+
*/
|
|
59
|
+
export declare function safeJsonBody<T = unknown>(c: Context<any>): Promise<T>;
|
|
60
|
+
/**
|
|
61
|
+
* 联系方式脱敏 — 用于管理端展示
|
|
62
|
+
*
|
|
63
|
+
* - 邮箱:ab***@example.com
|
|
64
|
+
* - 手机/其他:ab***cd
|
|
65
|
+
* - 长度 ≤ 4:***
|
|
66
|
+
*/
|
|
67
|
+
export declare function maskContact(value: string): string;
|
|
68
|
+
/**
|
|
69
|
+
* 标准化编码 — trim + lowercase(用于优惠码/折扣码等)
|
|
70
|
+
*/
|
|
71
|
+
export declare function normalizeCode(value?: string): string;
|
|
72
|
+
/**
|
|
73
|
+
* CSV 注入防护 — 对导出值做安全转义
|
|
74
|
+
*
|
|
75
|
+
* 如果值以 = + - @ \t \n 开头,前置制表符阻止公式注入。
|
|
76
|
+
* 来源:eshop admin 订单导出
|
|
77
|
+
*/
|
|
78
|
+
export declare function csvEscape(value: unknown): string;
|
|
79
|
+
/**
|
|
80
|
+
* 将对象数组导出为 CSV 字符串
|
|
81
|
+
*/
|
|
82
|
+
export declare function toCsv(rows: Record<string, unknown>[], columns: string[]): string;
|
|
83
|
+
//# sourceMappingURL=http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../../src/http.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AACnE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE/C;;GAEG;AAEH,wBAAgB,EAAE,CAChB,CAAC,EAAE,OAAO,CAAC,GAAG,CAAC,EACf,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,MAAM,GAAE,oBAA0B;;iXAGnC;AAED;;GAEG;AAEH,wBAAgB,IAAI,CAClB,CAAC,EAAE,OAAO,CAAC,GAAG,CAAC,EACf,OAAO,EAAE,MAAM,EACf,MAAM,GAAE,MAAY,EACpB,OAAO,CAAC,EAAE,OAAO;;iXAMlB;AAED;;;;;;;;;;;;;;;;GAgBG;AAEH,wBAAgB,aAAa,CAC3B,CAAC,EAAE,OAAO,CAAC,GAAG,CAAC,EACf,MAAM,EAAE,eAAe,EACvB,KAAK,EAAE,MAAM;;;gBAed;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS;IAAE,QAAQ,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,MAAM,CAEhG;AAED;;;;;GAKG;AAEH,wBAAsB,YAAY,CAAC,CAAC,GAAG,OAAO,EAAE,CAAC,EAAE,OAAO,CAAC,GAAG,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAE3E;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQjD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAMhD;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,MAAM,CAWhF"}
|
package/dist/src/http.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP 响应工具模块
|
|
3
|
+
*
|
|
4
|
+
* 提供统一的 ok/fail 响应格式,以及常用的请求解析工具。
|
|
5
|
+
* 泛型设计:兼容任意 Hono AppEnv 类型,无需绑定特定项目。
|
|
6
|
+
*
|
|
7
|
+
* 来源:eshop/xtools/vcode 三项目 lib/http.ts 合并
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* 成功响应 — 统一格式 { ok: true, ...data }
|
|
11
|
+
*/
|
|
12
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
13
|
+
export function ok(c, data, status = 200) {
|
|
14
|
+
return c.json({ ok: true, ...data }, status);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* 失败响应 — 统一格式 { ok: false, error: message }
|
|
18
|
+
*/
|
|
19
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
20
|
+
export function fail(c, message, status = 400, details) {
|
|
21
|
+
return c.json({ ok: false, error: message, ...(details ? { details } : {}) }, status);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 限流失败响应 — 429 + 标准 RateLimit 头
|
|
25
|
+
*
|
|
26
|
+
* 返回标准 HTTP 429 响应,包含以下头信息:
|
|
27
|
+
* - Retry-After: 秒数(建议客户端等待时间)
|
|
28
|
+
* - X-RateLimit-Limit: 窗口内允许的最大请求数
|
|
29
|
+
* - X-RateLimit-Remaining: 窗口内剩余请求数(0)
|
|
30
|
+
* - X-RateLimit-Reset: 窗口重置的 Unix 时间戳
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* const result = await rateLimiter.check(key, limit, windowMs);
|
|
35
|
+
* if (!result.ok) {
|
|
36
|
+
* return failRateLimit(c, result, limit);
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
41
|
+
export function failRateLimit(c, result, limit) {
|
|
42
|
+
const retryAfterSeconds = Math.ceil((result.resetMs || 0) / 1000);
|
|
43
|
+
const resetTimestamp = Math.ceil((Date.now() + (result.resetMs || 0)) / 1000);
|
|
44
|
+
return c.json({ ok: false, error: result.message || "请求过于频繁,请稍后再试" }, 429, {
|
|
45
|
+
"Retry-After": String(retryAfterSeconds),
|
|
46
|
+
"X-RateLimit-Limit": String(limit),
|
|
47
|
+
"X-RateLimit-Remaining": "0",
|
|
48
|
+
"X-RateLimit-Reset": String(resetTimestamp),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 获取站点域名 — 优先使用环境变量 APP_ORIGIN,降级使用请求 URL
|
|
53
|
+
*/
|
|
54
|
+
export function getOrigin(c) {
|
|
55
|
+
return c.env.APP_ORIGIN || new URL(c.req.url).origin;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 安全读取 JSON body — 解析失败返回 undefined(避免非 JSON 请求体导致 400)
|
|
59
|
+
*
|
|
60
|
+
* 支持泛型,让调用方可以明确 JSON 形状,避免下游 zod safeParse 的 unknown 类型报错。
|
|
61
|
+
* 默认类型为 unknown,保持向后兼容。
|
|
62
|
+
*/
|
|
63
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
64
|
+
export async function safeJsonBody(c) {
|
|
65
|
+
return c.req.json().catch(() => undefined);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* 联系方式脱敏 — 用于管理端展示
|
|
69
|
+
*
|
|
70
|
+
* - 邮箱:ab***@example.com
|
|
71
|
+
* - 手机/其他:ab***cd
|
|
72
|
+
* - 长度 ≤ 4:***
|
|
73
|
+
*/
|
|
74
|
+
export function maskContact(value) {
|
|
75
|
+
const text = value.trim();
|
|
76
|
+
if (text.length <= 4)
|
|
77
|
+
return "***";
|
|
78
|
+
if (text.includes("@") && !text.startsWith("@")) {
|
|
79
|
+
const [name, domain] = text.split("@");
|
|
80
|
+
return `${name.slice(0, 2)}***@${domain}`;
|
|
81
|
+
}
|
|
82
|
+
return `${text.slice(0, 2)}***${text.slice(-2)}`;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 标准化编码 — trim + lowercase(用于优惠码/折扣码等)
|
|
86
|
+
*/
|
|
87
|
+
export function normalizeCode(value) {
|
|
88
|
+
return (value || "").trim().toLowerCase();
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* CSV 注入防护 — 对导出值做安全转义
|
|
92
|
+
*
|
|
93
|
+
* 如果值以 = + - @ \t \n 开头,前置制表符阻止公式注入。
|
|
94
|
+
* 来源:eshop admin 订单导出
|
|
95
|
+
*/
|
|
96
|
+
export function csvEscape(value) {
|
|
97
|
+
const str = String(value ?? "");
|
|
98
|
+
if (/^[=+\-@\t\n]/.test(str)) {
|
|
99
|
+
return `\t${str}`;
|
|
100
|
+
}
|
|
101
|
+
return str;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* 将对象数组导出为 CSV 字符串
|
|
105
|
+
*/
|
|
106
|
+
export function toCsv(rows, columns) {
|
|
107
|
+
const header = columns.join(",");
|
|
108
|
+
const body = rows.map((row) => columns.map((col) => {
|
|
109
|
+
const val = csvEscape(row[col]);
|
|
110
|
+
return val.includes(",") || val.includes('"') || val.includes("\n")
|
|
111
|
+
? `"${val.replace(/"/g, '""')}"`
|
|
112
|
+
: val;
|
|
113
|
+
}).join(",")).join("\n");
|
|
114
|
+
return `${header}\n${body}`;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=http.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.js","sourceRoot":"","sources":["../../src/http.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 幂等性模块 — 防止同一请求被重复处理
|
|
3
|
+
*
|
|
4
|
+
* 使用 (key, action) 复合键作为幂等标识。
|
|
5
|
+
* 核心设计:原子 UPSERT + 非空哨兵值,消除 TOCTOU 竞态。
|
|
6
|
+
*
|
|
7
|
+
* 流程:
|
|
8
|
+
* 1. checkIdempotency() — 原子 UPSERT,返回 shouldProceed
|
|
9
|
+
* 2. shouldProceed === false → 返回缓存响应
|
|
10
|
+
* 3. shouldProceed === true → 执行业务逻辑 → saveIdempotentResponse()
|
|
11
|
+
*
|
|
12
|
+
* 来源:eshop src/lib/idempotency.ts(三项目中唯一实现)
|
|
13
|
+
*/
|
|
14
|
+
import { idempotencyKeys } from "./db/schema";
|
|
15
|
+
/**
|
|
16
|
+
* 通用 Drizzle 数据库接口(仅约束幂等模块需要的方法)
|
|
17
|
+
*/
|
|
18
|
+
interface DbLike {
|
|
19
|
+
insert: (table: typeof idempotencyKeys) => {
|
|
20
|
+
values: (data: {
|
|
21
|
+
key: string;
|
|
22
|
+
action: string;
|
|
23
|
+
resourceId: string;
|
|
24
|
+
responseJson: string;
|
|
25
|
+
createdAt: string;
|
|
26
|
+
}) => {
|
|
27
|
+
onConflictDoUpdate: (opts: {
|
|
28
|
+
target: [typeof idempotencyKeys.key, typeof idempotencyKeys.action];
|
|
29
|
+
set: Record<string, unknown>;
|
|
30
|
+
}) => {
|
|
31
|
+
returning: (cols: {
|
|
32
|
+
responseJson: typeof idempotencyKeys.responseJson;
|
|
33
|
+
}) => Promise<{
|
|
34
|
+
responseJson: string;
|
|
35
|
+
}[]>;
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
select: (cols: {
|
|
40
|
+
responseJson: typeof idempotencyKeys.responseJson;
|
|
41
|
+
}) => {
|
|
42
|
+
from: (table: typeof idempotencyKeys) => {
|
|
43
|
+
where: (cond: unknown) => {
|
|
44
|
+
limit: (n: number) => Promise<{
|
|
45
|
+
responseJson: string;
|
|
46
|
+
}[]>;
|
|
47
|
+
};
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 原子检查幂等性
|
|
53
|
+
*
|
|
54
|
+
* INSERT ON CONFLICT UPDATE RETURNING 是 SQLite 原子操作。
|
|
55
|
+
* 并发请求中只有一个获得 shouldProceed=true。
|
|
56
|
+
*
|
|
57
|
+
* @returns shouldProceed=true 时应执行业务逻辑;false 时 cachedResponse 含之前缓存的响应
|
|
58
|
+
*/
|
|
59
|
+
export declare function checkIdempotency(db: DbLike, key: string, action: string): Promise<{
|
|
60
|
+
shouldProceed: boolean;
|
|
61
|
+
cachedResponse: string | null;
|
|
62
|
+
}>;
|
|
63
|
+
/**
|
|
64
|
+
* 保存幂等响应
|
|
65
|
+
*
|
|
66
|
+
* 在 checkIdempotency 返回 shouldProceed=true 并执行业务逻辑后调用。
|
|
67
|
+
*/
|
|
68
|
+
export declare function saveIdempotentResponse(db: DbLike, key: string, action: string, resourceId: string, response: unknown): Promise<void>;
|
|
69
|
+
/**
|
|
70
|
+
* 查询已缓存的幂等响应(只读,不创建记录)
|
|
71
|
+
*
|
|
72
|
+
* @deprecated 使用 checkIdempotency() 替代
|
|
73
|
+
*/
|
|
74
|
+
export declare function getIdempotentResponse(db: DbLike, key: string, action: string): Promise<{
|
|
75
|
+
responseJson: string;
|
|
76
|
+
} | null>;
|
|
77
|
+
export {};
|
|
78
|
+
//# sourceMappingURL=idempotency.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotency.d.ts","sourceRoot":"","sources":["../../src/idempotency.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAK9C;;GAEG;AACH,UAAU,MAAM;IACd,MAAM,EAAE,CAAC,KAAK,EAAE,OAAO,eAAe,KAAK;QACzC,MAAM,EAAE,CAAC,IAAI,EAAE;YAAE,GAAG,EAAE,MAAM,CAAC;YAAC,MAAM,EAAE,MAAM,CAAC;YAAC,UAAU,EAAE,MAAM,CAAC;YAAC,YAAY,EAAE,MAAM,CAAC;YAAC,SAAS,EAAE,MAAM,CAAA;SAAE,KAAK;YAC9G,kBAAkB,EAAE,CAAC,IAAI,EAAE;gBACzB,MAAM,EAAE,CAAC,OAAO,eAAe,CAAC,GAAG,EAAE,OAAO,eAAe,CAAC,MAAM,CAAC,CAAC;gBACpE,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;aAC9B,KAAK;gBACJ,SAAS,EAAE,CAAC,IAAI,EAAE;oBAAE,YAAY,EAAE,OAAO,eAAe,CAAC,YAAY,CAAA;iBAAE,KAAK,OAAO,CAAC;oBAAE,YAAY,EAAE,MAAM,CAAA;iBAAE,EAAE,CAAC,CAAC;aACjH,CAAC;SACH,CAAC;KACH,CAAC;IACF,MAAM,EAAE,CAAC,IAAI,EAAE;QAAE,YAAY,EAAE,OAAO,eAAe,CAAC,YAAY,CAAA;KAAE,KAAK;QACvE,IAAI,EAAE,CAAC,KAAK,EAAE,OAAO,eAAe,KAAK;YACvC,KAAK,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK;gBACxB,KAAK,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;oBAAE,YAAY,EAAE,MAAM,CAAA;iBAAE,EAAE,CAAC,CAAC;aAC3D,CAAC;SACH,CAAC;KACH,CAAC;CACH;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,MAAM,EACV,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,aAAa,EAAE,OAAO,CAAC;IAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,CAwBpE;AAED;;;;GAIG;AACH,wBAAsB,sBAAsB,CAC1C,EAAE,EAAE,MAAM,EACV,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,OAAO,GAChB,OAAO,CAAC,IAAI,CAAC,CAiBf;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CACzC,EAAE,EAAE,MAAM,EACV,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,GACb,OAAO,CAAC;IAAE,YAAY,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAO1C"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 幂等性模块 — 防止同一请求被重复处理
|
|
3
|
+
*
|
|
4
|
+
* 使用 (key, action) 复合键作为幂等标识。
|
|
5
|
+
* 核心设计:原子 UPSERT + 非空哨兵值,消除 TOCTOU 竞态。
|
|
6
|
+
*
|
|
7
|
+
* 流程:
|
|
8
|
+
* 1. checkIdempotency() — 原子 UPSERT,返回 shouldProceed
|
|
9
|
+
* 2. shouldProceed === false → 返回缓存响应
|
|
10
|
+
* 3. shouldProceed === true → 执行业务逻辑 → saveIdempotentResponse()
|
|
11
|
+
*
|
|
12
|
+
* 来源:eshop src/lib/idempotency.ts(三项目中唯一实现)
|
|
13
|
+
*/
|
|
14
|
+
import { idempotencyKeys } from "./db/schema";
|
|
15
|
+
import { eq, and, sql } from "drizzle-orm";
|
|
16
|
+
const PENDING_SENTINEL = "__pending__";
|
|
17
|
+
/**
|
|
18
|
+
* 原子检查幂等性
|
|
19
|
+
*
|
|
20
|
+
* INSERT ON CONFLICT UPDATE RETURNING 是 SQLite 原子操作。
|
|
21
|
+
* 并发请求中只有一个获得 shouldProceed=true。
|
|
22
|
+
*
|
|
23
|
+
* @returns shouldProceed=true 时应执行业务逻辑;false 时 cachedResponse 含之前缓存的响应
|
|
24
|
+
*/
|
|
25
|
+
export async function checkIdempotency(db, key, action) {
|
|
26
|
+
const [row] = await db
|
|
27
|
+
.insert(idempotencyKeys)
|
|
28
|
+
.values({
|
|
29
|
+
key,
|
|
30
|
+
action,
|
|
31
|
+
resourceId: "",
|
|
32
|
+
responseJson: PENDING_SENTINEL,
|
|
33
|
+
createdAt: new Date().toISOString(),
|
|
34
|
+
})
|
|
35
|
+
.onConflictDoUpdate({
|
|
36
|
+
target: [idempotencyKeys.key, idempotencyKeys.action],
|
|
37
|
+
set: { responseJson: sql `idempotency_keys.response_json` },
|
|
38
|
+
})
|
|
39
|
+
.returning({ responseJson: idempotencyKeys.responseJson });
|
|
40
|
+
const shouldProceed = row?.responseJson === PENDING_SENTINEL;
|
|
41
|
+
const cachedResponse = shouldProceed
|
|
42
|
+
? null
|
|
43
|
+
: row?.responseJson === PENDING_SENTINEL
|
|
44
|
+
? null
|
|
45
|
+
: row?.responseJson ?? null;
|
|
46
|
+
return { shouldProceed, cachedResponse };
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 保存幂等响应
|
|
50
|
+
*
|
|
51
|
+
* 在 checkIdempotency 返回 shouldProceed=true 并执行业务逻辑后调用。
|
|
52
|
+
*/
|
|
53
|
+
export async function saveIdempotentResponse(db, key, action, resourceId, response) {
|
|
54
|
+
await db
|
|
55
|
+
.insert(idempotencyKeys)
|
|
56
|
+
.values({
|
|
57
|
+
key,
|
|
58
|
+
action,
|
|
59
|
+
resourceId,
|
|
60
|
+
responseJson: JSON.stringify(response),
|
|
61
|
+
createdAt: new Date().toISOString(),
|
|
62
|
+
})
|
|
63
|
+
.onConflictDoUpdate({
|
|
64
|
+
target: [idempotencyKeys.key, idempotencyKeys.action],
|
|
65
|
+
set: {
|
|
66
|
+
responseJson: JSON.stringify(response),
|
|
67
|
+
resourceId,
|
|
68
|
+
},
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* 查询已缓存的幂等响应(只读,不创建记录)
|
|
73
|
+
*
|
|
74
|
+
* @deprecated 使用 checkIdempotency() 替代
|
|
75
|
+
*/
|
|
76
|
+
export async function getIdempotentResponse(db, key, action) {
|
|
77
|
+
const [row] = await db
|
|
78
|
+
.select({ responseJson: idempotencyKeys.responseJson })
|
|
79
|
+
.from(idempotencyKeys)
|
|
80
|
+
.where(and(eq(idempotencyKeys.key, key), eq(idempotencyKeys.action, action)))
|
|
81
|
+
.limit(1);
|
|
82
|
+
return row || null;
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=idempotency.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotency.js","sourceRoot":"","sources":["../../src/idempotency.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAC9C,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,gBAAgB,GAAG,aAAa,CAAC;AAyBvC;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,EAAU,EACV,GAAW,EACX,MAAc;IAEd,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,EAAE;SACnB,MAAM,CAAC,eAAe,CAAC;SACvB,MAAM,CAAC;QACN,GAAG;QACH,MAAM;QACN,UAAU,EAAE,EAAE;QACd,YAAY,EAAE,gBAAgB;QAC9B,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC;SACD,kBAAkB,CAAC;QAClB,MAAM,EAAE,CAAC,eAAe,CAAC,GAAG,EAAE,eAAe,CAAC,MAAM,CAAC;QACrD,GAAG,EAAE,EAAE,YAAY,EAAE,GAAG,CAAA,gCAAgC,EAAE;KAC3D,CAAC;SACD,SAAS,CAAC,EAAE,YAAY,EAAE,eAAe,CAAC,YAAY,EAAE,CAAC,CAAC;IAE7D,MAAM,aAAa,GAAG,GAAG,EAAE,YAAY,KAAK,gBAAgB,CAAC;IAC7D,MAAM,cAAc,GAAG,aAAa;QAClC,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,GAAG,EAAE,YAAY,KAAK,gBAAgB;YACtC,CAAC,CAAC,IAAI;YACN,CAAC,CAAC,GAAG,EAAE,YAAY,IAAI,IAAI,CAAC;IAEhC,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,CAAC;AAC3C,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,EAAU,EACV,GAAW,EACX,MAAc,EACd,UAAkB,EAClB,QAAiB;IAEjB,MAAM,EAAE;SACL,MAAM,CAAC,eAAe,CAAC;SACvB,MAAM,CAAC;QACN,GAAG;QACH,MAAM;QACN,UAAU;QACV,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC;QACtC,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC;SACD,kBAAkB,CAAC;QAClB,MAAM,EAAE,CAAC,eAAe,CAAC,GAAG,EAAE,eAAe,CAAC,MAAM,CAAC;QACrD,GAAG,EAAE;YACH,YAAY,EAAE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC;YACtC,UAAU;SACX;KACF,CAAC,CAAC;AACP,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,EAAU,EACV,GAAW,EACX,MAAc;IAEd,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,EAAE;SACnB,MAAM,CAAC,EAAE,YAAY,EAAE,eAAe,CAAC,YAAY,EAAE,CAAC;SACtD,IAAI,CAAC,eAAe,CAAC;SACrB,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,eAAe,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;SAC5E,KAAK,CAAC,CAAC,CAAC,CAAC;IACZ,OAAO,GAAG,IAAI,IAAI,CAAC;AACrB,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @usethink/cf-core — Cloudflare Workers 共享内核
|
|
3
|
+
*
|
|
4
|
+
* 统一导出所有模块,支持两种导入方式:
|
|
5
|
+
*
|
|
6
|
+
* 1. 从根导入(适合小项目):
|
|
7
|
+
* import { ok, fail, sha256, verifyTurnstile } from "@usethink/cf-core";
|
|
8
|
+
*
|
|
9
|
+
* 2. 按子路径导入(推荐,tree-shakeable):
|
|
10
|
+
* import { ok, fail } from "@usethink/cf-core/http";
|
|
11
|
+
* import { sha256 } from "@usethink/cf-core/security";
|
|
12
|
+
*/
|
|
13
|
+
export { ok, fail, failRateLimit, getOrigin, safeJsonBody, maskContact, normalizeCode, csvEscape, toCsv } from "./http";
|
|
14
|
+
export { sha256, constantTimeEqual, getIpHash, getClientIp, getBearerToken, verifyTurnstile, buildSecurityHeaders, type SecurityHeadersOptions, } from "./security";
|
|
15
|
+
export { createCache, cache } from "./cache";
|
|
16
|
+
export { MemoryRateLimiter, KvRateLimiter, DbRateLimiter, type RateLimiter } from "./rate-limit";
|
|
17
|
+
export { checkIdempotency, saveIdempotentResponse, getIdempotentResponse } from "./idempotency";
|
|
18
|
+
export { writeAdminAudit, type AuditInput } from "./audit";
|
|
19
|
+
export { SystemConfig, type SystemConfigOptions } from "./config";
|
|
20
|
+
export { classifyError, retryWithBackoff, ErrorType, type RetryOptions } from "./error";
|
|
21
|
+
export { logger, type LogLevel, type LogEntry } from "./logger";
|
|
22
|
+
export { encrypt, decrypt, isEncryptionAvailable, generateUUID } from "./crypto";
|
|
23
|
+
export { initDatabase, initDatabaseWithHealthCheck, getOrCreateClient, createDrizzle, type DrizzleInstance } from "./db/connection";
|
|
24
|
+
export { systemConfig, adminAuditLogs, rateLimitWindows, idempotencyKeys, apiKeys, } from "./db/schema";
|
|
25
|
+
export { signJwt, verifyJwt, extractJwt, type JwtPayload } from "./auth/jwt";
|
|
26
|
+
export { hashPassword, verifyPassword } from "./auth/password";
|
|
27
|
+
export { createAdminAuth, type AdminAuthOptions } from "./middleware/admin-auth";
|
|
28
|
+
export { createApiKeyAuth, extractApiKey, type ApiKeyAuthOptions, type ApiKeyContext } from "./middleware/api-key-auth";
|
|
29
|
+
export { bootstrap, type BootstrapOptions } from "./bootstrap";
|
|
30
|
+
export type { CoreBindings, CoreVariables, CoreEnv, OkResponse, FailResponse, TurnstileResult, RateLimitResult, } from "./types";
|
|
31
|
+
//# sourceMappingURL=index.d.ts.map
|