Files
012-kaopeilian/backend/app/models/user.py
111 998211c483 feat: 初始化考培练系统项目
- 从服务器拉取完整代码
- 按框架规范整理项目结构
- 配置 Drone CI 测试环境部署
- 包含后端(FastAPI)、前端(Vue3)、管理端

技术栈: Vue3 + TypeScript + FastAPI + MySQL
2026-01-24 19:33:28 +08:00

172 lines
5.3 KiB
Python
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.
"""
用户相关数据模型
"""
from datetime import datetime
from typing import List, Optional
from sqlalchemy import (
Boolean,
Column,
DateTime,
ForeignKey,
Integer,
String,
Table,
Text,
UniqueConstraint,
func,
)
from sqlalchemy.orm import Mapped, mapped_column, relationship
from .base import Base, BaseModel, SoftDeleteMixin
# 用户-团队关联表(用于多对多关系)
user_teams = Table(
"user_teams",
BaseModel.metadata,
Column(
"user_id", Integer, ForeignKey("users.id", ondelete="CASCADE"), primary_key=True
),
Column(
"team_id", Integer, ForeignKey("teams.id", ondelete="CASCADE"), primary_key=True
),
Column("role", String(50), default="member", nullable=False), # member, leader
Column("joined_at", DateTime, server_default=func.now(), nullable=False),
UniqueConstraint("user_id", "team_id", name="uq_user_team"),
)
class UserTeam(Base):
"""用户团队关联模型(用于直接查询关联表)"""
__allow_unmapped__ = True
__table__ = user_teams # 重用已定义的表
# 定义列映射不需要id因为使用复合主键
user_id: Mapped[int]
team_id: Mapped[int]
role: Mapped[str]
joined_at: Mapped[datetime]
def __repr__(self) -> str:
return f"<UserTeam(user_id={self.user_id}, team_id={self.team_id}, role={self.role})>"
class User(BaseModel, SoftDeleteMixin):
"""用户模型"""
__allow_unmapped__ = True
__tablename__ = "users"
# 基础信息
username: Mapped[str] = mapped_column(String(50), unique=True, nullable=False)
email: Mapped[Optional[str]] = mapped_column(String(100), unique=True, nullable=True)
phone: Mapped[Optional[str]] = mapped_column(String(20), unique=True, nullable=True)
hashed_password: Mapped[str] = mapped_column(
"password_hash", String(200), nullable=False
)
# 个人信息
full_name: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
avatar_url: Mapped[Optional[str]] = mapped_column(String(500), nullable=True)
bio: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
# 性别: male/female可扩展
gender: Mapped[Optional[str]] = mapped_column(String(10), nullable=True)
# 学校
school: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
# 专业
major: Mapped[Optional[str]] = mapped_column(String(100), nullable=True)
# 企微员工userid用于SCRM系统对接
wework_userid: Mapped[Optional[str]] = mapped_column(String(64), unique=True, nullable=True, comment="企微员工userid")
# 系统角色admin, manager, trainee
role: Mapped[str] = mapped_column(String(20), default="trainee", nullable=False)
# 账号状态
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
is_verified: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
# 时间记录
last_login_at: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True)
password_changed_at: Mapped[Optional[datetime]] = mapped_column(
DateTime, nullable=True
)
# 关联关系
teams: Mapped[List["Team"]] = relationship(
"Team",
secondary=user_teams,
back_populates="members",
lazy="selectin",
)
exams = relationship("Exam", back_populates="user")
# 岗位关系(通过关联表)
position_memberships = relationship("PositionMember", back_populates="user", cascade="all, delete-orphan")
def __repr__(self) -> str:
return f"<User(id={self.id}, username={self.username}, role={self.role})>"
class Team(BaseModel, SoftDeleteMixin):
"""团队模型"""
__allow_unmapped__ = True
__tablename__ = "teams"
# 基础信息
name: Mapped[str] = mapped_column(String(100), unique=True, nullable=False)
code: Mapped[str] = mapped_column(String(50), unique=True, nullable=False)
description: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
# 团队类型department, project, study_group
team_type: Mapped[str] = mapped_column(
String(50), default="department", nullable=False
)
# 状态
is_active: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
# 团队负责人
leader_id: Mapped[Optional[int]] = mapped_column(
Integer, ForeignKey("users.id", ondelete="SET NULL"), nullable=True
)
# 父团队(支持层级结构)
parent_id: Mapped[Optional[int]] = mapped_column(
Integer, ForeignKey("teams.id", ondelete="CASCADE"), nullable=True
)
# 关联关系
members: Mapped[List["User"]] = relationship(
"User",
secondary=user_teams,
back_populates="teams",
lazy="selectin",
)
leader: Mapped[Optional["User"]] = relationship(
"User",
foreign_keys=[leader_id],
lazy="selectin",
)
parent: Mapped[Optional["Team"]] = relationship(
"Team",
remote_side="Team.id",
foreign_keys=[parent_id],
lazy="selectin",
)
children: Mapped[List["Team"]] = relationship(
"Team",
back_populates="parent",
lazy="selectin",
)
def __repr__(self) -> str:
return f"<Team(id={self.id}, name={self.name}, code={self.code})>"