我們開發(fā)軟件系統(tǒng)的時(shí)候,需要不斷的反思我們代碼里面是否有可以優(yōu)化的地方。而優(yōu)化的重點(diǎn)之一,就是把冗余的代碼優(yōu)化為可以復(fù)用的庫。我們在前面編寫了一些功能,但是其中存在很多冗余的方法
mgr/medicine.py
mgr/k8s.py
mgr/medicine.py
打開這3個(gè)文件我們可以看到他們的入口函數(shù)dispatcher? 實(shí)際的代碼相似度非常高,該函數(shù)的大體代碼基本類似,不同之處,只是分配給哪些函數(shù)處理
?
像這樣的冗余代碼如果持續(xù)增多,那么后續(xù)的維護(hù)成本也會(huì)變大,比如我想要將認(rèn)證的返回值進(jìn)行修改,那么我就需要去3個(gè)文件中挨個(gè)進(jìn)行配置,所以我們要將這種相似的代碼單獨(dú)做成一個(gè)公共程序
一、冗余代碼優(yōu)化
1、添加公共函數(shù)目錄
#在當(dāng)前應(yīng)用項(xiàng)目(mgr)下創(chuàng)建一個(gè)lib目錄,并創(chuàng)建公共代碼文件
lib
|-handler.py
2、修改K8S.py存量代碼
我們發(fā)現(xiàn) 請求消息給哪個(gè)函數(shù)處理, 完全是由 請求消息里面的action參數(shù)決定的, 所以,我們可以修改上面這3個(gè)代碼文件,先刪除原先的入口函數(shù)dispatcher函數(shù)(我前面幾個(gè)定義的名稱都不一致,這塊可以改成一樣的了)
#應(yīng)用公共函數(shù)
from lib.handler import dispatcherBase
#當(dāng)前函數(shù)所支持請求類型
Action2Handler = {
'list_customer': listcustomers,
'add_customer': addcustomer,
'modify_customer': modifycustomer,
'del_customer': deletecustomer,
}
def dispatcher(request):
return dispatcherBase(request, Action2Handler)
我們定義一個(gè)什么樣的action用什么函數(shù)處理的一張表 Action2Handler ,然后dispatcher 函數(shù)可以簡單到直接調(diào)用 dispatcherBase, 并且把Action2Handler 作為參數(shù)傳遞給給它。剩下的就交由 dispatcherBase 去處理了,下面我們把原先的入口函數(shù)刪除后,將上面的代碼添加到3個(gè)文件最下面,注意修改Action2Handler變量中的請求參數(shù)和對應(yīng)調(diào)用的函數(shù)名稱
Django_demo/mgr/order.py
from django.http import JsonResponse
from django.db import transaction
from django.db.models import F
# 導(dǎo)入 Order 對象定義
from paas.models import Order,OrderMedicine
def addorder(request):
info = request.params['data']
# 從請求消息中 獲取要添加訂單的信息
# 并且插入到數(shù)據(jù)庫中
with transaction.atomic():
new_order = Order.objects.create(name=info['name'], customer_id=info['customerid'])
batch = [OrderMedicine(order_id=new_order.id,medicine_id=mid,amount=1)
for mid in info['medicineids']]
# 在多對多關(guān)系表中 添加了 多條關(guān)聯(lián)記錄
OrderMedicine.objects.bulk_create(batch)
return JsonResponse({'ret': 0,'id':new_order.id})
def listorder(request):
# 返回一個(gè) QuerySet 對象 ,包含所有的表記錄
qs = Order.objects \
.annotate(
customer_name=F('customer__name'),
medicines_name=F('medicines__name')
) \
.values(
'id','name','create_date','customer_name','medicines_name'
)
# 將 QuerySet 對象 轉(zhuǎn)化為 list 類型
retlist = list(qs)
# 可能有 ID相同,藥品不同的訂單記錄, 需要合并
newlist = []
id2order = {}
for one in retlist:
orderid = one['id']
if orderid not in id2order:
newlist.append(one)
id2order[orderid] = one
else:
id2order[orderid]['medicines_name'] += ' | ' + one['medicines_name']
return JsonResponse({'ret': 0, 'retlist': newlist})
##################################新的入口函數(shù)################################
#添加導(dǎo)入公共函數(shù)
from .lib.handler import dispatcherBase
#傳入本地函數(shù)和方法
Action2Handler = {
'list_order': listorder,
'add_order': addorder,
}
#重新定義入口函數(shù)
def dispatcher(request):
return dispatcherBase(request, Action2Handler)
?3、定義公共函數(shù)
Django_demo/mgr/lib/handler.py
import json
from django.http import JsonResponse
def dispatcherBase(request,action2HandlerTable):
# 根據(jù)session判斷用戶是否是登錄的管理員用戶
if 'usertype' not in request.session:
return JsonResponse({
'ret': 302,
'msg': '未登錄',
'redirect': '/mgr/sign.html'},
status=302)
if request.session['usertype'] != 'mgr':
return JsonResponse({
'ret': 302,
'msg': '用戶非mgr類型',
'redirect': '/mgr/sign.html'},
status=302)
# 將請求參數(shù)統(tǒng)一放入request 的 params 屬性中,方便后續(xù)處理
# GET請求 參數(shù) 在 request 對象的 GET屬性中
if request.method == 'GET':
request.params = request.GET
# POST/PUT/DELETE 請求 參數(shù) 從 request 對象的 body 屬性中獲取
elif request.method in ['POST','PUT','DELETE']:
# 根據(jù)接口,POST/PUT/DELETE 請求的消息體都是 json格式
request.params = json.loads(request.body)
# 根據(jù)不同的action分派給不同的函數(shù)進(jìn)行處理
action = request.params['action']
print(action)
if action in action2HandlerTable:
handlerFunc = action2HandlerTable[action]
return handlerFunc(request)
else:
return JsonResponse({'ret': 1, 'msg': 'action參數(shù)錯(cuò)誤'})
前面的認(rèn)證轉(zhuǎn)換和參數(shù)獲取和之前的代碼是一致的,區(qū)別在于獲取到action參數(shù)后的操作
# 根據(jù)不同的action分派給不同的函數(shù)進(jìn)行處理
action = request.params['action']
if action in action2HandlerTable:
handlerFunc = action2HandlerTable[action]
return handlerFunc(request)
這段代碼就是根據(jù)action參數(shù)的值,到 action2HandlerTable 查找出對應(yīng)的 函數(shù)處理 ,可以根據(jù)這個(gè)方法去修改其他的兩個(gè)代碼文件
4、測試請求
import requests,pprint
#添加認(rèn)證
payload = {
'username': 'root',
'password': '12345678'
}
#發(fā)送登錄請求
response = requests.post('http://127.0.0.1:8000/api/mgr/signin',data=payload)
#拿到請求中的認(rèn)證信息進(jìn)行訪問
set_cookie = response.headers.get('Set-Cookie')
# 構(gòu)建添加 客戶信息的 消息體,是json格式
payload = {
"action":"list_order",
}
url='http://127.0.0.1:8000/api/mgr/orders/'
if set_cookie:
# 將Set-Cookie字段的值添加到請求頭中
headers = {'Cookie': set_cookie}
# 發(fā)送請求給web服務(wù)
response = requests.post(url,json=payload,headers=headers)
pprint.pprint(response.json())
返回
{'ret': 0,
'retlist': [{'create_date': '2023-10-26T01:15:09.718Z',
'customer_name': 'zhangsan',
'id': 13,
'medicines_name': 'gmkl',
'name': '天山訂單'},
{'create_date': '2023-10-26T01:14:29.897Z',
'customer_name': 'zhangsan',
'id': 12,
'medicines_name': 'gmkl',
'name': '天山訂單'},
{'create_date': '2023-10-26T01:13:35.943Z',
'customer_name': 'zhangsan',
'id': 11,
'medicines_name': 'gmkl',
'name': 'ts'},
{'create_date': '2023-10-25T03:08:00Z',
'customer_name': 'zhangsan',
'id': 5,
'medicines_name': 'gmkl',
'name': 'test'}]}
5、補(bǔ)全其他案例
Django_demo/mgr/k8s.py
from django.http import JsonResponse
from paas.models import PaasInfo
def listcustomers(request):
# 返回一個(gè) QuerySet 對象 ,包含所有的表記錄
qs = PaasInfo.objects.values()
# 將 QuerySet 對象 轉(zhuǎn)化為 list 類型
# 否則不能 被 轉(zhuǎn)化為 JSON 字符串
retlist = list(qs)
return JsonResponse({'ret': 0, 'retlist': retlist})
def addcustomer(request):
info = request.params['data']
# 從請求消息中 獲取要添加客戶的信息
# 并且插入到數(shù)據(jù)庫中
# 返回值 就是對應(yīng)插入記錄的對象
record = PaasInfo.objects.create(ClusterName=info['ClusterName'] ,
NodeSum=info['NodeSum'] ,
PrometheusAddress=info['PrometheusAddress'])
return JsonResponse({'ret': 0, 'id':record.id})
def modifycustomer(request):
# 從請求消息中 獲取修改客戶的信息
# 找到該客戶,并且進(jìn)行修改操作
customerid = request.params['id']
newdata = request.params['newdata']
print(customerid,newdata)
try:
# 根據(jù) id 從數(shù)據(jù)庫中找到相應(yīng)的客戶記錄
customer = PaasInfo.objects.get(id=customerid)
except PaasInfo.DoesNotExist:
return {
'ret': 1,
'msg': f'id 為`{customerid}`的客戶不存在'
}
if 'ClusterName' in newdata:
customer.ClusterName = newdata['ClusterName']
if 'NodeSum' in newdata:
customer.NodeSum = newdata['NodeSum']
if 'PrometheusAddress' in newdata:
customer.PrometheusAddress = newdata['PrometheusAddress']
# 注意,一定要執(zhí)行save才能將修改信息保存到數(shù)據(jù)庫
customer.save()
return JsonResponse({'ret': 0})
def deletecustomer(request):
customerid = request.params['id']
try:
# 根據(jù) id 從數(shù)據(jù)庫中找到相應(yīng)的客戶記錄
customer = PaasInfo.objects.get(id=customerid)
except PaasInfo.DoesNotExist:
return {
'ret': 1,
'msg': f'id 為`{customerid}`的客戶不存在'
}
# delete 方法就將該記錄從數(shù)據(jù)庫中刪除了
customer.delete()
return JsonResponse({'ret': 0})
#重定義入口函數(shù)
from .lib.handler import dispatcherBase
Action2Handler = {
'list_customer': listcustomers,
'add_customer': addcustomer,
'modify_customer': modifycustomer,
'del_customer': deletecustomer,
}
def dispatcher(request):
return dispatcherBase(request, Action2Handler)
Django_demo/mgr/medicine.py
from django.http import JsonResponse
# 導(dǎo)入 Medicine 對象定義(這塊可能顯示模塊導(dǎo)入不正常,忽略)
from paas.models import Medicine
import json
def listmedicine(request):
# 返回一個(gè) QuerySet 對象 ,包含所有的表記錄
qs = Medicine.objects.values()
# 將 QuerySet 對象 轉(zhuǎn)化為 list 類型
# 否則不能 被 轉(zhuǎn)化為 JSON 字符串
retlist = list(qs)
return JsonResponse({'ret': 0, 'retlist': retlist})
def addmedicine(request):
info = request.params['data']
# 從請求消息中 獲取要添加客戶的信息
# 并且插入到數(shù)據(jù)庫中
medicine = Medicine.objects.create(name=info['name'] ,
sn=info['sn'] ,
desc=info['desc'])
return JsonResponse({'ret': 0, 'id':medicine.id})
def modifymedicine(request):
# 從請求消息中 獲取修改客戶的信息
# 找到該客戶,并且進(jìn)行修改操作
medicineid = request.params['id']
newdata = request.params['newdata']
try:
# 根據(jù) id 從數(shù)據(jù)庫中找到相應(yīng)的客戶記錄
medicine = Medicine.objects.get(id=medicineid)
except Medicine.DoesNotExist:
return {
'ret': 1,
'msg': f'id 為`{medicineid}`的藥品不存在'
}
if 'name' in newdata:
medicine.name = newdata['name']
if 'sn' in newdata:
medicine.sn = newdata['sn']
if 'desc' in newdata:
medicine.desc = newdata['desc']
# 注意,一定要執(zhí)行save才能將修改信息保存到數(shù)據(jù)庫
medicine.save()
return JsonResponse({'ret': 0})
def deletemedicine(request):
medicineid = request.params['id']
try:
# 根據(jù) id 從數(shù)據(jù)庫中找到相應(yīng)的藥品記錄
medicine = Medicine.objects.get(id=medicineid)
except Medicine.DoesNotExist:
return {
'ret': 1,
'msg': f'id 為`{medicineid}`的客戶不存在'
}
# delete 方法就將該記錄從數(shù)據(jù)庫中刪除了
medicine.delete()
return JsonResponse({'ret': 0})
from .lib.handler import dispatcherBase
Action2Handler = {
'list_medicine': listmedicine,
'add_medicine': addmedicine,
'modify_medicine': modifymedicine,
'del_medicine': deletemedicine,
}
def dispatcher(request):
return dispatcherBase(request, Action2Handler)
Django_demo/mgr/urls.py
from django.urls import path
from .views import login
from .sign_in_out import signin,signout
#重定義路由名稱
from .k8s import dispatcher as k8s
from .order import dispatcher as order
from .medicine import dispatcher as medicine
urlpatterns = [
path('customers/', k8s),
path('medicines/', medicine),
path('orders/', order),
path('signin', signin),
path('signout', signout),
path('login',login)
]
二、數(shù)據(jù)庫冗余
現(xiàn)在我們的 mgr/order.py 里面用 listorder 函數(shù)列出訂單。如果一個(gè)訂單里面有多個(gè)藥品,就會(huì)產(chǎn)生多條記錄。為了解決這個(gè)問題,我們不得不用python代碼來處理冗余,像下面這樣
def listorder(request):
# 返回一個(gè) QuerySet 對象 ,包含所有的表記錄
qs = Order.objects\
.annotate(
customer_name=F('customer__name'),
medicines_name=F('medicines__name')
)\
.values(
'id','name','create_date','customer_name','medicines_name'
)
# 將 QuerySet 對象 轉(zhuǎn)化為 list 類型
retlist = list(qs)
# 可能有 ID相同,藥品不同的訂單記錄, 需要合并
newlist = []
id2order = {}
for one in retlist:
orderid = one['id']
if orderid not in id2order:
newlist.append(one)
id2order[orderid] = one
else:
id2order[orderid]['medicines_name'] += ' | ' + one['medicines_name']
return JsonResponse({'ret': 0, 'retlist': newlist})
?這樣做其實(shí)有一些問題,首先他會(huì)使得我們的代碼增加了額外的去除重復(fù)記錄的功能,并且還會(huì)代理性能問題,比如當(dāng)大量用戶登錄時(shí)需要列出訂單信息,需要服務(wù)程序從數(shù)據(jù)庫獲取到數(shù)據(jù)后,再去執(zhí)行去除重復(fù)記錄的任務(wù)
?1、冗余問題思路
我們可以修改數(shù)據(jù)庫表的設(shè)計(jì),就在訂單表(order) 里面 直接存入訂單包含的藥品信息
這樣就不用去關(guān)聯(lián)表(orderMedicine) 去獲取關(guān)聯(lián)藥品的信息,從而也不需要去除重復(fù)代碼
這樣,就不需要 從 OrderMedicine 表里面 去獲取關(guān)聯(lián)藥品信息了,當(dāng)然也不需要去除重復(fù)的代碼了。
????? 但又有了新問題,如果說我們希望訂單表里面有藥品信息,需要有藥品的id、名稱、數(shù)量,而且有可能存在多種藥品,關(guān)鍵是不同的訂單和藥品的數(shù)量也是不同的,對于這種情況,我們通常可以使用一個(gè)字段, 里面存儲(chǔ) json格式的字符串,記錄可變數(shù)量的數(shù)據(jù)信息。
Django_demo/paas/models.py
class Order(models.Model):
# 訂單名
name = models.CharField(max_length=200,null=True,blank=True)
# 創(chuàng)建日期
create_date = models.DateTimeField(default=datetime.datetime.now)
# 客戶
customer = models.ForeignKey(Customer,on_delete=models.PROTECT)
# 訂單購買的藥品,和Medicine表是多對多 的關(guān)系
medicines = models.ManyToManyField(Medicine, through='OrderMedicine')
# 為了提高效率,這里存放 訂單 medicines 冗余數(shù)據(jù)
medicinelist = models.CharField(max_length=2000,null=True,blank=True)
?我們在訂單表中添加了一個(gè)medicinelist的字段,里面用json格式來存儲(chǔ)訂單中的藥品
[
{"id": 1, "name": "青霉素", "amount": 20},
{"id": 2, "name": "來適可", "amount": 100}
]
id 表示藥品的id
name 表示 藥品的名字
amount 表示 藥品的數(shù)量
?上面的例子就表示該訂單中有id 為 1 和 2 的兩種藥品 數(shù)量分別是 20 和 100
這個(gè)字段最大長度可達(dá)2000個(gè)字符,通常足以存儲(chǔ)訂單中的藥品信息了
python manage.py makemigrations common
python manage.py migrate
2、修改接口參數(shù)
我們之前在請求接口的時(shí)候使用的是如下格式
{
"action":"add_order",
"data":{
"name":"華山醫(yī)院訂單002",
"customerid":3,
"medicineids":[1,2]
}
}
這里面只有藥品的id,沒有藥品的 名稱和數(shù)量。我們在開發(fā)的時(shí)候,經(jīng)常會(huì)遇到當(dāng)前的接口設(shè)計(jì)不能滿足新的需求,需要修改的情況。這時(shí)候就要和 接口的設(shè)計(jì)者 , 以及接口對接的開發(fā)團(tuán)隊(duì)進(jìn)行溝通, 說明為什么你需要修改接口
3、修改請求api格式
添加訂單格式
{
"action":"add_order",
"data":{
"name":"華山醫(yī)院訂單002",
"customerid":3,
"medicinelist":[
{"id":16,"amount":5,"name":"環(huán)丙沙星"},
{"id":15,"amount":5,"name":"克林霉素"}
]
}
}
列出訂單格式
{
"id": 2,
"name": "華山醫(yī)院訂單002",
"create_date": "2018-12-27T14:10:37.208Z",
"customer_name": "華山醫(yī)院",
"medicinelist":[
{"id":16,"amount":5,"name":"環(huán)丙沙星"},
{"id":15,"amount":5,"name":"克林霉素"}
]
}
既然接口變動(dòng)了,前端的開發(fā)團(tuán)隊(duì) 要根據(jù)修改后的接口,修改他們的代碼,保證按照新的接口實(shí)現(xiàn)消息格式, 上面這個(gè)是前端發(fā)請求api所要攜帶的參數(shù)
4、修改后端代碼邏輯
修改添加邏輯
Django_demo/mgr/order.py
def addorder(request):
info = request.params['data']
with transaction.atomic():
medicinelist = info['medicinelist']
new_order = Order.objects.create(name=info['name'],
customer_id=info['customerid'],
# 寫入json格式的藥品數(shù)據(jù)到 medicinelist 字段中
medicinelist=json.dumps(medicinelist,ensure_ascii=False),)
batch = [OrderMedicine(order_id=new_order.id,
medicine_id=medicine['id'],
amount=medicine['amount'])
for medicine in medicinelist]
OrderMedicine.objects.bulk_create(batch)
return JsonResponse({'ret': 0, 'id': new_order.id})
修改列出表邏輯
Django_demo/mgr/order.py
def listorder(request):
qs = Order.objects \
.annotate(
customer_name=F('customer__name')
)\
.values(
'id', 'name', 'create_date',
'customer_name',
'medicinelist'
)
# 將 QuerySet 對象 轉(zhuǎn)化為 list 類型
retlist = list(qs)
return JsonResponse({'ret': 0, 'retlist': retlist})
5、添加數(shù)據(jù)
import requests,pprint
#添加認(rèn)證
payload = {
'username': 'root',
'password': '12345678'
}
#發(fā)送登錄請求
response = requests.post('http://127.0.0.1:8000/api/mgr/signin',data=payload)
#拿到請求中的認(rèn)證信息進(jìn)行訪問
set_cookie = response.headers.get('Set-Cookie')
# 構(gòu)建添加 客戶信息的 消息體,是json格式
payload = {
"action":"add_order",
"data":{
"name":"華山醫(yī)院訂單002",
"customerid":1,
"medicinelist":[
{"id":6,"amount":5,"name":"gmkl"},
]
}
}
url='http://127.0.0.1:8000/api/mgr/orders/'
if set_cookie:
# 將Set-Cookie字段的值添加到請求頭中
headers = {'Cookie': set_cookie}
# 發(fā)送請求給web服務(wù)
response = requests.post(url,json=payload,headers=headers)
print(response)
注意
payload = {
"action":"add_order",
"data":{
"name":"華山醫(yī)院訂單002",
"customerid":1,
"medicinelist":[
{"id":6,"amount":5,"name":"gmkl"},
]
}
}
因?yàn)槲覀兪且砑佑唵?,所以客戶id ("customerid":1,)? 和藥品id + 名稱是必須要先知道的,或者我們在發(fā)起請求的時(shí)候不帶這種id,去到后端的時(shí)候去根據(jù)數(shù)據(jù)庫查詢客戶和藥品的id再寫入
6、調(diào)用查詢數(shù)據(jù)
import requests,pprint
#添加認(rèn)證
payload = {
'username': 'root',
'password': '12345678'
}
#發(fā)送登錄請求
response = requests.post('http://127.0.0.1:8000/api/mgr/signin',data=payload)
#拿到請求中的認(rèn)證信息進(jìn)行訪問
set_cookie = response.headers.get('Set-Cookie')
# 構(gòu)建添加 客戶信息的 消息體,是json格式
payload = {
"action":"list_order",
"data":{
"customer_name":"華山醫(yī)院訂單002",
}
}
url='http://127.0.0.1:8000/api/mgr/orders/'
if set_cookie:
# 將Set-Cookie字段的值添加到請求頭中
headers = {'Cookie': set_cookie}
# 發(fā)送請求給web服務(wù)
response = requests.post(url,json=payload,headers=headers)
pprint.pprint(response.json())
返回
{'create_date': '2023-11-02T09:05:27.095Z',
'customer_name': 'zhangsan',
'id': 19,
'medicinelist': '[{"id": 6, "amount": 5, "name": "gmkl"}]',
'name': '華山醫(yī)院訂單002'}]}
7、關(guān)于前后方案的
?? 上面這么做的好處很明顯,列出訂單的代碼就比較簡單了,不需要執(zhí)行去重的任務(wù)。
性能也提高了, 只要查詢一張表,并且不要執(zhí)行去重的任務(wù),但是有可能出現(xiàn)其他的問題
???
???? 冗余數(shù)據(jù),我們本來記錄在 OrderMedicine 表 中的, 現(xiàn)在我們還需要記錄在 Order 表中,這么一說咋還不如之前的方法呢? 這樣每次需要寫兩張表的性能反而是下降了吧
?? 但是除此之外還需要考慮到,是修改訂單的請求多,還是查詢訂單的請求多,那么一定是查詢的要多很多,每次查詢都要跑去重,和只有修改的時(shí)候才會(huì)寫兩張表之間選擇,肯定是后者的方案更好
????? 文章來源:http://www.zghlxwxcb.cn/news/detail-741426.html
???? 可能有小伙伴覺得,寫兩張表還更麻煩了,反正訂單里面有藥品信息,那么干脆不用OrderMedicine表不就更簡單了嗎,這樣做,最大的問題是, 如果以后我們需要統(tǒng)計(jì)藥品被采購的信息就非常麻煩了,因?yàn)?,現(xiàn)在我們在數(shù)據(jù)庫中存儲(chǔ)的訂單中的藥品信息,是以字符串類型存儲(chǔ)的 json信息,不能直接使用SQL語句進(jìn)行過濾查詢,只能把所有的記錄讀取出來進(jìn)行分析,那樣性能會(huì)非常低文章來源地址http://www.zghlxwxcb.cn/news/detail-741426.html
到了這里,關(guān)于Python 框架學(xué)習(xí) Django篇 (八) 代碼優(yōu)化、數(shù)據(jù)庫冗余處理的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!