Files
TODO_list_system/BEST_PRACTICES.md
beabigegg 22a231d78c 5th
2025-09-01 11:17:09 +08:00

1089 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# PANJIT To-Do System - 開發者最佳實踐指南
> **⚠️ 重要提醒**:本文件包含敏感的系統配置和最佳實踐資訊,僅供開發團隊內部使用。
> 此文件已在 .gitignore 中排除,請勿提交至版本控制系統。
## 🎯 文件目的
本文件記錄在開發 PANJIT To-Do System 過程中遇到的技術難點及最佳解決方案包含前後端整合、資料庫設計、LDAP整合、郵件系統等關鍵技術決策避免後續開發者重複踩坑。
---
## 🔐 LDAP/Active Directory 整合最佳實踐
### 1. LDAP 連接配置
**關鍵發現**LDAP 連接的穩定性很大程度取決於正確的配置參數組合。
#### 正確的配置模式
```python
# config.py - 推薦配置
LDAP_SERVER = "ldap://dc.company.com" # 或使用 IP
LDAP_PORT = 389 # 標準 LDAP port
LDAP_USE_SSL = False # 內網環境通常不需要 SSL
LDAP_SEARCH_BASE = "DC=company,DC=com"
LDAP_BIND_USER_DN = "CN=ServiceAccount,OU=ServiceAccounts,DC=company,DC=com"
LDAP_USER_LOGIN_ATTR = "userPrincipalName" # AD 環境必須使用此屬性
```
#### 常見錯誤及解決方案
**錯誤 1**:使用 `sAMAccountName` 作為登入屬性
```python
# ❌ 錯誤做法
LDAP_USER_LOGIN_ATTR = "sAMAccountName"
# ✅ 正確做法
LDAP_USER_LOGIN_ATTR = "userPrincipalName"
```
**錯誤 2**:服務帳號權限不足
```python
# 服務帳號至少需要以下權限:
# - Read permission on the search base
# - List Contents permission
# - Read All Properties permission
```
### 2. LDAP 搜尋最佳化
**關鍵發現**:正確的搜尋篩選器可以大幅提升效能並避免權限問題。
#### 用戶搜尋最佳實踐
```python
# ldap_utils.py - 優化後的搜尋篩選器
def search_ldap_principals(search_term):
# 多屬性搜尋,提高命中率
search_filter = f"""
(&
(objectClass=person)
(objectCategory=person)
(!(userAccountControl:1.2.840.113556.1.4.803:=2)) # 排除已停用帳號
(|
(displayName=*{search_term}*)
(mail=*{search_term}*)
(sAMAccountName=*{search_term}*)
(userPrincipalName=*{search_term}*)
)
)
"""
```
**關鍵技巧**
1. 使用 `objectCategory=person` 而不是只用 `objectClass=user`
2. 排除停用帳號避免無效結果
3. 多屬性搜尋提高使用者體驗
4. 限制搜尋結果數量避免效能問題
#### 群組搜尋最佳實踐
```python
# 同時支援 AD 群組和 OU
def get_ldap_group_members(group_name):
# 先嘗試搜尋 AD 群組
group_filter = f"(&(objectClass=group)(cn={group_name}))"
# 如果找不到群組,嘗試搜尋 OU
if not found:
ou_filter = f"(&(objectClass=organizationalUnit)(name=*{group_name}*))"
```
### 3. LDAP 連接穩定性
**關鍵發現**:連接池和重試機制對生產環境至關重要。
```python
# ldap_utils.py - 連接重試機制
def create_ldap_connection(retries=3):
for attempt in range(retries):
try:
server = Server(ldap_server, port=ldap_port, use_ssl=use_ssl)
conn = Connection(server, user=bind_dn, password=bind_password, auto_bind=True)
return conn
except Exception as e:
if attempt == retries - 1:
raise e
time.sleep(1) # 短暫等待後重試
```
---
## 📧 SMTP 郵件系統最佳實踐
### 1. 多種 SMTP 配置支援
**關鍵發現**:企業環境中可能遇到多種 SMTP 配置需求,系統必須具備彈性。
#### 配置架構設計
```python
# config.py - 彈性 SMTP 配置
class Config:
SMTP_SERVER = os.getenv('SMTP_SERVER', 'mail.company.com')
SMTP_PORT = int(os.getenv('SMTP_PORT', 25))
SMTP_USE_TLS = os.getenv('SMTP_USE_TLS', 'false').lower() in ['true', '1', 't']
SMTP_USE_SSL = os.getenv('SMTP_USE_SSL', 'false').lower() in ['true', '1', 't']
SMTP_AUTH_REQUIRED = os.getenv('SMTP_AUTH_REQUIRED', 'false').lower() in ['true', '1', 't']
SMTP_SENDER_EMAIL = os.getenv('SMTP_SENDER_EMAIL', 'temp-spec-system@company.com')
SMTP_SENDER_PASSWORD = os.getenv('SMTP_SENDER_PASSWORD', '')
```
#### 智能連接邏輯
```python
# utils.py - 智能 SMTP 連接
def send_email(to_addrs, subject, body):
# 根據 port 和配置自動選擇連接方式
if use_ssl and smtp_port == 465:
server = smtplib.SMTP_SSL(smtp_server, smtp_port)
else:
server = smtplib.SMTP(smtp_server, smtp_port)
if use_tls and smtp_port == 587:
server.starttls()
# 只在需要認證時才登入
if auth_required and sender_password:
server.login(sender_email, sender_password)
```
### 2. 郵件發送可靠性
**關鍵發現**:詳細的日誌和錯誤處理對於診斷郵件問題至關重要。
```python
# utils.py - 完整的錯誤處理
def send_email(to_addrs, subject, body):
try:
# ... 發送邏輯 ...
result = server.sendmail(sender_email, to_addrs, msg.as_string())
# 檢查發送結果
if result:
# 某些收件者失敗
print(f"[EMAIL WARNING] 部分收件者發送失敗: {result}")
else:
print(f"[EMAIL SUCCESS] 郵件成功發送至: {', '.join(to_addrs)}")
return True
except smtplib.SMTPAuthenticationError as e:
print(f"[EMAIL ERROR] SMTP 認證失敗: {e}")
return False
except smtplib.SMTPConnectError as e:
print(f"[EMAIL ERROR] SMTP 連接失敗: {e}")
return False
# ... 其他異常處理 ...
```
### 3. 郵件內容最佳化
**關鍵發現**HTML 格式郵件必須考慮各種郵件客戶端的相容性。
```python
# 推薦的 HTML 郵件格式
def create_email_body(spec, action):
body = f"""
<html>
<head>
<meta charset="utf-8">
<style>
body {{ font-family: Arial, sans-serif; }}
.header {{ color: #2c3e50; border-bottom: 2px solid #3498db; }}
.content {{ margin: 20px 0; }}
.highlight {{ background-color: #f8f9fa; padding: 10px; }}
</style>
</head>
<body>
<div class="header">
<h2>[暫規通知] 規範 '{spec.spec_code}' 已{action}</h2>
</div>
<div class="content">
<p>您好,</p>
<!-- 內容... -->
</div>
</body>
</html>
"""
return body
```
---
## 🗄️ 資料庫設計最佳實踐
### 1. 資料庫遷移策略
**關鍵發現**:平滑的資料庫升級對於生產環境至關重要。
#### 遷移腳本模板
```python
# migrate_*.py - 標準遷移腳本結構
def migrate_database():
engine = create_engine(Config.SQLALCHEMY_DATABASE_URI)
try:
with engine.connect() as conn:
# 檢查是否已經遷移
result = conn.execute(text("""
SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = 'table_name' AND COLUMN_NAME = 'new_column'
AND TABLE_SCHEMA = DATABASE()
"""))
if result.fetchone():
print("✓ 遷移已完成,無需重複執行")
return True
# 執行遷移
conn.execute(text("ALTER TABLE table_name ADD COLUMN new_column TYPE"))
conn.commit()
# 驗證遷移結果
# ...
except Exception as e:
print(f"✗ 遷移失敗:{str(e)}")
return False
```
### 2. 資料模型設計
**關鍵發現**:適當的索引和關聯設計可以大幅提升查詢效能。
```python
# models.py - 最佳實踐
class TempSpec(db.Model):
__tablename__ = 'ts_temp_spec'
# 主鍵
id = db.Column(db.Integer, primary_key=True)
# 業務鍵,建立索引
spec_code = db.Column(db.String(20), nullable=False, index=True)
# 常用查詢欄位,建立索引
status = db.Column(db.Enum(...), nullable=False, index=True)
end_date = db.Column(db.Date, index=True) # 用於到期查詢
# 新功能擴展欄位
notification_emails = db.Column(db.Text, nullable=True)
# 正確的關聯設置
uploads = db.relationship('Upload', back_populates='spec',
cascade='all, delete-orphan')
```
---
## 🔄 Flask 應用架構最佳實踐
### 1. 藍圖Blueprint組織
**關鍵發現**:良好的模組分離有助於維護和擴展。
```python
# 推薦的路由組織結構
routes/
├── __init__.py # 藍圖註冊
├── auth.py # 認證相關
├── api.py # API 介面
├── temp_spec.py # 核心業務邏輯
├── admin.py # 管理功能
└── upload.py # 檔案處理
```
### 2. 錯誤處理策略
```python
# app.py - 全局錯誤處理
@app.errorhandler(403)
def forbidden(error):
return render_template('403.html'), 403
@app.errorhandler(404)
def not_found(error):
return render_template('404.html'), 404
@app.errorhandler(500)
def internal_error(error):
db.session.rollback()
return render_template('500.html'), 500
```
### 3. 配置管理
```python
# config.py - 環境配置分離
class DevelopmentConfig(Config):
DEBUG = True
TESTING = False
class ProductionConfig(Config):
DEBUG = False
TESTING = False
# 生產環境特定設定
class TestingConfig(Config):
TESTING = True
# 測試環境設定
```
---
## 🏗️ 前端整合最佳實踐
### 1. ONLYOFFICE 整合要點
**關鍵發現**Docker 環境下的網路配置是最大的挑戰。
```python
# routes/temp_spec.py - URL 修正邏輯
def edit_spec(spec_id):
doc_url = get_file_uri(doc_filename)
callback_url = url_for('temp_spec.onlyoffice_callback', spec_id=spec_id, _external=True)
# Docker 環境 URL 修正
if '127.0.0.1' in doc_url or 'localhost' in doc_url:
doc_url = doc_url.replace('127.0.0.1', 'host.docker.internal')
doc_url = doc_url.replace('localhost', 'host.docker.internal')
callback_url = callback_url.replace('127.0.0.1', 'host.docker.internal')
callback_url = callback_url.replace('localhost', 'host.docker.internal')
```
### 2. 前端元件最佳化
**關鍵發現**Tom Select 元件需要正確配置才能提供良好的使用者體驗。
```javascript
// 推薦的 Tom Select 配置
const recipientSelect = new TomSelect('#recipients', {
valueField: 'value',
labelField: 'text',
searchField: 'text',
placeholder: '請輸入姓名或 Email 來搜尋...',
plugins: ['remove_button'],
maxItems: null,
create: false,
load: function(query, callback) {
if (!query || query.length < 2) {
callback();
return;
}
// 實作搜尋邏輯...
}
});
```
---
## 🚀 部署最佳實踐
### 1. Docker 配置優化
```yaml
# docker-compose.yml - 生產環境配置
version: '3.8'
services:
app:
build: .
environment:
- FLASK_ENV=production
- PYTHONUNBUFFERED=1
volumes:
- ./uploads:/app/uploads
- ./logs:/app/logs
restart: unless-stopped
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MYSQL_DATABASE: ${DB_NAME}
volumes:
- mysql_data:/var/lib/mysql
restart: unless-stopped
volumes:
mysql_data:
```
### 2. 日誌管理
```python
# app.py - 生產環境日誌配置
if not app.debug:
if not os.path.exists('logs'):
os.mkdir('logs')
file_handler = RotatingFileHandler('logs/tempspec.log',
maxBytes=10240000, backupCount=10)
file_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
))
file_handler.setLevel(logging.INFO)
app.logger.addHandler(file_handler)
```
### 3. 安全性配置
```python
# 推薦的安全標頭設置
@app.after_request
def after_request(response):
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'DENY'
response.headers['X-XSS-Protection'] = '1; mode=block'
return response
```
---
## 🐛 除錯與監控
### 1. 開發階段除錯
```python
# 推薦的除錯配置
DEBUG_LDAP = os.getenv('DEBUG_LDAP', 'false').lower() == 'true'
DEBUG_EMAIL = os.getenv('DEBUG_EMAIL', 'false').lower() == 'true'
DEBUG_DATABASE = os.getenv('DEBUG_DATABASE', 'false').lower() == 'true'
def debug_log(category, message):
if category == 'ldap' and DEBUG_LDAP:
print(f"[LDAP DEBUG] {message}")
elif category == 'email' and DEBUG_EMAIL:
print(f"[EMAIL DEBUG] {message}")
# ...
```
### 2. 生產環境監控
```python
# tasks.py - 健康檢查任務
@scheduler.task('cron', id='health_check', hour='*/1')
def health_check():
try:
# 檢查資料庫連接
db.session.execute(text('SELECT 1'))
# 檢查 LDAP 連接
test_ldap_connection()
# 檢查 SMTP 連接
test_smtp_connection()
app.logger.info("Health check passed")
except Exception as e:
app.logger.error(f"Health check failed: {e}")
```
---
## 📊 效能優化要點
### 1. 資料庫查詢優化
```python
# 推薦的查詢模式
def get_active_specs_expiring_soon():
return TempSpec.query.filter(
TempSpec.status == 'active',
TempSpec.end_date <= datetime.now().date() + timedelta(days=7)
).options(
joinedload(TempSpec.uploads) # 預載關聯資料
).all()
```
### 2. 快取策略
```python
# 推薦使用 Flask-Caching
from flask_caching import Cache
cache = Cache(app, config={'CACHE_TYPE': 'simple'})
@cache.memoize(timeout=300) # 5分鐘快取
def get_ldap_group_members(group_name):
# LDAP 查詢邏輯...
```
---
## 🔧 維護與升級
### 1. 版本控制策略
```bash
# 推薦的版本標籤格式
git tag v3.2.0-rc1 # 發布候選版本
git tag v3.2.0 # 正式版本
git tag v3.2.1 # 修正版本
```
### 2. 備份策略
```bash
#!/bin/bash
# backup.sh - 定期備份腳本
DATE=$(date +%Y%m%d_%H%M%S)
# 資料庫備份
mysqldump -u $DB_USER -p$DB_PASSWORD $DB_NAME > backup_${DATE}.sql
# 檔案備份
tar -czf uploads_${DATE}.tar.gz uploads/
```
---
---
## 🎨 前端開發最佳實踐
### 1. Next.js + TypeScript 架構
**關鍵發現**App Router 架構提供更好的開發體驗和效能。
#### 推薦的專案結構
```
frontend/src/
├── app/ # Next.js 13+ App Router
│ ├── (auth)/ # Route Groups
│ ├── dashboard/ # Dashboard 路由
│ ├── todos/ # 待辦事項頁面
│ ├── layout.tsx # 根版面
│ └── page.tsx # 首頁
├── components/ # React 組件
│ ├── layout/ # 版面組件
│ ├── todos/ # 待辦事項組件
│ └── ui/ # 通用 UI 組件
├── lib/ # 工具函數
├── store/ # Redux 狀態管理
├── types/ # TypeScript 類型定義
└── providers/ # Context Providers
```
#### TypeScript 最佳實踐
```typescript
// types/todo.ts - 完整的類型定義
export interface TodoItem {
id: string;
title: string;
description?: string;
status: 'NEW' | 'DOING' | 'BLOCKED' | 'DONE';
priority: 'LOW' | 'MEDIUM' | 'HIGH' | 'URGENT';
due_date?: string;
created_at: string;
creator_ad: string;
starred: boolean;
is_public: boolean;
tags: string[];
responsible_users: string[];
followers: string[];
}
// API 回應類型
export interface ApiResponse<T> {
data: T;
message?: string;
total?: number;
}
```
### 2. Material-UI 整合
**關鍵發現**:主題系統和組件客制化是關鍵。
#### 主題設計
```typescript
// providers/ThemeProvider.tsx
import { createTheme, ThemeProvider } from '@mui/material/styles';
const lightTheme = createTheme({
palette: {
mode: 'light',
primary: {
main: '#3b82f6', // 藍色主題
},
secondary: {
main: '#10b981', // 綠色輔助
},
},
typography: {
fontFamily: '"Noto Sans TC", "Roboto", sans-serif',
},
});
const darkTheme = createTheme({
palette: {
mode: 'dark',
primary: {
main: '#60a5fa',
},
background: {
default: '#0f172a',
paper: '#1e293b',
},
},
});
```
#### 響應式設計
```typescript
// hooks/useResponsive.ts
import { useTheme, useMediaQuery } from '@mui/material';
export const useResponsive = () => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const isTablet = useMediaQuery(theme.breakpoints.between('md', 'lg'));
const isDesktop = useMediaQuery(theme.breakpoints.up('lg'));
return { isMobile, isTablet, isDesktop };
};
```
### 3. 狀態管理策略
**關鍵發現**Redux Toolkit + React Query 組合提供最佳的開發體驗。
#### Redux Store 設計
```typescript
// store/todoSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface TodoState {
filterMode: 'all' | 'created' | 'responsible' | 'following';
appliedFilters: TodoFilters;
selectedItems: string[];
viewMode: 'list' | 'card' | 'calendar';
}
const todoSlice = createSlice({
name: 'todo',
initialState,
reducers: {
setFilterMode: (state, action: PayloadAction<FilterMode>) => {
state.filterMode = action.payload;
},
toggleSelectItem: (state, action: PayloadAction<string>) => {
const id = action.payload;
if (state.selectedItems.includes(id)) {
state.selectedItems = state.selectedItems.filter(item => item !== id);
} else {
state.selectedItems.push(id);
}
},
},
});
```
#### React Query 集成
```typescript
// lib/api/todos.ts
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
export const useTodos = (filters: TodoFilters) => {
return useQuery({
queryKey: ['todos', filters],
queryFn: () => fetchTodos(filters),
staleTime: 30000, // 30秒內數據視為新鮮
});
};
export const useCreateTodo = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: createTodo,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] });
toast.success('待辦事項建立成功!');
},
onError: (error) => {
toast.error(error.message || '建立失敗');
},
});
};
```
---
## 🔄 後端 API 設計最佳實踐
### 1. Flask 應用架構
**關鍵發現**Blueprint 和工廠模式提供最佳的可維護性。
#### 應用程式工廠模式
```python
# app.py - 推薦的應用程式結構
def create_app(config_name=None):
if config_name is None:
config_name = os.environ.get('FLASK_ENV', 'development')
app = Flask(__name__)
app.config.from_object(config[config_name])
# 初始化擴展
db.init_app(app)
migrate.init_app(app, db)
jwt.init_app(app)
mail.init_app(app)
CORS(app, origins=app.config['CORS_ORIGINS'])
# 註冊 Blueprint
from routes import register_blueprints
register_blueprints(app)
# 設定錯誤處理
setup_error_handlers(app)
return app
```
#### RESTful API 設計
```python
# routes/todos.py - 標準化的 API 結構
@todos_bp.route('', methods=['GET'])
@jwt_required()
def get_todos():
try:
# 參數驗證
page = request.args.get('page', 1, type=int)
per_page = min(request.args.get('per_page', 20, type=int), 100)
# 業務邏輯
todos, total = todo_service.get_user_todos(
user_ad=get_jwt_identity(),
page=page,
per_page=per_page,
filters=request.args
)
# 統一回應格式
return jsonify({
'data': [todo.to_dict() for todo in todos],
'pagination': {
'page': page,
'per_page': per_page,
'total': total,
'pages': (total + per_page - 1) // per_page
}
}), 200
except ValidationError as e:
return jsonify({'error': str(e)}), 400
except Exception as e:
logger.error(f"Error getting todos: {str(e)}")
return jsonify({'error': 'Internal server error'}), 500
```
### 2. 資料驗證策略
**關鍵發現**:輸入驗證和序列化分離提供更好的可維護性。
```python
# validators/todo.py
from marshmallow import Schema, fields, validate
class CreateTodoSchema(Schema):
title = fields.Str(required=True, validate=validate.Length(min=1, max=200))
description = fields.Str(missing=None, validate=validate.Length(max=2000))
status = fields.Str(missing='NEW', validate=validate.OneOf(['NEW', 'DOING', 'BLOCKED', 'DONE']))
priority = fields.Str(missing='MEDIUM', validate=validate.OneOf(['LOW', 'MEDIUM', 'HIGH', 'URGENT']))
due_date = fields.Date(missing=None)
responsible_users = fields.List(fields.Str(), missing=list)
followers = fields.List(fields.Str(), missing=list)
tags = fields.List(fields.Str(), missing=list)
is_public = fields.Bool(missing=False)
starred = fields.Bool(missing=False)
# 使用裝飾器進行驗證
def validate_json(schema):
def decorator(f):
def wrapper(*args, **kwargs):
try:
data = schema.load(request.json or {})
return f(data, *args, **kwargs)
except ValidationError as e:
return jsonify({'error': e.messages}), 400
return wrapper
return decorator
@todos_bp.route('', methods=['POST'])
@jwt_required()
@validate_json(CreateTodoSchema())
def create_todo(data):
# 已驗證的資料直接使用
todo = todo_service.create_todo(
creator_ad=get_jwt_identity(),
**data
)
return jsonify({'data': todo.to_dict()}), 201
```
### 3. 服務層架構
**關鍵發現**:業務邏輯分離到服務層提供更好的測試性。
```python
# services/todo_service.py
class TodoService:
def __init__(self):
self.logger = get_logger(__name__)
def create_todo(self, creator_ad: str, **kwargs) -> TodoItem:
"""建立新的待辦事項"""
try:
# 驗證負責人帳號
if kwargs.get('responsible_users'):
valid_users = validate_ad_accounts(kwargs['responsible_users'])
if len(valid_users) != len(kwargs['responsible_users']):
invalid_users = set(kwargs['responsible_users']) - set(valid_users.keys())
raise ValidationError(f"無效的負責人帳號: {', '.join(invalid_users)}")
# 建立待辦事項
todo = TodoItem(
title=kwargs['title'],
description=kwargs.get('description'),
status=kwargs.get('status', 'NEW'),
priority=kwargs.get('priority', 'MEDIUM'),
due_date=kwargs.get('due_date'),
creator_ad=creator_ad,
is_public=kwargs.get('is_public', False),
starred=kwargs.get('starred', False),
tags=kwargs.get('tags', [])
)
# 設定使用者資訊
user_info = get_user_info(creator_ad)
if user_info:
todo.creator_display_name = user_info['display_name']
todo.creator_email = user_info['email']
db.session.add(todo)
db.session.flush() # 取得 ID
# 新增負責人和追蹤者
self._add_users_to_todo(todo.id, kwargs.get('responsible_users', []), 'responsible')
self._add_users_to_todo(todo.id, kwargs.get('followers', []), 'follower')
db.session.commit()
# 發送通知
self._send_assignment_notifications(todo)
# 記錄稽核日誌
self._log_audit(todo.id, creator_ad, 'CREATE', '建立待辦事項')
return todo
except Exception as e:
db.session.rollback()
self.logger.error(f"Error creating todo: {str(e)}")
raise
```
---
## 📊 Excel 處理最佳實踐
### 1. 安全的檔案處理
**關鍵發現**:檔案上傳和處理需要多層驗證。
```python
# utils/excel_utils.py
import pandas as pd
from openpyxl import load_workbook
import tempfile
import os
class ExcelProcessor:
ALLOWED_EXTENSIONS = {'.xlsx', '.xls', '.csv'}
MAX_FILE_SIZE = 16 * 1024 * 1024 # 16MB
MAX_ROWS = 10000
def validate_file(self, file):
"""檔案安全驗證"""
# 檔案大小檢查
if len(file.read()) > self.MAX_FILE_SIZE:
raise ValidationError("檔案大小超過限制 (16MB)")
file.seek(0) # 重置檔案指針
# 檔案類型檢查
filename = secure_filename(file.filename)
if not any(filename.lower().endswith(ext) for ext in self.ALLOWED_EXTENSIONS):
raise ValidationError("不支援的檔案格式")
return filename
def parse_excel_file(self, file):
"""安全的 Excel 解析"""
try:
with tempfile.NamedTemporaryFile(delete=False) as temp_file:
file.save(temp_file.name)
# 使用 pandas 讀取檔案
if temp_file.name.endswith('.csv'):
df = pd.read_csv(temp_file.name, encoding='utf-8')
else:
df = pd.read_excel(temp_file.name)
# 行數限制
if len(df) > self.MAX_ROWS:
raise ValidationError(f"資料列數超過限制 ({self.MAX_ROWS})")
return self.validate_and_transform_data(df)
finally:
# 清理暫存檔案
if 'temp_file' in locals():
os.unlink(temp_file.name)
def validate_and_transform_data(self, df):
"""資料驗證和轉換"""
required_columns = ['標題', '負責人']
missing_columns = [col for col in required_columns if col not in df.columns]
if missing_columns:
raise ValidationError(f"缺少必要欄位: {', '.join(missing_columns)}")
validated_data = []
errors = []
for index, row in df.iterrows():
try:
todo_data = self.validate_row(row, index + 2) # +2 因為標題行
validated_data.append(todo_data)
except ValidationError as e:
errors.append(f"第 {index + 2} 行: {str(e)}")
return {
'data': validated_data,
'errors': errors,
'total': len(df)
}
```
### 2. 模板生成
**關鍵發現**:動態模板生成提供更好的使用者體驗。
```python
# routes/excel.py
@excel_bp.route('/template', methods=['GET'])
@jwt_required()
def download_template():
"""生成並下載 Excel 匯入模板"""
try:
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment
from openpyxl.worksheet.datavalidation import DataValidation
wb = Workbook()
ws = wb.active
ws.title = "待辦事項匯入模板"
# 標題行
headers = [
'標題*', '描述', '狀態', '優先級',
'到期日', '負責人*', '追蹤人員', '標籤', '備註'
]
# 設定標題樣式
title_font = Font(bold=True, color='FFFFFF')
title_fill = PatternFill(start_color='366092', end_color='366092', fill_type='solid')
for col, header in enumerate(headers, 1):
cell = ws.cell(row=1, column=col, value=header)
cell.font = title_font
cell.fill = title_fill
cell.alignment = Alignment(horizontal='center')
# 資料驗證
status_validation = DataValidation(
type="list",
formula1='"NEW,DOING,BLOCKED,DONE"',
showErrorMessage=True,
errorTitle="狀態錯誤",
error="請選擇: NEW, DOING, BLOCKED, DONE"
)
priority_validation = DataValidation(
type="list",
formula1='"LOW,MEDIUM,HIGH,URGENT"',
showErrorMessage=True,
errorTitle="優先級錯誤",
error="請選擇: LOW, MEDIUM, HIGH, URGENT"
)
ws.add_data_validation(status_validation)
ws.add_data_validation(priority_validation)
status_validation.add(f'C2:C{MAX_ROWS}')
priority_validation.add(f'D2:D{MAX_ROWS}')
# 範例資料
sample_data = [
['完成月報撰寫', '包含各部門數據統計和分析', 'NEW', 'HIGH', '2024/01/15', 'user1', 'manager1', '報告,月度', '請於期限內完成'],
['系統維護檢查', '定期檢查伺服器狀態', 'DOING', 'MEDIUM', '2024/01/20', 'user2', 'user1,user3', 'IT,維護', ''],
]
for row, data in enumerate(sample_data, 2):
for col, value in enumerate(data, 1):
ws.cell(row=row, column=col, value=value)
# 調整欄寬
column_widths = [20, 30, 12, 12, 12, 15, 20, 15, 20]
for col, width in enumerate(column_widths, 1):
ws.column_dimensions[ws.cell(row=1, column=col).column_letter].width = width
# 生成檔案
from io import BytesIO
output = BytesIO()
wb.save(output)
output.seek(0)
return send_file(
output,
as_attachment=True,
download_name=f'todo_import_template_{datetime.now().strftime("%Y%m%d")}.xlsx',
mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
)
except Exception as e:
logger.error(f"Error generating template: {str(e)}")
return jsonify({'error': '模板生成失敗'}), 500
```
---
## 📝 總結
本文件記錄了開發 PANJIT To-Do System 過程中的關鍵技術決策和最佳實踐。這些經驗可以幫助後續開發者:
1. **避免常見陷阱**:特別是 LDAP 配置、前後端整合、Excel 處理
2. **提升開發效率**:使用經過驗證的架構模式和工具鏈
3. **確保程式碼品質**:遵循 TypeScript、Python 最佳實踐
4. **提升使用者體驗**:響應式設計、效能優化、錯誤處理
5. **簡化維護工作**:清晰的架構分離、完整的日誌記錄
**重要技術決策總結**
- **前端**Next.js 14 App Router + TypeScript + Material-UI + Redux Toolkit
- **後端**Flask + SQLAlchemy + Celery + Redis + JWT
- **資料庫**MySQL 8.0 + 適當索引設計
- **部署**Docker + Nginx + 健康檢查
**重要提醒**:本文件包含敏感資訊,請勿外洩或提交至公開版本控制系統。
---
*最後更新2025年1月*
*文件版本V2.0*
*適用系統PANJIT To-Do System v1.0*