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将会继续处理这个请求
python 复制代码
# 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'))
python 复制代码
# 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 对象。
    • 无论这个方法返回什么,这个返回值都将作为请求的最终响应。
python 复制代码
# # Django 1.10 以前的旧的中间件风格
class CustomResponseMiddleware:
    def process_response(self, request, response):
        # 在这里对响应进行处理
        response['X-Custom-Header'] = 'Custom Value'
        return response
python 复制代码
# # 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 对象视图函数将不会被调用
python 复制代码
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 方法将不会被调用
python 复制代码
class SimpleMiddleware:
    def process_exception(self, request, exception):
        # 在这里处理异常
        # 如果返回 HttpResponse 对象,Django 将停止调用其他的 process_exception 方法
        # 如果返回 None 或者没有明确的返回值,Django 将继续调用其他的 process_exception 方法
        return None
python 复制代码
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__)的类。
python 复制代码
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的顺序注册
python 复制代码
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)测试

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

【2】测试process_request

(1)django框架

  • 如果将第一个中间件的process_request
    • 添加返回值HttpResponse
    • 那么将直接走当前中间件的process_response
python 复制代码
def process_request(self, request):
	print("第一个中间件的process_request")
	print("第一个中间价process_request拦截")
	return HttpResponse("中间件一拦截")
  • 结果
python 复制代码
第一个中间件的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示例

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%}
html 复制代码
<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令牌
html 复制代码
<input type="hidden" name="csrfmiddlewaretoken" value="xKeMNRcsTZ1ws40kq8MXKSAfdsVIu9pVIwQZaOdYpAPyPXKXYr6CMYlVN8p5bbcn">
  • 后端可以拿到的数据
    • 堕落一个csrf令牌
python 复制代码
<QueryDict: {'csrfmiddlewaretoken': ['xKeMNRcsTZ1ws40kq8MXKSAfdsVIu9pVIwQZaOdYpAPyPXKXYr6CMYlVN8p5bbcn'], 'username': [''], 'password': ['']}>

(2)使用ajax提交csrf

  • 通过jquery语法捕获表单的内容传递给后端
html 复制代码
<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()方法
html 复制代码
<script>
    $(document).ready(function () {
            $("#b1").on('click', function (event) {
                event.preventDefault();
                let formData = $("form").serialize()
                $.ajax({
                    url: "",
                    type: "post",
                    data: formData,
                    success: function () {

                    }
                })
            })
        }
    )
</script>
  • 使用模板语法取值
html 复制代码
<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文件
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中
html 复制代码
{% 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)介绍

  • 导入
python 复制代码
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. 灵活、可拓展
python 复制代码
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】分析

  • 这种方式中

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

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

    • 这不就是

      python 复制代码
      from 路径 import 类名
  • 有这么一个模块importlib

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

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

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

【3】使用反射函数和importlib

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

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

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

  • my_class/settings.py
python 复制代码
MODEL_LIST = [
    'dingding.DingDing',
    'qq.QQ',
    'wechat.WeChat',
]

(3)配置__init__.py

  • my_class/__init__.py
python 复制代码
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)外部文件使用

python 复制代码
import my_class

my_class.send_all("Hi")
# DingDing发送消息:>>>Hi
# QQ发送消息:>>>Hi
# WeChat发送消息:>>>Hi
相关推荐
封步宇AIGC25 分钟前
量化交易系统开发-实时行情自动化交易-3.4.1.2.A股交易数据
人工智能·python·机器学习·数据挖掘
何曾参静谧26 分钟前
「Py」Python基础篇 之 Python都可以做哪些自动化?
开发语言·python·自动化
Prejudices30 分钟前
C++如何调用Python脚本
开发语言·c++·python
喵叔哟33 分钟前
【.NET 8 实战--孢子记账--从单体到微服务】--简易权限--访问权限中间件
微服务·中间件·.net
青锐CC35 分钟前
webman使用中间件验证指定的控制器及方法[青锐CC]
中间件·前端框架·php
我狠狠地刷刷刷刷刷43 分钟前
中文分词模拟器
开发语言·python·算法
Jam-Young1 小时前
Python的装饰器
开发语言·python
man20171 小时前
【2024最新】基于springboot+vue的闲一品交易平台lw+ppt
vue.js·spring boot·后端
Mr.咕咕1 小时前
Django 搭建数据管理web——商品管理
前端·python·django
hlsd#1 小时前
关于 SpringBoot 时间处理的总结
java·spring boot·后端