@abtnode/core 1.16.53 → 1.16.54-beta-20251017-133309-7d40faa6

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.
@@ -485,6 +485,15 @@ module.exports = Object.freeze({
485
485
  PROCESS_NAME_LOG_ROTATE: 'abt-node-log-rotate',
486
486
  PROCESS_NAME_EVENT_HUB: 'abt-node-event-hub',
487
487
  PROCESS_NAME_PM2_EVENT_HUB: 'abt-node-pm2-event-hub',
488
+ PROCESS_NAME_ORPHAN_CLEANUP: 'abt-node-orphan-cleanup',
489
+
490
+ // Process script paths for orphan detection
491
+ DAEMON_SCRIPT_PATH: 'cli/lib/process/daemon.js',
492
+ SERVICE_SCRIPT_PATH: 'cli/lib/process/service.js',
493
+
494
+ // Orphan process cleanup settings
495
+ ORPHAN_CHECK_DELAY: 60 * 1000, // 60 seconds - delay before checking for orphan processes
496
+ ORPHAN_MIN_UPTIME: 60, // 60 seconds - minimum process uptime to be considered orphan
488
497
 
489
498
  BLOCKLET_LAUNCHER_URL: 'https://launcher.arcblock.io/',
490
499
  WEB_WALLET_URL: 'https://web.abtwallet.io',
@@ -38954,7 +38963,7 @@ module.exports = require("zlib");
38954
38963
  /***/ ((module) => {
38955
38964
 
38956
38965
  "use strict";
38957
- module.exports = /*#__PURE__*/JSON.parse('{"name":"@abtnode/core","publishConfig":{"access":"public"},"version":"1.16.52","description":"","main":"lib/index.js","files":["lib"],"scripts":{"lint":"eslint tests lib --ignore-pattern \'tests/assets/*\'","lint:fix":"eslint --fix tests lib","test":"node tools/jest.js","test:disk":"CI=true npm run test tests/blocklet/manager/disk.spec.js","test:blue":"CI=true npm run test tests/blocklet/manager/disk-blue-green.spec.js","coverage":"npm run test -- --coverage"},"keywords":[],"author":"wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)","license":"Apache-2.0","dependencies":{"@abtnode/analytics":"1.16.52","@abtnode/auth":"1.16.52","@abtnode/certificate-manager":"1.16.52","@abtnode/constant":"1.16.52","@abtnode/cron":"1.16.52","@abtnode/db-cache":"1.16.52","@abtnode/docker-utils":"1.16.52","@abtnode/logger":"1.16.52","@abtnode/models":"1.16.52","@abtnode/queue":"1.16.52","@abtnode/rbac":"1.16.52","@abtnode/router-provider":"1.16.52","@abtnode/static-server":"1.16.52","@abtnode/timemachine":"1.16.52","@abtnode/util":"1.16.52","@aigne/aigne-hub":"^0.10.1","@arcblock/did":"1.25.6","@arcblock/did-connect-js":"1.25.6","@arcblock/did-ext":"1.25.6","@arcblock/did-motif":"^1.1.14","@arcblock/did-util":"1.25.6","@arcblock/event-hub":"1.25.6","@arcblock/jwt":"1.25.6","@arcblock/pm2-events":"^0.0.5","@arcblock/validator":"1.25.6","@arcblock/vc":"1.25.6","@blocklet/constant":"1.16.52","@blocklet/did-space-js":"^1.1.33","@blocklet/env":"1.16.52","@blocklet/error":"^0.2.5","@blocklet/meta":"1.16.52","@blocklet/resolver":"1.16.52","@blocklet/sdk":"1.16.52","@blocklet/server-js":"1.16.52","@blocklet/store":"1.16.52","@blocklet/theme":"^3.1.49","@fidm/x509":"^1.2.1","@ocap/mcrypto":"1.25.6","@ocap/util":"1.25.6","@ocap/wallet":"1.25.6","@slack/webhook":"^5.0.4","archiver":"^7.0.1","axios":"^1.7.9","axon":"^2.0.3","chalk":"^4.1.2","cross-spawn":"^7.0.3","dayjs":"^1.11.13","deep-diff":"^1.0.2","detect-port":"^1.5.1","envfile":"^7.1.0","escape-string-regexp":"^4.0.0","fast-glob":"^3.3.2","filesize":"^10.1.1","flat":"^5.0.2","fs-extra":"^11.2.0","get-port":"^5.1.1","hasha":"^5.2.2","is-base64":"^1.1.0","is-cidr":"4","is-ip":"3","is-url":"^1.2.4","joi":"17.12.2","joi-extension-semver":"^5.0.0","js-yaml":"^4.1.0","kill-port":"^2.0.1","lodash":"^4.17.21","node-stream-zip":"^1.15.0","p-all":"^3.0.0","p-limit":"^3.1.0","p-map":"^4.0.0","p-retry":"^4.6.2","p-wait-for":"^3.2.0","private-ip":"^2.3.4","rate-limiter-flexible":"^5.0.5","read-last-lines":"^1.8.0","semver":"^7.6.3","sequelize":"^6.35.0","shelljs":"^0.8.5","slugify":"^1.6.6","ssri":"^8.0.1","stream-throttle":"^0.1.3","stream-to-promise":"^3.0.0","systeminformation":"^5.23.3","tail":"^2.2.4","tar":"^6.1.11","transliteration":"^2.3.5","ua-parser-js":"^1.0.2","ufo":"^1.5.3","uuid":"^11.1.0","valid-url":"^1.0.9","which":"^2.0.2","xbytes":"^1.8.0"},"devDependencies":{"expand-tilde":"^2.0.2","express":"^4.18.2","jest":"^29.7.0","unzipper":"^0.10.11"},"gitHead":"e5764f753181ed6a7c615cd4fc6682aacf0cb7cd"}');
38966
+ module.exports = /*#__PURE__*/JSON.parse('{"name":"@abtnode/core","publishConfig":{"access":"public"},"version":"1.16.53","description":"","main":"lib/index.js","files":["lib"],"scripts":{"lint":"eslint tests lib --ignore-pattern \'tests/assets/*\'","lint:fix":"eslint --fix tests lib","test":"node tools/jest.js","test:disk":"CI=true npm run test tests/blocklet/manager/disk.spec.js","test:blue":"CI=true npm run test tests/blocklet/manager/disk-blue-green.spec.js","coverage":"npm run test -- --coverage"},"keywords":[],"author":"wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)","license":"Apache-2.0","dependencies":{"@abtnode/analytics":"1.16.53","@abtnode/auth":"1.16.53","@abtnode/certificate-manager":"1.16.53","@abtnode/constant":"1.16.53","@abtnode/cron":"1.16.53","@abtnode/db-cache":"1.16.53","@abtnode/docker-utils":"1.16.53","@abtnode/logger":"1.16.53","@abtnode/models":"1.16.53","@abtnode/queue":"1.16.53","@abtnode/rbac":"1.16.53","@abtnode/router-provider":"1.16.53","@abtnode/static-server":"1.16.53","@abtnode/timemachine":"1.16.53","@abtnode/util":"1.16.53","@aigne/aigne-hub":"^0.10.1","@arcblock/did":"1.25.6","@arcblock/did-connect-js":"1.25.6","@arcblock/did-ext":"1.25.6","@arcblock/did-motif":"^1.1.14","@arcblock/did-util":"1.25.6","@arcblock/event-hub":"1.25.6","@arcblock/jwt":"1.25.6","@arcblock/pm2-events":"^0.0.5","@arcblock/validator":"1.25.6","@arcblock/vc":"1.25.6","@blocklet/constant":"1.16.53","@blocklet/did-space-js":"^1.1.34","@blocklet/env":"1.16.53","@blocklet/error":"^0.2.5","@blocklet/meta":"1.16.53","@blocklet/resolver":"1.16.53","@blocklet/sdk":"1.16.53","@blocklet/server-js":"1.16.53","@blocklet/store":"1.16.53","@blocklet/theme":"^3.1.51","@fidm/x509":"^1.2.1","@ocap/mcrypto":"1.25.6","@ocap/util":"1.25.6","@ocap/wallet":"1.25.6","@slack/webhook":"^5.0.4","archiver":"^7.0.1","axios":"^1.7.9","axon":"^2.0.3","chalk":"^4.1.2","cross-spawn":"^7.0.3","dayjs":"^1.11.13","deep-diff":"^1.0.2","detect-port":"^1.5.1","envfile":"^7.1.0","escape-string-regexp":"^4.0.0","fast-glob":"^3.3.2","filesize":"^10.1.1","flat":"^5.0.2","fs-extra":"^11.2.0","get-port":"^5.1.1","hasha":"^5.2.2","is-base64":"^1.1.0","is-cidr":"4","is-ip":"3","is-url":"^1.2.4","joi":"17.12.2","joi-extension-semver":"^5.0.0","js-yaml":"^4.1.0","kill-port":"^2.0.1","lodash":"^4.17.21","node-stream-zip":"^1.15.0","p-all":"^3.0.0","p-limit":"^3.1.0","p-map":"^4.0.0","p-retry":"^4.6.2","p-wait-for":"^3.2.0","private-ip":"^2.3.4","rate-limiter-flexible":"^5.0.5","read-last-lines":"^1.8.0","semver":"^7.6.3","sequelize":"^6.35.0","shelljs":"^0.8.5","slugify":"^1.6.6","ssri":"^8.0.1","stream-throttle":"^0.1.3","stream-to-promise":"^3.0.0","systeminformation":"^5.23.3","tail":"^2.2.4","tar":"^6.1.11","transliteration":"^2.3.5","ua-parser-js":"^1.0.2","ufo":"^1.5.3","uuid":"^11.1.0","valid-url":"^1.0.9","which":"^2.0.2","xbytes":"^1.8.0"},"devDependencies":{"expand-tilde":"^2.0.2","express":"^4.18.2","jest":"^29.7.0","unzipper":"^0.10.11"},"gitHead":"e5764f753181ed6a7c615cd4fc6682aacf0cb7cd"}');
38958
38967
 
38959
38968
  /***/ }),
38960
38969
 
package/lib/index.js CHANGED
@@ -10,6 +10,7 @@ const logger = require('@abtnode/logger')('@abtnode/core');
10
10
  const { fromBlockletStatus, toBlockletStatus, fromBlockletSource, toBlockletSource } = require('@blocklet/constant');
11
11
  const { listProviders } = require('@abtnode/router-provider');
12
12
  const { DEFAULT_CERTIFICATE_EMAIL, EVENTS } = require('@abtnode/constant');
13
+ const { CustomError } = require('@blocklet/error');
13
14
 
14
15
  const { isInstanceWorker } = require('@abtnode/util/lib/pm2/is-instence-worker');
15
16
  const RoutingSnapshot = require('./states/routing-snapshot');
@@ -54,6 +55,9 @@ const dockerRestartAllContainers = require('./util/docker/docker-restart-all-con
54
55
  const RouterBlocker = require('./router/security/blocker');
55
56
  const BlockletVault = require('./blocklet/security/vault');
56
57
 
58
+ const { getScope } = require('./util/audit-log');
59
+ const { migrateAuditLog } = require('./util/post-start-tasks/migrate-audit-log');
60
+
57
61
  /**
58
62
  * @typedef {{} & import('./api/team')}} TNode
59
63
  */
@@ -667,8 +671,27 @@ function ABTNode(options) {
667
671
  getReceivers: teamAPI.getReceivers.bind(teamAPI),
668
672
 
669
673
  // AuditLog
670
- createAuditLog: (params) => states.auditLog.create(params, instance),
674
+ createAuditLog: async (params) => {
675
+ if (!params || !params.args) {
676
+ throw new CustomError(400, 'Invalid audit log parameters');
677
+ }
678
+ const teamDid = getScope(params.args) || options.nodeDid;
679
+ if (!teamDid) {
680
+ throw new CustomError(400, 'teamDid is required for audit log creation.');
681
+ }
682
+ // 特殊情况处理:deleteBlocklet/installBlocklet 或 server 自身操作
683
+ if (['deleteBlocklet', 'installBlocklet'].includes(params.action) || teamDid === options.nodeDid) {
684
+ const result = await states.auditLog.create(params, instance);
685
+ // 如果是 installBlocklet, 不需要提前返回, 要将 log 记录在 service 中
686
+ if (params.action !== 'installBlocklet') {
687
+ return result;
688
+ }
689
+ }
690
+ const state = await teamManager.getAuditLogState(teamDid);
691
+ return state.create(params, instance);
692
+ },
671
693
  getAuditLogs: async (params) => {
694
+ const auditLogState = await teamManager.getAuditLogState(params.scope || options.nodeDid);
672
695
  if (params.scope) {
673
696
  const blocklet = await states.blocklet.getBlocklet(params.scope);
674
697
  if (blocklet) {
@@ -684,7 +707,7 @@ function ABTNode(options) {
684
707
  }
685
708
  }
686
709
 
687
- return states.auditLog.findPaginated.call(states.auditLog, params);
710
+ return auditLogState.findPaginated.call(auditLogState, params);
688
711
  },
689
712
 
690
713
  // Insights
@@ -833,9 +856,14 @@ function ABTNode(options) {
833
856
  updateOAuthClient: teamAPI.updateOAuthClient.bind(teamAPI),
834
857
 
835
858
  updateBlockletSettings: blockletManager.updateBlockletSettings.bind(blockletManager),
859
+
860
+ // migrate audit log
861
+ migrateAuditLog: (dataDir) => {
862
+ migrateAuditLog(dataDir, states, options.nodeDid, teamManager.getAuditLogState.bind(teamManager));
863
+ },
836
864
  };
837
865
 
838
- blockletManager.createAuditLog = (params) => states.auditLog.create(params, instance);
866
+ blockletManager.createAuditLog = (params) => instance.createAuditLog(params);
839
867
 
840
868
  const events = createEvents({
841
869
  blockletManager,
@@ -20,6 +20,7 @@ const logger = require('@abtnode/logger')('@abtnode/core:states:audit-log');
20
20
  const BaseState = require('./base');
21
21
 
22
22
  const { parse } = require('../util/ua');
23
+ const { getScope } = require('../util/audit-log');
23
24
 
24
25
  const getServerInfo = (info) =>
25
26
  `[${info.name}](${joinURL(process.env.NODE_ENV === 'production' ? info.routing.adminPath : '', '/settings/about')})`;
@@ -898,30 +899,6 @@ const getLogCategory = (action) => {
898
899
  }
899
900
  };
900
901
 
901
- const getScope = (args = {}) => {
902
- // this param usually means mutating an application (server or blocklet)
903
- if (args.teamDid) {
904
- return args.teamDid;
905
- }
906
-
907
- // this param usually means mutating a child component
908
- if (args.rootDid) {
909
- return args.rootDid;
910
- }
911
-
912
- // this param usually means mutating an blockle application
913
- if (args.did) {
914
- // this param usually means mutating a nested child component
915
- if (Array.isArray(args.did)) {
916
- return args.did[0];
917
- }
918
-
919
- return args.did;
920
- }
921
-
922
- return null;
923
- };
924
-
925
902
  const fixActor = (actor) => {
926
903
  if ([NODE_SERVICES.AUTH, 'blocklet'].includes(actor?.role)) {
927
904
  actor.role = '';
@@ -1068,6 +1045,75 @@ class AuditLogState extends BaseState {
1068
1045
 
1069
1046
  return super.paginate(conditions, { createdAt: -1 }, { pageSize: 20, ...paging });
1070
1047
  }
1048
+
1049
+ /* 数据迁移使用到的方法 */
1050
+ async insertBlockletAuditLogs(data) {
1051
+ if (!Array.isArray(data) || data.length === 0) {
1052
+ return { successIds: [], failedIds: [] };
1053
+ }
1054
+
1055
+ // 清除计数缓存,确保后续 count() 查询准确
1056
+ await this.clearCountCache();
1057
+
1058
+ // 记录所有待插入的 ID
1059
+ const allIds = data.map((x) => x.id);
1060
+
1061
+ /**
1062
+ * bulkCreate 参数说明:
1063
+ *
1064
+ * 1. ignoreDuplicates: true
1065
+ * - 生成 INSERT IGNORE (MySQL) 或 ON CONFLICT DO NOTHING (PostgreSQL)
1066
+ * - 遇到唯一键冲突时跳过该条记录,继续插入其他记录
1067
+ * - 核心参数:实现"部分失败不影响其他记录"
1068
+ *
1069
+ * 2. validate: false
1070
+ * - 跳过 Sequelize 模型层的验证逻辑
1071
+ * - 直接交给数据库处理约束检查
1072
+ * - 提升批量插入性能(无需逐条验证)
1073
+ *
1074
+ * 3. returning: true
1075
+ * - 插入后返回完整的记录数据(包含自动生成的字段)
1076
+ * - 用于获取成功插入的记录 ID
1077
+ * - PostgreSQL 原生支持,MySQL 通过额外查询实现
1078
+ */
1079
+ const result = await this.model.bulkCreate(data, {
1080
+ ignoreDuplicates: true,
1081
+ validate: false,
1082
+ returning: true,
1083
+ });
1084
+
1085
+ const insertedIds = result.map((x) => x.id).filter(Boolean);
1086
+
1087
+ // 全部插入成功,无需额外查询
1088
+ if (insertedIds.length === allIds.length) {
1089
+ return { successIds: allIds, failedIds: [] };
1090
+ }
1091
+
1092
+ // 计算未成功插入的 ID
1093
+ const notInsertedIds = allIds.filter((id) => !insertedIds.includes(id));
1094
+
1095
+ // 批量查询未插入的 ID,判断是否已存在(已存在的也算成功)
1096
+ const existingRecords = await this.find({
1097
+ where: { id: { [Op.in]: notInsertedIds } },
1098
+ attributes: ['id'],
1099
+ });
1100
+
1101
+ const existingIds = existingRecords.map((x) => x.id);
1102
+ const failedIds = notInsertedIds.filter((id) => !existingIds.includes(id));
1103
+
1104
+ // successIds = 本次插入成功的 + 已存在的(都算成功)
1105
+ const successIds = [...insertedIds, ...existingIds];
1106
+
1107
+ if (failedIds.length > 0) {
1108
+ logger.error(`Found ${failedIds.length} truly failed records`, { failedIds });
1109
+ }
1110
+
1111
+ return { successIds, failedIds };
1112
+ }
1113
+
1114
+ removeBlockletAuditLogs(ids) {
1115
+ return this.remove({ where: { id: { [Op.in]: ids } } });
1116
+ }
1071
1117
  }
1072
1118
 
1073
1119
  module.exports = AuditLogState;
@@ -59,6 +59,7 @@ const States = {
59
59
 
60
60
  AccessKey: require('../states/access-key'),
61
61
  Org: require('../states/org'),
62
+ AuditLog: require('../states/audit-log'),
62
63
  };
63
64
 
64
65
  const getDefaultTeamState = () => ({
@@ -85,6 +86,7 @@ const getDefaultTeamState = () => ({
85
86
 
86
87
  accessKey: null,
87
88
  org: null,
89
+ auditLog: null,
88
90
  });
89
91
 
90
92
  class TeamManager extends EventEmitter {
@@ -161,6 +163,7 @@ class TeamManager extends EventEmitter {
161
163
 
162
164
  accessKey: await this.createState(this.nodeDid, 'AccessKey'),
163
165
  Org: await this.createState(this.nodeDid, 'Org'),
166
+ AuditLog: await this.createState(this.nodeDid, 'AuditLog'),
164
167
  };
165
168
  }
166
169
 
@@ -204,6 +207,10 @@ class TeamManager extends EventEmitter {
204
207
  return this.getState(teamDid, 'org');
205
208
  }
206
209
 
210
+ getAuditLogState(teamDid) {
211
+ return this.getState(teamDid, 'auditLog');
212
+ }
213
+
207
214
  async getNotificationState(teamDid) {
208
215
  const state = await this.getState(teamDid ?? this.nodeDid, 'notification');
209
216
  state.setDefaultSender(this.nodeDid);
@@ -0,0 +1,27 @@
1
+ const getScope = (args = {}) => {
2
+ // this param usually means mutating an application (server or blocklet)
3
+ if (args.teamDid) {
4
+ return args.teamDid;
5
+ }
6
+
7
+ // this param usually means mutating a child component
8
+ if (args.rootDid) {
9
+ return args.rootDid;
10
+ }
11
+
12
+ // this param usually means mutating a blockle application
13
+ if (args.did) {
14
+ // this param usually means mutating a nested child component
15
+ if (Array.isArray(args.did)) {
16
+ return args.did[0];
17
+ }
18
+
19
+ return args.did;
20
+ }
21
+
22
+ return null;
23
+ };
24
+
25
+ module.exports = {
26
+ getScope,
27
+ };
@@ -0,0 +1,322 @@
1
+ /**
2
+ * 这个文件是为了将 audit log 数据从 server 的表迁移至各自的 services 表中.
3
+ * 迁移流程:
4
+ * 1. 读取存在的 blocklet
5
+ * 2. 基于 blocklet 在 server 的 audit log 表中查询自己数据
6
+ * 3. 将数据插入到 blocklet 的 audit log 表中
7
+ * 4. 将迁移记录写入 .audit-log-migration.lock 文件中(JSON 格式,gzip 压缩)
8
+ * 5. 删除 server 的 audit log 表中的数据 只删除迁移成功的数据
9
+ */
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const zlib = require('zlib');
14
+ const uniq = require('lodash/uniq');
15
+
16
+ const logger = require('@abtnode/logger')('migrate-audit-log');
17
+
18
+ const PAGE_SIZE = 100;
19
+ const INSERT_BATCH_SIZE = 100;
20
+ const MIGRATION_CONCURRENCY = 5;
21
+
22
+ function getMigrationRecordFile(dataDir) {
23
+ const folderPath = dataDir || process.env.ABT_NODE_DATA_DIR;
24
+ if (!folderPath) {
25
+ return null;
26
+ }
27
+ return path.join(folderPath, 'core', 'audit-log-migration.lock');
28
+ }
29
+
30
+ /**
31
+ * 创建迁移上下文,包含所有需要访问文件路径的方法
32
+ * @param {string} migrationRecordFile - 迁移记录文件的完整路径
33
+ * @returns {object} 包含 writeMigrationRecord, readMigrationRecord, auditLogIsMigrated 方法的对象
34
+ */
35
+ function createMigrationContext(migrationRecordFile) {
36
+ function writeMigrationRecord(data) {
37
+ try {
38
+ const jsonString = JSON.stringify(data);
39
+ const compressed = zlib.gzipSync(jsonString);
40
+ fs.writeFileSync(migrationRecordFile, compressed);
41
+ } catch (error) {
42
+ logger.error('Failed to write migration record:', error);
43
+ throw error;
44
+ }
45
+ }
46
+
47
+ function readMigrationRecord() {
48
+ if (!fs.existsSync(migrationRecordFile)) {
49
+ return null;
50
+ }
51
+ const compressed = fs.readFileSync(migrationRecordFile);
52
+ const decompressed = zlib.gunzipSync(compressed);
53
+ const result = JSON.parse(decompressed.toString('utf8'));
54
+ return result;
55
+ }
56
+
57
+ function auditLogIsMigrated() {
58
+ const migrationRecord = readMigrationRecord();
59
+ if (!migrationRecord) {
60
+ return false;
61
+ }
62
+ const { summary = {} } = migrationRecord || {};
63
+ const { total = 0, success = 0, skipped = 0, failed = 0 } = summary;
64
+ if (failed > 0) {
65
+ return false;
66
+ }
67
+ if (total === 0) {
68
+ return true;
69
+ }
70
+
71
+ return total === success + skipped;
72
+ }
73
+
74
+ return {
75
+ writeMigrationRecord,
76
+ readMigrationRecord,
77
+ auditLogIsMigrated,
78
+ };
79
+ }
80
+
81
+ async function getAllBlocklets(states) {
82
+ const result = await states.blocklet.find({ where: {}, attributes: ['appDid'] });
83
+ return result.map((x) => x.appDid);
84
+ }
85
+
86
+ async function getBlockletAuditLogs(states, appDid, page = 1, pageSize = PAGE_SIZE) {
87
+ const blocklet = await states.blocklet.getBlocklet(appDid);
88
+ if (!blocklet) {
89
+ return {
90
+ list: [],
91
+ paging: {
92
+ total: 0,
93
+ pageSize,
94
+ pageCount: 0,
95
+ page,
96
+ },
97
+ };
98
+ }
99
+
100
+ const params = {
101
+ scope: uniq(
102
+ [
103
+ blocklet.appDid,
104
+ blocklet.appPid,
105
+ blocklet.structV1Did,
106
+ ...(blocklet.migratedFrom || []).map((x) => x.appDid),
107
+ ].filter(Boolean)
108
+ ),
109
+ paging: { page, pageSize },
110
+ };
111
+
112
+ return states.auditLog.findPaginated.call(states.auditLog, params);
113
+ }
114
+
115
+ async function insertBlockletAuditLogs(blockletState, data) {
116
+ if (!data || data.length === 0) {
117
+ return { successIds: [], failedIds: [] };
118
+ }
119
+
120
+ // 如果数据量小于等于批次大小,直接一次性插入
121
+ if (data.length <= INSERT_BATCH_SIZE) {
122
+ const result = await blockletState.insertBlockletAuditLogs(data);
123
+ return result;
124
+ }
125
+
126
+ // 数据量大于批次大小,需要分批插入
127
+ const totalSuccessIds = [];
128
+ const totalFailedIds = [];
129
+
130
+ for (let i = 0; i < data.length; i += INSERT_BATCH_SIZE) {
131
+ const batch = data.slice(i, i + INSERT_BATCH_SIZE);
132
+ const batchNum = Math.floor(i / INSERT_BATCH_SIZE) + 1;
133
+
134
+ // eslint-disable-next-line no-await-in-loop
135
+ const result = await blockletState.insertBlockletAuditLogs(batch);
136
+
137
+ totalSuccessIds.push(...result.successIds);
138
+ totalFailedIds.push(...result.failedIds);
139
+
140
+ logger.info(
141
+ `Insert batch ${batchNum}: total: ${batch.length}, success: ${result.successIds.length}, failed: ${result.failedIds.length}`
142
+ );
143
+ }
144
+
145
+ return { successIds: totalSuccessIds, failedIds: totalFailedIds };
146
+ }
147
+
148
+ async function removeBlockletAuditLogs(states, ids = []) {
149
+ if (!ids || ids.length === 0) return 0;
150
+
151
+ // 如果 ID 数量小于等于批次大小,直接一次性删除
152
+ if (ids.length <= INSERT_BATCH_SIZE) {
153
+ const removed = await states.auditLog.removeBlockletAuditLogs(ids);
154
+ return removed;
155
+ }
156
+
157
+ // 数据量大于批次大小,需要分批删除
158
+ let totalRemoved = 0;
159
+
160
+ for (let i = 0; i < ids.length; i += INSERT_BATCH_SIZE) {
161
+ const batch = ids.slice(i, i + INSERT_BATCH_SIZE);
162
+ const batchNum = Math.floor(i / INSERT_BATCH_SIZE) + 1;
163
+
164
+ // eslint-disable-next-line no-await-in-loop
165
+ const removed = await states.auditLog.removeBlockletAuditLogs(batch);
166
+ totalRemoved += removed;
167
+
168
+ logger.info(`Remove batch ${batchNum}: removed ${removed} audit logs`);
169
+ }
170
+
171
+ return totalRemoved;
172
+ }
173
+
174
+ async function migrateBlocklet(states, appDid, nodeDid, blockletState) {
175
+ const startTime = Date.now();
176
+
177
+ if (appDid === nodeDid) {
178
+ return { status: 'skipped', auditLogsTotal: 0, successMigrated: 0, skipped: 0, duration: '0.00s' };
179
+ }
180
+
181
+ let page = 1;
182
+ let totalFound = 0;
183
+ let totalMigrated = 0;
184
+ let hasNextPage = true;
185
+
186
+ while (hasNextPage) {
187
+ // eslint-disable-next-line no-await-in-loop
188
+ const result = await getBlockletAuditLogs(states, appDid, page);
189
+ const { list: auditLogs, paging } = result;
190
+
191
+ logger.info(`Found ${auditLogs.length}/${paging.total} audit logs for blocklet ${appDid}, page: ${page}`);
192
+
193
+ if (!auditLogs || auditLogs.length === 0) {
194
+ hasNextPage = false;
195
+ break;
196
+ }
197
+
198
+ totalFound += auditLogs.length;
199
+
200
+ // eslint-disable-next-line no-await-in-loop
201
+ const insertResult = await insertBlockletAuditLogs(blockletState, auditLogs);
202
+ const { successIds } = insertResult;
203
+
204
+ // eslint-disable-next-line no-await-in-loop
205
+ const removeRows = await removeBlockletAuditLogs(states, successIds);
206
+
207
+ totalMigrated += successIds.length;
208
+
209
+ // 根据删除结果决定下一页查询策略
210
+ const allDeleted = removeRows === successIds.length;
211
+ if (allDeleted) {
212
+ // 删除成功,继续查询第一页(因为数据已被删除,第一页会有新数据)
213
+ page = 1;
214
+ } else {
215
+ // 删除失败,查询下一页(避免重复处理)
216
+ page++;
217
+ logger.warn(
218
+ `Delete incomplete for blocklet ${appDid}: migrated ${successIds.length}, deleted ${removeRows} - move to page ${page}`
219
+ );
220
+ }
221
+
222
+ hasNextPage = paging && paging.page < paging.pageCount;
223
+ }
224
+
225
+ const durationMs = Date.now() - startTime;
226
+ const duration = `${(durationMs / 1000).toFixed(2)}s`;
227
+ const skipped = totalFound - totalMigrated;
228
+
229
+ if (totalFound > 0) {
230
+ logger.info(`Blocklet ${appDid} migration completed: ${totalMigrated}/${totalFound} migrated in ${duration}`);
231
+ }
232
+
233
+ return totalFound === 0
234
+ ? { status: 'skipped', auditLogsTotal: 0, successMigrated: 0, skipped: 0, duration }
235
+ : { status: 'success', auditLogsTotal: totalFound, successMigrated: totalMigrated, skipped, duration };
236
+ }
237
+
238
+ async function runWithConcurrency(tasks, concurrency = MIGRATION_CONCURRENCY) {
239
+ const results = [];
240
+ for (let i = 0; i < tasks.length; i += concurrency) {
241
+ const batch = tasks.slice(i, i + concurrency);
242
+ // eslint-disable-next-line no-await-in-loop
243
+ const batchResults = await Promise.allSettled(batch.map((task) => task()));
244
+ results.push(...batchResults);
245
+ }
246
+ return results;
247
+ }
248
+
249
+ async function migrateAuditLog(dataDir, states, nodeDid, getBlockletStates) {
250
+ const totalStartTime = Date.now();
251
+
252
+ try {
253
+ const migrationRecordFile = getMigrationRecordFile(dataDir);
254
+ if (!migrationRecordFile) {
255
+ logger.info('The file path does not exist, skipping...');
256
+ return;
257
+ }
258
+ const ctx = createMigrationContext(migrationRecordFile);
259
+
260
+ const isMigrated = ctx.auditLogIsMigrated();
261
+ if (isMigrated) {
262
+ logger.info('Audit log migration has already been completed, skipping...');
263
+ return;
264
+ }
265
+
266
+ const blocklets = await getAllBlocklets(states);
267
+ const record = { summary: { total: blocklets.length, success: 0, skipped: 0, failed: 0, totalDuration: '0.00s' } };
268
+
269
+ logger.info(`Starting migration for ${blocklets.length} blocklets with concurrency of ${MIGRATION_CONCURRENCY}`);
270
+
271
+ const tasks = blocklets.map((appDid) => async () => {
272
+ // eslint-disable-next-line no-useless-catch
273
+ try {
274
+ const blockletState = await getBlockletStates(appDid);
275
+ const result = await migrateBlocklet(states, appDid, nodeDid, blockletState);
276
+ return { appDid, ...result };
277
+ } catch (error) {
278
+ logger.error(`Failed to migrate audit log for blocklet ${appDid}`, error);
279
+ throw error;
280
+ }
281
+ });
282
+
283
+ const results = await runWithConcurrency(tasks, MIGRATION_CONCURRENCY);
284
+
285
+ results.forEach((result) => {
286
+ if (result.status === 'fulfilled') {
287
+ const { appDid, status, duration, ...data } = result.value;
288
+ if (status === 'skipped') {
289
+ record.summary.skipped++;
290
+ } else {
291
+ record.summary.success++;
292
+ }
293
+ record[appDid] = { ...data, duration, migratedAt: new Date().toISOString() };
294
+ } else {
295
+ const { appDid, error } = result.reason;
296
+ record.summary.failed++;
297
+ record[appDid] = {
298
+ auditLogsTotal: 0,
299
+ successMigrated: 0,
300
+ skipped: 0,
301
+ duration: '0.00s',
302
+ migratedAt: new Date().toISOString(),
303
+ };
304
+ logger.error(`Failed to migrate audit log for blocklet ${appDid}`, error);
305
+ }
306
+ });
307
+
308
+ const totalDurationMs = Date.now() - totalStartTime;
309
+ const totalDuration = `${(totalDurationMs / 1000).toFixed(2)}s`;
310
+ record.summary.totalDuration = totalDuration;
311
+ record.summary.updatedAt = new Date().toISOString();
312
+
313
+ ctx.writeMigrationRecord(record);
314
+ logger.info(`Audit log migration completed, total time: ${totalDuration}`);
315
+ } catch (error) {
316
+ logger.error('Failed to migrate audit log', error);
317
+ }
318
+ }
319
+
320
+ module.exports = {
321
+ migrateAuditLog,
322
+ };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.16.53",
6
+ "version": "1.16.54-beta-20251017-133309-7d40faa6",
7
7
  "description": "",
8
8
  "main": "lib/index.js",
9
9
  "files": [
@@ -21,21 +21,21 @@
21
21
  "author": "wangshijun <wangshijun2010@gmail.com> (http://github.com/wangshijun)",
22
22
  "license": "Apache-2.0",
23
23
  "dependencies": {
24
- "@abtnode/analytics": "1.16.53",
25
- "@abtnode/auth": "1.16.53",
26
- "@abtnode/certificate-manager": "1.16.53",
27
- "@abtnode/constant": "1.16.53",
28
- "@abtnode/cron": "1.16.53",
29
- "@abtnode/db-cache": "1.16.53",
30
- "@abtnode/docker-utils": "1.16.53",
31
- "@abtnode/logger": "1.16.53",
32
- "@abtnode/models": "1.16.53",
33
- "@abtnode/queue": "1.16.53",
34
- "@abtnode/rbac": "1.16.53",
35
- "@abtnode/router-provider": "1.16.53",
36
- "@abtnode/static-server": "1.16.53",
37
- "@abtnode/timemachine": "1.16.53",
38
- "@abtnode/util": "1.16.53",
24
+ "@abtnode/analytics": "1.16.54-beta-20251017-133309-7d40faa6",
25
+ "@abtnode/auth": "1.16.54-beta-20251017-133309-7d40faa6",
26
+ "@abtnode/certificate-manager": "1.16.54-beta-20251017-133309-7d40faa6",
27
+ "@abtnode/constant": "1.16.54-beta-20251017-133309-7d40faa6",
28
+ "@abtnode/cron": "1.16.54-beta-20251017-133309-7d40faa6",
29
+ "@abtnode/db-cache": "1.16.54-beta-20251017-133309-7d40faa6",
30
+ "@abtnode/docker-utils": "1.16.54-beta-20251017-133309-7d40faa6",
31
+ "@abtnode/logger": "1.16.54-beta-20251017-133309-7d40faa6",
32
+ "@abtnode/models": "1.16.54-beta-20251017-133309-7d40faa6",
33
+ "@abtnode/queue": "1.16.54-beta-20251017-133309-7d40faa6",
34
+ "@abtnode/rbac": "1.16.54-beta-20251017-133309-7d40faa6",
35
+ "@abtnode/router-provider": "1.16.54-beta-20251017-133309-7d40faa6",
36
+ "@abtnode/static-server": "1.16.54-beta-20251017-133309-7d40faa6",
37
+ "@abtnode/timemachine": "1.16.54-beta-20251017-133309-7d40faa6",
38
+ "@abtnode/util": "1.16.54-beta-20251017-133309-7d40faa6",
39
39
  "@aigne/aigne-hub": "^0.10.1",
40
40
  "@arcblock/did": "1.25.6",
41
41
  "@arcblock/did-connect-js": "1.25.6",
@@ -47,16 +47,16 @@
47
47
  "@arcblock/pm2-events": "^0.0.5",
48
48
  "@arcblock/validator": "1.25.6",
49
49
  "@arcblock/vc": "1.25.6",
50
- "@blocklet/constant": "1.16.53",
51
- "@blocklet/did-space-js": "^1.1.33",
52
- "@blocklet/env": "1.16.53",
50
+ "@blocklet/constant": "1.16.54-beta-20251017-133309-7d40faa6",
51
+ "@blocklet/did-space-js": "^1.1.34",
52
+ "@blocklet/env": "1.16.54-beta-20251017-133309-7d40faa6",
53
53
  "@blocklet/error": "^0.2.5",
54
- "@blocklet/meta": "1.16.53",
55
- "@blocklet/resolver": "1.16.53",
56
- "@blocklet/sdk": "1.16.53",
57
- "@blocklet/server-js": "1.16.53",
58
- "@blocklet/store": "1.16.53",
59
- "@blocklet/theme": "^3.1.49",
54
+ "@blocklet/meta": "1.16.54-beta-20251017-133309-7d40faa6",
55
+ "@blocklet/resolver": "1.16.54-beta-20251017-133309-7d40faa6",
56
+ "@blocklet/sdk": "1.16.54-beta-20251017-133309-7d40faa6",
57
+ "@blocklet/server-js": "1.16.54-beta-20251017-133309-7d40faa6",
58
+ "@blocklet/store": "1.16.54-beta-20251017-133309-7d40faa6",
59
+ "@blocklet/theme": "^3.1.51",
60
60
  "@fidm/x509": "^1.2.1",
61
61
  "@ocap/mcrypto": "1.25.6",
62
62
  "@ocap/util": "1.25.6",
@@ -120,5 +120,5 @@
120
120
  "jest": "^29.7.0",
121
121
  "unzipper": "^0.10.11"
122
122
  },
123
- "gitHead": "7c37a9bd063192fb0a14921b9d0317de953016ff"
123
+ "gitHead": "78c11632ed47a75502a20c257568fc9e1655ca05"
124
124
  }