- 新增图像生成接口,支持试用、积分和自定义API Key模式 - 实现生成图片结果异步上传至MinIO存储,带重试机制 - 优化积分预扣除和异常退还逻辑,保障用户积分准确 - 添加获取生成历史记录接口,支持时间范围和分页 - 提供本地字典配置接口,支持模型、比例、提示模板和尺寸 - 实现图片批量上传接口,支持S3兼容对象存储 feat(admin): 增加管理员角色管理与权限分配接口 - 实现角色列表查询、角色创建、更新及删除功能 - 增加权限列表查询接口 - 实现用户角色分配接口,便于统一管理用户权限 - 增加系统字典增删查改接口,支持分类过滤和排序 - 权限控制全面覆盖管理接口,保证安全访问 feat(auth): 完善用户登录注册及权限相关接口与页面 - 实现手机号验证码发送及校验功能,保障注册安全 - 支持手机号注册、登录及退出接口,集成日志记录 - 增加修改密码功能,验证原密码后更新 - 提供动态导航菜单接口,基于权限展示不同菜单 - 实现管理界面路由及日志、角色、字典管理页面访问权限控制 - 添加系统日志查询接口,支持关键词和等级筛选 feat(app): 初始化Flask应用并配置蓝图与数据库 - 创建应用程序工厂,加载配置,初始化数据库和Redis客户端 - 注册认证、API及管理员蓝图,整合路由 - 根路由渲染主页模板 - 应用上下文中自动创建数据库表,保证运行环境准备完毕 feat(database): 提供数据库创建与迁移支持脚本 - 新增数据库创建脚本,支持自动检测是否已存在 - 添加数据库表初始化脚本,支持创建和删除所有表 - 实现RBAC权限初始化,包含基础权限和角色创建 - 新增字段手动修复脚本,添加用户API Key和积分字段 - 强制迁移脚本支持清理连接和修复表结构,初始化默认数据及角色分配 feat(config): 新增系统配置参数 - 配置数据库、Redis、Session和MinIO相关参数 - 添加AI接口地址及试用Key配置 - 集成阿里云短信服务配置及开发模式相关参数 feat(extensions): 初始化数据库、Redis和MinIO客户端 - 创建全局SQLAlchemy数据库实例和Redis客户端 - 配置基于boto3的MinIO兼容S3客户端 chore(logs): 添加示例系统日志文件 - 记录用户请求、验证码发送成功与失败的日志信息
258 lines
9.2 KiB
Python
258 lines
9.2 KiB
Python
from __future__ import annotations
|
|
|
|
import typing as t
|
|
|
|
from werkzeug.exceptions import BadRequest
|
|
from werkzeug.exceptions import HTTPException
|
|
from werkzeug.wrappers import Request as RequestBase
|
|
from werkzeug.wrappers import Response as ResponseBase
|
|
|
|
from . import json
|
|
from .globals import current_app
|
|
from .helpers import _split_blueprint_path
|
|
|
|
if t.TYPE_CHECKING: # pragma: no cover
|
|
from werkzeug.routing import Rule
|
|
|
|
|
|
class Request(RequestBase):
|
|
"""The request object used by default in Flask. Remembers the
|
|
matched endpoint and view arguments.
|
|
|
|
It is what ends up as :class:`~flask.request`. If you want to replace
|
|
the request object used you can subclass this and set
|
|
:attr:`~flask.Flask.request_class` to your subclass.
|
|
|
|
The request object is a :class:`~werkzeug.wrappers.Request` subclass and
|
|
provides all of the attributes Werkzeug defines plus a few Flask
|
|
specific ones.
|
|
"""
|
|
|
|
json_module: t.Any = json
|
|
|
|
#: The internal URL rule that matched the request. This can be
|
|
#: useful to inspect which methods are allowed for the URL from
|
|
#: a before/after handler (``request.url_rule.methods``) etc.
|
|
#: Though if the request's method was invalid for the URL rule,
|
|
#: the valid list is available in ``routing_exception.valid_methods``
|
|
#: instead (an attribute of the Werkzeug exception
|
|
#: :exc:`~werkzeug.exceptions.MethodNotAllowed`)
|
|
#: because the request was never internally bound.
|
|
#:
|
|
#: .. versionadded:: 0.6
|
|
url_rule: Rule | None = None
|
|
|
|
#: A dict of view arguments that matched the request. If an exception
|
|
#: happened when matching, this will be ``None``.
|
|
view_args: dict[str, t.Any] | None = None
|
|
|
|
#: If matching the URL failed, this is the exception that will be
|
|
#: raised / was raised as part of the request handling. This is
|
|
#: usually a :exc:`~werkzeug.exceptions.NotFound` exception or
|
|
#: something similar.
|
|
routing_exception: HTTPException | None = None
|
|
|
|
_max_content_length: int | None = None
|
|
_max_form_memory_size: int | None = None
|
|
_max_form_parts: int | None = None
|
|
|
|
@property
|
|
def max_content_length(self) -> int | None:
|
|
"""The maximum number of bytes that will be read during this request. If
|
|
this limit is exceeded, a 413 :exc:`~werkzeug.exceptions.RequestEntityTooLarge`
|
|
error is raised. If it is set to ``None``, no limit is enforced at the
|
|
Flask application level. However, if it is ``None`` and the request has
|
|
no ``Content-Length`` header and the WSGI server does not indicate that
|
|
it terminates the stream, then no data is read to avoid an infinite
|
|
stream.
|
|
|
|
Each request defaults to the :data:`MAX_CONTENT_LENGTH` config, which
|
|
defaults to ``None``. It can be set on a specific ``request`` to apply
|
|
the limit to that specific view. This should be set appropriately based
|
|
on an application's or view's specific needs.
|
|
|
|
.. versionchanged:: 3.1
|
|
This can be set per-request.
|
|
|
|
.. versionchanged:: 0.6
|
|
This is configurable through Flask config.
|
|
"""
|
|
if self._max_content_length is not None:
|
|
return self._max_content_length
|
|
|
|
if not current_app:
|
|
return super().max_content_length
|
|
|
|
return current_app.config["MAX_CONTENT_LENGTH"] # type: ignore[no-any-return]
|
|
|
|
@max_content_length.setter
|
|
def max_content_length(self, value: int | None) -> None:
|
|
self._max_content_length = value
|
|
|
|
@property
|
|
def max_form_memory_size(self) -> int | None:
|
|
"""The maximum size in bytes any non-file form field may be in a
|
|
``multipart/form-data`` body. If this limit is exceeded, a 413
|
|
:exc:`~werkzeug.exceptions.RequestEntityTooLarge` error is raised. If it
|
|
is set to ``None``, no limit is enforced at the Flask application level.
|
|
|
|
Each request defaults to the :data:`MAX_FORM_MEMORY_SIZE` config, which
|
|
defaults to ``500_000``. It can be set on a specific ``request`` to
|
|
apply the limit to that specific view. This should be set appropriately
|
|
based on an application's or view's specific needs.
|
|
|
|
.. versionchanged:: 3.1
|
|
This is configurable through Flask config.
|
|
"""
|
|
if self._max_form_memory_size is not None:
|
|
return self._max_form_memory_size
|
|
|
|
if not current_app:
|
|
return super().max_form_memory_size
|
|
|
|
return current_app.config["MAX_FORM_MEMORY_SIZE"] # type: ignore[no-any-return]
|
|
|
|
@max_form_memory_size.setter
|
|
def max_form_memory_size(self, value: int | None) -> None:
|
|
self._max_form_memory_size = value
|
|
|
|
@property # type: ignore[override]
|
|
def max_form_parts(self) -> int | None:
|
|
"""The maximum number of fields that may be present in a
|
|
``multipart/form-data`` body. If this limit is exceeded, a 413
|
|
:exc:`~werkzeug.exceptions.RequestEntityTooLarge` error is raised. If it
|
|
is set to ``None``, no limit is enforced at the Flask application level.
|
|
|
|
Each request defaults to the :data:`MAX_FORM_PARTS` config, which
|
|
defaults to ``1_000``. It can be set on a specific ``request`` to apply
|
|
the limit to that specific view. This should be set appropriately based
|
|
on an application's or view's specific needs.
|
|
|
|
.. versionchanged:: 3.1
|
|
This is configurable through Flask config.
|
|
"""
|
|
if self._max_form_parts is not None:
|
|
return self._max_form_parts
|
|
|
|
if not current_app:
|
|
return super().max_form_parts
|
|
|
|
return current_app.config["MAX_FORM_PARTS"] # type: ignore[no-any-return]
|
|
|
|
@max_form_parts.setter
|
|
def max_form_parts(self, value: int | None) -> None:
|
|
self._max_form_parts = value
|
|
|
|
@property
|
|
def endpoint(self) -> str | None:
|
|
"""The endpoint that matched the request URL.
|
|
|
|
This will be ``None`` if matching failed or has not been
|
|
performed yet.
|
|
|
|
This in combination with :attr:`view_args` can be used to
|
|
reconstruct the same URL or a modified URL.
|
|
"""
|
|
if self.url_rule is not None:
|
|
return self.url_rule.endpoint # type: ignore[no-any-return]
|
|
|
|
return None
|
|
|
|
@property
|
|
def blueprint(self) -> str | None:
|
|
"""The registered name of the current blueprint.
|
|
|
|
This will be ``None`` if the endpoint is not part of a
|
|
blueprint, or if URL matching failed or has not been performed
|
|
yet.
|
|
|
|
This does not necessarily match the name the blueprint was
|
|
created with. It may have been nested, or registered with a
|
|
different name.
|
|
"""
|
|
endpoint = self.endpoint
|
|
|
|
if endpoint is not None and "." in endpoint:
|
|
return endpoint.rpartition(".")[0]
|
|
|
|
return None
|
|
|
|
@property
|
|
def blueprints(self) -> list[str]:
|
|
"""The registered names of the current blueprint upwards through
|
|
parent blueprints.
|
|
|
|
This will be an empty list if there is no current blueprint, or
|
|
if URL matching failed.
|
|
|
|
.. versionadded:: 2.0.1
|
|
"""
|
|
name = self.blueprint
|
|
|
|
if name is None:
|
|
return []
|
|
|
|
return _split_blueprint_path(name)
|
|
|
|
def _load_form_data(self) -> None:
|
|
super()._load_form_data()
|
|
|
|
# In debug mode we're replacing the files multidict with an ad-hoc
|
|
# subclass that raises a different error for key errors.
|
|
if (
|
|
current_app
|
|
and current_app.debug
|
|
and self.mimetype != "multipart/form-data"
|
|
and not self.files
|
|
):
|
|
from .debughelpers import attach_enctype_error_multidict
|
|
|
|
attach_enctype_error_multidict(self)
|
|
|
|
def on_json_loading_failed(self, e: ValueError | None) -> t.Any:
|
|
try:
|
|
return super().on_json_loading_failed(e)
|
|
except BadRequest as ebr:
|
|
if current_app and current_app.debug:
|
|
raise
|
|
|
|
raise BadRequest() from ebr
|
|
|
|
|
|
class Response(ResponseBase):
|
|
"""The response object that is used by default in Flask. Works like the
|
|
response object from Werkzeug but is set to have an HTML mimetype by
|
|
default. Quite often you don't have to create this object yourself because
|
|
:meth:`~flask.Flask.make_response` will take care of that for you.
|
|
|
|
If you want to replace the response object used you can subclass this and
|
|
set :attr:`~flask.Flask.response_class` to your subclass.
|
|
|
|
.. versionchanged:: 1.0
|
|
JSON support is added to the response, like the request. This is useful
|
|
when testing to get the test client response data as JSON.
|
|
|
|
.. versionchanged:: 1.0
|
|
|
|
Added :attr:`max_cookie_size`.
|
|
"""
|
|
|
|
default_mimetype: str | None = "text/html"
|
|
|
|
json_module = json
|
|
|
|
autocorrect_location_header = False
|
|
|
|
@property
|
|
def max_cookie_size(self) -> int: # type: ignore
|
|
"""Read-only view of the :data:`MAX_COOKIE_SIZE` config key.
|
|
|
|
See :attr:`~werkzeug.wrappers.Response.max_cookie_size` in
|
|
Werkzeug's docs.
|
|
"""
|
|
if current_app:
|
|
return current_app.config["MAX_COOKIE_SIZE"] # type: ignore[no-any-return]
|
|
|
|
# return Werkzeug's default when not in an app context
|
|
return super().max_cookie_size
|