1. 更新:kinit-api:core/crud.py keys 查询多外键时需添加onclause属性

2. 修复:kinit-admin: form属性设置了ifshow无法设置值问题修复
3. 修复:kinit-admin:表格字段设置缓存问题修复
This commit is contained in:
ktianc 2023-02-08 11:11:28 +08:00
parent 1df58c937a
commit afa9110771
29 changed files with 416 additions and 221 deletions

View File

@ -162,15 +162,7 @@ export default defineComponent({
if (v.hidden !== undefined) {
return !v.hidden
} else if (v.ifshow !== undefined) {
const show = v.ifshow(unref(formModel))
if (!show) {
if (v.value !== undefined) {
formModel.value[v.field] = v.value
} else {
formModel.value[v.field] = undefined
}
}
return show
return v.ifshow(unref(formModel))
}
return true
})

View File

@ -15,7 +15,9 @@ import { ref, PropType } from 'vue'
import { propTypes } from '@/utils/propTypes'
import draggable from 'vuedraggable'
import { useAppStore } from '@/store/modules/app'
import { useCache } from '@/hooks/web/useCache'
const { wsCache } = useCache()
const appStore = useAppStore()
const emit = defineEmits(['getList', 'update:tableSize'])
@ -25,7 +27,8 @@ const props = defineProps({
columns: {
type: Array as PropType<any[]>,
default: () => []
}
},
cacheTableHeadersKey: propTypes.string.def('')
})
const handleClickRefresh = () => {
@ -43,6 +46,15 @@ const handleCommand = (command: string) => {
const checkAll = ref(false)
const columns = ref(props.columns)
const isIndeterminate = ref(true) // True
//
if (props.cacheTableHeadersKey) {
const cacheTableHeaders = wsCache.get(props.cacheTableHeadersKey)
if (cacheTableHeaders) {
Object.assign(columns.value, JSON.parse(cacheTableHeaders))
}
}
const handleCheckAllChange = (val: boolean) => {
columns.value.forEach((item) => {
if (item.disabled !== true) {
@ -51,6 +63,7 @@ const handleCheckAllChange = (val: boolean) => {
})
isIndeterminate.value = false
}
const handleCheckChange = () => {
checkAll.value = columns.value.every((item) => item.show)
if (checkAll.value) {

View File

@ -17,7 +17,10 @@ import Write from './components/Write.vue'
import { columns } from './components/menu.data'
import { useDictStore } from '@/store/modules/dict'
import { selectDictLabel, DictDetail } from '@/utils/dict'
import { useCache } from '@/hooks/web/useCache'
import { useRouter } from 'vue-router'
const { wsCache } = useCache()
const { t } = useI18n()
let menuTypeOptions = ref<DictDetail[]>([])
@ -117,9 +120,13 @@ watch(tableSize, (val) => {
tableSize.value = val
})
const route = useRouter()
const cacheTableHeadersKey = route.currentRoute.value.fullPath
watch(
columns,
async () => {
async (val) => {
wsCache.set(cacheTableHeadersKey, JSON.stringify(val))
await nextTick()
elTableRef.value?.doLayout()
},
@ -139,7 +146,12 @@ watch(
>
</ElCol>
</ElRow>
<RightToolbar @get-list="getList" v-model:table-size="tableSize" v-model:columns="columns" />
<RightToolbar
@get-list="getList"
v-model:table-size="tableSize"
v-model:columns="columns"
:cache-table-headers-key="cacheTableHeadersKey"
/>
</div>
<Table

View File

@ -17,7 +17,10 @@ import { Dialog } from '@/components/Dialog'
import { useI18n } from '@/hooks/web/useI18n'
import { RightToolbar } from '@/components/RightToolbar'
import { Search } from '@/components/Search'
import { useCache } from '@/hooks/web/useCache'
import { useRouter } from 'vue-router'
const { wsCache } = useCache()
const { t } = useI18n()
const { register, elTableRef, tableObject, methods } = useTable({
@ -36,7 +39,7 @@ const loading = ref(false)
const defaultCheckedKeys = ref([])
//
const AddAction = () => {
const addAction = () => {
dialogTitle.value = t('exampleDemo.add')
tableObject.currentRow = null
dialogVisible.value = true
@ -50,7 +53,6 @@ const updateAction = async (row: any) => {
dialogTitle.value = '编辑'
tableObject.currentRow = res.data
defaultCheckedKeys.value = res.data.menus.map((item: any) => item.id)
console.log(defaultCheckedKeys.value)
dialogVisible.value = true
actionType.value = 'edit'
}
@ -103,9 +105,13 @@ watch(tableSize, (val) => {
tableSize.value = val
})
const route = useRouter()
const cacheTableHeadersKey = route.currentRoute.value.fullPath
watch(
columns,
async () => {
async (val) => {
wsCache.set(cacheTableHeadersKey, JSON.stringify(val))
await nextTick()
elTableRef.value?.doLayout()
},
@ -122,10 +128,15 @@ watch(
<div class="mb-8px flex justify-between">
<ElRow :gutter="10">
<ElCol :span="1.5" v-hasPermi="['auth.role.create']">
<ElButton type="primary" @click="AddAction">新增角色</ElButton>
<ElButton type="primary" @click="addAction">新增角色</ElButton>
</ElCol>
</ElRow>
<RightToolbar @get-list="getList" v-model:table-size="tableSize" v-model:columns="columns" />
<RightToolbar
@get-list="getList"
v-model:table-size="tableSize"
v-model:columns="columns"
:cache-table-headers-key="cacheTableHeadersKey"
/>
</div>
<Table

View File

@ -34,7 +34,10 @@ import { useAuthStoreWithOut } from '@/store/modules/auth'
import { RightToolbar } from '@/components/RightToolbar'
import { Search } from '@/components/Search'
import { useAppStore } from '@/store/modules/app'
import { useCache } from '@/hooks/web/useCache'
import { useRouter } from 'vue-router'
const { wsCache } = useCache()
const appStore = useAppStore()
const { t } = useI18n()
@ -72,7 +75,7 @@ const loading = ref(false)
const { getList, setSearchParams, exportQueryList } = methods
//
const AddAction = () => {
const addAction = () => {
dialogTitle.value = t('exampleDemo.add')
tableObject.currentRow = null
dialogVisible.value = true
@ -147,9 +150,13 @@ watch(tableSize, (val) => {
tableSize.value = val
})
const route = useRouter()
const cacheTableHeadersKey = route.currentRoute.value.fullPath
watch(
columns,
async () => {
async (val) => {
wsCache.set(cacheTableHeadersKey, JSON.stringify(val))
await nextTick()
elTableRef.value?.doLayout()
},
@ -209,7 +216,7 @@ const handleCommand = (command: string) => {
<div class="mb-8px flex justify-between">
<ElRow :gutter="10">
<ElCol :span="1.5" v-hasPermi="['auth.user.create']">
<ElButton type="primary" @click="AddAction">新增用户</ElButton>
<ElButton type="primary" @click="addAction">新增用户</ElButton>
</ElCol>
<ElCol :span="1.5" v-hasPermi="['auth.user.import']" v-if="!mobile">
<ElButton @click="importList">批量导入用户</ElButton>
@ -250,7 +257,12 @@ const handleCommand = (command: string) => {
</ElDropdown>
</ElCol>
</ElRow>
<RightToolbar @get-list="getList" v-model:table-size="tableSize" v-model:columns="columns" />
<RightToolbar
@get-list="getList"
v-model:table-size="tableSize"
v-model:columns="columns"
:cache-table-headers-key="cacheTableHeadersKey"
/>
</div>
<Table

View File

@ -20,9 +20,10 @@ import { useI18n } from '@/hooks/web/useI18n'
import { useRouter } from 'vue-router'
import { Search } from '@/components/Search'
import { FormSetPropsType } from '@/types/form'
import { useCache } from '@/hooks/web/useCache'
const { wsCache } = useCache()
const { currentRoute } = useRouter()
const { t } = useI18n()
let dictType = currentRoute.value.query.dictType
@ -64,7 +65,7 @@ const actionType = ref('')
const loading = ref(false)
//
const AddAction = () => {
const addAction = () => {
dialogTitle.value = t('exampleDemo.add')
tableObject.currentRow = null
dialogVisible.value = true
@ -126,9 +127,13 @@ const tableSize = ref('default')
watch(tableSize, (val) => {
tableSize.value = val
})
const cacheTableHeadersKey = currentRoute.value.fullPath
watch(
columns,
async () => {
async (val) => {
wsCache.set(cacheTableHeadersKey, JSON.stringify(val))
await nextTick()
elTableRef.value?.doLayout()
},
@ -150,10 +155,15 @@ watch(
<div class="mb-8px flex justify-between">
<ElRow :gutter="10">
<ElCol :span="1.5">
<ElButton type="primary" @click="AddAction">{{ t('exampleDemo.add') }}</ElButton>
<ElButton type="primary" @click="addAction">{{ t('exampleDemo.add') }}</ElButton>
</ElCol>
</ElRow>
<RightToolbar @get-list="getList" v-model:table-size="tableSize" v-model:columns="columns" />
<RightToolbar
@get-list="getList"
v-model:table-size="tableSize"
v-model:columns="columns"
:cache-table-headers-key="cacheTableHeadersKey"
/>
</div>
<Table

View File

@ -18,9 +18,10 @@ import { ElButton, ElMessage, ElRow, ElCol } from 'element-plus'
import { useI18n } from '@/hooks/web/useI18n'
import { useRouter } from 'vue-router'
import { Search } from '@/components/Search'
import { useCache } from '@/hooks/web/useCache'
const { wsCache } = useCache()
const { push } = useRouter()
const { t } = useI18n()
const { register, elTableRef, tableObject, methods } = useTable({
@ -38,7 +39,7 @@ const actionType = ref('')
const loading = ref(false)
//
const AddAction = () => {
const addAction = () => {
dialogTitle.value = t('exampleDemo.add')
tableObject.currentRow = null
dialogVisible.value = true
@ -104,9 +105,14 @@ const tableSize = ref('default')
watch(tableSize, (val) => {
tableSize.value = val
})
const route = useRouter()
const cacheTableHeadersKey = route.currentRoute.value.fullPath
watch(
columns,
async () => {
async (val) => {
wsCache.set(cacheTableHeadersKey, JSON.stringify(val))
await nextTick()
elTableRef.value?.doLayout()
},
@ -123,10 +129,15 @@ watch(
<div class="mb-8px flex justify-between">
<ElRow :gutter="10">
<ElCol :span="1.5">
<ElButton type="primary" @click="AddAction">{{ t('exampleDemo.add') }}</ElButton>
<ElButton type="primary" @click="addAction">{{ t('exampleDemo.add') }}</ElButton>
</ElCol>
</ElRow>
<RightToolbar @get-list="getList" v-model:table-size="tableSize" v-model:columns="columns" />
<RightToolbar
@get-list="getList"
v-model:table-size="tableSize"
v-model:columns="columns"
:cache-table-headers-key="cacheTableHeadersKey"
/>
</div>
<Table

View File

@ -13,6 +13,10 @@ import { Search } from '@/components/Search'
import { selectDictLabel, DictDetail } from '@/utils/dict'
import { useDictStore } from '@/store/modules/dict'
import { FormSetPropsType } from '@/types/form'
import { useCache } from '@/hooks/web/useCache'
import { useRouter } from 'vue-router'
const { wsCache } = useCache()
const { register, elTableRef, tableObject, methods } = useTable({
getListApi: getRecordLoginListApi,
@ -60,9 +64,13 @@ watch(tableSize, (val) => {
tableSize.value = val
})
const route = useRouter()
const cacheTableHeadersKey = route.currentRoute.value.fullPath
watch(
columns,
async () => {
async (val) => {
wsCache.set(cacheTableHeadersKey, JSON.stringify(val))
await nextTick()
elTableRef.value?.doLayout()
},
@ -85,7 +93,12 @@ getList()
<div class="mb-8px flex justify-between">
<ElRow />
<RightToolbar @get-list="getList" v-model:table-size="tableSize" v-model:columns="columns" />
<RightToolbar
@get-list="getList"
v-model:table-size="tableSize"
v-model:columns="columns"
:cache-table-headers-key="cacheTableHeadersKey"
/>
</div>
<Table

View File

@ -10,6 +10,10 @@ import { RightToolbar } from '@/components/RightToolbar'
import { Dialog } from '@/components/Dialog'
import Detail from './components/Detail.vue'
import { Search } from '@/components/Search'
import { useCache } from '@/hooks/web/useCache'
import { useRouter } from 'vue-router'
const { wsCache } = useCache()
const { register, elTableRef, tableObject, methods } = useTable({
getListApi: getRecordOperationListApi,
@ -38,9 +42,13 @@ watch(tableSize, (val) => {
tableSize.value = val
})
const route = useRouter()
const cacheTableHeadersKey = route.currentRoute.value.fullPath
watch(
columns,
async () => {
async (val) => {
wsCache.set(cacheTableHeadersKey, JSON.stringify(val))
await nextTick()
elTableRef.value?.doLayout()
},
@ -56,7 +64,12 @@ watch(
<div class="mb-8px flex justify-between">
<ElRow />
<RightToolbar @get-list="getList" v-model:table-size="tableSize" v-model:columns="columns" />
<RightToolbar
@get-list="getList"
v-model:table-size="tableSize"
v-model:columns="columns"
:cache-table-headers-key="cacheTableHeadersKey"
/>
</div>
<Table

View File

@ -10,7 +10,6 @@ import type { UploadProps } from 'element-plus'
import { useCache } from '@/hooks/web/useCache'
import { useAppStore } from '@/store/modules/app'
import { propTypes } from '@/utils/propTypes'
import { config } from '@/config/axios/config'
const props = defineProps({
tabId: propTypes.number
@ -80,9 +79,8 @@ const getData = async () => {
const appStore = useAppStore()
const { wsCache } = useCache()
const loading = ref(false)
const { token } = config
const _token = wsCache.get(token)
const token = wsCache.get(appStore.getToken)
const save = async () => {
const formRef = unref(elFormRef)
@ -122,7 +120,7 @@ getData()
:on-success="handleLogoUploadSuccess"
accept="image/jpeg,image/gif,image/png"
name="file"
:headers="{ Authorization: _token }"
:headers="{ Authorization: token }"
>
<img v-if="form.web_logo" :src="form.web_logo" class="logo-image" />
<ElIcon v-else class="logo-image-uploader-icon"
@ -141,7 +139,7 @@ getData()
:on-success="handleICOUploadSuccess"
accept="image/x-icon"
name="file"
:headers="{ Authorization: _token }"
:headers="{ Authorization: token }"
>
<img v-if="form.web_ico" :src="form.web_ico" class="ico-image" />
<ElIcon v-else class="ico-image-uploader-icon"

View File

@ -6,7 +6,7 @@
# @IDE : PyCharm
# @desc : 增删改查
from typing import List
from typing import List, Any
from aioredis import Redis
from fastapi import UploadFile
from core.exception import CustomException
@ -42,7 +42,13 @@ class UserDal(DalBase):
def __init__(self, db: AsyncSession):
super(UserDal, self).__init__(db, models.VadminUser, schemas.UserSimpleOut)
async def create_data(self, data: schemas.UserIn, return_obj: bool = False, options: list = None, schema=None):
async def create_data(
self,
data: schemas.UserIn,
v_options: list = None,
v_return_obj: bool = False,
v_schema: Any = None
):
"""
创建用户
"""
@ -55,12 +61,12 @@ class UserDal(DalBase):
for data_id in data.role_ids:
obj.roles.append(await RoleDal(db=self.db).get_data(data_id=data_id))
await self.flush(obj)
if options:
obj = await self.get_data(obj.id, options=options)
if return_obj:
if v_options:
obj = await self.get_data(obj.id, v_options=v_options)
if v_return_obj:
return obj
if schema:
return schema.from_orm(obj).dict()
if v_schema:
return v_schema.from_orm(obj).dict()
return self.out_dict(obj)
async def reset_current_password(self, user: models.VadminUser, data: schemas.ResetPwd):
@ -225,24 +231,36 @@ class RoleDal(DalBase):
def __init__(self, db: AsyncSession):
super(RoleDal, self).__init__(db, models.VadminRole, schemas.RoleSimpleOut)
async def create_data(self, data: schemas.RoleIn, return_obj: bool = False, options: list = None, schema=None):
async def create_data(
self,
data: schemas.RoleIn,
v_options: list = None,
v_return_obj: bool = False,
v_schema: Any = None
):
"""创建数据"""
obj = self.model(**data.dict(exclude={'menu_ids'}))
for data_id in data.menu_ids:
obj.menus.append(await MenuDal(db=self.db).get_data(data_id=data_id))
obj.menus.append(await MenuDal(db=self.db).get_data(data_id))
await self.flush(obj)
if options:
obj = await self.get_data(obj.id, options=options)
if return_obj:
if v_options:
obj = await self.get_data(obj.id, v_options=v_options)
if v_return_obj:
return obj
if schema:
return schema.from_orm(obj).dict()
if v_schema:
return v_schema.from_orm(obj).dict()
return self.out_dict(await self.get_data(obj.id))
async def put_data(self, data_id: int, data: schemas.RoleIn, return_obj: bool = False, options: list = None,
schema=None):
async def put_data(
self,
data_id: int,
data: schemas.RoleIn,
v_options: list = None,
v_return_obj: bool = False,
v_schema: Any = None
):
"""更新单个数据"""
obj = await self.get_data(data_id, options=[self.model.menus])
obj = await self.get_data(data_id, v_options=[self.model.menus])
obj_dict = jsonable_encoder(data)
for key, value in obj_dict.items():
if key == "menu_ids":
@ -254,14 +272,14 @@ class RoleDal(DalBase):
setattr(obj, key, value)
await self.db.flush()
await self.db.refresh(obj)
if return_obj:
if v_return_obj:
return obj
if schema:
return schema.from_orm(obj).dict()
if v_schema:
return v_schema.from_orm(obj).dict()
return self.out_dict(obj)
async def get_role_menu_tree(self, role_id: int):
role = await self.get_data(role_id, options=[self.model.menus])
role = await self.get_data(role_id, v_options=[self.model.menus])
return [i.id for i in role.menus]
async def get_select_datas(self):
@ -283,9 +301,9 @@ class MenuDal(DalBase):
3获取菜单树列表角色添加菜单权限时使用
"""
if mode == 3:
sql = select(self.model).where(self.model.disabled == 0)
sql = select(self.model).where(self.model.disabled == 0, self.model.delete_datetime.is_(None))
else:
sql = select(self.model)
sql = select(self.model).where(self.model.delete_datetime.is_(None))
queryset = await self.db.execute(sql)
datas = queryset.scalars().all()
roots = filter(lambda i: not i.parent_id, datas)
@ -310,13 +328,14 @@ class MenuDal(DalBase):
}
"""
if any([i.is_admin for i in user.roles]):
sql = select(self.model).where(self.model.disabled == 0, self.model.menu_type != "2")
sql = select(self.model)\
.where(self.model.disabled == 0, self.model.menu_type != "2", self.model.delete_datetime.is_(None))
queryset = await self.db.execute(sql)
datas = queryset.scalars().all()
else:
datas = set()
for role in user.roles:
role_obj = await RoleDal(self.db).get_data(role.id, options=[models.VadminRole.menus])
role_obj = await RoleDal(self.db).get_data(role.id, v_options=[models.VadminRole.menus])
for menu in role_obj.menus:
# 该路由没有被禁用,并且菜单不是按钮
if not menu.disabled and menu.menu_type != "2":

View File

@ -18,8 +18,13 @@ class UserParams(QueryParams):
列表分页
"""
def __init__(self, name: str = None, telephone: str = None, is_active: bool | str = None,
params: Paging = Depends()):
def __init__(
self,
name: str = None,
telephone: str = None,
is_active: bool | str = None,
params: Paging = Depends()
):
super().__init__(params)
self.name = ("like", name)
self.telephone = ("like", telephone)

View File

@ -46,5 +46,5 @@ async def full_admin(telephone: str, db: AsyncSession):
如果令牌无效立即返回一个 HTTP 错误
"""
options = [models.VadminUser.roles, "roles.menus"]
return await crud.UserDal(db).get_data(telephone=telephone, v_return_none=True, options=options)
return await crud.UserDal(db).get_data(telephone=telephone, v_return_none=True, v_options=options)

View File

@ -35,8 +35,12 @@ app = APIRouter()
@app.post("/login/", summary="登录")
async def login_for_access_token(request: Request, data: LoginForm, manage: LoginManage = Depends(),
db: AsyncSession = Depends(db_getter)):
async def login_for_access_token(
request: Request,
data: LoginForm,
manage: LoginManage = Depends(),
db: AsyncSession = Depends(db_getter)
):
if data.method == "0":
result = await manage.password_login(data, db, request)
elif data.method == "1":
@ -45,8 +49,8 @@ async def login_for_access_token(request: Request, data: LoginForm, manage: Logi
return ErrorResponse(msg="请使用正确的登录方式")
if not result.status:
resp = {"message": result.msg}
await VadminLoginRecord.\
create_login_record(db, data, result.status, request, resp)
await VadminLoginRecord\
.create_login_record(db, data, result.status, request, resp)
return ErrorResponse(msg=result.msg)
user = result.user

View File

@ -33,8 +33,12 @@ class AuthValidation:
def __init__(self, func):
self.func = func
async def __call__(self, request: Request, token: str = Depends(settings.oauth2_scheme),
db: AsyncSession = Depends(db_getter)):
async def __call__(
self,
request: Request,
token: str = Depends(settings.oauth2_scheme),
db: AsyncSession = Depends(db_getter)
):
if not settings.OAUTH_ENABLE:
return Auth(db=db)
if not token:

View File

@ -46,7 +46,7 @@ class LoginValidation:
async def __call__(self, data: LoginForm, db: AsyncSession, request: Request) -> LoginResult:
self.result = LoginResult()
options = [models.VadminUser.roles, "roles.menus"]
user = await crud.UserDal(db).get_data(telephone=data.telephone, v_return_none=True, options=options)
user = await crud.UserDal(db).get_data(telephone=data.telephone, v_return_none=True, v_options=options)
if not user:
self.result.msg = "该手机号不存在!"
return self.result

View File

@ -51,7 +51,7 @@ async def get_user(data_id: int, auth: Auth = Depends(login_auth)):
model = models.VadminUser
options = [model.roles]
schema = schemas.UserOut
return SuccessResponse(await crud.UserDal(auth.db).get_data(data_id, options, schema))
return SuccessResponse(await crud.UserDal(auth.db).get_data(data_id, options, v_schema=schema))
@app.post("/user/current/reset/password/", summary="重置当前用户密码")
@ -77,8 +77,11 @@ async def get_user_current_info(auth: Auth = Depends(full_admin)):
@app.post("/user/export/query/list/to/excel/", summary="导出用户查询列表为excel")
async def post_user_export_query_list(header: list = Body(..., title="表头与对应字段"), params: UserParams = Depends(),
auth: Auth = Depends(login_auth)):
async def post_user_export_query_list(
header: list = Body(..., title="表头与对应字段"),
params: UserParams = Depends(),
auth: Auth = Depends(login_auth)
):
return SuccessResponse(await crud.UserDal(auth.db).export_query_list(header, params))
@ -137,7 +140,7 @@ async def get_role(data_id: int, auth: Auth = Depends(login_auth)):
model = models.VadminRole
options = [model.menus]
schema = schemas.RoleOut
return SuccessResponse(await crud.RoleDal(auth.db).get_data(data_id, options, schema))
return SuccessResponse(await crud.RoleDal(auth.db).get_data(data_id, options, v_schema=schema))
###########################################################
@ -181,7 +184,7 @@ async def put_menus(data_id: int, data: schemas.Menu, auth: Auth = Depends(login
@app.get("/menus/{data_id}/", summary="获取菜单信息")
async def put_menus(data_id: int, auth: Auth = Depends(login_auth)):
schema = schemas.MenuSimpleOut
return SuccessResponse(await crud.MenuDal(auth.db).get_data(data_id, None, schema))
return SuccessResponse(await crud.MenuDal(auth.db).get_data(data_id, None, v_schema=schema))
@app.get("/role/menus/tree/{role_id}/", summary="获取菜单列表树信息以及角色菜单权限ID角色权限使用")

View File

@ -69,5 +69,6 @@ class LoginRecordDal(DalBase):
class SMSSendRecordDal(DalBase):
def __init__(self, db: AsyncSession):
super(SMSSendRecordDal, self).__init__(db, models.VadminSMSSendRecord, schemas.SMSSendRecordSimpleOut)

View File

@ -57,8 +57,16 @@ class VadminLoginRecord(BaseModel):
ip = IPManage(req.client.host)
location = await ip.parse()
params = json.dumps({"body": body, "headers": header})
obj = VadminLoginRecord(**location.dict(), telephone=data.telephone, status=status, browser=browser,
system=system, response=json.dumps(resp), request=params, platform=data.platform,
login_method=data.method)
obj = VadminLoginRecord(
**location.dict(),
telephone=data.telephone,
status=status,
browser=browser,
system=system,
response=json.dumps(resp),
request=params,
platform=data.platform,
login_method=data.method
)
db.add(obj)
await db.flush()

View File

@ -19,24 +19,24 @@ app = APIRouter()
# 日志管理
###########################################################
@app.get("/logins/", summary="获取登录日志列表")
async def get_record_login(params: LoginParams = Depends(), auth: Auth = Depends(login_auth)):
datas = await crud.LoginRecordDal(auth.db).get_datas(**params.dict())
count = await crud.LoginRecordDal(auth.db).get_count(**params.to_count())
async def get_record_login(p: LoginParams = Depends(), auth: Auth = Depends(login_auth)):
datas = await crud.LoginRecordDal(auth.db).get_datas(**p.dict())
count = await crud.LoginRecordDal(auth.db).get_count(**p.to_count())
return SuccessResponse(datas, count=count)
@app.get("/operations/", summary="获取操作日志列表")
async def get_record_operation(params: OperationParams = Depends(), db: DatabaseManage = Depends(get_database),
async def get_record_operation(p: OperationParams = Depends(), db: DatabaseManage = Depends(get_database),
auth: Auth = Depends(login_auth)):
count = await db.get_count("operation_record", **params.to_count())
datas = await db.get_datas("operation_record", schema=schemas.OpertionRecordSimpleOut, **params.dict())
count = await db.get_count("operation_record", **p.to_count())
datas = await db.get_datas("operation_record", v_schema=schemas.OpertionRecordSimpleOut, **p.dict())
return SuccessResponse(datas, count=count)
@app.get("/sms/send/list/", summary="获取短信发送列表")
async def get_sms_send_list(params: SMSParams = Depends(), auth: Auth = Depends(login_auth)):
datas = await crud.SMSSendRecordDal(auth.db).get_datas(**params.dict())
count = await crud.SMSSendRecordDal(auth.db).get_count(**params.to_count())
async def get_sms_send_list(p: SMSParams = Depends(), auth: Auth = Depends(login_auth)):
datas = await crud.SMSSendRecordDal(auth.db).get_datas(**p.dict())
count = await crud.SMSSendRecordDal(auth.db).get_count(**p.to_count())
return SuccessResponse(datas, count=count)

View File

@ -30,8 +30,8 @@ class DictTypeDal(DalBase):
"""
data = {}
for dict_type in dict_types:
dict_data = await DictTypeDal(self.db).\
get_data(dict_type=dict_type, v_return_none=True, options=[self.model.details])
dict_data = await DictTypeDal(self.db)\
.get_data(dict_type=dict_type, v_return_none=True, v_options=[self.model.details])
if not dict_data:
data[dict_type] = []
continue
@ -105,8 +105,14 @@ class SettingsTabDal(DalBase):
"""
model = models.VadminSystemSettingsTab
options = [model.settings]
datas = await self.get_datas(limit=0, options=options, classify=("in", classify), disabled=False,
v_return_objs=True, hidden=hidden)
datas = await self.get_datas(
limit=0,
v_options=options,
classify=("in", classify),
disabled=False,
hidden=hidden,
v_return_objs=True
)
result = {}
for tab in datas:
tabs = {}
@ -115,4 +121,3 @@ class SettingsTabDal(DalBase):
tabs[item.config_key] = item.config_value
result[tab.tab_name] = tabs
return result

View File

@ -27,9 +27,9 @@ app = APIRouter()
# 字典类型管理
###########################################################
@app.get("/dict/types/", summary="获取字典类型列表")
async def get_dict_types(params: DictTypeParams = Depends(), auth: Auth = Depends(login_auth)):
datas = await crud.DictTypeDal(auth.db).get_datas(**params.dict())
count = await crud.DictTypeDal(auth.db).get_count(**params.to_count())
async def get_dict_types(p: DictTypeParams = Depends(), auth: Auth = Depends(login_auth)):
datas = await crud.DictTypeDal(auth.db).get_datas(**p.dict())
count = await crud.DictTypeDal(auth.db).get_count(**p.to_count())
return SuccessResponse(datas, count=count)
@ -45,16 +45,17 @@ async def delete_dict_types(ids: IdList = Depends(), auth: Auth = Depends(login_
@app.post("/dict/types/details/", summary="获取多个字典类型下的字典元素列表")
async def post_dicts_details(auth: Auth = Depends(login_auth),
dict_types: List[str] = Body(None, title="字典元素列表", description="查询字典元素列表")):
async def post_dicts_details(
auth: Auth = Depends(login_auth),
dict_types: List[str] = Body(None, title="字典元素列表", description="查询字典元素列表")
):
datas = await crud.DictTypeDal(auth.db).get_dicts_details(dict_types)
return SuccessResponse(datas)
@app.get("/dict/types/options/", summary="获取字典类型选择项")
async def get_dicts_options(auth: Auth = Depends(login_auth)):
datas = await crud.DictTypeDal(auth.db).get_select_datas()
return SuccessResponse(datas)
return SuccessResponse(await crud.DictTypeDal(auth.db).get_select_datas())
@app.put("/dict/types/{data_id}/", summary="更新字典类型")
@ -65,7 +66,7 @@ async def put_dict_types(data_id: int, data: schemas.DictType, auth: Auth = Depe
@app.get("/dict/types/{data_id}/", summary="获取字典类型详细")
async def get_dict_type(data_id: int, auth: Auth = Depends(login_auth)):
schema = schemas.DictTypeSimpleOut
return SuccessResponse(await crud.DictTypeDal(auth.db).get_data(data_id, None, schema))
return SuccessResponse(await crud.DictTypeDal(auth.db).get_data(data_id, None, v_schema=schema))
###########################################################
@ -99,7 +100,7 @@ async def put_dict_details(data_id: int, data: schemas.DictDetails, auth: Auth =
@app.get("/dict/details/{data_id}/", summary="获取字典元素详情")
async def get_dict_detail(data_id: int, auth: Auth = Depends(login_auth)):
schema = schemas.DictDetailsSimpleOut
return SuccessResponse(await crud.DictDetailsDal(auth.db).get_data(data_id, None, schema))
return SuccessResponse(await crud.DictDetailsDal(auth.db).get_data(data_id, None, v_schema=schema))
###########################################################

View File

@ -13,6 +13,7 @@
# SQLAlchemy join 内连接
# selectinload 官方文档:
# https://www.osgeo.cn/sqlalchemy/orm/loading_relationships.html?highlight=selectinload#sqlalchemy.orm.selectinload
import datetime
from typing import List
from fastapi import HTTPException
@ -24,133 +25,156 @@ from sqlalchemy.orm import selectinload
from starlette import status
from core.logger import logger
from sqlalchemy.sql.selectable import Select
from typing import Any
class DalBase:
def __init__(self, db: AsyncSession, model, schema, key_models: dict = None):
def __init__(self, db: AsyncSession, model: Any, schema: Any, key_models: dict = None):
self.db = db
self.model = model
self.schema = schema
self.key_models = key_models
async def get_data(self, data_id: int = None, options: list = None, schema=None, keys: dict = None, **kwargs):
async def get_data(
self,
data_id: int = None,
v_options: list = None,
v_join_query: dict = None,
v_order: str = None,
v_return_none: bool = False,
v_schema: Any = None,
**kwargs
):
"""
获取单个数据默认使用 ID 查询否则使用关键词查询
@param data_id:
@param keys: 外键字段查询内连接
@param options: 指示应使用select在预加载中加载给定的属性
@param schema: 指定使用的序列化对象
@param kwargs: 关键词参数,
@param kwargs: v_order排序默认正序 desc 是倒叙
@param kwargs: v_return_none是否返回空 None否认 抛出异常默认抛出异常
@param data_id: 数据 ID
@param v_join_query: 外键字段查询内连接
@param v_options: 指示应使用select在预加载中加载给定的属性
@param v_schema: 指定使用的序列化对象
@param v_order: 排序默认正序 desc 是倒叙
@param v_return_none: 是否返回空 None否认 抛出异常默认抛出异常
@param kwargs: 查询参数
"""
order = kwargs.pop("v_order", None)
return_none = kwargs.pop("v_return_none", False)
keys_exist = False
if keys:
for key, value in keys.items():
if value and isinstance(value, dict):
for k, v in value.items():
if v:
keys_exist = True
break
kwargs_exist = False
if kwargs:
for key, value in kwargs.items():
if value and getattr(self.model, key, None):
kwargs_exist = True
break
sql = select(self.model).where(self.model.delete_datetime.is_(None))
if data_id or kwargs_exist or keys_exist:
if data_id:
sql = sql.where(self.model.id == data_id)
sql = self.add_filter_condition(sql, keys, options, **kwargs)
if order and (order == "desc" or order == "descending"):
if data_id:
sql = sql.where(self.model.id == data_id)
sql = self.add_filter_condition(sql, v_join_query, v_options, **kwargs)
if v_order and (v_order == "desc" or v_order == "descending"):
sql = sql.order_by(self.model.create_datetime.desc())
queryset = await self.db.execute(sql)
data = queryset.scalars().first()
if not data and return_none:
if not data and v_return_none:
return None
if data and schema:
return schema.from_orm(data).dict()
if data and v_schema:
return v_schema.from_orm(data).dict()
if data:
return data
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="未找到此数据")
async def get_datas(self, page: int = 1, limit: int = 10, keys: dict = None, options: list = None, schema=None,
**kwargs):
async def get_datas(
self,
page: int = 1,
limit: int = 10,
v_join_query: dict = None,
v_options: list = None,
v_order: str = None,
v_order_field: str = None,
v_return_objs: bool = False,
v_start_sql: Any = None,
v_schema: Any = None,
**kwargs
):
"""
获取数据列表
@param page: 页码
@param limit: 当前页数据量
@param keys: 外键字段查询
@param options: 指示应使用select在预加载中加载给定的属性
@param schema: 指定使用的序列化对象
@param kwargs: v_order排序默认正序 desc 是倒叙
@param kwargs: v_order_field排序字段
@param kwargs: v_return_objs是否返回对象
@param kwargs: v_start_sql初始 sql
@param v_join_query: 外键字段查询
@param v_options: 指示应使用select在预加载中加载给定的属性
@param v_schema: 指定使用的序列化对象
@param v_order: 排序默认正序 desc 是倒叙
@param v_order_field: 排序字段
@param v_return_objs: 是否返回对象
@param v_start_sql: 初始 sql
@param kwargs: 查询参数
"""
order = kwargs.pop("v_order", None)
order_field = kwargs.pop("v_order_field", None)
return_objs = kwargs.pop("v_return_objs", False)
start_sql = kwargs.pop("v_start_sql", None)
if not isinstance(start_sql, Select):
start_sql = select(self.model).where(self.model.delete_datetime.is_(None))
sql = self.add_filter_condition(start_sql, keys, options, **kwargs)
if order_field and (order == "desc" or order == "descending"):
sql = sql.order_by(getattr(self.model, order_field).desc(), self.model.id.desc())
elif order_field:
sql = sql.order_by(getattr(self.model, order_field), self.model.id)
elif order == "desc" or order == "descending":
if not isinstance(v_start_sql, Select):
v_start_sql = select(self.model).where(self.model.delete_datetime.is_(None))
sql = self.add_filter_condition(v_start_sql, v_join_query, v_options, **kwargs)
if v_order_field and (v_order == "desc" or v_order == "descending"):
sql = sql.order_by(getattr(self.model, v_order_field).desc(), self.model.id.desc())
elif v_order_field:
sql = sql.order_by(getattr(self.model, v_order_field), self.model.id)
elif v_order == "desc" or v_order == "descending":
sql = sql.order_by(self.model.id.desc())
if limit != 0:
sql = sql.offset((page - 1) * limit).limit(limit)
queryset = await self.db.execute(sql)
if return_objs:
if v_return_objs:
return queryset.scalars().all()
if schema:
return [schema.from_orm(i).dict() for i in queryset.scalars().all()]
if v_schema:
return [v_schema.from_orm(i).dict() for i in queryset.scalars().all()]
return [self.out_dict(i) for i in queryset.scalars().all()]
async def get_count(self, keys: dict = None, **kwargs):
"""获取数据总数"""
async def get_count(self, v_join_query: dict = None, v_options: list = None, **kwargs):
"""
获取数据总数
@param v_join_query: 外键字段查询
@param v_options: 指示应使用select在预加载中加载给定的属性
@param kwargs: 查询参数
"""
sql = select(func.count(self.model.id).label('total')).where(self.model.delete_datetime.is_(None))
sql = self.add_filter_condition(sql, keys, **kwargs)
sql = self.add_filter_condition(sql, v_join_query, v_options, **kwargs)
queryset = await self.db.execute(sql)
return queryset.one()['total']
async def create_data(self, data, return_obj: bool = False, options: list = None, schema=None):
"""创建数据"""
async def create_data(self, data, v_options: list = None, v_return_obj: bool = False, v_schema: Any = None):
"""
创建数据
@param data: 创建数据
@param v_options: 指示应使用select在预加载中加载给定的属性
@param v_schema: 指定使用的序列化对象
@param v_return_obj: 是否返回对象
"""
if isinstance(data, dict):
obj = self.model(**data)
else:
obj = self.model(**data.dict())
await self.flush(obj)
if options:
obj = await self.get_data(obj.id, options=options)
if return_obj:
if v_options:
obj = await self.get_data(obj.id, v_options=v_options)
if v_return_obj:
return obj
if schema:
return schema.from_orm(obj).dict()
if v_schema:
return v_schema.from_orm(obj).dict()
return self.out_dict(obj)
async def put_data(self, data_id: int, data, return_obj: bool = False, options: list = None, schema=None):
async def put_data(
self,
data_id: int,
data: Any,
v_options: list = None,
v_return_obj: bool = False,
v_schema: Any = None
):
"""
更新单个数据
@param data_id: 修改行数据的 ID
@param data: 数据内容
@param v_options: 指示应使用select在预加载中加载给定的属性
@param v_return_obj: 是否返回对象
@param v_schema: 指定使用的序列化对象
"""
obj = await self.get_data(data_id, options=options)
obj = await self.get_data(data_id, v_options=v_options)
obj_dict = jsonable_encoder(data)
for key, value in obj_dict.items():
setattr(obj, key, value)
await self.flush(obj)
if return_obj:
if v_return_obj:
return obj
if schema:
return schema.from_orm(obj).dict()
if v_schema:
return v_schema.from_orm(obj).dict()
return self.out_dict(obj)
async def delete_datas(self, ids: List[int], soft: bool = False):
@ -160,67 +184,72 @@ class DalBase:
@param soft: 是否执行软删除
"""
if soft:
await self.db.execute(update(self.model).where(self.model.id.in_(ids)).
values(delete_datetime=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")))
await self.db.execute(
update(self.model)
.where(self.model.id.in_(ids))
.values(delete_datetime=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
)
else:
await self.db.execute(delete(self.model).where(self.model.id.in_(ids)))
def add_filter_condition(self, sql: select, keys: dict = None, options: list = None, **kwargs) -> select:
def add_filter_condition(self, sql: select, v_join_query: dict = None, v_options: list = None, **kwargs) -> select:
"""
添加过滤条件以及内连接过滤条件
@param sql:
@param keys: 外键字段查询内连接
@param options: 指示应使用select在预加载中加载给定的属性
@param v_join_query: 外键字段查询内连接
@param v_options: 指示应使用select在预加载中加载给定的属性
@param kwargs: 关键词参数
"""
if keys and self.key_models:
for key, value in keys.items():
model = self.key_models.get(key)
if model:
sql = sql.join(model)
if v_join_query and self.key_models:
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"))
for v_key, v_value in value.items():
if v_value is not None and v_value != "":
v_attr = getattr(model, v_key, None)
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 keys and not self.key_models:
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)
if options:
sql = sql.options(*[selectinload(i) for i in options])
if v_options:
sql = sql.options(*[selectinload(i) for i in v_options])
return sql
@classmethod
def filter_condition(cls, sql, attr, value):
def filter_condition(cls, sql: Any, attr: Any, value: Any):
"""
过滤条件
"""
if not attr:
return sql
if isinstance(value, tuple):
if value[0] == "date" and value[1]:
# 根据日期查询, 关键函数是func.time_format和func.date_format
sql = sql.where(func.date_format(attr, "%Y-%m-%d") == value[1])
elif value[0] == "like" and value[1]:
sql = sql.where(attr.like(f"%{value[1]}%"))
elif value[0] == "or" and value[1]:
sql = sql.where(or_(i for i in value[1]))
elif value[0] == "in" and value[1]:
sql = sql.where(attr.in_(value[1]))
elif value[0] == "between" and value[1]:
sql = sql.where(attr.between(value[1][0], value[1][1]))
elif value[0] == "month" and value[1]:
sql = sql.where(func.date_format(attr, "%Y-%m") == value[1])
if value[1]:
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":
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])
else:
sql = sql.where(attr == value)
return sql
async def flush(self, obj=None):
async def flush(self, obj: Any = None):
"""
刷新到数据库
"""
@ -230,7 +259,7 @@ class DalBase:
if obj:
await self.db.refresh(obj)
def out_dict(self, data):
def out_dict(self, data: Any):
"""
序列化
@param data:

View File

@ -35,3 +35,16 @@ class Telephone(str):
@classmethod
def validate(cls, v):
return vali_telephone(v)
class DateStr(str):
@classmethod
def __get_validators__(cls):
yield cls.validate
@classmethod
def validate(cls, v):
if isinstance(v, str):
return v
return v.strftime("%Y-%m-%d")

View File

@ -108,8 +108,8 @@ def register_exception(app: FastAPI):
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
content=jsonable_encoder(
{
"message": "接口异常!"
, "code": status.HTTP_500_INTERNAL_SERVER_ERROR
"message": "接口异常!",
"code": status.HTTP_500_INTERNAL_SERVER_ERROR
}
),
)

View File

@ -1,4 +1,5 @@
from abc import abstractmethod
from typing import Any
class DatabaseManage:
@ -29,7 +30,16 @@ class DatabaseManage:
pass
@abstractmethod
async def get_datas(self, collection: str, page: int = 1, limit: int = 10, schema=None, **kwargs):
async def get_datas(
self,
collection: str,
page: int = 1,
limit: int = 10,
v_schema: Any = None,
v_order: str = None,
v_order_field: str = None,
**kwargs
):
pass
@abstractmethod

View File

@ -1,4 +1,6 @@
import json
from typing import Any
from bson.json_util import dumps
from motor.motor_asyncio import AsyncIOMotorClient, AsyncIOMotorDatabase
from core.mongo import DatabaseManage
@ -30,15 +32,20 @@ class MongoManage(DatabaseManage):
async def create_data(self, collection: str, data: dict) -> InsertOneResult:
return await self.db[collection].insert_one(data)
async def get_datas(self, collection: str, schema: BaseModel = None, **kwargs):
async def get_datas(
self,
collection: str,
page: int = 1,
limit: int = 10,
v_schema: Any = None,
v_order: str = None,
v_order_field: str = None,
**kwargs
):
"""
使用 find() 要查询的一组文档 find() 没有I / O也不需要 await 表达式它只是创建一个 AsyncIOMotorCursor 实例
当您调用 to_list() 或为循环执行异步时 (async for) 查询实际上是在服务器上执行的
"""
page = kwargs.pop("page", 1)
limit = kwargs.pop("limit", 10)
order = kwargs.pop("v_order", None)
order_field = kwargs.pop("v_order_field", None)
params = self.filter_condition(**kwargs)
cursor = self.db[collection].find(params)
@ -50,8 +57,8 @@ class MongoManage(DatabaseManage):
async for row in cursor:
del row['_id']
data = json.loads(dumps(row))
if schema:
data = schema.parse_obj(data).dict()
if v_schema:
data = v_schema.parse_obj(data).dict()
datas.append(data)
return datas

View File

@ -156,7 +156,8 @@ class ExcelManage:
@param max_column: 最大列
"""
for index in range(0, max_column):
# 设置单元格对齐方式 Alignment(horizontal=水平对齐模式,vertical=垂直对齐模式,text_rotation=旋转角度,wrap_text=是否自动换行)
# 设置单元格对齐方式
# Alignment(horizontal=水平对齐模式,vertical=垂直对齐模式,text_rotation=旋转角度,wrap_text=是否自动换行)
alignment = Alignment(horizontal='center', vertical='center', text_rotation=0, wrap_text=False)
self.sheet.cell(row=row, column=index+1).alignment = alignment

View File

@ -39,7 +39,7 @@ class IPManage:
def __init__(self, ip: str):
self.ip = ip
self.url = f"https://api.ip138.com/ip/?ip={ip}&datatype=jsonp&token={IP_PARSE_TOKEN}"
self.url = f"http://api.ip138.com/ip/?ip={ip}&datatype=jsonp&token={IP_PARSE_TOKEN}"
async def parse(self):
"""