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>
12 KiB
12 KiB
HR Portal v2.0 開發指南
本文件提供 HR Portal 專案的開發規範、最佳實踐和常見操作指南。
📋 目錄
環境設置
前置需求
- Python 3.11+
- Node.js 20+
- Docker 24+
- PostgreSQL 16+ (或使用 Docker)
- Git
後端環境設置
# 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
資料庫初始化
# 方式 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
前端環境設置 (待創建)
# 待前端專案建立後補充
開發流程
1. 創建新功能
Step 1: 規劃
- 閱讀相關設計文件 (
2.專案設計區/4.HR_Portal/) - 確認需求和業務規則
- 設計 API 端點和資料結構
Step 2: 資料庫
- 更新 Schema (如需要)
- 創建 SQLAlchemy Model
- 測試 Model 關聯
Step 3: 資料驗證
- 創建 Pydantic Schemas
- 定義 Create/Update/Response Schemas
- 添加驗證規則和範例
Step 4: API 開發
- 創建 API 路由文件
- 實作端點邏輯
- 添加錯誤處理
- 測試 API
Step 5: 文檔
- 更新 API 文檔
- 添加使用範例
- 更新 PROGRESS.md
2. Git 工作流程
# 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 訊息規範
<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 和專案慣例:
# 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
代碼組織
# 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
# app/schemas/employee.py
class EmployeeStats(BaseSchema):
total_employees: int
active_employees: int
by_business_unit: Dict[str, int]
Step 2: 實作端點
# 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: 測試
curl http://localhost:8000/api/v1/employees/stats
2. 錯誤處理
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. 分頁實作
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. 查詢範例
# 簡單查詢
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. 創建/更新/刪除
# 創建
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. 事務處理
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. 單元測試 (待實作)
# 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. 整合測試 (待實作)
def test_employee_full_lifecycle(client: TestClient, db: Session):
# 1. 創建員工
# 2. 創建身份
# 3. 創建 NAS
# 4. 查詢驗證
# 5. 更新
# 6. 停用
# 7. 驗證所有資源已停用
pass
常見問題
Q1: 如何添加新的 API 端點?
- 在對應的路由文件中添加端點函數
- 定義 Pydantic Schema (如需要)
- 實作業務邏輯
- 測試端點
- 更新文檔
Q2: 如何修改資料庫 Schema?
開發環境:
- 修改
database/schema.sql - 修改對應的 SQLAlchemy Model
- 重新創建資料庫
生產環境 (使用 Alembic,待實作):
- 創建遷移腳本:
alembic revision --autogenerate -m "description" - 檢查遷移腳本
- 執行遷移:
alembic upgrade head
Q3: 如何處理循環導入?
使用字串引用型別:
# 不要這樣
from app.models.employee import Employee
class EmployeeIdentity(Base):
employee: Mapped["Employee"] = relationship(...)
# 應該這樣
class EmployeeIdentity(Base):
employee: Mapped["Employee"] = relationship(...)
Q4: 如何調試 API?
- 使用 Swagger UI: http://localhost:8000/docs
- 查看日誌: 終端輸出
- 使用 Python 調試器:
import pdb; pdb.set_trace() - 檢查 SQL 查詢:
# 在 config.py 設置 DATABASE_ECHO = True
Q5: 如何重置資料庫?
# 停止並刪除容器和資料
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 查詢
相關資源
最後更新: 2026-02-11 維護者: Porsche Chen