Files
1015_IT_behavior_alignment_V2/partner alignment SDD-2.txt
2025-10-28 15:50:53 +08:00

2668 lines
89 KiB
Plaintext
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.

夥伴對齊系統 - 軟體設計文件 (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 <refresh_token>
回應:
{
"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/<int:user_id>/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/<int:user_id>', 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 <access_token>
回應 (200):
{
"success": true,
"message": "登出成功"
}
POST /api/auth/refresh
刷新Token
請求Header:
Authorization: Bearer <refresh_token>
回應 (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 導航選單更新
<!-- 主導航選單 -->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container-fluid">
<a class="navbar-brand" href="/">夥伴對齊系統</a>
<div class="collapse navbar-collapse">
<ul class="navbar-nav me-auto">
<!-- 一般用戶可見 -->
<li class="nav-item">
<a class="nav-link" href="/dashboard">
?? 儀表板
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/assessment">
?? 能力評估
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/leaderboard">
?? 排行榜
</a>
</li>
<!-- 管理者可見 -->
<li class="nav-item dropdown" v-if="hasPermission('feedback:create')">
<a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown">
?? 管理
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="/star-feedback">STAR回饋</a></li>
<li><a class="dropdown-item" href="/admin">資料管理</a></li>
</ul>
</li>
<!-- 超級管理員可見 -->
<li class="nav-item dropdown" v-if="hasRole('super_admin')">
<a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown">
?? 系統
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item" href="/admin/users">用戶管理</a></li>
<li><a class="dropdown-item" href="/admin/roles">角色管理</a></li>
<li><a class="dropdown-item" href="/admin/logs">操作日誌</a></li>
</ul>
</li>
</ul>
<!-- 用戶選單 -->
<ul class="navbar-nav">
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown">
<img src="/api/users/avatar" class="rounded-circle" width="32">
{{ currentUser.full_name }}
<span class="badge bg-warning">{{ currentUser.total_points }} pts</span>
</a>
<ul class="dropdown-menu dropdown-menu-end">
<li><a class="dropdown-item" href="/profile">?? 個人資料</a></li>
<li><a class="dropdown-item" href="/settings">?? 設定</a></li>
<li><hr class="dropdown-divider"></li>
<li><a class="dropdown-item" href="#" @click="logout">?? 登出</a></li>
</ul>
</li>
</ul>
</div>
</div>
</nav>
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 = "<script>alert('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
□ 已啟用 HTTPSLet'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
* 登入失敗鎖定
* 二次密碼驗證
* 操作日誌完整記錄
* 健康檢查與監控