版本更新:

1. 新增(kinit-api,kinit-admin):支持发送邮箱通知
2. 优化(kinit-api):core/crud.py 优化
This commit is contained in:
ktianc 2023-03-30 15:56:47 +08:00
parent 0152dc9c70
commit 2ba2738107
20 changed files with 290 additions and 85 deletions

View File

@ -51,3 +51,7 @@ export const postImportUserApi = (data: any): Promise<IResponse> => {
export const postUsersInitPasswordSendSMSApi = (data: any): Promise<IResponse> => {
return request.post({ url: `/vadmin/auth/users/init/password/send/sms/`, data })
}
export const postUsersInitPasswordSendEmailApi = (data: any): Promise<IResponse> => {
return request.post({ url: `/vadmin/auth/users/init/password/send/email/`, data })
}

View File

@ -54,11 +54,21 @@ export const useValidator = () => {
}
}
const isEmail = (rule: any, val: any, callback: Callback) => {
// 判断是否为邮箱地址
if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(val)) {
callback()
} else {
callback(new Error('请填写正确的邮箱地址'))
}
}
return {
required,
lengthRange,
notSpace,
notSpecialCharacters,
isEqual
isEqual,
isEmail
}
}

View File

@ -6,7 +6,7 @@ import { useValidator } from '@/hooks/web/useValidator'
import { schema } from './user.data'
import { getRoleOptionsApi } from '@/api/vadmin/auth/role'
const { required } = useValidator()
const { required, isEmail } = useValidator()
const props = defineProps({
currentRow: {
@ -20,7 +20,8 @@ const rules = reactive({
is_active: [required()],
is_staff: [required()],
role_ids: [required()],
telephone: [required()]
telephone: [required()],
email: [required(), { validator: isEmail, trigger: 'blur' }]
})
const { register, methods, elFormRef } = useForm({

View File

@ -27,6 +27,12 @@ export const columns = reactive<TableColumn[]>([
show: true,
disabled: true
},
{
field: 'email',
label: '邮箱',
show: true,
disabled: true
},
{
field: 'gender',
label: '性别',
@ -76,6 +82,19 @@ export const schema = reactive<FormSchema[]>([
}
}
},
{
field: 'nickname',
label: '用户昵称',
colProps: {
span: 12
},
component: 'Input',
componentProps: {
style: {
width: '100%'
}
}
},
{
field: 'telephone',
label: '手机号码',
@ -90,8 +109,8 @@ export const schema = reactive<FormSchema[]>([
}
},
{
field: 'nickname',
label: '用户昵称',
field: 'email',
label: '邮箱',
colProps: {
span: 12
},

View File

@ -20,7 +20,8 @@ import { columns, searchSchema } from './components/user.data'
import { ref, unref, watch, nextTick } from 'vue'
import Write from './components/Write.vue'
import Import from './components/Import.vue'
import Password from './components/Password.vue'
import PasswordSendSMS from './components/PasswordSendSMS.vue'
import PasswordSendEmail from './components/PasswordSendEmail.vue'
import { Dialog } from '@/components/Dialog'
import {
ElButton,
@ -180,7 +181,8 @@ const importList = () => {
}
const passwordDialogVisible = ref(false)
const passwordDialogTitle = ref('重置密码并发送短信')
let passwordDialogType = 'sms'
let passwordDialogTitle = ref('重置密码并发送短信')
const selections = ref([] as any[])
//
@ -189,6 +191,21 @@ const sendPasswordToSMS = async () => {
selections.value = await getSelections()
if (selections.value.length > 0) {
passwordDialogVisible.value = true
passwordDialogType = 'sms'
passwordDialogTitle.value = '重置密码并发送短信'
} else {
return ElMessage.warning('请先选择数据')
}
}
//
const sendPasswordToEmail = async () => {
const { getSelections } = methods
selections.value = await getSelections()
if (selections.value.length > 0) {
passwordDialogVisible.value = true
passwordDialogType = 'email'
passwordDialogTitle.value = '重置密码并发送邮件'
} else {
return ElMessage.warning('请先选择数据')
}
@ -205,6 +222,8 @@ const handleCommand = (command: string) => {
} else if (command === 'c') {
sendPasswordToSMS()
} else if (command === 'd') {
sendPasswordToEmail()
} else if (command === 'e') {
delDatas(null, true)
}
}
@ -233,6 +252,9 @@ const handleCommand = (command: string) => {
<ElCol :span="1.5" v-hasPermi="['auth.user.reset']" v-if="!mobile">
<ElButton @click="sendPasswordToSMS">重置密码通知短信</ElButton>
</ElCol>
<ElCol :span="1.5" v-hasPermi="['auth.user.reset']" v-if="!mobile">
<ElButton @click="sendPasswordToEmail">重置密码通知邮件</ElButton>
</ElCol>
<ElCol :span="1.5" v-hasPermi="['auth.user.delete']" v-if="!mobile">
<ElButton type="danger" @click="delDatas(null, true)">批量删除</ElButton>
</ElCol>
@ -255,7 +277,10 @@ const handleCommand = (command: string) => {
<ElDropdownItem command="c" v-hasPermi="['auth.user.reset']"
>重置密码通知短信</ElDropdownItem
>
<ElDropdownItem command="d" v-hasPermi="['auth.user.delete']"
<ElDropdownItem command="d" v-hasPermi="['auth.user.reset']"
>重置密码通知邮件</ElDropdownItem
>
<ElDropdownItem command="e" v-hasPermi="['auth.user.delete']"
>批量删除</ElDropdownItem
>
</ElDropdownMenu>
@ -343,10 +368,20 @@ const handleCommand = (command: string) => {
<Dialog
v-model="passwordDialogVisible"
:title="passwordDialogTitle"
width="850px"
width="1000px"
maxHeight="550px"
>
<Password :selections="selections" @get-list="getList" />
<PasswordSendSMS
v-if="passwordDialogType === 'sms'"
:selections="selections"
@get-list="getList"
/>
<PasswordSendEmail
v-else-if="passwordDialogType === 'email'"
:selections="selections"
@get-list="getList"
/>
</Dialog>
</ContentWrap>
</template>

View File

@ -12,6 +12,7 @@ import Baidu from './baidu.vue'
import Privacy from './privacy.vue'
import Agreement from './agreement.vue'
import WXClient from './wxServer.vue'
import Email from './email.vue'
import { ContentWrap } from '@/components/ContentWrap'
import { getSystemSettingsTabsApi } from '@/api/vadmin/system/settings'
@ -37,6 +38,7 @@ getList()
<Privacy v-else-if="item.tab_name === 'web_privacy'" :tab-id="item.id" />
<Agreement v-else-if="item.tab_name === 'web_agreement'" :tab-id="item.id" />
<WXClient v-else-if="item.tab_name === 'wx_server'" :tab-id="item.id" />
<Email v-else-if="item.tab_name === 'web_email'" :tab-id="item.id" />
</ElTabPane>
</template>
</ElTabs>

View File

@ -11,7 +11,7 @@ from fastapi.security import OAuth2PasswordBearer
"""
系统版本
"""
VERSION = "1.7.1"
VERSION = "1.7.2"
"""安全警告: 不要在生产中打开调试运行!"""
DEBUG = True

View File

@ -21,6 +21,7 @@ from utils.file.aliyun_oss import AliyunOSS, BucketConf
from utils.aliyun_sms import AliyunSMS
from utils.excel.import_manage import ImportManage, FieldType
from utils.excel.write_xlsx import WriteXlsx
from utils.send_email import EmailSender
from .params import UserParams
from utils.tools import test_password
from . import models, schemas
@ -205,16 +206,16 @@ class UserDal(DalBase):
"error_url": im.generate_error_url()
}
async def init_password_send_sms(self, ids: List[int], rd: Redis):
async def init_password(self, ids: List[int]):
"""
初始化所选用户密码并发送通知短信
初始化所选用户密码
将用户密码改为系统默认密码并将初始化密码状态改为false
"""
users = await self.get_datas(limit=0, id=("in", ids), v_return_objs=True)
result = []
for user in users:
# 重置密码
data = {"id": user.id, "telephone": user.telephone, "name": user.name}
data = {"id": user.id, "telephone": user.telephone, "name": user.name, "email": user.email}
password = user.telephone[5:12] if settings.DEFAULT_PASSWORD == "0" else settings.DEFAULT_PASSWORD
user.password = self.model.get_password_hash(password)
user.is_reset_password = False
@ -223,6 +224,14 @@ class UserDal(DalBase):
data["password"] = password
result.append(data)
await self.db.flush()
return result
async def init_password_send_sms(self, ids: List[int], rd: Redis):
"""
初始化所选用户密码并发送通知短信
将用户密码改为系统默认密码并将初始化密码状态改为false
"""
result = await self.init_password(ids)
for user in result:
if not user["reset_password_status"]:
user["send_sms_status"] = False
@ -239,6 +248,35 @@ class UserDal(DalBase):
user["send_sms_msg"] = e.msg
return result
async def init_password_send_email(self, ids: List[int], rd: Redis):
"""
初始化所选用户密码并发送通知邮件
将用户密码改为系统默认密码并将初始化密码状态改为false
"""
result = await self.init_password(ids)
for user in result:
if not user["reset_password_status"]:
user["send_sms_status"] = False
user["send_sms_msg"] = "重置密码失败"
continue
password: str = user.pop("password")
email: str = user.get("email", None)
if email:
subject = "密码已重置"
body = f"您好,您的密码已经重置为{password},请及时登录并修改密码。"
es = EmailSender(rd)
try:
send_result = await es.send_email([email], subject, body)
user["send_sms_status"] = send_result
user["send_sms_msg"] = "" if send_result else "发送失败,请联系管理员"
except CustomException as e:
user["send_sms_status"] = False
user["send_sms_msg"] = e.msg
else:
user["send_sms_status"] = False
user["send_sms_msg"] = "未获取到邮箱地址"
return result
async def update_current_avatar(self, user: models.VadminUser, file: UploadFile):
"""
更新当前用户头像

View File

@ -23,6 +23,7 @@ class VadminUser(BaseModel):
avatar = Column(String(500), nullable=True, comment='头像')
telephone = Column(String(11), nullable=False, index=True, comment="手机号", unique=False)
email = Column(String(50), nullable=True, comment="邮箱地址")
name = Column(String(50), index=True, nullable=False, comment="姓名")
nickname = Column(String(50), nullable=True, comment="昵称")
password = Column(String(255), nullable=True, comment="密码")

View File

@ -22,6 +22,7 @@ class UserParams(QueryParams):
self,
name: str = None,
telephone: str = None,
email: str = None,
is_active: bool | str = None,
is_staff: bool | str = None,
params: Paging = Depends()
@ -29,6 +30,7 @@ class UserParams(QueryParams):
super().__init__(params)
self.name = ("like", name)
self.telephone = ("like", telephone)
self.email = ("like", email)
self.is_active = is_active
self.is_staff = is_staff

View File

@ -11,13 +11,14 @@
from typing import List, Optional
from pydantic import BaseModel, root_validator
from core.data_types import Telephone, DatetimeStr
from core.data_types import Telephone, DatetimeStr, Email
from .role import RoleSimpleOut
class User(BaseModel):
name: Optional[str] = None
telephone: Telephone
email: Optional[Email] = None
nickname: Optional[str] = None
avatar: Optional[str] = None
is_active: Optional[bool] = True
@ -40,6 +41,7 @@ class UserUpdateBaseInfo(BaseModel):
"""
name: str
telephone: Telephone
email: Optional[Email] = None
nickname: Optional[str] = None
gender: Optional[str] = "0"
@ -50,6 +52,7 @@ class UserUpdate(User):
"""
name: Optional[str] = None
telephone: Telephone
email: Optional[Email] = None
nickname: Optional[str] = None
avatar: Optional[str] = None
is_active: Optional[bool] = True

View File

@ -119,6 +119,15 @@ async def post_users_init_password(
return SuccessResponse(await crud.UserDal(auth.db).init_password_send_sms(ids.ids, request.app.state.redis))
@app.post("/users/init/password/send/email/", summary="初始化所选用户密码并发送通知邮件")
async def post_users_init_password_send_email(
request: Request,
ids: IdList = Depends(),
auth: Auth = Depends(FullAdminAuth(permissions=["auth.user.reset"]))
):
return SuccessResponse(await crud.UserDal(auth.db).init_password_send_email(ids.ids, request.app.state.redis))
@app.put("/users/wx/server/openid/", summary="更新当前用户服务端微信平台openid")
async def put_user_wx_server_openid(request: Request, code: str, auth: Auth = Depends(AllUserAuth())):
result = await crud.UserDal(auth.db).update_wx_server_openid(code, auth.user, request.app.state.redis)

View File

@ -19,14 +19,14 @@
# https://www.osgeo.cn/sqlalchemy/orm/loading_relationships.html?highlight=selectinload#sqlalchemy.orm.joinedload
import datetime
from typing import List
from typing import List, Set
from fastapi import HTTPException
from fastapi.encoders import jsonable_encoder
from sqlalchemy import func, delete, update, or_
from sqlalchemy.future import select
from sqlalchemy.ext.asyncio import AsyncSession
from starlette import status
from core.logger import logger
from core.exception import CustomException
from sqlalchemy.sql.selectable import Select
from typing import Any
@ -47,6 +47,7 @@ class DalBase:
data_id: int = None,
v_options: list = None,
v_join_query: dict = None,
v_or: List[tuple] = None,
v_order: str = None,
v_return_none: bool = False,
v_schema: Any = None,
@ -58,6 +59,7 @@ class DalBase:
:param data_id: 数据 ID
:param v_options: 指示应使用select在预加载中加载给定的属性
:param v_join_query: 外键字段查询内连接
:param v_or: 或逻辑查询
:param v_order: 排序默认正序 desc 是倒叙
:param v_return_none: 是否返回空 None否认 抛出异常默认抛出异常
:param v_schema: 指定使用的序列化对象
@ -66,7 +68,7 @@ class DalBase:
sql = select(self.model).where(self.model.is_delete == False)
if data_id:
sql = sql.where(self.model.id == data_id)
sql = self.add_filter_condition(sql, v_join_query, v_options, **kwargs)
sql = self.add_filter_condition(sql, v_options, v_join_query, v_or, **kwargs)
if v_order and (v_order in self.ORDER_FIELD):
sql = sql.order_by(self.model.create_datetime.desc())
queryset = await self.db.execute(sql)
@ -83,8 +85,9 @@ class DalBase:
self,
page: int = 1,
limit: int = 10,
v_join_query: dict = None,
v_options: list = None,
v_join_query: dict = None,
v_or: List[tuple] = None,
v_order: str = None,
v_order_field: str = None,
v_return_objs: bool = False,
@ -96,18 +99,19 @@ class DalBase:
获取数据列表
:param page: 页码
:param limit: 当前页数据量
:param v_join_query: 外键字段查询
:param v_options: 指示应使用select在预加载中加载给定的属性
:param v_schema: 指定使用的序列化对象
:param v_join_query: 外键字段查询
:param v_or: 或逻辑查询
:param v_order: 排序默认正序 desc 是倒叙
:param v_order_field: 排序字段
:param v_return_objs: 是否返回对象
:param v_start_sql: 初始 sql
:param v_schema: 指定使用的序列化对象
:param kwargs: 查询参数
"""
if not isinstance(v_start_sql, Select):
v_start_sql = select(self.model).where(self.model.is_delete == False)
sql = self.add_filter_condition(v_start_sql, v_join_query, v_options, **kwargs)
sql = self.add_filter_condition(v_start_sql, v_options, v_join_query, v_or, **kwargs)
if v_order_field and (v_order in self.ORDER_FIELD):
sql = sql.order_by(getattr(self.model, v_order_field).desc(), self.model.id.desc())
elif v_order_field:
@ -121,15 +125,17 @@ class DalBase:
return queryset.scalars().unique().all()
return [await self.out_dict(i, v_schema=v_schema) for i in queryset.scalars().unique().all()]
async def get_count(self, v_join_query: dict = None, v_options: list = None, **kwargs):
async def get_count(self, v_options: list = None, v_join_query: dict = None, v_or: List[tuple] = None, **kwargs):
"""
获取数据总数
:param v_join_query: 外键字段查询
:param v_options: 指示应使用select在预加载中加载给定的属性
:param v_join_query: 外键字段查询
:param v_or: 或逻辑查询
:param kwargs: 查询参数
"""
sql = select(func.count(self.model.id).label('total')).where(self.model.is_delete == False)
sql = self.add_filter_condition(sql, v_join_query, v_options, **kwargs)
sql = self.add_filter_condition(sql, v_options, v_join_query, v_or, **kwargs)
queryset = await self.db.execute(sql)
return queryset.one()['total']
@ -189,71 +195,117 @@ class DalBase:
else:
await self.db.execute(delete(self.model).where(self.model.id.in_(ids)))
def add_filter_condition(self, sql: select, v_join_query: dict = None, v_options: list = None, **kwargs) -> select:
def add_filter_condition(
self,
sql: select,
v_options: list = None,
v_join_query: dict = None,
v_or: List[tuple] = None,
**kwargs
) -> select:
"""
添加过滤条件以及内连接过滤条件
:param sql:
:param v_join_query: 外键字段查询内连接
:param v_options: 指示应使用select在预加载中加载给定的属性
:param v_join_query: 外键字段查询内连接
:param v_or: 或逻辑
:param kwargs: 关键词参数
"""
if v_join_query and self.key_models:
v_join: Set[str] = set()
v_join_left: Set[str] = set()
if v_join_query:
for key, value in v_join_query.items():
foreign_key = self.key_models.get(key)
if foreign_key and foreign_key.get("model"):
conditions = []
self.__dict_filter(conditions, foreign_key.get("model"), **value)
if conditions:
sql = sql.where(*conditions)
v_join.add(key)
if v_or:
sql = self.__or_filter(sql, v_or, v_join_left, v_join)
for item in v_join:
foreign_key = self.key_models.get(item)
# 当外键模型在查询模型中存在多个外键时则需要添加onclause属性
sql = sql.join(foreign_key.get("model"), onclause=foreign_key.get("onclause", None))
for v_key, v_value in value.items():
if v_value is not None and v_value != "":
v_attr = getattr(foreign_key.get("model"), v_key, None)
sql = self.filter_condition(sql, v_attr, v_value)
else:
logger.error(f"外键查询报错:{key}模型不存在,无法进行下一步查询。")
elif v_join_query and not self.key_models:
logger.error(f"外键查询报错key_models 外键模型无配置项,无法进行下一步查询。")
for field in kwargs:
value = kwargs.get(field)
if value is not None and value != "":
attr = getattr(self.model, field, None)
sql = self.filter_condition(sql, attr, value)
sql = sql.join(foreign_key.get("model"), onclause=foreign_key.get("onclause"))
for item in v_join_left:
foreign_key = self.key_models.get(item)
# 当外键模型在查询模型中存在多个外键时则需要添加onclause属性
sql = sql.outerjoin(foreign_key.get("model"), onclause=foreign_key.get("onclause"))
conditions = []
self.__dict_filter(conditions, self.model, **kwargs)
if conditions:
sql = sql.where(*conditions)
if v_options:
sql = sql.options(*[load for load in v_options])
return sql
@classmethod
def filter_condition(cls, sql: Any, attr: Any, value: Any):
def __or_filter(self, sql: select, v_or: List[tuple], v_join_left: Set[str], v_join: Set[str]):
"""
过滤条件
或逻辑操作
:param sql:
:param v_or: 或逻辑
:param v_join_left: 左连接
:param v_join: 内连接
"""
if not attr:
or_list = []
for item in v_or:
if len(item) == 2:
model = self.model
condition = {item[0]: item[1]}
self.__dict_filter(or_list, model, **condition)
elif len(item) == 4 and item[0] == "fk":
model = self.key_models.get(item[1]).get("model")
condition = {item[2]: item[3]}
conditions = []
self.__dict_filter(conditions, model, **condition)
if conditions:
or_list = or_list + conditions
v_join_left.add(item[1])
if item[1] in v_join:
v_join.remove(item[1])
else:
raise CustomException(msg="v_or 获取查询属性失败,语法错误!")
if or_list:
sql = sql.where(or_(i for i in or_list))
return sql
def __dict_filter(self, conditions: list, model, **kwargs):
"""
字典过滤
:param model:
:param kwargs:
"""
for field, value in kwargs.items():
if value is not None and value != "":
attr = getattr(model, field)
if isinstance(value, tuple):
if len(value) == 1:
if value[0] == "None":
sql = sql.where(attr.is_(None))
conditions.append(attr.is_(None))
elif value[0] == "not None":
sql = sql.where(attr.isnot(None))
elif len(value) == 2 and value[1] is not None:
conditions.append(attr.isnot(None))
else:
raise CustomException("SQL查询语法错误")
elif len(value) == 2 and value[1] not in [None, [], ""]:
if value[0] == "date":
# 根据日期查询, 关键函数是func.time_format和func.date_format
sql = sql.where(func.date_format(attr, "%Y-%m-%d") == value[1])
conditions.append(func.date_format(attr, "%Y-%m-%d") == value[1])
elif value[0] == "like":
sql = sql.where(attr.like(f"%{value[1]}%"))
elif value[0] == "or":
sql = sql.where(or_(i for i in value[1]))
conditions.append(attr.like(f"%{value[1]}%"))
elif value[0] == "in":
sql = sql.where(attr.in_(value[1]))
conditions.append(attr.in_(value[1]))
elif value[0] == "between" and len(value[1]) == 2:
sql = sql.where(attr.between(value[1][0], value[1][1]))
conditions.append(attr.between(value[1][0], value[1][1]))
elif value[0] == "month":
sql = sql.where(func.date_format(attr, "%Y-%m") == value[1])
conditions.append(func.date_format(attr, "%Y-%m") == value[1])
elif value[0] == "!=":
sql = sql.where(attr != value[1])
conditions.append(attr != value[1])
elif value[0] == ">":
sql = sql.where(attr > value[1])
conditions.append(attr > value[1])
else:
sql = sql.where(attr == value)
return sql
raise CustomException("SQL查询语法错误")
else:
conditions.append(attr == value)
async def flush(self, obj: Any = None):
"""

View File

@ -37,6 +37,17 @@ class Telephone(str):
return vali_telephone(v)
class Email(str):
@classmethod
def __get_validators__(cls):
yield cls.validate
@classmethod
def validate(cls, v):
return vali_email(v)
class DateStr(str):
@classmethod

View File

@ -16,7 +16,8 @@ from core.logger import logger
class CustomException(Exception):
def __init__(self, msg: str, code: int, status_code: int = status.HTTP_200_OK):
def __init__(self, msg: str, code: int = status.HTTP_400_BAD_REQUEST, status_code: int = status.HTTP_200_OK):
self.msg = msg
self.code = code
self.status_code = status_code
@ -33,7 +34,7 @@ def register_exception(app: FastAPI):
自定义异常
"""
return JSONResponse(
status_code=200,
status_code=exc.status_code,
content={"message": exc.msg, "code": exc.code},
)

View File

@ -24,5 +24,6 @@ error = logger.add(log_path_error, rotation="00:00", retention="3 days", enqueue
if __name__ == '__main__':
print(BASE_DIR)
logger.info("hell")
logger.error("hell")
# logger.info("1")
retry: int = 1
logger.error("未从Redis中获取到配置信息正在重新更新配置信息重试次数{}".format(retry))

View File

@ -22,13 +22,30 @@ def vali_telephone(value: str) -> str:
if not value or len(value) != 11 or not value.isdigit():
raise ValueError("请输入正确手机号")
REGEX_TELEPHONE = r'^1(3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8[0-9]|9[0-9])\d{8}$'
regex = r'^1(3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8[0-9]|9[0-9])\d{8}$'
if not re.match(REGEX_TELEPHONE, value):
if not re.match(regex, value):
raise ValueError("请输入正确手机号")
return value
def vali_email(value: str) -> str:
"""
邮箱地址验证器
:param value: 邮箱
:return: 邮箱
"""
if not value:
raise ValueError("请输入邮箱地址")
regex = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
if not re.match(regex, value):
raise ValueError("请输入正确邮箱地址")
return value

View File

@ -42,13 +42,13 @@ class Cache:
"""
获取系统配置
:params tab_name: 配置表标签名称
:params retry: 重试次数
:params retry_num: 重试次数
"""
result = await self.rd.get(tab_name)
if not result and retry > 0:
logger.error(f"未从Redis中获取到{tab_name}配置信息,正在重新更新配置信息,重试次数:{retry}")
logger.error(f"未从Redis中获取到{tab_name}配置信息,正在重新更新配置信息,重试次数:{retry}")
await self.cache_tab_names([tab_name])
await self.get_tab_name(tab_name, retry - 1)
return await self.get_tab_name(tab_name, retry - 1)
elif not result and retry == 0:
raise CustomException(f"获取{tab_name}配置信息失败,请联系管理员!", code=status.HTTP_ERROR)
else:

View File

@ -6,7 +6,6 @@
# @IDE : PyCharm
# @desc : 简要说明
import json
import requests
from core.logger import logger
from utils.cache import Cache