mirror of
https://github.com/fastapiadmin/FastapiAdmin.git
synced 2026-09-22 13:05:18 +00:00
本次提交包含多项优化: 1. 移除大量冗余的文件头注释与过时的from __future__导入 2. 将CRUD的list方法统一重命名为get_list保持接口一致 3. 修复前后端状态字段类型不匹配问题,将string类型status改为number 4. 修正前端文案错别字,将"代办事项"修正为标准写法 5. 更新sqlalchemy版本并调整依赖配置 6. 新增缓存工具类替代fastapi-cache2,重构缓存调用逻辑 7. 新增开源授权函生成相关工具与数据库字段支持 8. 为多个业务模块添加防重复提交loading状态 9. 修复邮件模型的外键关联缺失问题 10. 优化pdf生成工具的导入时机与文档注释
111 lines
6.2 KiB
Python
111 lines
6.2 KiB
Python
from datetime import datetime
|
||
from typing import TYPE_CHECKING
|
||
|
||
from sqlalchemy import Boolean, DateTime, ForeignKey, Integer, String, Text
|
||
from sqlalchemy.orm import Mapped, mapped_column, relationship, validates
|
||
|
||
from app.core.base_model import ModelMixin, TenantMixin, UserMixin
|
||
|
||
if TYPE_CHECKING:
|
||
from app.api.v1.module_platform.email.model import EmailConfigModel
|
||
|
||
|
||
class EmailConfigModel(ModelMixin):
|
||
"""
|
||
邮件 SMTP 配置表
|
||
|
||
每条记录对应一套 SMTP 配置(平台级,tenant_id=1)。
|
||
通过 is_default 标记当前生效的配置;只能有一条 is_default=True。
|
||
"""
|
||
|
||
__tablename__: str = "platform_email_config"
|
||
__table_args__: dict = {"comment": "邮件 SMTP 配置表"}
|
||
|
||
name: Mapped[str] = mapped_column(String(100), nullable=False, unique=True, comment="配置名称")
|
||
smtp_host: Mapped[str] = mapped_column(String(255), nullable=False, comment="SMTP 服务器地址")
|
||
smtp_port: Mapped[int] = mapped_column(Integer, nullable=False, default=465, comment="SMTP 端口(465=SSL, 587=TLS)")
|
||
smtp_user: Mapped[str] = mapped_column(String(255), nullable=False, comment="SMTP 登录用户名(发件邮箱)")
|
||
smtp_password: Mapped[str] = mapped_column(String(255), nullable=False, comment="SMTP 授权密码(AES 加密存储)")
|
||
from_name: Mapped[str] = mapped_column(String(100), nullable=False, default="FastapiAdmin", comment="发件人显示名")
|
||
use_tls: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True, comment="是否启用 SSL/TLS")
|
||
is_default: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False, comment="是否为默认配置")
|
||
timeout: Mapped[int] = mapped_column(Integer, nullable=False, default=30, comment="连接超时(秒)")
|
||
status: Mapped[int] = mapped_column(Integer, default=0, nullable=False, comment="状态(0:启动 1:停用)", index=True)
|
||
description: Mapped[str | None] = mapped_column(Text, default=None, nullable=True, comment="备注")
|
||
|
||
@validates("smtp_port")
|
||
def validate_port(self, key: str, value: int) -> int:
|
||
if value < 1 or value > 65535:
|
||
raise ValueError("SMTP 端口范围为 1-65535")
|
||
return value
|
||
|
||
@validates("timeout")
|
||
def validate_timeout(self, key: str, value: int) -> int:
|
||
if value < 5 or value > 120:
|
||
raise ValueError("超时时间应在 5-120 秒之间")
|
||
return value
|
||
|
||
|
||
class EmailTemplateModel(ModelMixin):
|
||
"""
|
||
邮件模板表
|
||
|
||
支持变量插值(Jinja2 语法),如 {{ username }}、{{ reset_link }}。
|
||
template_code 是唯一业务键,代码中通过 code 引用,不依赖自增 ID。
|
||
"""
|
||
|
||
__tablename__: str = "platform_email_template"
|
||
__table_args__: dict = {"comment": "邮件模板表"}
|
||
|
||
name: Mapped[str] = mapped_column(String(100), nullable=False, unique=True, comment="模板名称")
|
||
template_code: Mapped[str] = mapped_column(String(100), nullable=False, unique=True, comment="模板编码(业务键)")
|
||
subject: Mapped[str] = mapped_column(String(255), nullable=False, comment="邮件主题(可含变量)")
|
||
body_html: Mapped[str] = mapped_column(Text, nullable=False, comment="邮件正文 HTML(Jinja2 模板)")
|
||
body_text: Mapped[str | None] = mapped_column(Text, nullable=True, default=None, comment="邮件纯文本版本(降级用)")
|
||
variables: Mapped[str | None] = mapped_column(Text, nullable=True, default=None, comment='模板变量说明(JSON 格式,如 {"username": "用户名", "link": "链接"})')
|
||
status: Mapped[int] = mapped_column(Integer, default=0, nullable=False, comment="状态(0:启动 1:停用)", index=True)
|
||
description: Mapped[str | None] = mapped_column(Text, default=None, nullable=True, comment="备注")
|
||
|
||
@validates("template_code")
|
||
def validate_code(self, key: str, value: str) -> str:
|
||
value = value.strip()
|
||
if not value:
|
||
raise ValueError("模板编码不能为空")
|
||
return value
|
||
|
||
|
||
class EmailLogModel(ModelMixin, TenantMixin, UserMixin):
|
||
"""
|
||
邮件发送日志表
|
||
|
||
记录每次邮件发送的结果,支持重试追踪。
|
||
status 覆盖 ModelMixin:0=待发送 1=成功 2=失败。
|
||
tenant_id 为可空:平台主动发送邮件(注册/重置密码等)无租户归属。
|
||
"""
|
||
|
||
__tablename__: str = "platform_email_log"
|
||
__table_args__: dict = {"comment": "邮件发送日志表"}
|
||
__loader_options__: list[str] = ["created_by", "updated_by", "deleted_by", "tenant_by"]
|
||
|
||
config_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("platform_email_config.id", ondelete="SET NULL", onupdate="CASCADE"), nullable=True, index=True, comment="使用的 SMTP 配置 ID")
|
||
template_code: Mapped[str | None] = mapped_column(String(100), nullable=True, comment="模板编码(冗余存储,模板删除后仍可追溯)")
|
||
to_email: Mapped[str] = mapped_column(String(255), nullable=False, index=True, comment="收件人邮箱")
|
||
to_name: Mapped[str | None] = mapped_column(String(100), nullable=True, comment="收件人姓名")
|
||
subject: Mapped[str] = mapped_column(String(255), nullable=False, comment="邮件主题(渲染后)")
|
||
biz_type: Mapped[str] = mapped_column(String(50), nullable=False, default="other", comment="业务类型(register/reset_password/invite/expiry_warning/ticket_reply/other)")
|
||
error_msg: Mapped[str | None] = mapped_column(Text, nullable=True, default=None, comment="失败原因")
|
||
retry_count: Mapped[int] = mapped_column(Integer, nullable=False, default=0, comment="重试次数")
|
||
tenant_id: Mapped[int | None] = mapped_column(
|
||
Integer,
|
||
ForeignKey("platform_tenant.id", ondelete="SET NULL", onupdate="CASCADE"),
|
||
nullable=True,
|
||
index=True,
|
||
comment="关联租户 ID(可为空,如平台注册邮件)",
|
||
)
|
||
sent_time: Mapped[datetime | None] = mapped_column(DateTime, nullable=True, default=None, comment="实际发送时间")
|
||
status: Mapped[int] = mapped_column(Integer, default=0, nullable=False, comment="状态(0:启动 1:停用)", index=True)
|
||
description: Mapped[str | None] = mapped_column(Text, default=None, nullable=True, comment="备注")
|
||
|
||
# 关联关系
|
||
config: Mapped["EmailConfigModel | None"] = relationship("EmailConfigModel", lazy="selectin")
|