w-web-api 1.0.76 → 1.0.78

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.
Files changed (54) hide show
  1. package/dist/css/{app.ae472c4d.css → app.9fbdb016.css} +1 -1
  2. package/dist/index.tmp +1 -1
  3. package/dist/js/app-legacy.075a8cf9.js +1 -0
  4. package/dist/js/app.075a8cf9.js +1 -0
  5. package/dist/js/{chunk-vendors.47cf6375.js → chunk-vendors-legacy.de0404f6.js} +1 -1
  6. package/dist/js/{chunk-vendors-legacy.47cf6375.js → chunk-vendors.de0404f6.js} +1 -1
  7. package/dist/w-web-api.umd.js +2 -2
  8. package/dist/w-web-api.umd.js.map +1 -1
  9. package/docs/WWebApi.html +122 -2
  10. package/docs/index.html +1 -1
  11. package/docs/server_WWebApi.mjs.html +5 -2
  12. package/docs/src_components_Common_CheckYesNo.vue.html +1 -1
  13. package/package.json +16 -14
  14. package/server/WWebApi.mjs +4 -1
  15. package/server/procApis.mjs +26 -14
  16. package/server/procLang.mjs +0 -17
  17. package/server/srLog.mjs +13 -4
  18. package/server/staLogs/staEvent.mjs +8 -120
  19. package/server/staLogs/staLogsCore.callWorker.mjs +78 -0
  20. package/server/staLogs/staLogsCore.mjs +420 -0
  21. package/server/staLogs/staLogsCore.shellWorker.mjs +20 -0
  22. package/server/staLogs/test_staEvent.mjs +23 -7
  23. package/settings.json +1 -0
  24. package/src/components/LayoutContentEdit.vue +14 -2
  25. package/src/components/LayoutContentStats.vue +445 -557
  26. package/tableTags.json +1 -1
  27. package/dist/js/app-legacy.2aa145e5.js +0 -1
  28. package/dist/js/app.2aa145e5.js +0 -1
  29. package/logs/2026-06-19T16.log +0 -6
  30. package/logs/2026-06-20T11.log +0 -4
  31. package/logs/2026-06-20T14.log +0 -2
  32. package/logs/2026-06-21T20.log +0 -1
  33. package/logs/2026-06-21T21.log +0 -4
  34. package/logs/2026-06-22T07.log +0 -12
  35. package/logs/2026-06-22T08.log +0 -808
  36. package/logs/2026-06-22T09.log +0 -104
  37. package/logs/2026-06-22T10.log +0 -26
  38. package/logs/2026-06-22T11.log +0 -860
  39. package/logs/2026-06-22T12.log +0 -430
  40. package/logs/2026-06-22T23.log +0 -1547
  41. package/logs/2026-06-23T00.log +0 -473
  42. package/logs/2026-06-23T08.log +0 -231
  43. package/logs/2026-06-23T09.log +0 -179
  44. package/logs/2026-07-01T11.log +0 -488
  45. package/logs/2026-07-09T22.log +0 -370
  46. package/logs/2026-07-09T23.log +0 -370
  47. package/logs/2026-07-10T12.log +0 -284
  48. package/logs/2026-07-10T13.log +0 -777
  49. package/logs/2026-07-11T17.log +0 -2
  50. package/logs/2026-07-23T16.log +0 -856
  51. package/logs/2026-08-19T22.log +0 -0
  52. package/logs/2026-08-25T20.log +0 -0
  53. package/logs/2026-08-25T21.log +0 -1891
  54. package/z/344/277/256/346/255/243/350/246/217/345/212/203/346/233/270.md +0 -246
package/docs/WWebApi.html CHANGED
@@ -80,7 +80,7 @@
80
80
 
81
81
  <dt class="tag-source">Source:</dt>
82
82
  <dd class="tag-source"><ul class="dummy"><li>
83
- <a href="server_WWebApi.mjs.html">server/WWebApi.mjs</a>, <a href="server_WWebApi.mjs.html#line136">line 136</a>
83
+ <a href="server_WWebApi.mjs.html">server/WWebApi.mjs</a>, <a href="server_WWebApi.mjs.html#line139">line 139</a>
84
84
  </li></ul></dd>
85
85
 
86
86
 
@@ -871,6 +871,126 @@ instWWebApi.on('error', (err) => {
871
871
  </tr>
872
872
 
873
873
 
874
+
875
+ <tr>
876
+
877
+ <td class="name"><code>logFd</code></td>
878
+
879
+
880
+ <td class="type">
881
+
882
+
883
+ <span class="param-type">String</span>
884
+
885
+
886
+
887
+
888
+ </td>
889
+
890
+
891
+ <td class="attributes">
892
+
893
+ &lt;optional><br>
894
+
895
+
896
+
897
+
898
+
899
+ </td>
900
+
901
+
902
+
903
+ <td class="default">
904
+
905
+ <code>'./logs'</code>
906
+
907
+ </td>
908
+
909
+
910
+ <td class="description last"><p>輸入log紀錄儲存位置字串(srLog 寫入與統計掃描共用),預設'./logs'</p></td>
911
+ </tr>
912
+
913
+
914
+
915
+ <tr>
916
+
917
+ <td class="name"><code>logInterval</code></td>
918
+
919
+
920
+ <td class="type">
921
+
922
+
923
+ <span class="param-type">String</span>
924
+
925
+
926
+
927
+
928
+ </td>
929
+
930
+
931
+ <td class="attributes">
932
+
933
+ &lt;optional><br>
934
+
935
+
936
+
937
+
938
+
939
+ </td>
940
+
941
+
942
+
943
+ <td class="default">
944
+
945
+ <code>'hr'</code>
946
+
947
+ </td>
948
+
949
+
950
+ <td class="description last"><p>輸入log紀錄檔案拆檔時長字串,可選'hr'或'day',預設'hr'</p></td>
951
+ </tr>
952
+
953
+
954
+
955
+ <tr>
956
+
957
+ <td class="name"><code>logNumKeep</code></td>
958
+
959
+
960
+ <td class="type">
961
+
962
+
963
+ <span class="param-type">Integer</span>
964
+
965
+
966
+
967
+
968
+ </td>
969
+
970
+
971
+ <td class="attributes">
972
+
973
+ &lt;optional><br>
974
+
975
+
976
+
977
+
978
+
979
+ </td>
980
+
981
+
982
+
983
+ <td class="default">
984
+
985
+ <code>null</code>
986
+
987
+ </td>
988
+
989
+
990
+ <td class="description last"><p>輸入log檔保留數上限正整數,超出者自最舊刪除,未給採w-syslog預設(hr模式365*24、day模式365),預設null</p></td>
991
+ </tr>
992
+
993
+
874
994
  </tbody>
875
995
  </table>
876
996
 
@@ -959,7 +1079,7 @@ instWWebApi.on('error', (err) => {
959
1079
  <br class="clear">
960
1080
 
961
1081
  <footer>
962
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Wed Aug 26 2026 18:01:59 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
1082
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Wed Sep 02 2026 00:31:20 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
963
1083
  </footer>
964
1084
 
965
1085
  <script>prettyPrint();</script>
package/docs/index.html CHANGED
@@ -71,7 +71,7 @@
71
71
  <br class="clear">
72
72
 
73
73
  <footer>
74
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Wed Aug 26 2026 18:01:59 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
74
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Wed Sep 02 2026 00:31:20 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
75
75
  </footer>
76
76
 
77
77
  <script>prettyPrint();</script>
@@ -100,6 +100,9 @@ import ds from '../src/schema/index.mjs'
100
100
  * @param {String} [opt.webLogo=''] 輸入站台logo字串,採base64格式,預設''
101
101
  * @param {String} [opt.subfolder=''] 輸入站台所在子目錄字串,提供站台位於內網採反向代理進行服務時,故需支援位於子目錄情形,預設''
102
102
  * @param {String} [opt.urlRedirect=''] 輸入錯誤時自動轉址字串,提供站台例如無法登入或驗證失敗時須自動轉址,預設''
103
+ * @param {String} [opt.logFd='./logs'] 輸入log紀錄儲存位置字串(srLog 寫入與統計掃描共用),預設'./logs'
104
+ * @param {String} [opt.logInterval='hr'] 輸入log紀錄檔案拆檔時長字串,可選'hr'或'day',預設'hr'
105
+ * @param {Integer} [opt.logNumKeep=null] 輸入log檔保留數上限正整數,超出者自最舊刪除,未給採w-syslog預設(hr模式365*24、day模式365),預設null
103
106
  * @returns {Object} 回傳物件,其內server為hapi伺服器實體,wsrv為w-converhp的伺服器事件物件,wsds為w-serv-webdata的伺服器事件物件,可監聽error事件
104
107
  * @example
105
108
  *
@@ -955,7 +958,7 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
955
958
  //統計資訊頁:回傳各 event 之時段發生頻率(讀 logFd 之 log)。連線之 token 認證由 verifyConn 把關;
956
959
  //授權(限管理員)屬套件使用者責任(見 verifyConn 註解與設計要點與取捨「套件授權責任」)。timeLength 天數 / timeInterval 'hr'|'day'。
957
960
  getStaEvent: wrapErrLog('getStaEvent', (userId, timeLength = 7, timeInterval = 'hr') => {
958
- return staEvent(timeLength, timeInterval, { fdLog: logFd })
961
+ return staEvent(timeLength, timeInterval, { fdLog: logFd, srLog })
959
962
  }),
960
963
 
961
964
  // getAPIsList: (userId, query = {}) => { //待提供api管理系統界面使用
@@ -989,7 +992,7 @@ export default WWebApi
989
992
  <br class="clear">
990
993
 
991
994
  <footer>
992
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Wed Aug 26 2026 18:01:59 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
995
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Wed Sep 02 2026 00:31:20 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
993
996
  </footer>
994
997
 
995
998
  <script>prettyPrint();</script>
@@ -174,7 +174,7 @@ export default {
174
174
  <br class="clear">
175
175
 
176
176
  <footer>
177
- Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Wed Aug 26 2026 18:01:59 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
177
+ Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Wed Sep 02 2026 00:31:20 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
178
178
  </footer>
179
179
 
180
180
  <script>prettyPrint();</script>
package/package.json CHANGED
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "name": "w-web-api",
3
- "version": "1.0.76",
3
+ "version": "1.0.78",
4
4
  "main": "dist/w-web-api.umd.js",
5
5
  "dependencies": {
6
6
  "axios": "^1.18.1",
7
7
  "json5": "^2.2.3",
8
- "w-component-vue": "^2.5.5",
9
- "w-data-collector": "^1.0.33",
10
- "w-echarts-vue": "^1.0.4",
11
- "w-md2html": "^1.0.47",
12
- "w-serv-hapi": "^1.0.83",
13
- "w-serv-orm": "^1.0.50",
14
- "w-syslog": "^1.0.22",
15
- "w-ui-loginout": "^1.0.21",
16
- "wsemi": "^1.8.80"
8
+ "w-component-vue": "^2.5.8",
9
+ "w-data-collector": "^1.0.34",
10
+ "w-echarts-vue": "^1.0.6",
11
+ "w-md2html": "^1.0.48",
12
+ "w-serv-hapi": "^1.0.84",
13
+ "w-serv-orm": "^1.0.51",
14
+ "w-syslog": "^1.0.23",
15
+ "w-ui-loginout": "^1.0.22",
16
+ "wsemi": "^1.8.81"
17
17
  },
18
18
  "devDependencies": {
19
19
  "@vue/cli-plugin-babel": "^5.0.8",
@@ -28,16 +28,18 @@
28
28
  "eslint-plugin-vue": "^9.10.0",
29
29
  "vue": "^2.7.16",
30
30
  "vuex": "^3.6.2",
31
- "w-orm-lmdb": "^1.0.27",
31
+ "w-orm-lmdb": "^1.0.28",
32
32
  "w-package-tools": "^1.1.15",
33
33
  "w-package-tools-e2e": "^1.0.0",
34
- "w-screenctl": "^1.0.15"
34
+ "w-screenctl": "^1.0.16"
35
35
  },
36
36
  "scripts": {
37
37
  "serve": "vue-cli-service serve",
38
38
  "build": "vue-cli-service build",
39
- "test:unit": "node server/staLogs/test_staEvent.mjs",
40
- "test": "node server/staLogs/test_staEvent.mjs && mocha --timeout 300000",
39
+ "test:unit": "node server/staLogs/test_staEvent.mjs && mocha test/unit-*.test.mjs --timeout 300000",
40
+ "test:apiunit": "node server/staLogs/test_staEvent.mjs && mocha test/unit-*.test.mjs test/api-*.test.mjs --timeout 300000",
41
+ "test:e2e": "node test/run-e2e-isolated.mjs",
42
+ "test": "npm run test:apiunit && npm run test:e2e",
41
43
  "lint": "vue-cli-service lint",
42
44
  "deploy": "gh-pages -d docs"
43
45
  },
@@ -53,6 +53,9 @@ import ds from '../src/schema/index.mjs'
53
53
  * @param {String} [opt.webLogo=''] 輸入站台logo字串,採base64格式,預設''
54
54
  * @param {String} [opt.subfolder=''] 輸入站台所在子目錄字串,提供站台位於內網採反向代理進行服務時,故需支援位於子目錄情形,預設''
55
55
  * @param {String} [opt.urlRedirect=''] 輸入錯誤時自動轉址字串,提供站台例如無法登入或驗證失敗時須自動轉址,預設''
56
+ * @param {String} [opt.logFd='./logs'] 輸入log紀錄儲存位置字串(srLog 寫入與統計掃描共用),預設'./logs'
57
+ * @param {String} [opt.logInterval='hr'] 輸入log紀錄檔案拆檔時長字串,可選'hr'或'day',預設'hr'
58
+ * @param {Integer} [opt.logNumKeep=null] 輸入log檔保留數上限正整數,超出者自最舊刪除,未給採w-syslog預設(hr模式365*24、day模式365),預設null
56
59
  * @returns {Object} 回傳物件,其內server為hapi伺服器實體,wsrv為w-converhp的伺服器事件物件,wsds為w-serv-webdata的伺服器事件物件,可監聽error事件
57
60
  * @example
58
61
  *
@@ -908,7 +911,7 @@ function WWebApi(WOrm, url, db, getUserByToken, verifyClientUser, verifyAppUser,
908
911
  //統計資訊頁:回傳各 event 之時段發生頻率(讀 logFd 之 log)。連線之 token 認證由 verifyConn 把關;
909
912
  //授權(限管理員)屬套件使用者責任(見 verifyConn 註解與設計要點與取捨「套件授權責任」)。timeLength 天數 / timeInterval 'hr'|'day'。
910
913
  getStaEvent: wrapErrLog('getStaEvent', (userId, timeLength = 7, timeInterval = 'hr') => {
911
- return staEvent(timeLength, timeInterval, { fdLog: logFd })
914
+ return staEvent(timeLength, timeInterval, { fdLog: logFd, srLog })
912
915
  }),
913
916
 
914
917
  // getAPIsList: (userId, query = {}) => { //待提供api管理系統界面使用
@@ -1,5 +1,6 @@
1
1
  import iseobj from 'wsemi/src/iseobj.mjs'
2
2
  import isestr from 'wsemi/src/isestr.mjs'
3
+ import pmKeyMutex from 'wsemi/src/pmKeyMutex.mjs'
3
4
 
4
5
 
5
6
  function procApis(deps = {}) {
@@ -7,6 +8,10 @@ function procApis(deps = {}) {
7
8
  //deps
8
9
  let { woItems, procOrm, ds } = deps
9
10
 
11
+ //kmx: 後端層雙擊 / 並發防護 (CLAUDE.md 三層防護之第 3 層): 同 key 之寫入序列化, 防 API 直打 / e2e race 繞過前端
12
+ // key: 既有筆 `saveApi:<id>` / `deleteApi:<id>`; 新增筆無 id 以 `saveApi:new:<name>` 占位 (同名並發新增第 2 次仍序列化執行, 由呼叫端 / 後續唯一性檢查決定是否拒絕)
13
+ let kmx = pmKeyMutex()
14
+
10
15
 
11
16
  let getApisList = async (userId) => {
12
17
  return await woItems.apis.select({})
@@ -20,21 +25,27 @@ function procApis(deps = {}) {
20
25
  return Promise.reject('errApiRowInvalid')
21
26
  }
22
27
 
23
- //正規化:funNew 配 id/時間/預設並過濾欄位
24
- let o = ds.apis.funNew(row)
28
+ //key
29
+ let key = isestr(row.id) ? `saveApi:${row.id}` : `saveApi:new:${String(row.name || '')}`
30
+
31
+ return kmx(key, async () => {
32
+
33
+ //正規化:funNew 配 id/時間/預設並過濾欄位
34
+ let o = ds.apis.funNew(row)
25
35
 
26
- //保留原 id 與創建時間(既有筆)
27
- if (isestr(row.id)) {
28
- o.id = row.id
29
- if (isestr(row.timeCreate)) {
30
- o.timeCreate = row.timeCreate
36
+ //保留原 id 與創建時間(既有筆)
37
+ if (isestr(row.id)) {
38
+ o.id = row.id
39
+ if (isestr(row.timeCreate)) {
40
+ o.timeCreate = row.timeCreate
41
+ }
31
42
  }
32
- }
33
43
 
34
- //save
35
- let r = await procOrm(userId, 'apis', 'save', [o])
44
+ //save
45
+ let r = await procOrm(userId, 'apis', 'save', [o])
36
46
 
37
- return r
47
+ return r
48
+ })
38
49
  }
39
50
 
40
51
 
@@ -45,9 +56,10 @@ function procApis(deps = {}) {
45
56
  return Promise.reject('errApiIdInvalid')
46
57
  }
47
58
 
48
- let r = await procOrm(userId, 'apis', 'del', { id })
49
-
50
- return r
59
+ return kmx(`deleteApi:${id}`, async () => {
60
+ let r = await procOrm(userId, 'apis', 'del', { id })
61
+ return r
62
+ })
51
63
  }
52
64
 
53
65
 
@@ -2,7 +2,6 @@ import get from 'lodash-es/get.js'
2
2
  import each from 'lodash-es/each.js'
3
3
  import iseobj from 'wsemi/src/iseobj.mjs'
4
4
 
5
-
6
5
  let kpLang = {
7
6
 
8
7
  csIng: {
@@ -26,7 +25,6 @@ let kpLang = {
26
25
  cht: '拒絕登入',
27
26
  },
28
27
 
29
-
30
28
  systemMessage: {
31
29
  eng: 'System message',
32
30
  cht: '系統確認訊息',
@@ -178,7 +176,6 @@ let kpLang = {
178
176
  cht: '回傳數據範例',
179
177
  },
180
178
 
181
-
182
179
  //--- API 管理(展示/編輯/測試)---
183
180
 
184
181
  docInput: {
@@ -226,18 +223,6 @@ let kpLang = {
226
223
  eng: 'Frequency of backend logged events (APIs, kpFunExt, connection verification).',
227
224
  cht: '後端各事件(API、kpFunExt、連線驗證)之發生頻率統計。',
228
225
  },
229
- selectEvents: {
230
- eng: 'Select events',
231
- cht: '選擇事件',
232
- },
233
- selectAll: {
234
- eng: 'All',
235
- cht: '全選',
236
- },
237
- selectNone: {
238
- eng: 'Clear',
239
- cht: '清除',
240
- },
241
226
  eventStatsTable: {
242
227
  eng: 'Event Statistics',
243
228
  cht: '事件統計表',
@@ -555,7 +540,6 @@ let kpLang = {
555
540
 
556
541
  }
557
542
 
558
-
559
543
  let init = (opt = {}) => {
560
544
 
561
545
  //kpLangExt
@@ -640,5 +624,4 @@ let init = (opt = {}) => {
640
624
  return r
641
625
  }
642
626
 
643
-
644
627
  export default init
package/server/srLog.mjs CHANGED
@@ -1,5 +1,7 @@
1
1
  import get from 'lodash-es/get.js'
2
2
  import isestr from 'wsemi/src/isestr.mjs'
3
+ import ispint from 'wsemi/src/ispint.mjs'
4
+ import cint from 'wsemi/src/cint.mjs'
3
5
  import WSyslog from 'w-syslog/src/WSyslog.mjs'
4
6
 
5
7
 
@@ -18,10 +20,17 @@ let init = (opt = {}) => {
18
20
  interval = 'hr'
19
21
  }
20
22
 
21
- let srLog = WSyslog({
22
- fdLog,
23
- interval,
24
- })
23
+ //numKeep, settings logNumKeep (opt-in): 未給採 w-syslog 預設 (hr: 365*24, day: 365), 有給但非正整數視為設定錯誤
24
+ let numKeep = get(opt, 'logNumKeep', null)
25
+ let o = { fdLog, interval }
26
+ if (numKeep !== null && numKeep !== undefined && numKeep !== '') {
27
+ if (!ispint(numKeep)) {
28
+ throw new Error(`invalid logNumKeep[${numKeep}], must be positive integer`)
29
+ }
30
+ o.numKeep = cint(numKeep)
31
+ }
32
+
33
+ let srLog = WSyslog(o)
25
34
 
26
35
  return srLog
27
36
  }
@@ -1,131 +1,19 @@
1
- import ot from 'dayjs'
2
- import get from 'lodash-es/get.js'
3
- import size from 'lodash-es/size.js'
4
- import groupBy from 'lodash-es/groupBy.js'
5
- import each from 'lodash-es/each.js'
6
- import mapValues from 'lodash-es/mapValues.js'
7
- import merge from 'lodash-es/merge.js'
8
- import genPm from 'wsemi/src/genPm.mjs'
9
- import isestr from 'wsemi/src/isestr.mjs'
10
- import fsTreeFolder from 'wsemi/src/fsTreeFolder.mjs'
11
- import fsBuildReadStreamText from 'wsemi/src/fsBuildReadStreamText.mjs'
12
- import filterVpfsByWindow from './filterVpfsByWindow.mjs'
1
+ import { staLogs } from './staLogsCore.callWorker.mjs'
13
2
 
14
3
 
15
- //staEvent(通用事件頻率統計,骨架移植自 w-web-sso server/staLogs/staIp.mjs,但「不過濾特定 event」、
16
- //內層改依 `event` 欄位分組):掃 fdLog 下所有 NDJSON log 檔,依時間 bucket(hr/day)+ 各 event 名聚合次數。
17
- //輸出供前端統計圖:可呈現「全部(count)」或「各 event 各自」之時段發生頻率。
18
- //
19
- //回傳格式(時間遞增、無資料時段補 0):
4
+ //staEvent(通用事件頻率統計):掃 fdLog 下所有 NDJSON log 檔,依時間 bucket(hr/day)+ event 名聚合次數。
5
+ //自 ADR-028 起由 staLogsCore 提供:單趟掃描 + 檔級彙總快取(封閉小時檔只掃一次)+ single-flight,掃描交單一 worker(主執行緒不阻塞)。
6
+ //簽章與輸出格式不變(時間遞增、無資料時段補 { count: 0 }):
20
7
  // [
21
8
  // { time: '2026-06-22T10', data: { count: 6, 'verifyConn': 3, 'kpfun-getApisList': 2, 'kpfun-saveApi': 1 } },
22
9
  // { time: '2026-06-22T11', data: { count: 0 } },
23
10
  // ...
24
11
  // ]
25
- //
26
- //參數:
27
- // timeLength 往回統計天數(預設 7)
28
- // timeInterval 時間粒度 'hr'(每小時,預設)/ 'day'(每日)
29
- // opt.fdLog log 資料夾(預設 './logs',須與 srLog 寫入之 logFd 一致;e2e 可指向合成 log 達 hermetic)
12
+ //參數:timeLength 往回統計天數(預設 7);timeInterval 'hr'(預設)/ 'day';opt.fdLog log 資料夾(預設 './logs');opt.srLog(單檔略過時記 warn)
30
13
  async function staEvent(timeLength = 7, timeInterval = 'hr', opt = {}) {
31
-
32
- //fdLog
33
- let fdLog = get(opt, 'fdLog')
34
- if (!isestr(fdLog)) {
35
- fdLog = './logs'
36
- }
37
-
38
- //fmt
39
- let fmt = timeInterval === 'day' ? 'YYYY-MM-DD' : 'YYYY-MM-DDTHH'
40
-
41
- //unit
42
- let unit = timeInterval === 'hr' ? 'hour' : 'day'
43
-
44
- //kpTime,產生完整的時間區間(無資料時段亦補 { count: 0 },圖表 x 軸不留空洞)
45
- let now = ot()
46
- let tStart = now.subtract(timeLength, 'day')
47
- let tCurr = tStart.startOf(unit)
48
- let tEnd = now.startOf(unit)
49
- let kpTime = {}
50
- while (!tCurr.isAfter(tEnd)) {
51
- kpTime[tCurr.format(fmt)] = { data: { count: 0 } }
52
- tCurr = tCurr.add(1, unit)
53
- }
54
-
55
- //vpfs
56
- let vpfs = fsTreeFolder(fdLog)
57
- vpfs = filterVpfsByWindow(vpfs, tStart, fmt) //開檔前剔除窗外檔, 見該模組註解
58
-
59
- //logs(逐檔逐行 stream parse,挑出 tStart 之後、且帶 event 字串者)
60
- let logs = []
61
- for (let vpf of vpfs) {
62
-
63
- //fp
64
- let fp = vpf.path
65
-
66
- //ev
67
- let ev = fsBuildReadStreamText(fp)
68
-
69
- //line
70
- ev.on('line', (line) => {
71
- let v = null
72
- try {
73
- v = JSON.parse(line)
74
- let t = ot(v.time)
75
- let timeFmt = t.format(fmt)
76
- let b1 = t.isAfter(tStart)
77
- let b2 = isestr(v.event) //通用:任何帶 event 字串之 log 皆納入(非鎖定單一 event)
78
- let b = b1 && b2
79
- if (b) {
80
- logs.push({
81
- timeFmt,
82
- ...v,
83
- })
84
- }
85
- }
86
- catch (err) {} //略過非 JSON / 壞行
87
- })
88
-
89
- //await close
90
- let pm = genPm()
91
- ev.on('close', () => {
92
- pm.resolve()
93
- })
94
- await pm
95
-
96
- }
97
-
98
- //gs,依時間 bucket 分組
99
- let gs = groupBy(logs, 'timeFmt')
100
-
101
- //gsLog,各 bucket 內依 event 名計數 + 總數 count
102
- let gsLog = mapValues(gs, vs => {
103
- let count = size(vs)
104
- let gev = groupBy(vs, 'event')
105
- let kp = {}
106
- each(gev, (vvs, event) => {
107
- kp[event] = size(vvs)
108
- })
109
- return {
110
- data: {
111
- count,
112
- ...kp,
113
- }
114
- }
115
- })
116
-
117
- //merge(補零時段 + 實際資料)
118
- let mr = merge({}, kpTime, gsLog)
119
-
120
- //rs(時間遞增排序輸出)
121
- let rs = Object.keys(mr)
122
- .sort()
123
- .map(time => ({
124
- time,
125
- ...mr[time]
126
- }))
127
-
128
- return rs
14
+ let r = await staLogs(timeLength, timeInterval, opt)
15
+ return r.rs
129
16
  }
130
17
 
18
+
131
19
  export default staEvent
@@ -0,0 +1,78 @@
1
+ import { fileURLToPath } from 'url'
2
+ import path from 'path'
3
+ import { Worker } from 'worker_threads'
4
+ import genPm from 'wsemi/src/genPm.mjs'
5
+ import { staLogs as staLogsCore } from './staLogsCore.mjs'
6
+
7
+
8
+ let __filename = fileURLToPath(import.meta.url)
9
+ let __dirname = path.dirname(__filename)
10
+
11
+ let fpWkDefault = path.resolve(__dirname, 'staLogsCore.shellWorker.mjs')
12
+
13
+
14
+ //runWorker: 以單一 worker 執行掃描, 回傳 worker 之 payload; fpWk 可注入 (測試用)
15
+ // 終止路徑一律走 settle(): message(done/error) / error / exit 三者任一先到者定案, 其餘為 no-op, 且皆 terminate 回收
16
+ // exit 為必要: worker 未送任何訊息即結束 (如 worker 內 process.exit) 時若無 exit 監聽, promise 永不落定,
17
+ // 又因 staLogsCore 之 single-flight 以 finally 釋放 in-flight key, 該 key 會永久卡死 (後續同參數呼叫皆取得死 promise, 需重啟後端)
18
+ function runWorker(param, fpWk = fpWkDefault) {
19
+ let pm = genPm()
20
+
21
+ //wk
22
+ let wk = new Worker(fpWk)
23
+
24
+ //settle
25
+ let settled = false
26
+ let settle = (fn, v) => {
27
+ if (!settled) {
28
+ settled = true
29
+ fn(v)
30
+ }
31
+ try {
32
+ wk.terminate()
33
+ }
34
+ catch (err) {}
35
+ }
36
+
37
+ wk.on('message', (msg) => {
38
+ if (msg.mode === 'done') {
39
+ settle(pm.resolve, msg.payload)
40
+ }
41
+ else if (msg.mode === 'error') {
42
+ settle(pm.reject, msg.payload)
43
+ }
44
+ })
45
+
46
+ wk.on('error', (err) => {
47
+ settle(pm.reject, err)
48
+ })
49
+
50
+ wk.on('exit', (code) => {
51
+ settle(pm.reject, new Error(`staLogs worker exited without result, code=${code}`))
52
+ })
53
+
54
+ wk.postMessage(param)
55
+
56
+ return pm
57
+ }
58
+
59
+
60
+ //scanFilesByWorker: 把「需重掃之檔案清單」交給單一 worker 掃描 (主執行緒不阻塞), 回傳每檔彙總
61
+ async function scanFilesByWorker(files, tStartMs, fmt) {
62
+
63
+ //無需重掃時不 spawn
64
+ if (files.length === 0) {
65
+ return []
66
+ }
67
+
68
+ return runWorker({ files, tStartMs, fmt })
69
+ }
70
+
71
+
72
+ function staLogs(timeLength = 7, timeInterval = 'hr', opt = {}) {
73
+ return staLogsCore(timeLength, timeInterval, { ...opt, scanFiles: scanFilesByWorker })
74
+ }
75
+
76
+
77
+ export { staLogs, scanFilesByWorker, runWorker }
78
+ export default staLogs