befly 2.3.3 → 3.0.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/apis/health/info.ts +64 -0
- package/apis/tool/tokenCheck.ts +51 -0
- package/bin/befly.ts +202 -0
- package/checks/conflict.ts +408 -0
- package/checks/{table.js → table.ts} +139 -61
- package/config/env.ts +218 -0
- package/config/reserved.ts +96 -0
- package/main.ts +101 -0
- package/package.json +44 -8
- package/plugins/{db.js → db.ts} +24 -11
- package/plugins/logger.ts +28 -0
- package/plugins/redis.ts +51 -0
- package/plugins/tool.ts +34 -0
- package/scripts/syncDb/apply.ts +171 -0
- package/scripts/syncDb/constants.ts +70 -0
- package/scripts/syncDb/ddl.ts +182 -0
- package/scripts/syncDb/helpers.ts +172 -0
- package/scripts/syncDb/index.ts +215 -0
- package/scripts/syncDb/schema.ts +199 -0
- package/scripts/syncDb/sqlite.ts +50 -0
- package/scripts/syncDb/state.ts +104 -0
- package/scripts/syncDb/table.ts +204 -0
- package/scripts/syncDb/tableCreate.ts +142 -0
- package/scripts/syncDb/tests/constants.test.ts +104 -0
- package/scripts/syncDb/tests/ddl.test.ts +134 -0
- package/scripts/syncDb/tests/helpers.test.ts +70 -0
- package/scripts/syncDb/types.ts +92 -0
- package/scripts/syncDb/version.ts +73 -0
- package/scripts/syncDb.ts +9 -0
- package/scripts/{syncDev.js → syncDev.ts} +41 -25
- package/system.ts +149 -0
- package/tables/_common.json +21 -0
- package/tables/admin.json +10 -0
- package/tsconfig.json +58 -0
- package/types/api.d.ts +246 -0
- package/types/befly.d.ts +234 -0
- package/types/common.d.ts +215 -0
- package/types/context.ts +167 -0
- package/types/crypto.d.ts +23 -0
- package/types/database.d.ts +278 -0
- package/types/index.d.ts +16 -0
- package/types/index.ts +459 -0
- package/types/jwt.d.ts +99 -0
- package/types/logger.d.ts +43 -0
- package/types/plugin.d.ts +109 -0
- package/types/redis.d.ts +44 -0
- package/types/tool.d.ts +67 -0
- package/types/validator.d.ts +45 -0
- package/utils/addonHelper.ts +60 -0
- package/utils/api.ts +23 -0
- package/utils/{colors.js → colors.ts} +79 -21
- package/utils/crypto.ts +308 -0
- package/utils/datetime.ts +51 -0
- package/utils/dbHelper.ts +142 -0
- package/utils/errorHandler.ts +68 -0
- package/utils/index.ts +46 -0
- package/utils/jwt.ts +493 -0
- package/utils/logger.ts +284 -0
- package/utils/objectHelper.ts +68 -0
- package/utils/pluginHelper.ts +62 -0
- package/utils/redisHelper.ts +338 -0
- package/utils/response.ts +38 -0
- package/utils/{sqlBuilder.js → sqlBuilder.ts} +233 -97
- package/utils/sqlHelper.ts +447 -0
- package/utils/tableHelper.ts +167 -0
- package/utils/tool.ts +230 -0
- package/utils/typeHelper.ts +101 -0
- package/utils/validate.ts +451 -0
- package/utils/{xml.js → xml.ts} +100 -74
- package/.npmrc +0 -3
- package/.prettierignore +0 -2
- package/.prettierrc +0 -11
- package/apis/health/info.js +0 -49
- package/apis/tool/tokenCheck.js +0 -29
- package/bin/befly.js +0 -109
- package/config/env.js +0 -64
- package/main.js +0 -579
- package/plugins/logger.js +0 -14
- package/plugins/redis.js +0 -32
- package/plugins/tool.js +0 -8
- package/scripts/syncDb.js +0 -752
- package/system.js +0 -118
- package/tables/common.json +0 -16
- package/tables/tool.json +0 -6
- package/utils/api.js +0 -27
- package/utils/crypto.js +0 -260
- package/utils/index.js +0 -334
- package/utils/jwt.js +0 -387
- package/utils/logger.js +0 -143
- package/utils/redisHelper.js +0 -74
- package/utils/sqlManager.js +0 -471
- package/utils/tool.js +0 -31
- package/utils/validate.js +0 -226
package/types/redis.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redis 相关类型定义
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { RedisClient as BunRedisClient } from 'bun';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Redis 客户端类型
|
|
9
|
+
*/
|
|
10
|
+
export type RedisClient = BunRedisClient;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Redis 键前缀
|
|
14
|
+
*/
|
|
15
|
+
export type RedisKeyPrefix = string;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Redis TTL(秒)
|
|
19
|
+
*/
|
|
20
|
+
export type RedisTTL = number | null;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Redis 助手接口
|
|
24
|
+
*/
|
|
25
|
+
export interface RedisHelper {
|
|
26
|
+
/** 设置对象到 Redis */
|
|
27
|
+
setObject<T = any>(key: string, obj: T, ttl?: RedisTTL): Promise<string | null>;
|
|
28
|
+
/** 从 Redis 获取对象 */
|
|
29
|
+
getObject<T = any>(key: string): Promise<T | null>;
|
|
30
|
+
/** 从 Redis 删除对象 */
|
|
31
|
+
delObject(key: string): Promise<void>;
|
|
32
|
+
/** 生成基于时间的唯一 ID */
|
|
33
|
+
genTimeID(): Promise<number>;
|
|
34
|
+
/** 设置字符串值 */
|
|
35
|
+
setString(key: string, value: string, ttl?: RedisTTL): Promise<string | null>;
|
|
36
|
+
/** 获取字符串值 */
|
|
37
|
+
getString(key: string): Promise<string | null>;
|
|
38
|
+
/** 检查键是否存在 */
|
|
39
|
+
exists(key: string): Promise<number>;
|
|
40
|
+
/** 设置过期时间 */
|
|
41
|
+
expire(key: string, seconds: number): Promise<number>;
|
|
42
|
+
/** 获取剩余过期时间 */
|
|
43
|
+
ttl(key: string): Promise<number>;
|
|
44
|
+
}
|
package/types/tool.d.ts
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工具函数相关类型定义
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 日期格式化类型
|
|
7
|
+
*/
|
|
8
|
+
export type DateFormat = 'YYYY-MM-DD' | 'YYYY-MM-DD HH:mm:ss' | 'HH:mm:ss' | 'YYYY/MM/DD' | 'MM-DD' | string;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 颜色代码
|
|
12
|
+
*/
|
|
13
|
+
export type ColorCode = 'reset' | 'bright' | 'dim' | 'underscore' | 'blink' | 'reverse' | 'hidden' | 'black' | 'red' | 'green' | 'yellow' | 'blue' | 'magenta' | 'cyan' | 'white';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 分页参数
|
|
17
|
+
*/
|
|
18
|
+
export interface PaginationParams {
|
|
19
|
+
/** 页码(从 1 开始) */
|
|
20
|
+
page?: number;
|
|
21
|
+
/** 每页数量 */
|
|
22
|
+
limit?: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 分页结果
|
|
27
|
+
*/
|
|
28
|
+
export interface PaginationResult<T = any> {
|
|
29
|
+
/** 数据列表 */
|
|
30
|
+
list: T[];
|
|
31
|
+
/** 总条数 */
|
|
32
|
+
total: number;
|
|
33
|
+
/** 当前页码 */
|
|
34
|
+
page: number;
|
|
35
|
+
/** 每页数量 */
|
|
36
|
+
limit: number;
|
|
37
|
+
/** 总页数 */
|
|
38
|
+
pages: number;
|
|
39
|
+
/** 是否有下一页 */
|
|
40
|
+
hasNext: boolean;
|
|
41
|
+
/** 是否有上一页 */
|
|
42
|
+
hasPrev: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 文件类型
|
|
47
|
+
*/
|
|
48
|
+
export type FileType = 'image' | 'video' | 'audio' | 'document' | 'archive' | 'other';
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* MIME 类型
|
|
52
|
+
*/
|
|
53
|
+
export type MimeType = string;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 文件上传选项
|
|
57
|
+
*/
|
|
58
|
+
export interface FileUploadOptions {
|
|
59
|
+
/** 允许的文件类型 */
|
|
60
|
+
allowedTypes?: string[];
|
|
61
|
+
/** 最大文件大小(字节) */
|
|
62
|
+
maxSize?: number;
|
|
63
|
+
/** 保存目录 */
|
|
64
|
+
saveDir?: string;
|
|
65
|
+
/** 文件名生成函数 */
|
|
66
|
+
filename?: (originalName: string) => string;
|
|
67
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 验证器相关类型定义
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 字段验证错误信息
|
|
7
|
+
*/
|
|
8
|
+
export type ValidationError = string | null;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 字段类型
|
|
12
|
+
*/
|
|
13
|
+
export type FieldType = 'string' | 'number' | 'text' | 'array';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 验证结果
|
|
17
|
+
*/
|
|
18
|
+
export interface ValidationResult {
|
|
19
|
+
/** 验证状态码:0=成功,1=失败 */
|
|
20
|
+
code: 0 | 1;
|
|
21
|
+
/** 字段验证结果 */
|
|
22
|
+
fields: Record<string, any>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 字段规则
|
|
27
|
+
*/
|
|
28
|
+
export interface FieldRule {
|
|
29
|
+
/** 字段名 */
|
|
30
|
+
name: string;
|
|
31
|
+
/** 字段类型 */
|
|
32
|
+
type: FieldType;
|
|
33
|
+
/** 最小值/长度 */
|
|
34
|
+
min: number | null;
|
|
35
|
+
/** 最大值/长度 */
|
|
36
|
+
max: number | null;
|
|
37
|
+
/** 默认值 */
|
|
38
|
+
default: any;
|
|
39
|
+
/** 是否索引 */
|
|
40
|
+
index: 0 | 1;
|
|
41
|
+
/** 正则约束 */
|
|
42
|
+
regex: string | null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export type FieldType = 'string' | 'number' | 'text' | 'array';
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Addon 辅助工具函数
|
|
3
|
+
* 提供 addon 扫描、路径获取等功能
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import { join } from 'node:path';
|
|
8
|
+
import { getProjectRoot } from '../system.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 获取 addons 目录路径
|
|
12
|
+
*/
|
|
13
|
+
export const getAddonsDir = (): string => {
|
|
14
|
+
return join(getProjectRoot(), 'addons');
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 扫描所有可用的 addon
|
|
19
|
+
* @returns addon 名称数组(过滤掉 _ 开头的目录)
|
|
20
|
+
*/
|
|
21
|
+
export const scanAddons = (): string[] => {
|
|
22
|
+
const addonsDir = getAddonsDir();
|
|
23
|
+
if (!fs.existsSync(addonsDir)) {
|
|
24
|
+
return [];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
return fs
|
|
29
|
+
.readdirSync(addonsDir)
|
|
30
|
+
.filter((name) => {
|
|
31
|
+
const fullPath = join(addonsDir, name);
|
|
32
|
+
const stat = fs.statSync(fullPath);
|
|
33
|
+
const isDir = stat.isDirectory();
|
|
34
|
+
const notSkip = !name.startsWith('_'); // 跳过 _ 开头的目录
|
|
35
|
+
return isDir && notSkip;
|
|
36
|
+
})
|
|
37
|
+
.sort(); // 按字母顺序排序
|
|
38
|
+
} catch (error) {
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 获取 addon 的指定子目录路径
|
|
45
|
+
* @param addonName - addon 名称
|
|
46
|
+
* @param subDir - 子目录名称(apis, checks, plugins, tables, types, config)
|
|
47
|
+
*/
|
|
48
|
+
export const getAddonDir = (addonName: string, subDir: string): string => {
|
|
49
|
+
return join(getAddonsDir(), addonName, subDir);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 检查 addon 是否存在指定子目录
|
|
54
|
+
* @param addonName - addon 名称
|
|
55
|
+
* @param subDir - 子目录名称
|
|
56
|
+
*/
|
|
57
|
+
export const hasAddonDir = (addonName: string, subDir: string): boolean => {
|
|
58
|
+
const dir = getAddonDir(addonName, subDir);
|
|
59
|
+
return fs.existsSync(dir) && fs.statSync(dir).isDirectory();
|
|
60
|
+
};
|
package/utils/api.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API 工具类 - TypeScript 版本
|
|
3
|
+
* 提供 API 路由定义的便捷方法
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { ApiRoute, ApiOptions } from '../types/api.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 定义 API 路由(主函数)
|
|
10
|
+
* @param name - 接口名称
|
|
11
|
+
* @param options - 接口配置选项
|
|
12
|
+
* @returns API 路由定义
|
|
13
|
+
*/
|
|
14
|
+
export function Api(name: string, options: ApiOptions): ApiRoute {
|
|
15
|
+
return {
|
|
16
|
+
method: options.method || 'POST',
|
|
17
|
+
name: name,
|
|
18
|
+
auth: options.auth ?? false,
|
|
19
|
+
fields: options.fields ?? {},
|
|
20
|
+
required: options.required ?? [],
|
|
21
|
+
handler: async (befly, ctx, req) => await options.handler(befly, ctx, req)
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -1,19 +1,68 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* 终端颜色工具 - TypeScript 版本
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 格式化函数类型
|
|
7
|
+
*/
|
|
8
|
+
type Formatter = (input: string | number) => string;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 颜色工具接口
|
|
12
|
+
*/
|
|
13
|
+
interface ColorsInterface {
|
|
14
|
+
isColorSupported: boolean;
|
|
15
|
+
reset: Formatter;
|
|
16
|
+
bold: Formatter;
|
|
17
|
+
dim: Formatter;
|
|
18
|
+
italic: Formatter;
|
|
19
|
+
underline: Formatter;
|
|
20
|
+
inverse: Formatter;
|
|
21
|
+
hidden: Formatter;
|
|
22
|
+
strikethrough: Formatter;
|
|
23
|
+
black: Formatter;
|
|
24
|
+
red: Formatter;
|
|
25
|
+
green: Formatter;
|
|
26
|
+
yellow: Formatter;
|
|
27
|
+
blue: Formatter;
|
|
28
|
+
magenta: Formatter;
|
|
29
|
+
cyan: Formatter;
|
|
30
|
+
white: Formatter;
|
|
31
|
+
gray: Formatter;
|
|
32
|
+
bgBlack: Formatter;
|
|
33
|
+
bgRed: Formatter;
|
|
34
|
+
bgGreen: Formatter;
|
|
35
|
+
bgYellow: Formatter;
|
|
36
|
+
bgBlue: Formatter;
|
|
37
|
+
bgMagenta: Formatter;
|
|
38
|
+
bgCyan: Formatter;
|
|
39
|
+
bgWhite: Formatter;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface Process {
|
|
43
|
+
argv?: string[];
|
|
44
|
+
env?: Record<string, string | undefined>;
|
|
45
|
+
platform?: string;
|
|
46
|
+
stdout?: { isTTY?: boolean };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const p: Process = (typeof process !== 'undefined' ? process : {}) as Process;
|
|
50
|
+
const argv = p.argv || [];
|
|
51
|
+
const env = p.env || {};
|
|
52
|
+
|
|
4
53
|
const isColorSupported = !(!!env.NO_COLOR || argv.includes('--no-color')) && (!!env.FORCE_COLOR || argv.includes('--color') || p.platform === 'win32' || ((p.stdout || {}).isTTY && env.TERM !== 'dumb') || !!env.CI);
|
|
5
54
|
|
|
6
55
|
const formatter =
|
|
7
|
-
(open, close, replace = open) =>
|
|
8
|
-
(input) => {
|
|
9
|
-
|
|
10
|
-
|
|
56
|
+
(open: string, close: string, replace: string = open): Formatter =>
|
|
57
|
+
(input: string | number): string => {
|
|
58
|
+
const string = '' + input;
|
|
59
|
+
const index = string.indexOf(close, open.length);
|
|
11
60
|
return ~index ? open + replaceClose(string, close, replace, index) + close : open + string + close;
|
|
12
61
|
};
|
|
13
62
|
|
|
14
|
-
const replaceClose = (string, close, replace, index) => {
|
|
15
|
-
let result = ''
|
|
16
|
-
|
|
63
|
+
const replaceClose = (string: string, close: string, replace: string, index: number): string => {
|
|
64
|
+
let result = '';
|
|
65
|
+
let cursor = 0;
|
|
17
66
|
do {
|
|
18
67
|
result += string.substring(cursor, index) + replace;
|
|
19
68
|
cursor = index + close.length;
|
|
@@ -22,9 +71,10 @@ const replaceClose = (string, close, replace, index) => {
|
|
|
22
71
|
return result + string.substring(cursor);
|
|
23
72
|
};
|
|
24
73
|
|
|
25
|
-
const createColors = (enabled = isColorSupported) => {
|
|
26
|
-
|
|
27
|
-
|
|
74
|
+
const createColors = (enabled: boolean = isColorSupported): ColorsInterface => {
|
|
75
|
+
const f = enabled ? formatter : (): Formatter => String as Formatter;
|
|
76
|
+
|
|
77
|
+
const baseColors = {
|
|
28
78
|
isColorSupported: enabled,
|
|
29
79
|
reset: f('\x1b[0m', '\x1b[0m'),
|
|
30
80
|
bold: f('\x1b[1m', '\x1b[22m', '\x1b[22m\x1b[1m'),
|
|
@@ -70,14 +120,22 @@ const createColors = (enabled = isColorSupported) => {
|
|
|
70
120
|
bgBlueBright: f('\x1b[104m', '\x1b[49m'),
|
|
71
121
|
bgMagentaBright: f('\x1b[105m', '\x1b[49m'),
|
|
72
122
|
bgCyanBright: f('\x1b[106m', '\x1b[49m'),
|
|
73
|
-
bgWhiteBright: f('\x1b[107m', '\x1b[49m')
|
|
123
|
+
bgWhiteBright: f('\x1b[107m', '\x1b[49m'),
|
|
124
|
+
|
|
125
|
+
info: '',
|
|
126
|
+
success: '',
|
|
127
|
+
warn: '',
|
|
128
|
+
error: ''
|
|
74
129
|
};
|
|
75
|
-
};
|
|
76
130
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
131
|
+
// 添加图标
|
|
132
|
+
baseColors.info = baseColors.blue('i');
|
|
133
|
+
baseColors.success = baseColors.green('√');
|
|
134
|
+
baseColors.warn = baseColors.yellow('‼');
|
|
135
|
+
baseColors.error = baseColors.red('x');
|
|
136
|
+
|
|
137
|
+
return baseColors;
|
|
138
|
+
};
|
|
82
139
|
|
|
83
|
-
export
|
|
140
|
+
export const colors = createColors();
|
|
141
|
+
export { colors as Colors }; // 别名导出,保持向后兼容
|
package/utils/crypto.ts
ADDED
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 加密工具类 - TypeScript 版本
|
|
3
|
+
* 提供各种哈希、HMAC、密码加密等功能
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createSign } from 'node:crypto';
|
|
7
|
+
import type { EncodingType, HashAlgorithm, PasswordHashOptions } from '../types/crypto';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 加密工具类
|
|
11
|
+
*/
|
|
12
|
+
export class Crypto2 {
|
|
13
|
+
/**
|
|
14
|
+
* MD5 哈希
|
|
15
|
+
* @param data - 要哈希的数据
|
|
16
|
+
* @param encoding - 输出编码
|
|
17
|
+
* @returns MD5 哈希值
|
|
18
|
+
*/
|
|
19
|
+
static md5(data: string | Uint8Array, encoding: EncodingType = 'hex'): string {
|
|
20
|
+
const hasher = new Bun.CryptoHasher('md5');
|
|
21
|
+
hasher.update(data);
|
|
22
|
+
return hasher.digest(encoding);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* HMAC-MD5 签名
|
|
27
|
+
* @param key - 密钥
|
|
28
|
+
* @param data - 要签名的数据
|
|
29
|
+
* @param encoding - 输出编码
|
|
30
|
+
* @returns HMAC-MD5 签名
|
|
31
|
+
*/
|
|
32
|
+
static hmacMd5(key: string | Uint8Array, data: string | Uint8Array, encoding: EncodingType = 'hex'): string {
|
|
33
|
+
const hasher = new Bun.CryptoHasher('md5', key);
|
|
34
|
+
hasher.update(data);
|
|
35
|
+
return hasher.digest(encoding);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* SHA-1 哈希
|
|
40
|
+
* @param data - 要哈希的数据
|
|
41
|
+
* @param encoding - 输出编码
|
|
42
|
+
* @returns SHA-1 哈希值
|
|
43
|
+
*/
|
|
44
|
+
static sha1(data: string | Uint8Array, encoding: EncodingType = 'hex'): string {
|
|
45
|
+
const hasher = new Bun.CryptoHasher('sha1');
|
|
46
|
+
hasher.update(data);
|
|
47
|
+
return hasher.digest(encoding);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* HMAC-SHA1 签名
|
|
52
|
+
* @param key - 密钥
|
|
53
|
+
* @param data - 要签名的数据
|
|
54
|
+
* @param encoding - 输出编码
|
|
55
|
+
* @returns HMAC-SHA1 签名
|
|
56
|
+
*/
|
|
57
|
+
static hmacSha1(key: string | Uint8Array, data: string | Uint8Array, encoding: EncodingType = 'hex'): string {
|
|
58
|
+
const hasher = new Bun.CryptoHasher('sha1', key);
|
|
59
|
+
hasher.update(data);
|
|
60
|
+
return hasher.digest(encoding);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* SHA-256 哈希
|
|
65
|
+
* @param data - 要哈希的数据
|
|
66
|
+
* @param encoding - 输出编码
|
|
67
|
+
* @returns SHA-256 哈希值
|
|
68
|
+
*/
|
|
69
|
+
static sha256(data: string | Uint8Array, encoding: EncodingType = 'hex'): string {
|
|
70
|
+
const hasher = new Bun.CryptoHasher('sha256');
|
|
71
|
+
hasher.update(data);
|
|
72
|
+
return hasher.digest(encoding);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* RSA-SHA256 签名
|
|
77
|
+
* @param data - 要签名的数据
|
|
78
|
+
* @param privateKey - 私钥
|
|
79
|
+
* @param encoding - 输出编码
|
|
80
|
+
* @returns RSA-SHA256 签名
|
|
81
|
+
*/
|
|
82
|
+
static rsaSha256(data: string, privateKey: string | Buffer, encoding: BufferEncoding = 'hex'): string {
|
|
83
|
+
const sign = createSign('RSA-SHA256');
|
|
84
|
+
sign.update(data);
|
|
85
|
+
const signature = sign.sign(privateKey, encoding);
|
|
86
|
+
return signature;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* HMAC-SHA256 签名
|
|
91
|
+
* @param key - 密钥
|
|
92
|
+
* @param data - 要签名的数据
|
|
93
|
+
* @param encoding - 输出编码
|
|
94
|
+
* @returns HMAC-SHA256 签名
|
|
95
|
+
*/
|
|
96
|
+
static hmacSha256(key: string | Uint8Array, data: string | Uint8Array, encoding: EncodingType = 'hex'): string {
|
|
97
|
+
const hasher = new Bun.CryptoHasher('sha256', key);
|
|
98
|
+
hasher.update(data);
|
|
99
|
+
return hasher.digest(encoding);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* SHA-512 哈希
|
|
104
|
+
* @param data - 要哈希的数据
|
|
105
|
+
* @param encoding - 输出编码
|
|
106
|
+
* @returns SHA-512 哈希值
|
|
107
|
+
*/
|
|
108
|
+
static sha512(data: string | Uint8Array, encoding: EncodingType = 'hex'): string {
|
|
109
|
+
const hasher = new Bun.CryptoHasher('sha512');
|
|
110
|
+
hasher.update(data);
|
|
111
|
+
return hasher.digest(encoding);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* HMAC-SHA512 签名
|
|
116
|
+
* @param key - 密钥
|
|
117
|
+
* @param data - 要签名的数据
|
|
118
|
+
* @param encoding - 输出编码
|
|
119
|
+
* @returns HMAC-SHA512 签名
|
|
120
|
+
*/
|
|
121
|
+
static hmacSha512(key: string | Uint8Array, data: string | Uint8Array, encoding: EncodingType = 'hex'): string {
|
|
122
|
+
const hasher = new Bun.CryptoHasher('sha512', key);
|
|
123
|
+
hasher.update(data);
|
|
124
|
+
return hasher.digest(encoding);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* 通用哈希方法
|
|
129
|
+
* @param algorithm - 算法名称
|
|
130
|
+
* @param data - 要哈希的数据
|
|
131
|
+
* @param encoding - 输出编码
|
|
132
|
+
* @returns 哈希值
|
|
133
|
+
*/
|
|
134
|
+
static hash(algorithm: HashAlgorithm, data: string | Uint8Array, encoding: EncodingType = 'hex'): string {
|
|
135
|
+
const hasher = new Bun.CryptoHasher(algorithm);
|
|
136
|
+
hasher.update(data);
|
|
137
|
+
return hasher.digest(encoding);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* 通用 HMAC 方法
|
|
142
|
+
* @param algorithm - 算法名称
|
|
143
|
+
* @param key - 密钥
|
|
144
|
+
* @param data - 要签名的数据
|
|
145
|
+
* @param encoding - 输出编码
|
|
146
|
+
* @returns HMAC 签名
|
|
147
|
+
*/
|
|
148
|
+
static hmac(algorithm: HashAlgorithm, key: string | Uint8Array, data: string | Uint8Array, encoding: EncodingType = 'hex'): string {
|
|
149
|
+
const hasher = new Bun.CryptoHasher(algorithm, key);
|
|
150
|
+
hasher.update(data);
|
|
151
|
+
return hasher.digest(encoding);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* 文件哈希
|
|
156
|
+
* @param filePath - 文件路径
|
|
157
|
+
* @param algorithm - 算法名称
|
|
158
|
+
* @param encoding - 输出编码
|
|
159
|
+
* @returns 文件哈希值
|
|
160
|
+
*/
|
|
161
|
+
static async hashFile(filePath: string, algorithm: HashAlgorithm = 'sha256', encoding: EncodingType = 'hex'): Promise<string> {
|
|
162
|
+
const file = Bun.file(filePath);
|
|
163
|
+
const hasher = new Bun.CryptoHasher(algorithm);
|
|
164
|
+
|
|
165
|
+
const stream = file.stream();
|
|
166
|
+
const reader = stream.getReader();
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
while (true) {
|
|
170
|
+
const { done, value } = await reader.read();
|
|
171
|
+
if (done) break;
|
|
172
|
+
hasher.update(value);
|
|
173
|
+
}
|
|
174
|
+
return hasher.digest(encoding);
|
|
175
|
+
} finally {
|
|
176
|
+
reader.releaseLock();
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* 创建流式哈希器
|
|
182
|
+
* @param algorithm - 算法名称
|
|
183
|
+
* @param key - 可选的 HMAC 密钥
|
|
184
|
+
* @returns 流式哈希器实例
|
|
185
|
+
*/
|
|
186
|
+
static createHasher(algorithm: HashAlgorithm, key: string | Uint8Array | null = null): StreamHasher {
|
|
187
|
+
return new StreamHasher(algorithm, key);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* 密码哈希 (使用 Argon2)
|
|
192
|
+
* @param password - 密码
|
|
193
|
+
* @param options - 选项
|
|
194
|
+
* @returns 哈希后的密码
|
|
195
|
+
*/
|
|
196
|
+
static async hashPassword(password: string, options: PasswordHashOptions = {}): Promise<string> {
|
|
197
|
+
// 设置默认算法为 bcrypt
|
|
198
|
+
const finalOptions = {
|
|
199
|
+
algorithm: 'bcrypt',
|
|
200
|
+
...options
|
|
201
|
+
} as any;
|
|
202
|
+
return await Bun.password.hash(password, finalOptions);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* 验证密码
|
|
207
|
+
* @param password - 原始密码
|
|
208
|
+
* @param hash - 哈希值
|
|
209
|
+
* @returns 验证结果
|
|
210
|
+
*/
|
|
211
|
+
static async verifyPassword(password: string, hash: string): Promise<boolean> {
|
|
212
|
+
return await Bun.password.verify(password, hash);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Base64 编码
|
|
217
|
+
* @param data - 要编码的数据
|
|
218
|
+
* @returns Base64 编码的字符串
|
|
219
|
+
*/
|
|
220
|
+
static base64Encode(data: string): string {
|
|
221
|
+
return Buffer.from(data, 'utf8').toString('base64');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Base64 解码
|
|
226
|
+
* @param data - Base64 编码的字符串
|
|
227
|
+
* @returns 解码后的字符串
|
|
228
|
+
*/
|
|
229
|
+
static base64Decode(data: string): string {
|
|
230
|
+
return Buffer.from(data, 'base64').toString('utf8');
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* 生成随机十六进制字符串
|
|
235
|
+
* @param length - 字符串长度
|
|
236
|
+
* @returns 随机十六进制字符串
|
|
237
|
+
*/
|
|
238
|
+
static randomString(length: number): string {
|
|
239
|
+
const bytes = Math.ceil(length / 2);
|
|
240
|
+
const randomBytes = crypto.getRandomValues(new Uint8Array(bytes));
|
|
241
|
+
let result = '';
|
|
242
|
+
for (let i = 0; i < randomBytes.length; i++) {
|
|
243
|
+
result += randomBytes[i].toString(16).padStart(2, '0');
|
|
244
|
+
}
|
|
245
|
+
return result.slice(0, length);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* 快速哈希 (非密码学)
|
|
250
|
+
* @param data - 数据
|
|
251
|
+
* @param seed - 种子值
|
|
252
|
+
* @returns 64位哈希值
|
|
253
|
+
*/
|
|
254
|
+
static fastHash(data: string | Uint8Array, seed: number = 0): number {
|
|
255
|
+
return Bun.hash(data, seed);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* 流式哈希器类
|
|
261
|
+
*/
|
|
262
|
+
export class StreamHasher {
|
|
263
|
+
private hasher: any;
|
|
264
|
+
private finalized: boolean = false;
|
|
265
|
+
|
|
266
|
+
constructor(algorithm: HashAlgorithm, key: string | Uint8Array | null = null) {
|
|
267
|
+
this.hasher = new Bun.CryptoHasher(algorithm, key);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* 更新数据
|
|
272
|
+
* @param data - 数据
|
|
273
|
+
* @returns 支持链式调用
|
|
274
|
+
*/
|
|
275
|
+
update(data: string | Uint8Array): this {
|
|
276
|
+
if (this.finalized) {
|
|
277
|
+
throw new Error('哈希器已经完成,不能再更新数据');
|
|
278
|
+
}
|
|
279
|
+
this.hasher.update(data);
|
|
280
|
+
return this;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* 生成最终哈希值
|
|
285
|
+
* @param encoding - 输出编码
|
|
286
|
+
* @returns 哈希值
|
|
287
|
+
*/
|
|
288
|
+
digest(encoding: EncodingType = 'hex'): string {
|
|
289
|
+
if (this.finalized) {
|
|
290
|
+
throw new Error('哈希器已经完成');
|
|
291
|
+
}
|
|
292
|
+
this.finalized = true;
|
|
293
|
+
return this.hasher.digest(encoding);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* 复制哈希器
|
|
298
|
+
* @returns 新的哈希器实例
|
|
299
|
+
*/
|
|
300
|
+
copy(): StreamHasher {
|
|
301
|
+
if (this.finalized) {
|
|
302
|
+
throw new Error('不能复制已完成的哈希器');
|
|
303
|
+
}
|
|
304
|
+
const newHasher = new StreamHasher('md5'); // 临时算法
|
|
305
|
+
newHasher.hasher = this.hasher.copy();
|
|
306
|
+
return newHasher;
|
|
307
|
+
}
|
|
308
|
+
}
|