befly 3.16.5 → 3.16.7

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 CHANGED
@@ -2499,7 +2499,7 @@ var require_bn = __commonJS((exports, module) => {
2499
2499
  }
2500
2500
  assert(false, "Base should be between 2 and 36");
2501
2501
  };
2502
- BN.prototype.toNumber = function toNumber() {
2502
+ BN.prototype.toNumber = function toNumber2() {
2503
2503
  var ret = this.words[0];
2504
2504
  if (this.length === 2) {
2505
2505
  ret += this.words[1] * 67108864;
@@ -2918,7 +2918,7 @@ var require_bn = __commonJS((exports, module) => {
2918
2918
  }
2919
2919
  return out.strip();
2920
2920
  }
2921
- var comb10MulTo = function comb10MulTo(self, num, out) {
2921
+ var comb10MulTo = function comb10MulTo2(self, num, out) {
2922
2922
  var a = self.words;
2923
2923
  var b = num.words;
2924
2924
  var o = out.words;
@@ -4603,20 +4603,20 @@ var require_bn = __commonJS((exports, module) => {
4603
4603
  BN._prime = function prime(name) {
4604
4604
  if (primes[name])
4605
4605
  return primes[name];
4606
- var prime;
4606
+ var prime2;
4607
4607
  if (name === "k256") {
4608
- prime = new K256;
4608
+ prime2 = new K256;
4609
4609
  } else if (name === "p224") {
4610
- prime = new P224;
4610
+ prime2 = new P224;
4611
4611
  } else if (name === "p192") {
4612
- prime = new P192;
4612
+ prime2 = new P192;
4613
4613
  } else if (name === "p25519") {
4614
- prime = new P25519;
4614
+ prime2 = new P25519;
4615
4615
  } else {
4616
4616
  throw new Error("Unknown prime " + name);
4617
4617
  }
4618
- primes[name] = prime;
4619
- return prime;
4618
+ primes[name] = prime2;
4619
+ return prime2;
4620
4620
  };
4621
4621
  function Red(m) {
4622
4622
  if (typeof m === "string") {
@@ -5161,7 +5161,7 @@ var require_buffer = __commonJS((exports) => {
5161
5161
  const isCompatible = typeof data === "object" && data.constructor.name === "EncoderBuffer" && typeof data.length === "number" && typeof data.join === "function";
5162
5162
  return isCompatible;
5163
5163
  };
5164
- EncoderBuffer.prototype.join = function join(out, offset) {
5164
+ EncoderBuffer.prototype.join = function join3(out, offset) {
5165
5165
  if (!out)
5166
5166
  out = Buffer2.alloc(this.length);
5167
5167
  if (!offset)
@@ -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
 
@@ -11598,7 +11573,7 @@ var corsHook = {
11598
11573
  };
11599
11574
  var cors_default = corsHook;
11600
11575
 
11601
- // ../../node_modules/.bun/fast-xml-parser@5.3.3/node_modules/fast-xml-parser/src/util.js
11576
+ // ../../node_modules/.bun/fast-xml-parser@5.3.4/node_modules/fast-xml-parser/src/util.js
11602
11577
  var nameStartChar = ":A-Za-z_\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD";
11603
11578
  var nameChar = nameStartChar + "\\-.\\d\\u00B7\\u0300-\\u036F\\u203F-\\u2040";
11604
11579
  var nameRegexp = "[" + nameStartChar + "][" + nameChar + "]*";
@@ -11626,7 +11601,7 @@ function isExist(v) {
11626
11601
  return typeof v !== "undefined";
11627
11602
  }
11628
11603
 
11629
- // ../../node_modules/.bun/fast-xml-parser@5.3.3/node_modules/fast-xml-parser/src/validator.js
11604
+ // ../../node_modules/.bun/fast-xml-parser@5.3.4/node_modules/fast-xml-parser/src/validator.js
11630
11605
  var defaultOptions2 = {
11631
11606
  allowBooleanAttributes: false,
11632
11607
  unpairedTags: []
@@ -11928,7 +11903,7 @@ function getPositionFromMatch(match) {
11928
11903
  return match.startIndex + match[1].length;
11929
11904
  }
11930
11905
 
11931
- // ../../node_modules/.bun/fast-xml-parser@5.3.3/node_modules/fast-xml-parser/src/xmlparser/OptionsBuilder.js
11906
+ // ../../node_modules/.bun/fast-xml-parser@5.3.4/node_modules/fast-xml-parser/src/xmlparser/OptionsBuilder.js
11932
11907
  var defaultOptions3 = {
11933
11908
  preserveOrder: false,
11934
11909
  attributeNamePrefix: "@_",
@@ -11972,7 +11947,7 @@ var buildOptions = function(options) {
11972
11947
  return Object.assign({}, defaultOptions3, options);
11973
11948
  };
11974
11949
 
11975
- // ../../node_modules/.bun/fast-xml-parser@5.3.3/node_modules/fast-xml-parser/src/xmlparser/xmlNode.js
11950
+ // ../../node_modules/.bun/fast-xml-parser@5.3.4/node_modules/fast-xml-parser/src/xmlparser/xmlNode.js
11976
11951
  var METADATA_SYMBOL;
11977
11952
  if (typeof Symbol !== "function") {
11978
11953
  METADATA_SYMBOL = "@@xmlMetadata";
@@ -12008,7 +11983,7 @@ class XmlNode {
12008
11983
  }
12009
11984
  }
12010
11985
 
12011
- // ../../node_modules/.bun/fast-xml-parser@5.3.3/node_modules/fast-xml-parser/src/xmlparser/DocTypeReader.js
11986
+ // ../../node_modules/.bun/fast-xml-parser@5.3.4/node_modules/fast-xml-parser/src/xmlparser/DocTypeReader.js
12012
11987
  class DocTypeReader {
12013
11988
  constructor(processEntities) {
12014
11989
  this.suppressValidationErr = !processEntities;
@@ -12385,7 +12360,7 @@ function parse_int(numStr, base) {
12385
12360
  throw new Error("parseInt, Number.parseInt, window.parseInt are not supported");
12386
12361
  }
12387
12362
 
12388
- // ../../node_modules/.bun/fast-xml-parser@5.3.3/node_modules/fast-xml-parser/src/ignoreAttributes.js
12363
+ // ../../node_modules/.bun/fast-xml-parser@5.3.4/node_modules/fast-xml-parser/src/ignoreAttributes.js
12389
12364
  function getIgnoreAttributesFn(ignoreAttributes) {
12390
12365
  if (typeof ignoreAttributes === "function") {
12391
12366
  return ignoreAttributes;
@@ -12405,7 +12380,7 @@ function getIgnoreAttributesFn(ignoreAttributes) {
12405
12380
  return () => false;
12406
12381
  }
12407
12382
 
12408
- // ../../node_modules/.bun/fast-xml-parser@5.3.3/node_modules/fast-xml-parser/src/xmlparser/OrderedObjParser.js
12383
+ // ../../node_modules/.bun/fast-xml-parser@5.3.4/node_modules/fast-xml-parser/src/xmlparser/OrderedObjParser.js
12409
12384
  class OrderedObjParser {
12410
12385
  constructor(options) {
12411
12386
  this.options = options;
@@ -12428,8 +12403,8 @@ class OrderedObjParser {
12428
12403
  copyright: { regex: /&(copy|#169);/g, val: "\xA9" },
12429
12404
  reg: { regex: /&(reg|#174);/g, val: "\xAE" },
12430
12405
  inr: { regex: /&(inr|#8377);/g, val: "\u20B9" },
12431
- num_dec: { regex: /&#([0-9]{1,7});/g, val: (_, str) => String.fromCodePoint(Number.parseInt(str, 10)) },
12432
- num_hex: { regex: /&#x([0-9a-fA-F]{1,6});/g, val: (_, str) => String.fromCodePoint(Number.parseInt(str, 16)) }
12406
+ num_dec: { regex: /&#([0-9]{1,7});/g, val: (_, str) => fromCodePoint(str, 10, "&#") },
12407
+ num_hex: { regex: /&#x([0-9a-fA-F]{1,6});/g, val: (_, str) => fromCodePoint(str, 16, "&#x") }
12433
12408
  };
12434
12409
  this.addExternalEntities = addExternalEntities;
12435
12410
  this.parseXml = parseXml;
@@ -12907,8 +12882,16 @@ function parseValue(val, shouldParse, options) {
12907
12882
  }
12908
12883
  }
12909
12884
  }
12885
+ function fromCodePoint(str, base, prefix) {
12886
+ const codePoint = Number.parseInt(str, base);
12887
+ if (codePoint >= 0 && codePoint <= 1114111) {
12888
+ return String.fromCodePoint(codePoint);
12889
+ } else {
12890
+ return prefix + str + ";";
12891
+ }
12892
+ }
12910
12893
 
12911
- // ../../node_modules/.bun/fast-xml-parser@5.3.3/node_modules/fast-xml-parser/src/xmlparser/node2json.js
12894
+ // ../../node_modules/.bun/fast-xml-parser@5.3.4/node_modules/fast-xml-parser/src/xmlparser/node2json.js
12912
12895
  var METADATA_SYMBOL2 = XmlNode.getMetaDataSymbol();
12913
12896
  function prettify(node, options) {
12914
12897
  return compress(node, options);
@@ -13002,7 +12985,7 @@ function isLeafTag(obj, options) {
13002
12985
  return false;
13003
12986
  }
13004
12987
 
13005
- // ../../node_modules/.bun/fast-xml-parser@5.3.3/node_modules/fast-xml-parser/src/xmlparser/XMLParser.js
12988
+ // ../../node_modules/.bun/fast-xml-parser@5.3.4/node_modules/fast-xml-parser/src/xmlparser/XMLParser.js
13006
12989
  class XMLParser {
13007
12990
  constructor(options) {
13008
12991
  this.externalEntities = {};
@@ -13109,6 +13092,9 @@ class CacheKeys {
13109
13092
  static roleInfo(roleCode) {
13110
13093
  return `role:info:${roleCode}`;
13111
13094
  }
13095
+ static roleMenus(roleCode) {
13096
+ return `role:menus:${roleCode}`;
13097
+ }
13112
13098
  static roleApis(roleCode) {
13113
13099
  return `role:apis:${roleCode}`;
13114
13100
  }
@@ -13694,6 +13680,25 @@ class CacheHelper {
13694
13680
  }
13695
13681
  return trimmed;
13696
13682
  }
13683
+ assertMenuPathname(value, errorPrefix) {
13684
+ if (typeof value !== "string") {
13685
+ throw new Error(`${errorPrefix} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32`);
13686
+ }
13687
+ const trimmed = value.trim();
13688
+ if (!trimmed) {
13689
+ throw new Error(`${errorPrefix} \u4E0D\u5141\u8BB8\u4E3A\u7A7A\u5B57\u7B26\u4E32`);
13690
+ }
13691
+ if (!trimmed.startsWith("/")) {
13692
+ throw new Error(`${errorPrefix} \u5FC5\u987B\u662F pathname\uFF08\u4EE5 / \u5F00\u5934\uFF09`);
13693
+ }
13694
+ if (trimmed.includes(" ")) {
13695
+ throw new Error(`${errorPrefix} \u4E0D\u5141\u8BB8\u5305\u542B\u7A7A\u683C`);
13696
+ }
13697
+ if (trimmed.length > 1 && trimmed.endsWith("/")) {
13698
+ throw new Error(`${errorPrefix} \u4E0D\u5141\u8BB8\u4EE5 / \u7ED3\u5C3E`);
13699
+ }
13700
+ return trimmed;
13701
+ }
13697
13702
  assertApiPathList(value, roleCode) {
13698
13703
  if (value === null || value === undefined)
13699
13704
  return [];
@@ -13721,6 +13726,33 @@ class CacheHelper {
13721
13726
  }
13722
13727
  return out;
13723
13728
  }
13729
+ assertMenuPathList(value, roleCode) {
13730
+ if (value === null || value === undefined)
13731
+ return [];
13732
+ let list = value;
13733
+ if (typeof list === "string") {
13734
+ const trimmed = list.trim();
13735
+ if (trimmed === "" || trimmed === "null") {
13736
+ return [];
13737
+ }
13738
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
13739
+ try {
13740
+ list = JSON.parse(trimmed);
13741
+ } catch {
13742
+ 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}`);
13743
+ }
13744
+ }
13745
+ }
13746
+ if (!Array.isArray(list)) {
13747
+ const typeLabel = typeof list;
13748
+ 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}`);
13749
+ }
13750
+ const out = [];
13751
+ for (const item of list) {
13752
+ out.push(this.assertMenuPathname(item, `\u89D2\u8272\u83DC\u5355\u6743\u9650\u6570\u636E\u4E0D\u5408\u6CD5\uFF1Aaddon_admin_role.menus \u5143\u7D20\uFF0CroleCode=${roleCode}`));
13753
+ }
13754
+ return out;
13755
+ }
13724
13756
  async cacheApis() {
13725
13757
  try {
13726
13758
  const tableExists = await this.db.tableExists("addon_admin_api");
@@ -13807,6 +13839,56 @@ class CacheHelper {
13807
13839
  });
13808
13840
  }
13809
13841
  }
13842
+ async rebuildRoleMenuPermissions() {
13843
+ try {
13844
+ const roleTableExists = await this.db.tableExists("addon_admin_role");
13845
+ if (!roleTableExists.data) {
13846
+ Logger.warn("\u26A0\uFE0F \u89D2\u8272\u8868\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u89D2\u8272\u83DC\u5355\u6743\u9650\u7F13\u5B58");
13847
+ return;
13848
+ }
13849
+ const roles = await this.db.getAll({
13850
+ table: "addon_admin_role",
13851
+ fields: ["code", "menus"]
13852
+ });
13853
+ const roleMenuPathsMap = new Map;
13854
+ for (const role of roles.data.lists) {
13855
+ if (!role?.code)
13856
+ continue;
13857
+ const menuPaths = this.assertMenuPathList(role.menus, role.code);
13858
+ roleMenuPathsMap.set(role.code, menuPaths);
13859
+ }
13860
+ const roleCodes = Array.from(roleMenuPathsMap.keys());
13861
+ if (roleCodes.length === 0) {
13862
+ Logger.info("\u2705 \u6CA1\u6709\u9700\u8981\u7F13\u5B58\u7684\u89D2\u8272\u83DC\u5355\u6743\u9650");
13863
+ return;
13864
+ }
13865
+ const roleKeys = roleCodes.map((code) => CacheKeys.roleMenus(code));
13866
+ await this.redis.delBatch(roleKeys);
13867
+ const items = [];
13868
+ for (const roleCode of roleCodes) {
13869
+ const menuPaths = roleMenuPathsMap.get(roleCode) || [];
13870
+ const members = Array.from(new Set(menuPaths)).sort();
13871
+ if (members.length > 0) {
13872
+ items.push({ key: CacheKeys.roleMenus(roleCode), members });
13873
+ }
13874
+ }
13875
+ if (items.length > 0) {
13876
+ await this.redis.saddBatch(items);
13877
+ }
13878
+ } catch (error) {
13879
+ Logger.error({ err: error, msg: "\u26A0\uFE0F \u89D2\u8272\u83DC\u5355\u6743\u9650\u7F13\u5B58\u5F02\u5E38\uFF08\u5C06\u963B\u65AD\u542F\u52A8\uFF09" });
13880
+ throw new CoreError({
13881
+ kind: "runtime",
13882
+ message: "\u26A0\uFE0F \u89D2\u8272\u83DC\u5355\u6743\u9650\u7F13\u5B58\u5F02\u5E38\uFF08\u5C06\u963B\u65AD\u542F\u52A8\uFF09",
13883
+ logged: true,
13884
+ cause: error,
13885
+ meta: {
13886
+ subsystem: "cache",
13887
+ operation: "rebuildRoleMenuPermissions"
13888
+ }
13889
+ });
13890
+ }
13891
+ }
13810
13892
  async refreshRoleApiPermissions(roleCode, apiPaths) {
13811
13893
  if (!roleCode || typeof roleCode !== "string") {
13812
13894
  throw new Error("roleCode \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");
@@ -13826,10 +13908,30 @@ class CacheHelper {
13826
13908
  await this.redis.sadd(roleKey, members);
13827
13909
  }
13828
13910
  }
13911
+ async refreshRoleMenuPermissions(roleCode, menuPaths) {
13912
+ if (!roleCode || typeof roleCode !== "string") {
13913
+ throw new Error("roleCode \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");
13914
+ }
13915
+ if (!Array.isArray(menuPaths)) {
13916
+ throw new Error("menuPaths \u5FC5\u987B\u662F\u6570\u7EC4");
13917
+ }
13918
+ const normalizedPaths = menuPaths.map((p) => this.assertMenuPathname(p, `refreshRoleMenuPermissions: menuPaths \u5143\u7D20\uFF0CroleCode=${roleCode}`));
13919
+ const roleKey = CacheKeys.roleMenus(roleCode);
13920
+ if (normalizedPaths.length === 0) {
13921
+ await this.redis.del(roleKey);
13922
+ return;
13923
+ }
13924
+ const members = Array.from(new Set(normalizedPaths));
13925
+ await this.redis.del(roleKey);
13926
+ if (members.length > 0) {
13927
+ await this.redis.sadd(roleKey, members);
13928
+ }
13929
+ }
13829
13930
  async cacheAll() {
13830
13931
  await this.cacheApis();
13831
13932
  await this.cacheMenus();
13832
13933
  await this.rebuildRoleApiPermissions();
13934
+ await this.rebuildRoleMenuPermissions();
13833
13935
  }
13834
13936
  async getApis() {
13835
13937
  try {
@@ -13858,6 +13960,15 @@ class CacheHelper {
13858
13960
  return [];
13859
13961
  }
13860
13962
  }
13963
+ async getRoleMenuPermissions(roleCode) {
13964
+ try {
13965
+ const permissions = await this.redis.smembers(CacheKeys.roleMenus(roleCode));
13966
+ return permissions || [];
13967
+ } catch (error) {
13968
+ Logger.error({ err: error, roleCode, msg: "\u83B7\u53D6\u89D2\u8272\u83DC\u5355\u6743\u9650\u7F13\u5B58\u5931\u8D25" });
13969
+ return [];
13970
+ }
13971
+ }
13861
13972
  async checkRolePermission(roleCode, apiPath) {
13862
13973
  try {
13863
13974
  const pathname = this.assertApiPathname(apiPath, "checkRolePermission: apiPath");
@@ -13867,6 +13978,15 @@ class CacheHelper {
13867
13978
  return false;
13868
13979
  }
13869
13980
  }
13981
+ async checkRoleMenuPermission(roleCode, menuPath) {
13982
+ try {
13983
+ const pathname = this.assertMenuPathname(menuPath, "checkRoleMenuPermission: menuPath");
13984
+ return await this.redis.sismember(CacheKeys.roleMenus(roleCode), pathname);
13985
+ } catch (error) {
13986
+ Logger.error({ err: error, roleCode, msg: "\u68C0\u67E5\u89D2\u8272\u83DC\u5355\u6743\u9650\u5931\u8D25" });
13987
+ return false;
13988
+ }
13989
+ }
13870
13990
  async deleteRolePermissions(roleCode) {
13871
13991
  try {
13872
13992
  const result = await this.redis.del(CacheKeys.roleApis(roleCode));
@@ -13880,6 +14000,19 @@ class CacheHelper {
13880
14000
  return false;
13881
14001
  }
13882
14002
  }
14003
+ async deleteRoleMenuPermissions(roleCode) {
14004
+ try {
14005
+ const result = await this.redis.del(CacheKeys.roleMenus(roleCode));
14006
+ if (result > 0) {
14007
+ Logger.info(`\u2705 \u5DF2\u5220\u9664\u89D2\u8272 ${roleCode} \u7684\u83DC\u5355\u6743\u9650\u7F13\u5B58`);
14008
+ return true;
14009
+ }
14010
+ return false;
14011
+ } catch (error) {
14012
+ Logger.error({ err: error, roleCode, msg: "\u5220\u9664\u89D2\u8272\u83DC\u5355\u6743\u9650\u7F13\u5B58\u5931\u8D25" });
14013
+ return false;
14014
+ }
14015
+ }
13883
14016
  }
13884
14017
 
13885
14018
  // plugins/cache.ts
@@ -16293,6 +16426,52 @@ class RedisHelper {
16293
16426
  return null;
16294
16427
  }
16295
16428
  }
16429
+ async tryAcquireLock(key, token, ttlMs) {
16430
+ try {
16431
+ if (!key || typeof key !== "string") {
16432
+ throw new Error("tryAcquireLock: key \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");
16433
+ }
16434
+ if (!token || typeof token !== "string") {
16435
+ throw new Error("tryAcquireLock: token \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");
16436
+ }
16437
+ if (!(typeof ttlMs === "number" && Number.isFinite(ttlMs) && ttlMs > 0)) {
16438
+ throw new Error("tryAcquireLock: ttlMs \u5FC5\u987B\u662F\u6B63\u6570");
16439
+ }
16440
+ const pkey = `${this.prefix}${key}`;
16441
+ const startTime = Date.now();
16442
+ const ttlMsString = String(Math.floor(ttlMs));
16443
+ const res = await this.client.set(pkey, token, "NX", "PX", ttlMsString);
16444
+ const duration = Date.now() - startTime;
16445
+ this.logSlow("SET NX PX", pkey, duration, { ttlMs });
16446
+ return res === "OK";
16447
+ } catch (error) {
16448
+ Logger.error({ err: error, msg: "Redis tryAcquireLock \u9519\u8BEF" });
16449
+ return false;
16450
+ }
16451
+ }
16452
+ async releaseLock(key, token) {
16453
+ try {
16454
+ if (!key || typeof key !== "string") {
16455
+ throw new Error("releaseLock: key \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");
16456
+ }
16457
+ if (!token || typeof token !== "string") {
16458
+ throw new Error("releaseLock: token \u5FC5\u987B\u662F\u975E\u7A7A\u5B57\u7B26\u4E32");
16459
+ }
16460
+ const pkey = `${this.prefix}${key}`;
16461
+ const startTime = Date.now();
16462
+ const current = await this.client.get(pkey);
16463
+ if (current !== token) {
16464
+ return false;
16465
+ }
16466
+ const deleted = await this.client.del(pkey);
16467
+ const duration = Date.now() - startTime;
16468
+ this.logSlow("GET+DEL", pkey, duration);
16469
+ return deleted > 0;
16470
+ } catch (error) {
16471
+ Logger.error({ err: error, msg: "Redis releaseLock \u9519\u8BEF" });
16472
+ return false;
16473
+ }
16474
+ }
16296
16475
  async getString(key) {
16297
16476
  try {
16298
16477
  const pkey = `${this.prefix}${key}`;
@@ -16394,7 +16573,9 @@ class RedisHelper {
16394
16573
  const startTime = Date.now();
16395
16574
  const res = await this.client.sadd(pkey, ...members);
16396
16575
  const duration = Date.now() - startTime;
16397
- this.logSlow("SADD", pkey, duration, { membersCount: members.length });
16576
+ this.logSlow("SADD", pkey, duration, {
16577
+ membersCount: members.length
16578
+ });
16398
16579
  return res;
16399
16580
  } catch (error) {
16400
16581
  Logger.error({ err: error, msg: "Redis sadd \u9519\u8BEF" });
@@ -16884,17 +17065,57 @@ class Befly {
16884
17065
  });
16885
17066
  this.plugins = await loadPlugins(plugins, this.context);
16886
17067
  this.assertStartContextReady();
16887
- await new SyncTable(this.context).run(tables);
16888
- await syncApi(this.context, apis);
16889
- await syncMenu(this.context, checkedMenus);
16890
- const devEmail = this.config.devEmail;
16891
- const devPassword = this.config.devPassword;
16892
- if (typeof devEmail === "string" && devEmail.length > 0 && typeof devPassword === "string" && devPassword.length > 0) {
16893
- await syncDev(this.context, { devEmail, devPassword });
17068
+ const syncLockKey = "sync:lock";
17069
+ const syncReadyKey = "sync:ready";
17070
+ const syncLockTtlMs = 10 * 60 * 1000;
17071
+ const syncWaitReadyMaxMs = 120 * 1000;
17072
+ const syncWaitPollMs = 250;
17073
+ const syncToken = `${process.pid}:${Bun.nanoseconds()}`;
17074
+ const ctx = this.context;
17075
+ const acquired = await ctx.redis.tryAcquireLock(syncLockKey, syncToken, syncLockTtlMs);
17076
+ if (acquired) {
17077
+ Logger.info({ key: syncLockKey, msg: "\u2705 \u5DF2\u83B7\u53D6\u542F\u52A8\u540C\u6B65\u9501\uFF0C\u5C06\u6267\u884C\u81EA\u52A8\u540C\u6B65" });
17078
+ await ctx.redis.del(syncReadyKey);
17079
+ try {
17080
+ await new SyncTable(ctx).run(tables);
17081
+ await syncApi(ctx, apis);
17082
+ await syncMenu(ctx, checkedMenus);
17083
+ const devEmail = this.config.devEmail;
17084
+ const devPassword = this.config.devPassword;
17085
+ if (typeof devEmail === "string" && devEmail.length > 0 && typeof devPassword === "string" && devPassword.length > 0) {
17086
+ await syncDev(ctx, { devEmail, devPassword });
17087
+ } else {
17088
+ Logger.debug("\u8DF3\u8FC7 syncDev\uFF1A\u672A\u914D\u7F6E devEmail/devPassword");
17089
+ }
17090
+ await syncCache(ctx);
17091
+ await ctx.redis.setString(syncReadyKey, String(Date.now()), 60 * 60);
17092
+ } finally {
17093
+ const released = await ctx.redis.releaseLock(syncLockKey, syncToken);
17094
+ if (!released) {
17095
+ Logger.warn({ key: syncLockKey, msg: "\u540C\u6B65\u9501\u672A\u80FD\u4E3B\u52A8\u91CA\u653E\uFF08\u5C06\u4F9D\u8D56 TTL \u81EA\u52A8\u91CA\u653E\uFF09" });
17096
+ }
17097
+ }
16894
17098
  } else {
16895
- Logger.debug("\u8DF3\u8FC7 syncDev\uFF1A\u672A\u914D\u7F6E devEmail/devPassword");
17099
+ Logger.info({ key: syncLockKey, msg: "\u542F\u52A8\u540C\u6B65\u9501\u88AB\u5360\u7528\uFF1A\u7B49\u5F85\u540C\u6B65\u5B8C\u6210\u6807\u8BB0" });
17100
+ const waitStart = Date.now();
17101
+ while (true) {
17102
+ const ready = await ctx.redis.getString(syncReadyKey);
17103
+ if (typeof ready === "string" && ready.length > 0) {
17104
+ Logger.info({ key: syncReadyKey, msg: "\u2705 \u68C0\u6D4B\u5230\u540C\u6B65\u5B8C\u6210\u6807\u8BB0\uFF0C\u5C06\u8DF3\u8FC7\u81EA\u52A8\u540C\u6B65" });
17105
+ break;
17106
+ }
17107
+ const elapsed = Date.now() - waitStart;
17108
+ if (elapsed >= syncWaitReadyMaxMs) {
17109
+ throw new CoreError({
17110
+ kind: "runtime",
17111
+ 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`,
17112
+ logged: true,
17113
+ meta: { subsystem: "start", operation: "waitSyncReady" }
17114
+ });
17115
+ }
17116
+ await Bun.sleep(syncWaitPollMs);
17117
+ }
16896
17118
  }
16897
- await syncCache(this.context);
16898
17119
  this.hooks = await loadHooks(hooks);
16899
17120
  this.apis = await loadApis(apis);
16900
17121
  const apiFetch = apiHandler(this.apis, this.hooks, this.context);
@@ -16930,10 +17151,7 @@ class Befly {
16930
17151
  }
16931
17152
  });
16932
17153
  const finalStartupTime = calcPerfTime(serverStartTime);
16933
- const processRole = getProcessRole(env);
16934
- const roleLabel = processRole.role === "primary" ? "\u4E3B\u8FDB\u7A0B" : `\u5DE5\u4F5C\u8FDB\u7A0B #${processRole.instanceId}`;
16935
- const envLabel = processRole.env === "standalone" ? "" : ` [${processRole.env}]`;
16936
- Logger.info(`${this.config.appName} \u542F\u52A8\u6210\u529F! (${roleLabel}${envLabel})`);
17154
+ Logger.info(`${this.config.appName} \u542F\u52A8\u6210\u529F!`);
16937
17155
  Logger.info(`\u670D\u52A1\u5668\u542F\u52A8\u8017\u65F6: ${finalStartupTime}`);
16938
17156
  Logger.info(`\u670D\u52A1\u5668\u76D1\u542C\u5730\u5740: ${server.url}`);
16939
17157
  return server;