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 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
- await new SyncTable(this.context).run(tables);
16871
- await syncApi(this.context, apis);
16872
- await syncMenu(this.context, checkedMenus);
16873
- const devEmail = this.config.devEmail;
16874
- const devPassword = this.config.devPassword;
16875
- if (typeof devEmail === "string" && devEmail.length > 0 && typeof devPassword === "string" && devPassword.length > 0) {
16876
- await syncDev(this.context, { devEmail, devPassword });
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.debug("\u8DF3\u8FC7 syncDev\uFF1A\u672A\u914D\u7F6E devEmail/devPassword");
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
- const processRole = getProcessRole(env);
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;