upstream访问第三方服务

本文详细介绍了Nginx中的Upstream与Subrequest机制的区别及应用场景,并通过一个具体的Upstream访问第三方服务的例子展示了如何配置和使用Upstream。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

upstream访问第三方服务

 

upstream与subrequest机制

upstream说明

      upstream被定义为访问上游服务器。Nginx义为代理服要功能是透其次才是以TCP取第三器的

subrequest说明

      subrequest将会户请求。因阻塞程序的发过所以HTTP架提供了机制个复求分解负责功能。最初的原始负责构响应给

 

upstream与subrequest区别总结

      upstream从属户请求的subrequest原始的户请求相或多的新只是新的原始求之可以

 

upstream使用场景

      希望把第三乎原封不地返回户时般使upstream可以效地透HTTP

      Nginx的HTTP反向代理模块就是基于upstream⽅式实现的。

 

 

subrequest使用场景

      访问第三只是取某些信息再依据些信息响应这时应该subrequest业务件事取上游响应再根据响应

 

upstream执行流程

create_requestprocess_headerfinalize_request是必须实现其余5——input_filter_initinput_filterreinit_requestabort_requestrewrite_redirect是可

 

参考《深入理解Nginx》

 

upstream相关的结构体

ngx_http_upstream_conf_t

//接上游服器的超时时间毫秒
ngx_msec_t connect_timeout;
//
TCP包到上游服器的超时时间毫秒
ngx_msec_t send_timeout;
//
接收TCP包到上游服器的超时时间毫秒
ngx_msec_t read_timeout;

3时时间是必置的认为0如果不上游服器建TCPconnect_timeout值为0.

 

ngx_http_upstream_resolved_t

typedef struct {

    ngx_str_t                        host;

    in_port_t                        port;

    ngx_uint_t                       no_port; /* unsignedno_port:1 */

 

    ngx_uint_t                       naddrs;

    ngx_resolver_addr_t             *addrs;

 

    struct sockaddr                 *sockaddr;

    socklen_t                        socklen;

 

    ngx_resolver_ctx_t              *ctx;

} ngx_http_upstream_resolved_t;

 

upstream 访问第三方服务例子

//ngx_http_mytest_module.c

 

#include<ngx_config.h>

#include<ngx_core.h>

#include<ngx_http.h>

 

/*上下文结构体*/

typedef struct

{

    ngx_http_status_t status;

    ngx_str_t backendServer;

}ngx_http_mytest_ctx_t;

 

/*uptream相关配置,例如超时等待时间等*/

typedef struct

{

    ngx_http_upstream_conf_t upstream;

}ngx_http_mytest_conf_t;

 

 

/*配置项处理函数*/

static char *

ngx_http_mytest(ngx_conf_t*cf, ngx_command_t *cmd, void *conf);

 

 

/*真正的处理函数*/

/*设置upstream的host、回调函数、启动upstream等*/

static ngx_int_tngx_http_mytest_handler(ngx_http_request_t *r);

 

 

/*创建ngx_http_mytest_conf_t结构体,硬编码参数*/

static void*ngx_http_mytest_create_loc_conf(ngx_conf_t *cf);

 

 

/*设置hide_headers_hash*/

static char*ngx_http_mytest_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child);

 

 

/*解析http头部*/

static ngx_int_t

mytest_upstream_process_header(ngx_http_request_t*r);

 

 

/*处理http响应行*/

static ngx_int_t

mytest_process_status_line(ngx_http_request_t*r);

 

 

/*安全考虑隐藏头部*/

staticngx_str_t  ngx_http_proxy_hide_headers[]=

        {

                ngx_string("Date"),

                ngx_string("Server"),

                ngx_string("X-Pad"),

               ngx_string("X-Accel-Expires"),

               ngx_string("X-Accel-Redirect"),

               ngx_string("X-Accel-Limit-Rate"),

                ngx_string("X-Accel-Buffering"),

               ngx_string("X-Accel-Charset"),

                ngx_null_string

        };

 

 

/*command 结构体数组*/

staticngx_command_t  ngx_http_mytest_commands[]=

        {

 

                {

                        ngx_string("mytest"),

                        NGX_HTTP_MAIN_CONF |NGX_HTTP_SRV_CONF | NGX_HTTP_LOC_CONF | NGX_HTTP_LMT_CONF | NGX_CONF_NOARGS,

                        ngx_http_mytest,

                       NGX_HTTP_LOC_CONF_OFFSET,

                        0,

                        NULL

                },

 

                ngx_null_command

        };

 

 

/*模块上下文*/

staticngx_http_module_t ngx_http_mytest_module_ctx =

        {

                NULL, /* preconfiguration */

                NULL, /* postconfiguration */

 

                NULL, /* create mainconfiguration */

                NULL, /* init mainconfiguration */

 

                NULL, /* create serverconfiguration */

                NULL, /* merge serverconfiguration */

 

                ngx_http_mytest_create_loc_conf,/*create location configuration */

               ngx_http_mytest_merge_loc_conf  /*merge location configuration */

        };

 

 

/*nginx 模块*/

ngx_module_t  ngx_http_mytest_module =

        {

                NGX_MODULE_V1,

               &ngx_http_mytest_module_ctx,           /* module context */

                ngx_http_mytest_commands,              /* module directives */

                NGX_HTTP_MODULE,                       /* module type */

                NULL,                                  /* init master */

                NULL,                                  /* initmodule */

                NULL,                                  /* initprocess */

                NULL,                                  /* init thread*/

                NULL,                                  /* exitthread */

                NULL,                                  /* exitprocess */

                NULL,                                  /* exitmaster */

                NGX_MODULE_V1_PADDING

        };

 

 

static void*

ngx_http_mytest_create_loc_conf(ngx_conf_t*cf)

{

    ngx_http_mytest_conf_t *mycf;

 

    mycf = (ngx_http_mytest_conf_t  *)ngx_pcalloc(cf->pool,sizeof(ngx_http_mytest_conf_t));

    if (mycf == NULL)

    {

        return NULL;

    }

 

    /*对结构体变量中的成员进行硬编码,超时连接相关*/

    mycf->upstream.connect_timeout = 60000;

    mycf->upstream.send_timeout = 60000;

    mycf->upstream.read_timeout = 60000;

    mycf->upstream.store_access = 0600;

 

    /*处理上游服务器包体方式相关,这里以固定buffer转发包体*/

    mycf->upstream.buffering = 0;

    mycf->upstream.bufs.num = 8;

    mycf->upstream.bufs.size = ngx_pagesize;

    mycf->upstream.buffer_size =ngx_pagesize;

    mycf->upstream.busy_buffers_size = 2 *ngx_pagesize;

    mycf->upstream.temp_file_write_size = 2* ngx_pagesize;

    mycf->upstream.max_temp_file_size = 1024* 1024 * 1024;

 

 

    /*upstream模块要求hide_headers必须初始化*/

    mycf->upstream.hide_headers =NGX_CONF_UNSET_PTR;

    mycf->upstream.pass_headers =NGX_CONF_UNSET_PTR;

 

    return mycf;

}

 

 

static char *

ngx_http_mytest_merge_loc_conf(ngx_conf_t*cf, void *parent, void *child)

{

    ngx_http_mytest_conf_t *prev =(ngx_http_mytest_conf_t *)parent;

    ngx_http_mytest_conf_t *conf =(ngx_http_mytest_conf_t *)child;

 

    ngx_hash_init_t hash;

    hash.max_size = 100;

    hash.bucket_size = 1024;

    hash.name = "proxy_headers_hash";

    /*hide header*/

    if (ngx_http_upstream_hide_headers_hash(cf,&conf->upstream,

                                           &prev->upstream, ngx_http_proxy_hide_headers, &hash)

        != NGX_OK)

    {

        return NGX_CONF_ERROR;

    }

    return NGX_CONF_OK;

}

 

/*upstream  create_request 回调,构建到上游服务器的请求*/

static ngx_int_t

mytest_upstream_create_request(ngx_http_request_t*r)

{

    /*模仿baidu搜索请求 /s?wd= */

    static ngx_str_t backendQueryLine =

            ngx_string("GET /s?wd=%VHTTP/1.1\r\nHost: www.baidu.com\r\nConnection: close\r\n\r\n");

 

    /*请求长度 -2表示 %V*/

    ngx_int_t queryLineLen =backendQueryLine.len + r->args.len - 2;

 

    /*内存池申请内存,请求结束时内存被自动释放*/

    ngx_buf_t* b =ngx_create_temp_buf(r->pool, queryLineLen);

    if (b == NULL)

        return NGX_ERROR;

 

    /*last要指向请求的末尾*/

    b->last = b->pos + queryLineLen;

 

    /*作用相当于snprintf*/

    ngx_snprintf(b->pos, queryLineLen,(char*)backendQueryLine.data, &r->args);

 

    /*发送给上游服务器的请求*/

    r->upstream->request_bufs =ngx_alloc_chain_link(r->pool);

    if (r->upstream->request_bufs ==NULL)

        return NGX_ERROR;

 

    /*request_bufs这里只包含1个ngx_buf_t缓冲区*/

    r->upstream->request_bufs->buf =b;

    r->upstream->request_bufs->next =NULL;

 

    r->upstream->request_sent = 0;

    r->upstream->header_sent = 0;

 

    /*header_hash不可以为0*/

    r->header_hash = 1;

    return NGX_OK;

}

 

 

 

static ngx_int_t

mytest_process_status_line(ngx_http_request_t*r)

{

    size_t len;

    ngx_int_t rc;

    ngx_http_upstream_t *u;

 

    /*取出http请求的上下文*/

    ngx_http_mytest_ctx_t* ctx =ngx_http_get_module_ctx(r, ngx_http_mytest_module);

    if (ctx == NULL)

    {

        return NGX_ERROR;

    }

 

    u = r->upstream;

 

    /*解析http响应行*/

    rc = ngx_http_parse_status_line(r,&u->buffer, &ctx->status);

 

    /*NGX_AGAIN表示继续解析*/

    if (rc == NGX_AGAIN)

    {

        return rc;

    }

 

    /*NGX_ERROR没有接收到合法的http响应行*/

    if (rc == NGX_ERROR)

    {

        ngx_log_error(NGX_LOG_ERR, r->connection->log,0,

                      "upstream sent novalid HTTP/1.0 header");

 

        r->http_version =NGX_HTTP_VERSION_9;

        u->state->status = NGX_HTTP_OK;

 

        return NGX_OK;

    }

 

    /*解析到完整到响应行,将解析出来的内容赋值到headers_in结构体中*/

    if (u->state)

    {

        u->state->status =ctx->status.code;

    }

 

    /*赋值操作*/

    u->headers_in.status_n =ctx->status.code;

 

    len = ctx->status.end -ctx->status.start;

 

    u->headers_in.status_line.len = len;

 

    u->headers_in.status_line.data = ngx_pnalloc(r->pool,len);

    if (u->headers_in.status_line.data ==NULL)

    {

        return NGX_ERROR;

    }

   ngx_memcpy(u->headers_in.status_line.data, ctx->status.start,len);

 

 

    /*开始解析http响应头部*/

    /*之后收到的新字符流将有新的回调函数解析*/

    u->process_header =mytest_upstream_process_header;

 

    /*如果本次收到的字符流除了http响应行外,还有多余的字符*/

    /*将由mytest_upstream_process_header方法解析*/

    return mytest_upstream_process_header(r);

}

 

 

/*处理http头部*/

static ngx_int_t

mytest_upstream_process_header(ngx_http_request_t*r)

{

    ngx_int_t rc;

 

    /*为http头部量身定制*/

    /*例如 key 存储"Content-Length"*/

    /*value 存储 "1024" */

    ngx_table_elt_t *h;

 

    ngx_http_upstream_header_t     *hh;

 

    ngx_http_upstream_main_conf_t  *umcf;

 

    /*将upstream模块配置项ngx_http_upstream_main_conf_t取了*/

    /*该结构体中存储了需要做统一处理的http头部名称和回调方法*/

    umcf = ngx_http_get_module_main_conf(r,ngx_http_upstream_module);

 

    /*循环的解析所有的http头部*/

    for ( ;; )

    {

        /*解析http头部*/

        rc = ngx_http_parse_header_line(r,&r->upstream->buffer, 1);

 

        /*NGX_OK解析出一行http头部*/

        if (rc == NGX_OK)

        {

            /*向headers_in.headers这个ngx_list_t链表中添加http头部*/

            /*ngx_list_t是先插入再赋值*/

            h =ngx_list_push(&r->upstream->headers_in.headers);

            if (h == NULL)

            {

                return NGX_ERROR;

            }

 

            /*构造刚刚添加到headers链表中的http头部*/

            h->hash = r->header_hash;

 

            /*key-头部名称 value-对应的值*/

            h->key.len =r->header_name_end - r->header_name_start;

            h->value.len = r->header_end- r->header_start;

 

 

            /*zx:作者这里分配这么大空间的原因在于,在结构体中有三个变量*/

            //1.key

            //2.value

            //3.lowcase_case

            /*一次申请这一整段空间,+1用来'\0'来区分*/

            /*这也在于ngx_str_t的特性在于data字段记录的只是字串地址*/

            h->key.data =ngx_pnalloc(r->pool,

                                     h->key.len + 1 + h->value.len + 1 + h->key.len);

            if (h->key.data == NULL)

            {

                return NGX_ERROR;

            }

 

            /*赋值操作*/

            h->value.data = h->key.data +h->key.len + 1;

            h->lowcase_key = h->key.data+ h->key.len + 1 + h->value.len + 1;

 

            ngx_memcpy(h->key.data,r->header_name_start, h->key.len);

            h->key.data[h->key.len] ='\0';

            ngx_memcpy(h->value.data,r->header_start, h->value.len);

            h->value.data[h->value.len] ='\0';

 

            if (h->key.len ==r->lowcase_index)

            {

                ngx_memcpy(h->lowcase_key,r->lowcase_header, h->key.len);

            }

            else

            {

                ngx_strlow(h->lowcase_key,h->key.data, h->key.len);

            }

 

            /*upstream模块会对一些http头部做特殊处理*/

            hh =ngx_hash_find(&umcf->headers_in_hash, h->hash,

                              h->lowcase_key, h->key.len);

 

            if (hh && hh->handler(r,h, hh->offset) != NGX_OK)

            {

                return NGX_ERROR;

            }

 

            continue;

        }

 

        /*返回NGX_HTTP_PARSE_HEADER_DONE表示响应中所有的http头部都解析*/

        if (rc == NGX_HTTP_PARSE_HEADER_DONE)

        {

            /*如果之前解析http头部时没有发现server和date头部*/

            /*据http协议添加这两个头部*/

            if(r->upstream->headers_in.server == NULL)

            {

                h = ngx_list_push(&r->upstream->headers_in.headers);

                if (h == NULL)

                {

                    return NGX_ERROR;

                }

 

                /*zx:查看ngx_hash_key_lc/ngx_hash_key源码可知,可以改写*/

                ngx_str_t str=ngx_string("server");

               h->hash=ngx_hash_key_lc(str.data,str.len);

#if 0

                h->hash =ngx_hash(ngx_hash(ngx_hash(ngx_hash(

                                                     ngx_hash('s', 'e'), 'r'), 'v'), 'e'), 'r');

#endif

 

                ngx_str_set(&h->key,"Server");

                ngx_str_null(&h->value);

                h->lowcase_key = (u_char *)"server";

            }

 

            if(r->upstream->headers_in.date == NULL)

            {

                h = ngx_list_push(&r->upstream->headers_in.headers);

                if (h == NULL)

                {

                    return NGX_ERROR;

                }

 

                ngx_str_tstr=ngx_string("date");

                /*zx:同上*/

               h->hash=ngx_hash_key_lc(str.data,str.len);

#if 0

                h->hash =ngx_hash(ngx_hash(ngx_hash('d', 'a'), 't'), 'e');

#endif

 

                ngx_str_set(&h->key,"Date");

                ngx_str_null(&h->value);

                h->lowcase_key = (u_char *)"date";

            }

 

            return NGX_OK;

        }

 

        /*没有解析到完整的http头部,继续接收新的字符流*/

        if (rc == NGX_AGAIN)

        {

            return NGX_AGAIN;

        }

 

        /*其他返回值都是非法的*/

        ngx_log_error(NGX_LOG_ERR,r->connection->log, 0,

                      "upstream sent invalid header");

        returnNGX_HTTP_UPSTREAM_INVALID_HEADER;

    }

}

 

/*finalize_request回调 请求结束前会调用*/

static void

mytest_upstream_finalize_request(ngx_http_request_t*r, ngx_int_t rc)

{

    ngx_log_error(NGX_LOG_DEBUG,r->connection->log, 0,

                 "mytest_upstream_finalize_request");

}

 

 

static char *

ngx_http_mytest(ngx_conf_t*cf, ngx_command_t *cmd, void *conf)

{

    ngx_http_core_loc_conf_t  *clcf;

 

    /*首先找到mytest配置项所属的配置块,clcf貌似是location块内的数据*/

    /*结构,其实不然,它可以是main、srv或者loc级别配置项,也就是说在每个*/

    /*http{}和server{}内也都有一个ngx_http_core_loc_conf_t结构体*/

    clcf =ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);

 

    /*http框架在处理用户请求进行到NGX_HTTP_CONTENT_PHASE阶段时,如果*/

    /*请求的主机域名、URI与mytest配置项所在的配置块相匹配,就将调用我们*/

    /*实现的ngx_http_mytest_handler方法处理这个请求*/

    clcf->handler = ngx_http_mytest_handler;

 

    return NGX_CONF_OK;

}

 

static ngx_int_t

ngx_http_mytest_handler(ngx_http_request_t*r)

{

    /*首先建立http上下文结构体ngx_http_mytest_ctx_t*/

    ngx_http_mytest_ctx_t* myctx =ngx_http_get_module_ctx(r, ngx_http_mytest_module);

    if (myctx == NULL)

    {

        myctx = ngx_palloc(r->pool,sizeof(ngx_http_mytest_ctx_t));

        if (myctx == NULL)

        {

            return NGX_ERROR;

        }

        /*将新建的上下文与请求关联起来*/

        ngx_http_set_ctx(r, myctx,ngx_http_mytest_module);

    }

 

    /*对每1个要使用upstream的请求,必须调用且只能调用1次*/

    /*ngx_http_upstream_create方法,它会初始化r->upstream成员*/

    if (ngx_http_upstream_create(r) != NGX_OK)

    {

        ngx_log_error(NGX_LOG_ERR,r->connection->log, 0, "ngx_http_upstream_create() failed");

        return NGX_ERROR;

    }

 

    /*得到配置upstream结构体ngx_http_mytest_conf_t*/

    ngx_http_mytest_conf_t  *mycf = (ngx_http_mytest_conf_t*)ngx_http_get_module_loc_conf(r, ngx_http_mytest_module);

 

    /*配置upstream*/

    ngx_http_upstream_t *u = r->upstream;

 

    /*这里用配置文件中的结构体来赋给r->upstream->conf成员*/

    /*ngx_http_upstream_conf_t结构*/

    u->conf = &mycf->upstream;

 

    /*决定转发包体时使用的缓冲区*/

    u->buffering =mycf->upstream.buffering;

 

    /*以下代码开始初始化resolved结构体,用来保存上游服务器的地址*/

 

    u->resolved =(ngx_http_upstream_resolved_t*) ngx_pcalloc(r->pool,sizeof(ngx_http_upstream_resolved_t));

    if (u->resolved == NULL)

    {

        ngx_log_error(NGX_LOG_ERR,r->connection->log, 0,

                      "ngx_pcallocresolved error. %s.", strerror(errno));

        return NGX_ERROR;

    }

 

    /*这里的上游服务器就是www.baidu.com*/

    static struct sockaddr_in backendSockAddr;

    struct hostent *pHost =gethostbyname((char*) "www.baidu.com");

    if(pHost == NULL)

    {

        ngx_log_error(NGX_LOG_ERR,r->connection->log, 0,

                      "gethostbyname fail.%s", strerror(errno));

 

        return NGX_ERROR;

    }

 

    /*访问上游服务器的80端口*/

    backendSockAddr.sin_family = AF_INET;

    backendSockAddr.sin_port =htons((in_port_t) 80);

 

   memcpy(&backendSockAddr.sin_addr,pHost->h_addr_list[0],sizeof(structin_addr));

    char* pDmsIP = inet_ntoa(*(struct in_addr*)(pHost->h_addr_list[0]));

    /*inet_addr*/

    //backendSockAddr.sin_addr.s_addr =inet_addr(pDmsIP);

    myctx->backendServer.data =(u_char*)pDmsIP;

    myctx->backendServer.len =strlen(pDmsIP);

 

    /*将地址设置到resolved成员中*/

    u->resolved->sockaddr = (structsockaddr *)&backendSockAddr;

    u->resolved->socklen = sizeof(struct sockaddr_in);

    u->resolved->naddrs = 1;

 

    /*设置三个必须实现的回调方法:创建请求、处理头部、请求销毁*/

    u->create_request =mytest_upstream_create_request;

    u->process_header =mytest_process_status_line;

    u->finalize_request =mytest_upstream_finalize_request;

 

    /*这里必须将count成员加1*/

    r->main->count++;

    /*启动upstream*/

    ngx_http_upstream_init(r);

    /*必须返回NGX_DONE*/

    return NGX_DONE;

}


 

参考资料

《深入理解Nginx:模块开发与架构分析-陶辉》


### 如何验证 Apache、iptables、第三方服务以及高性能工具的配置正确性 #### 验证 Apache 的配置正确性 可以通过以下方法来验证 Apache 是否已成功隐藏其版本信息。首先,在浏览器中访问目标服务器并查看响应头中的 `Server` 字段是否仅显示为 `Apache` 而不包含具体版本号[^2]。其次,可以利用命令行工具如 `curl` 或者专用 HTTP 客户端发送请求到服务器,并解析返回的结果。 ```bash curl -I http://yourserver.com/ ``` 如果一切正常,则应该看不到详细的 Apache 版本编号或其他敏感数据。 对于更全面的功能测试,还可以尝试重启 Apache 并观察是否有错误日志输出: ```bash sudo systemctl restart apache2 && sudo journalctl -u apache2.service --since today | grep error ``` 上述操作可以帮助确认是否存在潜在问题或者语法上的失误。 --- #### 验证 iptables 的配置有效性 为了确保防火墙规则被正确定义并且生效,可执行如下几项检测措施: 1. **检查当前活动规则列表**: 使用下面这条指令展示现有的链表及其关联策略。 ```bash sudo iptables -L -v -n ``` 这里会显示出所有的入站(outbound), 出站(inbound) 及转发(forwarding) 流量处理方式。特别注意那些针对Tomcat默认端口(即8080)所设定的具体条件[^3]。 2. **模拟外部攻击行为**: 借助专门设计用来评估网络安全性的应用程序比如 nmap 执行扫描任务,以此判断哪些端口处于开放状态以及它们各自对应的防护等级如何. ```bash nmap -p 8080 your_server_ip_address ``` 通过以上手段能够直观了解到实际网络环境下的防御效果是否达到预期标准. --- #### 第三方服务 (以 Nginx 上游负载均衡为例) 当涉及到像 Nginx 提供的上游模块这样的高级特性时,除了基本功能外还需要额外关注几个方面来保障整体架构稳定可靠运行: - **健康状况监控:** 设置适当的超时参数(`max_fails`, `fail_timeout`)有助于及时发现不可用节点并将流量重新分配至其他可用实例上[^1]. ```nginx upstream backend { server backend1.example.com max_fails=3 fail_timeout=30s; server backend2.example.com max_fails=3 fail_timeout=30s; server backend3.example.com max_fails=3 fail_timeout=30s; } ``` - **压力测试分析:** 利用 ab(Apache Benchmark Tool) 对前端入口发起大量并发请求,记录下平均响应时间、吞吐率等相关指标变化趋势图以便后续优化调整方向参考. ```bash ab -c 100 -n 1000 http://api.example.com/test_endpoint ``` 这些实践均能有效提升系统的鲁棒性和用户体验满意度水平. --- #### 性能调优后的验证流程 最后一步便是衡量经过一系列改进之后的整体表现情况到底改善了多少个百分点?这通常涉及以下几个维度考量因素: - CPU利用率下降幅度百分比; - 内存占用峰值减少数量级差异程度; - 页面加载速度加快比例统计数值等等... 借助专业的性能剖析平台 New Relic APM 或 Datadog 实现全程跟踪监测过程自动化管理目的达成最终闭环控制体系构建完成整个生命周期管理工作流运转顺畅无阻塞现象发生. ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值