feat: 初始化考培练系统项目
- 从服务器拉取完整代码 - 按框架规范整理项目结构 - 配置 Drone CI 测试环境部署 - 包含后端(FastAPI)、前端(Vue3)、管理端 技术栈: Vue3 + TypeScript + FastAPI + MySQL
This commit is contained in:
171
backend/app/models/user.py
Normal file
171
backend/app/models/user.py
Normal file
@@ -0,0 +1,171 @@
|
||||
"""
|
||||
用户相关数据模型
|
||||
"""
|
||||
|
||||
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})>"
|
||||
Reference in New Issue
Block a user