- 新增图像生成接口,支持试用、积分和自定义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): 添加示例系统日志文件 - 记录用户请求、验证码发送成功与失败的日志信息
168 lines
6.2 KiB
Python
168 lines
6.2 KiB
Python
# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
|
# may not use this file except in compliance with the License. A copy of
|
|
# the License is located at
|
|
#
|
|
# https://aws.amazon.com/apache2.0/
|
|
#
|
|
# or in the "license" file accompanying this file. This file is
|
|
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
|
# ANY KIND, either express or implied. See the License for the specific
|
|
# language governing permissions and limitations under the License.
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def register_table_methods(base_classes, **kwargs):
|
|
base_classes.insert(0, TableResource)
|
|
|
|
|
|
# This class can be used to add any additional methods we want
|
|
# onto a table resource. Ideally to avoid creating a new
|
|
# base class for every method we can just update this
|
|
# class instead. Just be sure to move the bulk of the
|
|
# actual method implementation to another class.
|
|
class TableResource:
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
def batch_writer(self, overwrite_by_pkeys=None):
|
|
"""Create a batch writer object.
|
|
|
|
This method creates a context manager for writing
|
|
objects to Amazon DynamoDB in batch.
|
|
|
|
The batch writer will automatically handle buffering and sending items
|
|
in batches. In addition, the batch writer will also automatically
|
|
handle any unprocessed items and resend them as needed. All you need
|
|
to do is call ``put_item`` for any items you want to add, and
|
|
``delete_item`` for any items you want to delete.
|
|
|
|
Example usage::
|
|
|
|
with table.batch_writer() as batch:
|
|
for _ in range(1000000):
|
|
batch.put_item(Item={'HashKey': '...',
|
|
'Otherstuff': '...'})
|
|
# You can also delete_items in a batch.
|
|
batch.delete_item(Key={'HashKey': 'SomeHashKey'})
|
|
|
|
:type overwrite_by_pkeys: list(string)
|
|
:param overwrite_by_pkeys: De-duplicate request items in buffer
|
|
if match new request item on specified primary keys. i.e
|
|
``["partition_key1", "sort_key2", "sort_key3"]``
|
|
|
|
"""
|
|
return BatchWriter(
|
|
self.name, self.meta.client, overwrite_by_pkeys=overwrite_by_pkeys
|
|
)
|
|
|
|
|
|
class BatchWriter:
|
|
"""Automatically handle batch writes to DynamoDB for a single table."""
|
|
|
|
def __init__(
|
|
self, table_name, client, flush_amount=25, overwrite_by_pkeys=None
|
|
):
|
|
"""
|
|
|
|
:type table_name: str
|
|
:param table_name: The name of the table. The class handles
|
|
batch writes to a single table.
|
|
|
|
:type client: ``botocore.client.Client``
|
|
:param client: A botocore client. Note this client
|
|
**must** have the dynamodb customizations applied
|
|
to it for transforming AttributeValues into the
|
|
wire protocol. What this means in practice is that
|
|
you need to use a client that comes from a DynamoDB
|
|
resource if you're going to instantiate this class
|
|
directly, i.e
|
|
``boto3.resource('dynamodb').Table('foo').meta.client``.
|
|
|
|
:type flush_amount: int
|
|
:param flush_amount: The number of items to keep in
|
|
a local buffer before sending a batch_write_item
|
|
request to DynamoDB.
|
|
|
|
:type overwrite_by_pkeys: list(string)
|
|
:param overwrite_by_pkeys: De-duplicate request items in buffer
|
|
if match new request item on specified primary keys. i.e
|
|
``["partition_key1", "sort_key2", "sort_key3"]``
|
|
|
|
"""
|
|
self._table_name = table_name
|
|
self._client = client
|
|
self._items_buffer = []
|
|
self._flush_amount = flush_amount
|
|
self._overwrite_by_pkeys = overwrite_by_pkeys
|
|
|
|
def put_item(self, Item):
|
|
self._add_request_and_process({'PutRequest': {'Item': Item}})
|
|
|
|
def delete_item(self, Key):
|
|
self._add_request_and_process({'DeleteRequest': {'Key': Key}})
|
|
|
|
def _add_request_and_process(self, request):
|
|
if self._overwrite_by_pkeys:
|
|
self._remove_dup_pkeys_request_if_any(request)
|
|
self._items_buffer.append(request)
|
|
self._flush_if_needed()
|
|
|
|
def _remove_dup_pkeys_request_if_any(self, request):
|
|
pkey_values_new = self._extract_pkey_values(request)
|
|
for item in self._items_buffer:
|
|
if self._extract_pkey_values(item) == pkey_values_new:
|
|
self._items_buffer.remove(item)
|
|
logger.debug(
|
|
"With overwrite_by_pkeys enabled, skipping request:%s",
|
|
item,
|
|
)
|
|
|
|
def _extract_pkey_values(self, request):
|
|
if request.get('PutRequest'):
|
|
return [
|
|
request['PutRequest']['Item'][key]
|
|
for key in self._overwrite_by_pkeys
|
|
]
|
|
elif request.get('DeleteRequest'):
|
|
return [
|
|
request['DeleteRequest']['Key'][key]
|
|
for key in self._overwrite_by_pkeys
|
|
]
|
|
return None
|
|
|
|
def _flush_if_needed(self):
|
|
if len(self._items_buffer) >= self._flush_amount:
|
|
self._flush()
|
|
|
|
def _flush(self):
|
|
items_to_send = self._items_buffer[: self._flush_amount]
|
|
self._items_buffer = self._items_buffer[self._flush_amount :]
|
|
response = self._client.batch_write_item(
|
|
RequestItems={self._table_name: items_to_send}
|
|
)
|
|
unprocessed_items = response['UnprocessedItems']
|
|
if not unprocessed_items:
|
|
unprocessed_items = {}
|
|
item_list = unprocessed_items.get(self._table_name, [])
|
|
# Any unprocessed_items are immediately added to the
|
|
# next batch we send.
|
|
self._items_buffer.extend(item_list)
|
|
logger.debug(
|
|
"Batch write sent %s, unprocessed: %s",
|
|
len(items_to_send),
|
|
len(self._items_buffer),
|
|
)
|
|
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, exc_type, exc_value, tb):
|
|
# When we exit, we need to keep flushing whatever's left
|
|
# until there's nothing left in our items buffer.
|
|
while self._items_buffer:
|
|
self._flush()
|