夥伴對齊系統 - 軟體設計文件 (SDD) v2.0 文件修訂記錄 版本 日期 修改者 變更內容 1.0 2025-10-15 - 初版SDD文件建立 2.0 2025-10-15 - 新增評分競賽功能與權限管理系統 ? v2.0 新增功能概覽 1. 評分競賽儀表板 * 個人積分即時顯示 * 部門排名百分比計算 * 視覺化競賽排行榜 * 成就徽章系統 * 積分趨勢圖表 2. 完整權限管理系統 * 三層角色架構(超級管理員/管理者/使用者) * 使用者認證與授權 * 細粒度權限控制 * 操作日誌追蹤 * 安全的Session管理 1. 文件資訊 版本: 2.0 最後更新: 2025年10月 文件狀態: 修訂版 專案類型: 內部管理系統 2. 執行摘要 2.1 專案目標 建立一套完整的夥伴能力評估與對齊管理平台,透過視覺化拖拉介面簡化能力評估流程,並整合STAR回饋機制、遊戲化積分競賽系統與完整權限管理,提升組織內部人才發展與管理效率。 2.2 核心價值主張 * 效率提升: 拖拉式操作減少50%以上評估時間 * 結構化回饋: STAR框架確保回饋品質與可追蹤性 * 遊戲化激勵: ?? 競賽排名與百分比顯示促進良性競爭 * 安全管控: ?? 完整的權限管理確保資料安全 * 資料驅動: 完整的評估資料分析與匯出功能 2.3 關鍵成功指標 * 評估完成率提升30% * 回饋品質標準化達90% * 系統使用率達80%以上 * 用戶活躍度提升40%(遊戲化激勵) * 安全事件零發生(權限管理) 3. 系統架構設計 3.1 技術架構概覽 ┌─────────────────────────────────────────┐ │ 前端層 (Presentation) │ │ HTML5 + Bootstrap 5 + JavaScript │ │ + Chart.js (圖表) + 動態儀表板 │ └─────────────┬───────────────────────────┘ │ HTTP/REST API + JWT Token ┌─────────────▼───────────────────────────┐ │ 應用層 (Application) │ │ Python Flask + SQLAlchemy │ │ + Flask-Login (認證) │ │ + Flask-JWT-Extended (Token) │ │ + 權限裝飾器 (Authorization) │ └─────────────┬───────────────────────────┘ │ ORM ┌─────────────▼───────────────────────────┐ │ 資料層 (Data) │ │ MySQL 5.7+ / 8.0+ │ │ + 用戶認證表 + 權限控制表 │ └─────────────────────────────────────────┘ 3.2 系統分層說明 3.2.1 前端層(新增功能) * 職責: 使用者介面、互動邏輯、資料展示、即時儀表板 * 技術選型: o Bootstrap 5: 響應式UI框架 o 原生JavaScript: 拖拉功能、表單驗證 o Chart.js: 積分趨勢圖表與排名視覺化 o Counter.js: 數字動畫效果 o HTML5 Drag & Drop API: 能力評估拖拉介面 * 關鍵模組: o app.js: 全域工具函數與API通訊 o assessment.js: 能力評估拖拉邏輯 o admin.js: 後台管理介面邏輯 o dashboard.js: ?? 個人儀表板與競賽排名 o auth.js: ?? 登入/登出/權限驗證 3.2.2 應用層(新增功能) * 職責: 業務邏輯、資料處理、API路由、認證授權 * 技術選型: o Flask 2.x: 輕量化Web框架 o SQLAlchemy: ORM資料庫抽象層 o Flask-Login: 用戶Session管理 o Flask-JWT-Extended: Token認證 o Flask-Bcrypt: 密碼加密 o Flask-CORS: 跨域請求處理 o pandas + openpyxl: 資料匯出 * 安全機制: o 環境變數管理敏感資訊 o CORS跨域限制 o 輸入驗證與SQL注入防護 o 錯誤處理機制 o 密碼雜湊(Bcrypt) o JWT Token認證 o 角色權限檢查裝飾器 o 操作日誌記錄 3.2.3 資料層(新增功能) * 職責: 資料持久化、查詢優化、權限資料管理 * 技術選型: MySQL 5.7+ / 8.0+ * 關鍵特性: o 事務處理保證資料一致性 o 索引優化查詢效能 o JSON欄位儲存彈性資料 o 用戶認證資料加密儲存 o 權限資料關聯查詢優化 4. 資料庫設計(含新增表格) 4.1 ER關係圖(v2.0) ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ users │──────│ user_roles │──────│ roles │ │ (用戶) │ M:N │ (用戶角色) │ M:N │ (角色) │ └──────┬───────┘ └──────────────┘ └──────┬───────┘ │ │ │ │ M:N │ ┌──────▼────────┐ │ │role_permissions│ │ │ (角色權限) │ │ └──────┬────────┘ │ │ │ ┌──────▼────────┐ │ │ permissions │ │ │ (權限) │ │ └───────────────┘ │ ├─────────────┐ │ │ ┌───▼────┐ ┌───▼─────────┐ ┌──────────────────┐ │ audit_ │ │ assessments │ │ capabilities │ │ logs │ │ (能力評估) │ │ (能力項目) │ │(日誌) │ └─────────────┘ └──────────────────┘ └────────┘ │ ▼ ┌─────────────────┐ ┌──────────────────┐ │ star_feedbacks │─────?│ employee_points │ │ (STAR回饋) │ │ (員工積分) │ └─────────────────┘ └──────────┬───────┘ │ │ │ ▼ │ ┌──────────────────┐ └──────────────?│ monthly_rankings │ │ (月度排名) │ └──────────────────┘ 4.2 新增資料表結構 4.2.1 users (用戶表) ?? 欄位名稱 資料型別 說明 約束 id INT 主鍵 PK, AUTO_INCREMENT username VARCHAR(50) 用戶名稱 NOT NULL, UNIQUE email VARCHAR(100) 電子郵件 NOT NULL, UNIQUE password_hash VARCHAR(255) 密碼雜湊 NOT NULL full_name VARCHAR(100) 真實姓名 NOT NULL department VARCHAR(100) 部門 NOT NULL position VARCHAR(100) 職位 NOT NULL employee_id VARCHAR(50) 員工編號 UNIQUE is_active BOOLEAN 帳號狀態 DEFAULT TRUE last_login_at DATETIME 最後登入時間 NULL created_at DATETIME 建立時間 DEFAULT CURRENT_TIMESTAMP updated_at DATETIME 更新時間 ON UPDATE CURRENT_TIMESTAMP 索引策略: * idx_username: (username) UNIQUE * idx_email: (email) UNIQUE * idx_department: (department) * idx_employee_id: (employee_id) UNIQUE 密碼規則: * 最少8字元 * 包含大小寫字母 * 包含數字 * 包含特殊符號 4.2.2 roles (角色表) ?? 欄位名稱 資料型別 說明 約束 id INT 主鍵 PK, AUTO_INCREMENT name VARCHAR(50) 角色名稱 NOT NULL, UNIQUE display_name VARCHAR(100) 顯示名稱 NOT NULL description TEXT 角色說明 NULL level INT 角色層級 NOT NULL is_active BOOLEAN 是否啟用 DEFAULT TRUE created_at DATETIME 建立時間 DEFAULT CURRENT_TIMESTAMP 預設角色: INSERT INTO roles (name, display_name, description, level) VALUES ('super_admin', '超級管理員', '系統最高權限,可管理所有功能與用戶', 100), ('admin', '管理者', '部門管理權限,可管理部門內用戶與資料', 50), ('user', '一般使用者', '基本使用權限,可查看個人資料與排名', 10); 角色層級說明: * Level 100: 超級管理員 * Level 50: 管理者 * Level 10: 一般使用者 4.2.3 permissions (權限表) ?? 欄位名稱 資料型別 說明 約束 id INT 主鍵 PK, AUTO_INCREMENT name VARCHAR(100) 權限名稱 NOT NULL, UNIQUE display_name VARCHAR(100) 顯示名稱 NOT NULL resource VARCHAR(50) 資源類型 NOT NULL action VARCHAR(50) 動作 NOT NULL description TEXT 權限說明 NULL created_at DATETIME 建立時間 DEFAULT CURRENT_TIMESTAMP 權限命名規範: resource:action * 範例: user:create, assessment:read, report:export 預設權限清單: -- 用戶管理權限 INSERT INTO permissions (name, display_name, resource, action, description) VALUES ('user:create', '建立用戶', 'user', 'create', '建立新用戶帳號'), ('user:read', '查看用戶', 'user', 'read', '查看用戶資料'), ('user:update', '更新用戶', 'user', 'update', '更新用戶資料'), ('user:delete', '刪除用戶', 'user', 'delete', '刪除用戶帳號'), ('user:manage_roles', '管理角色', 'user', 'manage_roles', '分配用戶角色'), -- 評估管理權限 ('assessment:create', '建立評估', 'assessment', 'create', '建立能力評估'), ('assessment:read', '查看評估', 'assessment', 'read', '查看評估資料'), ('assessment:read_all', '查看所有評估', 'assessment', 'read_all', '查看所有部門評估'), ('assessment:update', '更新評估', 'assessment', 'update', '更新評估資料'), ('assessment:delete', '刪除評估', 'assessment', 'delete', '刪除評估記錄'), -- STAR回饋權限 ('feedback:create', '建立回饋', 'feedback', 'create', '建立STAR回饋'), ('feedback:read', '查看回饋', 'feedback', 'read', '查看回饋資料'), ('feedback:read_all', '查看所有回饋', 'feedback', 'read_all', '查看所有部門回饋'), -- 排名權限 ('ranking:read', '查看排名', 'ranking', 'read', '查看個人排名'), ('ranking:read_department', '查看部門排名', 'ranking', 'read_department', '查看部門排名'), ('ranking:read_all', '查看所有排名', 'ranking', 'read_all', '查看全公司排名'), ('ranking:calculate', '計算排名', 'ranking', 'calculate', '手動觸發排名計算'), -- 報表權限 ('report:export', '匯出報表', 'report', 'export', '匯出資料報表'), ('report:export_all', '匯出所有報表', 'report', 'export_all', '匯出全公司報表'), -- 系統管理權限 ('system:config', '系統設定', 'system', 'config', '修改系統設定'), ('system:logs', '查看日誌', 'system', 'logs', '查看系統操作日誌'), ('system:backup', '資料備份', 'system', 'backup', '執行資料備份'); 4.2.4 role_permissions (角色權限關聯表) ?? 欄位名稱 資料型別 說明 約束 id INT 主鍵 PK, AUTO_INCREMENT role_id INT 角色ID FK → roles.id permission_id INT 權限ID FK → permissions.id created_at DATETIME 建立時間 DEFAULT CURRENT_TIMESTAMP 索引策略: * unique_role_permission: (role_id, permission_id) UNIQUE * idx_role_id: (role_id) * idx_permission_id: (permission_id) 預設角色權限配置: # 超級管理員: 所有權限 super_admin_permissions = [所有權限] # 管理者: 部門管理權限 admin_permissions = [ 'user:read', 'user:update', 'assessment:create', 'assessment:read', 'assessment:read_all', 'assessment:update', 'assessment:delete', 'feedback:create', 'feedback:read', 'feedback:read_all', 'ranking:read', 'ranking:read_department', 'ranking:read_all', 'report:export' ] # 使用者: 基本查看權限 user_permissions = [ 'assessment:read', 'feedback:read', 'ranking:read' ] 4.2.5 user_roles (用戶角色關聯表) ?? 欄位名稱 資料型別 說明 約束 id INT 主鍵 PK, AUTO_INCREMENT user_id INT 用戶ID FK → users.id role_id INT 角色ID FK → roles.id assigned_by INT 分配者ID FK → users.id, NULL assigned_at DATETIME 分配時間 DEFAULT CURRENT_TIMESTAMP 索引策略: * unique_user_role: (user_id, role_id) UNIQUE * idx_user_id: (user_id) * idx_role_id: (role_id) 業務規則: * 一個用戶可擁有多個角色 * 權限取所有角色的聯集 * 預設新用戶自動分配 user 角色 4.2.6 audit_logs (操作日誌表) ?? 欄位名稱 資料型別 說明 約束 id INT 主鍵 PK, AUTO_INCREMENT user_id INT 用戶ID FK → users.id, NULL action VARCHAR(100) 操作動作 NOT NULL resource_type VARCHAR(50) 資源類型 NOT NULL resource_id INT 資源ID NULL details JSON 操作詳情 NULL ip_address VARCHAR(45) IP位址 NULL user_agent VARCHAR(255) 用戶代理 NULL status ENUM 狀態 'success', 'failed' error_message TEXT 錯誤訊息 NULL created_at DATETIME 建立時間 DEFAULT CURRENT_TIMESTAMP 索引策略: * idx_user_id: (user_id) * idx_action: (action) * idx_created_at: (created_at) * idx_resource: (resource_type, resource_id) 記錄操作類型: * login: 登入 * logout: 登出 * create: 建立資源 * read: 查看資源 * update: 更新資源 * delete: 刪除資源 * export: 匯出資料 * permission_change: 權限變更 4.3 原有資料表更新 4.3.1 employee_points (員工積分表) - 新增欄位 新增欄位: 欄位名稱 資料型別 說明 約束 user_id INT 關聯用戶ID FK → users.id, NULL department_rank INT 部門排名 NULL department_percentile DECIMAL(5,2) 部門百分比 NULL total_rank INT 總排名 NULL total_percentile DECIMAL(5,2) 總百分比 NULL 百分比計算說明: # 百分比 = (勝過的人數 / 總人數) × 100 # 例如: 部門10人,排名第3 # department_percentile = (7 / 10) × 100 = 70.00 # 表示勝過70%的同事 4.3.2 star_feedbacks (STAR回饋表) - 新增欄位 新增欄位: 欄位名稱 資料型別 說明 約束 evaluator_user_id INT 評分者用戶ID FK → users.id, NULL evaluatee_user_id INT 受評者用戶ID FK → users.id, NULL 5. ?? 評分競賽功能設計 5.1 個人儀表板 (Dashboard) 5.1.1 功能概述 頁面路由: /dashboard 權限: 所有已登入用戶 更新頻率: 實時(每次頁面載入時計算) 5.1.2 儀表板佈局 ┌─────────────────────────────────────────────────────────┐ │ ?? 歡迎回來,張三! 最後登入: 10/15 │ ├─────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ 總積分 │ │ 部門排名 │ │ 本月新增 │ │ │ │ │ │ │ │ │ │ │ │ 450 pts │ │ #3 / 15 │ │ +80 pts │ │ │ │ │ │ │ │ │ │ │ │ ?? Top 20% │ │ 勝過 80% │ │ ?? +21% │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ │ │ ├─────────────────────────────────────────────────────────┤ │ 積分趨勢 │ │ ┌──────────────────────────────────────────────────┐ │ │ │ │ │ │ │ ?? [折線圖: 過去6個月積分趨勢] │ │ │ │ │ │ │ └──────────────────────────────────────────────────┘ │ ├─────────────────────────────────────────────────────────┤ │ 部門排行榜 (Top 10) │ │ ┌──────────────────────────────────────────────────┐ │ │ │ 排名 姓名 積分 徽章 │ │ │ │ ?? 1 王五 580 ????? │ │ │ │ ?? 2 李四 520 ??? │ │ │ │ ?? 3 張三 450 ?? (你) │ │ │ │ 4 趙六 420 │ │ │ │ 5 ... │ │ │ └──────────────────────────────────────────────────┘ │ ├─────────────────────────────────────────────────────────┤ │ 最近收到的回饋 (最新5筆) │ │ ┌──────────────────────────────────────────────────┐ │ │ │ 10/14 | 王主管 | ????? | "專案交付優秀..." │ │ │ │ 10/10 | 李經理 | ???? | "團隊合作良好..." │ │ │ │ ... │ │ │ └──────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘ 5.1.3 核心指標說明 1. 總積分 (Total Points) * 顯示: 累積總積分 * 計算: 所有STAR回饋積分總和 * 顏色編碼: o ?? 綠色: Top 20% (優秀) o ?? 黃色: 21-50% (良好) o ?? 橙色: 51-80% (普通) o ?? 紅色: Bottom 20% (需努力) 2. 部門排名 (Department Rank) * 顯示: #當前排名 / 部門總人數 * 百分比: 勝過X%的部門同事 * 計算公式: department_percentile = ((total_count - rank) / total_count) × 100 # 例如: 15人中排名第3 # percentile = ((15 - 3) / 15) × 100 = 80% 3. 本月新增積分 * 顯示: 本月累積積分 * 趨勢: 與上月比較的百分比變化 * 圖示: o ?? 上升 o ?? 下降 o ?? 持平 5.1.4 徽章系統 積分徽章: 徽章 名稱 條件 描述 ?? 金牌選手 部門第1名 部門積分冠軍 ?? 銀牌選手 部門第2名 部門積分亞軍 ?? 銅牌選手 部門第3名 部門積分季軍 ?? 頂尖表現 Top 10% 位於前10% ? 優秀表現 Top 20% 位於前20% ?? 連勝王 連續3個月第1 持續領先 ?? 百分百 獲得5分評分10次 卓越品質 ?? 進步之星 月增長>50% 快速成長 成就徽章: 徽章 名稱 條件 描述 ?? 首次達成 首次獲得回饋 踏出第一步 ?? 堅持不懈 連續6個月有回饋 持續表現 ?? 團隊之星 收到跨部門回饋 跨域合作 ?? 全能選手 5個L等級都有能力 全面發展 5.2 競賽排行榜頁面 5.2.1 功能概述 頁面路由: /leaderboard 權限: 所有已登入用戶 篩選選項: * 全公司排名 * 部門排名 * 月度排名 * 年度排名 5.2.2 排行榜設計 ┌─────────────────────────────────────────────────────────┐ │ ?? 競賽排行榜 │ ├─────────────────────────────────────────────────────────┤ │ 篩選: [全公司▼] [本月▼] [技術部▼] ?? 搜尋 │ ├─────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────────────────────────────────────────┐ │ │ │ ?? 本月冠軍 │ │ │ │ ┌────────────────────────────────────────────┐ │ │ │ │ │ ?? 王五 (技術部 - 資深工程師) │ │ │ │ │ │ ?? 580 積分 │ │ │ │ │ │ ????? 勝過 98% 的同事 │ │ │ │ │ └────────────────────────────────────────────┘ │ │ │ └──────────────────────────────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────────────────┐ │ │ │ 排名 姓名 部門 職位 積分 百分比 │ │ │ │ ─────────────────────────────────────────────── │ │ │ │ ?? 1 王五 技術部 資深工程師 580 98% ????│ │ │ │ ?? 2 李四 業務部 業務經理 520 96% ???│ │ │ │ ?? 3 張三 技術部 工程師 450 94% ?? │ │ │ │ 4 趙六 技術部 工程師 420 92% ? │ │ │ │ 5 孫七 人資部 專員 380 90% │ │ │ │ ... │ │ │ │ 42 你 技術部 工程師 150 45% │ │ │ │ ... │ │ │ └──────────────────────────────────────────────────┘ │ │ │ │ [1] [2] [3] ... [10] 下一頁 → │ └─────────────────────────────────────────────────────────┘ 5.2.3 互動功能 點擊排名條目: * 顯示該員工的詳細積分分佈 * 查看最近的STAR回饋 * 積分趨勢圖表 排名變化顯示: * ↑ 綠色向上箭頭: 排名上升 * ↓ 紅色向下箭頭: 排名下降 * ?? 灰色橫線: 排名不變 * ?? 藍色標籤: 新進榜 排名規則: 1. 按積分降序排列 2. 積分相同時按最近回饋時間排序 3. 零積分不顯示排名,顯示為「尚未評分」 4. 可切換查看「本月」、「本季」、「本年」、「總積分」 5.3 百分比計算邏輯 5.3.1 部門百分比計算 def calculate_department_percentile(user_id, department): """ 計算用戶在部門中的百分比排名 Args: user_id: 用戶ID department: 部門名稱 Returns: dict: { 'rank': 排名, 'total': 部門總人數, 'percentile': 百分比, 'better_than_count': 勝過的人數 } """ # 1. 查詢部門所有員工積分 employees = db.session.query(EmployeePoints)\ .filter_by(department=department)\ .filter(EmployeePoints.total_points > 0)\ .order_by(EmployeePoints.total_points.desc())\ .all() total_count = len(employees) # 2. 找到用戶排名 user_rank = None for idx, emp in enumerate(employees, start=1): if emp.user_id == user_id: user_rank = idx break if user_rank is None: return { 'rank': 0, 'total': total_count, 'percentile': 0.0, 'better_than_count': 0 } # 3. 計算百分比 better_than_count = total_count - user_rank percentile = (better_than_count / total_count * 100) if total_count > 0 else 0 return { 'rank': user_rank, 'total': total_count, 'percentile': round(percentile, 2), 'better_than_count': better_than_count } # 使用範例 result = calculate_department_percentile(user_id=1, department="技術部") print(f"排名: #{result['rank']} / {result['total']}") print(f"勝過 {result['percentile']}% 的同事") print(f"勝過 {result['better_than_count']} 人") 5.3.2 全公司百分比計算 def calculate_total_percentile(user_id): """ 計算用戶在全公司的百分比排名 Returns: dict: 全公司排名資訊 """ # 查詢所有員工積分 all_employees = db.session.query(EmployeePoints)\ .filter(EmployeePoints.total_points > 0)\ .order_by(EmployeePoints.total_points.desc())\ .all() total_count = len(all_employees) # 找到用戶排名 user_rank = None for idx, emp in enumerate(all_employees, start=1): if emp.user_id == user_id: user_rank = idx break if user_rank is None: return { 'rank': 0, 'total': total_count, 'percentile': 0.0, 'tier': 'unranked' } # 計算百分比與層級 better_than_count = total_count - user_rank percentile = (better_than_count / total_count * 100) if total_count > 0 else 0 # 判斷層級 if percentile >= 80: tier = 'top_20' # 頂尖 20% elif percentile >= 50: tier = 'top_50' # 前 50% elif percentile >= 20: tier = 'middle' # 中段 else: tier = 'bottom_20' # 後 20% return { 'rank': user_rank, 'total': total_count, 'percentile': round(percentile, 2), 'tier': tier } 5.3.3 定期更新排名任務 # 定時任務: 每小時更新排名與百分比 from apscheduler.schedulers.background import BackgroundScheduler def update_all_rankings(): """更新所有員工的排名與百分比""" try: # 1. 更新部門排名 departments = db.session.query(EmployeePoints.department)\ .distinct().all() for (dept,) in departments: employees = db.session.query(EmployeePoints)\ .filter_by(department=dept)\ .filter(EmployeePoints.total_points > 0)\ .order_by(EmployeePoints.total_points.desc())\ .all() total = len(employees) for rank, emp in enumerate(employees, start=1): better_than = total - rank percentile = (better_than / total * 100) if total > 0 else 0 emp.department_rank = rank emp.department_percentile = round(percentile, 2) # 2. 更新全公司排名 all_employees = db.session.query(EmployeePoints)\ .filter(EmployeePoints.total_points > 0)\ .order_by(EmployeePoints.total_points.desc())\ .all() total = len(all_employees) for rank, emp in enumerate(all_employees, start=1): better_than = total - rank percentile = (better_than / total * 100) if total > 0 else 0 emp.total_rank = rank emp.total_percentile = round(percentile, 2) db.session.commit() print(f"? 排名更新完成: {total} 位員工") except Exception as e: db.session.rollback() print(f"? 排名更新失敗: {str(e)}") # 啟動定時任務 scheduler = BackgroundScheduler() scheduler.add_job(update_all_rankings, 'interval', hours=1) scheduler.start() 5.4 即時通知系統 5.4.1 通知觸發時機 事件 通知內容 優先級 收到新回饋 "?? 您收到來自XXX的新回饋,獲得XX積分!" 高 排名上升 "?? 恭喜!您的排名上升至第X名!" 中 進入前X% "?? 太棒了!您已進入部門前10%!" 高 獲得徽章 "? 解鎖新成就:XXX" 中 被超越 "?? 您的排名下降至第X名" 低 月度排名公布 "?? 本月排名已公布,您排名第X" 中 5.4.2 通知實作 # 通知資料表 (可選實作) class Notification(db.Model): id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('users.id')) type = db.Column(db.String(50)) # 'new_feedback', 'rank_up', 'badge' title = db.Column(db.String(100)) message = db.Column(db.Text) is_read = db.Column(db.Boolean, default=False) created_at = db.Column(db.DateTime, default=datetime.utcnow) def send_notification(user_id, notification_type, title, message): """發送通知給用戶""" notification = Notification( user_id=user_id, type=notification_type, title=title, message=message ) db.session.add(notification) db.session.commit() # 可擴展: Email、Slack、推播通知等 6. ?? 權限管理系統設計 6.1 認證流程設計 6.1.1 用戶註冊流程 用戶註冊請求 ↓ 驗證輸入資料 (email格式、密碼強度、必填欄位) ↓ 檢查用戶名/Email是否已存在 ↓ 密碼加密 (Bcrypt) ↓ 建立用戶記錄 ↓ 自動分配「一般使用者」角色 ↓ 發送歡迎郵件 (可選) ↓ 返回成功訊息 API端點: POST /api/auth/register 請求Body: { "username": "zhangsan", "email": "zhangsan@company.com", "password": "SecurePass123!", "full_name": "張三", "department": "技術部", "position": "工程師", "employee_id": "EMP001" } 密碼驗證規則: import re def validate_password(password): """ 密碼強度驗證 規則: - 至少8字元 - 包含大寫字母 - 包含小寫字母 - 包含數字 - 包含特殊符號 """ if len(password) < 8: return False, "密碼至少需要8個字元" if not re.search(r'[A-Z]', password): return False, "密碼需包含大寫字母" if not re.search(r'[a-z]', password): return False, "密碼需包含小寫字母" if not re.search(r'\d', password): return False, "密碼需包含數字" if not re.search(r'[!@#$%^&*(),.?":{}|<>]', password): return False, "密碼需包含特殊符號" return True, "密碼強度符合要求" 6.1.2 用戶登入流程 用戶登入請求 (username + password) ↓ 查詢用戶是否存在 ↓ 驗證密碼 (Bcrypt verify) ↓ 檢查帳號狀態 (is_active) ↓ 查詢用戶角色與權限 ↓ 生成JWT Token ↓ 更新最後登入時間 ↓ 記錄登入日誌 ↓ 返回Token + 用戶資訊 API端點: POST /api/auth/login 請求Body: { "username": "zhangsan", "password": "SecurePass123!" } 成功回應: { "success": true, "message": "登入成功", "data": { "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "token_type": "Bearer", "expires_in": 3600, "user": { "id": 1, "username": "zhangsan", "email": "zhangsan@company.com", "full_name": "張三", "department": "技術部", "position": "工程師", "roles": ["user"], "permissions": ["assessment:read", "feedback:read", "ranking:read"] } } } 6.1.3 JWT Token設計 Token結構: # Access Token (有效期1小時) { "user_id": 1, "username": "zhangsan", "roles": ["user"], "permissions": ["assessment:read", "feedback:read"], "exp": 1729000000, # 過期時間 "iat": 1728996400 # 簽發時間 } # Refresh Token (有效期7天) { "user_id": 1, "type": "refresh", "exp": 1729601200, "iat": 1728996400 } Token實作: from flask_jwt_extended import ( JWTManager, create_access_token, create_refresh_token, jwt_required, get_jwt_identity, get_jwt ) jwt = JWTManager(app) def generate_tokens(user): """生成Access Token和Refresh Token""" # 準備Token payload additional_claims = { "roles": [role.name for role in user.roles], "permissions": get_user_permissions(user), "department": user.department } # 生成tokens access_token = create_access_token( identity=user.id, additional_claims=additional_claims, expires_delta=timedelta(hours=1) ) refresh_token = create_refresh_token( identity=user.id, expires_delta=timedelta(days=7) ) return { "access_token": access_token, "refresh_token": refresh_token, "token_type": "Bearer", "expires_in": 3600 } 6.1.4 Token刷新流程 API端點: POST /api/auth/refresh 請求Header: Authorization: Bearer 回應: { "access_token": "new_access_token...", "expires_in": 3600 } 6.2 授權檢查機制 6.2.1 權限裝飾器 from functools import wraps from flask import jsonify from flask_jwt_extended import get_jwt def permission_required(*required_permissions): """ 權限檢查裝飾器 用法: @permission_required('user:create', 'user:update') def create_user(): ... """ def decorator(fn): @wraps(fn) @jwt_required() def wrapper(*args, **kwargs): # 從JWT獲取用戶權限 jwt_data = get_jwt() user_permissions = jwt_data.get('permissions', []) # 檢查是否擁有所需權限 has_permission = all( perm in user_permissions for perm in required_permissions ) if not has_permission: return jsonify({ 'success': False, 'message': '權限不足', 'required_permissions': list(required_permissions) }), 403 return fn(*args, **kwargs) return wrapper return decorator def role_required(*required_roles): """ 角色檢查裝飾器 用法: @role_required('admin', 'super_admin') def admin_function(): ... """ def decorator(fn): @wraps(fn) @jwt_required() def wrapper(*args, **kwargs): jwt_data = get_jwt() user_roles = jwt_data.get('roles', []) has_role = any(role in user_roles for role in required_roles) if not has_role: return jsonify({ 'success': False, 'message': '角色權限不足', 'required_roles': list(required_roles) }), 403 return fn(*args, **kwargs) return wrapper return decorator 6.2.2 資源所有權檢查 def resource_owner_or_permission(permission): """ 檢查是否為資源擁有者或擁有特定權限 用於: 用戶只能修改自己的資料,除非有管理權限 """ def decorator(fn): @wraps(fn) @jwt_required() def wrapper(resource_user_id, *args, **kwargs): current_user_id = get_jwt_identity() jwt_data = get_jwt() user_permissions = jwt_data.get('permissions', []) # 是資源擁有者 OR 有管理權限 is_owner = (current_user_id == resource_user_id) has_permission = permission in user_permissions if not (is_owner or has_permission): return jsonify({ 'success': False, 'message': '無權存取此資源' }), 403 return fn(resource_user_id, *args, **kwargs) return wrapper return decorator # 使用範例 @app.route('/api/users//profile', methods=['PUT']) @resource_owner_or_permission('user:update') def update_user_profile(user_id): """更新用戶資料 - 只能更新自己或有管理權限""" # ... 更新邏輯 pass 6.2.3 部門資料存取控制 def department_access_required(fn): """ 部門資料存取控制 規則: - 一般用戶: 只能看自己部門 - 管理者: 可看自己管理的部門 - 超級管理員: 可看所有部門 """ @wraps(fn) @jwt_required() def wrapper(*args, **kwargs): jwt_data = get_jwt() user_roles = jwt_data.get('roles', []) user_department = jwt_data.get('department') # 從請求參數獲取要存取的部門 request_department = request.args.get('department') or request.json.get('department') # 超級管理員: 全部通過 if 'super_admin' in user_roles: return fn(*args, **kwargs) # 管理者: 檢查是否為其管理的部門 if 'admin' in user_roles: managed_departments = get_managed_departments(get_jwt_identity()) if request_department in managed_departments: return fn(*args, **kwargs) # 一般用戶: 只能存取自己部門 if request_department == user_department: return fn(*args, **kwargs) return jsonify({ 'success': False, 'message': '無權存取其他部門資料' }), 403 return wrapper 6.3 角色權限配置 6.3.1 角色定義與權限矩陣 功能模組 權限 一般使用者 管理者 超級管理員 用戶管理 查看用戶清單 ? ? (部門內) ? (全部) 建立用戶 ? ? ? 更新用戶 ? (自己) ? (部門內) ? (全部) 刪除用戶 ? ? ? 管理角色 ? ? ? 能力評估 建立評估 ? ? ? 查看評估 ? (自己) ? (部門內) ? (全部) 更新評估 ? (自己) ? (部門內) ? (全部) 刪除評估 ? ? (部門內) ? STAR回饋 建立回饋 ? ? ? 查看回饋 ? (自己) ? (部門內) ? (全部) 排名系統 查看個人排名 ? ? ? 查看部門排名 ? (部門內) ? (部門內) ? (全部) 查看全公司排名 ? ? ? 手動計算排名 ? ? ? 報表匯出 匯出個人資料 ? ? ? 匯出部門資料 ? ? ? 匯出全公司資料 ? ? ? 系統管理 查看系統日誌 ? ? ? 系統設定 ? ? ? 資料備份 ? ? ? 6.3.2 權限初始化腳本 def initialize_roles_and_permissions(): """初始化角色與權限""" # 1. 建立權限 permissions_data = [ # 用戶管理 ('user:create', '建立用戶', 'user', 'create'), ('user:read', '查看用戶', 'user', 'read'), ('user:update', '更新用戶', 'user', 'update'), ('user:delete', '刪除用戶', 'user', 'delete'), ('user:manage_roles', '管理角色', 'user', 'manage_roles'), # 評估管理 ('assessment:create', '建立評估', 'assessment', 'create'), ('assessment:read', '查看評估', 'assessment', 'read'), ('assessment:read_all', '查看所有評估', 'assessment', 'read_all'), ('assessment:update', '更新評估', 'assessment', 'update'), ('assessment:delete', '刪除評估', 'assessment', 'delete'), # STAR回饋 ('feedback:create', '建立回饋', 'feedback', 'create'), ('feedback:read', '查看回饋', 'feedback', 'read'), ('feedback:read_all', '查看所有回饋', 'feedback', 'read_all'), # 排名 ('ranking:read', '查看排名', 'ranking', 'read'), ('ranking:read_department', '查看部門排名', 'ranking', 'read_department'), ('ranking:read_all', '查看所有排名', 'ranking', 'read_all'), ('ranking:calculate', '計算排名', 'ranking', 'calculate'), # 報表 ('report:export', '匯出報表', 'report', 'export'), ('report:export_all', '匯出所有報表', 'report', 'export_all'), # 系統 ('system:config', '系統設定', 'system', 'config'), ('system:logs', '查看日誌', 'system', 'logs'), ('system:backup', '資料備份', 'system', 'backup'), ] permissions = {} for name, display_name, resource, action in permissions_data: perm = Permission( name=name, display_name=display_name, resource=resource, action=action ) db.session.add(perm) permissions[name] = perm db.session.flush() # 2. 建立角色 roles_data = [ ('super_admin', '超級管理員', '系統最高權限', 100), ('admin', '管理者', '部門管理權限', 50), ('user', '一般使用者', '基本使用權限', 10), ] roles = {} for name, display_name, description, level in roles_data: role = Role( name=name, display_name=display_name, description=description, level=level ) db.session.add(role) roles[name] = role db.session.flush() # 3. 分配權限給角色 # 超級管理員: 所有權限 for perm in permissions.values(): roles['super_admin'].permissions.append(perm) # 管理者: 部門管理權限 admin_perms = [ 'user:read', 'user:update', 'assessment:create', 'assessment:read', 'assessment:read_all', 'assessment:update', 'assessment:delete', 'feedback:create', 'feedback:read', 'feedback:read_all', 'ranking:read', 'ranking:read_department', 'ranking:read_all', 'report:export' ] for perm_name in admin_perms: if perm_name in permissions: roles['admin'].permissions.append(permissions[perm_name]) # 一般使用者: 基本權限 user_perms = [ 'assessment:read', 'feedback:read', 'ranking:read' ] for perm_name in user_perms: if perm_name in permissions: roles['user'].permissions.append(permissions[perm_name]) db.session.commit() print("? 角色與權限初始化完成") 6.4 權限管理後台 6.4.1 用戶管理介面 頁面路由: /admin/users 權限要求: super_admin ┌─────────────────────────────────────────────────────────┐ │ ?? 用戶管理 │ ├─────────────────────────────────────────────────────────┤ │ [+ 新增用戶] ?? 搜尋: [_______] │ │ 篩選: [所有部門▼] [所有角色▼] [狀態▼] │ ├─────────────────────────────────────────────────────────┤ │ ? 用戶名 姓名 部門 職位 角色 狀態 操作 │ │ ───────────────────────────────────────────────────── │ │ □ zhangsan 張三 技術部 工程師 user ? [???]│ │ □ lisi 李四 業務部 經理 admin ? [???]│ │ □ wangwu 王五 技術部 資深 admin ? [???]│ │ □ zhaoliu 趙六 人資部 專員 user ? [???]│ │ ... │ ├─────────────────────────────────────────────────────────┤ │ 批次操作: [啟用] [停用] [刪除] │ │ [1] [2] [3] ... 下一頁 → │ └─────────────────────────────────────────────────────────┘ 用戶編輯對話框: ┌─────────────────────────────────────┐ │ 編輯用戶: 張三 │ ├─────────────────────────────────────┤ │ 用戶名: zhangsan │ │ 郵箱: zhangsan@company.com │ │ 姓名: 張三 │ │ 部門: [技術部▼] │ │ 職位: [工程師▼] │ │ 員工ID: EMP001 │ │ │ │ 角色: □ 超級管理員 │ │ ? 管理者 │ │ ? 一般使用者 │ │ │ │ 狀態: ● 啟用 ○ 停用 │ │ │ │ [重設密碼] │ │ │ │ [取消] [儲存] │ └─────────────────────────────────────┘ 6.4.2 角色管理介面 頁面路由: /admin/roles 權限要求: super_admin ┌─────────────────────────────────────────────────────────┐ │ ?? 角色管理 │ ├─────────────────────────────────────────────────────────┤ │ [+ 新增角色] │ ├─────────────────────────────────────────────────────────┤ │ 角色名稱 顯示名稱 層級 狀態 操作 │ │ ───────────────────────────────────────────────────── │ │ super_admin 超級管理員 100 ? [???權限] │ │ admin 管理者 50 ? [???權限] │ │ user 一般使用者 10 ? [???權限] │ │ ... │ └─────────────────────────────────────────────────────────┘ 權限配置對話框: ┌─────────────────────────────────────────┐ │ 配置角色權限: 管理者 │ ├─────────────────────────────────────────┤ │ ?? 用戶管理 │ │ □ user:create 建立用戶 │ │ ? user:read 查看用戶 │ │ ? user:update 更新用戶 │ │ □ user:delete 刪除用戶 │ │ □ user:manage_roles 管理角色 │ │ │ │ ?? 評估管理 │ │ ? assessment:create │ │ ? assessment:read │ │ ? assessment:read_all │ │ ? assessment:update │ │ ? assessment:delete │ │ │ │ ?? STAR回饋 │ │ ? feedback:create │ │ ? feedback:read │ │ ? feedback:read_all │ │ │ │ [全選] [反選] [重設] │ │ │ │ [取消] [儲存] │ └─────────────────────────────────────────┘ 6.4.3 操作日誌介面 頁面路由: /admin/audit-logs 權限要求: system:logs ┌─────────────────────────────────────────────────────────┐ │ ?? 操作日誌 │ ├─────────────────────────────────────────────────────────┤ │ 時間範圍: [2025-10-01] 至 [2025-10-15] │ │ 篩選: [所有用戶▼] [所有動作▼] [所有狀態▼] │ │ ?? 搜尋: [____________] │ ├─────────────────────────────────────────────────────────┤ │ 時間 用戶 動作 資源 狀態 IP │ │ ───────────────────────────────────────────────────── │ │ 10/15 14:23 張三 login - ? 192... │ │ 10/15 14:25 張三 create assessment ? 192... │ │ 10/15 14:30 李四 update user ? 192... │ │ 10/15 14:31 王五 delete feedback ? 192... │ │ ... │ ├─────────────────────────────────────────────────────────┤ │ [匯出日誌] [清除舊日誌] │ │ [1] [2] [3] ... 下一頁 → │ └─────────────────────────────────────────────────────────┘ 日誌詳情: ┌─────────────────────────────────────┐ │ 操作詳情 │ ├─────────────────────────────────────┤ │ 時間: 2025-10-15 14:25:33 │ │ 用戶: 張三 (zhangsan) │ │ 動作: 建立評估 │ │ 資源: assessment #123 │ │ 狀態: ? 成功 │ │ IP位址: 192.168.1.100 │ │ 瀏覽器: Chrome 118.0 │ │ │ │ 請求資料: │ │ { │ │ "department": "技術部", │ │ "position": "工程師", │ │ "assessment_data": {...} │ │ } │ │ │ │ 回應資料: │ │ { │ │ "success": true, │ │ "assessment_id": 123 │ │ } │ │ │ │ [關閉] │ └─────────────────────────────────────┘ 6.5 安全增強措施 6.5.1 登入安全 # 登入失敗計數 class LoginAttempt(db.Model): id = db.Column(db.Integer, primary_key=True) username = db.Column(db.String(50)) ip_address = db.Column(db.String(45)) attempt_time = db.Column(db.DateTime, default=datetime.utcnow) success = db.Column(db.Boolean, default=False) def check_login_attempts(username, ip_address): """ 檢查登入失敗次數 規則: - 5次失敗: 鎖定15分鐘 - 10次失敗: 鎖定1小時 - 20次失敗: 永久鎖定,需管理員解鎖 """ # 查詢最近15分鐘的失敗次數 recent_attempts = LoginAttempt.query.filter( LoginAttempt.username == username, LoginAttempt.ip_address == ip_address, LoginAttempt.success == False, LoginAttempt.attempt_time >= datetime.utcnow() - timedelta(minutes=15) ).count() if recent_attempts >= 5: return False, "登入失敗次數過多,請15分鐘後再試" return True, None def record_login_attempt(username, ip_address, success): """記錄登入嘗試""" attempt = LoginAttempt( username=username, ip_address=ip_address, success=success ) db.session.add(attempt) db.session.commit() 6.5.2 Session管理 # Session資料表 class UserSession(db.Model): id = db.Column(db.Integer, primary_key=True) user_id = db.Column(db.Integer, db.ForeignKey('users.id')) token_jti = db.Column(db.String(36), unique=True) # JWT ID ip_address = db.Column(db.String(45)) user_agent = db.Column(db.String(255)) created_at = db.Column(db.DateTime, default=datetime.utcnow) expires_at = db.Column(db.DateTime) is_active = db.Column(db.Boolean, default=True) def revoke_all_user_sessions(user_id): """撤銷用戶所有Session (例如: 密碼變更時)""" UserSession.query.filter_by(user_id=user_id).update( {'is_active': False} ) db.session.commit() # JWT Token撤銷檢查 @jwt.token_in_blocklist_loader def check_if_token_revoked(jwt_header, jwt_payload): jti = jwt_payload['jti'] session = UserSession.query.filter_by(token_jti=jti).first() if not session: return True # Token不存在,視為已撤銷 return not session.is_active # 檢查Session是否啟用 6.5.3 敏感操作二次驗證 def require_password_confirmation(fn): """ 敏感操作需要二次密碼驗證 適用於: - 刪除用戶 - 修改權限 - 匯出敏感資料 """ @wraps(fn) @jwt_required() def wrapper(*args, **kwargs): # 檢查是否提供密碼 password = request.json.get('confirm_password') if not password: return jsonify({ 'success': False, 'message': '此操作需要密碼確認' }), 401 # 驗證密碼 user_id = get_jwt_identity() user = User.query.get(user_id) if not user or not bcrypt.check_password_hash(user.password_hash, password): return jsonify({ 'success': False, 'message': '密碼驗證失敗' }), 401 return fn(*args, **kwargs) return wrapper # 使用範例 @app.route('/api/users/', methods=['DELETE']) @permission_required('user:delete') @require_password_confirmation def delete_user(user_id): """刪除用戶 - 需要密碼確認""" # ... 刪除邏輯 pass 7. API端點設計(更新與新增) 7.1 認證相關API ?? POST /api/auth/register 註冊新用戶 請求Body: { "username": "zhangsan", "email": "zhangsan@company.com", "password": "SecurePass123!", "full_name": "張三", "department": "技術部", "position": "工程師", "employee_id": "EMP001" } 回應 (201): { "success": true, "message": "註冊成功", "user_id": 1 } POST /api/auth/login 用戶登入 請求Body: { "username": "zhangsan", "password": "SecurePass123!" } 回應 (200): { "success": true, "access_token": "eyJhbGci...", "refresh_token": "eyJhbGci...", "user": { "id": 1, "username": "zhangsan", "full_name": "張三", "department": "技術部", "roles": ["user"], "permissions": ["assessment:read", "feedback:read"] } } POST /api/auth/logout 用戶登出 請求Header: Authorization: Bearer 回應 (200): { "success": true, "message": "登出成功" } POST /api/auth/refresh 刷新Token 請求Header: Authorization: Bearer 回應 (200): { "access_token": "new_token..." } GET /api/auth/me 獲取當前用戶資訊 回應 (200): { "id": 1, "username": "zhangsan", "email": "zhangsan@company.com", "full_name": "張三", "department": "技術部", "position": "工程師", "roles": ["user"], "permissions": ["assessment:read", "feedback:read"], "last_login_at": "2025-10-15T14:23:00Z" } 7.2 儀表板相關API ?? GET /api/dashboard 獲取個人儀表板資料 權限: 已登入用戶 回應 (200): { "user": { "id": 1, "full_name": "張三", "department": "技術部", "position": "工程師" }, "points": { "total_points": 450, "monthly_points": 80, "monthly_change_percent": 21.5 }, "department_ranking": { "rank": 3, "total": 15, "percentile": 80.0, "better_than_count": 12 }, "total_ranking": { "rank": 15, "total": 120, "percentile": 87.5, "tier": "top_20" }, "badges": [ { "icon": "??", "name": "頂尖表現", "description": "位於前10%" } ], "recent_feedbacks": [ { "id": 123, "evaluator_name": "王主管", "score": 5, "points_earned": 50, "feedback_date": "2025-10-14", "result_preview": "專案交付優秀,超越預期..." } ], "points_trend": [ {"month": "2025-05", "points": 320}, {"month": "2025-06", "points": 350}, {"month": "2025-07", "points": 380}, {"month": "2025-08", "points": 410}, {"month": "2025-09", "points": 420}, {"month": "2025-10", "points": 450} ] } GET /api/leaderboard 獲取排行榜資料 權限: 已登入用戶 請求參數: * scope: company | department (預設: company) * period: total | monthly | yearly (預設: total) * department: 部門名稱 (當scope=department時) * year: 年份 (當period=monthly/yearly時) * month: 月份 (當period=monthly時) * page: 頁碼 (預設: 1) * per_page: 每頁筆數 (預設: 50) 回應 (200): { "scope": "company", "period": "total", "rankings": [ { "rank": 1, "employee_name": "王五", "department": "技術部", "position": "資深工程師", "total_points": 580, "percentile": 98.0, "badges": ["??", "??", "??"], "rank_change": 0, "is_current_user": false }, { "rank": 2, "employee_name": "李四", "department": "業務部", "position": "業務經理", "total_points": 520, "percentile": 96.0, "badges": ["??", "??", "?"], "rank_change": 1, "is_current_user": false }, { "rank": 3, "employee_name": "張三", "department": "技術部", "position": "工程師", "total_points": 450, "percentile": 94.0, "badges": ["??", "??"], "rank_change": -1, "is_current_user": true } ], "total": 120, "page": 1, "per_page": 50, "pages": 3 } 7.3 用戶管理API ?? GET /api/admin/users 獲取用戶清單 權限: user:read 請求參數: * page: 頁碼 * per_page: 每頁筆數 * department: 部門篩選 * role: 角色篩選 * is_active: 狀態篩選 * search: 搜尋關鍵字 回應 (200): { "users": [ { "id": 1, "username": "zhangsan", "full_name": "張三", "email": "zhangsan@company.com", "department": "技術部", "position": "工程師", "roles": ["user"], "is_active": true, "last_login_at": "2025-10-15T14:23:00Z", "created_at": "2025-01-01T00:00:00Z" } ], "total": 50, "page": 1, "per_page": 20 } POST /api/admin/users 建立新用戶 權限: user:create PUT /api/admin/users/:user_id 更新用戶資訊 權限: user:update 或資源擁有者 DELETE /api/admin/users/:user_id 刪除用戶 權限: user:delete 需要: 密碼確認 PUT /api/admin/users/:user_id/roles 更新用戶角色 權限: user:manage_roles 請求Body: { "role_ids": [1, 2], "confirm_password": "password" } 7.4 角色權限管理API ?? GET /api/admin/roles 獲取角色清單 權限: super_admin POST /api/admin/roles 建立新角色 權限: super_admin PUT /api/admin/roles/:role_id/permissions 更新角色權限 權限: super_admin 請求Body: { "permission_ids": [1, 2, 3, 5, 8], "confirm_password": "password" } 7.5 操作日誌API ?? GET /api/admin/audit-logs 獲取操作日誌 權限: system:logs 請求參數: * start_date: 開始日期 * end_date: 結束日期 * user_id: 用戶ID篩選 * action: 動作篩選 * status: 狀態篩選 回應 (200): { "logs": [ { "id": 1, "user_id": 1, "username": "zhangsan", "action": "login", "resource_type": null, "resource_id": null, "status": "success", "ip_address": "192.168.1.100", "created_at": "2025-10-15T14:23:00Z" } ], "total": 1000, "page": 1, "per_page": 50 } 8. 前端頁面更新 8.1 新增頁面清單 頁面路由 頁面名稱 權限要求 說明 /login 登入頁面 公開 用戶登入 /register 註冊頁面 公開 新用戶註冊(可選關閉) /dashboard 個人儀表板 已登入 積分、排名、趨勢 /leaderboard 競賽排行榜 已登入 全公司/部門排名 /profile 個人資料 已登入 查看/編輯個人資料 /admin/users 用戶管理 user:read 用戶清單與管理 /admin/roles 角色管理 super_admin 角色與權限配置 /admin/logs 操作日誌 system:logs 系統操作記錄 8.2 導航選單更新 9. 部署與環境配置更新 9.1 環境變數更新 .env 範例: # 資料庫設定 DB_HOST=localhost DB_PORT=3306 DB_NAME=partner_alignment DB_USER=db_user DB_PASSWORD=strong_password # Flask設定 FLASK_ENV=production FLASK_DEBUG=False FLASK_HOST=127.0.0.1 FLASK_PORT=5000 # 安全設定 SECRET_KEY=your_64_char_random_secret_key_here JWT_SECRET_KEY=your_64_char_jwt_secret_key_here JWT_ACCESS_TOKEN_EXPIRES=3600 # 1小時 JWT_REFRESH_TOKEN_EXPIRES=604800 # 7天 # CORS設定 CORS_ORIGINS=https://your-domain.com # 郵件設定 (用於通知) MAIL_SERVER=smtp.gmail.com MAIL_PORT=587 MAIL_USE_TLS=True MAIL_USERNAME=your_email@company.com MAIL_PASSWORD=your_email_password # 系統設定 ENABLE_REGISTRATION=False # 是否開放註冊 DEFAULT_ROLE=user # 新用戶預設角色 SESSION_TIMEOUT=3600 # Session過期時間(秒) 9.2 初始化腳本 init_system.py: """系統初始化腳本""" from app import create_app, db from app.models import User, Role, Permission from flask_bcrypt import Bcrypt def init_system(): """初始化系統""" app = create_app() bcrypt = Bcrypt(app) with app.app_context(): print("?? 開始初始化系統...") # 1. 建立資料庫表格 print("?? 建立資 料庫表格...") db.create_all() print("? 資料庫表格建立完成") # 2. 初始化角色與權限 print("?? 初始化角色與權限...") initialize_roles_and_permissions() print("? 角色與權限初始化完成") # 3. 建立預設管理員帳號 print("?? 建立預設管理員...") admin_exists = User.query.filter_by(username='admin').first() if not admin_exists: admin_password = 'Admin@123456' # 建議首次登入後立即修改 admin_user = User( username='admin', email='admin@company.com', password_hash=bcrypt.generate_password_hash(admin_password).decode('utf-8'), full_name='系統管理員', department='系統管理', position='超級管理員', employee_id='ADMIN001', is_active=True ) # 分配超級管理員角色 super_admin_role = Role.query.filter_by(name='super_admin').first() admin_user.roles.append(super_admin_role) db.session.add(admin_user) db.session.commit() print("? 預設管理員建立完成") print(f" 用戶名: admin") print(f" 密碼: {admin_password}") print(" ?? 請立即登入並修改密碼!") else: print("?? 管理員帳號已存在,跳過建立") # 4. 初始化預設能力項目 print("?? 初始化能力項目...") init_capabilities() print("? 能力項目初始化完成") # 5. 建立測試資料(可選) if app.config.get('TESTING'): print("?? 建立測試資料...") create_test_data() print("? 測試資料建立完成") print("?? 系統初始化完成!") print("\n下一步:") print("1. 使用 admin/Admin@123456 登入系統") print("2. 立即修改管理員密碼") print("3. 建立其他管理者與用戶帳號") print("4. 開始使用系統") def initialize_roles_and_permissions(): """初始化角色與權限(如前面定義)""" # ... (參考前面 6.3.2 的實作) pass def init_capabilities(): """初始化能力項目""" from app.models import Capability capabilities_data = [ { "name": "程式設計與開發", "l1_description": "能撰寫基本程式碼,完成簡單功能開發", "l2_description": "能獨立開發中等複雜度功能,遵循編碼規範與最佳實踐", "l3_description": "能設計系統架構,優化程式效能,處理複雜技術問題", "l4_description": "能指導團隊技術方向,制定開發標準,進行技術評審", "l5_description": "能規劃技術策略,引領技術創新,影響組織技術方向" }, { "name": "系統分析與設計", "l1_description": "能理解基本需求,參與需求討論", "l2_description": "能獨立進行需求分析,設計簡單系統", "l3_description": "能處理複雜需求,設計可擴展的系統架構", "l4_description": "能主導大型專案設計,指導團隊架構決策", "l5_description": "能制定企業級架構標準,推動組織架構演進" }, { "name": "專案管理", "l1_description": "能執行分配的任務,按時回報進度", "l2_description": "能獨立管理小型專案,協調資源與時程", "l3_description": "能處理複雜專案,解決跨部門協作問題,控制風險", "l4_description": "能領導多個專案,培養專案經理,優化管理流程", "l5_description": "能制定專案管理策略,建立PMO體系,推動組織成熟度" }, { "name": "技術領導與指導", "l1_description": "能學習新技術,分享基本經驗", "l2_description": "能指導新人,協助團隊成員解決問題", "l3_description": "能主導技術分享,培訓團隊成員,提升團隊能力", "l4_description": "能建立技術團隊,制定培訓計畫,發展人才梯隊", "l5_description": "能建立技術文化,影響組織學習氛圍,培養技術領導者" } ] for cap_data in capabilities_data: existing = Capability.query.filter_by(name=cap_data['name']).first() if not existing: capability = Capability(**cap_data) db.session.add(capability) db.session.commit() def create_test_data(): """建立測試資料(開發/測試環境用)""" from app.models import User, EmployeePoints, StarFeedback import random from datetime import datetime, timedelta bcrypt = Bcrypt() # 建立測試用戶 departments = ['技術部', '業務部', '人資部', '財務部'] positions = ['工程師', '資深工程師', '專員', '資深專員', '經理'] test_users = [] for i in range(1, 21): # 建立20個測試用戶 username = f'user{i:02d}' user = User( username=username, email=f'{username}@company.com', password_hash=bcrypt.generate_password_hash('Test@1234').decode('utf-8'), full_name=f'測試員工{i:02d}', department=random.choice(departments), position=random.choice(positions), employee_id=f'TEST{i:03d}', is_active=True ) # 分配角色 if i <= 2: # 前2個為管理者 admin_role = Role.query.filter_by(name='admin').first() user.roles.append(admin_role) user_role = Role.query.filter_by(name='user').first() user.roles.append(user_role) db.session.add(user) test_users.append(user) db.session.commit() # 建立測試回饋與積分 for user in test_users: # 建立員工積分記錄 emp_points = EmployeePoints( user_id=user.id, employee_name=user.full_name, department=user.department, position=user.position, total_points=0, monthly_points=0 ) db.session.add(emp_points) # 隨機建立3-8筆回饋 num_feedbacks = random.randint(3, 8) for j in range(num_feedbacks): evaluator = random.choice(test_users) score = random.randint(3, 5) points = score * 10 feedback_date = datetime.now() - timedelta(days=random.randint(1, 90)) feedback = StarFeedback( evaluator_user_id=evaluator.id, evaluator_name=evaluator.full_name, evaluatee_user_id=user.id, evaluatee_name=user.full_name, evaluatee_department=user.department, evaluatee_position=user.position, situation=f"測試情境描述{j+1}: 專案中遇到技術挑戰需要解決", task=f"測試任務說明{j+1}: 需要在限定時間內完成特定目標", action=f"測試行動描述{j+1}: 採取具體步驟進行問題分析與解決", result=f"測試結果說明{j+1}: 成功達成目標並獲得正面回饋", score=score, points_earned=points, feedback_date=feedback_date.date() ) db.session.add(feedback) # 累加積分 emp_points.total_points += points emp_points.monthly_points += points db.session.commit() # 更新排名 from app.services import update_all_rankings update_all_rankings() if name == 'main': init_system() ### 9.3 資料庫遷移腳本 **migrations/add_auth_tables.sql**: ```sql -- 用戶表 CREATE TABLE IF NOT EXISTS users ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) NOT NULL UNIQUE, email VARCHAR(100) NOT NULL UNIQUE, password_hash VARCHAR(255) NOT NULL, full_name VARCHAR(100) NOT NULL, department VARCHAR(100) NOT NULL, position VARCHAR(100) NOT NULL, employee_id VARCHAR(50) UNIQUE, is_active BOOLEAN DEFAULT TRUE, last_login_at DATETIME NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_username (username), INDEX idx_email (email), INDEX idx_department (department), INDEX idx_employee_id (employee_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- 角色表 CREATE TABLE IF NOT EXISTS roles ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50) NOT NULL UNIQUE, display_name VARCHAR(100) NOT NULL, description TEXT, level INT NOT NULL, is_active BOOLEAN DEFAULT TRUE, created_at DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- 權限表 CREATE TABLE IF NOT EXISTS permissions ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL UNIQUE, display_name VARCHAR(100) NOT NULL, resource VARCHAR(50) NOT NULL, action VARCHAR(50) NOT NULL, description TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_resource (resource), INDEX idx_action (action) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- 角色權限關聯表 CREATE TABLE IF NOT EXISTS role_permissions ( id INT AUTO_INCREMENT PRIMARY KEY, role_id INT NOT NULL, permission_id INT NOT NULL, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE, FOREIGN KEY (permission_id) REFERENCES permissions(id) ON DELETE CASCADE, UNIQUE KEY unique_role_permission (role_id, permission_id), INDEX idx_role_id (role_id), INDEX idx_permission_id (permission_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- 用戶角色關聯表 CREATE TABLE IF NOT EXISTS user_roles ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, role_id INT NOT NULL, assigned_by INT, assigned_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (role_id) REFERENCES roles(id) ON DELETE CASCADE, FOREIGN KEY (assigned_by) REFERENCES users(id) ON DELETE SET NULL, UNIQUE KEY unique_user_role (user_id, role_id), INDEX idx_user_id (user_id), INDEX idx_role_id (role_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- 操作日誌表 CREATE TABLE IF NOT EXISTS audit_logs ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT, action VARCHAR(100) NOT NULL, resource_type VARCHAR(50) NOT NULL, resource_id INT, details JSON, ip_address VARCHAR(45), user_agent VARCHAR(255), status ENUM('success', 'failed') DEFAULT 'success', error_message TEXT, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL, INDEX idx_user_id (user_id), INDEX idx_action (action), INDEX idx_created_at (created_at), INDEX idx_resource (resource_type, resource_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -- 更新 employee_points 表,新增排名欄位 ALTER TABLE employee_points ADD COLUMN user_id INT AFTER id, ADD COLUMN department_rank INT, ADD COLUMN department_percentile DECIMAL(5,2), ADD COLUMN total_rank INT, ADD COLUMN total_percentile DECIMAL(5,2), ADD FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE SET NULL, ADD INDEX idx_user_id (user_id), ADD INDEX idx_department_rank (department, department_rank), ADD INDEX idx_total_rank (total_rank); -- 更新 star_feedbacks 表,新增用戶ID關聯 ALTER TABLE star_feedbacks ADD COLUMN evaluator_user_id INT AFTER id, ADD COLUMN evaluatee_user_id INT AFTER evaluator_name, ADD FOREIGN KEY (evaluator_user_id) REFERENCES users(id) ON DELETE SET NULL, ADD FOREIGN KEY (evaluatee_user_id) REFERENCES users(id) ON DELETE SET NULL, ADD INDEX idx_evaluator_user_id (evaluator_user_id), ADD INDEX idx_evaluatee_user_id (evaluatee_user_id); 10. 非功能需求(更新) 10.1 效能需求 指標 目標值 說明 頁面載入時間 < 2秒 包含儀表板圖表渲染 API回應時間 < 500ms 95百分位數 並發使用者 100+ 同時在線使用者數 資料庫查詢 < 100ms 單次查詢時間 檔案匯出 < 5秒 1000筆資料以內 排名計算 < 30秒 更新所有員工排名 儀表板載入 < 1.5秒 包含圖表與統計 登入驗證 < 200ms JWT驗證時間 效能優化策略(新增): * 排名資料快取(Redis) * 儀表板資料預計算 * 圖表資料增量載入 * Token驗證本地快取 * 分散式Session管理 10.2 安全需求(強化) 10.2.1 認證安全 ? 已實作: * Bcrypt密碼加密(成本因子12) * JWT Token認證 * Refresh Token機制 * Session管理與撤銷 * 登入失敗鎖定機制 * IP白名單(可選) 10.2.2 授權安全 ? 已實作: * 基於角色的存取控制(RBAC) * 細粒度權限檢查 * 資源所有權驗證 * 部門資料隔離 * 敏感操作二次驗證 10.2.3 資料安全 ? 已實作: * SQL注入防護(參數化查詢) * XSS防護(輸入清理) * CSRF防護(Token驗證) * 敏感資料加密 * 操作日誌完整記錄 10.2.4 通訊安全 ? 已實作: * HTTPS強制使用 * 安全標頭設定 * CORS跨域限制 * Token傳輸加密 10.3 可用性需求 目標: 系統可用性 ? 99.5% 容錯機制(強化): * 資料庫主從複製 * Redis故障轉移 * 優雅降級策略 * 健康檢查端點 * 自動重啟機制 監控指標: * 服務可用性 * API回應時間 * 資料庫連線數 * 記憶體使用率 * 錯誤率統計 10.4 可維護性需求 程式碼品質(強化): * 模組化架構設計 * 依賴注入模式 * 單元測試覆蓋率 ? 80% * 整合測試覆蓋核心流程 * 程式碼審查流程 文件完整性: * API文件(Swagger/OpenAPI) * 架構設計文件 * 部署運維手冊 * 故障排除指南 * 用戶使用手冊 10.5 擴展性需求 水平擴展能力: * 無狀態API設計 * Session外部儲存(Redis) * 資料庫讀寫分離 * 負載平衡支援 * 微服務化準備 資料增長預估: 資料類型 預估增長 保留政策 用戶資料 200人/年 永久保留 評估記錄 5000筆/年 永久保留 STAR回饋 10000筆/年 永久保留 操作日誌 100萬筆/年 保留2年 排名記錄 2400筆/年 保留5年 11. 測試策略(更新) 11.1 新增測試類型 11.1.1 認證授權測試 class TestAuthentication: """認證功能測試""" def test_login_with_valid_credentials(self): """測試: 正確帳密登入成功""" response = self.client.post('/api/auth/login', json={ 'username': 'testuser', 'password': 'Test@1234' }) assert response.status_code == 200 data = response.get_json() assert 'access_token' in data assert 'refresh_token' in data def test_login_with_invalid_password(self): """測試: 錯誤密碼登入失敗""" response = self.client.post('/api/auth/login', json={ 'username': 'testuser', 'password': 'WrongPassword' }) assert response.status_code == 401 def test_access_protected_route_without_token(self): """測試: 未提供Token存取受保護路由""" response = self.client.get('/api/dashboard') assert response.status_code == 401 def test_access_protected_route_with_valid_token(self): """測試: 有效Token存取受保護路由""" token = self.get_auth_token() response = self.client.get( '/api/dashboard', headers={'Authorization': f'Bearer {token}'} ) assert response.status_code == 200 class TestAuthorization: """授權功能測試""" def test_user_cannot_access_admin_route(self): """測試: 一般用戶無法存取管理員路由""" user_token = self.get_user_token() response = self.client.get( '/api/admin/users', headers={'Authorization': f'Bearer {user_token}'} ) assert response.status_code == 403 def test_admin_can_access_department_data(self): """測試: 管理者可存取部門資料""" admin_token = self.get_admin_token() response = self.client.get( '/api/assessments?department=技術部', headers={'Authorization': f'Bearer {admin_token}'} ) assert response.status_code == 200 def test_user_cannot_access_other_department_data(self): """測試: 一般用戶無法存取其他部門資料""" user_token = self.get_user_token(department='技術部') response = self.client.get( '/api/assessments?department=業務部', headers={'Authorization': f'Bearer {user_token}'} ) assert response.status_code == 403 11.1.2 儀表板功能測試 class TestDashboard: """儀表板功能測試""" def test_get_dashboard_data(self): """測試: 獲取儀表板資料""" token = self.get_auth_token() response = self.client.get( '/api/dashboard', headers={'Authorization': f'Bearer {token}'} ) assert response.status_code == 200 data = response.get_json() # 驗證資料結構 assert 'user' in data assert 'points' in data assert 'department_ranking' in data assert 'total_ranking' in data assert 'badges' in data assert 'recent_feedbacks' in data def test_dashboard_ranking_calculation(self): """測試: 儀表板排名計算正確性""" # 建立測試資料 self.create_test_employees_with_points([ ('員工A', 100), ('員工B', 80), ('測試用戶', 60), ('員工C', 40) ]) token = self.get_auth_token() response = self.client.get( '/api/dashboard', headers={'Authorization': f'Bearer {token}'} ) data = response.get_json() ranking = data['department_ranking'] assert ranking['rank'] == 3 assert ranking['total'] == 4 assert ranking['percentile'] == 25.0 # 勝過1人 / 4人 11.1.3 排行榜測試 class TestLeaderboard: """排行榜功能測試""" def test_get_company_leaderboard(self): """測試: 獲取全公司排行榜""" token = self.get_auth_token() response = self.client.get( '/api/leaderboard?scope=company', headers={'Authorization': f'Bearer {token}'} ) assert response.status_code == 200 data = response.get_json() assert 'rankings' in data assert len(data['rankings']) > 0 # 驗證排序 rankings = data['rankings'] for i in range(len(rankings) - 1): assert rankings[i]['total_points'] >= rankings[i+1]['total_points'] def test_leaderboard_with_ties(self): """測試: 排行榜並列排名處理""" # 建立並列積分的員工 self.create_test_employees_with_points([ ('員工A', 100), ('員工B', 80), ('員工C', 80), # 並列 ('員工D', 60) ]) token = self.get_auth_token() response = self.client.get( '/api/leaderboard', headers={'Authorization': f'Bearer {token}'} ) data = response.get_json() rankings = data['rankings'] assert rankings[0]['rank'] == 1 assert rankings[1]['rank'] == 2 assert rankings[2]['rank'] == 2 # 並列第2 assert rankings[3]['rank'] == 4 # 跳號 11.2 安全測試 class TestSecurity: """安全功能測試""" def test_sql_injection_prevention(self): """測試: SQL注入防護""" malicious_input = "admin' OR '1'='1" response = self.client.post('/api/auth/login', json={ 'username': malicious_input, 'password': 'test' }) assert response.status_code == 401 # 不應該成功登入 def test_xss_prevention(self): """測試: XSS防護""" xss_script = "" token = self.get_admin_token() response = self.client.post( '/api/assessments', headers={'Authorization': f'Bearer {token}'}, json={ 'department': xss_script, 'position': '工程師', 'assessment_data': {} } ) # 應該被拒絕或清理 assert response.status_code in [400, 422] def test_password_strength_validation(self): """測試: 密碼強度驗證""" weak_passwords = [ 'short', # 太短 'alllowercase', # 沒有大寫 'ALLUPPERCASE', # 沒有小寫 'NoNumbers!', # 沒有數字 'NoSpecial123' # 沒有特殊符號 ] for weak_pwd in weak_passwords: response = self.client.post('/api/auth/register', json={ 'username': 'testuser', 'email': 'test@test.com', 'password': weak_pwd, 'full_name': '測試', 'department': '技術部', 'position': '工程師' }) assert response.status_code == 400 def test_rate_limiting(self): """測試: 登入失敗次數限制""" for i in range(6): # 嘗試6次 response = self.client.post('/api/auth/login', json={ 'username': 'testuser', 'password': 'wrong_password' }) # 第6次應該被鎖定 assert response.status_code == 429 # Too Many Requests 12. 監控與維運 12.1 監控指標 12.1.1 系統監控 # 健康檢查端點 @app.route('/health') def health_check(): """系統健康檢查""" checks = { 'status': 'healthy', 'timestamp': datetime.utcnow().isoformat(), 'checks': {} } # 資料庫連線檢查 try: db.session.execute('SELECT 1') checks['checks']['database'] = 'ok' except Exception as e: checks['status'] = 'unhealthy' checks['checks']['database'] = f'error: {str(e)}' # Redis連線檢查(如有使用) try: redis_client.ping() checks['checks']['redis'] = 'ok' except Exception as e: checks['checks']['redis'] = f'error: {str(e)}' # 磁碟空間檢查 import shutil disk_usage = shutil.disk_usage('/') disk_percent = (disk_usage.used / disk_usage.total) * 100 if disk_percent > 90: checks['status'] = 'warning' checks['checks']['disk'] = f'high usage: {disk_percent:.1f}%' else: checks['checks']['disk'] = f'ok: {disk_percent:.1f}%' status_code = 200 if checks['status'] == 'healthy' else 503 return jsonify(checks), status_code # 系統指標端點 @app.route('/metrics') @permission_required('system:logs') def system_metrics(): """系統指標""" import psutil return jsonify({ 'cpu_percent': psutil.cpu_percent(), 'memory_percent': psutil.virtual_memory().percent, 'disk_percent': psutil.disk_usage('/').percent, 'active_users': get_active_user_count(), 'api_calls_today': get_api_call_count_today(), 'average_response_time': get_average_response_time() }) 12.1.2 業務監控 # 業務指標端點 @app.route('/api/admin/statistics') @permission_required('system:logs') def business_statistics(): """業務統計資料""" from datetime import datetime, timedelta today = datetime.now().date() week_ago = today - timedelta(days=7) month_ago = today - timedelta(days=30) return jsonify({ # 用戶統計 'users': { 'total': User.query.filter_by(is_active=True).count(), 'new_this_week': User.query.filter( User.created_at >= week_ago ).count(), 'active_today': get_active_users_today() }, # 評估統計 'assessments': { 'total': Assessment.query.count(), 'this_month': Assessment.query.filter( Assessment.created_at >= month_ago ).count() }, # 回饋統計 'feedbacks': { 'total': StarFeedback.query.count(), 'this_month': StarFeedback.query.filter( StarFeedback.feedback_date >= month_ago ).count(), 'average_score': db.session.query( func.avg(StarFeedback.score) ).scalar() }, # 排名統計 'rankings': { 'top_performer': get_top_performer(), 'most_improved': get_most_improved_employee() } }) 12.2 日誌管理 # 日誌配置 import logging from logging.handlers import RotatingFileHandler def setup_logging(app): """設定日誌""" # 應用程式日誌 if not app.debug: file_handler = RotatingFileHandler( 'logs/app.log', maxBytes=10485760, # 10MB 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) app.logger.setLevel(logging.INFO) app.logger.info('系統啟動') # 安全日誌 security_handler = RotatingFileHandler( 'logs/security.log', maxBytes=10485760, backupCount=10 ) security_handler.setLevel(logging.WARNING) security_logger = logging.getLogger('security') security_logger.addHandler(security_handler) return app 12.3 備份策略 #!/bin/bash # backup.sh - 資料庫備份腳本 # 設定 DB_HOST="localhost" DB_NAME="partner_alignment" DB_USER="backup_user" DB_PASS="backup_password" BACKUP_DIR="/backup/mysql" DATE=$(date +%Y%m%d_%H%M%S) RETENTION_DAYS=30 # 建立備份目錄 mkdir -p $BACKUP_DIR # 備份資料庫 echo "開始備份資料庫..." mysqldump -h $DB_HOST -u $DB_USER -p$DB_PASS $DB_NAME \ | gzip > $BACKUP_DIR/backup_$DATE.sql.gz # 檢查備份結果 if [ $? -eq 0 ]; then echo "? 備份成功: backup_$DATE.sql.gz" # 刪除舊備份 find $BACKUP_DIR -name "backup_*.sql.gz" -mtime +$RETENTION_DAYS -delete echo "? 清理超過${RETENTION_DAYS}天的舊備份" else echo "? 備份失敗" exit 1 fi # 上傳至雲端儲存(可選) # aws s3 cp $BACKUP_DIR/backup_$DATE.sql.gz s3://your-bucket/backups/ echo "備份完成" Crontab設定: # 每天凌晨2點執行備份 0 2 * * * /path/to/backup.sh >> /var/log/backup.log 2>&1 13. 故障排除指南 13.1 常見問題 問題1: 無法登入系統 症狀: 登入時顯示「用戶名或密碼錯誤」 可能原因: 1. 密碼錯誤 2. 帳號被停用 3. 登入失敗次數過多被鎖定 解決方案: -- 檢查帳號狀態 SELECT id, username, is_active, last_login_at FROM users WHERE username = 'username'; -- 重設密碼(管理員操作) UPDATE users SET password_hash = '$bcrypt_hash' WHERE username = 'username'; -- 清除登入失敗記錄 DELETE FROM login_attempts WHERE username = 'username' AND attempt_time < NOW() - INTERVAL 1 HOUR; -- 啟用帳號 UPDATE users SET is_active = TRUE WHERE username = 'username'; 問題2: 排名顯示不正確 症狀: 儀表板排名與實際積分不符 可能原因: 1. 排名未及時更新 2. 積分計算錯誤 3. 快取問題 解決方案: # 手動觸發排名更新 from app.services import update_all_rankings update_all_rankings() # 清除排名快取 redis_client.delete('rankings:*') # 重新計算所有積分 from app.services import recalculate_all_points recalculate_all_points() 問題3: 系統效能緩慢 症狀: API回應時間過長,頁面載入緩慢 診斷步驟: # 1. 檢查慢查詢 SELECT * FROM mysql.slow_log ORDER BY query_time DESC LIMIT 10; # 2. 檢查資料庫連線數 SHOW PROCESSLIST; # 3. 檢查系統資源 # CPU、記憶體、磁碟I/O # 4. 檢查應用程式日誌 tail -f logs/app.log | grep ERROR 優化措施: * 新增資料庫索引 * 啟用查詢快取 * 增加伺服器資源 * 優化慢查詢 13.2 緊急處理流程 系統故障發生 ↓ 1. 確認影響範圍 - 部分功能異常 vs 系統完全無法存取 - 影響用戶數量 ↓ 2. 啟動應急措施 - 切換至備用系統(如有) - 顯示維護公告 - 通知相關人員 ↓ 3. 問題診斷 - 查看錯誤日誌 - 檢查系統資源 - 確認最近變更 ↓ 4. 問題修復 - 回滾最近變更 - 修復程式碼錯誤 - 恢復資料庫 ↓ 5. 驗證測試 - 功能測試 - 效能測試 - 安全檢查 ↓ 6. 恢復服務 - 逐步開放存取 - 監控系統狀態 - 記錄事件報告 ↓ 7. 事後檢討 - 分析根本原因 - 改進預防措施 - 更新文件 14. 版本更新計畫 14.1 v2.1 規劃(短期) 預計時程: 1-2個月 功能清單: * [ ] 行動裝置專用介面優化 * [ ] 推播通知系統 * [ ] 成就系統擴充(更多徽章) * [ ] 社交功能(點讚、評論) * [ ] 匯出PDF報表 * [ ] 多語言支援(英文) 14.2 v3.0 規劃(中期) 預計時程: 3-6個月 功能清單: * [ ] AI智能推薦(能力發展建議) * [ ] 360度回饋系統 * [ ] 職涯發展路徑規劃 * [ ] 整合HR系統(如有) * [ ] 數據分析儀表板(管理層) * [ ] 自訂KPI指標 14.3 v4.0 規劃(長期) 預計時程: 6-12個月 功能清單: * [ ] 微服務架構重構 * [ ] 機器學習預測模型 * [ ] 區塊鏈認證(能力證書) * [ ] 開放API平台 * [ ] 第三方整合(Slack、Teams) * [ ] 雲端部署方案 15. 附錄 15.1 術語表(更新) 術語 定義 能力對齊 將員工能力與組織目標或職位要求進行匹配的過程 STAR框架 Situation-Task-Action-Result,結構化回饋方法 L1-L5 能力等級,從L1(執行者)到L5(策略制定者) 拖拉式介面 使用滑鼠拖拉操作的直觀式使用者介面 ORM Object-Relational Mapping,物件關聯映射 JWT JSON Web Token,用於身份驗證的Token標準 RBAC Role-Based Access Control,基於角色的存取控制 百分比排名 表示勝過多少百分比同事的競賽指標 徽章系統 遊戲化激勵機制,透過成就徽章鼓勵參與 二次驗證 敏感操作需要再次輸入密碼確認 15.2 權限速查表 快速查詢常用權限: # 一般用戶 USER_PERMISSIONS = [ 'assessment:read', # 查看自己的評估 'feedback:read', # 查看自己的回饋 'ranking:read' # 查看排名 ] # 管理者 ADMIN_PERMISSIONS = USER_PERMISSIONS + [ 'user:read', # 查看用戶 'user:update', # 更新用戶(部門內) 'assessment:create', # 建立評估 'assessment:read_all', # 查看所有評估(部門內) 'assessment:update', # 更新評估 'assessment:delete', # 刪除評估 'feedback:create', # 建立回饋 'feedback:read_all', # 查看所有回饋(部門內) 'ranking:read_department', # 查看部門排名 'ranking:read_all', # 查看全公司排名 'report:export' # 匯出報表 ] # 超級管理員 SUPER_ADMIN_PERMISSIONS = ADMIN_PERMISSIONS + [ 'user:create', # 建立用戶 'user:delete', # 刪除用戶 'user:manage_roles', # 管理角色 'ranking:calculate', # 計算排名 'report:export_all', # 匯出所有報表 'system:config', # 系統設定 'system:logs', # 查看日誌 'system:backup' # 資料備份 ] 15.3 API快速參考 認證相關: * POST /api/auth/register - 註冊 * POST /api/auth/login - 登入 * POST /api/auth/logout - 登出 * POST /api/auth/refresh - 刷新Token * GET /api/auth/me - 當前用戶資訊 儀表板相關: * GET /api/dashboard - 個人儀表板 * GET /api/leaderboard - 排行榜 用戶管理: * GET /api/admin/users - 用戶清單 * POST /api/admin/users - 建立用戶 * PUT /api/admin/users/:id - 更新用戶 * DELETE /api/admin/users/:id - 刪除用戶 評估管理: * GET /api/capabilities - 能力項目清單 * POST /api/assessments - 提交評估 * GET /api/assessments - 查詢評估 回饋管理: * POST /api/star-feedbacks - 提交回饋 * GET /api/star-feedbacks - 查詢回饋 排名系統: * GET /api/rankings/total - 總積分排名 * GET /api/rankings/monthly - 月度排名 * POST /api/rankings/calculate - 計算排名 15.4 配置檢查清單(更新) 部署前檢查: □ 已生成安全的 SECRET_KEY 和 JWT_SECRET_KEY □ 已設定強密碼的資料庫帳號 □ 已關閉生產環境 DEBUG 模式 □ 已配置正確的 CORS_ORIGINS □ 已啟用 HTTPS(Let's Encrypt或其他) □ 已設定資料庫備份機制 □ 已建立預設管理員帳號 □ 已初始化角色與權限 □ 已設定定時任務(排名更新) □ 已配置日誌記錄 □ 已設定監控告警 □ 已測試所有核心功能 □ 已準備故障恢復計畫 □ 已通知用戶系統上線 16. 核准簽署 角色 姓名 簽名 日期 專案經理 技術負責人 產品負責人 資訊安全負責人 品質保證 文件結束 版本: 2.0 最後更新: 2025年10月15日 下次審查: 2025年11月15日 ?? 變更摘要 (v1.0 → v2.0) ?? 新增功能 1. 評分競賽系統 o 個人儀表板與即時排名 o 部門百分比排名顯示 o 徽章成就系統 o 積分趨勢圖表 o 競賽排行榜 2. 完整權限管理 o 用戶認證系統(JWT) o 三層角色架構 o 細粒度權限控制 o 用戶管理後台 o 角色權限配置 o 操作日誌追蹤 ?? 架構更新 * 新增6個資料表(users, roles, permissions等) * 更新現有表格(新增用戶關聯) * 新增20+個API端點 * 前端新增5個主要頁面 * 強化安全機制 ?? 效能與安全 * 密碼加密(Bcrypt) * Token認證(JWT) * 登入失敗鎖定 * 二次密碼驗證 * 操作日誌完整記錄 * 健康檢查與監控