befly 3.16.4 → 3.16.6
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/dist/befly.js +318 -93
- package/dist/befly.min.js +16 -16
- package/dist/index.js +57 -16
- package/dist/lib/cacheHelper.d.ts +33 -0
- package/dist/lib/cacheHelper.js +185 -0
- package/dist/lib/cacheKeys.d.ts +6 -0
- package/dist/lib/cacheKeys.js +8 -0
- package/dist/lib/dbHelper.d.ts +2 -0
- package/dist/lib/dbHelper.js +74 -4
- package/dist/lib/redisHelper.d.ts +8 -0
- package/dist/lib/redisHelper.js +57 -0
- package/dist/sync/syncCache.js +2 -0
- package/dist/types/cache.d.ts +27 -0
- package/dist/types/database.d.ts +6 -0
- package/dist/types/redis.d.ts +10 -0
- package/package.json +2 -2
- package/dist/utils/convertBigIntFields.d.ts +0 -12
- package/dist/utils/convertBigIntFields.js +0 -53
package/dist/befly.js
CHANGED
|
@@ -9833,6 +9833,7 @@ async function syncCache(ctx) {
|
|
|
9833
9833
|
await ctx.cache.cacheApis();
|
|
9834
9834
|
await ctx.cache.cacheMenus();
|
|
9835
9835
|
await ctx.cache.rebuildRoleApiPermissions();
|
|
9836
|
+
await ctx.cache.rebuildRoleMenuPermissions();
|
|
9836
9837
|
}
|
|
9837
9838
|
|
|
9838
9839
|
// lib/cipher.ts
|
|
@@ -11482,32 +11483,6 @@ var calcPerfTime = (startTime, endTime = Bun.nanoseconds()) => {
|
|
|
11482
11483
|
}
|
|
11483
11484
|
};
|
|
11484
11485
|
|
|
11485
|
-
// utils/processInfo.ts
|
|
11486
|
-
function getProcessRole(env) {
|
|
11487
|
-
const runtimeEnv = env || {};
|
|
11488
|
-
const bunWorkerId = runtimeEnv["BUN_WORKER_ID"];
|
|
11489
|
-
const pm2InstanceId = runtimeEnv["PM2_INSTANCE_ID"];
|
|
11490
|
-
if (bunWorkerId !== undefined) {
|
|
11491
|
-
return {
|
|
11492
|
-
role: bunWorkerId === "" ? "primary" : "worker",
|
|
11493
|
-
instanceId: bunWorkerId || "0",
|
|
11494
|
-
env: "bun-cluster"
|
|
11495
|
-
};
|
|
11496
|
-
}
|
|
11497
|
-
if (pm2InstanceId !== undefined) {
|
|
11498
|
-
return {
|
|
11499
|
-
role: pm2InstanceId === "0" ? "primary" : "worker",
|
|
11500
|
-
instanceId: pm2InstanceId,
|
|
11501
|
-
env: "pm2-cluster"
|
|
11502
|
-
};
|
|
11503
|
-
}
|
|
11504
|
-
return {
|
|
11505
|
-
role: "primary",
|
|
11506
|
-
instanceId: null,
|
|
11507
|
-
env: "standalone"
|
|
11508
|
-
};
|
|
11509
|
-
}
|
|
11510
|
-
|
|
11511
11486
|
// utils/scanSources.ts
|
|
11512
11487
|
init_dist();
|
|
11513
11488
|
|
|
@@ -13109,6 +13084,9 @@ class CacheKeys {
|
|
|
13109
13084
|
static roleInfo(roleCode) {
|
|
13110
13085
|
return `role:info:${roleCode}`;
|
|
13111
13086
|
}
|
|
13087
|
+
static roleMenus(roleCode) {
|
|
13088
|
+
return `role:menus:${roleCode}`;
|
|
13089
|
+
}
|
|
13112
13090
|
static roleApis(roleCode) {
|
|
13113
13091
|
return `role:apis:${roleCode}`;
|
|
13114
13092
|
}
|
|
@@ -13694,6 +13672,25 @@ class CacheHelper {
|
|
|
13694
13672
|
}
|
|
13695
13673
|
return trimmed;
|
|
13696
13674
|
}
|
|
13675
|
+
assertMenuPathname(value, errorPrefix) {
|
|
13676
|
+
if (typeof value !== "string") {
|
|
13677
|
+
throw new Error(`${errorPrefix} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32`);
|
|
13678
|
+
}
|
|
13679
|
+
const trimmed = value.trim();
|
|
13680
|
+
if (!trimmed) {
|
|
13681
|
+
throw new Error(`${errorPrefix} \u4E0D\u5141\u8BB8\u4E3A\u7A7A\u5B57\u7B26\u4E32`);
|
|
13682
|
+
}
|
|
13683
|
+
if (!trimmed.startsWith("/")) {
|
|
13684
|
+
throw new Error(`${errorPrefix} \u5FC5\u987B\u662F pathname\uFF08\u4EE5 / \u5F00\u5934\uFF09`);
|
|
13685
|
+
}
|
|
13686
|
+
if (trimmed.includes(" ")) {
|
|
13687
|
+
throw new Error(`${errorPrefix} \u4E0D\u5141\u8BB8\u5305\u542B\u7A7A\u683C`);
|
|
13688
|
+
}
|
|
13689
|
+
if (trimmed.length > 1 && trimmed.endsWith("/")) {
|
|
13690
|
+
throw new Error(`${errorPrefix} \u4E0D\u5141\u8BB8\u4EE5 / \u7ED3\u5C3E`);
|
|
13691
|
+
}
|
|
13692
|
+
return trimmed;
|
|
13693
|
+
}
|
|
13697
13694
|
assertApiPathList(value, roleCode) {
|
|
13698
13695
|
if (value === null || value === undefined)
|
|
13699
13696
|
return [];
|
|
@@ -13721,6 +13718,33 @@ class CacheHelper {
|
|
|
13721
13718
|
}
|
|
13722
13719
|
return out;
|
|
13723
13720
|
}
|
|
13721
|
+
assertMenuPathList(value, roleCode) {
|
|
13722
|
+
if (value === null || value === undefined)
|
|
13723
|
+
return [];
|
|
13724
|
+
let list = value;
|
|
13725
|
+
if (typeof list === "string") {
|
|
13726
|
+
const trimmed = list.trim();
|
|
13727
|
+
if (trimmed === "" || trimmed === "null") {
|
|
13728
|
+
return [];
|
|
13729
|
+
}
|
|
13730
|
+
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
13731
|
+
try {
|
|
13732
|
+
list = JSON.parse(trimmed);
|
|
13733
|
+
} catch {
|
|
13734
|
+
throw new Error(`\u89D2\u8272\u83DC\u5355\u6743\u9650\u6570\u636E\u4E0D\u5408\u6CD5\uFF1Aaddon_admin_role.menus JSON \u89E3\u6790\u5931\u8D25\uFF0CroleCode=${roleCode}`);
|
|
13735
|
+
}
|
|
13736
|
+
}
|
|
13737
|
+
}
|
|
13738
|
+
if (!Array.isArray(list)) {
|
|
13739
|
+
const typeLabel = typeof list;
|
|
13740
|
+
throw new Error(`\u89D2\u8272\u83DC\u5355\u6743\u9650\u6570\u636E\u4E0D\u5408\u6CD5\uFF1Aaddon_admin_role.menus \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u6570\u7EC4\u6216 JSON \u6570\u7EC4\u5B57\u7B26\u4E32\uFF0CroleCode=${roleCode}\uFF0Ctype=${typeLabel}`);
|
|
13741
|
+
}
|
|
13742
|
+
const out = [];
|
|
13743
|
+
for (const item of list) {
|
|
13744
|
+
out.push(this.assertMenuPathname(item, `\u89D2\u8272\u83DC\u5355\u6743\u9650\u6570\u636E\u4E0D\u5408\u6CD5\uFF1Aaddon_admin_role.menus \u5143\u7D20\uFF0CroleCode=${roleCode}`));
|
|
13745
|
+
}
|
|
13746
|
+
return out;
|
|
13747
|
+
}
|
|
13724
13748
|
async cacheApis() {
|
|
13725
13749
|
try {
|
|
13726
13750
|
const tableExists = await this.db.tableExists("addon_admin_api");
|
|
@@ -13807,6 +13831,56 @@ class CacheHelper {
|
|
|
13807
13831
|
});
|
|
13808
13832
|
}
|
|
13809
13833
|
}
|
|
13834
|
+
async rebuildRoleMenuPermissions() {
|
|
13835
|
+
try {
|
|
13836
|
+
const roleTableExists = await this.db.tableExists("addon_admin_role");
|
|
13837
|
+
if (!roleTableExists.data) {
|
|
13838
|
+
Logger.warn("\u26A0\uFE0F \u89D2\u8272\u8868\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u89D2\u8272\u83DC\u5355\u6743\u9650\u7F13\u5B58");
|
|
13839
|
+
return;
|
|
13840
|
+
}
|
|
13841
|
+
const roles = await this.db.getAll({
|
|
13842
|
+
table: "addon_admin_role",
|
|
13843
|
+
fields: ["code", "menus"]
|
|
13844
|
+
});
|
|
13845
|
+
const roleMenuPathsMap = new Map;
|
|
13846
|
+
for (const role of roles.data.lists) {
|
|
13847
|
+
if (!role?.code)
|
|
13848
|
+
continue;
|
|
13849
|
+
const menuPaths = this.assertMenuPathList(role.menus, role.code);
|
|
13850
|
+
roleMenuPathsMap.set(role.code, menuPaths);
|
|
13851
|
+
}
|
|
13852
|
+
const roleCodes = Array.from(roleMenuPathsMap.keys());
|
|
13853
|
+
if (roleCodes.length === 0) {
|
|
13854
|
+
Logger.info("\u2705 \u6CA1\u6709\u9700\u8981\u7F13\u5B58\u7684\u89D2\u8272\u83DC\u5355\u6743\u9650");
|
|
13855
|
+
return;
|
|
13856
|
+
}
|
|
13857
|
+
const roleKeys = roleCodes.map((code) => CacheKeys.roleMenus(code));
|
|
13858
|
+
await this.redis.delBatch(roleKeys);
|
|
13859
|
+
const items = [];
|
|
13860
|
+
for (const roleCode of roleCodes) {
|
|
13861
|
+
const menuPaths = roleMenuPathsMap.get(roleCode) || [];
|
|
13862
|
+
const members = Array.from(new Set(menuPaths)).sort();
|
|
13863
|
+
if (members.length > 0) {
|
|
13864
|
+
items.push({ key: CacheKeys.roleMenus(roleCode), members });
|
|
13865
|
+
}
|
|
13866
|
+
}
|
|
13867
|
+
if (items.length > 0) {
|
|
13868
|
+
await this.redis.saddBatch(items);
|
|
13869
|
+
}
|
|
13870
|
+
} catch (error) {
|
|
13871
|
+
Logger.error({ err: error, msg: "\u26A0\uFE0F \u89D2\u8272\u83DC\u5355\u6743\u9650\u7F13\u5B58\u5F02\u5E38\uFF08\u5C06\u963B\u65AD\u542F\u52A8\uFF09" });
|
|
13872
|
+
throw new CoreError({
|
|
13873
|
+
kind: "runtime",
|
|
13874
|
+
message: "\u26A0\uFE0F \u89D2\u8272\u83DC\u5355\u6743\u9650\u7F13\u5B58\u5F02\u5E38\uFF08\u5C06\u963B\u65AD\u542F\u52A8\uFF09",
|
|
13875
|
+
logged: true,
|
|
13876
|
+
cause: error,
|
|
13877
|
+
meta: {
|
|
13878
|
+
subsystem: "cache",
|
|
13879
|
+
operation: "rebuildRoleMenuPermissions"
|
|
13880
|
+
}
|
|
13881
|
+
});
|
|
13882
|
+
}
|
|
13883
|
+
}
|
|
13810
13884
|
async refreshRoleApiPermissions(roleCode, apiPaths) {
|
|
13811
13885
|
if (!roleCode || typeof roleCode !== "string") {
|
|
13812
13886
|
throw new Error("roleCode \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");
|
|
@@ -13826,10 +13900,30 @@ class CacheHelper {
|
|
|
13826
13900
|
await this.redis.sadd(roleKey, members);
|
|
13827
13901
|
}
|
|
13828
13902
|
}
|
|
13903
|
+
async refreshRoleMenuPermissions(roleCode, menuPaths) {
|
|
13904
|
+
if (!roleCode || typeof roleCode !== "string") {
|
|
13905
|
+
throw new Error("roleCode \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");
|
|
13906
|
+
}
|
|
13907
|
+
if (!Array.isArray(menuPaths)) {
|
|
13908
|
+
throw new Error("menuPaths \u5FC5\u987B\u662F\u6570\u7EC4");
|
|
13909
|
+
}
|
|
13910
|
+
const normalizedPaths = menuPaths.map((p) => this.assertMenuPathname(p, `refreshRoleMenuPermissions: menuPaths \u5143\u7D20\uFF0CroleCode=${roleCode}`));
|
|
13911
|
+
const roleKey = CacheKeys.roleMenus(roleCode);
|
|
13912
|
+
if (normalizedPaths.length === 0) {
|
|
13913
|
+
await this.redis.del(roleKey);
|
|
13914
|
+
return;
|
|
13915
|
+
}
|
|
13916
|
+
const members = Array.from(new Set(normalizedPaths));
|
|
13917
|
+
await this.redis.del(roleKey);
|
|
13918
|
+
if (members.length > 0) {
|
|
13919
|
+
await this.redis.sadd(roleKey, members);
|
|
13920
|
+
}
|
|
13921
|
+
}
|
|
13829
13922
|
async cacheAll() {
|
|
13830
13923
|
await this.cacheApis();
|
|
13831
13924
|
await this.cacheMenus();
|
|
13832
13925
|
await this.rebuildRoleApiPermissions();
|
|
13926
|
+
await this.rebuildRoleMenuPermissions();
|
|
13833
13927
|
}
|
|
13834
13928
|
async getApis() {
|
|
13835
13929
|
try {
|
|
@@ -13858,6 +13952,15 @@ class CacheHelper {
|
|
|
13858
13952
|
return [];
|
|
13859
13953
|
}
|
|
13860
13954
|
}
|
|
13955
|
+
async getRoleMenuPermissions(roleCode) {
|
|
13956
|
+
try {
|
|
13957
|
+
const permissions = await this.redis.smembers(CacheKeys.roleMenus(roleCode));
|
|
13958
|
+
return permissions || [];
|
|
13959
|
+
} catch (error) {
|
|
13960
|
+
Logger.error({ err: error, roleCode, msg: "\u83B7\u53D6\u89D2\u8272\u83DC\u5355\u6743\u9650\u7F13\u5B58\u5931\u8D25" });
|
|
13961
|
+
return [];
|
|
13962
|
+
}
|
|
13963
|
+
}
|
|
13861
13964
|
async checkRolePermission(roleCode, apiPath) {
|
|
13862
13965
|
try {
|
|
13863
13966
|
const pathname = this.assertApiPathname(apiPath, "checkRolePermission: apiPath");
|
|
@@ -13867,6 +13970,15 @@ class CacheHelper {
|
|
|
13867
13970
|
return false;
|
|
13868
13971
|
}
|
|
13869
13972
|
}
|
|
13973
|
+
async checkRoleMenuPermission(roleCode, menuPath) {
|
|
13974
|
+
try {
|
|
13975
|
+
const pathname = this.assertMenuPathname(menuPath, "checkRoleMenuPermission: menuPath");
|
|
13976
|
+
return await this.redis.sismember(CacheKeys.roleMenus(roleCode), pathname);
|
|
13977
|
+
} catch (error) {
|
|
13978
|
+
Logger.error({ err: error, roleCode, msg: "\u68C0\u67E5\u89D2\u8272\u83DC\u5355\u6743\u9650\u5931\u8D25" });
|
|
13979
|
+
return false;
|
|
13980
|
+
}
|
|
13981
|
+
}
|
|
13870
13982
|
async deleteRolePermissions(roleCode) {
|
|
13871
13983
|
try {
|
|
13872
13984
|
const result = await this.redis.del(CacheKeys.roleApis(roleCode));
|
|
@@ -13880,6 +13992,19 @@ class CacheHelper {
|
|
|
13880
13992
|
return false;
|
|
13881
13993
|
}
|
|
13882
13994
|
}
|
|
13995
|
+
async deleteRoleMenuPermissions(roleCode) {
|
|
13996
|
+
try {
|
|
13997
|
+
const result = await this.redis.del(CacheKeys.roleMenus(roleCode));
|
|
13998
|
+
if (result > 0) {
|
|
13999
|
+
Logger.info(`\u2705 \u5DF2\u5220\u9664\u89D2\u8272 ${roleCode} \u7684\u83DC\u5355\u6743\u9650\u7F13\u5B58`);
|
|
14000
|
+
return true;
|
|
14001
|
+
}
|
|
14002
|
+
return false;
|
|
14003
|
+
} catch (error) {
|
|
14004
|
+
Logger.error({ err: error, roleCode, msg: "\u5220\u9664\u89D2\u8272\u83DC\u5355\u6743\u9650\u7F13\u5B58\u5931\u8D25" });
|
|
14005
|
+
return false;
|
|
14006
|
+
}
|
|
14007
|
+
}
|
|
13883
14008
|
}
|
|
13884
14009
|
|
|
13885
14010
|
// plugins/cache.ts
|
|
@@ -13921,57 +14046,6 @@ var configPlugin = {
|
|
|
13921
14046
|
};
|
|
13922
14047
|
var config_default = configPlugin;
|
|
13923
14048
|
|
|
13924
|
-
// utils/convertBigIntFields.ts
|
|
13925
|
-
function convertBigIntFields(arr, fields = ["id", "pid", "sort"]) {
|
|
13926
|
-
if (arr === null || arr === undefined) {
|
|
13927
|
-
return arr;
|
|
13928
|
-
}
|
|
13929
|
-
const MAX_SAFE_INTEGER_BIGINT = BigInt(Number.MAX_SAFE_INTEGER);
|
|
13930
|
-
const MIN_SAFE_INTEGER_BIGINT = BigInt(Number.MIN_SAFE_INTEGER);
|
|
13931
|
-
const convertRecord = (source) => {
|
|
13932
|
-
const converted = {};
|
|
13933
|
-
for (const [key, value] of Object.entries(source)) {
|
|
13934
|
-
converted[key] = value;
|
|
13935
|
-
}
|
|
13936
|
-
for (const [key, value] of Object.entries(converted)) {
|
|
13937
|
-
if (value === undefined || value === null) {
|
|
13938
|
-
continue;
|
|
13939
|
-
}
|
|
13940
|
-
const shouldConvert = fields.includes(key) || key.endsWith("Id") || key.endsWith("_id") || key.endsWith("At") || key.endsWith("_at");
|
|
13941
|
-
if (!shouldConvert) {
|
|
13942
|
-
continue;
|
|
13943
|
-
}
|
|
13944
|
-
let bigintValue = null;
|
|
13945
|
-
if (typeof value === "bigint") {
|
|
13946
|
-
bigintValue = value;
|
|
13947
|
-
} else if (typeof value === "string") {
|
|
13948
|
-
if (!/^-?\d+$/.test(value)) {
|
|
13949
|
-
continue;
|
|
13950
|
-
}
|
|
13951
|
-
try {
|
|
13952
|
-
bigintValue = BigInt(value);
|
|
13953
|
-
} catch {
|
|
13954
|
-
continue;
|
|
13955
|
-
}
|
|
13956
|
-
} else {
|
|
13957
|
-
continue;
|
|
13958
|
-
}
|
|
13959
|
-
if (bigintValue > MAX_SAFE_INTEGER_BIGINT || bigintValue < MIN_SAFE_INTEGER_BIGINT) {
|
|
13960
|
-
throw new Error(`BIGINT \u5B57\u6BB5\u8D85\u51FA JS \u5B89\u5168\u6574\u6570\u8303\u56F4\uFF0C\u8BF7\u6539\u7528 bigint/string \u6216\u8C03\u6574\u5B57\u6BB5\u8BBE\u8BA1 (field: ${key}, value: ${String(value)})`);
|
|
13961
|
-
}
|
|
13962
|
-
converted[key] = Number(bigintValue);
|
|
13963
|
-
}
|
|
13964
|
-
return converted;
|
|
13965
|
-
};
|
|
13966
|
-
if (Array.isArray(arr)) {
|
|
13967
|
-
return arr.map((item) => convertRecord(item));
|
|
13968
|
-
}
|
|
13969
|
-
if (typeof arr === "object") {
|
|
13970
|
-
return convertRecord(arr);
|
|
13971
|
-
}
|
|
13972
|
-
return arr;
|
|
13973
|
-
}
|
|
13974
|
-
|
|
13975
14049
|
// lib/dbHelper.ts
|
|
13976
14050
|
init_util();
|
|
13977
14051
|
|
|
@@ -15350,6 +15424,72 @@ class DbHelper {
|
|
|
15350
15424
|
sql = null;
|
|
15351
15425
|
isTransaction = false;
|
|
15352
15426
|
idMode;
|
|
15427
|
+
static convertBigIntFields(arr, fields) {
|
|
15428
|
+
if (arr === null || arr === undefined) {
|
|
15429
|
+
return arr;
|
|
15430
|
+
}
|
|
15431
|
+
const defaultFields = ["id", "pid", "sort"];
|
|
15432
|
+
const buildFields = (userFields) => {
|
|
15433
|
+
if (!userFields || userFields.length === 0) {
|
|
15434
|
+
return defaultFields;
|
|
15435
|
+
}
|
|
15436
|
+
const merged = ["id", "pid", "sort"];
|
|
15437
|
+
for (const f of userFields) {
|
|
15438
|
+
if (typeof f !== "string") {
|
|
15439
|
+
continue;
|
|
15440
|
+
}
|
|
15441
|
+
const trimmed = f.trim();
|
|
15442
|
+
if (trimmed.length === 0) {
|
|
15443
|
+
continue;
|
|
15444
|
+
}
|
|
15445
|
+
if (!merged.includes(trimmed)) {
|
|
15446
|
+
merged.push(trimmed);
|
|
15447
|
+
}
|
|
15448
|
+
}
|
|
15449
|
+
return merged;
|
|
15450
|
+
};
|
|
15451
|
+
const effectiveFields = buildFields(fields);
|
|
15452
|
+
const fieldSet = new Set(effectiveFields);
|
|
15453
|
+
const MAX_SAFE_INTEGER_BIGINT = BigInt(Number.MAX_SAFE_INTEGER);
|
|
15454
|
+
const MIN_SAFE_INTEGER_BIGINT = BigInt(Number.MIN_SAFE_INTEGER);
|
|
15455
|
+
const convertRecord = (source) => {
|
|
15456
|
+
const converted = {};
|
|
15457
|
+
for (const [key, value] of Object.entries(source)) {
|
|
15458
|
+
let nextValue = value;
|
|
15459
|
+
if (value !== undefined && value !== null) {
|
|
15460
|
+
const shouldConvert = fieldSet.has(key) || key.endsWith("Id") || key.endsWith("_id") || key.endsWith("At") || key.endsWith("_at");
|
|
15461
|
+
if (shouldConvert) {
|
|
15462
|
+
let bigintValue = null;
|
|
15463
|
+
if (typeof value === "bigint") {
|
|
15464
|
+
bigintValue = value;
|
|
15465
|
+
} else if (typeof value === "string") {
|
|
15466
|
+
if (/^-?\d+$/.test(value)) {
|
|
15467
|
+
try {
|
|
15468
|
+
bigintValue = BigInt(value);
|
|
15469
|
+
} catch {
|
|
15470
|
+
bigintValue = null;
|
|
15471
|
+
}
|
|
15472
|
+
}
|
|
15473
|
+
}
|
|
15474
|
+
if (bigintValue !== null) {
|
|
15475
|
+
if (bigintValue <= MAX_SAFE_INTEGER_BIGINT && bigintValue >= MIN_SAFE_INTEGER_BIGINT) {
|
|
15476
|
+
nextValue = Number(bigintValue);
|
|
15477
|
+
}
|
|
15478
|
+
}
|
|
15479
|
+
}
|
|
15480
|
+
}
|
|
15481
|
+
converted[key] = nextValue;
|
|
15482
|
+
}
|
|
15483
|
+
return converted;
|
|
15484
|
+
};
|
|
15485
|
+
if (Array.isArray(arr)) {
|
|
15486
|
+
return arr.map((item) => convertRecord(item));
|
|
15487
|
+
}
|
|
15488
|
+
if (typeof arr === "object") {
|
|
15489
|
+
return convertRecord(arr);
|
|
15490
|
+
}
|
|
15491
|
+
return arr;
|
|
15492
|
+
}
|
|
15353
15493
|
constructor(options) {
|
|
15354
15494
|
this.redis = options.redis;
|
|
15355
15495
|
if (typeof options.dbName !== "string" || options.dbName.trim() === "") {
|
|
@@ -15524,7 +15664,7 @@ class DbHelper {
|
|
|
15524
15664
|
sql: execRes.sql
|
|
15525
15665
|
};
|
|
15526
15666
|
}
|
|
15527
|
-
const convertedList = convertBigIntFields([deserialized]);
|
|
15667
|
+
const convertedList = DbHelper.convertBigIntFields([deserialized], options.bigint);
|
|
15528
15668
|
const data = convertedList[0] ?? deserialized;
|
|
15529
15669
|
return {
|
|
15530
15670
|
data,
|
|
@@ -15575,7 +15715,7 @@ class DbHelper {
|
|
|
15575
15715
|
const deserializedList = camelList.map((item) => DbUtils.deserializeArrayFields(item)).filter((item) => item !== null);
|
|
15576
15716
|
return {
|
|
15577
15717
|
data: {
|
|
15578
|
-
lists: convertBigIntFields(deserializedList),
|
|
15718
|
+
lists: DbHelper.convertBigIntFields(deserializedList, options.bigint),
|
|
15579
15719
|
total,
|
|
15580
15720
|
page: prepared.page,
|
|
15581
15721
|
limit: prepared.limit,
|
|
@@ -15597,6 +15737,8 @@ class DbHelper {
|
|
|
15597
15737
|
};
|
|
15598
15738
|
if (options.fields !== undefined)
|
|
15599
15739
|
prepareOptions.fields = options.fields;
|
|
15740
|
+
if (options.bigint !== undefined)
|
|
15741
|
+
prepareOptions.bigint = options.bigint;
|
|
15600
15742
|
if (options.where !== undefined)
|
|
15601
15743
|
prepareOptions.where = options.where;
|
|
15602
15744
|
if (options.joins !== undefined)
|
|
@@ -15637,7 +15779,7 @@ class DbHelper {
|
|
|
15637
15779
|
}
|
|
15638
15780
|
const camelResult = arrayKeysToCamel(result);
|
|
15639
15781
|
const deserializedList = camelResult.map((item) => DbUtils.deserializeArrayFields(item)).filter((item) => item !== null);
|
|
15640
|
-
const lists = convertBigIntFields(deserializedList);
|
|
15782
|
+
const lists = DbHelper.convertBigIntFields(deserializedList, options.bigint);
|
|
15641
15783
|
return {
|
|
15642
15784
|
data: {
|
|
15643
15785
|
lists,
|
|
@@ -16276,6 +16418,52 @@ class RedisHelper {
|
|
|
16276
16418
|
return null;
|
|
16277
16419
|
}
|
|
16278
16420
|
}
|
|
16421
|
+
async tryAcquireLock(key, token, ttlMs) {
|
|
16422
|
+
try {
|
|
16423
|
+
if (!key || typeof key !== "string") {
|
|
16424
|
+
throw new Error("tryAcquireLock: key \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");
|
|
16425
|
+
}
|
|
16426
|
+
if (!token || typeof token !== "string") {
|
|
16427
|
+
throw new Error("tryAcquireLock: token \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");
|
|
16428
|
+
}
|
|
16429
|
+
if (!(typeof ttlMs === "number" && Number.isFinite(ttlMs) && ttlMs > 0)) {
|
|
16430
|
+
throw new Error("tryAcquireLock: ttlMs \u5FC5\u987B\u662F\u6B63\u6570");
|
|
16431
|
+
}
|
|
16432
|
+
const pkey = `${this.prefix}${key}`;
|
|
16433
|
+
const startTime = Date.now();
|
|
16434
|
+
const ttlMsString = String(Math.floor(ttlMs));
|
|
16435
|
+
const res = await this.client.set(pkey, token, "NX", "PX", ttlMsString);
|
|
16436
|
+
const duration = Date.now() - startTime;
|
|
16437
|
+
this.logSlow("SET NX PX", pkey, duration, { ttlMs });
|
|
16438
|
+
return res === "OK";
|
|
16439
|
+
} catch (error) {
|
|
16440
|
+
Logger.error({ err: error, msg: "Redis tryAcquireLock \u9519\u8BEF" });
|
|
16441
|
+
return false;
|
|
16442
|
+
}
|
|
16443
|
+
}
|
|
16444
|
+
async releaseLock(key, token) {
|
|
16445
|
+
try {
|
|
16446
|
+
if (!key || typeof key !== "string") {
|
|
16447
|
+
throw new Error("releaseLock: key \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");
|
|
16448
|
+
}
|
|
16449
|
+
if (!token || typeof token !== "string") {
|
|
16450
|
+
throw new Error("releaseLock: token \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");
|
|
16451
|
+
}
|
|
16452
|
+
const pkey = `${this.prefix}${key}`;
|
|
16453
|
+
const startTime = Date.now();
|
|
16454
|
+
const current = await this.client.get(pkey);
|
|
16455
|
+
if (current !== token) {
|
|
16456
|
+
return false;
|
|
16457
|
+
}
|
|
16458
|
+
const deleted = await this.client.del(pkey);
|
|
16459
|
+
const duration = Date.now() - startTime;
|
|
16460
|
+
this.logSlow("GET+DEL", pkey, duration);
|
|
16461
|
+
return deleted > 0;
|
|
16462
|
+
} catch (error) {
|
|
16463
|
+
Logger.error({ err: error, msg: "Redis releaseLock \u9519\u8BEF" });
|
|
16464
|
+
return false;
|
|
16465
|
+
}
|
|
16466
|
+
}
|
|
16279
16467
|
async getString(key) {
|
|
16280
16468
|
try {
|
|
16281
16469
|
const pkey = `${this.prefix}${key}`;
|
|
@@ -16867,17 +17055,57 @@ class Befly {
|
|
|
16867
17055
|
});
|
|
16868
17056
|
this.plugins = await loadPlugins(plugins, this.context);
|
|
16869
17057
|
this.assertStartContextReady();
|
|
16870
|
-
|
|
16871
|
-
|
|
16872
|
-
|
|
16873
|
-
const
|
|
16874
|
-
const
|
|
16875
|
-
|
|
16876
|
-
|
|
17058
|
+
const syncLockKey = "sync:lock";
|
|
17059
|
+
const syncReadyKey = "sync:ready";
|
|
17060
|
+
const syncLockTtlMs = 10 * 60 * 1000;
|
|
17061
|
+
const syncWaitReadyMaxMs = 120 * 1000;
|
|
17062
|
+
const syncWaitPollMs = 250;
|
|
17063
|
+
const syncToken = `${process.pid}:${Bun.nanoseconds()}`;
|
|
17064
|
+
const ctx = this.context;
|
|
17065
|
+
const acquired = await ctx.redis.tryAcquireLock(syncLockKey, syncToken, syncLockTtlMs);
|
|
17066
|
+
if (acquired) {
|
|
17067
|
+
Logger.info({ key: syncLockKey, msg: "\u2705 \u5DF2\u83B7\u53D6\u542F\u52A8\u540C\u6B65\u9501\uFF0C\u5C06\u6267\u884C\u81EA\u52A8\u540C\u6B65" });
|
|
17068
|
+
await ctx.redis.del(syncReadyKey);
|
|
17069
|
+
try {
|
|
17070
|
+
await new SyncTable(ctx).run(tables);
|
|
17071
|
+
await syncApi(ctx, apis);
|
|
17072
|
+
await syncMenu(ctx, checkedMenus);
|
|
17073
|
+
const devEmail = this.config.devEmail;
|
|
17074
|
+
const devPassword = this.config.devPassword;
|
|
17075
|
+
if (typeof devEmail === "string" && devEmail.length > 0 && typeof devPassword === "string" && devPassword.length > 0) {
|
|
17076
|
+
await syncDev(ctx, { devEmail, devPassword });
|
|
17077
|
+
} else {
|
|
17078
|
+
Logger.debug("\u8DF3\u8FC7 syncDev\uFF1A\u672A\u914D\u7F6E devEmail/devPassword");
|
|
17079
|
+
}
|
|
17080
|
+
await syncCache(ctx);
|
|
17081
|
+
await ctx.redis.setString(syncReadyKey, String(Date.now()), 60 * 60);
|
|
17082
|
+
} finally {
|
|
17083
|
+
const released = await ctx.redis.releaseLock(syncLockKey, syncToken);
|
|
17084
|
+
if (!released) {
|
|
17085
|
+
Logger.warn({ key: syncLockKey, msg: "\u540C\u6B65\u9501\u672A\u80FD\u4E3B\u52A8\u91CA\u653E\uFF08\u5C06\u4F9D\u8D56 TTL \u81EA\u52A8\u91CA\u653E\uFF09" });
|
|
17086
|
+
}
|
|
17087
|
+
}
|
|
16877
17088
|
} else {
|
|
16878
|
-
Logger.
|
|
17089
|
+
Logger.info({ key: syncLockKey, msg: "\u542F\u52A8\u540C\u6B65\u9501\u88AB\u5360\u7528\uFF1A\u7B49\u5F85\u540C\u6B65\u5B8C\u6210\u6807\u8BB0" });
|
|
17090
|
+
const waitStart = Date.now();
|
|
17091
|
+
while (true) {
|
|
17092
|
+
const ready = await ctx.redis.getString(syncReadyKey);
|
|
17093
|
+
if (typeof ready === "string" && ready.length > 0) {
|
|
17094
|
+
Logger.info({ key: syncReadyKey, msg: "\u2705 \u68C0\u6D4B\u5230\u540C\u6B65\u5B8C\u6210\u6807\u8BB0\uFF0C\u5C06\u8DF3\u8FC7\u81EA\u52A8\u540C\u6B65" });
|
|
17095
|
+
break;
|
|
17096
|
+
}
|
|
17097
|
+
const elapsed = Date.now() - waitStart;
|
|
17098
|
+
if (elapsed >= syncWaitReadyMaxMs) {
|
|
17099
|
+
throw new CoreError({
|
|
17100
|
+
kind: "runtime",
|
|
17101
|
+
message: `\u542F\u52A8\u7B49\u5F85\u540C\u6B65\u5B8C\u6210\u8D85\u65F6\uFF08${syncWaitReadyMaxMs}ms\uFF09\uFF1A\u8BF7\u68C0\u67E5\u662F\u5426\u6709\u5B9E\u4F8B\u5361\u5728\u540C\u6B65\u9636\u6BB5\u6216 Redis \u9501 TTL \u8FC7\u957F`,
|
|
17102
|
+
logged: true,
|
|
17103
|
+
meta: { subsystem: "start", operation: "waitSyncReady" }
|
|
17104
|
+
});
|
|
17105
|
+
}
|
|
17106
|
+
await Bun.sleep(syncWaitPollMs);
|
|
17107
|
+
}
|
|
16879
17108
|
}
|
|
16880
|
-
await syncCache(this.context);
|
|
16881
17109
|
this.hooks = await loadHooks(hooks);
|
|
16882
17110
|
this.apis = await loadApis(apis);
|
|
16883
17111
|
const apiFetch = apiHandler(this.apis, this.hooks, this.context);
|
|
@@ -16913,10 +17141,7 @@ class Befly {
|
|
|
16913
17141
|
}
|
|
16914
17142
|
});
|
|
16915
17143
|
const finalStartupTime = calcPerfTime(serverStartTime);
|
|
16916
|
-
|
|
16917
|
-
const roleLabel = processRole.role === "primary" ? "\u4E3B\u8FDB\u7A0B" : `\u5DE5\u4F5C\u8FDB\u7A0B #${processRole.instanceId}`;
|
|
16918
|
-
const envLabel = processRole.env === "standalone" ? "" : ` [${processRole.env}]`;
|
|
16919
|
-
Logger.info(`${this.config.appName} \u542F\u52A8\u6210\u529F! (${roleLabel}${envLabel})`);
|
|
17144
|
+
Logger.info(`${this.config.appName} \u542F\u52A8\u6210\u529F!`);
|
|
16920
17145
|
Logger.info(`\u670D\u52A1\u5668\u542F\u52A8\u8017\u65F6: ${finalStartupTime}`);
|
|
16921
17146
|
Logger.info(`\u670D\u52A1\u5668\u76D1\u542C\u5730\u5740: ${server.url}`);
|
|
16922
17147
|
return server;
|