將 SSP 程序書改為「池 + 關聯」架構:
之前(一對一,重複上傳):
控制項 A → 上傳 程序書1.pdf
控制項 B → 再上傳一次 程序書1.pdf(重複)
AO [a] → 又上傳一次 程序書1.pdf(第三次)
現在(池 + 多對多):
SSP 程序書池:
📄 程序書1.pdf(上傳一次)
📄 程序書2.pdf
📄 程序書3.pdf
控制項 A → 關聯 程序書1, 程序書2
控制項 B → 關聯 程序書1, 程序書3
AO [a] → 關聯 程序書1
(同一份文件,不同地方使用,不需重複上傳)
Step 1: 管理者進入 SSP → 「程序書管理」頁面
→ 拖拽批次上傳所有程序書
→ 每份可填寫描述(如「資訊安全政策 v3.0」)
Step 2: 在控制項詳情頁
→ 點「關聯程序書」→ Dialog 顯示池中所有程序書
→ 多選 → 確認
Step 3: 在 AO 詳情頁
→ 同上操作
GET /api/1.0/ssp/<ssp_uid>/document-pool
Authorization: Bearer <token>
Response:
{
"status": true,
"data": [
{
"uid": "doc-uuid-1",
"file_uid": "file-uuid-1",
"file_name": "資訊安全政策v3.0.pdf",
"file_size": 1024000,
"description": "資訊安全政策 v3.0",
"mapping_count": 5,
"created_at": "2026-03-29T10:00:00"
},
{
"uid": "doc-uuid-2",
"file_uid": "file-uuid-2",
"file_name": "存取控制管理辦法.docx",
"file_size": 512000,
"description": "存取控制管理辦法 v2.1",
"mapping_count": 3,
"created_at": "2026-03-29T10:05:00"
}
]
}| 欄位 | 說明 |
|---|---|
uid |
程序書 UID(用於關聯和刪除) |
file_uid |
檔案 UID(用於下載) |
file_name |
檔案名稱 |
file_size |
檔案大小(bytes) |
description |
程序書描述 |
mapping_count |
被關聯的次數(控制項 + AO 的總數) |
created_at |
上傳時間 |
POST /api/1.0/ssp/<ssp_uid>/document-pool
Authorization: Bearer <token>
Content-Type: application/json
{
"documents": [
{
"file_uid": "已上傳檔案的 UID(透過 /file/upload 取得)",
"description": "資訊安全政策 v3.0"
},
{
"file_uid": "另一個 file_uid",
"description": "存取控制管理辦法"
}
]
}
流程: 先用 POST /file/upload 上傳實體檔案取得 file_uid,再呼叫此 API 加入池。
DELETE /api/1.0/ssp/<ssp_uid>/document-pool/<doc_uid>
注意: 刪除會 cascade 刪除所有關聯。建議前端在刪除前:
mapping_count > 0 時顯示警告:「此程序書已關聯 N 個控制項/檢查項目,刪除後關聯也會被移除」GET /api/1.0/ssp/<ssp_uid>/control-implementation/<control_identifier>/document-mappings
Response:
{
"status": true,
"data": [
{
"uid": "doc-uuid-1",
"file_uid": "file-uuid-1",
"file_name": "資訊安全政策v3.0.pdf",
"file_size": 1024000,
"description": "資訊安全政策 v3.0"
}
]
}POST /api/1.0/ssp/<ssp_uid>/control-implementation/<control_identifier>/document-mappings
Content-Type: application/json
{
"document_uids": ["doc-uuid-1", "doc-uuid-2", "doc-uuid-3"]
}
Response:
{
"status": true,
"data": {
"added_count": 3
}
}注意: 已存在的關聯會自動跳過,不會重複建立。
DELETE /api/1.0/ssp/<ssp_uid>/control-implementation/<control_identifier>/document-mapping/<doc_uid>
API 格式與控制項關聯完全相同,只是 URL 多了 /objective/<statement_identifier> 層級。
GET /api/1.0/ssp/<ssp_uid>/control-implementation/<ctrl_id>/objective/<stmt_id>/document-mappings
POST .../objective/<stmt_id>/document-mappings
Body: { "document_uids": ["doc-uid-1", "doc-uid-2"] }
DELETE .../objective/<stmt_id>/document-mapping/<doc_uid>
入口: SSP 詳情頁面新增「程序書管理」tab 或左側選單項目
┌──────────────────────────────────────────────────────────────┐
│ SSP 程序書管理 [上傳程序書] │
├──────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┬──────────┬───────────┬──────────┬──────────┐ │
│ │ 檔案名稱 │ 描述 │ 檔案大小 │ 關聯數量 │ 操作 │ │
│ ├──────────┼──────────┼───────────┼──────────┼──────────┤ │
│ │ 資訊安全 │ v3.0 │ 1.0 MB │ 5 │ 🔗 🗑 │ │
│ │ 政策.pdf │ │ │ │ │ │
│ ├──────────┼──────────┼───────────┼──────────┼──────────┤ │
│ │ 存取控制 │ v2.1 │ 512 KB │ 3 │ 🔗 🗑 │ │
│ │ 管理辦法 │ │ │ │ │ │
│ └──────────┴──────────┴───────────┴──────────┴──────────┘ │
│ │
│ 拖拽檔案到此處上傳,或點擊「上傳程序書」按鈕 │
└──────────────────────────────────────────────────────────────┘
操作:
mapping_count > 0 時警告)在控制項和 AO 詳情頁面,「參考程序書」區塊改為:
┌────────────────────────────────────────────┐
│ 參考程序書 [關聯程序書] │
├────────────────────────────────────────────┤
│ 📄 資訊安全政策v3.0.pdf [取消關聯] │
│ 📄 存取控制管理辦法.docx [取消關聯] │
│ │
│ (尚未關聯程序書時顯示空狀態提示) │
└────────────────────────────────────────────┘
點擊「關聯程序書」按鈕,彈出從池中選擇的 Dialog:
┌────────────────────────────────────────────┐
│ 關聯程序書 [搜尋...] │
├────────────────────────────────────────────┤
│ ☑ 資訊安全政策v3.0.pdf (已關聯) │
│ ☐ 存取控制管理辦法.docx │
│ ☐ 實體安全管理規範.pdf │
│ ☑ 事件應變計畫.pdf (已關聯) │
├────────────────────────────────────────────┤
│ [取消] [確認關聯] │
└────────────────────────────────────────────┘
邏輯:
GET /document-poolGET /document-mappingsPOST /document-mappings,取消的打 DELETE /document-mapping/<uid>// Step 1: 上傳實體檔案
const fileRes = await uploadFile(file) // POST /file/upload
const fileUid = fileRes.data.uid
// Step 2: 加入程序書池
await api.post(`/ssp/${sspUid}/document-pool`, {
documents: [{ file_uid: fileUid, description: '資訊安全政策 v3.0' }]
})
// Step 3: 刷新池清單
refreshPool()| Method | URL | 說明 |
|---|---|---|
| GET | /ssp/<uid>/document-pool |
列出程序書池 |
| POST | /ssp/<uid>/document-pool |
上傳到池 |
| DELETE | /ssp/<uid>/document-pool/<doc_uid> |
從池刪除(cascade) |
| GET | .../control-implementation/<ctrl>/document-mappings |
控制項的關聯清單 |
| POST | .../control-implementation/<ctrl>/document-mappings |
建立控制項關聯 |
| DELETE | .../control-implementation/<ctrl>/document-mapping/<doc_uid> |
取消控制項關聯 |
| GET | .../objective/<stmt>/document-mappings |
AO 的關聯清單 |
| POST | .../objective/<stmt>/document-mappings |
建立 AO 關聯 |
| DELETE | .../objective/<stmt>/document-mapping/<doc_uid> |
取消 AO 關聯 |
| 舊 API | 狀態 | 說明 |
|---|---|---|
POST .../reference-documents |
保留但建議逐步遷移 | 舊的直接上傳到控制項/AO |
DELETE .../reference-document/<uid> |
保留 | 舊的刪除 |
新 document-pool 系列 |
新增 | 池 + 關聯模式 |
建議前端先實作新的池 + 關聯 UI,舊的 API 暫時保留相容,後續視遷移進度決定是否廢棄。