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

124
backend/pre_init_check.py Normal file
View File

@@ -0,0 +1,124 @@
"""
初始化前檢查
確保資料庫和 Keycloak 都已準備好
"""
import sys
sys.path.insert(0, '.')
import psycopg2
from app.services.keycloak_service import KeycloakService
print("="*60)
print("HR Portal 初始化前檢查")
print("="*60)
# ============================================
# 1. 檢查資料庫
# ============================================
print("\n[1] 檢查資料庫狀態...")
try:
conn = psycopg2.connect(
host="10.1.0.20",
port=5433,
database="hr_portal",
user="admin",
password="DC1qaz2wsx"
)
cur = conn.cursor()
# 檢查 tenants
cur.execute("SELECT COUNT(*) FROM tenants;")
tenant_count = cur.fetchone()[0]
# 檢查 installation_sessions
cur.execute("SELECT COUNT(*) FROM installation_sessions;")
session_count = cur.fetchone()[0]
# 檢查系統狀態
cur.execute("SELECT current_phase, initialization_completed, is_locked FROM installation_system_status WHERE id = 1;")
system_status = cur.fetchone()
cur.close()
conn.close()
print(f" OK 資料庫連線成功")
print(f" - tenants: {tenant_count}")
print(f" - installation_sessions: {session_count}")
if system_status:
print(f" - 系統狀態: {system_status[0]}")
print(f" - 初始化完成: {system_status[1]}")
print(f" - 系統鎖定: {system_status[2]}")
# 判斷資料庫是否準備好
db_ready = (tenant_count == 0 and session_count == 0 and
system_status and system_status[0] == 'initialization' and
not system_status[1] and not system_status[2])
if db_ready:
print(f" OK 資料庫已準備好進行初始化")
else:
print(f" X 資料庫尚未準備好")
if tenant_count > 0:
print(f" - 需要清理 tenants 表")
if session_count > 0:
print(f" - 需要清理 installation_sessions 表")
if system_status and system_status[0] != 'initialization':
print(f" - 系統狀態應為 'initialization'")
except Exception as e:
print(f" X 資料庫檢查失敗: {e}")
db_ready = False
# ============================================
# 2. 檢查 Keycloak
# ============================================
print("\n[2] 檢查 Keycloak 狀態...")
try:
keycloak = KeycloakService()
if keycloak.admin:
print(f" OK Keycloak Admin 連線成功")
print(f" - Realm: {keycloak.realm_name}")
print(f" - URL: {keycloak.server_url}")
# 檢查用戶
users = keycloak.admin.get_users({})
print(f" - 現有用戶數: {len(users)}")
# 檢查是否有待建立的管理員用戶
admin_users = [u for u in users if 'porsche' in u.get('username', '').lower()]
if admin_users:
print(f" X 發現管理員用戶 (應先刪除):")
for u in admin_users:
print(f" - {u['username']} ({u['email']})")
keycloak_ready = False
else:
print(f" OK 無衝突的管理員用戶")
keycloak_ready = True
else:
print(f" X Keycloak Admin 初始化失敗")
keycloak_ready = False
except Exception as e:
print(f" X Keycloak 檢查失敗: {e}")
keycloak_ready = False
# ============================================
# 3. 總結
# ============================================
print("\n" + "="*60)
if db_ready and keycloak_ready:
print("OK 系統已準備好進行初始化")
print("="*60)
print("\n請前往: http://10.1.0.245:10180/installation/complete")
sys.exit(0)
else:
print("X 系統尚未準備好")
print("="*60)
if not db_ready:
print("\n請執行: python cleanup_database.py")
if not keycloak_ready:
print("\n請刪除 Keycloak 中的衝突用戶")
sys.exit(1)