Files
FastapiAdmin/backend/app/api/v1/module_platform/email/model.py
T
zhangtao 73f2823692 refactor: 大规模代码整理与功能优化
1. 重构后端API路由、CRUD与模块结构,整合日志管理,移除废弃demo代码
2. 优化前端组件类型定义、样式与路由配置,修复权限判断逻辑
3. 调整默认排序规则、滚动条样式与工具类函数,更新依赖与配置文件
4. 修复多处类型不匹配与默认值问题,完善表单与菜单验证逻辑
2026-06-17 01:56:31 +08:00

152 lines
5.4 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 sqlalchemy import Boolean, DateTime, ForeignKey, Integer, String, Text
from sqlalchemy.orm import Mapped, mapped_column, validates
from app.core.base_model import ModelMixin
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="连接超时(秒)"
)
@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\": \"链接\"}",
)
@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):
"""
邮件发送日志表
记录每次邮件发送的结果,支持重试追踪。
status 覆盖 ModelMixin0=待发送 1=成功 2=失败。
"""
__tablename__: str = "platform_email_log"
__table_args__: dict = {"comment": "邮件发送日志表"}
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, nullable=True, index=True, comment="关联租户 ID(可为空,如平台注册邮件)"
)
sent_time: Mapped[datetime | None] = mapped_column(
DateTime, nullable=True, default=None, comment="实际发送时间"
)