7步精通:从编写到发布,打造你的Nginx模块

🔥关注墨瑾轩,带你探索编程的奥秘!🚀
🔥超萌技术攻略,轻松晋级编程高手🚀
🔥技术宝库已备好,就等你来挖掘🚀
🔥订阅墨瑾轩,智趣学习不孤单🚀
🔥即刻启航,编程之旅更有趣🚀

在这里插入图片描述在这里插入图片描述

第一章:Nginx模块开发的基础知识

1.1 什么是Nginx模块?

想象一下,Nginx就像是一个多功能的瑞士军刀,而模块就是这把刀上的不同工具。每个模块都赋予了Nginx新的功能,让这个服务器能够执行特定的任务。模块化的设计让Nginx既灵活又强大,可以轻松应对各种网络服务的需求。

1.2 Nginx模块的类型
  • 核心模块:这些是Nginx自带的模块,它们提供了基本的网络服务功能,比如HTTP服务、静态文件服务等。
  • 第三方模块:这些是由社区成员开发的,可以提供额外的功能,比如缓存、压缩等。
  • 自定义模块:这是我们今天要重点讨论的,开发者可以根据自己的需求,编写新的模块,实现独一无二的功能。
1.3 为什么需要开发Nginx模块?
  • 扩展性:Nginx的模块化设计允许开发者扩展其功能,满足特定的业务需求。
  • 性能优化:通过编写高效的模块,可以提升Nginx处理请求的速度。
  • 定制化服务:开发者可以根据自己的需求,定制Nginx的行为,提供更加个性化的服务。
1.4 如何开始Nginx模块开发?

在开始之前,你需要对C语言有一定的了解,因为Nginx模块主要是用C语言编写的。此外,对Nginx的工作原理和配置也要有一定的认识。

开始Nginx模块开发之旅

现在,我们已经对Nginx模块有了一个基本的了解,接下来,我们将一步步深入探索如何开发一个Nginx模块。不要担心,我会手把手教你,即使是编程小白,也能跟着我的步伐,一步步成为Nginx模块开发的高手!

第二章:搭建Nginx模块开发环境

2.1 安装Nginx源码

在开始编写代码之前,我们需要安装Nginx的源码。这就像是准备烘焙蛋糕的面粉和糖,没有这些基础材料,我们可做不出美味的蛋糕。

  1. 下载Nginx源码
    打开你的终端,使用wgetcurl命令下载最新的Nginx源码包。例如:

    wget http://nginx.org/download/nginx-1.18.0.tar.gz
    

    确保下载的版本是最新的,或者至少是你计划支持的版本。

  2. 解压源码包
    使用tar命令解压下载的源码包:

    tar -zxvf nginx-1.18.0.tar.gz
    
  3. 进入源码目录
    使用cd命令进入解压后的目录:

    cd nginx-1.18.0
    
2.2 配置编译选项

在编译Nginx之前,我们需要配置编译选项,这就像是调整烤箱的温度,确保蛋糕烤得恰到好处。

  1. 运行配置脚本
    使用./configure脚本配置编译选项。你可以添加不同的参数来启用或禁用某些特性。例如,启用HTTP缓存:

    ./configure --with-http_ssl_module --with-http_gzip_static_module
    
  2. 检查配置结果
    配置完成后,检查终端输出,确保没有错误信息。如果有错误,根据提示进行调整。

2.3 安装编译工具

编译工具就像是烘焙蛋糕的烤箱,没有它,我们的代码就无法“烤熟”。

  1. 安装编译器
    大多数Linux发行版都自带了gcc编译器。如果没有,你可以使用包管理器安装它:

    sudo apt-get install build-essential
    
  2. 安装PCRE库
    Nginx需要PCRE库来处理正则表达式。安装PCRE库:

    sudo apt-get install libpcre3 libpcre3-dev
    
  3. 安装zlib库
    如果你启用了gzip模块,还需要安装zlib库:

    sudo apt-get install zlib1g-dev
    
2.4 编译Nginx

现在,我们已经准备好了所有材料和工具,可以开始编译Nginx了。

  1. 使用make命令编译
    在Nginx源码目录下,运行make命令开始编译过程:

    make
    
  2. 检查编译结果
    编译完成后,检查是否有错误。如果没有错误,你将看到编译成功的信息。

  3. 安装Nginx
    使用make install命令将编译好的Nginx安装到系统:

    sudo make install
    
2.5 验证安装

安装完成后,我们需要验证Nginx是否安装成功。

  1. 启动Nginx
    使用以下命令启动Nginx服务:

    sudo systemctl start nginx
    
  2. 检查Nginx状态
    使用nginx -v命令检查Nginx版本,确保安装的是正确的版本:

    nginx -v
    
2.6 配置模块开发环境

最后,我们需要配置模块开发环境,这就像是为烘焙蛋糕准备模具。

  1. 创建模块目录
    在Nginx源码目录中创建一个新的目录,用于存放我们的模块代码:

    mkdir my_module
    cd my_module
    
  2. 创建模块框架
    创建一个C文件,比如ngx_http_my_module.c,并添加基本的模块框架代码。例如:

    #include <ngx_config.h>
    #include <ngx_core.h>
    #include <ngx_http.h>
    
    static char *ngx_http_my_module(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
    
    static ngx_command_t ngx_http_my_module_commands[] = {
        {ngx_string("my_module"),
         NGX_HTTP_LOC_CONF | NGX_CONF_NOARGS,
         ngx_http_my_module,
         0,
         0,
         NULL},
        ngx_null_command
    };
    
    static ngx_http_module_t ngx_http_my_module_ctx = {
        NULL,                               /* preconfiguration */
        NULL,                               /* postconfiguration */
        NULL,                               /* create main configuration */
        NULL,                               /* init main configuration */
        NULL,                               /* create server configuration */
        NULL,                               /* merge server configuration */
        NULL,                               /* create location configuration */
        NULL                                /* merge location configuration */
    };
    
    ngx_module_t ngx_http_my_module_module = {
        NGX_MODULE_V1,
        &ngx_http_my_module_ctx,            /* module context */
        ngx_http_my_module_commands,        /* module directives */
        NGX_HTTP_MODULE,                    /* module type */
        NULL,                               /* init master */
        NULL,                               /* init module */
        NULL,                               /* init process */
        NULL,                               /* init thread */
        NULL,                               /* exit thread */
        NULL,                               /* exit process */
        NULL,                               /* exit master */
        NGX_MODULE_V1_PADDING
    };
    
    static char *
    ngx_http_my_module(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
        ngx_http_core_loc_conf_t *loc_conf = ngx_http_conf_get_loc_conf(cf, ngx_http_core_module);
        loc_conf->handler = my_module_handler;
        return NGX_CONF_OK;
    }
    
    static ngx_int_t
    my_module_handler(ngx_http_request_t *r) {
        // 模块处理逻辑
        return NGX_OK;
    }
    
  3. 配置模块编译
    修改Nginx的./configure脚本,添加我们的模块到编译列表中。例如,在http/modules部分添加:

    ngx_addon_name=HTTP my_module
    
  4. 重新编译Nginx
    返回Nginx源码目录,重新运行./configuremake命令,编译包含新模块的Nginx。

2.7 结语

恭喜你,现在你已经成功搭建了Nginx模块的开发环境,并且创建了模块的基础框架。在下一章,我们将深入到模块的编写和实现,让你的模块真正开始工作。准备好了吗?让我们继续前进,探索Nginx模块开发的奇妙世界!🌈🚀

第三章:编写第一个Nginx模块

3.1 模块结构概览

在我们开始编写自己的Nginx模块之前,先来了解一个Nginx模块的基本结构。就像搭积木一样,我们需要知道每个积木块的作用。

  • 配置指令:定义模块可以接收的配置指令。
  • 上下文结构体:存储模块的配置信息。
  • 初始化函数:模块加载时的初始化操作。
  • 请求处理函数:处理HTTP请求。
3.2 创建模块框架

我们将创建一个简单的模块,这个模块会在每个请求上添加一个自定义的HTTP头部。

  1. 定义配置指令
    ngx_http_my_module.c中,我们定义一个配置指令my_header,用于设置我们要添加的头部的名称和值。

    // 配置指令结构体
    typedef struct {
        ngx_str_t header_name;
        ngx_str_t header_value;
    } ngx_http_my_module_loc_conf_t;
    
    // 配置指令数组
    static ngx_command_t ngx_http_my_module_commands[] = {
        {
            ngx_string("my_header"),
            NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE2,
            ngx_conf_set_str_slot,
            NGX_HTTP_LOC_CONF_OFFSET,
            offsetof(ngx_http_my_module_loc_conf_t, header_name),
            NULL
        },
        {
            ngx_string("my_header_value"),
            NGX_HTTP_MAIN_CONF | NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1,
            ngx_conf_set_str_slot,
            NGX_HTTP_LOC_CONF_OFFSET,
            offsetof(ngx_http_my_module_loc_conf_t, header_value),
            NULL
        },
        ngx_null_command
    };
    
  2. 定义模块上下文
    模块的上下文结构体用于存储模块的配置信息。

    // 模块上下文结构体
    static ngx_http_module_t ngx_http_my_module_ctx = {
        NULL,                               /* preconfiguration */
        NULL,                               /* postconfiguration */
        NULL,                               /* create main configuration */
        NULL,                               /* init main configuration */
        NULL,                               /* create server configuration */
        NULL,                               /* merge server configuration */
        ngx_http_my_module_create_loc_conf,  /* create location configuration */
        NULL                                /* merge location configuration */
    };
    
  3. 实现配置解析函数
    我们需要实现一个函数来解析我们的配置指令。

    static char *
    ngx_http_my_module(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) {
        ngx_http_my_module_loc_conf_t *loc_conf = conf;
        char *p = ngx_palloc(cf->pool, sizeof(ngx_str_t));
        *p = *(ngx_str_t *) cf->args->elts;
        loc_conf->header_name = *p;
        return NGX_CONF_OK;
    }
    
  4. 实现模块的请求处理函数
    这是模块的核心,我们将在这里添加自定义的HTTP头部。

    static ngx_int_t
    ngx_http_my_module_handler(ngx_http_request_t *r) {
        ngx_http_my_module_loc_conf_t *loc_conf;
    
        // 获取模块配置
        loc_conf = ngx_http_get_module_loc_conf(r, ngx_http_my_module);
    
        // 添加自定义头部
        if (loc_conf->header_name.len && loc_conf->header_value.len) {
            ngx_table_elt_t *h = ngx_list_push(&r->headers_out.headers);
            if (h == NULL) {
                return NGX_ERROR;
            }
            h->hash = 1;
            ngx_str_set(&h->key, (loc_conf->header_name.data));
            h->value = loc_conf->header_value;
        }
    
        return ngx_http_next_header_filter(r);
    }
    
  5. 注册模块到Nginx
    在模块的初始化函数中,我们需要注册我们的请求处理函数。

    static ngx_int_t
    ngx_http_my_module_init(ngx_conf_t *cf) {
        ngx_http_handler_pt *h;
        ngx_http_core_main_conf_t *cmcf;
    
        cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);
    
        h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE]);
        if (h == NULL) {
            return NGX_ERROR;
        }
        *h = ngx_http_my_module_handler;
    
        return NGX_OK;
    }
    
3.3 编译和加载模块

现在我们的模块已经编写完成,接下来我们需要将其编译进Nginx。

  1. 修改Nginx配置文件
    在Nginx的配置文件中,添加我们的模块配置指令。

    http {
        my_header X-Custom-Header;
        my_header_value "Custom Value";
    }
    
  2. 重新编译Nginx
    在Nginx源码目录下,运行./configuremake命令重新编译Nginx。

  3. 加载模块
    编译完成后,重启Nginx以加载我们的模块。

    sudo systemctl restart nginx
    
3.4 测试模块功能

编写完模块后,我们需要对其进行测试,确保它按预期工作。

  1. 发送HTTP请求
    使用curl或浏览器访问你的Nginx服务器,检查响应头部是否包含我们添加的自定义头部。

    curl -i http://localhost
    
  2. 检查响应头部
    在响应中查找X-Custom-Header: Custom Value,确认我们的模块工作正常。

3.5 结语

恭喜你,你已经成功编写并测试了你的第一个Nginx模块!这只是一个开始,Nginx模块的世界非常广阔,你可以继续探索和学习,开发出更多有趣和有用的模块。在下一章,我们将深入讨论如何编译和加载Nginx模块,确保你的模块能够在生产环境中稳定运行。准备好了吗?让我们继续前进,深入Nginx模块开发的更深层次!🚀🌟

第四章:编译和加载Nginx模块

4.1 模块编译前的准备

在我们深入到编译Nginx模块的步骤之前,让我们先确保一切准备工作都已就绪。这包括确保源代码的完整性,以及所有依赖库的可用性。

  1. 检查源代码完整性
    确保你的模块代码已经完整编写,并且所有必要的头文件和配置都已经添加到Nginx的配置中。

  2. 确认依赖库
    检查是否已经安装了所有需要的依赖库,如PCRE、zlib等。

  3. 清理之前的编译文件
    为了确保编译的清洁性,运行make clean来清理之前的编译生成的文件。

    make clean
    
4.2 编译模块

编译模块是一个精细的过程,需要确保每一步都正确无误。

  1. 配置Nginx
    使用./configure脚本配置Nginx,确保包含了你的模块。

    ./configure --with-http_ssl_module --add-module=/path/to/your/module
    
  2. 编译Nginx
    使用make命令开始编译过程。

    make
    
  3. 解决编译错误
    如果在编译过程中出现错误,仔细阅读错误信息,并根据提示进行修正。

4.3 模块的静态编译与动态加载

Nginx模块可以静态编译进Nginx,也可以动态加载。

  1. 静态编译
    静态编译意味着模块在编译Nginx时被包含进去,上面提到的make命令已经包含了这一步骤。

  2. 动态加载
    动态加载模块允许你在不重启Nginx的情况下加载和卸载模块。首先,确保Nginx配置了动态模块加载支持:

    ./configure --with-ngx_http_module
    

    然后,编译模块为一个单独的.so文件:

    make modules
    

    最后,使用load_module指令在Nginx配置文件中加载模块:

    load_module /path/to/your/module.so;
    
4.4 Nginx配置文件的修改

为了使Nginx识别新编译的模块,需要修改Nginx配置文件。

  1. 编辑Nginx配置
    找到Nginx的主配置文件nginx.conf,或者在特定的服务器块或位置配置文件中添加模块的配置指令。

  2. 添加模块配置指令
    根据你的模块功能,添加相应的配置指令。例如,如果你的模块添加了一个自定义的HTTP头部,你需要添加类似下面的配置:

    http {
        my_module_header "X-Custom-Header" "Custom Value";
    }
    
4.5 重新加载或重启Nginx

加载新模块后,需要重新加载或重启Nginx以应用更改。

  1. 重新加载配置
    使用nginx -s reload命令重新加载配置,这样可以实现不停机更新。

    sudo nginx -s reload
    
  2. 重启Nginx服务
    如果需要重启Nginx服务,可以使用以下命令:

    sudo systemctl restart nginx
    
4.6 验证模块是否加载成功

在Nginx重新加载或重启后,验证新模块是否成功加载。

  1. 检查Nginx错误日志
    查看Nginx的错误日志文件,确认没有加载模块的错误信息。

  2. 使用nginx -V命令
    这个命令会显示Nginx的版本信息以及所有已编译的模块。

    nginx -V
    
4.7 结语

现在,你已经了解了如何编译和加载Nginx模块。这个过程可能看起来有点复杂,但一旦你掌握了它,就能够轻松地为你的Nginx服务器添加新的功能。在下一章,我们将进入测试模块功能的阶段,确保我们的模块不仅能够加载,而且能够按预期工作。准备好了吗?让我们继续前进,确保我们的模块坚如磐石!🛠️💪

第五章:测试Nginx模块

5.1 单元测试的基本概念

在软件开发中,单元测试是确保每个部分或单元按照预期工作的基石。对于Nginx模块来说,单元测试可以帮助我们验证模块的每个功能点是否正常工作。

  1. 理解单元测试
    单元测试是针对模块中最小的可测试部分进行的测试。在Nginx模块中,这通常意味着对函数或方法的测试。

  2. 准备测试环境
    设置一个测试环境,这可能包括一个测试用的Nginx配置和一些模拟的HTTP请求。

5.2 编写单元测试用例

我们将使用C语言编写测试用例,并可能需要使用一些测试框架,如CUnit或Check。

  1. 创建测试文件
    创建一个新的C文件,例如test_ngx_http_my_module.c,用于编写测试用例。

  2. 编写测试函数
    为模块中的每个重要函数编写测试函数。例如,如果你的模块有一个函数用于添加HTTP头部,你会写一个测试函数来验证这个功能。

    void test_add_header(void) {
        // 设置测试环境
        ngx_http_request_t request;
        ngx_http_my_module_loc_conf_t loc_conf;
    
        // 初始化测试数据
        ngx_str_set(&loc_conf.header_name, "X-Test-Header");
        ngx_str_set(&loc_conf.header_value, "Test Value");
    
        // 调用函数
        ngx_http_my_module_handler(&request);
    
        // 验证结果
        // 这里应该有代码来检查request.headers_out中是否添加了正确的头部
    }
    
  3. 集成测试框架
    如果使用CUnit或Check,需要按照框架的要求初始化测试并运行测试用例。

5.3 集成测试

集成测试是验证模块在实际Nginx环境中与其他组件协同工作的情况。

  1. 配置Nginx
    使用你的模块配置启动Nginx,并确保所有必要的设置都已经就绪。

  2. 发送HTTP请求
    使用工具如curl或Postman发送HTTP请求到你的Nginx服务器,并检查响应。

    curl -i http://localhost
    
  3. 检查模块行为
    检查Nginx的访问日志和错误日志,确认模块的行为是否符合预期。

5.4 性能测试

性能测试是评估模块对Nginx性能影响的重要步骤。

  1. 确定性能指标
    确定你想要测试的性能指标,如响应时间、并发处理能力等。

  2. 使用压力测试工具
    使用压力测试工具,如Apache JMeter或ab(Apache Bench),来模拟高负载情况。

    ab -n 1000 -c 100 http://localhost
    
  3. 分析结果
    分析测试结果,确定模块是否影响了Nginx的性能,并根据需要进行优化。

5.5 使用日志进行调试

日志是调试模块时的重要资源。

  1. 增加日志记录
    在模块中增加日志记录点,记录关键变量和模块的执行流程。

  2. 配置Nginx日志级别
    调整Nginx配置文件中的日志级别,以便捕获更详细的信息。

    error_log /path/to/your/error.log debug;
    
  3. 分析日志文件
    分析日志文件,查找可能的错误或异常行为。

5.6 结语

通过本章的内容,我们学习了如何对Nginx模块进行单元测试、集成测试和性能测试,以及如何使用日志进行调试。这些测试和调试技巧对于确保你的模块稳定、高效地运行至关重要。

第六章:优化和调试Nginx模块

6.1 性能优化的重要性

在我们深入到优化和调试之前,让我们先来谈谈为什么性能优化如此重要。性能优化不仅可以提高用户体验,还能确保我们的模块在高负载情况下依然稳定运行。

6.2 代码审查

优化的第一步是进行彻底的代码审查。

  1. 理解代码逻辑
    重新审视模块的代码,确保你完全理解每一部分的逻辑。

  2. 寻找瓶颈
    识别可能影响性能的瓶颈,比如循环、递归调用或复杂的正则表达式。

  3. 优化算法
    使用更高效的算法或数据结构来替换低效的部分。

6.3 内存管理

内存管理是性能优化的关键。

  1. 避免内存泄漏
    使用工具如Valgrind检查内存泄漏。

  2. 合理分配内存
    根据需要合理分配内存,避免过度分配或过小分配。

  3. 使用内存池
    Nginx提供了内存池机制,可以高效地管理小块内存的分配和释放。

6.4 减少磁盘I/O

磁盘I/O是影响性能的另一个重要因素。

  1. 减少日志记录
    适当减少日志记录的频率和详细程度,尤其是在高流量的情况下。

  2. 使用异步I/O
    如果模块涉及到磁盘操作,考虑使用异步I/O来提高效率。

6.5 并发和多线程

在多用户环境中,优化并发处理能力是非常重要的。

  1. 使用线程池
    利用Nginx的线程池来处理并发请求。

  2. 优化锁机制
    如果模块使用了锁,优化锁的使用,减少锁的粒度和持有时间。

6.6 使用Nginx提供的API

Nginx提供了许多API来帮助开发者优化模块。

  1. 使用Nginx的缓存机制
    如果适用,使用Nginx的缓存来减少对后端服务的请求。

  2. 利用变量和配置指令
    使用Nginx的变量和配置指令来动态调整模块的行为。

6.7 调试技巧

调试是优化过程中不可或缺的一部分。

  1. 使用调试器
    使用gdb或其他调试器逐步执行代码,观察变量的值和程序的流程。

  2. 增加详细的日志记录
    在关键部分增加日志记录,以便更好地了解程序的运行状态。

  3. 使用条件编译
    使用条件编译来在调试版本中包含额外的日志或检查。

6.8 性能测试

在优化后,重新进行性能测试以验证优化的效果。

  1. 使用相同的测试工具和场景
    使用之前相同的测试工具和场景来保证测试结果的可比性。

  2. 比较测试结果
    比较优化前后的测试结果,确保优化带来了性能的提升。

6.9 结语

通过本章的内容,我们学习了如何对Nginx模块进行性能优化和调试。记住,优化是一个持续的过程,需要不断地测试、分析和调整。

第七章:发布和维护Nginx模块

7.1 发布前的准备

在我们把模块分享给全世界之前,需要做一些准备工作,确保模块的发布既专业又用户友好。

  1. 编写文档
    为模块编写详细的文档,包括安装指南、配置选项、使用示例等。

  2. 版本控制
    使用Git等版本控制系统管理代码,并在平台上创建仓库,如GitHub。

  3. 许可证声明
    为模块选择合适的开源许可证,并在文档和源代码中声明。

  4. 准备发布说明
    编写发布说明,列出模块的新特性、改进点以及修复的bug。

7.2 发布模块

发布模块是将你的工作展示给社区的重要步骤。

  1. 选择发布平台
    选择一个合适的平台来发布你的模块,如GitHub、GitLab等。

  2. 推送代码
    将代码推送到远程仓库,并确保所有分支和标签都是最新的。

  3. 创建发布
    在平台上创建一个新的发布,上传编译好的模块文件和文档。

  4. 编写发布日志
    在发布页面上,编写详细的发布日志,说明此次发布的主要内容。

7.3 社区互动

与社区的互动对于模块的成功至关重要。

  1. 响应问题和反馈
    在社区论坛、邮件列表或GitHub Issues中积极回应用户的问题和反馈。

  2. 编写FAQ
    根据用户的常见问题编写FAQ,帮助用户更快地解决问题。

  3. 参与讨论
    加入相关的社区讨论,分享你的模块,获取反馈,并与其他开发者交流。

7.4 维护模块

模块的维护是一个持续的过程,需要开发者不断地投入时间和精力。

  1. 监控问题报告
    定期检查问题报告,及时了解用户遇到的问题。

  2. 定期更新
    根据Nginx的更新和社区的反馈,定期更新模块。

  3. 性能优化
    持续对模块进行性能优化,确保它在各种环境下都能表现良好。

  4. 安全性维护
    关注安全漏洞,及时修复可能的安全问题。

7.5 处理贡献

如果你的模块受到社区的欢迎,可能会有其他开发者提交贡献。

  1. 建立贡献指南
    为希望贡献代码的开发者提供清晰的贡献指南。

  2. 审查贡献
    审查社区提交的代码,确保它们符合模块的质量和功能要求。

  3. 合并代码
    将经过审查的代码合并到主分支。

  4. 感谢贡献者
    在文档和发布说明中感谢贡献者的工作。

7.6 结语

发布和维护Nginx模块是一个既充满挑战也充满机遇的过程。通过与社区的互动和不断的维护,你的模块可以不断成长和完善。

在本章中,我们学习了如何准备、发布和维护Nginx模块。希望这些知识能帮助你的模块在社区中获得成功。

如果你准备开始你的模块开发之旅,或者已经在途中,记得我随时在这里为你提供帮助。不要犹豫,开始你的模块开发吧,让我们一起创造更加精彩的网络世界!🚀🌐

  • 20
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

墨瑾轩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值