feat: HR Portal - Complete Multi-Tenant System with Redis Session Storage

Major Features:
-  Multi-tenant architecture (tenant isolation)
-  Employee CRUD with lifecycle management (onboarding/offboarding)
-  Department tree structure with email domain management
-  Company info management (single-record editing)
-  System functions CRUD (permission management)
-  Email account management (multi-account per employee)
-  Keycloak SSO integration (auth.lab.taipei)
-  Redis session storage (10.1.0.254:6379)
  - Solves Cookie 4KB limitation
  - Cross-system session sharing
  - Sliding expiration (8 hours)
  - Automatic token refresh

Technical Stack:
Backend:
- FastAPI + SQLAlchemy
- PostgreSQL 16 (10.1.0.20:5433)
- Keycloak Admin API integration
- Docker Mailserver integration (SSH)
- Alembic migrations

Frontend:
- Next.js 14 (App Router)
- NextAuth 4 with Keycloak Provider
- Redis session storage (ioredis)
- Tailwind CSS

Infrastructure:
- Redis 7 (10.1.0.254:6379) - Session + Cache
- Keycloak 26.1.0 (auth.lab.taipei)
- Docker Mailserver (10.1.0.254)

Architecture Highlights:
- Session管理由 Keycloak + Redis 統一控制
- 支援多系統 (HR/WebMail/Calendar/Drive/Office) 共享 session
- Token 自動刷新,異質服務整合
- 未來可無縫遷移到雲端

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-23 20:12:43 +08:00
commit 360533393f
386 changed files with 70353 additions and 0 deletions

615
DEVELOPMENT_GUIDE.md Normal file
View File

@@ -0,0 +1,615 @@
# HR Portal v2.0 開發指南
本文件提供 HR Portal 專案的開發規範、最佳實踐和常見操作指南。
---
## 📋 目錄
1. [環境設置](#環境設置)
2. [開發流程](#開發流程)
3. [代碼規範](#代碼規範)
4. [API 開發指南](#api-開發指南)
5. [資料庫操作](#資料庫操作)
6. [測試指南](#測試指南)
7. [常見問題](#常見問題)
---
## 環境設置
### 前置需求
- Python 3.11+
- Node.js 20+
- Docker 24+
- PostgreSQL 16+ (或使用 Docker)
- Git
### 後端環境設置
```bash
# 1. 克隆專案
cd W:\DevOps-Workspace\3.Develop\4.HR_Portal
# 2. 創建 Python 虛擬環境
cd backend
python -m venv venv
# 3. 啟動虛擬環境
# Windows:
venv\Scripts\activate
# Linux/Mac:
source venv/bin/activate
# 4. 安裝依賴
pip install -r requirements.txt
# 5. 配置環境變數
cp .env.example .env
# 編輯 .env 填入實際值
# 6. 啟動資料庫 (Docker)
cd ../database
docker-compose up -d
cd ../backend
# 7. 啟動開發伺服器
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
```
### 資料庫初始化
```bash
# 方式 1: SQLAlchemy 自動創建 (開發環境)
# 啟動 FastAPI 時會自動創建表格
# 方式 2: 手動執行 SQL (生產環境推薦)
cd database
docker exec -i hr-portal-db-test psql -U hr_admin -d hr_portal < schema.sql
# 測試資料庫
docker exec -i hr-portal-db-test psql -U hr_admin -d hr_portal < test_schema.sql
```
### 前端環境設置 (待創建)
```bash
# 待前端專案建立後補充
```
---
## 開發流程
### 1. 創建新功能
#### Step 1: 規劃
1. 閱讀相關設計文件 (`2.專案設計區/4.HR_Portal/`)
2. 確認需求和業務規則
3. 設計 API 端點和資料結構
#### Step 2: 資料庫
1. 更新 Schema (如需要)
2. 創建 SQLAlchemy Model
3. 測試 Model 關聯
#### Step 3: 資料驗證
1. 創建 Pydantic Schemas
2. 定義 Create/Update/Response Schemas
3. 添加驗證規則和範例
#### Step 4: API 開發
1. 創建 API 路由文件
2. 實作端點邏輯
3. 添加錯誤處理
4. 測試 API
#### Step 5: 文檔
1. 更新 API 文檔
2. 添加使用範例
3. 更新 PROGRESS.md
### 2. Git 工作流程
```bash
# 1. 創建功能分支
git checkout -b feature/your-feature-name
# 2. 開發和提交
git add .
git commit -m "feat: add your feature description"
# 3. 推送到遠端
git push origin feature/your-feature-name
# 4. 創建 Pull Request
# 在 GitHub/Gitea 上創建 PR
# 5. Code Review 後合併
git checkout main
git merge feature/your-feature-name
git push origin main
```
### 3. Commit 訊息規範
遵循 [Conventional Commits](https://www.conventionalcommits.org/):
```
<type>(<scope>): <subject>
<body>
<footer>
```
**Type**:
- `feat`: 新功能
- `fix`: Bug 修復
- `docs`: 文檔更新
- `style`: 代碼格式調整
- `refactor`: 重構
- `test`: 測試
- `chore`: 構建/工具相關
**範例**:
```
feat(api): add employee search endpoint
- Add keyword search for employee list
- Support filtering by status
- Add pagination support
Closes #123
```
---
## 代碼規範
### Python 代碼風格
遵循 [PEP 8](https://pep8.org/) 和專案慣例:
```python
# 1. 導入順序
# 標準庫
from datetime import datetime
from typing import List, Optional
# 第三方庫
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
# 本地模組
from app.db.session import get_db
from app.models.employee import Employee
from app.schemas.employee import EmployeeCreate
# 2. 函數命名: snake_case
def get_employee_list():
pass
# 3. 類別命名: PascalCase
class EmployeeService:
pass
# 4. 常數命名: UPPER_CASE
MAX_PAGE_SIZE = 100
# 5. 型別提示
def create_employee(
employee_data: EmployeeCreate,
db: Session = Depends(get_db)
) -> EmployeeResponse:
pass
# 6. Docstring (Google Style)
def get_employee(employee_id: int, db: Session):
"""
獲取員工詳情
Args:
employee_id: 員工 ID
db: 資料庫 Session
Returns:
Employee: 員工物件
Raises:
HTTPException: 員工不存在時拋出 404
"""
pass
```
### 代碼組織
```python
# API 端點結構
@router.get("/", response_model=PaginatedResponse)
def get_employees(
db: Session = Depends(get_db),
pagination: PaginationParams = Depends(get_pagination_params),
# 查詢參數
status_filter: Optional[EmployeeStatus] = Query(None),
search: Optional[str] = Query(None),
):
"""
1. Docstring
2. 查詢構建
3. 分頁處理
4. 返回結果
"""
# 構建查詢
query = db.query(Employee)
if status_filter:
query = query.filter(Employee.status == status_filter)
# 分頁
total = query.count()
offset = (pagination.page - 1) * pagination.page_size
items = query.offset(offset).limit(pagination.page_size).all()
# 返回
return PaginatedResponse(...)
```
---
## API 開發指南
### 1. 創建新的 API 端點
#### 範例: 創建「員工統計」端點
**Step 1: 創建 Schema**
```python
# app/schemas/employee.py
class EmployeeStats(BaseSchema):
total_employees: int
active_employees: int
by_business_unit: Dict[str, int]
```
**Step 2: 實作端點**
```python
# app/api/v1/employees.py
@router.get("/stats", response_model=EmployeeStats)
def get_employee_stats(db: Session = Depends(get_db)):
"""獲取員工統計"""
total = db.query(Employee).count()
active = db.query(Employee).filter(
Employee.status == EmployeeStatus.ACTIVE
).count()
# 按事業部統計
from sqlalchemy import func
by_bu = db.query(
BusinessUnit.name,
func.count(EmployeeIdentity.id)
).join(EmployeeIdentity).group_by(BusinessUnit.name).all()
return EmployeeStats(
total_employees=total,
active_employees=active,
by_business_unit={name: count for name, count in by_bu}
)
```
**Step 3: 測試**
```bash
curl http://localhost:8000/api/v1/employees/stats
```
### 2. 錯誤處理
```python
from fastapi import HTTPException, status
# 404 - 資源不存在
if not employee:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Employee with id {employee_id} not found"
)
# 400 - 請求驗證失敗
if existing:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"Username '{username}' already exists"
)
# 403 - 權限不足
if not has_permission:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Permission denied"
)
```
### 3. 分頁實作
```python
from app.schemas.base import PaginationParams, PaginatedResponse
from app.api.deps import get_pagination_params
@router.get("/", response_model=PaginatedResponse)
def get_items(
db: Session = Depends(get_db),
pagination: PaginationParams = Depends(get_pagination_params),
):
query = db.query(Model)
# 總數
total = query.count()
# 分頁
offset = (pagination.page - 1) * pagination.page_size
items = query.offset(offset).limit(pagination.page_size).all()
# 總頁數
total_pages = (total + pagination.page_size - 1) // pagination.page_size
return PaginatedResponse(
total=total,
page=pagination.page,
page_size=pagination.page_size,
total_pages=total_pages,
items=items
)
```
---
## 資料庫操作
### 1. 查詢範例
```python
# 簡單查詢
employee = db.query(Employee).filter(Employee.id == 1).first()
# 多條件查詢
employees = db.query(Employee).filter(
Employee.status == EmployeeStatus.ACTIVE,
Employee.hire_date >= date(2020, 1, 1)
).all()
# Join 查詢
results = db.query(Employee, EmployeeIdentity).join(
EmployeeIdentity,
Employee.id == EmployeeIdentity.employee_id
).all()
# 搜尋 (LIKE)
search_pattern = f"%{keyword}%"
employees = db.query(Employee).filter(
Employee.legal_name.ilike(search_pattern)
).all()
# 排序
employees = db.query(Employee).order_by(
Employee.hire_date.desc()
).all()
# 分組統計
from sqlalchemy import func
stats = db.query(
Employee.status,
func.count(Employee.id)
).group_by(Employee.status).all()
```
### 2. 創建/更新/刪除
```python
# 創建
employee = Employee(
employee_id="EMP001",
username_base="john.doe",
legal_name="John Doe"
)
db.add(employee)
db.commit()
db.refresh(employee) # 重新載入以獲取自動生成的欄位
# 更新
employee.legal_name = "Jane Doe"
db.commit()
# 批量更新
db.query(Employee).filter(
Employee.status == EmployeeStatus.ACTIVE
).update({"status": EmployeeStatus.INACTIVE})
db.commit()
# 刪除 (實際刪除,慎用!)
db.delete(employee)
db.commit()
# 軟刪除 (推薦)
employee.is_active = False
db.commit()
```
### 3. 事務處理
```python
try:
# 開始事務
employee = Employee(...)
db.add(employee)
db.flush() # 獲取 ID 但不提交
identity = EmployeeIdentity(
employee_id=employee.id,
...
)
db.add(identity)
# 提交事務
db.commit()
except Exception as e:
# 回滾
db.rollback()
raise
```
---
## 測試指南
### 1. 單元測試 (待實作)
```python
# tests/test_employees.py
import pytest
from fastapi.testclient import TestClient
def test_create_employee(client: TestClient, db: Session):
response = client.post(
"/api/v1/employees/",
json={
"username_base": "test.user",
"legal_name": "Test User",
"hire_date": "2020-01-01"
}
)
assert response.status_code == 201
data = response.json()
assert data["username_base"] == "test.user"
def test_get_employee_not_found(client: TestClient):
response = client.get("/api/v1/employees/99999")
assert response.status_code == 404
```
### 2. 整合測試 (待實作)
```python
def test_employee_full_lifecycle(client: TestClient, db: Session):
# 1. 創建員工
# 2. 創建身份
# 3. 創建 NAS
# 4. 查詢驗證
# 5. 更新
# 6. 停用
# 7. 驗證所有資源已停用
pass
```
---
## 常見問題
### Q1: 如何添加新的 API 端點?
1. 在對應的路由文件中添加端點函數
2. 定義 Pydantic Schema (如需要)
3. 實作業務邏輯
4. 測試端點
5. 更新文檔
### Q2: 如何修改資料庫 Schema?
**開發環境**:
1. 修改 `database/schema.sql`
2. 修改對應的 SQLAlchemy Model
3. 重新創建資料庫
**生產環境** (使用 Alembic,待實作):
1. 創建遷移腳本: `alembic revision --autogenerate -m "description"`
2. 檢查遷移腳本
3. 執行遷移: `alembic upgrade head`
### Q3: 如何處理循環導入?
使用字串引用型別:
```python
# 不要這樣
from app.models.employee import Employee
class EmployeeIdentity(Base):
employee: Mapped["Employee"] = relationship(...)
# 應該這樣
class EmployeeIdentity(Base):
employee: Mapped["Employee"] = relationship(...)
```
### Q4: 如何調試 API?
1. **使用 Swagger UI**: http://localhost:8000/docs
2. **查看日誌**: 終端輸出
3. **使用 Python 調試器**:
```python
import pdb; pdb.set_trace()
```
4. **檢查 SQL 查詢**:
```python
# 在 config.py 設置
DATABASE_ECHO = True
```
### Q5: 如何重置資料庫?
```bash
# 停止並刪除容器和資料
cd database
docker-compose down -v
# 重新啟動
docker-compose up -d
# 重新初始化
docker exec -i hr-portal-db-test psql -U hr_admin -d hr_portal < schema.sql
```
---
## 最佳實踐
### 1. API 設計
- ✅ 使用 RESTful 規範
- ✅ 返回正確的 HTTP 狀態碼
- ✅ 提供清晰的錯誤訊息
- ✅ 支援分頁和篩選
- ✅ 使用 Pydantic 驗證請求
### 2. 資料庫
- ✅ 使用外鍵約束
- ✅ 添加索引優化查詢
- ✅ 使用軟刪除
- ✅ 記錄審計日誌
- ✅ 避免 N+1 查詢問題
### 3. 安全
- ✅ 使用 JWT Token 認證
- ✅ 實作權限控制
- ✅ 驗證所有輸入
- ✅ 使用 HTTPS
- ✅ 不在日誌中記錄敏感資訊
### 4. 性能
- ✅ 使用連線池
- ✅ 添加適當的索引
- ✅ 使用分頁
- ✅ 考慮使用快取
- ✅ 優化 SQL 查詢
---
## 相關資源
- [FastAPI 官方文檔](https://fastapi.tiangolo.com/)
- [SQLAlchemy 文檔](https://docs.sqlalchemy.org/)
- [Pydantic 文檔](https://docs.pydantic.dev/)
- [PostgreSQL 文檔](https://www.postgresql.org/docs/)
---
**最後更新**: 2026-02-11
**維護者**: Porsche Chen