docs: 更新 README 並封存 sql-query-management-refactor 提案

- 更新 README.md 反映目前開發狀態 (v3.0)
  - 新增設備狀態監控、設備歷史查詢功能說明
  - 新增 Redis 快取系統、SQL 查詢安全架構狀態
  - 擴展專案結構說明,展示 sql/ 模組細節
  - 新增 Redis 環境需求與設定說明
  - 補充 2026-01-29 ~ 2026-02-03 變更日誌
- 封存 sql-query-management-refactor 至 archive/2026-02-03-
- 同步 3 個新 specs 至主目錄 (sql-loader, query-builder, common-filters)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
beabigegg
2026-02-03 16:40:01 +08:00
parent f36e55e8a1
commit de38959568
11 changed files with 237 additions and 4 deletions

View File

@@ -1,6 +1,6 @@
# MES Dashboard 報表系統 # MES Dashboard 報表系統
基於 Flask + Gunicorn 的 MES 數據報表查詢與可視化系統 基於 Flask + Gunicorn + Redis 的 MES 數據報表查詢與可視化系統
--- ---
@@ -12,8 +12,12 @@
| WIP 明細查詢 | ✅ 已完成 | | WIP 明細查詢 | ✅ 已完成 |
| Hold 狀態分析 | ✅ 已完成 | | Hold 狀態分析 | ✅ 已完成 |
| 數據表查詢工具 | ✅ 已完成 | | 數據表查詢工具 | ✅ 已完成 |
| 設備狀態監控 | ✅ 已完成 |
| 設備歷史查詢 | ✅ 已完成 |
| 管理員認證系統 | ✅ 已完成 | | 管理員認證系統 | ✅ 已完成 |
| 頁面狀態管理 | ✅ 已完成 | | 頁面狀態管理 | ✅ 已完成 |
| Redis 快取系統 | ✅ 已完成 |
| SQL 查詢安全架構 | ✅ 已完成 |
| 部署自動化 | ✅ 已完成 | | 部署自動化 | ✅ 已完成 |
--- ---
@@ -63,6 +67,7 @@ nano .env
- Python 3.11+ - Python 3.11+
- Conda (Miniconda/Anaconda) - Conda (Miniconda/Anaconda)
- Oracle Database 連線 - Oracle Database 連線
- Redis Server 7.x+ (設備狀態快取)
### 部署步驟 ### 部署步驟
@@ -118,6 +123,11 @@ GUNICORN_BIND=0.0.0.0:8080 # 服務監聽位址
GUNICORN_WORKERS=2 # Worker 數量 GUNICORN_WORKERS=2 # Worker 數量
GUNICORN_THREADS=4 # 每個 Worker 的執行緒數 GUNICORN_THREADS=4 # 每個 Worker 的執行緒數
# Redis 設定
REDIS_HOST=localhost # Redis 伺服器位址
REDIS_PORT=6379 # Redis 端口
REDIS_DB=0 # Redis 資料庫編號
# 管理員設定 # 管理員設定
ADMIN_EMAILS=admin@example.com # 管理員郵件(逗號分隔) ADMIN_EMAILS=admin@example.com # 管理員郵件(逗號分隔)
``` ```
@@ -143,6 +153,8 @@ ADMIN_EMAILS=admin@example.com # 管理員郵件(逗號分隔)
- WIP 即時概況 - WIP 即時概況
- WIP 明細查詢 - WIP 明細查詢
- Hold 狀態分析 - Hold 狀態分析
- 設備狀態監控
- 設備歷史查詢
- 數據表查詢工具 - 數據表查詢工具
### WIP 即時概況 ### WIP 即時概況
@@ -151,6 +163,7 @@ ADMIN_EMAILS=admin@example.com # 管理員郵件(逗號分隔)
- 按 SPEC 和 WORKCENTER 統計 - 按 SPEC 和 WORKCENTER 統計
- 按產品線統計(匯總 + 明細) - 按產品線統計(匯總 + 明細)
- Hold 狀態分類(品質異常/非品質異常) - Hold 狀態分類(品質異常/非品質異常)
- 柏拉圖視覺化圖表
### WIP 明細查詢 ### WIP 明細查詢
@@ -167,9 +180,24 @@ ADMIN_EMAILS=admin@example.com # 管理員郵件(逗號分隔)
- Hold 明細查詢 - Hold 明細查詢
- 品質異常分類統計 - 品質異常分類統計
### 設備狀態監控
- 即時設備狀態總覽PRD/SBY/UDT/SDT/EGT/NST
- 按工作中心群組統計
- 設備稼動率OU%與運轉率RUN%
- 階層篩選(廠區/產線/重點設備/監控設備)
- Redis 快取自動更新30 秒間隔)
### 設備歷史查詢
- 歷史狀態趨勢分析
- 稼動率熱力圖視覺化
- 設備狀態明細查詢
- 支援 CSV 匯出
### 管理員功能 ### 管理員功能
- LDAP 認證登入 - LDAP 認證登入(支援本地測試模式)
- 頁面狀態管理released/dev - 頁面狀態管理released/dev
- Dev 頁面僅管理員可見 - Dev 頁面僅管理員可見
@@ -186,6 +214,7 @@ ADMIN_EMAILS=admin@example.com # 管理員郵件(逗號分隔)
| Gunicorn | 23.x | WSGI 伺服器 | | Gunicorn | 23.x | WSGI 伺服器 |
| SQLAlchemy | 2.x | ORM | | SQLAlchemy | 2.x | ORM |
| oracledb | 2.x | Oracle 驅動 | | oracledb | 2.x | Oracle 驅動 |
| Redis | 7.x | 快取伺服器 |
| Pandas | 2.x | 資料處理 | | Pandas | 2.x | 資料處理 |
### 前端技術棧 ### 前端技術棧
@@ -212,9 +241,32 @@ DashBoard/
├── src/mes_dashboard/ # 主程式 ├── src/mes_dashboard/ # 主程式
│ ├── app.py # Flask 應用 │ ├── app.py # Flask 應用
│ ├── config/ # 設定 │ ├── config/ # 設定
│ │ ├── settings.py # 環境設定
│ │ ├── constants.py # 常數定義
│ │ └── workcenter_groups.py # 工作中心群組設定
│ ├── core/ # 核心模組 │ ├── core/ # 核心模組
│ │ ├── database.py # 資料庫連線
│ │ ├── redis_client.py # Redis 客戶端
│ │ ├── cache.py # 快取管理
│ │ └── cache_updater.py # 快取自動更新
│ ├── routes/ # 路由 │ ├── routes/ # 路由
│ │ ├── wip_routes.py # WIP 相關 API
│ │ ├── resource_routes.py # 設備狀態 API
│ │ ├── dashboard_routes.py # 儀表板 API
│ │ └── ... # 其他路由
│ ├── services/ # 服務層 │ ├── services/ # 服務層
│ │ ├── wip_service.py # WIP 業務邏輯
│ │ ├── resource_service.py # 設備狀態邏輯
│ │ ├── resource_cache.py # 設備快取服務
│ │ └── ... # 其他服務
│ ├── sql/ # SQL 查詢管理
│ │ ├── loader.py # SQLLoader (LRU 快取)
│ │ ├── builder.py # QueryBuilder (參數化)
│ │ ├── filters.py # CommonFilters
│ │ ├── dashboard/ # 儀表板查詢
│ │ ├── resource/ # 設備查詢
│ │ ├── wip/ # WIP 查詢
│ │ └── resource_history/ # 設備歷史查詢
│ └── templates/ # HTML 模板 │ └── templates/ # HTML 模板
├── scripts/ # 腳本 ├── scripts/ # 腳本
│ ├── deploy.sh # 部署腳本 │ ├── deploy.sh # 部署腳本
@@ -290,6 +342,29 @@ pytest tests/stress/ -v
## 變更日誌 ## 變更日誌
### 2026-02-03
- 重構 SQL 查詢管理架構,提升安全性與效能
- 新增 SQLLoader (LRU 快取)、QueryBuilder (參數化)、CommonFilters 模組
- 抽取 20 個 SQL 檔案至 `src/mes_dashboard/sql/` 目錄
- 修復所有 SQL 注入風險LIKE 萬用字元跳脫、IN 條件參數化)
- 優化 workcenter_cards API 回應時間55s → 0.1s
### 2026-02-02
- 新增 Hold Summary 柏拉圖視覺化圖表
- 設備頁面統一排序、階層篩選與標籤優化
### 2026-01-30
- 新增本地認證模式支援開發測試環境
### 2026-01-29
- 新增設備狀態監控頁面
- 新增設備歷史查詢頁面
- 整合 Redis 快取系統30 秒自動更新)
### 2026-01-28 ### 2026-01-28
- 新增管理員認證系統LDAP 整合) - 新增管理員認證系統LDAP 整合)
@@ -318,5 +393,5 @@ pytest tests/stress/ -v
--- ---
**文檔版本**: 2.0 **文檔版本**: 3.0
**最後更新**: 2026-01-28 **最後更新**: 2026-02-03

View File

@@ -0,0 +1,64 @@
## Requirements
### Requirement: 廠區排除篩選
系統 SHALL 提供 `add_location_exclusion()` 方法,排除設定檔中定義的廠區。
#### Scenario: 排除設定的廠區
- **WHEN** `EXCLUDED_LOCATIONS = ["ATEC", "F區"]`
- **AND** 呼叫 `CommonFilters.add_location_exclusion(builder)`
- **THEN** 產生條件 `(LOCATIONNAME IS NULL OR LOCATIONNAME NOT IN (:p0, :p1))`
#### Scenario: 無排除廠區時不產生條件
- **WHEN** `EXCLUDED_LOCATIONS = []`
- **AND** 呼叫 `CommonFilters.add_location_exclusion(builder)`
- **THEN** 不新增任何條件
### Requirement: 資產狀態排除篩選
系統 SHALL 提供 `add_asset_status_exclusion()` 方法,排除設定檔中定義的資產狀態。
#### Scenario: 排除設定的資產狀態
- **WHEN** `EXCLUDED_ASSET_STATUSES = ["報廢", "閒置"]`
- **AND** 呼叫 `CommonFilters.add_asset_status_exclusion(builder)`
- **THEN** 產生條件 `(PJ_ASSETSSTATUS IS NULL OR PJ_ASSETSSTATUS NOT IN (:p0, :p1))`
### Requirement: WIP 基礎篩選
系統 SHALL 提供 `add_wip_base_filters()` 方法,處理 WIP 查詢的常用篩選條件。
#### Scenario: 工單模糊搜尋
- **WHEN** 呼叫 `CommonFilters.add_wip_base_filters(builder, workorder="WO123")`
- **THEN** 產生 LIKE 條件搜尋 WORKORDER 欄位
#### Scenario: 批號模糊搜尋
- **WHEN** 呼叫 `CommonFilters.add_wip_base_filters(builder, lotid="LOT001")`
- **THEN** 產生 LIKE 條件搜尋 LOTID 欄位
#### Scenario: 多條件組合
- **WHEN** 呼叫 `CommonFilters.add_wip_base_filters(builder, workorder="WO", package="PKG")`
- **THEN** 產生兩個 LIKE 條件,以 AND 連接
### Requirement: 狀態篩選
系統 SHALL 提供 `add_status_filter()` 方法,處理 WIP 狀態篩選。
#### Scenario: 單一狀態篩選
- **WHEN** 呼叫 `CommonFilters.add_status_filter(builder, status="HOLD")`
- **THEN** 產生條件 `STATUS = :p0`
#### Scenario: 多狀態篩選
- **WHEN** 呼叫 `CommonFilters.add_status_filter(builder, statuses=["RUN", "QUEUE"])`
- **THEN** 產生條件 `STATUS IN (:p0, :p1)`
### Requirement: Hold 類型篩選
系統 SHALL 提供 `add_hold_type_filter()` 方法,區分品質與非品質 Hold。
#### Scenario: 品質 Hold 篩選
- **WHEN** 呼叫 `CommonFilters.add_hold_type_filter(builder, hold_type="quality")`
- **THEN** 產生條件排除 `NON_QUALITY_HOLD_REASONS` 中的 Hold 原因
#### Scenario: 非品質 Hold 篩選
- **WHEN** 呼叫 `CommonFilters.add_hold_type_filter(builder, hold_type="non_quality")`
- **THEN** 產生條件僅包含 `NON_QUALITY_HOLD_REASONS` 中的 Hold 原因

View File

@@ -0,0 +1,55 @@
## Requirements
### Requirement: 參數化條件建構
系統 SHALL 提供 `QueryBuilder` 類別,建構參數化的 SQL 條件,避免 SQL 注入風險。
#### Scenario: 建構等值條件
- **WHEN** 呼叫 `builder.add_param_condition("status", "RUN")`
- **THEN** 產生條件 `status = :p0``params = {"p0": "RUN"}`
#### Scenario: 建構 IN 條件
- **WHEN** 呼叫 `builder.add_in_condition("status", ["RUN", "QUEUE", "HOLD"])`
- **THEN** 產生條件 `status IN (:p0, :p1, :p2)`
- **AND** `params = {"p0": "RUN", "p1": "QUEUE", "p2": "HOLD"}`
#### Scenario: 空值 IN 條件不產生語句
- **WHEN** 呼叫 `builder.add_in_condition("status", [])`
- **THEN** 不新增任何條件
### Requirement: LIKE 條件安全處理
系統 SHALL 在建構 LIKE 條件時,自動跳脫 SQL 萬用字元(`%``_`)。
#### Scenario: 跳脫 LIKE 萬用字元
- **WHEN** 呼叫 `builder.add_like_condition("name", "test%value")`
- **THEN** 產生條件 `name LIKE :p0 ESCAPE '\'`
- **AND** `params = {"p0": "%test\\%value%"}`
#### Scenario: LIKE 位置控制
- **WHEN** 呼叫 `builder.add_like_condition("name", "prefix", position="start")`
- **THEN** `params = {"p0": "prefix%"}`(不含前綴 %
### Requirement: WHERE 子句組合
系統 SHALL 自動組合多個條件為完整的 WHERE 子句。
#### Scenario: 多條件 AND 組合
- **WHEN** 新增多個條件後呼叫 `builder.build()`
- **THEN** 產生 `WHERE cond1 AND cond2 AND cond3`
#### Scenario: 無條件時不產生 WHERE
- **WHEN** 未新增任何條件即呼叫 `builder.build()`
- **THEN** `{{ WHERE_CLAUSE }}` 被替換為空字串
### Requirement: NOT IN 條件建構
系統 SHALL 支援 NOT IN 條件,用於排除特定值。
#### Scenario: 建構 NOT IN 條件
- **WHEN** 呼叫 `builder.add_not_in_condition("location", ["ATEC", "F區"])`
- **THEN** 產生條件 `location NOT IN (:p0, :p1)`
#### Scenario: NOT IN 處理 NULL 值
- **WHEN** 呼叫 `builder.add_not_in_condition("location", values, allow_null=True)`
- **THEN** 產生條件 `(location IS NULL OR location NOT IN (...))`

View File

@@ -0,0 +1,39 @@
## Requirements
### Requirement: SQL 檔案載入
系統 SHALL 提供 `SQLLoader` 類別,從 `.sql` 檔案載入 SQL 查詢字串。
#### Scenario: 載入存在的 SQL 檔案
- **WHEN** 呼叫 `SQLLoader.load("wip/summary")`
- **THEN** 系統回傳 `sql/wip/summary.sql` 檔案的完整內容
#### Scenario: 載入不存在的 SQL 檔案
- **WHEN** 呼叫 `SQLLoader.load("nonexistent/query")`
- **THEN** 系統拋出 `FileNotFoundError` 並包含檔案路徑
### Requirement: SQL 檔案快取
系統 SHALL 使用 LRU cache 快取已載入的 SQL 檔案內容,避免重複讀取檔案系統。
#### Scenario: 重複載入相同檔案使用快取
- **WHEN** 連續呼叫 `SQLLoader.load("wip/summary")` 兩次
- **THEN** 第二次呼叫從記憶體快取取得,不重新讀取檔案
#### Scenario: 快取容量限制
- **WHEN** 快取達到 100 個條目上限
- **THEN** 系統自動移除最少使用的條目
### Requirement: 結構性參數替換
系統 SHALL 提供 `load_with_params()` 方法,支援 Jinja2 風格的結構性參數替換(僅用於非使用者輸入的結構性參數)。
#### Scenario: 替換結構性參數
- **WHEN** SQL 檔案內容為 `SELECT * FROM {{ table_name }}`
- **AND** 呼叫 `SQLLoader.load_with_params("query", table_name="DWH.MY_TABLE")`
- **THEN** 系統回傳 `SELECT * FROM DWH.MY_TABLE`
#### Scenario: 未提供的參數保持原樣
- **WHEN** SQL 檔案內容為 `SELECT * FROM {{ table_name }} {{ WHERE_CLAUSE }}`
- **AND** 呼叫 `SQLLoader.load_with_params("query", table_name="T")`
- **THEN** 系統回傳 `SELECT * FROM T {{ WHERE_CLAUSE }}`