Files
insistence 726883f68a feat: 新增跨时区支持并完善定时任务调度 (#127)
* feat: 新增跨时区支持

* perf: 优化代码

* fix: 修复测试

* perf: 优化调度器日志打印
2026-09-12 17:01:05 +08:00

263 lines
14 KiB
Python

from sqlalchemy import CHAR, BigInteger, Column, Index, Integer, String, Text, UniqueConstraint
from common.mixin import AuditTimeMixin, CreateTimeMixin
from common.types import DbUtcDateTime
from config.database import Base
from utils.time_util import TimezoneUtil
class SysFileInfo(AuditTimeMixin, Base):
"""
文件信息表
"""
__tablename__ = 'sys_file_info'
__create_time_nullable__ = False
__update_time_nullable__ = False
__table_args__ = (
UniqueConstraint(
'storage_type',
'access_type',
'storage_key',
name='uk_sys_file_info_storage_location',
),
Index('idx_sys_file_info_access_status', 'access_type', 'status'),
Index('idx_sys_file_info_owner_status', 'owner_user_id', 'status'),
Index('idx_sys_file_info_dept_status', 'dept_id', 'status'),
Index('idx_sys_file_info_status_deleted_time', 'status', 'deleted_time'),
{'comment': '文件信息表'},
)
file_id = Column(String(36), primary_key=True, nullable=False, comment='文件ID')
original_name = Column(String(255), nullable=False, comment='原始文件名')
stored_name = Column(String(255), nullable=False, comment='存储文件名')
storage_key = Column(String(500), nullable=False, comment='存储相对路径')
storage_type = Column(String(20), nullable=False, server_default='local', comment='存储类型')
access_type = Column(String(20), nullable=False, server_default='public', comment='访问类型')
upload_user_id = Column(BigInteger, nullable=True, comment='上传用户ID')
uploader_access_enabled = Column(
CHAR(1),
nullable=False,
server_default='1',
comment='是否保留上传人访问权限',
)
owner_user_id = Column(BigInteger, nullable=True, comment='所有者用户ID')
dept_id = Column(BigInteger, nullable=True, comment='所属部门ID')
acl_version = Column(Integer, nullable=False, server_default='0', comment='访问控制版本')
business_type = Column(String(50), nullable=True, comment='业务类型')
business_id = Column(String(64), nullable=True, comment='业务ID')
extension = Column(String(20), nullable=False, server_default="''", comment='文件扩展名')
content_type = Column(String(255), nullable=True, comment='内容类型')
file_size = Column(BigInteger, nullable=False, server_default='0', comment='文件大小')
file_hash = Column(String(64), nullable=False, comment='文件SHA-256')
status = Column(String(20), nullable=False, server_default='active', comment='文件状态')
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
update_by = Column(String(64), nullable=True, server_default="''", comment='更新者')
expire_time = Column(DbUtcDateTime(), nullable=True, comment='过期时间')
deleted_time = Column(DbUtcDateTime(), nullable=True, comment='移入回收站时间')
del_flag = Column(CHAR(1), nullable=False, server_default='0', comment='删除标志')
class SysFileReference(CreateTimeMixin, Base):
"""
文件业务引用表
"""
__tablename__ = 'sys_file_reference'
__create_time_nullable__ = False
__table_args__ = (
UniqueConstraint(
'file_id',
'business_type',
'business_id',
name='uk_sys_file_reference_business',
),
Index('idx_sys_file_reference_file', 'file_id'),
Index('idx_sys_file_reference_business', 'business_type', 'business_id'),
{'comment': '文件业务引用表'},
)
reference_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='引用ID')
file_id = Column(String(36), nullable=False, comment='文件ID')
business_type = Column(String(50), nullable=False, comment='业务类型')
business_id = Column(String(64), nullable=False, comment='业务ID')
business_name = Column(String(255), nullable=True, comment='业务名称')
retention_expire_time = Column(DbUtcDateTime(), nullable=True, comment='保留期限到期时间')
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
class SysFileRetentionPolicy(AuditTimeMixin, Base):
"""
文件业务保留策略表
"""
__tablename__ = 'sys_file_retention_policy'
__create_time_nullable__ = False
__update_time_nullable__ = False
__table_args__ = {'comment': '文件业务保留策略表'}
business_type = Column(String(50), primary_key=True, nullable=False, comment='业务类型')
retention_days = Column(Integer, nullable=False, comment='保留天数')
status = Column(CHAR(1), nullable=False, server_default='0', comment='状态(0启用 1停用)')
remark = Column(String(500), nullable=True, comment='备注')
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
update_by = Column(String(64), nullable=True, server_default="''", comment='更新者')
class SysFileRetentionNotice(CreateTimeMixin, Base):
"""
文件保留期限提醒表
"""
__tablename__ = 'sys_file_retention_notice'
__create_time_nullable__ = False
__table_args__ = (
UniqueConstraint(
'file_id',
'notice_type',
'expire_time',
name='uk_sys_file_retention_notice_file_type_time',
),
Index('idx_sys_file_retention_notice_file', 'file_id'),
Index('idx_sys_file_retention_notice_status_time', 'status', 'create_time'),
{'comment': '文件保留期限提醒表'},
)
notice_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='提醒ID')
file_id = Column(String(36), nullable=False, comment='文件ID')
notice_type = Column(String(20), nullable=False, comment='提醒类型')
expire_time = Column(DbUtcDateTime(), nullable=False, comment='文件过期时间')
status = Column(CHAR(1), nullable=False, server_default='0', comment='状态(0未读 1已读 2已失效)')
read_by = Column(String(64), nullable=True, server_default="''", comment='读取者')
read_time = Column(DbUtcDateTime(), nullable=True, comment='读取时间')
class SysFileAcl(CreateTimeMixin, Base):
"""
文件访问控制表
"""
__tablename__ = 'sys_file_acl'
__create_time_nullable__ = False
__table_args__ = (
UniqueConstraint(
'file_id',
'subject_type',
'subject_id',
'permission',
name='uk_sys_file_acl_subject_permission',
),
Index('idx_sys_file_acl_file_status', 'file_id', 'del_flag', 'expire_time'),
Index('idx_sys_file_acl_subject', 'subject_type', 'subject_id'),
{'comment': '文件访问控制表'},
)
acl_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='访问控制ID')
file_id = Column(String(36), nullable=False, comment='文件ID')
subject_type = Column(String(20), nullable=False, comment='主体类型')
subject_id = Column(BigInteger, nullable=False, comment='主体ID')
permission = Column(String(20), nullable=False, server_default='download', comment='权限类型')
effect = Column(String(10), nullable=False, server_default='allow', comment='授权效果')
include_children = Column(CHAR(1), nullable=False, server_default='0', comment='部门是否包含下级')
expire_time = Column(DbUtcDateTime(), nullable=True, comment='授权过期时间')
create_by = Column(String(64), nullable=True, server_default="''", comment='创建者')
del_flag = Column(CHAR(1), nullable=False, server_default='0', comment='删除标志')
class SysFileAccessLog(Base):
"""
文件访问审计表
"""
__tablename__ = 'sys_file_access_log'
__table_args__ = (
Index('idx_sys_file_access_log_file_time', 'file_id', 'access_time'),
Index('idx_sys_file_access_log_actor_time', 'actor_user_id', 'access_time'),
{'comment': '文件访问审计表'},
)
audit_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='审计ID')
file_id = Column(String(36), nullable=False, comment='文件ID')
action = Column(String(20), nullable=False, comment='操作类型')
actor_user_id = Column(BigInteger, nullable=True, comment='操作用户ID')
actor_name = Column(String(64), nullable=True, server_default="''", comment='操作用户名称')
result = Column(String(20), nullable=False, comment='操作结果')
request_id = Column(String(64), nullable=True, server_default="''", comment='请求ID')
trace_id = Column(String(64), nullable=True, server_default="''", comment='链路ID')
ip_address = Column(String(128), nullable=True, server_default="''", comment='客户端地址')
user_agent = Column(String(500), nullable=True, server_default="''", comment='用户代理')
bytes_sent = Column(BigInteger, nullable=False, server_default='0', comment='发送字节数')
error_message = Column(String(500), nullable=True, server_default="''", comment='失败原因')
operation_detail = Column(Text, nullable=True, comment='操作详情')
access_time = Column(DbUtcDateTime(), nullable=False, default=TimezoneUtil.utc_now, comment='访问时间')
class SysFileReconcileRun(Base):
"""
文件存储对账任务表
"""
__tablename__ = 'sys_file_reconcile_run'
__table_args__ = (
UniqueConstraint('lock_name', name='uk_sys_file_reconcile_run_lock'),
Index('idx_sys_file_reconcile_run_status_time', 'status', 'started_time'),
{'comment': '文件存储对账任务表'},
)
run_id = Column(String(36), primary_key=True, nullable=False, comment='任务ID')
trigger_type = Column(String(20), nullable=False, comment='触发类型')
status = Column(String(20), nullable=False, comment='任务状态')
check_hash = Column(CHAR(1), nullable=False, server_default='0', comment='是否校验文件摘要')
lock_name = Column(String(32), nullable=True, comment='运行锁名称')
scanned_file_count = Column(BigInteger, nullable=False, server_default='0', comment='扫描文件记录数')
scanned_storage_count = Column(BigInteger, nullable=False, server_default='0', comment='扫描物理文件数')
issue_count = Column(BigInteger, nullable=False, server_default='0', comment='发现异常数')
new_issue_count = Column(BigInteger, nullable=False, server_default='0', comment='新增或重新出现异常数')
resolved_issue_count = Column(BigInteger, nullable=False, server_default='0', comment='自动恢复异常数')
started_by = Column(String(64), nullable=True, server_default="''", comment='发起人')
started_time = Column(DbUtcDateTime(), nullable=False, default=TimezoneUtil.utc_now, comment='开始时间')
finished_time = Column(DbUtcDateTime(), nullable=True, comment='完成时间')
error_message = Column(Text, nullable=True, comment='失败原因')
class SysFileReconcileIssue(Base):
"""
文件存储对账异常表
"""
__tablename__ = 'sys_file_reconcile_issue'
__table_args__ = (
UniqueConstraint('issue_key', name='uk_sys_file_reconcile_issue_key'),
Index('idx_sys_file_reconcile_issue_status_severity', 'status', 'severity'),
Index('idx_sys_file_reconcile_issue_file', 'file_id'),
Index('idx_sys_file_reconcile_issue_run', 'last_run_id'),
{'comment': '文件存储对账异常表'},
)
issue_id = Column(BigInteger, primary_key=True, nullable=False, autoincrement=True, comment='异常ID')
issue_key = Column(String(64), nullable=False, comment='异常唯一标识')
last_run_id = Column(String(36), nullable=False, comment='最近发现任务ID')
issue_type = Column(String(32), nullable=False, comment='异常类型')
severity = Column(String(10), nullable=False, comment='严重级别')
file_id = Column(String(36), nullable=True, comment='文件ID')
storage_type = Column(String(20), nullable=True, comment='存储类型')
access_type = Column(String(20), nullable=True, comment='访问类型')
expected_root = Column(String(20), nullable=True, comment='预期存储区域')
expected_key = Column(String(500), nullable=True, comment='预期相对路径')
actual_root = Column(String(20), nullable=True, comment='实际存储区域')
actual_key = Column(String(500), nullable=True, comment='实际相对路径')
expected_size = Column(BigInteger, nullable=True, comment='预期文件大小')
actual_size = Column(BigInteger, nullable=True, comment='实际文件大小')
expected_hash = Column(String(64), nullable=True, comment='预期SHA-256')
actual_hash = Column(String(64), nullable=True, comment='实际SHA-256')
status = Column(String(20), nullable=False, server_default='open', comment='处理状态')
detail = Column(Text, nullable=True, comment='异常说明')
occurrence_count = Column(Integer, nullable=False, server_default='1', comment='发现次数')
first_seen_time = Column(DbUtcDateTime(), nullable=False, default=TimezoneUtil.utc_now, comment='首次发现时间')
last_seen_time = Column(DbUtcDateTime(), nullable=False, default=TimezoneUtil.utc_now, comment='最近发现时间')
handle_action = Column(String(32), nullable=True, comment='处理动作')
handle_reason = Column(String(500), nullable=True, comment='处理原因')
handled_by = Column(String(64), nullable=True, comment='处理人')
handled_time = Column(DbUtcDateTime(), nullable=True, comment='处理时间')
quarantine_key = Column(String(500), nullable=True, comment='隔离区相对路径')