Files
hr-portal/backend/app/models/usage_log.py
Porsche Chen 360533393f 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>
2026-02-23 20:12:43 +08:00

57 lines
2.1 KiB
Python

"""
使用量記錄 Model
記錄租戶和用戶的資源使用情況
"""
from datetime import datetime, date
from sqlalchemy import Column, Integer, String, Date, DateTime, Numeric, ForeignKey, UniqueConstraint
from sqlalchemy.orm import relationship
from app.db.base import Base
class UsageLog(Base):
"""使用量記錄表 (每日統計)"""
__tablename__ = "usage_logs"
__table_args__ = (
UniqueConstraint("tenant_id", "user_id", "date", name="uq_usage_tenant_user_date"),
)
id = Column(Integer, primary_key=True, index=True)
tenant_id = Column(Integer, ForeignKey("organizes.id", ondelete="CASCADE"), nullable=False, index=True)
user_id = Column(Integer, ForeignKey("employees.id", ondelete="CASCADE"), nullable=True, index=True)
date = Column(Date, nullable=False, index=True, comment="統計日期")
# 郵件使用量
email_storage_gb = Column(Numeric(10, 2), default=0, nullable=False, comment="郵件儲存 (GB)")
emails_sent = Column(Integer, default=0, nullable=False, comment="發送郵件數")
emails_received = Column(Integer, default=0, nullable=False, comment="接收郵件數")
# 雲端硬碟使用量
drive_storage_gb = Column(Numeric(10, 2), default=0, nullable=False, comment="硬碟儲存 (GB)")
files_uploaded = Column(Integer, default=0, nullable=False, comment="上傳檔案數")
files_downloaded = Column(Integer, default=0, nullable=False, comment="下載檔案數")
# 時間記錄
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
def __repr__(self):
return f"<UsageLog Tenant#{self.tenant_id} User#{self.user_id} {self.date}>"
@property
def total_storage_gb(self) -> float:
"""總儲存使用量 (GB)"""
return float(self.email_storage_gb) + float(self.drive_storage_gb)
@classmethod
def get_or_create(cls, tenant_id: int, user_id: int = None, log_date: date = None):
"""獲取或創建當日記錄"""
if log_date is None:
log_date = date.today()
return cls(
tenant_id=tenant_id,
user_id=user_id,
date=log_date
)