Ver código fonte

解决生产端问题

jerry 1 ano atrás
pai
commit
8420f72b1f
84 arquivos alterados com 923 adições e 67 exclusões
  1. 267 0
      client.html
  2. 25 0
      client/README.md
  3. 4 0
      client/docs/.vitepress/config.mts
  4. 12 2
      client/docs/.vitepress/plugins/SSE/client.ts
  5. 11 9
      client/docs/.vitepress/theme/Layout.vue
  6. 10 0
      client/docs/.vitepress/theme/components/ExcalidrawView.vue
  7. 1 1
      client/docs/.vitepress/theme/components/SseListener.vue
  8. 11 11
      client/docs/Hardware/Gate/MSA_C.md
  9. 1 1
      client/docs/Hardware/Gate/aGate_1.0.md
  10. 21 21
      client/docs/Hardware/Gate/aGate_1.3.1.md
  11. 2 2
      client/docs/Hardware/Gate/aGate_1.3.md
  12. 1 1
      client/docs/Hardware/Power/aPower_1.3.md
  13. 1 1
      client/docs/Hardware/Power/aPower_2.md
  14. 4 4
      client/docs/Software/EnergyStatistics.md
  15. 6 6
      client/docs/Software/PCS.md
  16. 1 1
      client/docs/Software/ScriptingForSchedulingConfiguration.md
  17. 2 2
      client/docs/System/SystemDistribution.md
  18. 0 0
      client/docs/public/assets/Hardware/Gate/MSA_C/ConnectDER-MSA外观.png
  19. 0 0
      client/docs/public/assets/Hardware/Gate/MSA_C/EQB-MSA外观.png
  20. 0 0
      client/docs/public/assets/Hardware/Gate/MSA_C/MSA-C+CND+aPbox2.0电气方案图.png
  21. 0 0
      client/docs/public/assets/Hardware/Gate/MSA_C/MSA-C+CND电气方案图.png
  22. 0 0
      client/docs/public/assets/Hardware/Gate/MSA_C/MSA-C+EQB+aPbox2.0电气方案图.png
  23. 0 0
      client/docs/public/assets/Hardware/Gate/MSA_C/MSA-C+EQB电气方案图.png
  24. 0 0
      client/docs/public/assets/Hardware/Gate/MSA_C/MSA-C+aPbox2.0典型应用系统图.png
  25. 0 0
      client/docs/public/assets/Hardware/Gate/MSA_C/MSA-C+aPbox2.0系统架构图.jpg
  26. 0 0
      client/docs/public/assets/Hardware/Gate/MSA_C/MSA-C+aPbox2.0系统架构图.png
  27. 0 0
      client/docs/public/assets/Hardware/Gate/MSA_C/MSA-C+aPbox2.0系统组成示意图.png
  28. 0 0
      client/docs/public/assets/Hardware/Gate/MSA_C/MSA-C_SystemCommunicationStructureDiagram.excalidraw
  29. 0 0
      client/docs/public/assets/Hardware/Gate/MSA_C/MSA-C不连接MSA电气方案图.png
  30. 0 0
      client/docs/public/assets/Hardware/Gate/aGate_1.0/aGate1.0-EMS接口原理图.png
  31. 0 0
      client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aGate1.3.1-EMS接口原理图.png
  32. 0 0
      client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aGate1.3.1光伏CT采样原理图.png
  33. 0 0
      client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aGate1.3.1内门图.png
  34. 0 0
      client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aGate1.3.1内门开孔图.png
  35. 0 0
      client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aGate1.3.1外门内侧图.png
  36. 0 0
      client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aGate1.3.1市电CT采样原理图.png
  37. 0 0
      client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aGate1.3.1继电器盒开孔图.png
  38. 0 0
      client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aGate1.3.1铜牌加宽图.png
  39. 0 0
      client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aGate1.3光伏CT采样原理图.png
  40. 0 0
      client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aGate1.3内门图.png
  41. 0 0
      client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aGate1.3外门内侧图.png
  42. 0 0
      client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aGate1.3市电CT采样原理图.png
  43. 0 0
      client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aPower电流100A铭牌.png
  44. 0 0
      client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aPower电流80A铭牌.png
  45. 0 0
      client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aPower空开100A丝印.png
  46. 0 0
      client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aPower空开100A标签.png
  47. 0 0
      client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aPower空开125A丝印.png
  48. 0 0
      client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aPower空开125A手册变更1.png
  49. 0 0
      client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aPower空开125A手册变更2.png
  50. 0 0
      client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aPower空开125A标签.png
  51. 0 0
      client/docs/public/assets/Hardware/Gate/aGate_1.3/aGate1.3-EMS接口原理图.png
  52. 0 0
      client/docs/public/assets/Hardware/Gate/public/aGate1.3-EMS接口图.jpg
  53. 0 0
      client/docs/public/assets/Hardware/Other/aPbox_2.0/aPbox2.0整体布局示意图.png
  54. 0 0
      client/docs/public/assets/Hardware/Other/aPbox_2.0/aPbox2.0电气原理示意图.png
  55. 0 0
      client/docs/public/assets/Hardware/Other/aPbox_2.0/aPbox2.0硬件架构图.png
  56. 0 0
      client/docs/public/assets/Hardware/Other/aPbox_2.0/aPbox2.0造型设计图.png
  57. 0 0
      client/docs/public/assets/Hardware/Power/aPower_1.3/aPower 1.3 Datasheet (SKU_ APR-05K13V1-US)_20240903.pdf
  58. 0 0
      client/docs/public/assets/Hardware/Power/aPower_2/aPower 2 Datasheet (SKU_ APR-10K15V2-US)_20250331.pdf
  59. 0 0
      client/docs/public/assets/Software/EnergyStatistics/aGate1.0_1.1_GenCtrlPartial.excalidraw
  60. 0 0
      client/docs/public/assets/Software/EnergyStatistics/aGate1.0_1.1_GridMeterPartial.excalidraw
  61. 0 0
      client/docs/public/assets/Software/EnergyStatistics/aGate1.0_1.1_PVMeterPartial.excalidraw
  62. 0 0
      client/docs/public/assets/Software/EnergyStatistics/aGate1.0_1.1_SystemFrameworkDiagram.excalidraw
  63. 0 0
      client/docs/public/assets/Software/PCS/GridImportAndExport.png
  64. 0 0
      client/docs/public/assets/Software/PCS/PartialBackupSchematic-OnlyUseSplitCT-1.0.png
  65. 0 0
      client/docs/public/assets/Software/PCS/PartialBackupSchematic-OnlyUseSplitCT-1.3.png
  66. 0 0
      client/docs/public/assets/Software/PCS/PartialBackupSchematic-UseGenCT.png
  67. 0 0
      client/docs/public/assets/Software/PCS/PartialBackupSchematic-UseSplitCT.png
  68. 0 0
      client/docs/public/assets/Software/PCS/WholeBackupSchematic.png
  69. 0 0
      client/docs/public/assets/Software/ScriptingForSchedulingConfiguration/SchedulingLogic.svg
  70. 0 0
      client/docs/public/assets/System/SystemDistribution/aGate1.0-WholeHomeBackup.excalidraw
  71. 0 0
      client/docs/public/assets/System/SystemDistribution/aGate1.2_1.3-WholeHomeBackup+V2L.excalidraw
  72. 0 0
      client/docs/public/assets/System/SystemDistribution/aGate1.x-WholeHomeBackup.excalidraw
  73. 0 0
      client/docs/public/assets/System/SystemDistribution/test.excalidraw
  74. 1 0
      client/package.json
  75. 3 1
      client/tsconfig.json
  76. 164 0
      index.html
  77. 1 1
      server/src/app/index.ts
  78. 0 1
      server/src/middleware/SSE/server.ts
  79. 89 0
      server/src/middleware/SSE/server.ts
  80. 0 1
      server/src/middleware/casdoor/config.ts
  81. 38 0
      server/src/middleware/casdoor/config.ts
  82. 0 1
      server/src/middleware/casdoor/mid.ts
  83. 200 0
      server/src/middleware/casdoor/mid.ts
  84. 47 0
      ssr.html

Diferenças do arquivo suprimidas por serem muito extensas
+ 267 - 0
client.html


+ 25 - 0
client/README.md

@@ -0,0 +1,25 @@
+# franklin wiki
+
+## 脚本说明
+
+### 启动测试环境
+
+```bash
+pnpm run dev
+```
+
+测试环境下已屏蔽登录功能。无需登录。
+
+### 构建项目
+
+```bash
+pnpm run build
+```
+
+### 安装项目
+
+```bash
+pnpm run install
+```
+
+安装路径默认为:/var/www/fwh-wiki/fwhwiki/,请确保路径存在。

+ 4 - 0
client/docs/.vitepress/config.mts

@@ -1,4 +1,5 @@
 import { defineConfig } from 'vitepress'
+import path from 'path'
 import menuConfig from './menuConfig'
 import vueJsx from '@vitejs/plugin-vue-jsx'
 import { withMermaid } from 'vitepress-plugin-mermaid'
@@ -149,6 +150,9 @@ export default defineConfig(
           // 'vitepress': resolve(__dirname, '../../packages/vitepress/src/client'),
           // 'dayjs/dayjs.min.js': 'dayjs/esm/index.js',
           // 'dayjs': 'dayjs/esm/index.js',
+          '@': path.resolve(__dirname, './'),
+          '@theme': path.resolve(__dirname, './theme'),
+          '@docs': path.resolve(__dirname, '../'),
         },
       },
       optimizeDeps: {

+ 12 - 2
client/docs/.vitepress/plugins/SSE/client.ts

@@ -9,10 +9,20 @@ export default class EventClient {
     this.source.onmessage = (e: MessageEvent) => {
       this.handleMsg(JSON.parse(e.data))
     }
-    this.source.onerror = () => {
+    this.source.onerror = (e: Event) => {
       this.source?.close()
       this.source = undefined
-      console.warn('SSE 连接关闭或出错')
+      console.warn("SSE 连接关闭或出错:", e)
+
+      // 如果是 MessageEvent 或 ErrorEvent,可尝试打印更多细节
+      if (e instanceof ErrorEvent) {
+        console.error("错误详情:", e.message, e.filename, e.lineno)
+      } else if (e instanceof MessageEvent) {
+        console.error("消息内容:", e.data)
+      } else {
+        // 一些浏览器可能用 `EventSource.readyState` 表示连接状态
+        console.warn("EventSource 状态:", this.source?.readyState)
+      }
     }
   }
 

+ 11 - 9
client/docs/.vitepress/theme/Layout.vue

@@ -7,15 +7,17 @@
       </ClientOnly>
     </template>
     <template #nav-bar-content-after>
-      <div style="margin-left: 15px">
-        <n-dropdown :options="personOptions" @select="handlePersonSelect">
-          <n-button text style="display: flex; justify-content: center" >
-            <template #icon>
-              <n-icon><PersonIcon /></n-icon>
-            </template>
-          </n-button>
-        </n-dropdown>
-      </div>
+      <ClientOnly>
+        <div style="margin-left: 15px">
+          <n-dropdown :options="personOptions" @select="handlePersonSelect">
+            <n-button text style="display: flex; justify-content: center" >
+              <template #icon>
+                <n-icon><PersonIcon /></n-icon>
+              </template>
+            </n-button>
+          </n-dropdown>
+        </div>
+      </ClientOnly>
     </template>
   </Layout>
 </template>

+ 10 - 0
client/docs/.vitepress/theme/components/ExcalidrawView.vue

@@ -146,6 +146,16 @@ const getExcalidraw = async (path: string): Promise<ExportedDataState | undefine
   return json
 }
 
+// const getExcalidraw = async (path: string): Promise<ExportedDataState | undefined> => {
+//   try {
+//     const data = await import(/* @vite-ignore */ withBase(path));
+//     return data.default;
+//   } catch (err) {
+//     console.error('加载 excalidraw 失败:', err);
+//     return undefined;
+//   }
+// };
+
 const drawCanvas = async (data: ExportedDataState, width?: number, height?: number): Promise<HTMLCanvasElement> => {
   // const maxWidthOrHeight = width > height ? height : width
   // 改为只限制宽度,因为渲染前父窗口高度可能为0,导致maxWidthOrHeight不生效

+ 1 - 1
client/docs/.vitepress/theme/components/SseListener.vue

@@ -17,7 +17,7 @@ onMounted(async () => {
     console.log(signinUrl)
   }
   
-  client = new EventClient.default(signinUrl)
+  client = new EventClient(signinUrl)
 })
 
 onUnmounted(() => {

+ 11 - 11
client/docs/Hardware/Gate/MSA_C.md

@@ -16,42 +16,42 @@ editLink: false
 ### MSA-C通讯架构
 
 <ClientOnly>
-  <ExcalidrawView filePath="/Hardware/Gate/assets/MSA_C/MSA-C_SystemCommunicationStructureDiagram.excalidraw" />
+  <ExcalidrawView filePath="/assets/Hardware/Gate/MSA_C/MSA-C_SystemCommunicationStructureDiagram.excalidraw" />
 </ClientOnly>
 
 ### MSA-C典型应用
 
-![MSA-C+aPbox2.0典型应用系统图](./assets/MSA_C/MSA-C+aPbox2.0典型应用系统图.png)
+![MSA-C+aPbox2.0典型应用系统图](/assets/Hardware/Gate/MSA_C/MSA-C+aPbox2.0典型应用系统图.png)
 
 ### MSA-C系统组成
 
-![MSA-C+aPbox2.0系统组成示意图](./assets/MSA_C/MSA-C+aPbox2.0系统组成示意图.png)
+![MSA-C+aPbox2.0系统组成示意图](/assets/Hardware/Gate/MSA_C/MSA-C+aPbox2.0系统组成示意图.png)
 
 ### MSA-C系统架构
 
-![MSA-C+aPbox2.0系统架构图](./assets/MSA_C/MSA-C+aPbox2.0系统架构图.png)
+![MSA-C+aPbox2.0系统架构图](/assets/Hardware/Gate/MSA_C/MSA-C+aPbox2.0系统架构图.png)
 
 ### 搭配MSA的电气方案
 
 **MSA-C + EQB**
 
-![MSA-C+EQB电气方案图](./assets/MSA_C/MSA-C+EQB电气方案图.png)
+![MSA-C+EQB电气方案图](/assets/Hardware/Gate/MSA_C/MSA-C+EQB电气方案图.png)
 
 带aPbox2.0
 
-![MSA-C+EQB+aPbox2.0电气方案图](./assets/MSA_C/MSA-C+EQB+aPbox2.0电气方案图.png)
+![MSA-C+EQB+aPbox2.0电气方案图](/assets/Hardware/Gate/MSA_C/MSA-C+EQB+aPbox2.0电气方案图.png)
 
 **MSA-C + CND**
 
-![MSA-C+CND电气方案图](./assets/MSA_C/MSA-C+CND电气方案图.png)
+![MSA-C+CND电气方案图](/assets/Hardware/Gate/MSA_C/MSA-C+CND电气方案图.png)
 
 带aPbox2.0
 
-![MSA-C+CND+aPbox2.0电气方案图](./assets/MSA_C/MSA-C+CND+aPbox2.0电气方案图.png)
+![MSA-C+CND+aPbox2.0电气方案图](/assets/Hardware/Gate/MSA_C/MSA-C+CND+aPbox2.0电气方案图.png)
 
 **MSA-C不与MSA连接**
 
-![MSA-C不连接MSA电气方案图](./assets/MSA_C/MSA-C不连接MSA电气方案图.png)
+![MSA-C不连接MSA电气方案图](/assets/Hardware/Gate/MSA_C/MSA-C不连接MSA电气方案图.png)
 
 ## MSA-C规格参数
 
@@ -287,7 +287,7 @@ editLink: false
 
 ### EQB-MSA
 
-![EQB-MSA外观](./assets/MSA_C/EQB-MSA外观.png)
+![EQB-MSA外观](/assets/Hardware/Gate/MSA_C/EQB-MSA外观.png)
 
 **开发手册**
 
@@ -297,7 +297,7 @@ editLink: false
 
 ### ConnectDER-MSA
 
-![ConnectDER-MSA外观](./assets/MSA_C/ConnectDER-MSA外观.png)
+![ConnectDER-MSA外观](/assets/Hardware/Gate/MSA_C/ConnectDER-MSA外观.png)
 
 **开发手册**
 

+ 1 - 1
client/docs/Hardware/Gate/aGate_1.0.md

@@ -7,7 +7,7 @@ editLink: false
 ## EMS接口说明
 
 **接口原理图**
-![aGate1.0-EMS接口原理图](./assets/aGate_1.0/aGate1.0-EMS接口原理图.png)
+![aGate1.0-EMS接口原理图](/assets/Hardware/Gate/aGate_1.0/aGate1.0-EMS接口原理图.png)
 
 **接口说明表**
 <ClientOnly>

+ 21 - 21
client/docs/Hardware/Gate/aGate_1.3.1.md

@@ -33,50 +33,50 @@ aGate 1.3.1基于aGate 1.3进行升级优化,以满足SUNRUN客户的需求。
 
 * 外门内侧样式变更
 <div style="display: flex; align-items: center; gap: 10px;">
-  <img src="./assets/aGate_1.3.1/aGate1.3外门内侧图.png" style="width: 45%;" />
+  <img src="/assets/Hardware/Gate/aGate_1.3.1/aGate1.3外门内侧图.png" style="width: 45%;" />
   <span style="font-size: 24px;"> -> </span>
-  <img src="./assets/aGate_1.3.1/aGate1.3.1外门内侧图.png" style="width: 45%;" />
+  <img src="/assets/Hardware/Gate/aGate_1.3.1/aGate1.3.1外门内侧图.png" style="width: 45%;" />
 </div>
 
 * 内门样式变更
 <div style="display: flex; align-items: center; gap: 10px;">
-  <img src="./assets/aGate_1.3.1/aGate1.3内门图.png" style="width: 45%;" />
+  <img src="/assets/Hardware/Gate/aGate_1.3.1/aGate1.3内门图.png" style="width: 45%;" />
   <span style="font-size: 24px;"> -> </span>
-  <img src="./assets/aGate_1.3.1/aGate1.3.1内门图.png" style="width: 45%;" />
+  <img src="/assets/Hardware/Gate/aGate_1.3.1/aGate1.3.1内门图.png" style="width: 45%;" />
 </div>
 
 * aGate的aPower空开电流变更(100A -> 125A)
   * 安装手册变更
-  ![aPower空开125A手册变更1](./assets/aGate_1.3.1/aPower空开125A手册变更1.png)
-  ![aPower空开125A手册变更2](./assets/aGate_1.3.1/aPower空开125A手册变更2.png)
+  ![aPower空开125A手册变更1](/assets/Hardware/Gate/aGate_1.3.1/aPower空开125A手册变更1.png)
+  ![aPower空开125A手册变更2](/assets/Hardware/Gate/aGate_1.3.1/aPower空开125A手册变更2.png)
 
   * 门板内侧原理图丝印更改
   <div style="display: flex; align-items: center; gap: 10px;">
-    <img src="./assets/aGate_1.3.1/aPower空开100A丝印.png" style="width: 45%;" />
+    <img src="/assets/Hardware/Gate/aGate_1.3.1/aPower空开100A丝印.png" style="width: 45%;" />
     <span style="font-size: 24px;"> -> </span>
-    <img src="./assets/aGate_1.3.1/aPower空开125A丝印.png" style="width: 45%;" />
+    <img src="/assets/Hardware/Gate/aGate_1.3.1/aPower空开125A丝印.png" style="width: 45%;" />
   </div>
 
   * aGate现场标签更改
   <div style="display: flex; align-items: center; gap: 10px;">
-    <img src="./assets/aGate_1.3.1/aPower空开100A标签.png" style="width: 45%;" />
+    <img src="/assets/Hardware/Gate/aGate_1.3.1/aPower空开100A标签.png" style="width: 45%;" />
     <span style="font-size: 24px;"> -> </span>
-    <img src="./assets/aGate_1.3.1/aPower空开125A标签.png" style="width: 45%;" />
+    <img src="/assets/Hardware/Gate/aGate_1.3.1/aPower空开125A标签.png" style="width: 45%;" />
   </div>
 
 * aGate铭牌变更(aPower电流80A -> 100A, Model变更)
 <div style="display: flex; align-items: center; gap: 10px;">
-  <img src="./assets/aGate_1.3.1/aPower电流80A铭牌.png" style="width: 45%;" />
+  <img src="/assets/Hardware/Gate/aGate_1.3.1/aPower电流80A铭牌.png" style="width: 45%;" />
   <span style="font-size: 24px;"> -> </span>
-  <img src="./assets/aGate_1.3.1/aPower电流100A铭牌.png" style="width: 45%;" />
+  <img src="/assets/Hardware/Gate/aGate_1.3.1/aPower电流100A铭牌.png" style="width: 45%;" />
 </div>
 
 ##### 硬件变更(增强散热)
 
 * 与aPower Breaker连接的铜牌距离加宽增强散热
-![aGate1.3.1铜牌加宽图](./assets/aGate_1.3.1/aGate1.3.1铜牌加宽图.png)
+![aGate1.3.1铜牌加宽图](/assets/Hardware/Gate/aGate_1.3.1/aGate1.3.1铜牌加宽图.png)
 * 塑胶件做开孔增强散热
-![aGate1.3.1继电器盒开孔图](./assets/aGate_1.3.1/aGate1.3.1继电器盒开孔图.png)
+![aGate1.3.1继电器盒开孔图](/assets/Hardware/Gate/aGate_1.3.1/aGate1.3.1继电器盒开孔图.png)
 
 ##### 软件变更
 
@@ -94,22 +94,22 @@ EMS可能未实现aPower空开电流限制功能,待确认。
 #### 预留手动断市电继电器窗口
 
 * aGate内面板开孔(在系统故障且继电器断开时,客户可根据Franklin客服指导操作闭合继电器,保证家庭供电)
-![aGate1.3.1内门开孔图](./assets/aGate_1.3.1/aGate1.3.1内门开孔图.png)
+![aGate1.3.1内门开孔图](/assets/Hardware/Gate/aGate_1.3.1/aGate1.3.1内门开孔图.png)
 
 #### CT2/3/4 硬件独立采集 {#CT硬件独立采集}
 
 * 市电CT电路变更(CT3/CT4)
 <div style="display: flex; align-items: center; gap: 10px;">
-  <img src="./assets/aGate_1.3.1/aGate1.3市电CT采样原理图.png" style="width: 45%;" />
+  <img src="/assets/Hardware/Gate/aGate_1.3.1/aGate1.3市电CT采样原理图.png" style="width: 45%;" />
   <span style="font-size: 24px;"> -> </span>
-  <img src="./assets/aGate_1.3.1/aGate1.3.1市电CT采样原理图.png" style="width: 45%;" />
+  <img src="/assets/Hardware/Gate/aGate_1.3.1/aGate1.3.1市电CT采样原理图.png" style="width: 45%;" />
 </div>
 
 * 光伏CT电路变更(CT2)
 <div style="display: flex; align-items: center; gap: 10px;">
-  <img src="./assets/aGate_1.3.1/aGate1.3光伏CT采样原理图.png" style="width: 45%;" />
+  <img src="/assets/Hardware/Gate/aGate_1.3.1/aGate1.3光伏CT采样原理图.png" style="width: 45%;" />
   <span style="font-size: 24px;"> -> </span>
-  <img src="./assets/aGate_1.3.1/aGate1.3.1光伏CT采样原理图.png" style="width: 45%;" />
+  <img src="/assets/Hardware/Gate/aGate_1.3.1/aGate1.3.1光伏CT采样原理图.png" style="width: 45%;" />
 </div>
 
 独立采样,配置更灵活,可支持配电方案,具体可支持的配电方案,请参考[系统配电](/System/SystemDistribution.md)。
@@ -117,10 +117,10 @@ EMS可能未实现aPower空开电流限制功能,待确认。
 ## EMS接口说明
 
 **接口图**
-![aGate1.3-EMS接口图](./assets/public/aGate1.3-EMS接口图.jpg)
+![aGate1.3-EMS接口图](/assets/Hardware/Gate/public/aGate1.3-EMS接口图.jpg)
 
 **接口原理图**
-![aGate1.3.1-EMS接口原理图](./assets/aGate_1.3.1/aGate1.3.1-EMS接口原理图.png)
+![aGate1.3.1-EMS接口原理图](/assets/Hardware/Gate/aGate_1.3.1/aGate1.3.1-EMS接口原理图.png)
 
 **接口说明表**
 <ClientOnly>

+ 2 - 2
client/docs/Hardware/Gate/aGate_1.3.md

@@ -7,10 +7,10 @@ editLink: false
 ## EMS接口说明
 
 **接口图**
-![aGate1.3-EMS接口图](./assets/public/aGate1.3-EMS接口图.jpg)
+![aGate1.3-EMS接口图](/assets/Hardware/Gate/public/aGate1.3-EMS接口图.jpg)
 
 **接口原理图**
-![aGate1.3-EMS接口原理图](./assets/aGate_1.3/aGate1.3-EMS接口原理图.png)
+![aGate1.3-EMS接口原理图](/assets/Hardware/Gate/aGate_1.3/aGate1.3-EMS接口原理图.png)
 
 **接口说明表**
 <ClientOnly>

+ 1 - 1
client/docs/Hardware/Power/aPower_1.3.md

@@ -9,7 +9,7 @@ editLink: false
 更新于2025-03-31
 
 <iframe
-  src="./assets/aPower_1.3/aPower 1.3 Datasheet (SKU_ APR-05K13V1-US)_20240903.pdf"
+  src="/assets/Hardware/Power/aPower_1.3/aPower 1.3 Datasheet (SKU_ APR-05K13V1-US)_20240903.pdf"
   width="100%"
   height="800px"
   style="border: none;"

+ 1 - 1
client/docs/Hardware/Power/aPower_2.md

@@ -16,7 +16,7 @@ aPower2编码:
 更新于2025-03-31
 
 <iframe
-  src="./assets/aPower_2/aPower 2 Datasheet (SKU_ APR-10K15V2-US)_20250331.pdf"
+  src="/assets/Hardware/Power/aPower_2/aPower 2 Datasheet (SKU_ APR-10K15V2-US)_20250331.pdf"
   width="100%"
   height="800px"
   style="border: none;"

+ 4 - 4
client/docs/Software/EnergyStatistics.md

@@ -34,7 +34,7 @@ EMS直接采集由EMS-DSP
 ### 系统框架
 
 <ClientOnly>
-  <ExcalidrawView filePath="/Software/assets/EnergyStatistics/aGate1.0_1.1_SystemFrameworkDiagram.excalidraw" />
+  <ExcalidrawView filePath="/assets/Software/EnergyStatistics/aGate1.0_1.1_SystemFrameworkDiagram.excalidraw" />
 </ClientOnly>
 
 ### 市电采集
@@ -42,7 +42,7 @@ EMS直接采集由EMS-DSP
 市电采集使用的智能电表采集,Grid meter用于采集市电实时功率和统计Import/Export总能量。局部放大框图如下:
 
 <ClientOnly>
-  <ExcalidrawView filePath="/Software/assets/EnergyStatistics/aGate1.0_1.1_GridMeterPartial.excalidraw" />
+  <ExcalidrawView filePath="/assets/Software/EnergyStatistics/aGate1.0_1.1_GridMeterPartial.excalidraw" />
 </ClientOnly>
 
 :::warning
@@ -54,7 +54,7 @@ EMS直接采集由EMS-DSP
 PV采集使用的智能电表采集,PV meter用于采集光伏实时功率和统计Import总能量。局部放大框图如下:
 
 <ClientOnly>
-  <ExcalidrawView filePath="/Software/assets/EnergyStatistics/aGate1.0_1.1_PVMeterPartial.excalidraw" />
+  <ExcalidrawView filePath="/assets/Software/EnergyStatistics/aGate1.0_1.1_PVMeterPartial.excalidraw" />
 </ClientOnly>
 
 ### 发电机采集
@@ -62,5 +62,5 @@ PV采集使用的智能电表采集,PV meter用于采集光伏实时功率和
 发电机采样EMS直接采集,EMS-DSP直接采集Gen CT的电流数据,电压数据通过EMS-DSP的Gen电压采集线获取。(采集线连接位置,请参考对应设备的硬件介绍)局部放大框图如下:
 
 <ClientOnly>
-  <ExcalidrawView filePath="/Software/assets/EnergyStatistics/aGate1.0_1.1_GenCtrlPartial.excalidraw" />
+  <ExcalidrawView filePath="/assets/Software/EnergyStatistics/aGate1.0_1.1_GenCtrlPartial.excalidraw" />
 </ClientOnly>

+ 6 - 6
client/docs/Software/PCS.md

@@ -271,7 +271,7 @@ Powerwall和受控太阳能系统符合 UL 1741 PCS 标准,其标称电压为
 
 全屋备电场景中,市电入户直接连接到aGate,是标准的使用场景,全屋备电示意图如下图所示:
 
-![WholeBackupSchematic](./assets/PCS/WholeBackupSchematic.png)
+![WholeBackupSchematic](/assets/Software/PCS/WholeBackupSchematic.png)
 
 **MSA**
 
@@ -287,24 +287,24 @@ Powerwall和受控太阳能系统符合 UL 1741 PCS 标准,其标称电压为
 
 在场景一中,aGate连接到市电入户一分二接线盒,市电入户仅有两个分支,我们可以利用市电split CT来采集另一分支的电流来计算总市电功率,市电Split CT统计的能量视为非备电能量,配电示意图如下图所示:
 
-![PartialBackupSchematic-UseSplitCT](./assets/PCS/PartialBackupSchematic-UseSplitCT.png)
+![PartialBackupSchematic-UseSplitCT](/assets/Software/PCS/PartialBackupSchematic-UseSplitCT.png)
 
 在场景二中,aGate连接用户配电盘,市电入口连接在用户配电盘上,需要统计配电盘市电入口总功率,可以使用市电Split CT或Gen CT来对配电盘市电入口总功率进行统计。市电Split CT接法如下图所示:
 
 <div style="display: flex; align-items: center; gap: 10px;">
   <div style="text-align: center; width: 45%;">
-    <img src="./assets/PCS/PartialBackupSchematic-OnlyUseSplitCT-1.0.png" />
+    <img src="/assets/Software/PCS/PartialBackupSchematic-OnlyUseSplitCT-1.0.png" />
     <span>aGate 1.0/1.1</span>
   </div>
   <div style="text-align: center; width: 45%;">
-    <img src="./assets/PCS/PartialBackupSchematic-OnlyUseSplitCT-1.3.png" />
+    <img src="/assets/Software/PCS/PartialBackupSchematic-OnlyUseSplitCT-1.3.png" />
     <span>aGate 1.3</span>
   </div>
 </div>
 
 Gen CT接法如下图所示:
 
-![PartialBackupSchematic-UseGenCT](./assets/PCS/PartialBackupSchematic-UseGenCT.png)
+![PartialBackupSchematic-UseGenCT](/assets/Software/PCS/PartialBackupSchematic-UseGenCT.png)
 
 **MSA**
 
@@ -323,7 +323,7 @@ Gen CT接法如下图所示:
 
 市电电流限制分为两个设置项Grid Export Limit和Grid Import Limit,分别限制市电口倒送功率和用电功率。APP设置项如下图所示:
 
-![GridImportAndExport](./assets/PCS/GridImportAndExport.png){ style="width: 40%; height: auto;" }
+![GridImportAndExport](/assets/Software/PCS/GridImportAndExport.png){ style="width: 40%; height: auto;" }
 
 **Grid Export Limit**
 

+ 1 - 1
client/docs/Software/ScriptingForSchedulingConfiguration.md

@@ -405,7 +405,7 @@ null: 无意义数值,即无论是几都没有意义
 
 **优先级控制逻辑图**
 
-![优先级调度逻辑图](./assets/ScriptingForSchedulingConfiguration/SchedulingLogic.svg)
+![优先级调度逻辑图](/assets/Software/ScriptingForSchedulingConfiguration/SchedulingLogic.svg)
 
 #### 常用优先级组合
 ##### P[1,2,0] - L[1,3,2]

+ 2 - 2
client/docs/System/SystemDistribution.md

@@ -101,7 +101,7 @@ editLink: false
         title: 'Test',
         description: '测试',
         tags: ['aGate 1.0/1.1'],
-        filePath: '/System/assets/SystemDistribution/test.excalidraw'
+        filePath: '/assets/System/SystemDistribution/test.excalidraw'
       },
       {
         title: 'aGate1.x 全屋备电',
@@ -114,7 +114,7 @@ editLink: false
           'aGate PV',
           '油机接入'
         ],
-        filePath: '/System/assets/SystemDistribution/aGate1.0-WholeHomeBackup.excalidraw'
+        filePath: '/assets/System/SystemDistribution/aGate1.0-WholeHomeBackup.excalidraw'
       }
     ]"
   />

+ 0 - 0
client/docs/Hardware/Gate/assets/MSA_C/ConnectDER-MSA外观.png → client/docs/public/assets/Hardware/Gate/MSA_C/ConnectDER-MSA外观.png


+ 0 - 0
client/docs/Hardware/Gate/assets/MSA_C/EQB-MSA外观.png → client/docs/public/assets/Hardware/Gate/MSA_C/EQB-MSA外观.png


+ 0 - 0
client/docs/Hardware/Gate/assets/MSA_C/MSA-C+CND+aPbox2.0电气方案图.png → client/docs/public/assets/Hardware/Gate/MSA_C/MSA-C+CND+aPbox2.0电气方案图.png


+ 0 - 0
client/docs/Hardware/Gate/assets/MSA_C/MSA-C+CND电气方案图.png → client/docs/public/assets/Hardware/Gate/MSA_C/MSA-C+CND电气方案图.png


+ 0 - 0
client/docs/Hardware/Gate/assets/MSA_C/MSA-C+EQB+aPbox2.0电气方案图.png → client/docs/public/assets/Hardware/Gate/MSA_C/MSA-C+EQB+aPbox2.0电气方案图.png


+ 0 - 0
client/docs/Hardware/Gate/assets/MSA_C/MSA-C+EQB电气方案图.png → client/docs/public/assets/Hardware/Gate/MSA_C/MSA-C+EQB电气方案图.png


+ 0 - 0
client/docs/Hardware/Gate/assets/MSA_C/MSA-C+aPbox2.0典型应用系统图.png → client/docs/public/assets/Hardware/Gate/MSA_C/MSA-C+aPbox2.0典型应用系统图.png


+ 0 - 0
client/docs/Hardware/Gate/assets/MSA_C/MSA-C+aPbox2.0系统架构图.jpg → client/docs/public/assets/Hardware/Gate/MSA_C/MSA-C+aPbox2.0系统架构图.jpg


+ 0 - 0
client/docs/Hardware/Gate/assets/MSA_C/MSA-C+aPbox2.0系统架构图.png → client/docs/public/assets/Hardware/Gate/MSA_C/MSA-C+aPbox2.0系统架构图.png


+ 0 - 0
client/docs/Hardware/Gate/assets/MSA_C/MSA-C+aPbox2.0系统组成示意图.png → client/docs/public/assets/Hardware/Gate/MSA_C/MSA-C+aPbox2.0系统组成示意图.png


+ 0 - 0
client/docs/Hardware/Gate/assets/MSA_C/MSA-C_SystemCommunicationStructureDiagram.excalidraw → client/docs/public/assets/Hardware/Gate/MSA_C/MSA-C_SystemCommunicationStructureDiagram.excalidraw


+ 0 - 0
client/docs/Hardware/Gate/assets/MSA_C/MSA-C不连接MSA电气方案图.png → client/docs/public/assets/Hardware/Gate/MSA_C/MSA-C不连接MSA电气方案图.png


+ 0 - 0
client/docs/Hardware/Gate/assets/aGate_1.0/aGate1.0-EMS接口原理图.png → client/docs/public/assets/Hardware/Gate/aGate_1.0/aGate1.0-EMS接口原理图.png


+ 0 - 0
client/docs/Hardware/Gate/assets/aGate_1.3.1/aGate1.3.1-EMS接口原理图.png → client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aGate1.3.1-EMS接口原理图.png


+ 0 - 0
client/docs/Hardware/Gate/assets/aGate_1.3.1/aGate1.3.1光伏CT采样原理图.png → client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aGate1.3.1光伏CT采样原理图.png


+ 0 - 0
client/docs/Hardware/Gate/assets/aGate_1.3.1/aGate1.3.1内门图.png → client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aGate1.3.1内门图.png


+ 0 - 0
client/docs/Hardware/Gate/assets/aGate_1.3.1/aGate1.3.1内门开孔图.png → client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aGate1.3.1内门开孔图.png


+ 0 - 0
client/docs/Hardware/Gate/assets/aGate_1.3.1/aGate1.3.1外门内侧图.png → client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aGate1.3.1外门内侧图.png


+ 0 - 0
client/docs/Hardware/Gate/assets/aGate_1.3.1/aGate1.3.1市电CT采样原理图.png → client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aGate1.3.1市电CT采样原理图.png


+ 0 - 0
client/docs/Hardware/Gate/assets/aGate_1.3.1/aGate1.3.1继电器盒开孔图.png → client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aGate1.3.1继电器盒开孔图.png


+ 0 - 0
client/docs/Hardware/Gate/assets/aGate_1.3.1/aGate1.3.1铜牌加宽图.png → client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aGate1.3.1铜牌加宽图.png


+ 0 - 0
client/docs/Hardware/Gate/assets/aGate_1.3.1/aGate1.3光伏CT采样原理图.png → client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aGate1.3光伏CT采样原理图.png


+ 0 - 0
client/docs/Hardware/Gate/assets/aGate_1.3.1/aGate1.3内门图.png → client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aGate1.3内门图.png


+ 0 - 0
client/docs/Hardware/Gate/assets/aGate_1.3.1/aGate1.3外门内侧图.png → client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aGate1.3外门内侧图.png


+ 0 - 0
client/docs/Hardware/Gate/assets/aGate_1.3.1/aGate1.3市电CT采样原理图.png → client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aGate1.3市电CT采样原理图.png


+ 0 - 0
client/docs/Hardware/Gate/assets/aGate_1.3.1/aPower电流100A铭牌.png → client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aPower电流100A铭牌.png


+ 0 - 0
client/docs/Hardware/Gate/assets/aGate_1.3.1/aPower电流80A铭牌.png → client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aPower电流80A铭牌.png


+ 0 - 0
client/docs/Hardware/Gate/assets/aGate_1.3.1/aPower空开100A丝印.png → client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aPower空开100A丝印.png


+ 0 - 0
client/docs/Hardware/Gate/assets/aGate_1.3.1/aPower空开100A标签.png → client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aPower空开100A标签.png


+ 0 - 0
client/docs/Hardware/Gate/assets/aGate_1.3.1/aPower空开125A丝印.png → client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aPower空开125A丝印.png


+ 0 - 0
client/docs/Hardware/Gate/assets/aGate_1.3.1/aPower空开125A手册变更1.png → client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aPower空开125A手册变更1.png


+ 0 - 0
client/docs/Hardware/Gate/assets/aGate_1.3.1/aPower空开125A手册变更2.png → client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aPower空开125A手册变更2.png


+ 0 - 0
client/docs/Hardware/Gate/assets/aGate_1.3.1/aPower空开125A标签.png → client/docs/public/assets/Hardware/Gate/aGate_1.3.1/aPower空开125A标签.png


+ 0 - 0
client/docs/Hardware/Gate/assets/aGate_1.3/aGate1.3-EMS接口原理图.png → client/docs/public/assets/Hardware/Gate/aGate_1.3/aGate1.3-EMS接口原理图.png


+ 0 - 0
client/docs/Hardware/Gate/assets/public/aGate1.3-EMS接口图.jpg → client/docs/public/assets/Hardware/Gate/public/aGate1.3-EMS接口图.jpg


+ 0 - 0
client/docs/Hardware/Other/assets/aPbox_2.0/aPbox2.0整体布局示意图.png → client/docs/public/assets/Hardware/Other/aPbox_2.0/aPbox2.0整体布局示意图.png


+ 0 - 0
client/docs/Hardware/Other/assets/aPbox_2.0/aPbox2.0电气原理示意图.png → client/docs/public/assets/Hardware/Other/aPbox_2.0/aPbox2.0电气原理示意图.png


+ 0 - 0
client/docs/Hardware/Other/assets/aPbox_2.0/aPbox2.0硬件架构图.png → client/docs/public/assets/Hardware/Other/aPbox_2.0/aPbox2.0硬件架构图.png


+ 0 - 0
client/docs/Hardware/Other/assets/aPbox_2.0/aPbox2.0造型设计图.png → client/docs/public/assets/Hardware/Other/aPbox_2.0/aPbox2.0造型设计图.png


+ 0 - 0
client/docs/Hardware/Power/assets/aPower_1.3/aPower 1.3 Datasheet (SKU_ APR-05K13V1-US)_20240903.pdf → client/docs/public/assets/Hardware/Power/aPower_1.3/aPower 1.3 Datasheet (SKU_ APR-05K13V1-US)_20240903.pdf


+ 0 - 0
client/docs/Hardware/Power/assets/aPower_2/aPower 2 Datasheet (SKU_ APR-10K15V2-US)_20250331.pdf → client/docs/public/assets/Hardware/Power/aPower_2/aPower 2 Datasheet (SKU_ APR-10K15V2-US)_20250331.pdf


+ 0 - 0
client/docs/Software/assets/EnergyStatistics/aGate1.0_1.1_GenCtrlPartial.excalidraw → client/docs/public/assets/Software/EnergyStatistics/aGate1.0_1.1_GenCtrlPartial.excalidraw


+ 0 - 0
client/docs/Software/assets/EnergyStatistics/aGate1.0_1.1_GridMeterPartial.excalidraw → client/docs/public/assets/Software/EnergyStatistics/aGate1.0_1.1_GridMeterPartial.excalidraw


+ 0 - 0
client/docs/Software/assets/EnergyStatistics/aGate1.0_1.1_PVMeterPartial.excalidraw → client/docs/public/assets/Software/EnergyStatistics/aGate1.0_1.1_PVMeterPartial.excalidraw


+ 0 - 0
client/docs/Software/assets/EnergyStatistics/aGate1.0_1.1_SystemFrameworkDiagram.excalidraw → client/docs/public/assets/Software/EnergyStatistics/aGate1.0_1.1_SystemFrameworkDiagram.excalidraw


+ 0 - 0
client/docs/Software/assets/PCS/GridImportAndExport.png → client/docs/public/assets/Software/PCS/GridImportAndExport.png


+ 0 - 0
client/docs/Software/assets/PCS/PartialBackupSchematic-OnlyUseSplitCT-1.0.png → client/docs/public/assets/Software/PCS/PartialBackupSchematic-OnlyUseSplitCT-1.0.png


+ 0 - 0
client/docs/Software/assets/PCS/PartialBackupSchematic-OnlyUseSplitCT-1.3.png → client/docs/public/assets/Software/PCS/PartialBackupSchematic-OnlyUseSplitCT-1.3.png


+ 0 - 0
client/docs/Software/assets/PCS/PartialBackupSchematic-UseGenCT.png → client/docs/public/assets/Software/PCS/PartialBackupSchematic-UseGenCT.png


+ 0 - 0
client/docs/Software/assets/PCS/PartialBackupSchematic-UseSplitCT.png → client/docs/public/assets/Software/PCS/PartialBackupSchematic-UseSplitCT.png


+ 0 - 0
client/docs/Software/assets/PCS/WholeBackupSchematic.png → client/docs/public/assets/Software/PCS/WholeBackupSchematic.png


+ 0 - 0
client/docs/Software/assets/ScriptingForSchedulingConfiguration/SchedulingLogic.svg → client/docs/public/assets/Software/ScriptingForSchedulingConfiguration/SchedulingLogic.svg


+ 0 - 0
client/docs/System/assets/SystemDistribution/aGate1.0-WholeHomeBackup.excalidraw → client/docs/public/assets/System/SystemDistribution/aGate1.0-WholeHomeBackup.excalidraw


+ 0 - 0
client/docs/System/assets/SystemDistribution/aGate1.2_1.3-WholeHomeBackup+V2L.excalidraw → client/docs/public/assets/System/SystemDistribution/aGate1.2_1.3-WholeHomeBackup+V2L.excalidraw


+ 0 - 0
client/docs/System/assets/SystemDistribution/aGate1.x-WholeHomeBackup.excalidraw → client/docs/public/assets/System/SystemDistribution/aGate1.x-WholeHomeBackup.excalidraw


+ 0 - 0
client/docs/System/assets/SystemDistribution/test.excalidraw → client/docs/public/assets/System/SystemDistribution/test.excalidraw


+ 1 - 0
client/package.json

@@ -9,6 +9,7 @@
     "test": "echo \"Error: no test specified\" && exit 1",
     "dev": "run-p docs:dev wl:run",
     "predev": "pnpm run pdf:encrypt",
+    "build": "pnpm run docs:build",
     "docs:dev": "pnpm run build:module && vitepress dev docs",
     "docs:build": "pnpm run build:module && vitepress build docs --minify false -d true",
     "docs:preview": "pnpm run build:module && vitepress preview docs",

+ 3 - 1
client/tsconfig.json

@@ -17,7 +17,9 @@
     "types": ["vite/client", "vue"],
     "baseUrl": ".",
     "paths": {
-      "@theme/*": [".vitepress/theme/*"]
+      "@/*": ["docs/.vitepress/*"],
+      "@theme/*": ["docs/.vitepress/theme/*"],
+      "@docs/*": ["docs/*"]
     }
   },
   "include": ["env.d.ts", "*.ts", "*.tsx", "src/**/*", ".vitepress/**/*"],

Diferenças do arquivo suprimidas por serem muito extensas
+ 164 - 0
index.html


+ 1 - 1
server/src/app/index.ts

@@ -16,7 +16,7 @@ app.use(koaLoggerMiddleware)
 // })
 
 app.use(async (ctx, next) => {
-  await SseMid(ctx.req, ctx.res, next)
+  await SseMid(ctx, next)
 }).use(async (ctx, next) => {
   await AuthMid(ctx.req, ctx.res, next)
 })

+ 0 - 1
server/src/middleware/SSE/server.ts

@@ -1 +0,0 @@
-../../../../docs/.vitepress/plugins/SSE/server.ts

+ 89 - 0
server/src/middleware/SSE/server.ts

@@ -0,0 +1,89 @@
+import type { Context, Next } from 'koa';
+import { parse } from 'cookie'
+import { PassThrough } from 'stream'
+
+interface Massage {
+  type: string;
+  value: string;
+  time: number;
+}
+
+let sessionList: {[session: string]: Set<PassThrough>} = {}
+
+export default async function SseMid(ctx: Context, next: Next) {
+  const BaseUrl = process.env.BASE_URL
+
+  // 检查session_id
+  let session_id: string | undefined = undefined
+  const cookies = parse(ctx.req.headers.cookie ?? '')
+  if (!cookies.session_id) {
+    session_id = crypto.randomUUID()
+    ctx.res.setHeader('Set-Cookie', `session_id=${session_id}; HttpOnly; Path=/`)
+  } else {
+    session_id = cookies.session_id
+  }
+
+  // 处理SSE
+  if (ctx.req.url === BaseUrl + 'api/sse') {
+    console.log(`recv SSE ${ctx.req.url}`)
+    ctx.req.socket.setTimeout(0)
+    ctx.req.socket.setNoDelay(true)
+    ctx.req.socket.setKeepAlive(true)
+
+    // 设置响应头
+    ctx.set('Content-Type', 'text/event-stream');
+    ctx.set('Cache-Control', 'no-cache');
+    ctx.set('Connection', 'keep-alive');
+    ctx.set('Access-Control-Allow-Origin', '*');
+
+    const stream = new PassThrough()
+
+    ctx.status = 200;
+    ctx.body = stream;
+
+    const interval = setInterval(() => {
+      const msg = {
+        type: 'pending',
+        value: session_id,
+        time: Date.now()
+      }
+      stream.write(`data: ${JSON.stringify(msg)}\n\n`)
+    }, 60 * 1000)
+  
+    ctx.req.on('close', () => {
+      console.log("SSE close")
+      clearInterval(interval)
+      ctx.res.end()
+      if (sessionList[session_id]) {
+        sessionList[session_id].delete(stream)
+      }
+    })
+
+    // 插入res
+    if (!sessionList[session_id]) {
+      sessionList[session_id] = new Set<PassThrough>
+    }
+    sessionList[session_id].add(stream)
+
+    return
+  }
+
+  // 继续执行下一个中间件
+  await next();
+}
+
+export function sendReLogin(session_id: string): boolean {
+  if (!sessionList[session_id]) return false
+
+  console.log("send relogin: ", session_id)
+  sessionList[session_id].forEach(stream => {
+    const msg: Massage = {
+      type: 'command',
+      value: 'Re-login',
+      time: Date.now()
+    }
+    stream.write(`data: ${JSON.stringify(msg)}\n\n`)
+  });
+
+  return true
+}

+ 0 - 1
server/src/middleware/casdoor/config.ts

@@ -1 +0,0 @@
-../../../../docs/.vitepress/plugins/casdoor/config.ts

+ 38 - 0
server/src/middleware/casdoor/config.ts

@@ -0,0 +1,38 @@
+export const ServerUrl = "https://raspberrypi.dev/casdoor"
+export const ClientId = "37ded064c1576151ccdd"
+export const SecretKey = "0212a4cf97f1a78595f77ad3190f8542e31273e3"
+// cert-built-in
+export const Certificate = `
+-----BEGIN CERTIFICATE-----
+MIIE3TCCAsWgAwIBAgIDAeJAMA0GCSqGSIb3DQEBCwUAMCgxDjAMBgNVBAoTBWFk
+bWluMRYwFAYDVQQDEw1jZXJ0LWJ1aWx0LWluMB4XDTI1MDUwNzA5NDMwN1oXDTQ1
+MDUwNzA5NDMwN1owKDEOMAwGA1UEChMFYWRtaW4xFjAUBgNVBAMTDWNlcnQtYnVp
+bHQtaW4wggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC6bhrTnADekQLx
+KtKiOqehyevrHzXKvBykfjwO6k0oLV9VSMntki2FNASDYTs3eFtG7wqPJoWJuDPC
++XISWIwKc0Bc8trIeQkSiJOvLWF57wOX0HlxvGd6ooRrPe6yAHjMcMtNP5s+DzpO
+qfOIOCH9oOFBxHN02VIRoo+ACflI9mkS6TRIAJ8fG5L631qulyYwyq3TJ6vaBxte
+1ZWV0Fh72VD3FaME9pmHdv3IcnX6U2pvwZoN+jSOiaTSCI15+5PFsC3av1k5ZJTH
++Yp7okwVvQRJbwOwM4Lr3eQE7tcUVxLbstVKwhsR2EqtVHRlt+r0SUE72aASaM3m
+iWZS8lKJIJxbSRgtf3zOkpZR4zIBU3zdB9OoHykxLlbg4POOLloWMsdVflXbWzRt
+Abwo5SSsMq+Yr7+E4EB6ricXz1OvUPELEZa1c6QhTUzeBhrV5ro/QNNN32HCFgu4
+ivLxijF7FFGdxFykUArWxEd+d5+lCuHn8lDxYGBvp65G36f8knyOtN4K02cFoNtY
+5l+s9yqkyELfw8it0bkxO609KZd9Xkp3wUloBMdiJ7UxUNIVS0bpL7wkwt/CzPdM
+Bm/RAPzkjazKgireWZ0cKv3QfYu4+QC8E7QeXI9Oqm6YCSMPsGJBOM9arA7xZdUc
+JZnNstHWHrFihbY1nG6WQwT8ndMYpwIDAQABoxAwDjAMBgNVHRMBAf8EAjAAMA0G
+CSqGSIb3DQEBCwUAA4ICAQAri/3HicFl5pu3B2udfdVgxmgUmf1nvPFhuiV7hgIP
+5CVHe2IiDPJYGL55NNqYQpRLTE7Egnz6TMRiVuYKYBHLM2Tb133GpTvXntiwb+D/
+FSha4clBJBBHlx7lX8xGy+FIwlWQQzB/O8S1hbRi869Kb91QFIRgbRsW6Bt+0Y+9
+OtilWi5scqV28Lt2XyzxsQgsYAu0VOSxFs8Rk1WIvAAciefiPMGAlIpsm52c/0H+
+s1LqdPLoj+GF3/iMOd4EIRFGiH3ntdBqYjvf39w90uNn0uWBnVdyu8IsbNU92v84
+jdE7sTYMD74Q9rt9JH/dr2Z4ufjRSn6x7mRNxw3oPnMd5qdbB1ux8j5zrJAARRrQ
+QKju4s5SB+6WWjALhoMfO7N0wIqoKDleoaGxUj1r4AX9P+wRP0H1W2PV/H0y+el1
+gjpPCfs/IZGgWkSvpHI0b6yikTaCZeAgoBPB2sjTWZyeBnkXEM8BGgVjgaoFUwIl
+OgE9eIWpTKBvPYYellO+hB47j75BnIF7GImDBIwHvEtk7jw3psaLSYGGoNhcG5X9
+xJe+ghCvXu1cz5mvUxMIrUsbUL4zSCsJpkzQQQ+95ZJroGsl6dwewMY8hYhXo7wJ
+EikVj20dYSjAC2azqGic6fIDosE+qdT8zzCkmEOJQg42D1eRl+E2KmIThFpIyNp/
+Vw==
+-----END CERTIFICATE-----
+`
+export const AppName = "app-franklinwh-wiki"
+export const OrganizationName = "organization-franklinwh"
+export const RedirectPath = "/fwhwiki/casdoor/callback.html"

+ 0 - 1
server/src/middleware/casdoor/mid.ts

@@ -1 +0,0 @@
-../../../../docs/.vitepress/plugins/casdoor/mid.ts

+ 200 - 0
server/src/middleware/casdoor/mid.ts

@@ -0,0 +1,200 @@
+import Connect from 'connect';
+import { ServerResponse } from 'node:http';
+import https from 'https';
+import { parse as parseCookie } from 'cookie';
+import { sendReLogin } from '../SSE/server';
+
+import casdoorServer from 'casdoor-nodejs-sdk';
+import { 
+  ServerUrl, 
+  ClientId, 
+  SecretKey, 
+  Certificate, 
+  AppName, 
+  OrganizationName
+} from './config';
+
+const sdk = new casdoorServer.SDK({
+  endpoint: ServerUrl,
+  clientId: ClientId,
+  clientSecret: SecretKey,
+  certificate: Certificate,
+  orgName: OrganizationName,
+  appName: AppName
+}, {
+  httpsAgent: new https.Agent({ rejectUnauthorized: false })
+})
+
+// 未授权响应
+function resUnauthorized(res: ServerResponse<Connect.IncomingMessage>, cookies?: Record<string, string | undefined>) {
+  res.statusCode = 401; // Unauthorized
+  res.setHeader('Content-Type', 'text/plain')
+  res.end('Unauthorized')
+
+  if (cookies) {
+    const session_id = cookies['session_id']
+    if (session_id) {
+      // 重新登录
+      setTimeout(() => {
+        sendReLogin(session_id);
+      }, 3000);
+    }
+  }
+}
+
+// 处理登录请求
+async function handleSignin(url: URL): Promise<{ access_token: string; refresh_token: string; } | undefined> {
+  const code = url.searchParams.get('code');
+  const state = url.searchParams.get('state');
+  if (code && state) {
+    const token = await sdk.getAuthToken(code);
+    return token
+  }
+
+  return undefined
+}
+
+// 验证Token
+function verifyToken(token: string): boolean {
+  try {
+    sdk.parseJwtToken(token);
+    return true;
+  } catch (err) {
+    console.error("token verify failed.");
+    console.error(err);
+  }
+
+  return false;
+}
+
+// 刷新Token
+async function refreshToken(refresh_token: string) {
+  const tokenGroup = await sdk.refreshToken(refresh_token).catch(err => {
+    console.error("refresh token failed.")
+    console.error(err)
+    return undefined
+  })
+
+  return tokenGroup;
+}
+
+export default async function AuthMid(req: Connect.IncomingMessage, res: ServerResponse<Connect.IncomingMessage>, next: Connect.NextFunction) {
+  const BaseUrl = process.env.BASE_URL
+
+  if (req.url) {
+    const url = new URL(req.url, "https://raspberrypi.dev");
+    
+    // 请求保护资源,验证JWT
+    if (!url.pathname.startsWith(BaseUrl + 'casdoor') && url.pathname.endsWith('.md')) {
+      console.log("verify JWT")
+      const cookieStr = req.headers.cookie
+      if (!cookieStr) {
+        resUnauthorized(res);
+        return
+      } else {
+        const cookies = parseCookie(cookieStr);
+        console.log(cookies)
+        const token = cookies['token'];
+        const refresh_token = cookies['refresh_token'];
+
+        // 验证Token
+        if (token && verifyToken(token)) {
+          // 验证成功
+          await next();
+          return
+        }
+
+        // 验证刷新Token
+        if (refresh_token && verifyToken(refresh_token)) {
+          // 刷新Token
+          const tokenGroup = await refreshToken(refresh_token);
+          if (tokenGroup) {
+            // 刷新成功,设置token
+            res.setHeader('Set-Cookie', [
+              `token=${tokenGroup.access_token}; HttpOnly; Path=/; Max-Age=86400; SameSite=Strict`,
+              `refresh_token=${tokenGroup.refresh_token}; HttpOnly; Path=/; SameSite=Strict`
+            ]);
+            await next();
+            return
+          }
+        }
+
+        resUnauthorized(res, cookies);
+        return
+      }
+    }
+
+    // 处理登录查询
+    if (url.pathname.startsWith(BaseUrl + 'api/islogin')) {
+      const cookieStr = req.headers.cookie
+      if (cookieStr) {
+        const cookies = parseCookie(cookieStr);
+        const token = cookies['token'];
+        const refresh_token = cookies['refresh_token'];
+
+        // 验证Token
+        if (token && verifyToken(token)) {
+          // 验证成功
+          res.statusCode = 200;
+          res.setHeader('Content-Type', 'text/plain')
+          res.end(token)
+          return
+        }
+
+        // 验证刷新Token
+        if (refresh_token && verifyToken(refresh_token)) {
+          // 刷新Token
+          const tokenGroup = await refreshToken(refresh_token);
+          if (tokenGroup) {
+            // 刷新成功,设置token
+            res.setHeader('Set-Cookie', [
+              `token=${tokenGroup.access_token}; HttpOnly; Path=/; Max-Age=86400; SameSite=Strict`,
+              `refresh_token=${tokenGroup.refresh_token}; HttpOnly; Path=/; SameSite=Strict`
+            ]);
+            res.statusCode = 200;
+            res.setHeader('Content-Type', 'text/plain')
+            res.end(token)
+            return
+          }
+        }
+      }
+      resUnauthorized(res);
+      return
+    }
+
+    // 处理登录请求
+    if (url.pathname.startsWith(BaseUrl + 'api/signin')) {
+      const signinToken = await handleSignin(url).catch((err) => {
+        console.log("handleSignin: ", err)
+      });
+      console.log("signinToken: ", signinToken)
+      if (signinToken) {
+        res.setHeader('Set-Cookie', [
+          `token=${signinToken.access_token}; HttpOnly; Path=/; Max-Age=86400; SameSite=Strict`,
+          `refresh_token=${signinToken.refresh_token}; HttpOnly; Path=/; SameSite=Strict`
+        ])
+        res.statusCode = 200;
+        res.setHeader('Content-Type', 'application/json')
+        res.end(JSON.stringify(signinToken))
+        return
+      }
+    }
+
+    // 处理登录请求
+    if (url.pathname.startsWith(BaseUrl + 'api/exit')) {
+      console.log("exit")
+      // 清除token
+      res.setHeader('Set-Cookie', [
+        `token=; HttpOnly; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Strict`,
+        `refresh_token=; HttpOnly; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; SameSite=Strict`
+      ])
+      res.statusCode = 200;
+      res.setHeader('Content-Type', 'text/plain')
+      res.end("Exit successful.")
+      return
+    }
+  }
+  
+  // 继续执行下一个中间件
+  await next();
+} 

Diferenças do arquivo suprimidas por serem muito extensas
+ 47 - 0
ssr.html


Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff