Files
FastapiAdmin/backend/app/api/v1/module_platform/email/model.py
T
zhangtao 7d2367e34e refactor: 统一项目代码风格并修复多处类型与调用问题
本次提交包含多项优化:
1.  移除大量冗余的文件头注释与过时的from __future__导入
2.  将CRUD的list方法统一重命名为get_list保持接口一致
3.  修复前后端状态字段类型不匹配问题,将string类型status改为number
4.  修正前端文案错别字,将"代办事项"修正为标准写法
5.  更新sqlalchemy版本并调整依赖配置
6.  新增缓存工具类替代fastapi-cache2,重构缓存调用逻辑
7.  新增开源授权函生成相关工具与数据库字段支持
8.  为多个业务模块添加防重复提交loading状态
9.  修复邮件模型的外键关联缺失问题
10. 优化pdf生成工具的导入时机与文档注释
2026-06-21 17:34:11 +08:00

111 lines
6.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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="邮件正文 HTMLJinja2 模板)")
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 覆盖 ModelMixin0=待发送 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")