32 項目結(jié)構(gòu) & 事務(wù) & Logging日志
1 項目結(jié)構(gòu)
以下主要是以drf編寫api時的結(jié)構(gòu)為示例。
1.1 APP結(jié)構(gòu)
1.1.1 單APP
例如:訂單系統(tǒng)

1.1.2 Base + 業(yè)務(wù)APP
例如:供應(yīng)鏈系統(tǒng)

1.1.3 獨立的APP
app中的功能各自獨立的,每個app中編寫自己的 models / views 等。

1.2 視圖結(jié)構(gòu)
1.2.1 view.py

1.2.2 views + 文件
根據(jù)業(yè)務(wù)拆分成多個視圖文件,每個視圖文件中實現(xiàn)指定的一些業(yè)務(wù)。

1.2.3 views + 文件夾

這里的router路由需要注意:

1.3 返回值
1.3.1 自定義mixins
將視圖中的 mixins相關(guān)的視圖重寫,業(yè)務(wù)視圖繼承自己視圖,對于各種情況的返回值進行定制:
1.內(nèi)置返回
對于認證、權(quán)限等組件進行自定義,實現(xiàn)返回自定義的格式數(shù)據(jù)。

2.視圖返回
對于用戶請求的返回值進行定制。

1.3.2 自定義異常處理機制
在之前學習源碼時,我們知道:請求到來都會執(zhí)行dispatch方法,在 try...except代碼塊中:
- 觸發(fā):認證、權(quán)限、限流等
- 執(zhí)行視圖
- 上述過程如果出現(xiàn)異常,則由
self.handle_exception(exc)對異常處理并封裝返回值,然后返回。

1.handle_exception源碼




2.自定義exception_handler
詳細案例見:dbhot11.zip
utils/handlers.py
from django.http import Http404
from rest_framework import exceptions
from rest_framework.response import Response
from rest_framework.exceptions import ValidationError
from rest_framework.exceptions import Throttled
from rest_framework.exceptions import PermissionDenied
from rest_framework.exceptions import NotAuthenticated
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.views import set_rollback
def exception_handler(exc, context):
if isinstance(exc, Http404):
exc = exceptions.NotFound()
exc.ret_code = 1001
elif isinstance(exc, PermissionDenied):
exc = exceptions.PermissionDenied()
exc.ret_code = 1002
elif isinstance(exc, (AuthenticationFailed, NotAuthenticated)):
exc.ret_code = 1003
elif isinstance(exc, Throttled):
exc.ret_code = 1004
elif isinstance(exc, ValidationError):
exc.ret_code = 1005
# 只處理drf相關(guān)的異常
if isinstance(exc, exceptions.APIException):
headers = {}
if getattr(exc, 'auth_header', None):
headers['WWW-Authenticate'] = exc.auth_header
if getattr(exc, 'wait', None):
headers['Retry-After'] = '%d' % exc.wait
if isinstance(exc.detail, (list, dict)):
data = exc.detail
else:
code = getattr(exc, 'ret_code', None) or -1
data = {'code': code, 'detail': exc.detail}
set_rollback()
return Response(data, status=exc.status_code, headers=headers)
return None
utils/exceptions.py
from rest_framework import exceptions
class ExtraException(exceptions.APIException):
def __init__(self, detail=None, ret_code=None, code=None):
super().__init__(detail, code)
self.ret_code = ret_code
views.py
from rest_framework import exceptions
from rest_framework import serializers
from rest_framework.viewsets import GenericViewSet
from rest_framework.mixins import ListModelMixin
from rest_framework.mixins import CreateModelMixin, RetrieveModelMixin, DestroyModelMixin, UpdateModelMixin
from rest_framework.authentication import BaseAuthentication
from rest_framework.permissions import BasePermission
from rest_framework.throttling import BaseThrottle
from api import models
from utils.exceptions import ExtraException
class ExtraAuthentication(BaseAuthentication):
def authenticate(self, request):
raise exceptions.AuthenticationFailed("認證失敗")
def authenticate_header(self, request):
return "api"
class ExtraPermission(BasePermission):
def has_permission(self, request, view):
return False
def has_object_permission(self, request, view, obj):
return False
class ExtraThrottle(BaseThrottle):
def allow_request(self, request, view):
return False
class DemoSerializer(serializers.ModelSerializer):
class Meta:
model = models.UserInfo
fields = "__all__"
class DemoView(ListModelMixin, CreateModelMixin, RetrieveModelMixin, DestroyModelMixin, UpdateModelMixin,
GenericViewSet):
# authentication_classes = [ExtraAuthentication]
# throttle_classes = [ExtraThrottle]
# permission_classes = [ExtraPermission]
queryset = models.UserInfo.objects.all()
serializer_class = DemoSerializer
def perform_create(self, serializer):
self.dispatch
if True:
# 自定義錯誤
# raise ExtraException("數(shù)據(jù)異常")
raise ExtraException("更新失敗", ret_code=9000)
serializer.save()
def finalize_response(self, request, response, *args, **kwargs):
response = super().finalize_response(request, response, *args, **kwargs)
if response.exception:
return response
response.data = {'code': 0, 'data': response.data}
return response
3.正常返回


def finalize_response(self, request, response, *args, **kwargs):
response = super().finalize_response(request, response, *args, **kwargs)
if response.exception:
return response
response.data = {'code': 0, 'data': response.data}
return response
可以通過自定義 DrfGenericViewSet:
from rest_framework.viewsets import GenericViewSet as DrfGenericViewSet
class GenericViewSet(DrfGenericViewSet):
def finalize_response(self, request, response, *args, **kwargs):
response = super().finalize_response(request, response, *args, **kwargs)
if response.exception:
return response
response.data = {'code': 0, 'data': response.data}
return response
可以定義到中間件中使用:
from django.utils.deprecation import MiddlewareMixin
class ReturnCodeMiddleware(MiddlewareMixin):
def process_response(self, request, response):
if not hasattr(response, 'exception'):
return response
if response.exception:
return response
response.data = {'code': 0, 'data': response.data}
response._is_rendered = False
response.content = response.render().content
return response
2 事務(wù)
2.1 局部事務(wù)(*)
基于上下文管理,如果出現(xiàn)異常則自動回滾;無異常則自動提交。
from rest_framework.views import APIView
from rest_framework.response import Response
from django.db import transaction
from api import models
class Demo1View(APIView):
def get(self, request, *args, **kwargs):
try:
with transaction.atomic():
models.UserInfo.objects.create(name='v1', age=1)
models.Order.objects.create(name='v1', age=1)
except Exception as e:
print("異常,自動回滾")
return Response("...")
事務(wù)提交的回調(diào)函數(shù)(本質(zhì)上就是事務(wù)完成后,自動執(zhí)行一個函數(shù)):
from rest_framework.views import APIView
from rest_framework.response import Response
from django.db import transaction
from api import models
from functools import partial
def db_success_callback(*args, **kwargs):
print(args, **kwargs)
class Demo1View(APIView):
def get(self, request, *args, **kwargs):
try:
with transaction.atomic():
# 回調(diào)函數(shù),事務(wù)正常提交自動執(zhí)行
transaction.on_commit(db_success_callback)
transaction.on_commit( partial(db_success_callback, 11, 22, 33) )
models.UserInfo.objects.create(name='v1', age=1)
models.Order.objects.create(title='v1', count=1)
except Exception as e:
print("異常,自動回滾") # on_commit回調(diào)函數(shù)內(nèi)部異常時不會回滾
return Response("...")
回滾到 指定事務(wù)點:
from rest_framework.views import APIView
from rest_framework.response import Response
from django.db import transaction
from api import models
class Demo1View(APIView):
def get(self, request, *args, **kwargs):
try:
with transaction.atomic():
# 回調(diào)函數(shù),事務(wù)正常提交自動執(zhí)行
n1 = transaction.savepoint()
models.UserInfo.objects.create(name='v1', age=1)
n2 = transaction.savepoint()
models.UserInfo.objects.create(name='v2', age=1)
# 必須在事務(wù)里面,回顧到指定 事務(wù)點,后續(xù)東西不提交
transaction.savepoint_rollback(n2)
except Exception as e:
print("異常,自動回滾", e) # on_commit回調(diào)函數(shù)內(nèi)部異常時不會回滾
return Response("...")
2.2 視圖事務(wù)
針對整個視圖進行開啟事務(wù):
- 視圖內(nèi),有數(shù)據(jù)庫操作異常,自動回滾
- 視圖內(nèi),有其他異常,不會回滾。
from rest_framework.views import APIView
from rest_framework.response import Response
from django.db import transaction, IntegrityError
from api import models
class Demo1View(APIView):
@transaction.atomic
def get(self, request, *args, **kwargs):
try:
models.UserInfo.objects.create(name='v100', age=1)
models.UserInfo.objects.create(name="v200", age="xxx") # 有異常,回滾,即:v100不會保存
int("asdf") # 有異常,不會滾,即:兩條數(shù)據(jù)正常保存到數(shù)據(jù)庫
except Exception as e:
pass
return Response("...")
定義事務(wù)點,自定義回滾位置:
from rest_framework.views import APIView
from rest_framework.response import Response
from django.db import transaction, IntegrityError
from api import models
class Demo1View(APIView):
@transaction.atomic
def get(self, request, *args, **kwargs):
try:
models.UserInfo.objects.create(name='v10', age=1)
n1 = transaction.savepoint()
models.UserInfo.objects.create(name="v11", age=1)
n2 = transaction.savepoint()
models.UserInfo.objects.create(name='v12', age=1)
n3 = transaction.savepoint()
models.UserInfo.objects.create(name='v13', age=1)
# 后續(xù)讀取到某些值后,發(fā)現(xiàn) v12不應(yīng)該創(chuàng)建,那么就可以主動回滾(只生成n2之前的)
transaction.savepoint_rollback(n2)
except Exception as e:
print("有異常", e)
return Response("...")
2.3 全局事務(wù)
效率低:項目中一般不會使用。
如果想要開啟全局事務(wù),需要在連接數(shù)據(jù)庫時多設(shè)置一個參數(shù):
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.mysql',
'NAME': 'dbhot4',
'USER': 'root',
'PASSWORD': 'root123',
'HOST': '127.0.0.1',
'PORT': '3306',
'ATOMIC_REQUESTS': True
}
}
-
只要視圖函數(shù)執(zhí)行異常,無論是什么原因觸發(fā),均自動回滾。
class Demo1View(APIView): def get(self, request, *args, **kwargs): models.UserInfo.objects.create(name='v1', age=1) models.UserInfo.objects.create(xxxxxxx='v2', age=1) # 錯誤 return Response("...")class Demo1View(APIView): def get(self, request, *args, **kwargs): models.UserInfo.objects.create(name='v1', age=1) models.UserInfo.objects.create(name='v2', age=1) int("asdf") # 錯誤 return Response("...") -
如果視圖函數(shù)執(zhí)行不報錯(try處理異常,也叫不報錯),則不會回滾
class Demo1View(APIView): def get(self, request, *args, **kwargs): try: models.UserInfo.objects.create(name='v1', age=1) models.UserInfo.objects.create(xxxxxxx='v2', age=1) int("xxx") except Exception as e: pass return Response("...") # 視圖函數(shù)執(zhí)行沒有報錯,不會滾回。
如果開啟了全局事務(wù),想要免除某個指定的函數(shù)不需要開啟事務(wù),則可以使用:
from rest_framework.views import APIView
from rest_framework.response import Response
from django.db import transaction, IntegrityError
from api import models
from django.utils.decorators import method_decorator
@method_decorator(transaction.non_atomic_requests, name='dispatch')
class Demo1View(APIView):
def get(self, request, *args, **kwargs):
models.UserInfo.objects.create(name='v100', age=1)
models.UserInfo.objects.create(name="v200", age="xxx") # 報錯
return Response("...")
3 Logging日志
什么時候用到日志?
- 可預知的情況,寫日志
- 不可預知情況,寫日志
3.1 基礎(chǔ)版
CRITICAL = 50
FATAL = CRITICAL
ERROR = 40
WARNING = 30
WARN = WARNING
INFO = 20
DEBUG = 10
NOTSET = 0
import logging
# 1. 對日志進行配置
logging.basicConfig(
filename='error.log', # 日志文件
format='%(asctime)s : %(message)s', # 寫日志時,文件的格式。
datefmt='%Y-%m-%d %H:%M:%S %p',
level=20 # 級別,以后只有大于20的級別時,才能真正日志內(nèi)容寫入到文件中。
)
# 2.寫日志
"""
CRITICAL = 50
FATAL = CRITICAL
ERROR = 40
WARNING = 30
WARN = WARNING
INFO = 20
DEBUG = 10
NOTSET = 0
"""
logging.debug("你好呀") # 10,你好呀
logging.info("中午好") # 10,你好呀
logging.error("你傻呀") # 40,你傻呀

默認這種形式不支持在文件中寫入日志:
import logging
import traceback
# 1. 對日志進行配置
logging.basicConfig(
filename='v10.log', # 日志文件
format='%(asctime)s : %(message)s', # 寫日志時,文件的格式。
datefmt='%Y-%m-%d %H:%M:%S %p',
level=20 # 級別,以后只有大于20的級別時,才能真正日志內(nèi)容寫入到文件中。
)
# 2. 對日志進行配置(不生效,因為已配置過后生成在內(nèi)存,不再支持設(shè)置)
logging.basicConfig(
filename='v100.log', # 日志文件
format='%(asctime)s : %(message)s', # 寫日志時,文件的格式。
datefmt='%Y-%m-%d %H:%M:%S %p',
level=20 # 級別,以后只有大于20的級別時,才能真正日志內(nèi)容寫入到文件中。
)
logging.error("沙雕alex")
3.2 對象版(支持多文件)
-
Formatter,格式化。
-
FileHandler,維護文件,專門用于往文件中寫內(nèi)容。
-
Logger,定義級別,大于這個級別才調(diào)用 FileHandler 去寫內(nèi)容。
-
寫日志
logger = Logger() logger.error("內(nèi)容") # 10 "xxx"
示例1:
import logging
# 定義 Formatter
fmt = logging.Formatter(fmt="%(asctime)s - %(name)s - %(levelname)s -%(module)s: %(message)s")
# 定義 FileHandler
handler_object = logging.FileHandler('v2.log', 'a', encoding='utf-8')
handler_object.setFormatter(fmt)
# 定義 Logger
logger_object = logging.Logger('s1', level=logging.INFO) # 20
logger_object.addHandler(handler_object)
# ===>寫日志<====
logger_object.error("alex是個大sb") # 40>20則寫入日志
示例2:
import logging
# 定義 Formatter
fmt = logging.Formatter(fmt="%(asctime)s - %(name)s - %(levelname)s -%(module)s: %(message)s")
# 定義 FileHandler
handler_object = logging.FileHandler('v2.log', 'a', encoding='utf-8')
handler_object.setFormatter(fmt)
handler_object2 = logging.FileHandler('v22.log', 'a', encoding='utf-8')
handler_object2.setFormatter(fmt)
# 定義 Logger
logger_object = logging.Logger('s1', level=logging.INFO) # 20
logger_object.addHandler(handler_object)
logger_object.addHandler(handler_object2)
# 寫日志
logger_object.error("alex是個大sb")
3.3 配置版
示例1:簡單配置
import logging.config
# 1. 定義字典
LOGGING_CONFIG = {
"version": 1,
"disable_existing_loggers": True, # 刪除已存在其他日志的Handler
'formatters': {
'standard': {
'format': '{asctime} {levelname} :{message}',
'style': '{',
"datefmt": '%Y-%m-%d %H:%M:%S %p',
},
},
'handlers': {
'console': {
'class': 'logging.StreamHandler', # 寫到終端的輸出流中
'formatter': 'standard', # 用到的什么格式
},
'demo': {
"class": 'logging.handlers.RotatingFileHandler', # 寫到文件中
'formatter': 'standard',
'filename': 'demo.log', # 日志的文件名
'maxBytes': 1024, # 根據(jù)文件大小拆分日志
'backupCount': 30, # 5份文件
"encoding": "utf-8"
}
},
'loggers': {
'nb': {
'handlers': ['console', 'demo'],
'level': "INFO", # >=20 則觸發(fā)日志
'propagate': False
}
}
}
# 2. 根據(jù)自定對logging進行配置
logging.config.dictConfig(LOGGING_CONFIG)
# 3. 寫日志
logger_object = logging.getLogger("nb")
logger_object.info('6666666666')
示例2:多文件配置
import logging.config
# 1. 定義字典
LOGGING_CONFIG = {
"version": 1,
"disable_existing_loggers": True, # 刪除已存在其他日志的Handler
'formatters': {
'standard': {
'format': '{asctime} {levelname} {threadName} :{message}',
'style': '{',
"datefmt": '%Y-%m-%d %H:%M:%S %p',
},
'simple': {
'format': '%(asctime)s %(levelname)s %(message)s',
'style': '%',
"datefmt": '%Y-%m-%d',
},
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'formatter': 'simple',
},
'run': {
# 運行日志,按天自動分割
"class": 'logging.handlers.TimedRotatingFileHandler', # 基于時間
'formatter': 'standard',
'filename': "run.log",
'when': 'D', # 根據(jù)天拆分日志
'interval': 1, # 1天
'backupCount': 3, # 保留備份
"encoding": "utf-8"
},
'error': {
# 錯誤日志,按照文件大小分割
"class": 'logging.handlers.RotatingFileHandler',
'formatter': 'standard',
'filename': 'error.log',
'maxBytes': 1024 * 1025 * 50, # 根據(jù)文件大小拆分日志 50M
'backupCount': 5,
"encoding": "utf-8"
},
},
'loggers': {
'run': {
'handlers': ['run'],
'level': "INFO", # >=20 則觸發(fā)日志
'propagate': True # 觸發(fā)父級-->’root'
},
'error': {
'handlers': ['console', 'error'],
'level': "ERROR", # >=20 則觸發(fā)日志
'propagate': False
}
},
'root': {
'handlers': ['console', ],
'level': 'DEBUG',
'propagate': True
}
}
# 2. 根據(jù)自定對logging進行配置
logging.config.dictConfig(LOGGING_CONFIG)
# 3. 寫日志
# root = logging.getLogger()
# root.info("測試測試")
# run = logging.getLogger('run')
# run.info("測試測試")
# run = logging.getLogger('error')
# run.info("有信息了")
# run.error("錯誤了")
示例3:過濾
import logging
class CallbackFilter(logging.Filter):
def __init__(self, callback):
self.callback = callback
def filter(self, record):
if self.callback(record):
return True
return False
class DynamicFilter(logging.Filter):
def filter(self, record):
# record,包含了日志相關(guān)的對象 logging.LogRecord
# print(record, type(record))
if not record.msg:
return False
return True
import logging.config
# 1. 定義字典
LOGGING_CONFIG = {
"version": 1,
"disable_existing_loggers": True, # 刪除已存在其他日志的Handler
'formatters': {
'standard': {
'format': '{asctime} {levelname} {threadName} :{message}',
'style': '{',
"datefmt": '%Y-%m-%d %H:%M:%S %p',
},
'simple': {
'format': '%(asctime)s %(levelname)s %(message)s',
'style': '%',
"datefmt": '%Y-%m-%d',
},
},
"filters": {
"dy": {
"()": "utils.DynamicFilter"
},
"call": {
"()": "utils.CallbackFilter",
"callback": lambda record: len(record.msg) > 4
}
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'formatter': 'simple',
},
'run': {
# 運行日志,按天自動分割
"class": 'logging.handlers.TimedRotatingFileHandler',
'formatter': 'standard',
'filters': ["dy", 'call'],
'filename': "run.log",
'when': 'D', # 根據(jù)天拆分日志
'interval': 1, # 1天
'backupCount': 3, # 保留備份
"encoding": "utf-8"
},
'error': {
# 錯誤日志,按照文件大小分割
"class": 'logging.handlers.RotatingFileHandler',
'formatter': 'standard',
'filename': 'error.log',
'maxBytes': 1024 * 1025 * 50, # 根據(jù)文件大小拆分日志 50M
'backupCount': 5,
"encoding": "utf-8"
},
},
'loggers': {
'run': {
'handlers': ['run'],
'level': "INFO", # >=20 則觸發(fā)日志
'propagate': True
},
'error': {
'handlers': ['console', 'error'],
'level': "ERROR", # >=20 則觸發(fā)日志
'propagate': False
}
},
'root': {
'handlers': ['console', ],
'level': 'DEBUG',
'propagate': True
}
}
# 2. 根據(jù)自定對logging進行配置
logging.config.dictConfig(LOGGING_CONFIG)
# 3. 寫日志
run = logging.getLogger('run')
run.info("測試測xxxxx")
3.4 Django
###########
# LOGGING #
###########
import os
# BASE_LOG_DIR = BASE_DIR / 'log'
BASE_LOG_DIR = os.path.join(BASE_DIR, 'log')
BASE_LOG_DIR.mkdir(exist_ok=True) # 文件夾不存在則創(chuàng)建,存在不創(chuàng)建
# The callable to use to configure logging
LOGGING_CONFIG = "logging.config.dictConfig"
# Custom logging configuration.
# 1. 定義字典
LOGGING = {
"version": 1,
"disable_existing_loggers": False, # 刪除已存在其他日志的Handler
'formatters': {
'standard': {
'format': '{asctime} {levelname} {threadName} :{message}',
'style': '{',
"datefmt": '%Y-%m-%d %H:%M:%S %p',
},
'simple': {
'format': '%(asctime)s %(levelname)s %(message)s',
'style': '%',
"datefmt": '%Y-%m-%d',
},
},
# "filters": {
# "dy": {
# "()": "django.utils.log.RequireDebugFalse"
# },
# "call": {
# "()": "django.utils.log.CallbackFilter",
# "callback": lambda record: len(record.msg) > 4
# }
# },
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'formatter': 'simple',
},
'run': {
# 運行日志,按天自動分割
"class": 'logging.handlers.TimedRotatingFileHandler',
'formatter': 'standard',
# 'filters': ["dy", 'call'],
'filename': os.path.join(BASE_LOG_DIR, 'run.log'),
'when': 'D', # 根據(jù)天拆分日志
'interval': 1, # 1天
'backupCount': 3, # 保留備份
"encoding": "utf-8"
},
'error': {
# 錯誤日志,按照文件大小分割
"class": 'logging.handlers.RotatingFileHandler',
'formatter': 'standard',
'filename': os.path.join(BASE_LOG_DIR, 'error.log'),
'maxBytes': 1024 * 1025 * 50, # 根據(jù)文件大小拆分日志 50M
'backupCount': 5,
"encoding": "utf-8"
},
},
'loggers': {
'run': {
'handlers': ['run'],
'level': "INFO", # >=20 則觸發(fā)日志
'propagate': True
},
'error': {
'handlers': ['console', 'error'],
'level': "ERROR", # >=40 則觸發(fā)日志
'propagate': False
}
},
'root': {
'handlers': ['console', ],
'level': 'DEBUG',
'propagate': True
}
}
logger = logging.getLogger("error")
logger.error("...")
logger = logging.getLogger("run")
logger.info("...")
logger = logging.getLogger()
logger.info("...")

浙公網(wǎng)安備 33010602011771號