docs: 更新專案開發指南與架構文檔
- 將 claude.md 重命名為 CLAUDE.md (符合官方慣例) - 大幅擴充 architecture_findings.md (311→935 行) - 新增章節: SQL 集中管理、熔斷器、API 響應格式、認證授權、 頁面狀態管理、日誌系統、健康檢查、Blueprint 結構、配置管理、 錯誤處理模式 - 修正 MesApi 位置 (api.js → mes-api.js) - 移除敏感資訊,改用 placeholder 格式 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
617
CLAUDE.md
Normal file
617
CLAUDE.md
Normal file
@@ -0,0 +1,617 @@
|
|||||||
|
# MES Dashboard 開發指南
|
||||||
|
|
||||||
|
> 此文檔為 AI 開發助手的專案開發規範,確保後續開發保持一致性。
|
||||||
|
|
||||||
|
## 專案概述
|
||||||
|
|
||||||
|
MES Dashboard 是一個**製造執行系統儀表板**,用於即時監控在製品 (WIP)、設備狀態、生產效率等 MES/ERP 數據。
|
||||||
|
|
||||||
|
- **技術棧**: Flask 3.0 + SQLAlchemy 2.0 + Oracle (oracledb) + Redis + Jinja2 + Vue 3
|
||||||
|
- **Python 版本**: 3.9+(推薦 3.11)
|
||||||
|
- **部署**: Gunicorn + Systemd
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 開始任務前
|
||||||
|
|
||||||
|
1. **閱讀架構文檔**: 查看 `docs/architecture_findings.md` 了解:
|
||||||
|
- 資料庫連線管理模式
|
||||||
|
- 快取機制和 TTL 常量
|
||||||
|
- Filter cache (workcenter/family) 使用方式
|
||||||
|
- 前端全局組件 (Toast, MesApi)
|
||||||
|
- 數據表篩選規則和欄位映射
|
||||||
|
- E10 狀態定義和 OU% 計算
|
||||||
|
- 測試慣例
|
||||||
|
|
||||||
|
2. **修改後更新文檔**: 如果修改了以下模式,需更新 `docs/architecture_findings.md`:
|
||||||
|
- 資料庫連線或連線池方式
|
||||||
|
- 快取策略或 TTL 值
|
||||||
|
- 全局前端組件使用
|
||||||
|
- 數據表欄位名稱或篩選規則
|
||||||
|
- 新的共用工具或服務
|
||||||
|
- 測試慣例或設置模式
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 目錄結構
|
||||||
|
|
||||||
|
```
|
||||||
|
/home/egg/Project/DashBoard/
|
||||||
|
├── src/mes_dashboard/ # 主應用程式
|
||||||
|
│ ├── config/ # 配置管理
|
||||||
|
│ │ ├── settings.py # Flask 環境配置類
|
||||||
|
│ │ ├── database.py # 資料庫連線配置
|
||||||
|
│ │ ├── constants.py # 應用常量 (狀態碼、TTL、排除條件)
|
||||||
|
│ │ ├── tables.py # 表格元數據配置
|
||||||
|
│ │ └── workcenter_groups.py
|
||||||
|
│ ├── core/ # 核心基礎設施
|
||||||
|
│ │ ├── database.py # 連線池、查詢執行、keep-alive
|
||||||
|
│ │ ├── cache.py # 快取抽象層
|
||||||
|
│ │ ├── cache_updater.py # WIP 快取背景更新
|
||||||
|
│ │ ├── circuit_breaker.py # 熔斷器保護
|
||||||
|
│ │ ├── redis_client.py # Redis 客戶端
|
||||||
|
│ │ ├── response.py # API 響應標準化
|
||||||
|
│ │ ├── permissions.py # 權限檢查
|
||||||
|
│ │ ├── log_store.py # SQLite 日誌儲存
|
||||||
|
│ │ ├── metrics.py # 性能指標收集
|
||||||
|
│ │ └── utils.py # 通用工具函數
|
||||||
|
│ ├── routes/ # API 路由 (Blueprint)
|
||||||
|
│ ├── services/ # 業務邏輯層
|
||||||
|
│ ├── sql/ # SQL 查詢模板 (集中管理)
|
||||||
|
│ │ ├── loader.py # SQL 檔案載入器
|
||||||
|
│ │ ├── builder.py # 參數化查詢構建器
|
||||||
|
│ │ ├── filters.py # 通用篩選條件
|
||||||
|
│ │ ├── dashboard/ # 儀表板 SQL
|
||||||
|
│ │ ├── wip/ # WIP SQL
|
||||||
|
│ │ ├── resource/ # 設備 SQL
|
||||||
|
│ │ ├── resource_history/ # 歷史 SQL
|
||||||
|
│ │ └── job_query/ # 維修工單 SQL
|
||||||
|
│ ├── templates/ # Jinja2 HTML 模板
|
||||||
|
│ └── static/ # 靜態資源 (JS/CSS/dist)
|
||||||
|
├── tests/ # 測試套件
|
||||||
|
├── data/ # 運行時數據 (page_status.json)
|
||||||
|
├── logs/ # 日誌目錄
|
||||||
|
├── .env # 環境變數 (不提交版控)
|
||||||
|
└── requirements.txt # Python 依賴
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## SQL 管理規範
|
||||||
|
|
||||||
|
### 原則
|
||||||
|
1. **SQL 檔案集中管理**: 所有 SQL 查詢放在 `src/mes_dashboard/sql/` 目錄下
|
||||||
|
2. **禁止硬編碼 SQL**: 不在 Python 程式碼中直接寫入複雜 SQL
|
||||||
|
3. **使用 QueryBuilder**: 所有動態條件必須使用 `QueryBuilder` 構建
|
||||||
|
|
||||||
|
### SQL 檔案位置規範
|
||||||
|
|
||||||
|
```
|
||||||
|
sql/
|
||||||
|
├── dashboard/ # 儀表板相關 (kpi.sql, heatmap.sql, ...)
|
||||||
|
├── wip/ # WIP 相關 (summary.sql, detail.sql, ...)
|
||||||
|
├── resource/ # 設備相關 (by_status.sql, detail.sql, ...)
|
||||||
|
├── resource_history/ # 歷史相關
|
||||||
|
└── job_query/ # 維修工單相關
|
||||||
|
```
|
||||||
|
|
||||||
|
### SQL 載入方式
|
||||||
|
|
||||||
|
```python
|
||||||
|
from mes_dashboard.sql.loader import SQLLoader
|
||||||
|
|
||||||
|
# 載入 SQL 檔案 (自動 LRU 快取)
|
||||||
|
sql = SQLLoader.load("wip/summary")
|
||||||
|
|
||||||
|
# 結構性參數替換 (用於 SQL 片段)
|
||||||
|
sql = SQLLoader.load_with_params("dashboard/kpi",
|
||||||
|
LATEST_STATUS_SUBQUERY="...")
|
||||||
|
```
|
||||||
|
|
||||||
|
### QueryBuilder 使用規範
|
||||||
|
|
||||||
|
```python
|
||||||
|
from mes_dashboard.sql.builder import QueryBuilder
|
||||||
|
|
||||||
|
builder = QueryBuilder()
|
||||||
|
|
||||||
|
# 添加條件 (自動參數化,防 SQL 注入)
|
||||||
|
builder.add_in_condition("STATUS", ["PRD", "SBY"])
|
||||||
|
builder.add_like_condition("LOTID", user_input, position="both")
|
||||||
|
builder.add_not_in_condition("HOLD_REASON", exclude_list)
|
||||||
|
|
||||||
|
# 構建 WHERE 子句
|
||||||
|
where_clause, params = builder.build_where_only()
|
||||||
|
|
||||||
|
# 替換佔位符並執行
|
||||||
|
sql = sql.replace("{{ WHERE_CLAUSE }}", where_clause)
|
||||||
|
df = read_sql_df(sql, params)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 佔位符規範
|
||||||
|
|
||||||
|
| 類型 | 語法 | 用途 | 安全性 |
|
||||||
|
|------|------|------|--------|
|
||||||
|
| 結構性 | `{{ PLACEHOLDER }}` | 靜態 SQL 片段 | 僅限預定義值 |
|
||||||
|
| 參數 | `:param_name` | 動態用戶輸入 | Bind variables |
|
||||||
|
|
||||||
|
### Oracle SQL 特殊規則
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- CTE 多次使用時加 MATERIALIZE 提示
|
||||||
|
WITH cte AS (/*+ MATERIALIZE */ SELECT ...)
|
||||||
|
|
||||||
|
-- 日期範圍查詢
|
||||||
|
TXNDATE >= :start_date AND TXNDATE < :end_date + 1
|
||||||
|
|
||||||
|
-- IN 子句上限 1000 個值,需分批處理
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 資料庫連線規範
|
||||||
|
|
||||||
|
### 連線池設置 (位置: `core/database.py`)
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 生產環境配置
|
||||||
|
QueuePool(
|
||||||
|
pool_size=10, # 基礎連線數
|
||||||
|
max_overflow=20, # 額外連線數 (總計 30)
|
||||||
|
pool_timeout=30, # 等待超時 30 秒
|
||||||
|
pool_recycle=1800, # 30 分鐘回收
|
||||||
|
pool_pre_ping=True, # 使用前驗證
|
||||||
|
)
|
||||||
|
|
||||||
|
# 開發環境配置
|
||||||
|
pool_size=2, max_overflow=3
|
||||||
|
```
|
||||||
|
|
||||||
|
### 查詢執行規範
|
||||||
|
|
||||||
|
```python
|
||||||
|
from mes_dashboard.core.database import read_sql_df
|
||||||
|
|
||||||
|
# 標準查詢執行
|
||||||
|
df = read_sql_df(sql, params)
|
||||||
|
|
||||||
|
# 自動功能:
|
||||||
|
# - Circuit Breaker 檢查
|
||||||
|
# - 慢查詢警告 (>1 秒)
|
||||||
|
# - 指標記錄
|
||||||
|
# - 連線自動歸還
|
||||||
|
```
|
||||||
|
|
||||||
|
### 禁止事項
|
||||||
|
|
||||||
|
- ❌ 直接使用 `oracledb.connect()` 建立連線
|
||||||
|
- ❌ 在 Service 層手動管理連線
|
||||||
|
- ❌ 忘記使用 `with` 語句或 `g.db`
|
||||||
|
- ❌ 在 SQL 字串中拼接用戶輸入
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 資料表欄位規則
|
||||||
|
|
||||||
|
### 關鍵欄位映射
|
||||||
|
|
||||||
|
| 表名 | 正確欄位 | 錯誤欄位 |
|
||||||
|
|------|---------|---------|
|
||||||
|
| DW_MES_RESOURCE | `PJ_ASSETSSTATUS` | ~~ASSETSTATUS~~ |
|
||||||
|
| DW_MES_RESOURCE | `LOCATIONNAME` | ~~LOCATION~~ |
|
||||||
|
| DW_MES_RESOURCESTATUS_SHIFT | `HISTORYID` (映射到 RESOURCEID) | |
|
||||||
|
| DW_PJ_LOT_V | `WORKCENTER_GROUP` 映射來源 | |
|
||||||
|
|
||||||
|
### 標準篩選條件
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 位置排除 (config/constants.py)
|
||||||
|
EXCLUDED_LOCATIONS = ['ATEC', 'F區', '報廢', '實驗室', ...]
|
||||||
|
|
||||||
|
# 資產狀態排除
|
||||||
|
EXCLUDED_ASSET_STATUSES = ['Disapproved']
|
||||||
|
|
||||||
|
# 設備類型篩選
|
||||||
|
EQUIPMENT_TYPE_FILTER = """
|
||||||
|
((OBJECTCATEGORY = 'ASSEMBLY' AND OBJECTTYPE = 'ASSEMBLY')
|
||||||
|
OR (OBJECTCATEGORY = 'WAFERSORT' AND OBJECTTYPE = 'WAFERSORT'))
|
||||||
|
"""
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## API 設計規範
|
||||||
|
|
||||||
|
### Blueprint 結構
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 路由檔案位置: routes/<module>_routes.py
|
||||||
|
# URL 前綴: /api/<module>/
|
||||||
|
|
||||||
|
wip_bp = Blueprint('wip', __name__, url_prefix='/api/wip')
|
||||||
|
resource_bp = Blueprint('resource', __name__, url_prefix='/api/resource')
|
||||||
|
```
|
||||||
|
|
||||||
|
### 統一響應格式 (位置: `core/response.py`)
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 成功響應
|
||||||
|
{
|
||||||
|
"success": True,
|
||||||
|
"data": {...},
|
||||||
|
"meta": {"timestamp": "ISO8601"}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 錯誤響應
|
||||||
|
{
|
||||||
|
"success": False,
|
||||||
|
"error": {
|
||||||
|
"code": "DB_CONNECTION_FAILED",
|
||||||
|
"message": "資料庫連線失敗,請稍後再試",
|
||||||
|
"details": "ORA-12541" # 僅開發模式
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 錯誤代碼規範
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 使用預定義的錯誤函數
|
||||||
|
from mes_dashboard.core.response import (
|
||||||
|
success_response,
|
||||||
|
validation_error, # 400
|
||||||
|
unauthorized_error, # 401
|
||||||
|
forbidden_error, # 403
|
||||||
|
not_found_error, # 404
|
||||||
|
db_connection_error, # 503
|
||||||
|
internal_error, # 500
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 前端開發規範
|
||||||
|
|
||||||
|
### 範本繼承
|
||||||
|
|
||||||
|
```jinja2
|
||||||
|
{# 頁面模板必須繼承 _base.html #}
|
||||||
|
{% extends "_base.html" %}
|
||||||
|
|
||||||
|
{% block head_extra %}
|
||||||
|
{# 頁面特定的 CSS #}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
{# 頁面內容 #}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{# 頁面特定的 JS #}
|
||||||
|
{% endblock %}
|
||||||
|
```
|
||||||
|
|
||||||
|
### API 調用規範
|
||||||
|
|
||||||
|
使用統一的 API 客戶端 (`static/js/mes-api.js`):
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// GET 請求
|
||||||
|
const data = await MesApi.get('/api/wip/summary', {
|
||||||
|
params: { page: 1 },
|
||||||
|
timeout: 60000,
|
||||||
|
});
|
||||||
|
|
||||||
|
// POST 請求
|
||||||
|
const data = await MesApi.post('/api/query_table', {
|
||||||
|
table_name: 'TABLE_A',
|
||||||
|
filters: {...}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 特性:
|
||||||
|
// - 自動重試 (3 次,指數退避)
|
||||||
|
// - 自動 Toast 通知
|
||||||
|
// - 請求 ID 追蹤
|
||||||
|
// - AbortSignal 支援
|
||||||
|
```
|
||||||
|
|
||||||
|
### Toast 通知規範
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 使用全局 Toast 系統 (NOT MESToast)
|
||||||
|
Toast.info('訊息內容');
|
||||||
|
Toast.success('操作成功');
|
||||||
|
Toast.warning('警告訊息');
|
||||||
|
Toast.error('連線失敗', { retry: () => loadData() });
|
||||||
|
|
||||||
|
const id = Toast.loading('載入中...');
|
||||||
|
Toast.dismiss(id);
|
||||||
|
```
|
||||||
|
|
||||||
|
### JavaScript 注意事項
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// .reverse() 會修改原陣列,使用前先複製
|
||||||
|
const reversed = [...originalArray].reverse();
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 快取策略
|
||||||
|
|
||||||
|
### 多層快取架構
|
||||||
|
|
||||||
|
```
|
||||||
|
請求 → 進程級快取 (30 秒 TTL)
|
||||||
|
→ Redis 快取 (可配置 TTL)
|
||||||
|
→ Oracle 資料庫
|
||||||
|
```
|
||||||
|
|
||||||
|
### 快取 TTL 常量 (位置: `config/constants.py`)
|
||||||
|
|
||||||
|
```python
|
||||||
|
CACHE_TTL_DEFAULT = 60 # 1 分鐘
|
||||||
|
CACHE_TTL_FILTER_OPTIONS = 600 # 10 分鐘
|
||||||
|
CACHE_TTL_KPI = 60 # 1 分鐘
|
||||||
|
```
|
||||||
|
|
||||||
|
### 快取使用方式
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 通用快取
|
||||||
|
from mes_dashboard.core.cache import cache_get, cache_set
|
||||||
|
|
||||||
|
# Filter 快取 (workcenter/family)
|
||||||
|
from mes_dashboard.services.filter_cache import get_workcenters
|
||||||
|
|
||||||
|
# 重要: WORKCENTERNAME → WORKCENTER_GROUP 轉換
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 配置管理規範
|
||||||
|
|
||||||
|
### 環境變數 (.env)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 資料庫 (必需) - 實際值請參考 .env.example
|
||||||
|
DB_HOST=<your_database_host>
|
||||||
|
DB_PORT=1521
|
||||||
|
DB_SERVICE=<your_service_name>
|
||||||
|
DB_USER=<your_username>
|
||||||
|
DB_PASSWORD=<your_password>
|
||||||
|
|
||||||
|
# Flask (必需)
|
||||||
|
FLASK_ENV=production
|
||||||
|
SECRET_KEY=<your_secret_key>
|
||||||
|
|
||||||
|
# 認證 (必需)
|
||||||
|
LDAP_API_URL=<your_ldap_api_url>
|
||||||
|
ADMIN_EMAILS=<admin_email_list>
|
||||||
|
|
||||||
|
# 快取 (建議)
|
||||||
|
REDIS_ENABLED=true
|
||||||
|
REDIS_URL=redis://localhost:6379/0
|
||||||
|
|
||||||
|
# 熔斷器 (生產建議啟用)
|
||||||
|
CIRCUIT_BREAKER_ENABLED=true
|
||||||
|
CIRCUIT_BREAKER_FAILURE_THRESHOLD=5
|
||||||
|
```
|
||||||
|
|
||||||
|
### 環境配置類 (位置: `config/settings.py`)
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 根據 FLASK_ENV 自動選擇配置
|
||||||
|
class DevelopmentConfig(Config):
|
||||||
|
DEBUG = True
|
||||||
|
DB_POOL_SIZE = 2
|
||||||
|
|
||||||
|
class ProductionConfig(Config):
|
||||||
|
DEBUG = False
|
||||||
|
DB_POOL_SIZE = 10
|
||||||
|
```
|
||||||
|
|
||||||
|
### 禁止事項
|
||||||
|
|
||||||
|
- ❌ 硬編碼敏感資訊 (密碼、密鑰)
|
||||||
|
- ❌ 提交 `.env` 檔案到版控
|
||||||
|
- ❌ 在程式碼中直接寫死配置值
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 錯誤處理規範
|
||||||
|
|
||||||
|
### 三層錯誤處理
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 1. 路由層 - 驗證錯誤
|
||||||
|
@bp.route('/api/query')
|
||||||
|
def query():
|
||||||
|
if not request.json.get('table_name'):
|
||||||
|
return validation_error("table_name 為必填")
|
||||||
|
|
||||||
|
# 2. 服務層 - 業務錯誤
|
||||||
|
def get_wip_summary(filters):
|
||||||
|
try:
|
||||||
|
df = query_wip(filters)
|
||||||
|
if df.empty:
|
||||||
|
return None # 返回 None,不拋出異常
|
||||||
|
return process_data(df)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error(f"WIP query failed: {exc}")
|
||||||
|
return None # 優雅降級
|
||||||
|
|
||||||
|
# 3. 核心層 - 基礎設施錯誤
|
||||||
|
def read_sql_df(sql, params):
|
||||||
|
if not circuit_breaker.allow_request():
|
||||||
|
raise RuntimeError("Circuit breaker open")
|
||||||
|
# ... 執行查詢
|
||||||
|
```
|
||||||
|
|
||||||
|
### 熔斷器狀態
|
||||||
|
|
||||||
|
```
|
||||||
|
CLOSED → 正常運作
|
||||||
|
OPEN → 拒絕請求 (達到失敗閾值)
|
||||||
|
HALF_OPEN → 測試恢復
|
||||||
|
```
|
||||||
|
|
||||||
|
### 日誌記錄規範
|
||||||
|
|
||||||
|
```python
|
||||||
|
import logging
|
||||||
|
logger = logging.getLogger('mes_dashboard')
|
||||||
|
|
||||||
|
# 層級使用:
|
||||||
|
logger.debug("詳細調試資訊")
|
||||||
|
logger.info("一般操作記錄")
|
||||||
|
logger.warning("警告但可繼續")
|
||||||
|
logger.error("錯誤需要關注", exc_info=True) # 包含堆棧
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 頁面狀態管理
|
||||||
|
|
||||||
|
### 頁面狀態 (位置: `data/page_status.json`)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pages": [
|
||||||
|
{"route": "/wip-overview", "name": "WIP 即時概況", "status": "released"},
|
||||||
|
{"route": "/tables", "name": "表格總覽", "status": "dev"}
|
||||||
|
],
|
||||||
|
"api_public": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 狀態定義
|
||||||
|
|
||||||
|
- `released`: 所有用戶可訪問
|
||||||
|
- `dev`: 僅管理員可訪問
|
||||||
|
- `None`: 未註冊,由 Flask 路由控制
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 測試規範
|
||||||
|
|
||||||
|
### 測試目錄結構
|
||||||
|
|
||||||
|
```
|
||||||
|
tests/
|
||||||
|
├── conftest.py # pytest fixtures
|
||||||
|
├── test_*_service.py # 服務層測試 (單元測試)
|
||||||
|
├── test_*_routes.py # API 端點測試 (整合測試)
|
||||||
|
├── e2e/test_*_e2e.py # 端對端測試
|
||||||
|
└── stress/ # 壓力測試
|
||||||
|
```
|
||||||
|
|
||||||
|
### 測試注意事項
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 在 setUp 中重置資料庫引擎
|
||||||
|
from mes_dashboard.core import database as db
|
||||||
|
db._ENGINE = None
|
||||||
|
|
||||||
|
# 並行查詢 mock (ThreadPoolExecutor)
|
||||||
|
# 使用 function-based side_effect,不用 list
|
||||||
|
mock_read_sql.side_effect = lambda sql, params: ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 執行測試
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 單元測試
|
||||||
|
pytest tests/ -v
|
||||||
|
|
||||||
|
# 特定測試
|
||||||
|
pytest tests/test_wip_service.py -v
|
||||||
|
|
||||||
|
# 覆蓋率報告
|
||||||
|
pytest tests/ --cov=mes_dashboard
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 常見反模式
|
||||||
|
|
||||||
|
### ❌ 避免的做法
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 1. SQL 字串拼接 (SQL 注入風險)
|
||||||
|
sql = f"SELECT * FROM TABLE WHERE ID = '{user_input}'"
|
||||||
|
|
||||||
|
# 2. 直接建立資料庫連線
|
||||||
|
conn = oracledb.connect(...)
|
||||||
|
|
||||||
|
# 3. 硬編碼配置
|
||||||
|
DB_HOST = "192.168.1.100" # 不要這樣做!
|
||||||
|
|
||||||
|
# 4. 忽略錯誤
|
||||||
|
try:
|
||||||
|
do_something()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# 5. 在路由中直接執行 SQL
|
||||||
|
@bp.route('/api/data')
|
||||||
|
def get_data():
|
||||||
|
sql = "SELECT * FROM TABLE"
|
||||||
|
df = pd.read_sql(sql, conn) # 錯誤!
|
||||||
|
```
|
||||||
|
|
||||||
|
### ✅ 正確做法
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 1. 使用 QueryBuilder
|
||||||
|
builder = QueryBuilder()
|
||||||
|
builder.add_param_condition("ID", user_input)
|
||||||
|
|
||||||
|
# 2. 使用 read_sql_df()
|
||||||
|
df = read_sql_df(sql, params)
|
||||||
|
|
||||||
|
# 3. 使用環境變數
|
||||||
|
DB_HOST = os.getenv('DB_HOST')
|
||||||
|
|
||||||
|
# 4. 記錄錯誤
|
||||||
|
try:
|
||||||
|
do_something()
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error(f"Failed: {exc}")
|
||||||
|
return error_response(...)
|
||||||
|
|
||||||
|
# 5. 分層架構
|
||||||
|
# routes → services → core/database
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 新功能開發檢查清單
|
||||||
|
|
||||||
|
- [ ] SQL 查詢放在 `sql/` 目錄
|
||||||
|
- [ ] 使用 `QueryBuilder` 構建動態條件
|
||||||
|
- [ ] 使用 `read_sql_df()` 執行查詢
|
||||||
|
- [ ] API 響應使用標準格式
|
||||||
|
- [ ] 錯誤處理有日誌記錄
|
||||||
|
- [ ] 敏感配置使用環境變數
|
||||||
|
- [ ] 有對應的單元測試
|
||||||
|
- [ ] 頁面註冊到 `page_status.json`
|
||||||
|
- [ ] 更新 `docs/architecture_findings.md` (如有架構變更)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 參考檔案
|
||||||
|
|
||||||
|
| 功能 | 檔案位置 |
|
||||||
|
|------|---------|
|
||||||
|
| SQL 載入 | `src/mes_dashboard/sql/loader.py` |
|
||||||
|
| 查詢構建 | `src/mes_dashboard/sql/builder.py` |
|
||||||
|
| 資料庫操作 | `src/mes_dashboard/core/database.py` |
|
||||||
|
| API 響應 | `src/mes_dashboard/core/response.py` |
|
||||||
|
| 熔斷器 | `src/mes_dashboard/core/circuit_breaker.py` |
|
||||||
|
| 配置類 | `src/mes_dashboard/config/settings.py` |
|
||||||
|
| 常量定義 | `src/mes_dashboard/config/constants.py` |
|
||||||
|
| 頁面狀態 | `src/mes_dashboard/services/page_registry.py` |
|
||||||
|
| API 客戶端 | `src/mes_dashboard/static/js/mes-api.js` |
|
||||||
|
| Toast 系統 | `src/mes_dashboard/static/js/toast.js` |
|
||||||
|
| 架構文檔 | `docs/architecture_findings.md` |
|
||||||
58
claude.md
58
claude.md
@@ -1,58 +0,0 @@
|
|||||||
# MES Dashboard - Claude Code Instructions
|
|
||||||
|
|
||||||
## Project Overview
|
|
||||||
MES Dashboard 是一個工廠製造執行系統的儀表板應用,使用 Flask + Oracle 資料庫 + ECharts 前端圖表。
|
|
||||||
|
|
||||||
## Before Starting Any Task
|
|
||||||
1. **Review architecture documentation**: Read `docs/architecture_findings.md` to understand:
|
|
||||||
- Database connection management patterns
|
|
||||||
- Caching mechanisms and TTL constants
|
|
||||||
- Filter cache (workcenter/family) usage
|
|
||||||
- Frontend global components (Toast, MesApi)
|
|
||||||
- Data table filtering rules and column mappings
|
|
||||||
- E10 status definitions and OU% calculation
|
|
||||||
- Testing conventions
|
|
||||||
|
|
||||||
## When Making Changes
|
|
||||||
If any of the following patterns are modified or new patterns are discovered:
|
|
||||||
- Database connection or pooling approach
|
|
||||||
- Caching strategy or TTL values
|
|
||||||
- Global frontend components usage
|
|
||||||
- Data table column names or filtering rules
|
|
||||||
- New shared utilities or services
|
|
||||||
- Testing conventions or setup patterns
|
|
||||||
|
|
||||||
**Update `docs/architecture_findings.md`** to reflect the changes.
|
|
||||||
|
|
||||||
## Key Architecture Rules
|
|
||||||
|
|
||||||
### Database
|
|
||||||
- Always use `mes_dashboard.core.database.read_sql_df()` for queries
|
|
||||||
- Never create direct connections in services
|
|
||||||
- Reset `db._ENGINE = None` in test setUp
|
|
||||||
|
|
||||||
### Caching
|
|
||||||
- Use `mes_dashboard.core.cache` for all caching operations
|
|
||||||
- Use `mes_dashboard.services.filter_cache` for workcenter/family lookups
|
|
||||||
- Always convert WORKCENTERNAME → WORKCENTER_GROUP for display
|
|
||||||
|
|
||||||
### Frontend
|
|
||||||
- Toast notifications: Use `Toast.warning()`, `Toast.error()`, `Toast.success()` (NOT MESToast)
|
|
||||||
- API calls: Use `MesApi.get()` with proper timeout
|
|
||||||
- Array operations: Remember `.reverse()` modifies in-place
|
|
||||||
|
|
||||||
### Data Tables
|
|
||||||
- DW_MES_RESOURCE: Use `PJ_ASSETSSTATUS` (not ASSETSTATUS), `LOCATIONNAME` (not LOCATION)
|
|
||||||
- DW_MES_RESOURCESTATUS_SHIFT: HISTORYID maps to RESOURCEID
|
|
||||||
- DW_PJ_LOT_V: Source for WORKCENTER_GROUP mapping
|
|
||||||
|
|
||||||
### SQL
|
|
||||||
- Use `/*+ MATERIALIZE */` hint for Oracle CTEs used multiple times
|
|
||||||
- Date range: `TXNDATE >= start AND TXNDATE < end + 1`
|
|
||||||
- Apply EQUIPMENT_TYPE_FILTER, location exclusions, asset status exclusions
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
- Unit tests: `tests/test_*_service.py`
|
|
||||||
- Integration tests: `tests/test_*_routes.py`
|
|
||||||
- E2E tests: `tests/e2e/test_*_e2e.py`
|
|
||||||
- For parallel queries (ThreadPoolExecutor), mock with function-based side_effect, not list
|
|
||||||
@@ -12,21 +12,129 @@
|
|||||||
```python
|
```python
|
||||||
from mes_dashboard.core.database import read_sql_df, get_engine
|
from mes_dashboard.core.database import read_sql_df, get_engine
|
||||||
|
|
||||||
# 讀取資料
|
# 讀取資料 (推薦方式)
|
||||||
df = read_sql_df(sql)
|
df = read_sql_df(sql, params)
|
||||||
|
|
||||||
# 取得 engine(若需要直接操作)
|
# 取得 engine(若需要直接操作)
|
||||||
engine = get_engine()
|
engine = get_engine()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 連線池配置 (位置: `core/database.py`)
|
||||||
|
|
||||||
|
| 參數 | 開發環境 | 生產環境 | 說明 |
|
||||||
|
|------|---------|---------|------|
|
||||||
|
| pool_size | 2 | 10 | 基礎連線數 |
|
||||||
|
| max_overflow | 3 | 20 | 額外連線數 |
|
||||||
|
| pool_timeout | 30 | 30 | 等待超時 (秒) |
|
||||||
|
| pool_recycle | 1800 | 1800 | 回收週期 (30分鐘) |
|
||||||
|
| pool_pre_ping | True | True | 使用前驗證連線 |
|
||||||
|
|
||||||
|
### Keep-Alive 機制
|
||||||
|
- 背景執行緒每 5 分鐘執行 `SELECT 1 FROM DUAL`
|
||||||
|
- 防止 NAT/防火牆斷開閒置連線
|
||||||
|
- 啟動: `start_keepalive()`,停止: `stop_keepalive()`
|
||||||
|
|
||||||
### 注意事項
|
### 注意事項
|
||||||
- **禁止**在各 service 中自行建立連線
|
- **禁止**在各 service 中自行建立連線
|
||||||
|
- **禁止**直接使用 `oracledb.connect()`
|
||||||
- 連線池由 `database.py` 統一管理,避免連線洩漏
|
- 連線池由 `database.py` 統一管理,避免連線洩漏
|
||||||
- 測試環境需在 setUp 中重置:`db._ENGINE = None`
|
- 測試環境需在 setUp 中重置:`db._ENGINE = None`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 2. 快取機制
|
## 2. SQL 集中管理
|
||||||
|
|
||||||
|
### 目錄結構
|
||||||
|
所有 SQL 查詢放在 `src/mes_dashboard/sql/` 目錄:
|
||||||
|
|
||||||
|
```
|
||||||
|
sql/
|
||||||
|
├── loader.py # SQL 檔案載入器 (LRU 快取)
|
||||||
|
├── builder.py # 參數化查詢構建器
|
||||||
|
├── filters.py # 通用篩選條件
|
||||||
|
├── dashboard/ # 儀表板 SQL
|
||||||
|
│ ├── kpi.sql
|
||||||
|
│ ├── heatmap.sql
|
||||||
|
│ └── workcenter_cards.sql
|
||||||
|
├── wip/ # WIP SQL
|
||||||
|
│ ├── summary.sql
|
||||||
|
│ └── detail.sql
|
||||||
|
├── resource/ # 設備 SQL
|
||||||
|
│ ├── by_status.sql
|
||||||
|
│ └── detail.sql
|
||||||
|
├── resource_history/ # 歷史 SQL
|
||||||
|
└── job_query/ # 維修工單 SQL
|
||||||
|
```
|
||||||
|
|
||||||
|
### SQLLoader 使用方式
|
||||||
|
|
||||||
|
```python
|
||||||
|
from mes_dashboard.sql.loader import SQLLoader
|
||||||
|
|
||||||
|
# 載入 SQL 檔案 (自動 LRU 快取,最多 100 個)
|
||||||
|
sql = SQLLoader.load("wip/summary")
|
||||||
|
|
||||||
|
# 結構性參數替換 (用於 SQL 片段)
|
||||||
|
sql = SQLLoader.load_with_params("dashboard/kpi",
|
||||||
|
LATEST_STATUS_SUBQUERY="...",
|
||||||
|
WHERE_CLAUSE="...")
|
||||||
|
|
||||||
|
# 清除快取
|
||||||
|
SQLLoader.clear_cache()
|
||||||
|
```
|
||||||
|
|
||||||
|
### QueryBuilder 使用方式
|
||||||
|
|
||||||
|
```python
|
||||||
|
from mes_dashboard.sql.builder import QueryBuilder
|
||||||
|
|
||||||
|
builder = QueryBuilder()
|
||||||
|
|
||||||
|
# 添加條件 (自動參數化,防 SQL 注入)
|
||||||
|
builder.add_param_condition("STATUS", "PRD")
|
||||||
|
builder.add_in_condition("STATUS", ["PRD", "SBY"])
|
||||||
|
builder.add_not_in_condition("HOLD_REASON", exclude_list)
|
||||||
|
builder.add_like_condition("LOTID", user_input, position="both")
|
||||||
|
builder.add_or_like_conditions(["COL1", "COL2"], [val1, val2])
|
||||||
|
builder.add_is_null("COLUMN")
|
||||||
|
builder.add_is_not_null("COLUMN")
|
||||||
|
builder.add_condition("FIXED_CONDITION = 1") # 固定條件
|
||||||
|
|
||||||
|
# 構建 WHERE 子句
|
||||||
|
where_clause, params = builder.build_where_only()
|
||||||
|
|
||||||
|
# 替換佔位符並執行
|
||||||
|
sql = sql.replace("{{ WHERE_CLAUSE }}", where_clause)
|
||||||
|
df = read_sql_df(sql, params)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 佔位符規範
|
||||||
|
|
||||||
|
| 類型 | 語法 | 用途 | 安全性 |
|
||||||
|
|------|------|------|--------|
|
||||||
|
| 結構性 | `{{ PLACEHOLDER }}` | 靜態 SQL 片段 | 僅限預定義值 |
|
||||||
|
| 參數 | `:param_name` | 動態用戶輸入 | Oracle bind variables |
|
||||||
|
|
||||||
|
### Oracle IN 子句限制
|
||||||
|
Oracle IN 子句上限 1000 個值,需分批處理:
|
||||||
|
|
||||||
|
```python
|
||||||
|
BATCH_SIZE = 1000
|
||||||
|
|
||||||
|
# 參考 job_query_service.py 的 _build_resource_filter()
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 快取機制
|
||||||
|
|
||||||
|
### 多層快取架構
|
||||||
|
|
||||||
|
```
|
||||||
|
請求 → 進程級快取 (30 秒 TTL)
|
||||||
|
→ Redis 快取 (可配置 TTL)
|
||||||
|
→ Oracle 資料庫
|
||||||
|
```
|
||||||
|
|
||||||
### 全局快取 API
|
### 全局快取 API
|
||||||
使用 `mes_dashboard.core.cache` 模組:
|
使用 `mes_dashboard.core.cache` 模組:
|
||||||
@@ -49,13 +157,35 @@ if result is None:
|
|||||||
|
|
||||||
### 快取 TTL 常數
|
### 快取 TTL 常數
|
||||||
定義於 `mes_dashboard.config.constants`:
|
定義於 `mes_dashboard.config.constants`:
|
||||||
- `CACHE_TTL_FILTER_OPTIONS`: 篩選選項(較長)
|
|
||||||
- `CACHE_TTL_TREND`: 趨勢資料(中等)
|
```python
|
||||||
- `CACHE_TTL_REALTIME`: 即時資料(較短)
|
CACHE_TTL_DEFAULT = 60 # 1 分鐘
|
||||||
|
CACHE_TTL_FILTER_OPTIONS = 600 # 10 分鐘
|
||||||
|
CACHE_TTL_PIVOT_COLUMNS = 300 # 5 分鐘
|
||||||
|
CACHE_TTL_KPI = 60 # 1 分鐘
|
||||||
|
CACHE_TTL_TREND = 300 # 5 分鐘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Redis 快取配置
|
||||||
|
環境變數:
|
||||||
|
```
|
||||||
|
REDIS_ENABLED=true
|
||||||
|
REDIS_URL=redis://localhost:6379/0
|
||||||
|
REDIS_KEY_PREFIX=mes_wip
|
||||||
|
```
|
||||||
|
|
||||||
|
### 專用快取服務
|
||||||
|
|
||||||
|
| 服務 | 位置 | 用途 |
|
||||||
|
|------|------|------|
|
||||||
|
| WIP 快取更新器 | `core/cache_updater.py` | 背景線程自動更新 WIP 數據 |
|
||||||
|
| 資源快取 | `services/resource_cache.py` | DW_MES_RESOURCE 表快取 (4 小時同步) |
|
||||||
|
| 設備狀態快取 | `services/realtime_equipment_cache.py` | 設備實時狀態 (5 分鐘同步) |
|
||||||
|
| Filter 快取 | `services/filter_cache.py` | 篩選選項快取 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. Filter Cache(篩選選項快取)
|
## 4. Filter Cache(篩選選項快取)
|
||||||
|
|
||||||
### 位置
|
### 位置
|
||||||
`mes_dashboard.services.filter_cache`
|
`mes_dashboard.services.filter_cache`
|
||||||
@@ -86,7 +216,306 @@ WORKCENTERNAME (資料庫) → WORKCENTER_GROUP (顯示)
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. 前端全局組件
|
## 5. 熔斷器 (Circuit Breaker)
|
||||||
|
|
||||||
|
### 位置
|
||||||
|
`mes_dashboard.core.circuit_breaker`
|
||||||
|
|
||||||
|
### 狀態機制
|
||||||
|
|
||||||
|
```
|
||||||
|
CLOSED (正常)
|
||||||
|
↓ 失敗達到閾值
|
||||||
|
OPEN (故障,拒絕請求)
|
||||||
|
↓ 等待 recovery_timeout
|
||||||
|
HALF_OPEN (測試恢復)
|
||||||
|
↓ 成功 → CLOSED / 失敗 → OPEN
|
||||||
|
```
|
||||||
|
|
||||||
|
### 配置 (環境變數)
|
||||||
|
|
||||||
|
```
|
||||||
|
CIRCUIT_BREAKER_ENABLED=true
|
||||||
|
CIRCUIT_BREAKER_FAILURE_THRESHOLD=5 # 最少失敗次數
|
||||||
|
CIRCUIT_BREAKER_FAILURE_RATE=0.5 # 失敗率閾值 (0.0-1.0)
|
||||||
|
CIRCUIT_BREAKER_RECOVERY_TIMEOUT=30 # OPEN 狀態等待秒數
|
||||||
|
CIRCUIT_BREAKER_WINDOW_SIZE=10 # 滑動窗口大小
|
||||||
|
```
|
||||||
|
|
||||||
|
### 使用方式
|
||||||
|
熔斷器已整合在 `read_sql_df()` 中,自動:
|
||||||
|
- 檢查是否允許請求
|
||||||
|
- 記錄成功/失敗
|
||||||
|
- 狀態轉移
|
||||||
|
|
||||||
|
### 狀態查詢
|
||||||
|
```python
|
||||||
|
from mes_dashboard.core.circuit_breaker import get_database_circuit_breaker
|
||||||
|
|
||||||
|
cb = get_database_circuit_breaker()
|
||||||
|
status = cb.get_status()
|
||||||
|
# status.state, status.failure_count, status.success_count, status.failure_rate
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 統一 API 響應格式
|
||||||
|
|
||||||
|
### 位置
|
||||||
|
`mes_dashboard.core.response`
|
||||||
|
|
||||||
|
### 響應格式
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 成功響應
|
||||||
|
{
|
||||||
|
"success": True,
|
||||||
|
"data": {...},
|
||||||
|
"meta": {"timestamp": "2024-02-04T10:30:45.123456"}
|
||||||
|
}
|
||||||
|
|
||||||
|
# 錯誤響應
|
||||||
|
{
|
||||||
|
"success": False,
|
||||||
|
"error": {
|
||||||
|
"code": "DB_CONNECTION_FAILED",
|
||||||
|
"message": "資料庫連線失敗,請稍後再試",
|
||||||
|
"details": "ORA-12541" # 僅開發模式
|
||||||
|
},
|
||||||
|
"meta": {"timestamp": "..."}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 錯誤代碼
|
||||||
|
|
||||||
|
| 代碼 | HTTP | 說明 |
|
||||||
|
|------|------|------|
|
||||||
|
| DB_CONNECTION_FAILED | 503 | 資料庫連線失敗 |
|
||||||
|
| DB_QUERY_TIMEOUT | 504 | 查詢逾時 |
|
||||||
|
| DB_QUERY_ERROR | 500 | 查詢執行錯誤 |
|
||||||
|
| SERVICE_UNAVAILABLE | 503 | 服務不可用 |
|
||||||
|
| CIRCUIT_BREAKER_OPEN | 503 | 熔斷器開啟 |
|
||||||
|
| VALIDATION_ERROR | 400 | 驗證失敗 |
|
||||||
|
| UNAUTHORIZED | 401 | 未授權 |
|
||||||
|
| FORBIDDEN | 403 | 禁止訪問 |
|
||||||
|
| NOT_FOUND | 404 | 不存在 |
|
||||||
|
| TOO_MANY_REQUESTS | 429 | 過多請求 |
|
||||||
|
| INTERNAL_ERROR | 500 | 內部錯誤 |
|
||||||
|
|
||||||
|
### 便利函數
|
||||||
|
|
||||||
|
```python
|
||||||
|
from mes_dashboard.core.response import (
|
||||||
|
success_response,
|
||||||
|
validation_error, # 400
|
||||||
|
unauthorized_error, # 401
|
||||||
|
forbidden_error, # 403
|
||||||
|
not_found_error, # 404
|
||||||
|
db_connection_error, # 503
|
||||||
|
internal_error, # 500
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 認證與授權機制
|
||||||
|
|
||||||
|
### 認證服務
|
||||||
|
位置: `mes_dashboard.services.auth_service`
|
||||||
|
|
||||||
|
#### LDAP 認證 (生產環境)
|
||||||
|
```python
|
||||||
|
from mes_dashboard.services.auth_service import authenticate
|
||||||
|
|
||||||
|
user = authenticate(username, password)
|
||||||
|
# 返回: {username, displayName, mail, department}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 本地認證 (開發環境)
|
||||||
|
```
|
||||||
|
LOCAL_AUTH_ENABLED=true
|
||||||
|
LOCAL_AUTH_USERNAME=admin
|
||||||
|
LOCAL_AUTH_PASSWORD=password
|
||||||
|
```
|
||||||
|
|
||||||
|
### Session 管理
|
||||||
|
```python
|
||||||
|
# 登入後存入 session
|
||||||
|
session["admin"] = {
|
||||||
|
"username": user.get("username"),
|
||||||
|
"displayName": user.get("displayName"),
|
||||||
|
"mail": user.get("mail"),
|
||||||
|
"department": user.get("department"),
|
||||||
|
"login_time": datetime.now().isoformat()
|
||||||
|
}
|
||||||
|
|
||||||
|
# Session 配置
|
||||||
|
SESSION_COOKIE_SECURE = True # HTTPS only (生產)
|
||||||
|
SESSION_COOKIE_HTTPONLY = True # 防止 JS 訪問
|
||||||
|
SESSION_COOKIE_SAMESITE = 'Lax' # CSRF 防護
|
||||||
|
PERMANENT_SESSION_LIFETIME = 28800 # 8 小時
|
||||||
|
```
|
||||||
|
|
||||||
|
### 權限檢查
|
||||||
|
位置: `mes_dashboard.core.permissions`
|
||||||
|
|
||||||
|
```python
|
||||||
|
from mes_dashboard.core.permissions import is_admin_logged_in, admin_required
|
||||||
|
|
||||||
|
# 檢查登入狀態
|
||||||
|
if is_admin_logged_in():
|
||||||
|
...
|
||||||
|
|
||||||
|
# 裝飾器保護路由
|
||||||
|
@admin_required
|
||||||
|
def admin_only_view():
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
### 登入速率限制
|
||||||
|
- 單 IP 每 5 分鐘最多 5 次嘗試
|
||||||
|
- 位置: `routes/auth_routes.py`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 頁面狀態管理
|
||||||
|
|
||||||
|
### 位置
|
||||||
|
- 服務: `mes_dashboard.services.page_registry`
|
||||||
|
- 數據: `data/page_status.json`
|
||||||
|
|
||||||
|
### 狀態定義
|
||||||
|
|
||||||
|
| 狀態 | 說明 |
|
||||||
|
|------|------|
|
||||||
|
| `released` | 所有用戶可訪問 |
|
||||||
|
| `dev` | 僅管理員可訪問 |
|
||||||
|
| `None` | 未註冊,由 Flask 路由控制 |
|
||||||
|
|
||||||
|
### 數據格式
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"pages": [
|
||||||
|
{"route": "/wip-overview", "name": "WIP 即時概況", "status": "released"},
|
||||||
|
{"route": "/tables", "name": "表格總覽", "status": "dev"}
|
||||||
|
],
|
||||||
|
"api_public": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### API
|
||||||
|
|
||||||
|
```python
|
||||||
|
from mes_dashboard.services.page_registry import (
|
||||||
|
get_page_status, # 取得頁面狀態
|
||||||
|
set_page_status, # 設定頁面狀態
|
||||||
|
is_api_public, # API 是否公開
|
||||||
|
get_all_pages, # 取得所有頁面
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 權限檢查 (自動)
|
||||||
|
在 `app.py` 的 `@app.before_request` 中自動執行:
|
||||||
|
- dev 頁面 + 非管理員 → 403
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 日誌系統
|
||||||
|
|
||||||
|
### 雙層日誌架構
|
||||||
|
|
||||||
|
| 層級 | 目標 | 用途 |
|
||||||
|
|------|------|------|
|
||||||
|
| 控制台 (stderr) | Gunicorn 捕獲 | 即時監控 |
|
||||||
|
| SQLite | 管理員儀表板 | 歷史查詢 |
|
||||||
|
|
||||||
|
### 配置 (環境變數)
|
||||||
|
```
|
||||||
|
LOG_STORE_ENABLED=true
|
||||||
|
LOG_SQLITE_PATH=logs/admin_logs.sqlite
|
||||||
|
LOG_SQLITE_RETENTION_DAYS=7
|
||||||
|
LOG_SQLITE_MAX_ROWS=100000
|
||||||
|
```
|
||||||
|
|
||||||
|
### 日誌記錄規範
|
||||||
|
|
||||||
|
```python
|
||||||
|
import logging
|
||||||
|
logger = logging.getLogger('mes_dashboard')
|
||||||
|
|
||||||
|
logger.debug("詳細調試資訊")
|
||||||
|
logger.info("一般操作記錄")
|
||||||
|
logger.warning("警告但可繼續")
|
||||||
|
logger.error("錯誤需要關注", exc_info=True) # 包含堆棧
|
||||||
|
```
|
||||||
|
|
||||||
|
### SQLite 日誌查詢
|
||||||
|
位置: `mes_dashboard.core.log_store`
|
||||||
|
|
||||||
|
```python
|
||||||
|
from mes_dashboard.core.log_store import get_log_store
|
||||||
|
|
||||||
|
store = get_log_store()
|
||||||
|
logs = store.query_logs(
|
||||||
|
level="ERROR",
|
||||||
|
limit=100,
|
||||||
|
offset=0,
|
||||||
|
search="keyword"
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 10. 健康檢查
|
||||||
|
|
||||||
|
### 端點
|
||||||
|
|
||||||
|
| 端點 | 認證 | 說明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `/health` | 無需 | 基本健康檢查 |
|
||||||
|
| `/health/deep` | 需管理員 | 詳細指標 |
|
||||||
|
|
||||||
|
### 基本檢查項目
|
||||||
|
- 資料庫連線 (`SELECT 1 FROM DUAL`)
|
||||||
|
- Redis 連線 (`PING`)
|
||||||
|
- 各快取狀態
|
||||||
|
|
||||||
|
### 詳細檢查項目 (deep)
|
||||||
|
- 資料庫延遲 (毫秒)
|
||||||
|
- 連線池狀態 (size, checked_out, overflow)
|
||||||
|
- 快取新鮮度
|
||||||
|
- 熔斷器狀態
|
||||||
|
- 查詢性能指標 (P50/P95/P99)
|
||||||
|
|
||||||
|
### 狀態判定
|
||||||
|
- `200 OK` (healthy/degraded): DB 正常
|
||||||
|
- `503 Unavailable` (unhealthy): DB 故障
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. API 路由結構 (Blueprint)
|
||||||
|
|
||||||
|
### Blueprint 列表
|
||||||
|
|
||||||
|
| Blueprint | URL 前綴 | 檔案 |
|
||||||
|
|-----------|---------|------|
|
||||||
|
| wip | `/api/wip` | `wip_routes.py` |
|
||||||
|
| resource | `/api/resource` | `resource_routes.py` |
|
||||||
|
| dashboard | `/api/dashboard` | `dashboard_routes.py` |
|
||||||
|
| excel_query | `/api/excel-query` | `excel_query_routes.py` |
|
||||||
|
| hold | `/api/hold` | `hold_routes.py` |
|
||||||
|
| resource_history | `/api/resource-history` | `resource_history_routes.py` |
|
||||||
|
| job_query | `/api/job-query` | `job_query_routes.py` |
|
||||||
|
| admin | `/admin` | `admin_routes.py` |
|
||||||
|
| auth | `/admin` | `auth_routes.py` |
|
||||||
|
| health | `/` | `health_routes.py` |
|
||||||
|
|
||||||
|
### 路由註冊
|
||||||
|
位置: `routes/__init__.py` 的 `register_routes(app)`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 12. 前端全局組件
|
||||||
|
|
||||||
### Toast 通知
|
### Toast 通知
|
||||||
定義於 `static/js/toast.js`,透過 `_base.html` 載入:
|
定義於 `static/js/toast.js`,透過 `_base.html` 載入:
|
||||||
@@ -96,59 +525,90 @@ WORKCENTERNAME (資料庫) → WORKCENTER_GROUP (顯示)
|
|||||||
Toast.info('訊息');
|
Toast.info('訊息');
|
||||||
Toast.success('成功');
|
Toast.success('成功');
|
||||||
Toast.warning('警告');
|
Toast.warning('警告');
|
||||||
Toast.error('錯誤');
|
Toast.error('錯誤', { retry: () => loadData() });
|
||||||
Toast.loading('載入中...');
|
|
||||||
|
const id = Toast.loading('載入中...');
|
||||||
|
Toast.update(id, { message: '完成!' });
|
||||||
|
Toast.dismiss(id);
|
||||||
|
|
||||||
// 錯誤用法(不存在)
|
// 錯誤用法(不存在)
|
||||||
MESToast.warning('...'); // ❌ 錯誤
|
MESToast.warning('...'); // ❌ 錯誤
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 自動消失時間
|
||||||
|
- info: 3000ms
|
||||||
|
- success: 2000ms
|
||||||
|
- warning: 5000ms
|
||||||
|
- error: 永久(需手動關閉)
|
||||||
|
- loading: 永久
|
||||||
|
|
||||||
### MesApi(HTTP 請求)
|
### MesApi(HTTP 請求)
|
||||||
定義於 `static/js/api.js`,提供統一的 API 呼叫介面:
|
定義於 `static/js/mes-api.js`:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const result = await MesApi.get('/api/endpoint', { timeout: 30000 });
|
// GET 請求
|
||||||
if (result.success) {
|
const data = await MesApi.get('/api/wip/summary', {
|
||||||
// 處理資料
|
params: { page: 1 },
|
||||||
} else {
|
timeout: 60000,
|
||||||
Toast.error(result.error);
|
retries: 5,
|
||||||
}
|
signal: abortController.signal,
|
||||||
|
silent: true // 禁用 toast 通知
|
||||||
|
});
|
||||||
|
|
||||||
|
// POST 請求
|
||||||
|
const data = await MesApi.post('/api/query_table', {
|
||||||
|
table_name: 'TABLE_A',
|
||||||
|
filters: {...}
|
||||||
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### MesApi 特性
|
||||||
|
- 自動重試 (3 次,指數退避: 1s, 2s, 4s)
|
||||||
|
- 自動 Toast 通知
|
||||||
|
- 請求 ID 追蹤
|
||||||
|
- AbortSignal 支援
|
||||||
|
- 4xx 不重試,5xx 重試
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 5. 資料表預篩選規則
|
## 13. 資料表預篩選規則
|
||||||
|
|
||||||
### 設備類型篩選
|
### 設備類型篩選
|
||||||
定義於 `mes_dashboard.config.constants.EQUIPMENT_TYPE_FILTER`:
|
定義於 `mes_dashboard.config.constants.EQUIPMENT_TYPE_FILTER`:
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
-- 只查詢特定設備類型
|
((OBJECTCATEGORY = 'ASSEMBLY' AND OBJECTTYPE = 'ASSEMBLY')
|
||||||
r.EQUIPMENTTYPE IN ('主要設備', '輔助設備')
|
OR (OBJECTCATEGORY = 'WAFERSORT' AND OBJECTTYPE = 'WAFERSORT'))
|
||||||
```
|
```
|
||||||
|
|
||||||
### 排除條件
|
### 排除條件
|
||||||
```python
|
```python
|
||||||
# 排除的地點
|
# 排除的地點
|
||||||
EXCLUDED_LOCATIONS = ['TEST', 'LAB', ...]
|
EXCLUDED_LOCATIONS = [
|
||||||
|
'ATEC', 'F區', 'F區焊接站', '報廢', '實驗室',
|
||||||
|
'山東', '成型站_F區', '焊接F區', '無錫', '熒茂'
|
||||||
|
]
|
||||||
|
|
||||||
# 排除的資產狀態
|
# 排除的資產狀態
|
||||||
EXCLUDED_ASSET_STATUSES = ['報廢', '停用', ...]
|
EXCLUDED_ASSET_STATUSES = ['Disapproved']
|
||||||
```
|
```
|
||||||
|
|
||||||
### SQL 範例
|
### CommonFilters 使用
|
||||||
```python
|
位置: `mes_dashboard.sql.filters`
|
||||||
# 建立篩選條件
|
|
||||||
location_filter = _build_location_filter('r')
|
|
||||||
# → AND (r.LOCATIONNAME IS NULL OR r.LOCATIONNAME NOT IN ('TEST', 'LAB'))
|
|
||||||
|
|
||||||
asset_status_filter = _build_asset_status_filter('r')
|
```python
|
||||||
# → AND r.PJ_ASSETSSTATUS NOT IN ('報廢', '停用')
|
from mes_dashboard.sql.filters import CommonFilters
|
||||||
|
|
||||||
|
# 添加標準篩選
|
||||||
|
CommonFilters.add_location_exclusion(builder, 'r')
|
||||||
|
CommonFilters.add_asset_status_exclusion(builder, 'r')
|
||||||
|
CommonFilters.add_wip_base_filters(builder, filters)
|
||||||
|
CommonFilters.add_equipment_filter(builder, filters)
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 6. 資料庫欄位對應
|
## 14. 資料庫欄位對應
|
||||||
|
|
||||||
### DW_MES_RESOURCE
|
### DW_MES_RESOURCE
|
||||||
| 常見錯誤 | 正確欄位名 |
|
| 常見錯誤 | 正確欄位名 |
|
||||||
@@ -176,7 +636,7 @@ asset_status_filter = _build_asset_status_filter('r')
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 7. E10 狀態定義
|
## 15. E10 狀態定義
|
||||||
|
|
||||||
| 狀態 | 說明 | 計入 OU% |
|
| 狀態 | 說明 | 計入 OU% |
|
||||||
|-----|------|---------|
|
|-----|------|---------|
|
||||||
@@ -192,9 +652,103 @@ asset_status_filter = _build_asset_status_filter('r')
|
|||||||
OU% = PRD / (PRD + SBY + UDT + SDT + EGT) × 100
|
OU% = PRD / (PRD + SBY + UDT + SDT + EGT) × 100
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 狀態顯示名稱
|
||||||
|
```python
|
||||||
|
STATUS_DISPLAY_NAMES = {
|
||||||
|
'PRD': '生產中',
|
||||||
|
'SBY': '待機',
|
||||||
|
'UDT': '非計畫停機',
|
||||||
|
'SDT': '計畫停機',
|
||||||
|
'EGT': '工程時間',
|
||||||
|
'NST': '未排單',
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8. 平行查詢
|
## 16. 配置管理
|
||||||
|
|
||||||
|
### 環境變數 (.env)
|
||||||
|
|
||||||
|
#### 資料庫
|
||||||
|
```
|
||||||
|
DB_HOST=<your_database_host>
|
||||||
|
DB_PORT=1521
|
||||||
|
DB_SERVICE=<your_service_name>
|
||||||
|
DB_USER=<your_username>
|
||||||
|
DB_PASSWORD=<your_password>
|
||||||
|
DB_POOL_SIZE=5
|
||||||
|
DB_MAX_OVERFLOW=10
|
||||||
|
```
|
||||||
|
|
||||||
|
> 實際值請參考 `.env` 或 `.env.example`
|
||||||
|
|
||||||
|
#### Flask
|
||||||
|
```
|
||||||
|
FLASK_ENV=production
|
||||||
|
FLASK_DEBUG=0
|
||||||
|
SECRET_KEY=your_secret_key
|
||||||
|
SESSION_LIFETIME=28800
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 認證
|
||||||
|
```
|
||||||
|
LDAP_API_URL=<your_ldap_api_url>
|
||||||
|
ADMIN_EMAILS=<admin_email_list>
|
||||||
|
LOCAL_AUTH_ENABLED=false
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Gunicorn
|
||||||
|
```
|
||||||
|
GUNICORN_BIND=0.0.0.0:8080
|
||||||
|
GUNICORN_WORKERS=4
|
||||||
|
GUNICORN_THREADS=8
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 快取
|
||||||
|
```
|
||||||
|
REDIS_ENABLED=true
|
||||||
|
REDIS_URL=redis://localhost:6379/0
|
||||||
|
CACHE_CHECK_INTERVAL=600
|
||||||
|
RESOURCE_CACHE_ENABLED=true
|
||||||
|
RESOURCE_SYNC_INTERVAL=14400
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 熔斷器
|
||||||
|
```
|
||||||
|
CIRCUIT_BREAKER_ENABLED=true
|
||||||
|
CIRCUIT_BREAKER_FAILURE_THRESHOLD=5
|
||||||
|
CIRCUIT_BREAKER_FAILURE_RATE=0.5
|
||||||
|
CIRCUIT_BREAKER_RECOVERY_TIMEOUT=30
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 日誌
|
||||||
|
```
|
||||||
|
LOG_STORE_ENABLED=true
|
||||||
|
LOG_SQLITE_PATH=logs/admin_logs.sqlite
|
||||||
|
LOG_SQLITE_RETENTION_DAYS=7
|
||||||
|
```
|
||||||
|
|
||||||
|
### 環境配置類
|
||||||
|
位置: `mes_dashboard.config.settings`
|
||||||
|
|
||||||
|
```python
|
||||||
|
class DevelopmentConfig(Config):
|
||||||
|
DEBUG = True
|
||||||
|
DB_POOL_SIZE = 2
|
||||||
|
|
||||||
|
class ProductionConfig(Config):
|
||||||
|
DEBUG = False
|
||||||
|
DB_POOL_SIZE = 10
|
||||||
|
|
||||||
|
class TestingConfig(Config):
|
||||||
|
TESTING = True
|
||||||
|
DB_POOL_SIZE = 1
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 17. 平行查詢
|
||||||
|
|
||||||
### ThreadPoolExecutor
|
### ThreadPoolExecutor
|
||||||
對於多個獨立查詢,使用平行執行提升效能:
|
對於多個獨立查詢,使用平行執行提升效能:
|
||||||
@@ -207,7 +761,6 @@ with ThreadPoolExecutor(max_workers=4) as executor:
|
|||||||
executor.submit(read_sql_df, kpi_sql): 'kpi',
|
executor.submit(read_sql_df, kpi_sql): 'kpi',
|
||||||
executor.submit(read_sql_df, trend_sql): 'trend',
|
executor.submit(read_sql_df, trend_sql): 'trend',
|
||||||
executor.submit(read_sql_df, heatmap_sql): 'heatmap',
|
executor.submit(read_sql_df, heatmap_sql): 'heatmap',
|
||||||
executor.submit(read_sql_df, comparison_sql): 'comparison',
|
|
||||||
}
|
}
|
||||||
for future in as_completed(futures):
|
for future in as_completed(futures):
|
||||||
query_name = futures[future]
|
query_name = futures[future]
|
||||||
@@ -220,7 +773,7 @@ with ThreadPoolExecutor(max_workers=4) as executor:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 9. Oracle SQL 優化
|
## 18. Oracle SQL 優化
|
||||||
|
|
||||||
### CTE MATERIALIZE Hint
|
### CTE MATERIALIZE Hint
|
||||||
防止 Oracle 優化器將 CTE inline 多次執行:
|
防止 Oracle 優化器將 CTE inline 多次執行:
|
||||||
@@ -238,13 +791,17 @@ SELECT ...
|
|||||||
### 日期範圍查詢
|
### 日期範圍查詢
|
||||||
```sql
|
```sql
|
||||||
-- 包含 end_date 當天
|
-- 包含 end_date 當天
|
||||||
WHERE TXNDATE >= TO_DATE('{start_date}', 'YYYY-MM-DD')
|
WHERE TXNDATE >= TO_DATE(:start_date, 'YYYY-MM-DD')
|
||||||
AND TXNDATE < TO_DATE('{end_date}', 'YYYY-MM-DD') + 1
|
AND TXNDATE < TO_DATE(:end_date, 'YYYY-MM-DD') + 1
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 慢查詢警告
|
||||||
|
- 閾值: 1 秒 (警告),5 秒 (`SLOW_QUERY_THRESHOLD`)
|
||||||
|
- 自動記錄到日誌
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 10. 前端資料限制
|
## 19. 前端資料限制
|
||||||
|
|
||||||
### 明細資料上限
|
### 明細資料上限
|
||||||
為避免瀏覽器記憶體問題,明細查詢有筆數限制:
|
為避免瀏覽器記憶體問題,明細查詢有筆數限制:
|
||||||
@@ -266,7 +823,7 @@ if (result.truncated) {
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 11. JavaScript 注意事項
|
## 20. JavaScript 注意事項
|
||||||
|
|
||||||
### Array.reverse() 原地修改
|
### Array.reverse() 原地修改
|
||||||
```javascript
|
```javascript
|
||||||
@@ -282,15 +839,18 @@ const reversed = [...arr].reverse();
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 12. 測試規範
|
## 21. 測試規範
|
||||||
|
|
||||||
### 測試檔案結構
|
### 測試檔案結構
|
||||||
```
|
```
|
||||||
tests/
|
tests/
|
||||||
├── test_*_service.py # 單元測試(service layer)
|
├── conftest.py # pytest fixtures
|
||||||
├── test_*_routes.py # 整合測試(API endpoints)
|
├── test_*_service.py # 單元測試(service layer)
|
||||||
└── e2e/
|
├── test_*_routes.py # 整合測試(API endpoints)
|
||||||
└── test_*_e2e.py # 端對端測試(完整流程)
|
├── e2e/
|
||||||
|
│ └── test_*_e2e.py # 端對端測試(完整流程)
|
||||||
|
└── stress/
|
||||||
|
└── test_*.py # 壓力測試
|
||||||
```
|
```
|
||||||
|
|
||||||
### 測試前重置
|
### 測試前重置
|
||||||
@@ -307,4 +867,70 @@ pytest tests/test_resource_history_service.py -v
|
|||||||
|
|
||||||
# 全部相關測試
|
# 全部相關測試
|
||||||
pytest tests/test_resource_history_*.py tests/e2e/test_resource_history_e2e.py -v
|
pytest tests/test_resource_history_*.py tests/e2e/test_resource_history_e2e.py -v
|
||||||
|
|
||||||
|
# 覆蓋率報告
|
||||||
|
pytest tests/ --cov=mes_dashboard
|
||||||
```
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 22. 錯誤處理模式
|
||||||
|
|
||||||
|
### 三層錯誤處理
|
||||||
|
|
||||||
|
```python
|
||||||
|
# 1. 路由層 - 驗證錯誤
|
||||||
|
@bp.route('/api/query')
|
||||||
|
def query():
|
||||||
|
if not request.json.get('table_name'):
|
||||||
|
return validation_error("table_name 為必填")
|
||||||
|
|
||||||
|
# 2. 服務層 - 業務錯誤 (優雅降級)
|
||||||
|
def get_wip_summary(filters):
|
||||||
|
try:
|
||||||
|
df = query_wip(filters)
|
||||||
|
if df.empty:
|
||||||
|
return None
|
||||||
|
return process_data(df)
|
||||||
|
except Exception as exc:
|
||||||
|
logger.error(f"WIP query failed: {exc}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# 3. 核心層 - 基礎設施錯誤
|
||||||
|
def read_sql_df(sql, params):
|
||||||
|
if not circuit_breaker.allow_request():
|
||||||
|
raise RuntimeError("Circuit breaker open")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 全局錯誤處理
|
||||||
|
位置: `app.py` 的 `_register_error_handlers()`
|
||||||
|
|
||||||
|
- 401 → `unauthorized_error()`
|
||||||
|
- 403 → `forbidden_error()`
|
||||||
|
- 404 → JSON (API) 或 HTML (頁面)
|
||||||
|
- 500 → `internal_error()`
|
||||||
|
- Exception → 通用處理
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 參考檔案索引
|
||||||
|
|
||||||
|
| 功能 | 檔案位置 |
|
||||||
|
|------|---------|
|
||||||
|
| SQL 載入 | `src/mes_dashboard/sql/loader.py` |
|
||||||
|
| 查詢構建 | `src/mes_dashboard/sql/builder.py` |
|
||||||
|
| 通用篩選 | `src/mes_dashboard/sql/filters.py` |
|
||||||
|
| 資料庫操作 | `src/mes_dashboard/core/database.py` |
|
||||||
|
| 快取 | `src/mes_dashboard/core/cache.py` |
|
||||||
|
| 熔斷器 | `src/mes_dashboard/core/circuit_breaker.py` |
|
||||||
|
| API 響應 | `src/mes_dashboard/core/response.py` |
|
||||||
|
| 權限檢查 | `src/mes_dashboard/core/permissions.py` |
|
||||||
|
| 日誌存儲 | `src/mes_dashboard/core/log_store.py` |
|
||||||
|
| 配置類 | `src/mes_dashboard/config/settings.py` |
|
||||||
|
| 常量定義 | `src/mes_dashboard/config/constants.py` |
|
||||||
|
| 認證服務 | `src/mes_dashboard/services/auth_service.py` |
|
||||||
|
| 頁面狀態 | `src/mes_dashboard/services/page_registry.py` |
|
||||||
|
| Filter 快取 | `src/mes_dashboard/services/filter_cache.py` |
|
||||||
|
| 資源快取 | `src/mes_dashboard/services/resource_cache.py` |
|
||||||
|
| API 客戶端 | `src/mes_dashboard/static/js/mes-api.js` |
|
||||||
|
| Toast 系統 | `src/mes_dashboard/static/js/toast.js` |
|
||||||
|
|||||||
Reference in New Issue
Block a user