版本更新:
1. 新增(kinit-api,kinit-admin):支持发送邮箱通知 2. 优化(kinit-api):core/crud.py 优化
This commit is contained in:
parent
0152dc9c70
commit
2ba2738107
@ -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 })
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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({
|
||||
|
@ -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
|
||||
},
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -11,7 +11,7 @@ from fastapi.security import OAuth2PasswordBearer
|
||||
"""
|
||||
系统版本
|
||||
"""
|
||||
VERSION = "1.7.1"
|
||||
VERSION = "1.7.2"
|
||||
|
||||
"""安全警告: 不要在生产中打开调试运行!"""
|
||||
DEBUG = True
|
||||
|
@ -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):
|
||||
"""
|
||||
更新当前用户头像
|
||||
|
@ -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="密码")
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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,72 +195,118 @@ 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"):
|
||||
# 当外键模型在查询模型中存在多个外键时,则需要添加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)
|
||||
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"))
|
||||
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:
|
||||
return sql
|
||||
if isinstance(value, tuple):
|
||||
if len(value) == 1:
|
||||
if value[0] == "None":
|
||||
sql = sql.where(attr.is_(None))
|
||||
elif value[0] == "not None":
|
||||
sql = sql.where(attr.isnot(None))
|
||||
elif len(value) == 2 and value[1] is not None:
|
||||
if value[0] == "date":
|
||||
# 根据日期查询, 关键函数是:func.time_format和func.date_format
|
||||
sql = sql.where(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]))
|
||||
elif value[0] == "in":
|
||||
sql = sql.where(attr.in_(value[1]))
|
||||
elif value[0] == "between" and len(value[1]) == 2:
|
||||
sql = sql.where(attr.between(value[1][0], value[1][1]))
|
||||
elif value[0] == "month":
|
||||
sql = sql.where(func.date_format(attr, "%Y-%m") == value[1])
|
||||
elif value[0] == "!=":
|
||||
sql = sql.where(attr != value[1])
|
||||
elif value[0] == ">":
|
||||
sql = sql.where(attr > value[1])
|
||||
else:
|
||||
sql = sql.where(attr == value)
|
||||
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":
|
||||
conditions.append(attr.is_(None))
|
||||
elif value[0] == "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
|
||||
conditions.append(func.date_format(attr, "%Y-%m-%d") == value[1])
|
||||
elif value[0] == "like":
|
||||
conditions.append(attr.like(f"%{value[1]}%"))
|
||||
elif value[0] == "in":
|
||||
conditions.append(attr.in_(value[1]))
|
||||
elif value[0] == "between" and len(value[1]) == 2:
|
||||
conditions.append(attr.between(value[1][0], value[1][1]))
|
||||
elif value[0] == "month":
|
||||
conditions.append(func.date_format(attr, "%Y-%m") == value[1])
|
||||
elif value[0] == "!=":
|
||||
conditions.append(attr != value[1])
|
||||
elif value[0] == ">":
|
||||
conditions.append(attr > value[1])
|
||||
else:
|
||||
raise CustomException("SQL查询语法错误")
|
||||
else:
|
||||
conditions.append(attr == value)
|
||||
|
||||
async def flush(self, obj: Any = None):
|
||||
"""
|
||||
刷新到数据库
|
||||
|
@ -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
|
||||
|
@ -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},
|
||||
)
|
||||
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
|
Binary file not shown.
@ -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:
|
||||
|
@ -6,7 +6,6 @@
|
||||
# @IDE : PyCharm
|
||||
# @desc : 简要说明
|
||||
|
||||
import json
|
||||
import requests
|
||||
from core.logger import logger
|
||||
from utils.cache import Cache
|
||||
|
Loading…
x
Reference in New Issue
Block a user