- 从服务器拉取完整代码 - 按框架规范整理项目结构 - 配置 Drone CI 测试环境部署 - 包含后端(FastAPI)、前端(Vue3)、管理端 技术栈: Vue3 + TypeScript + FastAPI + MySQL
172 lines
5.3 KiB
Python
172 lines
5.3 KiB
Python
"""
|
||
用户相关数据模型
|
||
"""
|
||
|
||
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})>"
|