提供 Excel 批次匯入匯出機制,讓使用者能高效維護專案內的 SSP 控制項現況說明與 Job 任務設置,取代逐筆 UI 操作。
| 模組 | 匯出內容 | 匯入可設定欄位 |
|---|---|---|
| SSP 控制項現況 | 控制項 + 檢查項目的實作狀態與現況描述 | 實作狀態、現況描述 |
| Job 任務設置 | 所有 Job 的任務類型、指派人員、部門、設備 | 任務類型、指派人員、參與部門、設備 |
兩個模組共用相同的匯入匯出流程架構:
┌──────────────┐
│ 匯出 Excel │
│ GET /export │
└──────┬───────┘
│
瀏覽器下載 .xlsx
(灰底=唯讀,黃底=可編輯)
│
使用者離線編輯 Excel
│
┌──────▼───────┐
│ 上傳 Excel │
│ POST /import │
│(multipart) │
└──────┬───────┘
│
┌──────▼───────┐
│ 驗證結果預覽 │
│ valid / error │
└──────┬───────┘
│
┌────────┼────────┐
│有錯誤 │全部有效
▼ ▼
前端表格編輯 ┌─────────────┐
│ │ 確認匯入 │
▼ │POST /confirm │
┌─────────────┐ └──────┬──────┘
│ 重新驗證 │ │
│POST /validate│ 批次寫入完成
│ (JSON) │ 刷新列表
└──────┬──────┘
│
回到驗證結果預覽
(可重複多次)
每個模組提供 4 支 API:
| 端點 | Method | Content-Type | 用途 |
|---|---|---|---|
/export |
GET | — | 下載 Excel |
/import |
POST | multipart/form-data | 上傳 Excel + 驗證 |
/import/validate |
POST | application/json | JSON 重新驗證 |
/import/confirm |
POST | application/json | 確認寫入 |
| 項目 | 規格 |
|---|---|
| 格式 | .xlsx(OpenXML) |
| 大小限制 | 10 MB |
| Header 行 | 第 1 行,藍底白字,凍結 |
| 唯讀欄位 | 灰底(#F2F2F2),匯入時忽略修改 |
| 可編輯欄位 | 淡黃底(#FFFFC0,同 Excel 備註色) |
| 下拉選單 | openpyxl DataValidation,限制合法值 |
| 框線 | 全部資料格加細框線 |
{
"status": true,
"data": {
"valid_count": 45,
"error_count": 3,
"items": [
{
"row": 2,
"...欄位資料...",
"errors": []
},
{
"row": 5,
"...欄位資料...",
"errors": [
{"field": "欄位名", "message": "錯誤訊息"}
]
}
]
}
}errors 為空陣列 → 該行有效errors 有值 → 該行有錯誤,前端標紅顯示即使未經 validate 直接呼叫 confirm,也會:
skipped_count 告知跳過筆數| Method | URL | 說明 |
|---|---|---|
| GET | /api/1.0/ssp/<ssp_uid>/control-implementations/export |
匯出 Excel |
| POST | /api/1.0/ssp/<ssp_uid>/control-implementations/import |
上傳 Excel + 驗證 |
| POST | /api/1.0/ssp/<ssp_uid>/control-implementations/import/validate |
JSON 重新驗證 |
| POST | /api/1.0/ssp/<ssp_uid>/control-implementations/import/confirm |
確認匯入 |
| 欄 | 標題 | 控制項行 | 檢查項目行 | 說明 |
|---|---|---|---|---|
| A | 控制項識別碼 | 灰底(有值) | 灰底(重複) | e.g. AC.L1-3.1.1 |
| B | 控制項名稱 | 灰底(有值) | 灰底 | e.g. 授權存取控制 |
| C | 實作狀態 | 黃底(可填) | 灰底 | 下拉選單 |
| D | 現況描述 | 黃底(可填) | 灰底 | 自由文字 |
| E | 檢查項目名稱 | 灰底 | 灰底(有值) | e.g. [a] authorized users... |
| F | 檢查項目實作狀態 | 灰底 | 黃底(可填) | 下拉選單 |
| G | 檢查項目現況描述 | 灰底 | 黃底(可填) | 自由文字 |
implemented, partial, not_applicable, not_implemented, inherited, unknown
匯出時:
匯入時:
statement_identifier| 資料 | 來源 |
|---|---|
| 控制項識別碼 | system_security_plan_control_implementations.control_identifier |
| 控制項名稱 | assessment_plan_controls.control_title(透過 SSP → AP → Controls) |
| 檢查項目名稱 | assessment_plan_tasks.title(透過 SSP → AP → Controls → TaskControls → Tasks) |
| 實作狀態/現況描述 | system_security_plan_control_implementations / ssp_control_implementation_objectives |
| 欄位 | 說明 | 前端顯示 |
|---|---|---|
control_identifier |
控制項識別碼 | 顯示 |
control_name |
控制項名稱 | 顯示 |
objective_id |
檢查項目內部 ID | 不顯示(原封帶回) |
objective_name |
檢查項目名稱 | 顯示 |
implementation_status |
控制項實作狀態 | 控制項行顯示 |
implementation_description |
控制項現況描述 | 控制項行顯示 |
obj_implementation_status |
檢查項目實作狀態 | 檢查項目行顯示 |
obj_implementation_description |
檢查項目現況描述 | 檢查項目行顯示 |
{
"updated_controls": 15,
"updated_objectives": 30,
"total": 45,
"skipped_count": 0
}| Method | URL | 說明 |
|---|---|---|
| GET | /api/1.0/grc/project/<pid>/ap/<apid>/jobs/export |
匯出 Excel |
| POST | /api/1.0/grc/project/<pid>/ap/<apid>/jobs/import |
上傳 Excel + 驗證 |
| POST | /api/1.0/grc/project/<pid>/ap/<apid>/jobs/import/validate |
JSON 重新驗證 |
| POST | /api/1.0/grc/project/<pid>/ap/<apid>/jobs/import/confirm |
確認匯入(需 manager 角色) |
| 欄 | 標題 | 底色 | 可見 | 必填 | 說明 |
|---|---|---|---|---|---|
| A | job_uid | — | 隱藏 | — | 程式用,匯入時精確匹配 Job |
| B | 控制項群組 | 灰底 | 可見 | — | group name |
| C | 控制項識別碼 | 灰底 | 可見 | — | e.g. AC.L1-3.1.1 |
| D | 控制項名稱 | 灰底 | 可見 | — | control title |
| E | 檢查項目名稱 | 灰底 | 可見 | — | AO name |
| F | 任務名稱 | 灰底 | 可見 | — | job name |
| G | 任務類型 | 黃底 | 可見 | 必填 | 下拉:general / survey |
| H | 指派人員 | 黃底 | 可見 | 必填 | 逗號分隔 login_name |
| I | 參與部門 | 黃底 | 可見 | 選填 | 逗號分隔部門名稱 |
| J | 設備 | 黃底 | 可見 | 選填 | 逗號分隔設備名稱 |
匯出資料按 控制項群組 → 控制項識別碼 → 檢查項目名稱 升序排列。
job_uid)使用 openpyxl column_dimensions["A"].hidden = Trueiter_rows(values_only=True) 仍能讀取隱藏欄的值job_uid 精確匹配,解決同一 AO 下多個同名 Job 的問題| 欄位 | 規則 | 錯誤訊息 |
|---|---|---|
| job_uid(A 欄) | 必須存在且屬於此 AP | 「任務不屬於此稽核計畫」 |
| job_uid 重複 | 同一 Excel 不允許重複 | 「重複的任務識別碼」 |
| 任務類型 | 必填,general 或 survey |
「無效的任務類型」 |
| 指派人員 | 必填,每個 login_name 須存在(不分大小寫) | 「使用者不存在:xxx」 |
| 參與部門 | 選填,名稱須存在且不重名 | 「部門不存在」/「部門名稱重複,請改用 UI」 |
| 設備 | 選填,名稱須存在且不重名 | 「設備不存在」/「設備名稱重複,請改用 UI」 |
update_job 需要 control_uid 才能更新 assignees(repo 層限制)JobImportLookupQuery.get_job_control_uid_map() 批次查詢 job_uid → control_uid 的 mapping_check_manager_role() 在 service 層驗證403 GRC_403002| 欄位 | 說明 | 前端顯示 |
|---|---|---|
job_uid |
Job 內部 ID | 不顯示(原封帶回) |
job_name |
任務名稱 | 顯示 |
control_name |
控制項名稱 | 顯示 |
ao_name |
檢查項目名稱 | 顯示 |
job_type |
任務類型 | 顯示,可編輯 |
assignees |
指派人員(逗號分隔) | 顯示,可編輯 |
departments |
參與部門(逗號分隔) | 顯示,可編輯 |
devices |
設備(逗號分隔) | 顯示,可編輯 |
{
"updated_count": 30,
"skipped_count": 0
}| 錯誤碼 | HTTP | 訊息 | 適用模組 |
|---|---|---|---|
GRC_400001 |
400 | 上傳檔案格式無效,請使用匯出的 Excel 範本 | SSP |
GRC_400002 |
400 | 上傳檔案超過大小限制(10MB) | SSP |
GRC_400003 |
400 | 上傳檔案格式無效,請使用匯出的 Excel 範本 | Job |
GRC_400004 |
400 | 上傳檔案超過大小限制(10MB) | Job |
GRC_403002 |
403 | 使用者不具備管理者角色 | Job confirm |
GRC_404012 |
404 | SSP 系統安全計畫不存在 | SSP |
GRC_404015 |
404 | 稽核計畫不存在 | Job |
api/ ← Route 層(HTTP 邊界)
├── oscal/routes/ssp/
│ └── ssp_control_impl_import_route.py ← SSP 匯入匯出 routes
├── oscal/serializers/ssp/
│ └── ssp_control_impl_import.py ← SSP Marshmallow schemas
├── grc/routes/
│ └── job_import_route.py ← Job 匯入匯出 routes
└── grc/serializers/
└── job_import.py ← Job Marshmallow schemas
app/ ← App Service 層(業務邏輯編排)
├── oscal/service/
│ └── ssp_control_impl_import_service.py ← SSP 匯入匯出 service
└── grc/service/
└── job_import_service.py ← Job 匯入匯出 service
infra/ ← 基礎設施層(ORM 查詢)
├── oscal/repository/
│ └── ssp_catalog_title_query.py ← SSP 控制項/AO 標題查詢
└── grc/repository/
├── job_export_query.py ← Job 匯出資料查詢
└── job_import_lookup_query.py ← Job 匯入驗證/確認 lookup
| Container | Provider | 類別 |
|---|---|---|
oscal_container |
ssp_catalog_title_query |
SspCatalogTitleQuery (Singleton) |
oscal_container |
ssp_control_impl_import_service |
SspControlImplImportService (Factory) |
grc_container |
job_export_query |
JobExportQuery (Singleton) |
grc_container |
job_import_lookup_query |
JobImportLookupQuery (Singleton) |
grc_container |
job_import_service |
JobImportService (Factory) |
SSP Import Service:
SspControlImplImportService
├── SystemSecurityPlanDomainService (SSP 查詢)
├── ControlImplementationDomainService (控制項查詢)
├── ControlImplementationObjectiveDomainService (Objective 查詢)
├── SspControlImplementationService (upsert 複用)
└── SspCatalogTitleQuery (標題查詢,infra)
Job Import Service:
JobImportService
├── JobService (update_job 複用)
├── JobExportQuery (匯出資料查詢,infra)
├── JobImportLookupQuery (驗證/確認 lookup,infra)
├── ProjectDomainService (權限檢查用)
└── ProjectParticipantDomainService (權限檢查用)
| 檔案 | 職責 |
|---|---|
api/oscal/serializers/ssp/ssp_control_impl_import.py |
SSP Marshmallow schemas |
api/oscal/routes/ssp/ssp_control_impl_import_route.py |
SSP 4 支 API routes |
app/oscal/service/ssp_control_impl_import_service.py |
SSP 匯入匯出 service |
infra/oscal/repository/ssp_catalog_title_query.py |
SSP 標題查詢(infra) |
infra/oscal/__init__.py |
package init |
infra/oscal/repository/__init__.py |
package init |
api/grc/serializers/job_import.py |
Job Marshmallow schemas |
api/grc/routes/job_import_route.py |
Job 4 支 API routes |
app/grc/service/job_import_service.py |
Job 匯入匯出 service |
infra/grc/repository/job_export_query.py |
Job 匯出資料查詢(infra) |
infra/grc/repository/job_import_lookup_query.py |
Job 匯入驗證/確認 lookup(infra) |
| 檔案 | 變更內容 |
|---|---|
api/oscal/__init__.py |
註冊 SSP import/export routes |
api/grc/__init__.py |
註冊 Job import/export routes |
di_containers/oscal/oscal_containers.py |
SSP DI wiring |
di_containers/grc/grc_containers.py |
Job DI wiring |
common/code/grc_error_code.py |
新增 4 個 error codes |
| 項目 | 原因 | 替代方案 |
|---|---|---|
| 問卷配置匯入 | 問卷有類型、審核流程、per-user 權限,Excel 無法表達 | 保留在 UI 操作 |
| 指派人員審核者設定 | 增加 Excel 複雜度,使用頻率低 | 保留在 UI 操作 |
| SSP 備註欄位 | UI 暫無此功能,為預留欄位 | 未來需要時加回 |
| 新增 Job | 匯入只更新現有 Job,不支援新增 | 使用現有 Job create API |