提交文件
This commit is contained in:
411
.qoder/repowiki/zh/content/后端开发指南/数据库集成.md
Normal file
411
.qoder/repowiki/zh/content/后端开发指南/数据库集成.md
Normal file
@@ -0,0 +1,411 @@
|
||||
# 数据库集成
|
||||
|
||||
<cite>
|
||||
**本文引用的文件**
|
||||
- [backend/app/core/database.py](file://backend/app/core/database.py)
|
||||
- [backend/app/core/config.py](file://backend/app/core/config.py)
|
||||
- [backend/app/core/init_db.py](file://backend/app/core/init_db.py)
|
||||
- [backend/app/models/models.py](file://backend/app/models/models.py)
|
||||
- [backend/app/models/finance.py](file://backend/app/models/finance.py)
|
||||
- [backend/alembic/env.py](file://backend/alembic/env.py)
|
||||
- [backend/alembic/versions/001_initial.py](file://backend/alembic/versions/001_initial.py)
|
||||
- [backend/alembic/versions/002_template.py](file://backend/alembic/versions/002_template.py)
|
||||
- [backend/app/main.py](file://backend/app/main.py)
|
||||
- [backend/init_db.py](file://backend/init_db.py)
|
||||
- [backend/app/services/staff_service.py](file://backend/app/services/staff_service.py)
|
||||
- [backend/app/api/v1/staff.py](file://backend/app/api/v1/staff.py)
|
||||
- [backend/requirements.txt](file://backend/requirements.txt)
|
||||
- [backend/.env.example](file://backend/.env.example)
|
||||
- [backend/app/schemas/schemas.py](file://backend/app/schemas/schemas.py)
|
||||
</cite>
|
||||
|
||||
## 目录
|
||||
1. [简介](#简介)
|
||||
2. [项目结构](#项目结构)
|
||||
3. [核心组件](#核心组件)
|
||||
4. [架构总览](#架构总览)
|
||||
5. [详细组件分析](#详细组件分析)
|
||||
6. [依赖分析](#依赖分析)
|
||||
7. [性能考虑](#性能考虑)
|
||||
8. [故障排查指南](#故障排查指南)
|
||||
9. [结论](#结论)
|
||||
10. [附录](#附录)
|
||||
|
||||
## 简介
|
||||
本指南面向“医院绩效系统”的数据库集成开发,围绕 SQLAlchemy ORM 配置、数据库连接与会话管理、初始化流程、表结构与关系映射、Alembic 迁移与版本管理、事务与并发控制、连接池配置、数据模型设计原则与索引优化、性能与查询优化、缓存策略、备份恢复与迁移测试、以及生产部署注意事项进行系统化说明。内容基于后端仓库的实际实现,确保可操作与可落地。
|
||||
|
||||
## 项目结构
|
||||
后端采用 FastAPI + SQLAlchemy 2.x 异步 ORM 的架构,数据库层位于 app/core,模型定义于 app/models,迁移脚本位于 alembic/versions,应用入口在 app/main.py。核心文件组织如下:
|
||||
- 配置与连接:app/core/config.py、app/core/database.py
|
||||
- 初始化:app/core/init_db.py、init_db.py
|
||||
- 模型:app/models/models.py、app/models/finance.py
|
||||
- 迁移:alembic/env.py、alembic/versions/*.py
|
||||
- API 与服务:app/api/v1/*、app/services/*
|
||||
- 依赖与环境:requirements.txt、.env.example
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "应用层"
|
||||
MAIN["app/main.py"]
|
||||
API["API 路由<br/>app/api/v1/*"]
|
||||
SERVICES["服务层<br/>app/services/*"]
|
||||
end
|
||||
subgraph "数据访问层"
|
||||
CORE_DB["连接与会话<br/>app/core/database.py"]
|
||||
MODELS["模型定义<br/>app/models/models.py<br/>app/models/finance.py"]
|
||||
INIT_DB["初始化脚本<br/>app/core/init_db.py<br/>init_db.py"]
|
||||
end
|
||||
subgraph "迁移与版本"
|
||||
ALEMBIC_ENV["Alembic 环境<br/>alembic/env.py"]
|
||||
ALEMBIC_V1["初始迁移<br/>alembic/versions/001_initial.py"]
|
||||
ALEMBIC_V2["模板迁移<br/>alembic/versions/002_template.py"]
|
||||
end
|
||||
MAIN --> API
|
||||
API --> SERVICES
|
||||
SERVICES --> CORE_DB
|
||||
CORE_DB --> MODELS
|
||||
INIT_DB --> MODELS
|
||||
ALEMBIC_ENV --> ALEMBIC_V1
|
||||
ALEMBIC_ENV --> ALEMBIC_V2
|
||||
```
|
||||
|
||||
图表来源
|
||||
- [backend/app/main.py](file://backend/app/main.py#L15-L77)
|
||||
- [backend/app/core/database.py](file://backend/app/core/database.py#L9-L25)
|
||||
- [backend/app/models/models.py](file://backend/app/models/models.py#L62-L114)
|
||||
- [backend/alembic/env.py](file://backend/alembic/env.py#L18-L18)
|
||||
- [backend/alembic/versions/001_initial.py](file://backend/alembic/versions/001_initial.py#L21-L183)
|
||||
- [backend/alembic/versions/002_template.py](file://backend/alembic/versions/002_template.py#L21-L96)
|
||||
|
||||
章节来源
|
||||
- [backend/app/main.py](file://backend/app/main.py#L15-L77)
|
||||
- [backend/app/core/database.py](file://backend/app/core/database.py#L9-L25)
|
||||
- [backend/app/models/models.py](file://backend/app/models/models.py#L62-L114)
|
||||
- [backend/alembic/env.py](file://backend/alembic/env.py#L18-L18)
|
||||
- [backend/alembic/versions/001_initial.py](file://backend/alembic/versions/001_initial.py#L21-L183)
|
||||
- [backend/alembic/versions/002_template.py](file://backend/alembic/versions/002_template.py#L21-L96)
|
||||
|
||||
## 核心组件
|
||||
- 异步数据库引擎与会话工厂:通过 asyncpg 驱动创建异步引擎,使用 async_sessionmaker 构建会话工厂;提供 get_db 依赖以在请求生命周期内自动提交/回滚/关闭会话。
|
||||
- 配置中心:Settings 提供 DATABASE_URL、连接池大小等配置项,支持从 .env 加载。
|
||||
- 模型基类与实体:Base 作为 DeclarativeBase 子类,统一模型元数据;各业务实体(如 Department、Staff、Assessment 等)定义字段、索引与关系。
|
||||
- 迁移环境:Alembic env.py 使用 async_engine_from_config,支持异步迁移执行。
|
||||
- 初始化脚本:app/core/init_db.py 与 init_db.py 提供示例数据与表初始化能力。
|
||||
|
||||
章节来源
|
||||
- [backend/app/core/database.py](file://backend/app/core/database.py#L9-L39)
|
||||
- [backend/app/core/config.py](file://backend/app/core/config.py#L18-L22)
|
||||
- [backend/app/models/models.py](file://backend/app/models/models.py#L23-L25)
|
||||
- [backend/alembic/env.py](file://backend/alembic/env.py#L42-L53)
|
||||
- [backend/app/core/init_db.py](file://backend/app/core/init_db.py#L103-L115)
|
||||
- [backend/init_db.py](file://backend/init_db.py#L11-L83)
|
||||
|
||||
## 架构总览
|
||||
系统采用“异步 ORM + 迁移驱动”的数据库集成架构,API 层通过依赖注入获取 AsyncSession,服务层执行查询与写入,模型层定义表结构与关系,Alembic 管理数据库版本演进。
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Client as "客户端"
|
||||
participant API as "API 路由<br/>app/api/v1/staff.py"
|
||||
participant Service as "服务层<br/>app/services/staff_service.py"
|
||||
participant DB as "数据库引擎/会话<br/>app/core/database.py"
|
||||
participant Models as "模型定义<br/>app/models/models.py"
|
||||
Client->>API : GET /api/v1/staff
|
||||
API->>DB : 依赖注入 get_db()
|
||||
DB-->>API : AsyncSession
|
||||
API->>Service : 调用 get_list(db, filters...)
|
||||
Service->>DB : 执行 select 查询含索引
|
||||
DB->>Models : 映射到 ORM 实体
|
||||
Models-->>DB : 返回结果
|
||||
DB-->>Service : 结果集
|
||||
Service-->>API : 列表与总数
|
||||
API-->>Client : JSON 响应
|
||||
```
|
||||
|
||||
图表来源
|
||||
- [backend/app/api/v1/staff.py](file://backend/app/api/v1/staff.py#L20-L49)
|
||||
- [backend/app/services/staff_service.py](file://backend/app/services/staff_service.py#L16-L49)
|
||||
- [backend/app/core/database.py](file://backend/app/core/database.py#L28-L39)
|
||||
- [backend/app/models/models.py](file://backend/app/models/models.py#L88-L114)
|
||||
|
||||
## 详细组件分析
|
||||
|
||||
### SQLAlchemy ORM 配置与连接管理
|
||||
- 使用 asyncpg 驱动与 create_async_engine 构建异步引擎,echo 由配置开关控制。
|
||||
- async_sessionmaker(class_=AsyncSession, expire_on_commit=False) 提供会话工厂,避免过早失效导致的懒加载问题。
|
||||
- get_db 依赖在 try/finally 中自动 commit/rollback/close,保证异常安全与资源释放。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start(["进入请求"]) --> GetSession["依赖注入 AsyncSession"]
|
||||
GetSession --> TryExec["执行业务逻辑"]
|
||||
TryExec --> Commit{"是否异常?"}
|
||||
Commit --> |否| DoCommit["commit()"]
|
||||
Commit --> |是| DoRollback["rollback()"]
|
||||
DoCommit --> Close["close()"]
|
||||
DoRollback --> Close
|
||||
Close --> End(["结束请求"])
|
||||
```
|
||||
|
||||
图表来源
|
||||
- [backend/app/core/database.py](file://backend/app/core/database.py#L28-L39)
|
||||
|
||||
章节来源
|
||||
- [backend/app/core/database.py](file://backend/app/core/database.py#L9-L39)
|
||||
|
||||
### 数据库初始化流程
|
||||
- app/core/init_db.py:按顺序创建管理员、科室、指标、员工等初始数据,避免重复插入。
|
||||
- init_db.py:直接创建所有表并填充测试数据,适合快速启动。
|
||||
- 两者均使用异步会话,确保与 ORM 协同一致。
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant CLI as "命令行"
|
||||
participant Init as "初始化脚本<br/>app/core/init_db.py"
|
||||
participant DB as "AsyncSession"
|
||||
participant Models as "模型"
|
||||
CLI->>Init : 运行 main()
|
||||
Init->>DB : 异步会话创建
|
||||
Init->>DB : 检查是否存在数据
|
||||
alt 不存在
|
||||
Init->>DB : 写入默认数据管理员/科室/指标/员工
|
||||
DB->>Models : flush/commit
|
||||
else 已存在
|
||||
Init-->>CLI : 跳过初始化
|
||||
end
|
||||
```
|
||||
|
||||
图表来源
|
||||
- [backend/app/core/init_db.py](file://backend/app/core/init_db.py#L103-L115)
|
||||
- [backend/init_db.py](file://backend/init_db.py#L11-L83)
|
||||
|
||||
章节来源
|
||||
- [backend/app/core/init_db.py](file://backend/app/core/init_db.py#L12-L115)
|
||||
- [backend/init_db.py](file://backend/init_db.py#L11-L83)
|
||||
|
||||
### 表结构定义与关系映射
|
||||
- Department 与 Staff:一对多(部门-员工),Staff 外键关联 Department。
|
||||
- Assessment 与 Staff:一对多(员工-考核记录),Assessment 与 Staff 建立双向关系。
|
||||
- Assessment 与 Indicator:多对多通过 AssessmentDetail 关联,AssessmentDetail 双向映射。
|
||||
- PerformancePlan 与 Department/Staff/User:多层级计划,支持父子计划与审批流。
|
||||
- Indicator 与 IndicatorTemplate:模板与指标的多对多通过 TemplateIndicator 关联。
|
||||
- Finance 模块:DepartmentFinance 记录科室收支,外键关联 Department。
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class Department {
|
||||
+id : int
|
||||
+name : str
|
||||
+code : str
|
||||
+dept_type : Enum
|
||||
+parent_id : int?
|
||||
+level : int
|
||||
+sort_order : int
|
||||
+is_active : bool
|
||||
}
|
||||
class Staff {
|
||||
+id : int
|
||||
+employee_id : str
|
||||
+name : str
|
||||
+department_id : int
|
||||
+position : str
|
||||
+title : str?
|
||||
+base_salary : float
|
||||
+performance_ratio : float
|
||||
+status : Enum
|
||||
}
|
||||
class Assessment {
|
||||
+id : int
|
||||
+staff_id : int
|
||||
+period_year : int
|
||||
+period_month : int
|
||||
+status : Enum
|
||||
}
|
||||
class AssessmentDetail {
|
||||
+id : int
|
||||
+assessment_id : int
|
||||
+indicator_id : int
|
||||
+score : float
|
||||
}
|
||||
class Indicator {
|
||||
+id : int
|
||||
+name : str
|
||||
+code : str
|
||||
+indicator_type : Enum
|
||||
}
|
||||
class PerformancePlan {
|
||||
+id : int
|
||||
+plan_level : Enum
|
||||
+plan_year : int
|
||||
+status : Enum
|
||||
}
|
||||
class IndicatorTemplate {
|
||||
+id : int
|
||||
+template_type : Enum
|
||||
}
|
||||
class TemplateIndicator {
|
||||
+id : int
|
||||
+template_id : int
|
||||
+indicator_id : int
|
||||
}
|
||||
class DepartmentFinance {
|
||||
+id : int
|
||||
+department_id : int
|
||||
+finance_type : Enum
|
||||
+amount : float
|
||||
}
|
||||
Department "1" <-- "many" Staff : "department_id"
|
||||
Staff "1" <-- "many" Assessment : "staff_id"
|
||||
Assessment "1" <-- "many" AssessmentDetail : "assessment_id"
|
||||
Indicator "1" <-- "many" AssessmentDetail : "indicator_id"
|
||||
PerformancePlan "1" <-- "many" TemplateIndicator : "plan_id"
|
||||
Indicator "1" <-- "many" TemplateIndicator : "indicator_id"
|
||||
Department "1" <-- "many" DepartmentFinance : "department_id"
|
||||
```
|
||||
|
||||
图表来源
|
||||
- [backend/app/models/models.py](file://backend/app/models/models.py#L62-L114)
|
||||
- [backend/app/models/models.py](file://backend/app/models/models.py#L149-L203)
|
||||
- [backend/app/models/models.py](file://backend/app/models/models.py#L270-L339)
|
||||
- [backend/app/models/models.py](file://backend/app/models/models.py#L387-L438)
|
||||
- [backend/app/models/finance.py](file://backend/app/models/finance.py#L45-L74)
|
||||
|
||||
章节来源
|
||||
- [backend/app/models/models.py](file://backend/app/models/models.py#L62-L114)
|
||||
- [backend/app/models/models.py](file://backend/app/models/models.py#L149-L203)
|
||||
- [backend/app/models/models.py](file://backend/app/models/models.py#L270-L339)
|
||||
- [backend/app/models/models.py](file://backend/app/models/models.py#L387-L438)
|
||||
- [backend/app/models/finance.py](file://backend/app/models/finance.py#L45-L74)
|
||||
|
||||
### Alembic 迁移脚本与版本管理
|
||||
- env.py:配置 target_metadata 为 Base.metadata,使用 async_engine_from_config 执行异步迁移。
|
||||
- 001_initial.py:创建 departments、staff、indicators、assessments、assessment_details、salary_records、users 等核心表及索引。
|
||||
- 002_template.py:新增 indicator_templates 与 template_indicators 表,并为 indicators 表动态添加若干列(带存在性检查)。
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
A["执行 alembic revision/upgrade"] --> B["env.py 异步引擎连接"]
|
||||
B --> C["读取 target_metadata(Base.metadata)"]
|
||||
C --> D["生成 SQL 并执行"]
|
||||
D --> E["001_initial.py: 创建核心表与索引"]
|
||||
D --> F["002_template.py: 新增模板表与列"]
|
||||
```
|
||||
|
||||
图表来源
|
||||
- [backend/alembic/env.py](file://backend/alembic/env.py#L42-L53)
|
||||
- [backend/alembic/versions/001_initial.py](file://backend/alembic/versions/001_initial.py#L21-L183)
|
||||
- [backend/alembic/versions/002_template.py](file://backend/alembic/versions/002_template.py#L21-L96)
|
||||
|
||||
章节来源
|
||||
- [backend/alembic/env.py](file://backend/alembic/env.py#L18-L66)
|
||||
- [backend/alembic/versions/001_initial.py](file://backend/alembic/versions/001_initial.py#L21-L183)
|
||||
- [backend/alembic/versions/002_template.py](file://backend/alembic/versions/002_template.py#L21-L96)
|
||||
|
||||
### 事务处理与并发控制
|
||||
- get_db 依赖在 try/finally 中确保异常时回滚、最终关闭会话,避免长事务与连接泄漏。
|
||||
- 会话工厂 expire_on_commit=False,减少后续查询中的额外 SELECT。
|
||||
- 建议在高并发场景下结合数据库层面的锁与重试策略(例如在服务层对关键更新加排他锁或幂等写入)。
|
||||
|
||||
章节来源
|
||||
- [backend/app/core/database.py](file://backend/app/core/database.py#L28-L39)
|
||||
|
||||
### 连接池配置
|
||||
- 配置项 DATABASE_POOL_SIZE 与 DATABASE_MAX_OVERFLOW 由 Settings 提供,可在 .env 中覆盖。
|
||||
- 生产环境建议根据并发与数据库资源合理设置池大小,避免连接耗尽或过度占用。
|
||||
|
||||
章节来源
|
||||
- [backend/app/core/config.py](file://backend/app/core/config.py#L18-L22)
|
||||
- [backend/.env.example](file://backend/.env.example#L3-L4)
|
||||
|
||||
### 数据模型设计原则、字段约束与索引优化
|
||||
- 字段约束:使用 CheckConstraint 对数值范围进行约束(如指标权重、财务金额非负)。
|
||||
- 索引优化:为高频过滤与连接字段建立复合索引(如部门类型、员工状态、考核期、工资期等)。
|
||||
- 枚举映射:通过 Enum 与 values_callable 将 Python 枚举映射为字符串存储,保持一致性与可读性。
|
||||
- 关系设计:明确主外键、级联策略(如评估明细的级联删除),避免孤儿数据。
|
||||
|
||||
章节来源
|
||||
- [backend/app/models/models.py](file://backend/app/models/models.py#L143-L146)
|
||||
- [backend/app/models/finance.py](file://backend/app/models/finance.py#L73-L74)
|
||||
- [backend/app/models/models.py](file://backend/app/models/models.py#L82-L85)
|
||||
- [backend/app/models/models.py](file://backend/app/models/models.py#L174-L178)
|
||||
- [backend/app/models/models.py](file://backend/app/models/models.py#L227-L230)
|
||||
- [backend/app/models/models.py](file://backend/app/models/models.py#L334-L338)
|
||||
|
||||
### 查询优化与缓存策略
|
||||
- 查询优化:优先使用索引字段过滤与排序;对关联查询使用 selectinload 或 join,减少 N+1 查询。
|
||||
- 缓存策略:对静态配置与只读数据(如枚举、字典表)使用进程内缓存;对热点统计结果使用 Redis 缓存短期聚合数据。
|
||||
- 分页与总数:使用子查询统计总数,避免 count(*) 的全表扫描。
|
||||
|
||||
章节来源
|
||||
- [backend/app/services/staff_service.py](file://backend/app/services/staff_service.py#L24-L49)
|
||||
|
||||
### 数据备份与恢复
|
||||
- 备份:使用数据库自带工具(如 PostgreSQL 的 pg_dump)定期备份;生产环境建议增量与全量结合。
|
||||
- 恢复:在隔离环境中验证备份可用性;迁移前先在预生产环境演练。
|
||||
|
||||
(本节为通用实践说明)
|
||||
|
||||
### 迁移测试与生产部署注意事项
|
||||
- 迁移测试:在本地与预生产环境执行升级/降级,验证索引、约束与数据完整性。
|
||||
- 生产部署:使用只读迁移(避免破坏性变更);变更前冻结写入或切换到只读模式;回滚预案准备充分。
|
||||
|
||||
(本节为通用实践说明)
|
||||
|
||||
## 依赖分析
|
||||
- FastAPI 应用通过依赖注入获取 AsyncSession,服务层负责业务逻辑与查询组合。
|
||||
- 模型层定义表结构与关系,Alembic 通过 env.py 与版本脚本同步数据库结构。
|
||||
- 配置层集中管理数据库连接与池参数,.env 提供环境变量覆盖。
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
Config["配置<br/>app/core/config.py"] --> Engine["引擎<br/>app/core/database.py"]
|
||||
Engine --> Session["会话<br/>app/core/database.py"]
|
||||
Session --> Services["服务层<br/>app/services/*"]
|
||||
Services --> Models["模型<br/>app/models/*"]
|
||||
AlembicEnv["Alembic 环境<br/>alembic/env.py"] --> Versions["版本脚本<br/>alembic/versions/*"]
|
||||
Versions --> Models
|
||||
```
|
||||
|
||||
图表来源
|
||||
- [backend/app/core/config.py](file://backend/app/core/config.py#L18-L22)
|
||||
- [backend/app/core/database.py](file://backend/app/core/database.py#L9-L25)
|
||||
- [backend/alembic/env.py](file://backend/alembic/env.py#L18-L18)
|
||||
- [backend/alembic/versions/001_initial.py](file://backend/alembic/versions/001_initial.py#L21-L183)
|
||||
|
||||
章节来源
|
||||
- [backend/app/core/config.py](file://backend/app/core/config.py#L18-L22)
|
||||
- [backend/app/core/database.py](file://backend/app/core/database.py#L9-L25)
|
||||
- [backend/alembic/env.py](file://backend/alembic/env.py#L18-L18)
|
||||
- [backend/alembic/versions/001_initial.py](file://backend/alembic/versions/001_initial.py#L21-L183)
|
||||
|
||||
## 性能考虑
|
||||
- 连接池:根据并发与数据库承载能力调整池大小与溢出限制。
|
||||
- 查询:为高频过滤字段建立索引;避免 SELECT *,仅选择必要列。
|
||||
- 事务:缩短事务时间,批量写入使用 flush/commit 合理拆分。
|
||||
- 缓存:对稳定数据与统计结果进行缓存,降低数据库压力。
|
||||
|
||||
(本节提供通用指导)
|
||||
|
||||
## 故障排查指南
|
||||
- 连接失败:检查 DATABASE_URL 与 .env 是否正确;确认数据库服务可达与账号权限。
|
||||
- 迁移异常:查看 Alembic 输出与数据库日志;确认版本脚本无破坏性变更。
|
||||
- 会话异常:确认 get_db 依赖注入是否生效;避免跨请求复用会话对象。
|
||||
- 数据不一致:检查事务边界与异常处理;必要时引入幂等写入与重试。
|
||||
|
||||
章节来源
|
||||
- [backend/.env.example](file://backend/.env.example#L3-L4)
|
||||
- [backend/alembic/env.py](file://backend/alembic/env.py#L42-L53)
|
||||
- [backend/app/core/database.py](file://backend/app/core/database.py#L28-L39)
|
||||
|
||||
## 结论
|
||||
本指南基于现有代码实现了“异步 ORM + 迁移驱动”的数据库集成方案,覆盖连接管理、初始化、模型设计、迁移与版本控制、事务与并发、性能与运维等关键环节。建议在生产环境中进一步完善监控、备份与回滚策略,并持续优化查询与索引以满足业务增长需求。
|
||||
|
||||
## 附录
|
||||
- 依赖清单:见 requirements.txt。
|
||||
- 环境变量示例:见 .env.example。
|
||||
- API 示例:参考 app/api/v1/staff.py 的依赖注入与服务调用。
|
||||
|
||||
章节来源
|
||||
- [backend/requirements.txt](file://backend/requirements.txt#L1-L17)
|
||||
- [backend/.env.example](file://backend/.env.example#L1-L11)
|
||||
- [backend/app/api/v1/staff.py](file://backend/app/api/v1/staff.py#L20-L49)
|
||||
Reference in New Issue
Block a user