mirror of
https://github.com/fastapi-practices/fastapi-best-architecture.git
synced 2026-09-21 21:15:13 +00:00
@@ -2,7 +2,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
|
||||
from fastapi import APIRouter, Query
|
||||
from fastapi import APIRouter, Query, File, UploadFile, Form
|
||||
from typing_extensions import Annotated
|
||||
|
||||
from backend.app.common.response.response_schema import response_base
|
||||
@@ -69,3 +69,12 @@ async def task_demo_delete(job_id: Annotated[str, Query(..., description='任务
|
||||
scheduler.remove_job(job_id=job_id)
|
||||
|
||||
return await response_base.success({'msg': 'success'})
|
||||
|
||||
|
||||
@router.post('/files', summary='文件上传')
|
||||
async def create_file(file: bytes = File(), fileb: UploadFile = File(), token: str = Form()):
|
||||
return {
|
||||
'file_size': len(file),
|
||||
'token': token,
|
||||
'fileb_content_type': fileb.content_type,
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ from typing import Any
|
||||
from asgiref.sync import sync_to_async
|
||||
from pydantic import validate_arguments, BaseModel
|
||||
|
||||
from backend.app.core.conf import settings
|
||||
from backend.app.utils.encoders import jsonable_encoder
|
||||
|
||||
_ExcludeData = set[int | str] | dict[int | str, Any]
|
||||
@@ -37,7 +38,7 @@ class ResponseModel(BaseModel):
|
||||
data: Any | None = None
|
||||
|
||||
class Config:
|
||||
json_encoders = {datetime: lambda x: x.strftime('%Y-%m-%d %H:%M:%S')}
|
||||
json_encoders = {datetime: lambda x: x.strftime(settings.DATETIME_FORMAT)}
|
||||
|
||||
|
||||
class ResponseBase:
|
||||
@@ -58,7 +59,7 @@ class ResponseBase:
|
||||
@staticmethod
|
||||
@sync_to_async
|
||||
def __json_encoder(data: Any, exclude: _ExcludeData | None = None, **kwargs):
|
||||
custom_encoder = {datetime: lambda x: x.strftime('%Y-%m-%d %H:%M:%S')}
|
||||
custom_encoder = {datetime: lambda x: x.strftime(settings.DATETIME_FORMAT)}
|
||||
kwargs.update({'custom_encoder': custom_encoder})
|
||||
result = jsonable_encoder(data, exclude=exclude, **kwargs)
|
||||
return result
|
||||
|
||||
@@ -63,6 +63,10 @@ class Settings(BaseSettings):
|
||||
# Limiter
|
||||
LIMITER_REDIS_PREFIX: str = 'fba_limiter'
|
||||
|
||||
# DateTime
|
||||
DATETIME_TIMEZONE: str = 'Asia/Shanghai'
|
||||
DATETIME_FORMAT: str = '%Y-%m-%d %H:%M:%S'
|
||||
|
||||
# MySQL
|
||||
DB_ECHO: bool = False
|
||||
DB_DATABASE: str = 'fba'
|
||||
|
||||
@@ -32,7 +32,6 @@ SCHEMA_ERROR_MSG_TEMPLATES: dict[str, str] = {
|
||||
'type_error.subclass': '预期 {expected_class} 的子类',
|
||||
'type_error.tuple': '值不是有效的元组',
|
||||
'type_error.uuid': '值不是有效的 UUID',
|
||||
|
||||
# Value Errors
|
||||
'value_error.any_str.max_length': '确保此值最多包含 {limit_value} 个字符',
|
||||
'value_error.any_str.min_length': '确保此值至少包含 {limit_value} 个字符',
|
||||
@@ -44,7 +43,7 @@ SCHEMA_ERROR_MSG_TEMPLATES: dict[str, str] = {
|
||||
'value_error.decimal.max_places': '确保小数位数不超过 {decimal_places} 位',
|
||||
'value_error.decimal.not_finite': '值不是有效的小数(Decimal)',
|
||||
'value_error.decimal.whole_digits': '确保小数点前不超过 {whole_digits} 位',
|
||||
'value_error.discriminated_union.invalid_discriminator': '不匹配鉴别器 {discriminator_key!r} 和值 {discriminator_value!r}(允许的值:{allowed_values})',
|
||||
'value_error.discriminated_union.invalid_discriminator': '不匹配鉴别器 {discriminator_key!r} 和值 {discriminator_value!r}(允许的值:{allowed_values})', # noqa: E501
|
||||
'value_error.discriminated_union.missing_discriminator': '鉴别器 {discriminator_key!r} 的值缺失',
|
||||
'value_error.extra': '不允许使用额外字段',
|
||||
'value_error.frozenset.max_items': '确保此值最多包含 {limit_value} 个项目',
|
||||
|
||||
@@ -0,0 +1,224 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
import pytz
|
||||
|
||||
from backend.app.core.conf import settings
|
||||
|
||||
|
||||
class DateTimeUtils:
|
||||
def __init__(self, timezone_str=settings.DATETIME_TIMEZONE):
|
||||
"""
|
||||
初始化函数,设置时区
|
||||
|
||||
:param timezone_str: 时区字符串,默认为 UTC
|
||||
"""
|
||||
self.timezone_str = timezone_str
|
||||
self.timezone = pytz.timezone(self.timezone_str)
|
||||
|
||||
def get_current_time(self) -> datetime.datetime:
|
||||
"""
|
||||
获取当前时间
|
||||
|
||||
:return: 当前时间的 datetime 对象
|
||||
"""
|
||||
return datetime.datetime.now(self.timezone)
|
||||
|
||||
@staticmethod
|
||||
def get_current_timestamp() -> int:
|
||||
"""
|
||||
获取当前时间戳 (秒)
|
||||
|
||||
:return: 当前时间戳 (秒)
|
||||
"""
|
||||
return int(datetime.datetime.now().timestamp())
|
||||
|
||||
@staticmethod
|
||||
def get_current_milliseconds() -> int:
|
||||
"""
|
||||
获取当前时间戳 (毫秒)
|
||||
|
||||
:return: 当前时间戳 (毫秒)
|
||||
"""
|
||||
return int(datetime.datetime.now().timestamp() * 1000)
|
||||
|
||||
def timestamp_to_datetime(self, timestamp: int) -> datetime.datetime:
|
||||
"""
|
||||
时间戳转 datetime 对象
|
||||
|
||||
:param timestamp: 时间戳 (秒)
|
||||
:return: datetime 对象
|
||||
"""
|
||||
return datetime.datetime.utcfromtimestamp(timestamp).replace(tzinfo=self.timezone)
|
||||
|
||||
def datetime_to_timestamp(self, dt: datetime.datetime) -> int:
|
||||
"""
|
||||
datetime 对象转时间戳(秒)
|
||||
|
||||
:param dt: datetime 对象
|
||||
:return: 时间戳 (秒)
|
||||
"""
|
||||
return int(dt.astimezone(self.timezone).timestamp())
|
||||
|
||||
def datetime_to_milliseconds(self, dt: datetime.datetime) -> int:
|
||||
"""
|
||||
datetime 对象转时间戳(毫秒)
|
||||
|
||||
:param dt: datetime 对象
|
||||
:return: 时间戳 (毫秒)
|
||||
"""
|
||||
return int(dt.astimezone(self.timezone).timestamp() * 1000)
|
||||
|
||||
def str_to_datetime(self, time_str: str, format_str: str = settings.DATETIME_FORMAT) -> datetime.datetime:
|
||||
"""
|
||||
时间字符串转 datetime 对象
|
||||
|
||||
:param time_str: 时间字符串
|
||||
:param format_str: 时间字符串的格式,默认为 '%Y-%m-%d %H:%M:%S'
|
||||
:return: datetime 对象
|
||||
"""
|
||||
return datetime.datetime.strptime(time_str, format_str).replace(tzinfo=self.timezone)
|
||||
|
||||
def datetime_to_str(self, dt: datetime.datetime, format_str: str = settings.DATETIME_FORMAT) -> str:
|
||||
"""
|
||||
datetime 对象转时间字符串
|
||||
|
||||
:param dt: datetime 对象
|
||||
:param format_str: 时间字符串的格式,默认为 '%Y-%m-%d %H:%M:%S'
|
||||
:return: 时间字符串
|
||||
"""
|
||||
return dt.astimezone(self.timezone).strftime(format_str)
|
||||
|
||||
@staticmethod
|
||||
def get_timezone(timezone_str: str) -> pytz.timezone:
|
||||
"""
|
||||
获取指定时区的 pytz.timezone 对象
|
||||
|
||||
:param timezone_str: 时区字符串
|
||||
:return: pytz.timezone 对象
|
||||
"""
|
||||
return pytz.timezone(timezone_str)
|
||||
|
||||
def get_timezone_time(self, timezone_str: str) -> datetime.datetime:
|
||||
"""
|
||||
获取指定时区的当前时间
|
||||
|
||||
:param timezone_str: 时区字符串
|
||||
:return: 当前时间的 datetime 对象
|
||||
"""
|
||||
timezone = self.get_timezone(timezone_str)
|
||||
return datetime.datetime.now(timezone)
|
||||
|
||||
def datetime_to_timezone(self, dt: datetime.datetime, timezone_str: str) -> datetime.datetime:
|
||||
"""
|
||||
将 datetime 对象转换为指定时区的 datetime 对象
|
||||
|
||||
:param dt: datetime 对象
|
||||
:param timezone_str: 目标时区字符串
|
||||
:return: 目标时区的 datetime 对象
|
||||
"""
|
||||
timezone = self.get_timezone(timezone_str)
|
||||
return dt.astimezone(timezone)
|
||||
|
||||
def datetime_to_timezone_str(
|
||||
self, dt: datetime.datetime, timezone_str: str, format_str: str = settings.DATETIME_FORMAT
|
||||
) -> str:
|
||||
"""
|
||||
将 datetime 对象转换为指定时区的时间字符串
|
||||
|
||||
:param dt: datetime 对象
|
||||
:param timezone_str: 目标时区字符串
|
||||
:param format_str: 时间字符串的格式,默认为 '%Y-%m-%d %H:%M:%S'
|
||||
:return: 目标时区的时间字符串
|
||||
"""
|
||||
dt_timezone = self.datetime_to_timezone(dt, timezone_str)
|
||||
return dt_timezone.strftime(format_str)
|
||||
|
||||
def str_to_timezone(
|
||||
self, time_str: str, timezone_str: str, format_str: str = settings.DATETIME_FORMAT
|
||||
) -> datetime.datetime:
|
||||
"""
|
||||
将指定时区的时间字符串转换为 datetime 对象
|
||||
|
||||
:param time_str: 指定时区的时间字符串
|
||||
:param timezone_str: 指定时区字符串
|
||||
:param format_str: 时间字符串的格式,默认为 '%Y-%m-%d %H:%M:%S'
|
||||
:return: datetime 对象
|
||||
"""
|
||||
dt = datetime.datetime.strptime(time_str, format_str).replace(tzinfo=self.timezone)
|
||||
return self.datetime_to_timezone(dt, timezone_str)
|
||||
|
||||
@staticmethod
|
||||
def datetime_to_utc(dt: datetime.datetime) -> datetime.datetime:
|
||||
"""
|
||||
将 datetime 对象转换为 UTC 时间
|
||||
|
||||
:param dt: datetime 对象
|
||||
:return: UTC 时间的 datetime 对象
|
||||
"""
|
||||
return dt.astimezone(pytz.utc)
|
||||
|
||||
def str_to_utc(self, time_str: str, format_str: str = settings.DATETIME_FORMAT) -> datetime.datetime:
|
||||
"""
|
||||
将时间字符串转换为 UTC 时间的 datetime 对象
|
||||
|
||||
:param time_str: 时间字符串
|
||||
:param format_str: 时间字符串的格式,默认为 '%Y-%m-%d %H:%M:%S'
|
||||
:return: UTC 时间的 datetime 对象
|
||||
"""
|
||||
dt = datetime.datetime.strptime(time_str, format_str).replace(tzinfo=self.timezone)
|
||||
return self.datetime_to_utc(dt)
|
||||
|
||||
def utc_to_datetime(self, utc_time: datetime.datetime) -> datetime.datetime:
|
||||
"""
|
||||
将 UTC 时间的 datetime 对象转换为指定时区的 datetime 对象
|
||||
|
||||
:param utc_time: UTC 时间的 datetime 对象
|
||||
:return: 目标时区的 datetime 对象
|
||||
"""
|
||||
return utc_time.replace(tzinfo=pytz.utc).astimezone(self.timezone).replace(tzinfo=None)
|
||||
|
||||
def get_expire_time(self, expires_delta: datetime.timedelta) -> datetime:
|
||||
"""
|
||||
获取过期时间
|
||||
|
||||
:param expires_delta: 时间间隔对象
|
||||
:return: 过期时间的 datetime 对象
|
||||
"""
|
||||
return self.get_current_time() + expires_delta
|
||||
|
||||
@staticmethod
|
||||
def get_expire_time_from_datetime(expire_time: datetime, seconds: int) -> datetime:
|
||||
"""
|
||||
获取从指定时间开始一定时间后的过期时间
|
||||
|
||||
:param expire_time: 指定时间的 datetime 对象
|
||||
:param seconds: 时间间隔(秒)
|
||||
:return: 过期时间的 datetime 对象
|
||||
"""
|
||||
return expire_time + datetime.timedelta(seconds=seconds)
|
||||
|
||||
@staticmethod
|
||||
def get_expire_seconds(expires_delta: datetime.timedelta) -> int:
|
||||
"""
|
||||
获取过期时间(秒)
|
||||
|
||||
:param expires_delta: 时间间隔对象
|
||||
:return: 过期时间(秒)
|
||||
"""
|
||||
return int(expires_delta.total_seconds())
|
||||
|
||||
def get_expire_seconds_from_datetime(self, expire_datetime: datetime) -> int:
|
||||
"""
|
||||
获取从指定时间开始到当前时间的时间间隔(秒)
|
||||
|
||||
:param expire_datetime: 指定时间的 datetime 对象
|
||||
:return: 时间间隔(秒)
|
||||
"""
|
||||
current_time = self.get_current_time()
|
||||
if expire_datetime < current_time:
|
||||
return 0
|
||||
return int((expire_datetime - current_time).total_seconds())
|
||||
|
||||
|
||||
datetime_utils = DateTimeUtils()
|
||||
@@ -7,6 +7,8 @@ from typing import List
|
||||
|
||||
import psutil
|
||||
|
||||
from backend.app.core.conf import settings
|
||||
|
||||
|
||||
class ServerInfo:
|
||||
@staticmethod
|
||||
@@ -103,6 +105,6 @@ class ServerInfo:
|
||||
'mem_vms': ServerInfo.format_bytes(mem_info.vms), # 虚拟内存, 即当前进程申请的虚拟内存
|
||||
'mem_rss': ServerInfo.format_bytes(mem_info.rss), # 常驻内存, 即当前进程实际使用的物理内存
|
||||
'mem_free': ServerInfo.format_bytes(mem_info.vms - mem_info.rss), # 空闲内存
|
||||
'startup': start_time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'startup': start_time.strftime(settings.DATETIME_FORMAT),
|
||||
'elapsed': f'{ServerInfo.fmt_timedelta(datetime.now() - start_time)}',
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ pytest==7.2.2
|
||||
pytest-pretty==1.2.0
|
||||
python-jose==3.3.0
|
||||
python-multipart==0.0.5
|
||||
pytz==2023.3
|
||||
redis[hiredis]==4.5.5
|
||||
ruff==0.0.262
|
||||
SQLAlchemy==2.0.8
|
||||
|
||||
Reference in New Issue
Block a user