大家好~我是
米洛
!
我正在從0到1打造一個開源的介面測試平臺, 也在編寫一套與之對應的完整教程
,希望大家多多支援。
歡迎關注我的公眾號測試開發坑貨
,獲取最新文章教程!
回顧
上一節我們提出了優化Dao邏輯
的想法,那今天就試著來兌現之,並運用到Redis配置管理的開發中去。
初步構思list方法
我們在dao/init.py新建類: Mapper,以後所有的dao類都繼承自它。
想想list需要什麼,一般需要,欄位
,引數
, 是like還是等於
這3個重要的資訊。
明白這個以後,我們的虛擬碼就好編寫了:
# 1. 獲取session
async with async_session() as session:
condition = [model.deleted_at == 0]
# 2. 根據引數裡的欄位,欄位值構造查詢條件
DatabaseHelper.where(欄位,欄位值,condition)
# 3. 查詢出結果,後續與原來的方式一致,就不寫了
可以看到,這邊除了需要上面的3個資訊以外,還需要try去寫入日誌,也就是說需要我們平時經常建立的log成員變數,還需要model
這個類,否則你不知道改的是什麼表。
思考
我們平時的引數,都是key-value的形式,一旦value不為空,那麼說明我們要根據這個條件來查詢。
而model和log,我們可以通過類的裝飾器,由子類傳遞給父類(這裡會用到setattr和getattr)
class Mapper(object):
log = None
model = None
@classmethod
async def list_data(cls, **kwargs):
try:
async with async_session() as session:
# 構造查詢條件,預設是資料未被刪除
condition = [getattr(cls.model, "deleted_at") == 0]
# 遍歷引數,當引數不為None的時候傳遞
for k, v in kwargs.items():
# 判斷是否是like的情況 TODO: 這裡沒支援in查詢
like = v is not None and len(v) > 2 and v.startswith("%") and v.endswith("%")
# 如果是like模式,則使用Model.欄位.like 否則用 Model.欄位 等於
DatabaseHelper.where(v, getattr(cls.model, k).like(v) if like else getattr(cls.model, k) == v,
condition)
result = await session.execute(select(cls.model).where(*condition))
return result.scalars().all()
except Exception as e:
# 這邊呼叫cls本身的log引數,寫入日誌+丟擲異常
cls.log.error(f"獲取{cls.model}列表失敗, error: {e}")
raise Exception(f"獲取資料失敗")
註釋寫的比較詳細,由於現在欄位是個變數,所以我們不能用model.欄位
來取值,所以取而代之的是getattr(model, 欄位)
。不熟悉的朋友可以去搜尋下getattr
。
這樣,一個粗略的list方法就寫好了,但是這個是不帶分頁的,所以我們還需要補充一個分頁的模式,其實也很簡單。
@classmethod
async def list_data_with_pagination(cls, page, size, **kwargs):
try:
async with async_session() as session:
condition = [getattr(cls.model, "deleted_at") == 0]
for k, v in kwargs.items():
like = v is not None and len(v) > 2 and v.startswith("%") and v.endswith("%")
sql = DatabaseHelper.where(v, getattr(cls.model, k).like(v) if like else getattr(cls.model, k) == v,
condition)
return await DatabaseHelper.pagination(page, size, session, sql)
except Exception as e:
cls.log.error(f"獲取{cls.model}列表失敗, error: {e}")
raise Exception(f"獲取資料失敗")
基本上長的差不多,只是最後返回那裡,呼叫了pagination相關方法。
看看dao裝飾器
我們很壞,把這個裝飾器套到子類上,並把model和log傳給父類。畢竟方法都是在呼叫父類的方法,父類無法直接拿到子類的資料
。
這樣就避免了在呼叫list
方法的時候,還需要傳入model和log的尷尬情況。
完善其他方法
list搞定以後,其他的還會遠嗎?但還真的好像還有點問題,因為我們一般會有一些重名判斷,但沒關係,我們可以編寫一個query方法。
@classmethod
def query_wrapper(cls, **kwargs):
condition = [getattr(cls.model, "deleted_at") == 0]
# 遍歷引數,當引數不為None的時候傳遞
for k, v in kwargs.items():
# 判斷是否是like的情況 TODO: 這裡沒支援in查詢
like = v is not None and len(v) > 2 and v.startswith("%") and v.endswith("%")
# 如果是like模式,則使用Model.欄位.like 否則用 Model.欄位 等於
DatabaseHelper.where(v, getattr(cls.model, k).like(v) if like else getattr(cls.model, k) == v,
condition)
return select(cls.model).where(*condition)
@classmethod
async def query_record(cls, **kwargs):
try:
async with async_session() as session:
sql = cls.query_wrapper(**kwargs)
result = await session.execute(sql)
return result.scalars().first()
except Exception as e:
cls.log.error(f"查詢{cls.model}失敗, error: {e}")
raise Exception(f"查詢資料失敗")
由於查詢方法太過於通用,所以抽成了query_wrapper方法。
- 正常編寫insert介面
@classmethod
async def insert_record(cls, model):
try:
async with async_session() as session:
async with session.begin():
session.add(model)
await session.flush()
session.expunge(model)
return model
except Exception as e:
cls.log.error(f"新增{cls.model}記錄失敗, error: {e}")
raise Exception(f"新增記錄失敗")
這邊返回了model,如果不需要也可以不用,但我們還是給返回。
- insert介面層
和以前一樣,先查,如果沒有再關掉。
但這樣會產生2個session,開->關->開->關
但也解耦了查詢和插入2個操作。
- 編寫刪除和修改方法
@classmethod
async def update_record_by_id(cls, user, model, not_null=False):
try:
async with async_session() as session:
async with session.begin():
query = cls.query_wrapper(id=model.id)
result = await session.execute(query)
original = result.scalars().first()
if original is None:
raise Exception("記錄不存在")
DatabaseHelper.update_model(original, model, user, not_null)
await session.flush()
session.expunge(original)
return original
except Exception as e:
cls.log.error(f"更新{cls.model}記錄失敗, error: {e}")
raise Exception(f"更新記錄失敗")
@classmethod
async def delete_record_by_id(cls, user, id):
"""
邏輯刪除
:param user:
:param id:
:return:
"""
try:
async with async_session() as session:
async with session.begin():
query = cls.query_wrapper(id=id)
result = await session.execute(query)
original = result.scalars().first()
if original is None:
raise Exception("記錄不存在")
DatabaseHelper.delete_model(original, user)
except Exception as e:
cls.log.error(f"刪除{cls.model}記錄失敗, error: {e}")
raise Exception(f"刪除記錄失敗")
@classmethod
async def delete_by_id(cls, id):
"""
物理刪除
:param id:
:return:
"""
try:
async with async_session() as session:
async with session.begin():
query = cls.query_wrapper(id=id)
result = await session.execute(query)
original = result.scalars().first()
if original is None:
raise Exception("記錄不存在")
session.delete(original)
except Exception as e:
cls.log.error(f"邏輯刪除{cls.model}記錄失敗, error: {e}")
raise Exception(f"刪除記錄失敗")
刪除這邊支援了物理刪除和邏輯刪除,當然我們一般是用軟刪除
。
完善其他介面
可以看到刪除和修改和以前是差不多的(也是內部進行資料是否存在判斷),這些步驟做完。redis的管理工作就可以順利進行了,接著我們需要為之編寫頁面咯!
由於是很基礎的表格頁面,所以我們不贅述
。下一節我們會利用配置的redis連線編寫redisManager,管理我們的連線資料
,為之後線上執行redis以及將它作為前置條件打下基礎。