# 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"""

[暫規通知] 規範 '{spec.spec_code}' 已{action}

您好,

""" 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 { 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) => { state.filterMode = action.payload; }, toggleSelectItem: (state, action: PayloadAction) => { 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*