- 新增图像生成接口,支持试用、积分和自定义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): 添加示例系统日志文件 - 记录用户请求、验证码发送成功与失败的日志信息
531 lines
13 KiB
Python
531 lines
13 KiB
Python
import json
|
|
import platform
|
|
import time
|
|
import Tea
|
|
import asyncio
|
|
import threading
|
|
import random
|
|
import hashlib
|
|
|
|
from datetime import datetime
|
|
from urllib.parse import urlencode
|
|
from io import BytesIO
|
|
|
|
from Tea.model import TeaModel
|
|
from Tea.stream import READABLE
|
|
from typing import Any, BinaryIO, Dict, List
|
|
|
|
_process_start_time = int(time.time() * 1000)
|
|
_seqId = 0
|
|
|
|
class Client:
|
|
"""
|
|
This is a utility module
|
|
"""
|
|
|
|
class __ModelEncoder(json.JSONEncoder):
|
|
def default(self, o: Any) -> Any:
|
|
if isinstance(o, TeaModel):
|
|
return o.to_map()
|
|
elif isinstance(o, bytes):
|
|
return o.decode('utf-8')
|
|
super().default(o)
|
|
|
|
@staticmethod
|
|
def __read_part(f, size=1024):
|
|
while True:
|
|
part = f.read(size)
|
|
if part:
|
|
yield part
|
|
else:
|
|
return
|
|
|
|
@staticmethod
|
|
def __get_default_agent():
|
|
return f'AlibabaCloud ({platform.system()}; {platform.machine()}) ' \
|
|
f'Python/{platform.python_version()} Core/{Tea.__version__} TeaDSL/1'
|
|
|
|
@staticmethod
|
|
def to_bytes(
|
|
val: str,
|
|
) -> bytes:
|
|
"""
|
|
Convert a string(utf8) to bytes
|
|
@return: the return bytes
|
|
"""
|
|
if isinstance(val, bytes):
|
|
return val
|
|
elif isinstance(val, str):
|
|
return val.encode(encoding="utf-8")
|
|
else:
|
|
return str(val).encode(encoding="utf-8")
|
|
|
|
|
|
@staticmethod
|
|
def to_string(
|
|
val: bytes,
|
|
) -> str:
|
|
"""
|
|
Convert a bytes to string(utf8)
|
|
@return: the return string
|
|
"""
|
|
if isinstance(val, str):
|
|
return val
|
|
elif isinstance(val, bytes):
|
|
return val.decode('utf-8')
|
|
else:
|
|
return str(val)
|
|
|
|
@staticmethod
|
|
def parse_json(
|
|
val: str,
|
|
) -> Any:
|
|
"""
|
|
Parse it by JSON format
|
|
@return: the parsed result
|
|
"""
|
|
try:
|
|
return json.loads(val)
|
|
except ValueError:
|
|
raise RuntimeError(f'Failed to parse the value as json format, Value: "{val}".')
|
|
|
|
@staticmethod
|
|
async def read_as_bytes_async(stream) -> bytes:
|
|
"""
|
|
Read data from a readable stream, and compose it to a bytes
|
|
@param stream: the readable stream
|
|
@return: the bytes result
|
|
"""
|
|
if isinstance(stream, bytes):
|
|
return stream
|
|
elif isinstance(stream, str):
|
|
return bytes(stream, encoding='utf-8')
|
|
else:
|
|
return await stream.read()
|
|
|
|
@staticmethod
|
|
async def read_as_string_async(stream) -> str:
|
|
"""
|
|
Read data from a readable stream, and compose it to a string
|
|
@param stream: the readable stream
|
|
@return: the string result
|
|
"""
|
|
buff = await Client.read_as_bytes_async(stream)
|
|
return Client.to_string(buff)
|
|
|
|
@staticmethod
|
|
async def read_as_json_async(stream) -> Any:
|
|
"""
|
|
Read data from a readable stream, and parse it by JSON format
|
|
@param stream: the readable stream
|
|
@return: the parsed result
|
|
"""
|
|
return Client.parse_json(
|
|
await Client.read_as_string_async(stream)
|
|
)
|
|
|
|
@staticmethod
|
|
def read_as_bytes(stream) -> bytes:
|
|
"""
|
|
Read data from a readable stream, and compose it to a bytes
|
|
@param stream: the readable stream
|
|
@return: the bytes result
|
|
"""
|
|
if isinstance(stream, READABLE):
|
|
b = b''
|
|
for part in Client.__read_part(stream, 1024):
|
|
b += part
|
|
return b
|
|
elif isinstance(stream, bytes):
|
|
return stream
|
|
else:
|
|
return bytes(stream, encoding='utf-8')
|
|
|
|
@staticmethod
|
|
def read_as_string(stream) -> str:
|
|
"""
|
|
Read data from a readable stream, and compose it to a string
|
|
@param stream: the readable stream
|
|
@return: the string result
|
|
"""
|
|
buff = Client.read_as_bytes(stream)
|
|
return Client.to_string(buff)
|
|
|
|
@staticmethod
|
|
def read_as_json(stream) -> Any:
|
|
"""
|
|
Read data from a readable stream, and parse it by JSON format
|
|
@param stream: the readable stream
|
|
@return: the parsed result
|
|
"""
|
|
return Client.parse_json(Client.read_as_string(stream))
|
|
|
|
@staticmethod
|
|
def get_nonce() -> str:
|
|
"""
|
|
Generate a nonce string
|
|
@return: the nonce string
|
|
"""
|
|
global _seqId
|
|
thread_id = threading.get_ident()
|
|
current_time = int(time.time() * 1000)
|
|
seq = _seqId
|
|
_seqId += 1
|
|
randNum = random.getrandbits(64)
|
|
msg = f'{_process_start_time}-{thread_id}-{current_time}-{seq}-{randNum}'
|
|
md5 = hashlib.md5()
|
|
md5.update(msg.encode('utf-8'))
|
|
return md5.hexdigest()
|
|
|
|
@staticmethod
|
|
def get_date_utcstring() -> str:
|
|
"""
|
|
Get an UTC format string by current date, e.g. 'Thu, 06 Feb 2020 07:32:54 GMT'
|
|
@return: the UTC format string
|
|
"""
|
|
return datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT')
|
|
|
|
@staticmethod
|
|
def default_string(
|
|
real: str,
|
|
default: str,
|
|
) -> str:
|
|
"""
|
|
If not set the real, use default value
|
|
@return: the return string
|
|
"""
|
|
return real if real is not None else default
|
|
|
|
@staticmethod
|
|
def default_number(
|
|
real: int,
|
|
default: int,
|
|
) -> int:
|
|
"""
|
|
If not set the real, use default value
|
|
@return: the return number
|
|
"""
|
|
return real if real is not None else default
|
|
|
|
@staticmethod
|
|
def to_form_string(
|
|
val: dict,
|
|
) -> str:
|
|
"""
|
|
Format a map to form string, like a=a%20b%20c
|
|
@return: the form string
|
|
"""
|
|
if not val:
|
|
return ""
|
|
keys = sorted(list(val))
|
|
dic = {k: val[k] for k in keys if not isinstance(val[k], READABLE)}
|
|
return urlencode(dic)
|
|
|
|
@staticmethod
|
|
def to_jsonstring(
|
|
val: Any,
|
|
) -> str:
|
|
"""
|
|
Stringify a value by JSON format
|
|
@return: the JSON format string
|
|
"""
|
|
if isinstance(val, str):
|
|
return str(val)
|
|
return json.dumps(
|
|
val, cls=Client.__ModelEncoder, ensure_ascii=False, separators=(",", ":")
|
|
)
|
|
|
|
@staticmethod
|
|
def empty(
|
|
val: str,
|
|
) -> bool:
|
|
"""
|
|
Check the string is empty?
|
|
@return: if string is null or zero length, return true
|
|
"""
|
|
return not val
|
|
|
|
@staticmethod
|
|
def equal_string(
|
|
val1: str,
|
|
val2: str,
|
|
) -> bool:
|
|
"""
|
|
Check one string equals another one?
|
|
@return: if equals, return true
|
|
"""
|
|
return val1 == val2
|
|
|
|
@staticmethod
|
|
def equal_number(
|
|
val1: int,
|
|
val2: int,
|
|
) -> bool:
|
|
"""
|
|
Check one number equals another one?
|
|
@return: if equals, return true
|
|
"""
|
|
return val1 == val2
|
|
|
|
@staticmethod
|
|
def is_unset(
|
|
value: Any,
|
|
) -> bool:
|
|
"""
|
|
Check one value is unset
|
|
@return: if unset, return true
|
|
"""
|
|
return value is None
|
|
|
|
@staticmethod
|
|
def stringify_map_value(
|
|
m: Dict[str, Any],
|
|
) -> Dict[str, str]:
|
|
"""
|
|
Stringify the value of map
|
|
@return: the new stringified map
|
|
"""
|
|
if m is None:
|
|
return {}
|
|
|
|
dic_result = {}
|
|
for k, v in m.items():
|
|
if v is not None:
|
|
if isinstance(v, bytes):
|
|
v = v.decode('utf-8')
|
|
else:
|
|
v = str(v)
|
|
dic_result[k] = v
|
|
return dic_result
|
|
|
|
@staticmethod
|
|
def anyify_map_value(
|
|
m: Dict[str, str],
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Anyify the value of map
|
|
@return: the new anyfied map
|
|
"""
|
|
return m
|
|
|
|
@staticmethod
|
|
def assert_as_boolean(
|
|
value: Any,
|
|
) -> bool:
|
|
"""
|
|
Assert a value, if it is a boolean, return it, otherwise throws
|
|
@return: the boolean value
|
|
"""
|
|
if not isinstance(value, bool):
|
|
raise ValueError(f'{value} is not a bool')
|
|
return value
|
|
|
|
@staticmethod
|
|
def assert_as_string(
|
|
value: Any,
|
|
) -> str:
|
|
"""
|
|
Assert a value, if it is a string, return it, otherwise throws
|
|
@return: the string value
|
|
"""
|
|
if not isinstance(value, str):
|
|
raise ValueError(f'{value} is not a str')
|
|
return value
|
|
|
|
@staticmethod
|
|
def assert_as_bytes(
|
|
value: Any,
|
|
) -> bytes:
|
|
"""
|
|
Assert a value, if it is a bytes, return it, otherwise throws
|
|
@return: the bytes value
|
|
"""
|
|
if not isinstance(value, bytes):
|
|
raise ValueError(f'{value} is not a bytes')
|
|
return value
|
|
|
|
@staticmethod
|
|
def assert_as_number(
|
|
value: Any,
|
|
) -> int:
|
|
"""
|
|
Assert a value, if it is a number, return it, otherwise throws
|
|
@return: the number value
|
|
"""
|
|
if not isinstance(value, (int, float)):
|
|
raise ValueError(f'{value} is not a number')
|
|
return value
|
|
|
|
@staticmethod
|
|
def assert_as_integer(
|
|
value: Any,
|
|
) -> int:
|
|
"""
|
|
Assert a value, if it is a integer, return it, otherwise throws
|
|
@return: the integer value
|
|
"""
|
|
if not isinstance(value, int):
|
|
raise ValueError(f'{value} is not a int number')
|
|
return value
|
|
|
|
@staticmethod
|
|
def assert_as_map(
|
|
value: Any,
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Assert a value, if it is a map, return it, otherwise throws
|
|
@return: the map value
|
|
"""
|
|
if not isinstance(value, dict):
|
|
raise ValueError(f'{value} is not a dict')
|
|
return value
|
|
|
|
@staticmethod
|
|
def get_user_agent(
|
|
user_agent: str,
|
|
) -> str:
|
|
"""
|
|
Get user agent, if it userAgent is not null, splice it with defaultUserAgent and return, otherwise return defaultUserAgent
|
|
@return: the string value
|
|
"""
|
|
if user_agent:
|
|
return f'{Client.__get_default_agent()} {user_agent}'
|
|
return Client.__get_default_agent()
|
|
|
|
@staticmethod
|
|
def is_2xx(
|
|
code: int,
|
|
) -> bool:
|
|
"""
|
|
If the code between 200 and 300, return true, or return false
|
|
@return: boolean
|
|
"""
|
|
return 200 <= code < 300
|
|
|
|
@staticmethod
|
|
def is_3xx(
|
|
code: int,
|
|
) -> bool:
|
|
"""
|
|
If the code between 300 and 400, return true, or return false
|
|
@return: boolean
|
|
"""
|
|
return 300 <= code < 400
|
|
|
|
@staticmethod
|
|
def is_4xx(
|
|
code: int,
|
|
) -> bool:
|
|
"""
|
|
If the code between 400 and 500, return true, or return false
|
|
@return: boolean
|
|
"""
|
|
return 400 <= code < 500
|
|
|
|
@staticmethod
|
|
def is_5xx(
|
|
code: int,
|
|
) -> bool:
|
|
"""
|
|
If the code between 500 and 600, return true, or return false
|
|
@return: boolean
|
|
"""
|
|
return 500 <= code < 600
|
|
|
|
@staticmethod
|
|
def validate_model(
|
|
m: TeaModel,
|
|
) -> None:
|
|
"""
|
|
Validate model
|
|
@return: void
|
|
"""
|
|
if isinstance(m, TeaModel):
|
|
m.validate()
|
|
|
|
@staticmethod
|
|
def to_map(
|
|
in_: TeaModel,
|
|
) -> Dict[str, Any]:
|
|
"""
|
|
Model transforms to map[string]any
|
|
@return: map[string]any
|
|
"""
|
|
if isinstance(in_, TeaModel):
|
|
return in_.to_map()
|
|
else:
|
|
return in_
|
|
|
|
@staticmethod
|
|
def sleep(
|
|
millisecond: int,
|
|
) -> None:
|
|
"""
|
|
Suspends the current thread for the specified number of milliseconds.
|
|
"""
|
|
time.sleep(millisecond / 1000)
|
|
|
|
@staticmethod
|
|
async def sleep_async(
|
|
millisecond: int,
|
|
) -> None:
|
|
"""
|
|
Suspends the current thread for the specified number of milliseconds.
|
|
"""
|
|
await asyncio.sleep(millisecond / 1000)
|
|
|
|
@staticmethod
|
|
def to_array(
|
|
input: Any,
|
|
) -> List[Dict[str, Any]]:
|
|
"""
|
|
Transform input as array.
|
|
"""
|
|
if input is None:
|
|
return []
|
|
|
|
out = []
|
|
for i in input:
|
|
if isinstance(i, TeaModel):
|
|
out.append(i.to_map())
|
|
else:
|
|
out.append(i)
|
|
return out
|
|
|
|
@staticmethod
|
|
def assert_as_readable(
|
|
value: Any,
|
|
) -> BinaryIO:
|
|
"""
|
|
Assert a value, if it is a readable, return it, otherwise throws
|
|
@return: the readable value
|
|
"""
|
|
if isinstance(value, str):
|
|
value = value.encode('utf-8')
|
|
|
|
if isinstance(value, bytes):
|
|
value = BytesIO(value)
|
|
elif not isinstance(value, READABLE):
|
|
raise ValueError(f'The value is not a readable')
|
|
return value
|
|
|
|
@staticmethod
|
|
def assert_as_array(
|
|
value: Any,
|
|
) -> list:
|
|
if not isinstance(value, list):
|
|
raise ValueError('The value is not a list')
|
|
return value
|
|
|
|
@staticmethod
|
|
def get_host_name() -> str:
|
|
"""
|
|
Get hostname of current machine
|
|
@return: the string value
|
|
"""
|
|
import socket
|
|
try:
|
|
return socket.gethostname()
|
|
except Exception:
|
|
return ''
|