mirror of
https://github.com/insistence/RuoYi-Vue3-FastAPI.git
synced 2026-10-14 13:06:46 +00:00
107 lines
3.8 KiB
Python
107 lines
3.8 KiB
Python
import re
|
||
from datetime import date, datetime, time, timezone
|
||
from typing import Annotated, Any
|
||
|
||
from pydantic import AfterValidator, AwareDatetime, BeforeValidator, PlainSerializer
|
||
from sqlalchemy import DateTime
|
||
from sqlalchemy.dialects.mysql import DATETIME
|
||
from sqlalchemy.dialects.postgresql import TIMESTAMP
|
||
from sqlalchemy.engine import Dialect
|
||
from sqlalchemy.types import TypeDecorator, TypeEngine
|
||
|
||
from utils.time_util import TimezoneUtil
|
||
|
||
|
||
def validate_aware_datetime_input(value: Any) -> datetime:
|
||
"""
|
||
校验API输入的真实时刻
|
||
|
||
:param value: 待校验的输入值
|
||
:return: 携带UTC时区的毫秒精度时刻
|
||
"""
|
||
return TimezoneUtil.parse_rfc3339(value)
|
||
|
||
|
||
ApiUtcDateTime = Annotated[
|
||
AwareDatetime,
|
||
BeforeValidator(validate_aware_datetime_input),
|
||
AfterValidator(TimezoneUtil.to_utc_milliseconds),
|
||
PlainSerializer(TimezoneUtil.format_rfc3339, return_type=str, when_used='json'),
|
||
]
|
||
|
||
|
||
def validate_business_time_input(value: Any) -> time:
|
||
"""
|
||
校验不含日期和时区的一天内时间
|
||
|
||
仅接受time对象或HH:mm:ss格式字符串,可含小数秒,不接受时间戳或持续时长。
|
||
|
||
:param value: 待校验的输入值
|
||
:return: 一天内的时间对象
|
||
"""
|
||
if isinstance(value, str) and re.fullmatch(r'(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d{1,6})?', value):
|
||
value = time.fromisoformat(value)
|
||
if not isinstance(value, time) or value.tzinfo is not None:
|
||
raise ValueError('一天内时间必须为 HH:mm:ss,可含小数秒,不能带时区')
|
||
return value
|
||
|
||
|
||
BusinessDate = Annotated[date, BeforeValidator(TimezoneUtil.parse_business_date)]
|
||
BusinessTime = Annotated[time, BeforeValidator(validate_business_time_input)]
|
||
|
||
|
||
class DbUtcDateTime(TypeDecorator[datetime]):
|
||
"""
|
||
SQLAlchemy数据库列的UTC时刻类型。
|
||
|
||
PostgreSQL使用带时区时间戳;MySQL和SQLite在驱动边界保存naive UTC,
|
||
应用层始终只接收和返回aware UTC datetime。
|
||
"""
|
||
|
||
impl = DateTime
|
||
cache_ok = True
|
||
|
||
def load_dialect_impl(self, dialect: Dialect) -> TypeEngine[Any]:
|
||
"""
|
||
获取当前数据库对应的毫秒精度时间类型
|
||
|
||
:param dialect: SQLAlchemy数据库方言
|
||
:return: 数据库实际使用的时间类型
|
||
"""
|
||
if dialect.name == 'postgresql':
|
||
return dialect.type_descriptor(TIMESTAMP(timezone=True, precision=3))
|
||
if dialect.name == 'mysql':
|
||
return dialect.type_descriptor(DATETIME(fsp=3))
|
||
return dialect.type_descriptor(DateTime())
|
||
|
||
def process_bind_param(self, value: datetime | None, dialect: Dialect) -> datetime | None:
|
||
"""
|
||
在写入边界统一UTC时区和毫秒精度
|
||
|
||
:param value: 待写入时刻
|
||
:param dialect: SQLAlchemy数据库方言
|
||
:return: 符合驱动要求的UTC时刻,空值返回None
|
||
"""
|
||
if value is None:
|
||
return None
|
||
if value.tzinfo is None or value.utcoffset() is None:
|
||
raise ValueError('DbUtcDateTime只接受携带时区信息的datetime')
|
||
utc_value = TimezoneUtil.to_utc_milliseconds(value)
|
||
if dialect.name in {'mysql', 'sqlite'}:
|
||
return utc_value.replace(tzinfo=None)
|
||
return utc_value
|
||
|
||
def process_result_value(self, value: datetime | None, dialect: Dialect) -> datetime | None:
|
||
"""
|
||
将数据库读出的时间恢复为带时区UTC时刻
|
||
|
||
:param value: 驱动返回的时间值
|
||
:param dialect: SQLAlchemy数据库方言
|
||
:return: 携带UTC时区的毫秒精度时刻,空值返回None
|
||
"""
|
||
if value is None:
|
||
return None
|
||
if value.tzinfo is None or value.utcoffset() is None:
|
||
value = value.replace(tzinfo=timezone.utc)
|
||
return TimezoneUtil.to_utc_milliseconds(value)
|