feat: 初始化考培练系统项目

- 从服务器拉取完整代码
- 按框架规范整理项目结构
- 配置 Drone CI 测试环境部署
- 包含后端(FastAPI)、前端(Vue3)、管理端

技术栈: Vue3 + TypeScript + FastAPI + MySQL
This commit is contained in:
111
2026-01-24 19:33:28 +08:00
commit 998211c483
1197 changed files with 228429 additions and 0 deletions

171
backend/app/models/user.py Normal file
View 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})>"