# dj4_log **Repository Path**: beginning_of_spring/dj4_log ## Basic Information - **Project Name**: dj4_log - **Description**: django4 框架 docker 部署 supervisor 监控 uwsgi和daphne 服务器 记录日志 - **Primary Language**: Python - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2024-09-27 - **Last Updated**: 2024-10-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # dj4_log #### 介绍 Django5 框架 docker 部署 supervisor 监控 uwsgi和daphne 服务器。详细记录日志。集成 celery 在 django 服务和 celery 服务任意使用logger记录日志。 - 补充 daphen 和 uwsgi 的性能比较。asgi补充 - 类似cat日志:耗时、异常处理、有分析页面(每个接口耗时、异常展示详细信息) - celery手动调用页面,ws的手动调用页面 #### 软件架构 - django5.1x - python3.10x - restframework - mysql-connector-python #### 统一的请求方式`post|get` 未为了尽可能使用`restframework`特性,增加开发速度。`GenericAPIView`视图和`GenericViewSet`视图集进行了封装。如果是继承自最基础`from rest_framework.views import APIView`可以直接定义`post`和`get`方法,不必再使用下列方法。 - 获取数据请求方式使用`get` - 增删改请求方式使用`post` ##### 方式一 使用基础视图`CreateUpdateDeleteAPIView`。完成增删改操作。 ```python class CreateUpdateDeleteAPIView( mixins.CreateModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, GenericAPIView ): """创建数据、修改、删除 根据后缀 post/put/delete/ 处理增删改""" methods = settings.METHODS_MAP def post(self, request, *args, **kwargs): """ 请求方式 -> 方法映射 :param request: :param args: :param kwargs: :return: example url -> /user/post/ 创建 -> rest framework mixins create url -> /user//put/ 修改 -> rest framework mixins update url -> /user/delete/ 删除 -> rest framework mixins destroy """ method = kwargs.get('method') if not method in self.methods: return response(message='fail', code=Code.C_4_5) func = getattr(self, self.methods.get(method)) return func(request, *args, **kwargs) # 使用视图 class CreateUserView(view.CreateUpdateDeleteAPIView): queryset = models.User.objects.all() serializer_class = serializers.CreateUserSerializer permission_classes = () ``` 将路由配置在`urlpatterns`。注意使用`UrlPath.url_method`。前端请求url为:根路由+子路由+method=`/v1/system/user/register/post/`。 ```python from django.urls import path from . import views from framework.routers import Router from utils.url_path import UrlPath # 基础视图 urlpatterns = [ # 注册用户 path(UrlPath.url_method('user/register/'), views.CreateUserView.as_view()), ] router = Router() # viewsets 视图 router.register('users', views.UserModelViewSets, basename='users') urlpatterns += router.urls ``` 序列化器,在序列化器中实现验证、datetime格式化、返回字段设置。 ```python class CreateUserSerializer(ModelSerializer): password = serializers.CharField(write_only=True, required=False, label='密码') username = serializers.CharField(required=True, label='用户名') tel = serializers.CharField(required=True, label='手机号') class Meta: model = models.User fields = ('id', 'username', 'password', 'tel', 'name') @staticmethod def validate_tel(value): """验证手机格式""" pattern = r'^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$' if not re.search(re.compile(pattern), value): raise exceptions.ValidationError('手机号格式不正确') return value def create(self, validated_data): """创建用户设置密码""" queryset = models.User.objects.filter(username=validated_data['username']) if queryset.exists(): raise exceptions.ValidationError('用户名已存在') password = validated_data.pop('password', None) with transaction.atomic(): user = super().create(validated_data) if not password: password = validated_data['username'] user.set_password(password) user.save() return user ``` 以上就完成了用户注册接口。 ##### 方式二 使用更加简便的视图集`ModelViewSets`。`restframework`特性在于更少的代码实现重复的表单接口`curd`。视图集实现具体逻辑,routers实现路由定义,序列化起实现验证等一系列序列化和反序列化操作。 实现需求:人员的下拉框、删除单个和多个用户、用户多个分页、单个用户查看详情、修改单个用户、修改多个用户某个字段接口。 视图集已经实现了全部逻辑。 ```python class ModelViewSets( # 创建用户 mixins.CreateModelMixin, # 查看单个 mixins.RetrieveModelMixin, # 修改单个 mixins.UpdateModelMixin, # 修改多个 mixins.PuBatchMixin, # 删除单个 mixins.DestroyModelMixin, # 多条数据分页 mixins.ListModelMixin, # 用户下拉框 mixins.SelectMixin, # 批量删除 mixins.DeleteBatchMixin, GenericViewSet ): """ 全局分页、单个查询、创建、修改、单个、多个删除、下拉框 """ ``` 在你的`views`使用它。 ```python class UserModelViewSets(view.ModelViewSets): """ 此视图 创建用户不可用 """ queryset = models.User.objects.all() serializer_class = serializers.UserSerializer ``` 注册到路由 ```python from django.urls import path from . import views from framework.routers import Router from utils.url_path import UrlPath # 基础视图 urlpatterns = [ # 注册用户 path(UrlPath.url_method('user/register/'), views.CreateUserView.as_view()), ] router = Router() # 视图集 注册 router.register('users', views.UserModelViewSets, basename='users') # 注册到路由 urlpatterns += router.urls ``` 路由`from framework.routers import Router`封装了从url到具体逻辑的映射。你会得到以下接口。 ``` /v1/system/users/ 多个分页接口 /v1/system/users/post/ 创建接口 - 此处不用创建接口 /v1/system/users/delete_batch/ 删除多个接口 /v1/system/users/put_batch/ 修改多个接口 /v1/system/users/select/ 下拉框接口 /v1/system/^users/(?P[^/.]+)/$ 查看单个用户详细信息 /v1/system/^users/(?P[^/.]+)/put/$ 修改单个接口 /v1/system/^users/(?P[^/.]+)/delete/$ 删除单个接口 ``` #### 登陆认证 - **access_token**:连接 token,时效短 - **refresh_token**:刷新 token,时效长 #### 权限认证 框架默认开启全局权限认证。认证模式`RBAC`:用户、角色、资源。即一个用户可能存在多个角色,一个角色有多个资源。资源是菜单和按钮权限。也就是全局认证将认证到按钮级别。 ##### 权限配置 抽象出一个基础员工角色,所有角色拥有员工角色权限。这样可以避免前端重复设置权限。 - 基础员工角色:包含员工拥有权限 - 主管角色:拥有员工角色+特殊权限 ##### 视图级别 - 视图级别权限是用户认证通过到视图逻辑层面权限。特殊需求可以关闭权限此视图权限控制。 - 开启权限需要将对应`url`配置在资源表中并将资源绑定给某个角色,角色赋予某个用户 ```python permission_classes = () # 具体视图设置 ``` ##### 菜单级别 - 菜单是资源的一种 ##### 按钮级别 - 按钮是资源的一种 ##### 行级别 - 根据实际需求视图中处理 #### 状态码 后端统一状态码。旨在和前端统一的响应格式。前端根据后端议定的状态码提示和处理。 | 状态码 | 描述 | 备注 | | ------ | -------------------------- | ------------------------------------------------------------ | | 2000 | success | 请求成功 | | 2001 | 创建数据 | | | 4000 | 请求参数验证失败 | 可预期的错误 | | 4001 | 认证失败/refresh token失效 | 用户名或密码错误,前端重新登陆 | | 4010 | jwt access token 过期 | 前端内部调用接口,通过 refresh 获取 access token | | 4003 | 没有权限访问 | 后端校验权限失败,用户无权限访问 | | 4004 | 没有找到数据 | 后端在逻辑中没有找到某个数据,逻辑不能进行 | | 4005 | 不支持的请求 | 请求不支持 | | 5000 | 内部异常 | 后端逻辑中,预知一些错误,逻辑不能进行,这些错误可能来源于关系性数据库的静态表配置。 | | 5010 | 意外的错误 | 后端,不可预期的错误。 | | 2002 | 修改数据 | | | 2003 | 删除数据 | | #### 使用说明 ##### 结构 ``` -- conf/ -- readme.md -- default.conf docker-compose nginx配置挂载,也可以个在当前文件夹下新建nginx文件夹挂载到conf.d文件夹中 -- src/ 后端代码 -- app/ 后端模块 -- celery_tasks/ celery 任务 -- conf/ 部署需要配置 -- readme.md -- req.txt -- supervisor.conf 启动uwsgi任务配置 -- supervisord.conf 默认配置 -- uwsgi.ini -- core/ -- utils/ 框架继承api和额外的封装api __init__.py 全局code -- base.py 公共api -- exceptions.py 全局异常捕捉 处理统一响应 -- mixins.py 依赖restframework 公共curd -- pagination.py 全局分页 -- permissions.py 全局权限校验 -- serializers.py 公共序列化组件 -- value_dispath.py 重构逻辑判定组件 -- views.py 认证视图、curd核心 -- Dockerfile -- manage.py -- .env docker-comose.yml 对应应用下 environment变量 -- readme.md -- docker-compose.yml ``` ##### 本地开发 - clone 后新增`app`放到·`/src/app`下。如果专注开发通过ide打开src即可。 - /src/core/目录下新建`local_settings.py`新增配置 - 新增日志 ```python LOGGING = { "version": 1, "disable_existing_loggers": False, "formatters": { "simple": { "format": "{levelname} {message}", "style": "{", }, }, "filters": { "require_debug_true": { "()": "django.utils.log.RequireDebugTrue", # 当DEBUG为True时,传递记录。 }, }, "handlers": { "console": { "level": "INFO", "filters": ["require_debug_true"], "class": "logging.StreamHandler", }, }, "loggers": { "django": { "level": "INFO", "handlers": ['console'], 'propagate': True }, }, } ``` - jwt配置增加 access token有效时间 ```python SIMPLE_JWT = { # token 有效期 'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60), # 重新获取 access token 过期时间 'REFRESH_TOKEN_LIFETIME': timedelta(days=1), # 请求 'AUTH_HEADER_TYPES': ('Bearer',), 'TOKEN_USER_CLASS': 'system.models.User' } ``` - Restframework 权限认证可取消。复制 settings.py`REST_FRAMEWORK`注释`DEFAULT_PAGINATION_CLASS`。保留全局分页和过滤 ``` # # REST_FRAMEWORK = { # # jwt 认证 # 'DEFAULT_AUTHENTICATION_CLASSES': ( # 'rest_framework_simplejwt.authentication.JWTAuthentication', # ), # # 分页 # 'DEFAULT_PAGINATION_CLASS': 'utils.pagination.Pagination', # # 过滤 # 'DEFAULT_FILTER_BACKENDS': ( # 'django_filters.rest_framework.DjangoFilterBackend', # ) # } ``` ​ #### 部署-生产 - .env文件 ``` DATABASE_PORT = 3306 DATABASE_HOST = 192.168.20.119 DATABASE_NAME = dj DATABASE_USER = root DATABASE_PASSWORD = hello LOGS_DIR = /logs # 对应 Dockerfile文件中日志记录 对应docker-compose.yml日志挂载路径。方便追溯问题 ALLOWED_HOSTS = ['localhost', '192.168.20.119'] # 设置可以访问的ip HTTP_PORT = 8088 # nginx 代理 web端口 DJ_IMAGES_VERSION = 1.0.0 # docker images web 版本 PJ_TAG = hi # docker 容器 tag PG_DEBUG = 0 # web debug 开关 0 关闭 ``` - LOGGINGS 生产环境 ```python LOGGING = { "version": 1, "disable_existing_loggers": False, "formatters": { "verbose": { "format": "{name} {levelname} {asctime} {module} {process:d} {thread:d} {message}", "style": "{", } }, "handlers": { "file": { "class": "logging.handlers.RotatingFileHandler", "filename": LOGS_DIR / 'dj.log', 'level': 'INFO', 'maxBytes': 50 * 1024 * 1024, 'backupCount': 10, 'formatter': 'verbose' } }, "loggers": { "django": { "level": "INFO", "handlers": ['file'], 'propagate': True }, }, } ``` ##### SETTINGS.PY检查清单 - ALLOWED_HOSTS [`DEBUG = False`](https://docs.djangoproject.com/zh-hans/4.2/ref/settings/#std-setting-DEBUG) 时,Django 在未正确配置 [`ALLOWED_HOSTS`](https://docs.djangoproject.com/zh-hans/4.2/ref/settings/#std-setting-ALLOWED_HOSTS) 时无法工作。 - [`STATIC_ROOT`](https://docs.djangoproject.com/zh-hans/4.2/ref/settings/#std-setting-STATIC_ROOT)和[`STATIC_URL`](https://docs.djangoproject.com/zh-hans/4.2/ref/settings/#std-setting-STATIC_URL)静态文件由开发服务器自动托管。但在生产环境,你必须定义配置 [`STATIC_ROOT`](https://docs.djangoproject.com/zh-hans/4.2/ref/settings/#std-setting-STATIC_ROOT) , [`collectstatic`](https://docs.djangoproject.com/zh-hans/4.2/ref/contrib/staticfiles/#django-admin-collectstatic) 将会拷贝它们。这些配置基于你是否需要Django提供静态文件服务。多数情况下`nginx`会作为文件服务器(为了配置方便需要配合`docker-compose`文件挂载)。 - DATABASES .env正确配置 ##### nginx配置 - Docker-compose 中正确挂载 nginx 文件或文件夹。 #### 生产环境问题排查 - uwsgi 日志 uwsgi日志路径在supervisor.conf中。将容器中路径挂载到本地。它记录着每个请求简略信息。 ``` [pid: 12|app: 0|req: 1/1] 192.168.65.1 () {40 vars in 763 bytes} [Mon Sep 30 15:17:11 2024] GET /v1/system/users/ => generated 143 bytes in 345 msecs (HTTP/1.1 400) 4 headers in 172 bytes (1 switches on core 0) [pid: 12|app: 0|req: 2/2] 192.168.65.1 () {40 vars in 763 bytes} [Mon Sep 30 15:17:48 2024] GET /v1/system/users/ => generated 236 bytes in 93 msecs (HTTP/1.1 200) 8 headers in 254 bytes (1 switches on core 0) [pid: 12|app: 0|req: 3/3] 192.168.65.1 () {40 vars in 763 bytes} [Mon Sep 30 15:18:48 2024] GET /v1/system/users/ => generated 236 bytes in 19 msecs (HTTP/1.1 200) 8 headers in 254 bytes (1 switches on core 0) ``` - django日志 Django日志路径在后端`settings.py`记录。构建容器时`.env`文件配置。挂载到本地。它记录着`loggers`限定等级的日志详细信息。你可以通过它来定位问题。 - nginx日志 ​ 通过挂载路径查看连接和错误日志 #### 监控