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) { if (v.hidden !== undefined) {
return !v.hidden return !v.hidden
} else if (v.ifshow !== undefined) { } else if (v.ifshow !== undefined) {
const show = v.ifshow(unref(formModel)) return 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 true return true
}) })

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,6 +10,10 @@ import { RightToolbar } from '@/components/RightToolbar'
import { Dialog } from '@/components/Dialog' import { Dialog } from '@/components/Dialog'
import Detail from './components/Detail.vue' import Detail from './components/Detail.vue'
import { Search } from '@/components/Search' 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({ const { register, elTableRef, tableObject, methods } = useTable({
getListApi: getRecordOperationListApi, getListApi: getRecordOperationListApi,
@ -38,9 +42,13 @@ watch(tableSize, (val) => {
tableSize.value = val tableSize.value = val
}) })
const route = useRouter()
const cacheTableHeadersKey = route.currentRoute.value.fullPath
watch( watch(
columns, columns,
async () => { async (val) => {
wsCache.set(cacheTableHeadersKey, JSON.stringify(val))
await nextTick() await nextTick()
elTableRef.value?.doLayout() elTableRef.value?.doLayout()
}, },
@ -56,7 +64,12 @@ watch(
<div class="mb-8px flex justify-between"> <div class="mb-8px flex justify-between">
<ElRow /> <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> </div>
<Table <Table

View File

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

View File

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

View File

@ -46,5 +46,5 @@ async def full_admin(telephone: str, db: AsyncSession):
如果令牌无效立即返回一个 HTTP 错误 如果令牌无效立即返回一个 HTTP 错误
""" """
options = [models.VadminUser.roles, "roles.menus"] 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="登录") @app.post("/login/", summary="登录")
async def login_for_access_token(request: Request, data: LoginForm, manage: LoginManage = Depends(), async def login_for_access_token(
db: AsyncSession = Depends(db_getter)): request: Request,
data: LoginForm,
manage: LoginManage = Depends(),
db: AsyncSession = Depends(db_getter)
):
if data.method == "0": if data.method == "0":
result = await manage.password_login(data, db, request) result = await manage.password_login(data, db, request)
elif data.method == "1": elif data.method == "1":
@ -45,8 +49,8 @@ async def login_for_access_token(request: Request, data: LoginForm, manage: Logi
return ErrorResponse(msg="请使用正确的登录方式") return ErrorResponse(msg="请使用正确的登录方式")
if not result.status: if not result.status:
resp = {"message": result.msg} resp = {"message": result.msg}
await VadminLoginRecord.\ await VadminLoginRecord\
create_login_record(db, data, result.status, request, resp) .create_login_record(db, data, result.status, request, resp)
return ErrorResponse(msg=result.msg) return ErrorResponse(msg=result.msg)
user = result.user user = result.user

View File

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

View File

@ -46,7 +46,7 @@ class LoginValidation:
async def __call__(self, data: LoginForm, db: AsyncSession, request: Request) -> LoginResult: async def __call__(self, data: LoginForm, db: AsyncSession, request: Request) -> LoginResult:
self.result = LoginResult() self.result = LoginResult()
options = [models.VadminUser.roles, "roles.menus"] 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: if not user:
self.result.msg = "该手机号不存在!" self.result.msg = "该手机号不存在!"
return self.result return self.result

View File

@ -51,7 +51,7 @@ async def get_user(data_id: int, auth: Auth = Depends(login_auth)):
model = models.VadminUser model = models.VadminUser
options = [model.roles] options = [model.roles]
schema = schemas.UserOut 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="重置当前用户密码") @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") @app.post("/user/export/query/list/to/excel/", summary="导出用户查询列表为excel")
async def post_user_export_query_list(header: list = Body(..., title="表头与对应字段"), params: UserParams = Depends(), async def post_user_export_query_list(
auth: Auth = Depends(login_auth)): header: list = Body(..., title="表头与对应字段"),
params: UserParams = Depends(),
auth: Auth = Depends(login_auth)
):
return SuccessResponse(await crud.UserDal(auth.db).export_query_list(header, params)) 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 model = models.VadminRole
options = [model.menus] options = [model.menus]
schema = schemas.RoleOut 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="获取菜单信息") @app.get("/menus/{data_id}/", summary="获取菜单信息")
async def put_menus(data_id: int, auth: Auth = Depends(login_auth)): async def put_menus(data_id: int, auth: Auth = Depends(login_auth)):
schema = schemas.MenuSimpleOut 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角色权限使用") @app.get("/role/menus/tree/{role_id}/", summary="获取菜单列表树信息以及角色菜单权限ID角色权限使用")

View File

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

View File

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

View File

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

View File

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

View File

@ -27,9 +27,9 @@ app = APIRouter()
# 字典类型管理 # 字典类型管理
########################################################### ###########################################################
@app.get("/dict/types/", summary="获取字典类型列表") @app.get("/dict/types/", summary="获取字典类型列表")
async def get_dict_types(params: DictTypeParams = Depends(), auth: Auth = Depends(login_auth)): async def get_dict_types(p: DictTypeParams = Depends(), auth: Auth = Depends(login_auth)):
datas = await crud.DictTypeDal(auth.db).get_datas(**params.dict()) datas = await crud.DictTypeDal(auth.db).get_datas(**p.dict())
count = await crud.DictTypeDal(auth.db).get_count(**params.to_count()) count = await crud.DictTypeDal(auth.db).get_count(**p.to_count())
return SuccessResponse(datas, count=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="获取多个字典类型下的字典元素列表") @app.post("/dict/types/details/", summary="获取多个字典类型下的字典元素列表")
async def post_dicts_details(auth: Auth = Depends(login_auth), async def post_dicts_details(
dict_types: List[str] = Body(None, title="字典元素列表", description="查询字典元素列表")): auth: Auth = Depends(login_auth),
dict_types: List[str] = Body(None, title="字典元素列表", description="查询字典元素列表")
):
datas = await crud.DictTypeDal(auth.db).get_dicts_details(dict_types) datas = await crud.DictTypeDal(auth.db).get_dicts_details(dict_types)
return SuccessResponse(datas) return SuccessResponse(datas)
@app.get("/dict/types/options/", summary="获取字典类型选择项") @app.get("/dict/types/options/", summary="获取字典类型选择项")
async def get_dicts_options(auth: Auth = Depends(login_auth)): async def get_dicts_options(auth: Auth = Depends(login_auth)):
datas = await crud.DictTypeDal(auth.db).get_select_datas() return SuccessResponse(await crud.DictTypeDal(auth.db).get_select_datas())
return SuccessResponse(datas)
@app.put("/dict/types/{data_id}/", summary="更新字典类型") @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="获取字典类型详细") @app.get("/dict/types/{data_id}/", summary="获取字典类型详细")
async def get_dict_type(data_id: int, auth: Auth = Depends(login_auth)): async def get_dict_type(data_id: int, auth: Auth = Depends(login_auth)):
schema = schemas.DictTypeSimpleOut 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="获取字典元素详情") @app.get("/dict/details/{data_id}/", summary="获取字典元素详情")
async def get_dict_detail(data_id: int, auth: Auth = Depends(login_auth)): async def get_dict_detail(data_id: int, auth: Auth = Depends(login_auth)):
schema = schemas.DictDetailsSimpleOut 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 内连接 # SQLAlchemy join 内连接
# selectinload 官方文档: # selectinload 官方文档:
# https://www.osgeo.cn/sqlalchemy/orm/loading_relationships.html?highlight=selectinload#sqlalchemy.orm.selectinload # https://www.osgeo.cn/sqlalchemy/orm/loading_relationships.html?highlight=selectinload#sqlalchemy.orm.selectinload
import datetime import datetime
from typing import List from typing import List
from fastapi import HTTPException from fastapi import HTTPException
@ -24,133 +25,156 @@ from sqlalchemy.orm import selectinload
from starlette import status from starlette import status
from core.logger import logger from core.logger import logger
from sqlalchemy.sql.selectable import Select from sqlalchemy.sql.selectable import Select
from typing import Any
class DalBase: 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.db = db
self.model = model self.model = model
self.schema = schema self.schema = schema
self.key_models = key_models 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 查询否则使用关键词查询 获取单个数据默认使用 ID 查询否则使用关键词查询
@param data_id: @param data_id: 数据 ID
@param keys: 外键字段查询内连接 @param v_join_query: 外键字段查询内连接
@param options: 指示应使用select在预加载中加载给定的属性 @param v_options: 指示应使用select在预加载中加载给定的属性
@param schema: 指定使用的序列化对象 @param v_schema: 指定使用的序列化对象
@param kwargs: 关键词参数, @param v_order: 排序默认正序 desc 是倒叙
@param kwargs: v_order排序默认正序 desc 是倒叙 @param v_return_none: 是否返回空 None否认 抛出异常默认抛出异常
@param kwargs: 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)) sql = select(self.model).where(self.model.delete_datetime.is_(None))
if data_id or kwargs_exist or keys_exist: if data_id:
if data_id: sql = sql.where(self.model.id == 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, keys, options, **kwargs) if v_order and (v_order == "desc" or v_order == "descending"):
if order and (order == "desc" or order == "descending"):
sql = sql.order_by(self.model.create_datetime.desc()) sql = sql.order_by(self.model.create_datetime.desc())
queryset = await self.db.execute(sql) queryset = await self.db.execute(sql)
data = queryset.scalars().first() data = queryset.scalars().first()
if not data and return_none: if not data and v_return_none:
return None return None
if data and schema: if data and v_schema:
return schema.from_orm(data).dict() return v_schema.from_orm(data).dict()
if data: if data:
return data return data
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="未找到此数据") 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, async def get_datas(
**kwargs): 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 page: 页码
@param limit: 当前页数据量 @param limit: 当前页数据量
@param keys: 外键字段查询 @param v_join_query: 外键字段查询
@param options: 指示应使用select在预加载中加载给定的属性 @param v_options: 指示应使用select在预加载中加载给定的属性
@param schema: 指定使用的序列化对象 @param v_schema: 指定使用的序列化对象
@param kwargs: v_order排序默认正序 desc 是倒叙 @param v_order: 排序默认正序 desc 是倒叙
@param kwargs: v_order_field排序字段 @param v_order_field: 排序字段
@param kwargs: v_return_objs是否返回对象 @param v_return_objs: 是否返回对象
@param kwargs: v_start_sql初始 sql @param v_start_sql: 初始 sql
@param kwargs: 查询参数
""" """
order = kwargs.pop("v_order", None) if not isinstance(v_start_sql, Select):
order_field = kwargs.pop("v_order_field", None) v_start_sql = select(self.model).where(self.model.delete_datetime.is_(None))
return_objs = kwargs.pop("v_return_objs", False) sql = self.add_filter_condition(v_start_sql, v_join_query, v_options, **kwargs)
start_sql = kwargs.pop("v_start_sql", None) if v_order_field and (v_order == "desc" or v_order == "descending"):
if not isinstance(start_sql, Select): sql = sql.order_by(getattr(self.model, v_order_field).desc(), self.model.id.desc())
start_sql = select(self.model).where(self.model.delete_datetime.is_(None)) elif v_order_field:
sql = self.add_filter_condition(start_sql, keys, options, **kwargs) sql = sql.order_by(getattr(self.model, v_order_field), self.model.id)
if order_field and (order == "desc" or order == "descending"): elif v_order == "desc" or v_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":
sql = sql.order_by(self.model.id.desc()) sql = sql.order_by(self.model.id.desc())
if limit != 0: if limit != 0:
sql = sql.offset((page - 1) * limit).limit(limit) sql = sql.offset((page - 1) * limit).limit(limit)
queryset = await self.db.execute(sql) queryset = await self.db.execute(sql)
if return_objs: if v_return_objs:
return queryset.scalars().all() return queryset.scalars().all()
if schema: if v_schema:
return [schema.from_orm(i).dict() for i in queryset.scalars().all()] return [v_schema.from_orm(i).dict() for i in queryset.scalars().all()]
return [self.out_dict(i) 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 = 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) queryset = await self.db.execute(sql)
return queryset.one()['total'] 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): if isinstance(data, dict):
obj = self.model(**data) obj = self.model(**data)
else: else:
obj = self.model(**data.dict()) obj = self.model(**data.dict())
await self.flush(obj) await self.flush(obj)
if options: if v_options:
obj = await self.get_data(obj.id, options=options) obj = await self.get_data(obj.id, v_options=v_options)
if return_obj: if v_return_obj:
return obj return obj
if schema: if v_schema:
return schema.from_orm(obj).dict() return v_schema.from_orm(obj).dict()
return self.out_dict(obj) 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) obj_dict = jsonable_encoder(data)
for key, value in obj_dict.items(): for key, value in obj_dict.items():
setattr(obj, key, value) setattr(obj, key, value)
await self.flush(obj) await self.flush(obj)
if return_obj: if v_return_obj:
return obj return obj
if schema: if v_schema:
return schema.from_orm(obj).dict() return v_schema.from_orm(obj).dict()
return self.out_dict(obj) return self.out_dict(obj)
async def delete_datas(self, ids: List[int], soft: bool = False): async def delete_datas(self, ids: List[int], soft: bool = False):
@ -160,67 +184,72 @@ class DalBase:
@param soft: 是否执行软删除 @param soft: 是否执行软删除
""" """
if soft: if soft:
await self.db.execute(update(self.model).where(self.model.id.in_(ids)). await self.db.execute(
values(delete_datetime=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))) update(self.model)
.where(self.model.id.in_(ids))
.values(delete_datetime=datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"))
)
else: else:
await self.db.execute(delete(self.model).where(self.model.id.in_(ids))) 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 sql:
@param keys: 外键字段查询内连接 @param v_join_query: 外键字段查询内连接
@param options: 指示应使用select在预加载中加载给定的属性 @param v_options: 指示应使用select在预加载中加载给定的属性
@param kwargs: 关键词参数 @param kwargs: 关键词参数
""" """
if keys and self.key_models: if v_join_query and self.key_models:
for key, value in keys.items(): for key, value in v_join_query.items():
model = self.key_models.get(key) foreign_key = self.key_models.get(key)
if model: if foreign_key and foreign_key.get("model"):
sql = sql.join(model) # 当外键模型在查询模型中存在多个外键时则需要添加onclause属性
sql = sql.join(foreign_key.get("model"), onclause=foreign_key.get("onclause"))
for v_key, v_value in value.items(): for v_key, v_value in value.items():
if v_value is not None and v_value != "": 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) sql = self.filter_condition(sql, v_attr, v_value)
else: else:
logger.error(f"外键查询报错:{key}模型不存在,无法进行下一步查询。") 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 外键模型无配置项,无法进行下一步查询。") logger.error(f"外键查询报错key_models 外键模型无配置项,无法进行下一步查询。")
for field in kwargs: for field in kwargs:
value = kwargs.get(field) value = kwargs.get(field)
if value is not None and value != "": if value is not None and value != "":
attr = getattr(self.model, field, None) attr = getattr(self.model, field, None)
sql = self.filter_condition(sql, attr, value) sql = self.filter_condition(sql, attr, value)
if options: if v_options:
sql = sql.options(*[selectinload(i) for i in options]) sql = sql.options(*[selectinload(i) for i in v_options])
return sql return sql
@classmethod @classmethod
def filter_condition(cls, sql, attr, value): def filter_condition(cls, sql: Any, attr: Any, value: Any):
""" """
过滤条件 过滤条件
""" """
if not attr: if not attr:
return sql return sql
if isinstance(value, tuple): if isinstance(value, tuple):
if value[0] == "date" and value[1]: if value[1]:
# 根据日期查询, 关键函数是func.time_format和func.date_format if value[0] == "date":
sql = sql.where(func.date_format(attr, "%Y-%m-%d") == value[1]) # 根据日期查询, 关键函数是func.time_format和func.date_format
elif value[0] == "like" and value[1]: sql = sql.where(func.date_format(attr, "%Y-%m-%d") == value[1])
sql = sql.where(attr.like(f"%{value[1]}%")) elif value[0] == "like":
elif value[0] == "or" and value[1]: sql = sql.where(attr.like(f"%{value[1]}%"))
sql = sql.where(or_(i for i in value[1])) elif value[0] == "or":
elif value[0] == "in" and value[1]: sql = sql.where(or_(i for i in value[1]))
sql = sql.where(attr.in_(value[1])) elif value[0] == "in":
elif value[0] == "between" and value[1]: sql = sql.where(attr.in_(value[1]))
sql = sql.where(attr.between(value[1][0], value[1][1])) elif value[0] == "between":
elif value[0] == "month" and value[1]: sql = sql.where(attr.between(value[1][0], value[1][1]))
sql = sql.where(func.date_format(attr, "%Y-%m") == value[1]) elif value[0] == "month":
sql = sql.where(func.date_format(attr, "%Y-%m") == value[1])
else: else:
sql = sql.where(attr == value) sql = sql.where(attr == value)
return sql return sql
async def flush(self, obj=None): async def flush(self, obj: Any = None):
""" """
刷新到数据库 刷新到数据库
""" """
@ -230,7 +259,7 @@ class DalBase:
if obj: if obj:
await self.db.refresh(obj) await self.db.refresh(obj)
def out_dict(self, data): def out_dict(self, data: Any):
""" """
序列化 序列化
@param data: @param data:

View File

@ -35,3 +35,16 @@ class Telephone(str):
@classmethod @classmethod
def validate(cls, v): def validate(cls, v):
return vali_telephone(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, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
content=jsonable_encoder( content=jsonable_encoder(
{ {
"message": "接口异常!" "message": "接口异常!",
, "code": status.HTTP_500_INTERNAL_SERVER_ERROR "code": status.HTTP_500_INTERNAL_SERVER_ERROR
} }
), ),
) )

View File

@ -1,4 +1,5 @@
from abc import abstractmethod from abc import abstractmethod
from typing import Any
class DatabaseManage: class DatabaseManage:
@ -29,7 +30,16 @@ class DatabaseManage:
pass pass
@abstractmethod @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 pass
@abstractmethod @abstractmethod

View File

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

View File

@ -156,7 +156,8 @@ class ExcelManage:
@param max_column: 最大列 @param max_column: 最大列
""" """
for index in range(0, 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) alignment = Alignment(horizontal='center', vertical='center', text_rotation=0, wrap_text=False)
self.sheet.cell(row=row, column=index+1).alignment = alignment self.sheet.cell(row=row, column=index+1).alignment = alignment

View File

@ -39,7 +39,7 @@ class IPManage:
def __init__(self, ip: str): def __init__(self, ip: str):
self.ip = ip 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): async def parse(self):
""" """