@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@@ -0,0 +1,18 @@
|
||||
from rest_framework.views import exception_handler as drf_exception_handler
|
||||
|
||||
|
||||
def exception_handler(exc, context):
|
||||
response = drf_exception_handler(exc, context)
|
||||
if response is None:
|
||||
return response
|
||||
|
||||
request = context.get("request")
|
||||
response.data = {
|
||||
"error": {
|
||||
"code": getattr(exc, "default_code", "request_error"),
|
||||
"message": "请求未能完成",
|
||||
"details": response.data,
|
||||
"request_id": getattr(request, "request_id", None),
|
||||
}
|
||||
}
|
||||
return response
|
||||
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class CommonConfig(AppConfig):
|
||||
default_auto_field = 'django.db.models.BigAutoField'
|
||||
name = 'common'
|
||||
@@ -0,0 +1,17 @@
|
||||
from channels.generic.websocket import AsyncJsonWebsocketConsumer
|
||||
|
||||
|
||||
class HealthConsumer(AsyncJsonWebsocketConsumer):
|
||||
group_name = "healthcheck"
|
||||
|
||||
async def connect(self):
|
||||
try:
|
||||
await self.channel_layer.group_add(self.group_name, self.channel_name)
|
||||
await self.channel_layer.group_discard(self.group_name, self.channel_name)
|
||||
except Exception:
|
||||
await self.close(code=1011)
|
||||
return
|
||||
|
||||
await self.accept()
|
||||
await self.send_json({"status": "ok", "channel_layer": "ok"})
|
||||
await self.close()
|
||||
@@ -0,0 +1,11 @@
|
||||
import contextvars
|
||||
import logging
|
||||
|
||||
|
||||
request_id_context = contextvars.ContextVar("request_id", default="-")
|
||||
|
||||
|
||||
class RequestIDFilter(logging.Filter):
|
||||
def filter(self, record):
|
||||
record.request_id = request_id_context.get()
|
||||
return True
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import re
|
||||
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
from django.db import connection
|
||||
|
||||
|
||||
MINIMUM_VERSION = (8, 0, 35)
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "检查生产 MySQL 的版本、字符集、引擎、严格模式和事务隔离级别"
|
||||
|
||||
def handle(self, *args, **options):
|
||||
if connection.vendor != "mysql":
|
||||
raise CommandError(f"数据库必须是 MySQL,当前为 {connection.vendor}")
|
||||
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute(
|
||||
"""
|
||||
SELECT VERSION(),
|
||||
@@character_set_server,
|
||||
@@collation_server,
|
||||
@@sql_mode,
|
||||
@@transaction_isolation,
|
||||
@@default_storage_engine
|
||||
"""
|
||||
)
|
||||
(
|
||||
version_text,
|
||||
charset,
|
||||
collation,
|
||||
sql_mode,
|
||||
isolation,
|
||||
storage_engine,
|
||||
) = cursor.fetchone()
|
||||
|
||||
match = re.match(r"^(\d+)\.(\d+)\.(\d+)", version_text)
|
||||
if match is None:
|
||||
raise CommandError(f"无法解析 MySQL 版本: {version_text}")
|
||||
version = tuple(int(part) for part in match.groups())
|
||||
if version < MINIMUM_VERSION:
|
||||
minimum = ".".join(str(part) for part in MINIMUM_VERSION)
|
||||
raise CommandError(f"MySQL 版本过低: {version_text},最低要求 {minimum}")
|
||||
if charset.lower() != "utf8mb4":
|
||||
raise CommandError(f"character_set_server 必须是 utf8mb4,当前为 {charset}")
|
||||
if not collation.lower().startswith("utf8mb4_"):
|
||||
raise CommandError(f"collation_server 必须属于 utf8mb4,当前为 {collation}")
|
||||
|
||||
modes = {mode.strip().upper() for mode in sql_mode.split(",")}
|
||||
if not {"STRICT_TRANS_TABLES", "STRICT_ALL_TABLES"} & modes:
|
||||
raise CommandError(f"MySQL 未启用严格模式: {sql_mode}")
|
||||
if isolation.upper().replace("_", "-") != "READ-COMMITTED":
|
||||
raise CommandError(f"事务隔离级别必须是 READ-COMMITTED,当前为 {isolation}")
|
||||
if storage_engine.lower() != "innodb":
|
||||
raise CommandError(f"默认存储引擎必须是 InnoDB,当前为 {storage_engine}")
|
||||
|
||||
self.stdout.write(
|
||||
self.style.SUCCESS(
|
||||
"MySQL 检查通过: "
|
||||
f"version={version_text}, charset={charset}, "
|
||||
f"collation={collation}, isolation={isolation}, engine={storage_engine}"
|
||||
)
|
||||
)
|
||||
@@ -0,0 +1,19 @@
|
||||
import uuid
|
||||
|
||||
from .logging import request_id_context
|
||||
|
||||
|
||||
class RequestIDMiddleware:
|
||||
def __init__(self, get_response):
|
||||
self.get_response = get_response
|
||||
|
||||
def __call__(self, request):
|
||||
request_id = request.headers.get("X-Request-ID") or str(uuid.uuid4())
|
||||
request.request_id = request_id
|
||||
token = request_id_context.set(request_id)
|
||||
try:
|
||||
response = self.get_response(request)
|
||||
response["X-Request-ID"] = request_id
|
||||
return response
|
||||
finally:
|
||||
request_id_context.reset(token)
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -0,0 +1,14 @@
|
||||
from django.db import connection
|
||||
from django.http import JsonResponse
|
||||
from django.shortcuts import render
|
||||
|
||||
|
||||
def home(request):
|
||||
return render(request, "index.html")
|
||||
|
||||
|
||||
def health(request):
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute("SELECT 1")
|
||||
cursor.fetchone()
|
||||
return JsonResponse({"status": "ok", "database": "ok"})
|
||||
Reference in New Issue
Block a user