Django 中间件

【一】Django框架之生命周期流程图

在这里插入图片描述

【二】介绍

【1】概述

  • Django 中的中间件(Middleware)是一个轻量级底层的“插件”系统,用来全局改变 Django 的输入输出
  • 每个中间件组件负责处理特定的全局任务,例如处理会话、处理跨站请求伪造保护、实现缓存处理等。

【2】主要作用

  • 请求处理

    • 在 Django 处理视图之前,中间件可以对请求进行预处理。
    • 例如,某些中间件可以更改 HTTP 请求头,或者在请求对象上设置其他属性。
  • 响应处理

    • 在 Django 完成视图处理后,中间件可以对响应进行后处理。
    • 例如,某些中间件可以更改 HTTP 响应头,或者在响应内容中添加额外的数据。
  • 异常处理

    • 如果视图抛出异常,中间件可以捕获这些异常并进行处理。
    • 例如,某些中间件可以将异常记录到日志中,或者显示一个友好的错误页面。
  • 全局任务

    • 中间件可以处理那些需要在每个请求和响应中都执行的任务。
    • 例如,Django 的 SessionMiddleware 在每个请求开始时加载会话数据,并在每个响应结束时保存会话数据。
  • 大致顺序

    • 中间件是按照在 settings.py 文件的 MIDDLEWARE 设置中定义的顺序执行的。
    • 当一个请求到来时,Django 会按照从上到下的顺序应用每个中间件,然后调用相应的视图。
    • 当视图处理完成后,Django 会按照从下到上的顺序应用每个中间件,然后返回响应。

【三】默认的中间件

【1】七大中间件

(1)SecurityMiddleware

  • django.middleware.security.SecurityMiddleware

  • 安全中间件

    • 负责处理与网站安全相关的任务
  • 例如:

    • 自动转换非HTTPS请求到HTTPS(如果 SECURE_SSL_REDIRECT 设置为 True
    • 添加 HTTP Strict Transport Security(HSTS)头部(如果 SECURE_HSTS_SECONDS 设置的值大于0)
    • 以及防止点击劫持(如果 X_FRAME_OPTIONS 设置为 'DENY''SAMEORIGIN')。
  • 它可以通过配置自定义安全策略来确保网站的安全性。

(2)SessionMiddleware

  • django.contrib.sessions.middleware.SessionMiddleware
  • 会话中间件
    • 它在每个请求开始时加载会话数据,并在每个响应结束时保存会话数据。

(3)CommonMiddleware

  • django.middleware.common.CommonMiddleware
  • 通用中间件
    • 提供了一些常见而关键的HTTP请求处理功能
    • 例如,根据请求的HTTP头信息设置语言、时区等。

(4)CsrfViewMiddleware

  • django.middleware.csrf.CsrfViewMiddleware
  • 跨站请求伪造中间件CSRF(Cross-Site Request Forgery)
    • 用于防止跨站请求伪造攻击。
    • 它在每个POST请求中验证一个CSRF标记,确保请求是通过合法的表单提交得到的,从而保护用户免受恶意站点的攻击。

(5)AuthenticationMiddleware

  • django.contrib.auth.middleware.AuthenticationMiddleware
  • 认证中间件
    • 负责处理用户身份认证相关的任务
    • 这个中间件为每个请求添加一个 user 属性,表示当前的登录用户。如果用户没有登录,request.user 将是一个 AnonymousUser 实例。否则,request.user 将是一个 User 实例。

(6)MessageMiddleware

  • django.contrib.messages.middleware.MessageMiddleware
  • 消息中间件
    • 用于在请求处理过程中存储和传递临时的、一次性的用户消息。
    • 它允许在HTTP重定向之间跨请求传递消息,例如成功或错误提示,以改善用户体验。

(7)XFrameOptionsMiddleware

  • django.middleware.clickjacking.XFrameOptionsMiddleware
  • 点击劫持中间件
    • 用于防止页面被嵌入到其他网站中,从而提供一定的点击劫持保护。
    • 它通过设置X-Frame-Options HTTP头部来限制页面的显示方式,从而防止恶意网页通过iframe等方式嵌入当前网页。

【2】中间件方法

(1)process_request

  • process_request(self, request)
    • 他会在Django开始处理每个请求之前被调用
    • 这个方法接收一个request参数,这参数是一个HttpResponse对象,包含关于当前请求的所有信息
    • 这个方法可以修改request对象,或者直接返回一个HttpResponse对象来终止请求的处理
    • 这个参数如果是None,Django将会继续处理这个请求
# Django 1.10 以前的旧的中间件风格
from django.http import HttpResponseRedirect
from django.urls import reverse
class AuthenticationMiddleware:
    def process_request(self, request):
        # 在这里进行身份验证操作
        if not request.user.is_authenticated:
            # 如果用户未经身份验证,则返回HttpResponse或重定向到登录页面
            return HttpResponseRedirect(reverse('login'))
# Django 1.10 以后的新的中间件风格,它需要定义 __call__ 方法。__call__ 方法在每个请求上都会被调用
from django.http import HttpResponseRedirect
from django.urls import reverse

class AuthenticationMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        # 在这里进行身份验证操作
        if not request.user.is_authenticated:
            # 如果用户未经身份验证,则重定向到登录页面
            return HttpResponseRedirect(reverse('login'))
        
        # 如果用户已经通过身份验证,继续处理请求
        # get_response 是一个回调函数,当我们在中间件中调用它时,Django 会继续处理请求,调用视图函数并生成响应。
        response = self.get_response(request)
        return response

(2)process_response

  • process_response(self, request, response)
    • 它会在 Django 完成处理每个请求之后被调用
    • 这个方法接收两个参数:request 是一个 HttpRequest 对象,包含了关于当前请求的所有信息response 是一个 HttpResponse 对象,包含了当前请求的响应
    • 这个方法可以修改 response 对象,或者返回一个全新的 HttpResponse 对象。
    • 无论这个方法返回什么,这个返回值都将作为请求的最终响应。
# # Django 1.10 以前的旧的中间件风格
class CustomResponseMiddleware:
    def process_response(self, request, response):
        # 在这里对响应进行处理
        response['X-Custom-Header'] = 'Custom Value'
        return response
# # Django 1.10 以后的新的中间件风格,它需要定义 __call__ 方法。__call__ 方法在每个请求上都会被调用
class CustomResponseMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        # 在这里,可以添加在请求被处理之前需要执行的逻辑

        response = self.get_response(request)

        # 在这里对响应进行处理
        response['X-Custom-Header'] = 'Custom Value'

        return response
  • 每个中间件都是依次处理的,而不是并行处理的。尽管在新的中间件风格(Django 1.10 及更高版本)中,__call__ 方法同时处理进入和离开的请求,但这并不意味着中间件是并行运行的。

    当一个请求到达 Django 应用时,它会按照 MIDDLEWARE 列表的顺序,依次通过每个中间件的 __call__ 方法。当在 __call__ 方法中调用 get_response(request) 时,控制权会传递给下一个中间件,直到所有的中间件都运行完毕,然后才会执行视图函数。

    在视图函数执行完毕并生成响应后,控制权会按照与进入时相反的顺序,回到每个中间件的 __call__ 方法。在这个阶段,中间件可以修改响应或执行其他后处理操作。

    所以,尽管 __call__ 方法看起来同时处理进入和离开的请求,但实际上,中间件仍然是按照特定的顺序依次执行的。这就是为什么 Django 中间件有时被描述为像洋葱一样的结构:每个中间件都像洋葱的一层,请求和响应都必须穿过所有的层。

(3)process_view

  • process_view(request, view_func, view_args, view_kwargs)
    • 路由匹配成功后执行视图函数之前
    • 这个方法的参数包括请求对象、视图函数、视图函数的参数和关键字参数。
    • 如果这个方法返回一个 HttpResponse 对象视图函数将不会被调用
import logging
logger = logging.getLogger(__name__)

class LoggingMiddleware:
    def process_view(self, request, view_func, view_args, view_kwargs):
        # 在这里记录日志
        logger.info(f"Request received: {request.path}")
        # 返回None,继续执行原视图函数
        return None

(4)process_template_response

  • process_template_response(request, response)
    • 这个方法在视图函数处理完请求并生成了一个模板响应对象后返回的 HttpResponse 对象有 render 属性的时候才会触发
    • 顺序是按照配置文件中注册了的中间件从下往上依次经过。
    • 这个方法的参数包括请求对象和视图函数返回的响应对象。必须返回一个TemplateResponse对象,通常是传入的响应对象或者一个新的响应对象。

(5)process_exception

  • process_exception(request, exception)
    • 这个方法在视图函数或者其他的中间件方法抛出异常时运行。
    • 顺序是按照配置文件中注册了的中间件从下往上依次经过
    • 例如返回一个定制的错误页面或进行日志记录等。
    • 这个方法的参数包括请求对象和抛出的异常对象。
    • 如果这个方法返回一个 HttpResponse 对象,这个响应将会被发送到客户端,而且其他的 process_exception 方法将不会被调用
class SimpleMiddleware:
    def process_exception(self, request, exception):
        # 在这里处理异常
        # 如果返回 HttpResponse 对象,Django 将停止调用其他的 process_exception 方法
        # 如果返回 None 或者没有明确的返回值,Django 将继续调用其他的 process_exception 方法
        return None
class ErrorHandlerMiddleware:
    def process_exception(self, request, exception):
        # 在这里处理异常
        if isinstance(exception, CustomException):
            # 如果自定义异常,返回一个定制的错误页面
            return render(request, 'error.html', {'error': str(exception)})
        else:
            # 默认情况,返回一个500服务器错误
            return HttpResponseServerError("Internal Server Error")

【四】自定义中间件

【1】简单示例

(1)自定义

  • 自定义中间件位置
    • 可以在项目的根目录或者在应用目录下创建任意名称的文件夹
  • 创建py文件
    • py文件的名字也是任意
  • 创建自定义中间件类
    • 需要继承MiddlewareMixin混入类
  • 版本
    • 在 Django 1.10 之前,中间件需要定义一些特定的方法,如 process_request(request)process_response(request, response) 等,并且需要继承 MiddlewareMixin 混入类
    • 在 Django 1.10 及之后的版本中,推荐的写法是定义一个只有两个方法(__init____call__)的类。
from django.utils.deprecation import MiddlewareMixin


class MyMiddleWare1(MiddlewareMixin):
    def process_request(self, request):
        print("第一个中间件的process_request")

    def process_response(self, request, response):
        print("第一个中间件的process_response")
        return response


class MyMiddleWare2(MiddlewareMixin):
    def process_request(self, request):
        print("第二个中间件的process_request")

    def process_response(self, request, response):
        print("第二个中间件的process_response")
        return response

(2)添加到配置文件

  • 在settings文件的
    • MIDDLEWARE列表里面添加自定中间件
    • 整体是字符串格式
    • 路径之间通过.符号连接
    • 多个中间件之间以,逗号连接
  • 这里按照中间件1然后中间件2的顺序注册
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    # 添加自定义中间件
    'app01.mymiddleware.my_middleware.MyMiddleWare1',
    'app01.mymiddleware.my_middleware.MyMiddleWare2',
]

(3)测试

  • 视图函数
def home(request):
    print("访问主页")
    return HttpResponse("主页")
  • 输出结果
第一个中间件的process_request
第二个中间件的process_request
访问主页
第二个中间件的process_response
第一个中间件的process_response

【2】测试process_request

(1)django框架

  • 如果将第一个中间件的process_request
    • 添加返回值HttpResponse
    • 那么将直接走当前中间件的process_response
def process_request(self, request):
	print("第一个中间件的process_request")
	print("第一个中间价process_request拦截")
	return HttpResponse("中间件一拦截")
  • 结果
第一个中间件的process_request
第一个中间价process_request拦截
第一个中间件的process_response

(2)flask框架

  • flask框架的中间件也有类似的方法
  • 但是Flask 的中间件处理方式与 Django 略有不同。
    • 方法名字不同:
      • Flask 使用称为 “before_request” 和 “after_request” 的装饰器来处理请求前和请求后的操作。
    • 处理结果不同:
      • 在 Flask 中,如果在第一个中间件的 before_request 中返回了一个 Response 对象,那么 Flask 将立即停止处理后续的 before_request 函数,并且立即开始处理所有已注册的 after_request 函数。
      • 和djanfo相同的是都是倒叙

【五】跨站请求伪造

【1】介绍

(1)概述

  • 跨站请求伪造
    • (Cross-Site Request Forgery,CSRF)是一种网络攻击方式
    • 攻击者通过伪造用户的请求来让被攻击的网站执行非预期的操作
  • 例如:
    • 假设你已经登录了一个网站,并且在这个网站上有一些敏感操作,比如更改密码或者转账。
    • 一个 CSRF 攻击可能会在你访问一个恶意网站时,伪造一个请求到这个网站,尝试更改你的密码或者转账。
    • 因为你已经登录了这个网站,所以这个请求可能会被这个网站接受,并执行这个操作。

(2)html示例

<!DOCTYPE html>
<html>
<head>
    <title>Test Page</title>
</head>
<body>
    <form action="没被攻击的网站" method="post">
        <input type="text" placeholder="请输入转账目标用户">
        <input type="text" name="hiddenField" value="实际被更改的收款方" style="display: none;">
        <input type="submit" value="Submit">
    </form>
</body>

(3)防止 CSRF 攻击措施

  1. 使用 CSRF 令牌
    • 在每个需要用户交互的请求中,服务器都会生成一个唯一的 CSRF 令牌,并将其嵌入到表单中。
    • 当用户提交表单时,服务器会检查请求中的 CSRF 令牌是否与之前生成的令牌匹配。
    • 如果不匹配,服务器就会拒绝这个请求。因为攻击者无法预测 CSRF 令牌,所以这可以有效地防止 CSRF 攻击。
  2. 检查 Referer 头
    • 服务器可以检查每个请求的 Referer 头,以确保它来自于一个可信的源。
    • 如果一个请求来自于一个不可信的源,服务器就可以拒绝这个请求。
  3. 使用 SameSite Cookie 属性
    • 这是一个相对较新的方法,通过设置 Cookie 的 SameSite 属性,可以限制 Cookie 只能在同一个站点中使用。
    • 这可以防止攻击者在其他站点中使用用户的 Cookie。
  4. 使用验证码
    • 在执行敏感操作时,要求用户输入验证码,可以防止 CSRF 攻击,因为攻击者无法预测验证码。
  5. 使用双重 Cookie 验证:
    • 在这种方法中,当用户登录时,服务器会发送两个 Cookie:一个是常规的会话 Cookie,另一个是 CSRF Cookie,它包含一个随机生成的 CSRF 令牌。
    • 然后,每当用户发送一个修改数据的请求(例如 POST 请求)时,客户端都需要在请求的参数中包含这个 CSRF 令牌。
    • 服务器会检查这个 CSRF 令牌是否与 CSRF Cookie 中的令牌匹配。如果不匹配,服务器就会拒绝这个请求。

【2】POST请求校验CSRF

  • 首先取消settings文件中的注释
  • 开启csrf检查

(1)form表单提交csrf

  • 在form表单里面添加
    • {% csrf_token%}
<form action="" method="post">
    {% csrf_token %}
    <p>username: <input type="text" name="username"></p>
    <p>password: <input type="password" name="password"></p>
    <button class="" id="b1">提交</button>
</form>
  • 前端源码会自动出现一个标签
    • name是csrfmiddlewaretoken
    • value是实时刷新的csrf令牌
<input type="hidden" name="csrfmiddlewaretoken" value="xKeMNRcsTZ1ws40kq8MXKSAfdsVIu9pVIwQZaOdYpAPyPXKXYr6CMYlVN8p5bbcn">
  • 后端可以拿到的数据
    • 堕落一个csrf令牌
<QueryDict: {'csrfmiddlewaretoken': ['xKeMNRcsTZ1ws40kq8MXKSAfdsVIu9pVIwQZaOdYpAPyPXKXYr6CMYlVN8p5bbcn'], 'username': [''], 'password': ['']}>

(2)使用ajax提交csrf

  • 通过jquery语法捕获表单的内容传递给后端
<script>
    $(document).ready(function () {
            $("#b1").on('click', function (event) {
                event.preventDefault();
                $.ajax({
                    url: "",
                    type: "post",
                    data: {
                        username: $("input[name='username']").val(),
                        password: $("input[name='password']").val(),
                        csrfmiddlewaretoken: $("input[name='csrfmiddlewaretoken']").val(),
                    },
                    success: function () {

                    }
                })
            })
        }
    )
</script>
  • 使用jquery的serialize()方法
<script>
    $(document).ready(function () {
            $("#b1").on('click', function (event) {
                event.preventDefault();
                let formData = $("form").serialize()
                $.ajax({
                    url: "",
                    type: "post",
                    data: formData,
                    success: function () {

                    }
                })
            })
        }
    )
</script>
  • 使用模板语法取值
<script>
    $(document).ready(function () {
            $("#b1").on('click', function (event) {
                event.preventDefault();
                $.ajax({
                    url: "",
                    type: "post",
                    data: {
                        username: $("input[name='username']").val(),
                        password: $("input[name='password']").val(),
                        csrfmiddlewaretoken: "{{ csrf_token }}",
                    },
                    success: function () {

                    }
                })
            })
        }
    )
</script>

(3)添加Js文件

  • 在static文件夹内添加js文件
function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = jQuery.trim(cookies[i]);
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) === (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}
var csrftoken = getCookie('csrftoken');

function csrfSafeMethod(method) {
  // these HTTP methods do not require CSRF protection
  return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}

$.ajaxSetup({
  beforeSend: function (xhr, settings) {
    if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
      xhr.setRequestHeader("X-CSRFToken", csrftoken);
    }
  }
});

  • ajax发送
    • 此时后端可以拿到传输的数据
    • 不过csrf令牌不在以出现在request.POST中
{% load static %}
<script src="{% static 'js/csrf_check.js' %}"></script>
<script>
    $(document).ready(function () {
            $("#b1").on('click', function (event) {
                event.preventDefault();
                $.ajax({
                    url: "",
                    type: "post",
                    data: {
                        username: $("input[name='username']").val(),
                        password: $("input[name='password']").val(),
                    },
                    success: function () {

                    }
                })
            })
        }
    )
</script>

【3】CSRF装饰器

  • 校验csrf总体有两种策略
    • 网站整体都校验csrf,部分不校验csrf
    • 网站整体都不校验csrf,部分校验csrf

(1)介绍

  • 导入
from django.views.decorators.csrf import csrf_exempt, csrf_protect
  • csrf_protect装饰器
    • 以确保视图函数在处理 POST 请求时进行 CSRF 验证。
    • 这对于某些视图函数来说是非常有用的,特别是当全局设置中禁用了 CSRF 中间件,但你仍然想要在某些特定的视图函数中启用 CSRF 保护。
    • 如果请求中没有有效的CSRF令牌或令牌校验失败,Django将返回403 Forbidden响应。
  • csrf_exempt装饰器
    • 可以使特定的视图函数免于 CSRF 验证。
    • 这在某些情况下是非常有用的,例如,当全局设置中启用了 CSRF 中间件,但你想要在某些特定的视图函数中禁用 CSRF 保护。
    • 比如与第三方系统进行集成、开放API接口等

(2)FBV使用

  • 没有特殊要求和特殊情况
  • 直接使用即可

(3)CBV使用

  • 在给CBV添加装饰器中
    • 三种方式都可以使用@csrf_protect装饰器
    • 针对@csrf_exempt装饰器只能给dispath方法,其他无效

【六】中间件思想importlib

【1】中间件的导入方式

  • 在settings配置文件中
    • MIDDLEWARE列表里面添加自定中间件
    • 整体是字符串格式
    • 路径之间通过.符号连接
    • 多个中间件之间以,逗号连接
  • 优势
    1. 想要添加具有同样方法的类,直接创建文件以后,添加配置即可
    2. 不想使用某个类的方法了,直接注释掉配置的响应配置
  • 总结
    1. 动态加载
    2. 灵活、可拓展
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

【2】分析

  • 这种方式中

    • 在最后一个.点符号之前,都是文件路径

    • 最后一个.点符号之后,就是类名了

    • 这不就是

      • from 路径 import 类名
        
  • 有这么一个模块importlib

    • 他提供了动态导入模块的方式
    • 即程序运行时,而不是在编译时
import importlib

# 动态导入 math 模块
math = importlib.import_module('math')

# 现在可以使用 math 模块了
print(math.sqrt(16))  # 输出:4.0

【3】使用反射函数和importlib

(1)模仿创建多个类文件

  • my_class/qq.py
class QQ(object):
    def send(self, content):
        print(f"QQ发送消息:>>>{content}")
  • my_class/dingding.py
class DingDing(object):
    def send(self, content):
        print(f"DingDing发送消息:>>>{content}")
  • my_class/wechat.py
class WeChat(object):
    def send(self, content):
        print(f"WeChat发送消息:>>>{content}")

(2)模仿创建配置文件settings

  • my_class/settings.py
MODEL_LIST = [
    'dingding.DingDing',
    'qq.QQ',
    'wechat.WeChat',
]

(3)配置__init__.py

  • my_class/__init__.py
import os
import sys
from my_class import settings
import importlib

# 添加当前文件夹到工作目录
sys.path.append(os.path.dirname(__file__))

# 创建共同的方法接口
def send_all(content):
    for path_str in settings.MODEL_LIST:
        # 最右按照点符号侧切分一次,得到路径和类名
        model_path, class_name = path_str.rsplit('.', maxsplit=1)
        # 导入每个类的文件对象
        model = importlib.import_module(model_path)
        # 使用反射方法拿到类名
        cls = getattr(model, class_name)
        # 实例化类
        obj = cls()
        # 执行统一方法
        obj.send(content)

if __name__ == '__main__':
    send_all("hello world")
    # DingDing发送消息:>>>hello world
    # QQ发送消息:>>>hello world
    # WeChat发送消息:>>>hello world
    

(4)外部文件使用

import my_class

my_class.send_all("Hi")
# DingDing发送消息:>>>Hi
# QQ发送消息:>>>Hi
# WeChat发送消息:>>>Hi

相关推荐

  1. Django——中间

    2024-03-28 11:10:03       14 阅读
  2. Django中间

    2024-03-28 11:10:03       5 阅读
  3. Django 中间

    2024-03-28 11:10:03       7 阅读
  4. Backend - Django Middleware 中间

    2024-03-28 11:10:03       24 阅读
  5. django中的中间

    2024-03-28 11:10:03       30 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-03-28 11:10:03       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-03-28 11:10:03       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-03-28 11:10:03       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-03-28 11:10:03       18 阅读

热门阅读

  1. Linux系统账号文件

    2024-03-28 11:10:03       15 阅读
  2. axios配置以及说明

    2024-03-28 11:10:03       18 阅读
  3. Flutter 绘制原理

    2024-03-28 11:10:03       18 阅读
  4. 数据结构奇妙旅程之深入解析希尔排序

    2024-03-28 11:10:03       17 阅读
  5. Unity DOTS系列之IJobChunk来迭代处理数据

    2024-03-28 11:10:03       22 阅读
  6. 3月26日,每日信息差

    2024-03-28 11:10:03       15 阅读
  7. C++|char*大小端转换

    2024-03-28 11:10:03       18 阅读