SpringCloud微服务(简略笔记一)

目录

介绍

​编辑

认识微服务

单体架构

分布式架构

微服务分析:

分布式架构(SpringCloud微服务)

服务拆分与服务远程调用:

提供者与消费者

微服务治理

Eureka注册中心

操作步骤

 Ribbon负载均衡

负载均衡流程

自定义负载均衡策略

饥饿加载

总结

nacos

启动

注册中心前的准备

服务跨集群调用问题

NacosRole负载均衡

Nacos——服务实例的权重设置

Nacos环境隔离

Nacos与eureka的共同点

nacos配置管理

Nacos配置管理-配置热更新

Nacos配置管理

Fegin远程调用

自定义Feign配置

Http客户端Feign

抽取FeignClient

统一网关Gateway

搭建网关步骤步骤:

 路由断言工厂Route Predicate Factory

路由过滤器GatewayFilter

全局过滤器GlobalFilter

过滤器执行顺序

跨域问题处理

Docker

项目部署的问题


介绍

微服务是一种软件架构风格,它是以专注于单一职责的很多小型项目为基础,组合出复杂的大型应用。

认识微服务

单体架构

单体架构:讲业务的所有功能集中在一个项目中开发,打成一个包部署

分布式架构

分布式架构:根据业务功能对系统进行拆分,每个业务模块作为独立项目开发,称为一个服务。

微服务分析:

单体架构:我们一开始接触的项目,是MVC架构,分模块开发,一个项目的数据放到一个数据库中,一次打包,启动项目。

分布式架构:一个业务抽离成一个业务,此业务的数据抽离成一个数据库,也就是说,多个业务,多个数据库,多个服务。

分布式架构(SpringCloud微服务)

服务拆分与服务远程调用:

举个例子:

我们有两个表,订单表和用户表,两个表要做关联查询,订单表有userId,要在订单表中展示出User的数据。

@Data
public class Order {
    private Long id;
    private Long price;
    private String name;
    private Integer num;
    private Long userId;
    private User user;
}

  由于用户和订单抽离成了两个服务,也就是有两个数据库的数据,

1.有了userId,我直接id查询是行不通的,因为order数据库没有user数据。

2.既然user用户表成了一个服务,那么可以用类似浏览器前端发送ajax的形式,获取服务数据。

package cn.itcast.order;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
public class OrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

}
package cn.itcast.order.service;

import cn.itcast.order.mapper.OrderMapper;
import cn.itcast.order.pojo.Order;
import cn.itcast.order.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private RestTemplate restTemplate;

    public Order queryOrderById(Long orderId) {
        // 1.查询订单
        Order order = orderMapper.findById(orderId);
        // 2. 利用RestTemplate 发起http请求,查询用户
        // 2.1 url路径
        String url = "http://localhost:8081/user/" + order.getUserId();
        // 2.2 发起http请求,实现远程调用
        User user = restTemplate.getForObject(url, User.class);

        // 3. 封装user到Order
        order.setUser(user);
        // 4.返回
        return order;
    }
}

提供者与消费者

* 服务提供者:一次业务中,被其它微服务调用的服务。(提供接口给其它微服务)

* 服务消费者:一次业务中,调用其它微服务的服务。(调用其它微服务提供的接口)

一个服务既可以是提供者又可以是消费者。

微服务治理

Eureka注册中心

eureka的作用

* 消费者该如何获取服务提供者具体信息?

    * 服务提供者启动时向eureka注册自己的信息

    * 消费者根据服务名称向eureka拉去提供者信息

    *eureka保存这些信息

* 如果有多个服务提供者,消费者该如何选择?

    * 服务消费者利用负载均衡算法,从服务列表中挑选一个

* 消费者如何感知服务提供者建康状态?

    * 服务提供者会每隔30秒向EurekaServer发送心跳请求,报告健康状态

    *eureka会更新记录服务列表信息,心跳不正常会被剔除

    * 消费者就可以拉渠道最新的信息

操作步骤

1. 搭建注册中心

搭建EurekaServer

步骤一:依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>

步骤二:启动类注解

@EnableEurekaServer
@SpringBootApplication
public class EurekaApplication {
    public static void main(String[] args) {
        SpringApplication.run(EurekaApplication.class, args);
    }
}

步骤三:yml配置

server:
  port: 10086
spring:
  application:
    name: eureka-server   # eureka的服务名称
eureka:
  client:
    service-url:  # eureka的地址信息
      defaultZone: http://127.0.0.1:10086/eureka

2. 服务注册

      <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
server:
  port: 8080
spring:
  application:
    name: orderservice
  datasource:
    url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver
mybatis:
  type-aliases-package: cn.itcast.user.pojo
  configuration:
    map-underscore-to-camel-case: true
logging:
  level:
    cn.itcast: debug
  pattern:
    dateformat: MM-dd HH:mm:ss:SSS
eureka:
  client:
    service-url:  # eureka的地址信息
      defaultZone: http://127.0.0.1:10086/eureka

3. 服务发现

        String url = "http://userservice/user/" + order.getUserId();
        // 2.2 发起http请求,实现远程调用
        User user = restTemplate.getForObject(url, User.class);

负载均衡

    @Bean
    @LoadBalanced
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

 Ribbon负载均衡

负载均衡流程

自定义负载均衡策略

userservice: # 给某个微服务配置负载均衡规则,这里是userservice服务
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule # 负载均衡规则 

饥饿加载

Ribbon默认是采用懒加载,即第一次访问时才回去创建LoadBalanceClient,请求时间会很长。

而饥饿加载则会在项目启动时创建,降低第一次访问的耗时,通过下面配置开启饥饿加载:

ribbon:
  eager-load:
    enabled: true
    clients: userservice

总结

1. Ribbon负载均衡规则

         * 规则接口是IRule

         * 默认实现是ZoneAvoidanceRule,根据zone

           选择服务列表,然后轮询

2.   负载均衡自定义方式

      * 代码方式:配置灵活,但修改时需要重新打包

         发布

       * 配置方式:直观,方便,无需重新打包发布,

          但是无法做全局配置

3. 饥饿加载

       * 开启饥饿加载

       * 指定饥饿加载的微服务名称。

nacos

GitHub的Release下载页:https://github.com/alibaba/nacos/releases

启动

windos下,进入加压后的bin目录,命令:startup.cmd -m standalone

网址:http://127.0.0.1:8848/nacos

账号密码都是:nacos

注册中心前的准备

父工程依赖

<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-alibaba-dependencies</artifactId>
    <version>2.2.5.RELEASE</version>
    <type>pom</type>
    <scope>import</scope>
</dependency>

客户端依赖

<!-- nacos客户端依赖包 -->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

 yml配置

server:
  port: 8080
spring:
  application:
    name: orderservice
  datasource:
    url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
mybatis:
  type-aliases-package: cn.itcast.user.pojo
  configuration:
    map-underscore-to-camel-case: true
logging:
  level:
    cn.itcast: debug
  pattern:
    dateformat: MM-dd HH:mm:ss:SSS
#eureka:
#  client:
#    service-url:  # eureka的地址信息
#      defaultZone: http://127.0.0.1:10086/eureka

服务跨集群调用问题

服务调用尽可能选择本地集群的服务,跨集群调用延迟较高,本地集群不可访问时,再去访问其它集群。

yml配置

server:
  port: 8081
spring:
  application:
    name: userservice
  datasource:
    url: jdbc:mysql://localhost:3306/cloud_user?useSSL=false
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        cluster-name: SH # 集群名称  代替杭州

mybatis:
  type-aliases-package: cn.itcast.user.pojo
  configuration:
    map-underscore-to-camel-case: true
logging:
  level:
    cn.itcast: debug
  pattern:
    dateformat: MM-dd HH:mm:ss:SSS
#eureka:
#  client:
#    service-url:  # eureka的地址信息
#      defaultZone: http://127.0.0.1:10086/eureka

NacosRole负载均衡

server:
  port: 8080
spring:
  application:
    name: orderservice
  datasource:
    url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        cluster-name: HZ # 集群名称  代替杭州
userservice:
  ribbon:
    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
mybatis:
  type-aliases-package: cn.itcast.user.pojo
  configuration:
    map-underscore-to-camel-case: true
logging:
  level:
    cn.itcast: debug
  pattern:
    dateformat: MM-dd HH:mm:ss:SSS
#eureka:
#  client:
#    service-url:  # eureka的地址信息
#      defaultZone: http://127.0.0.1:10086/eureka

Nacos——服务实例的权重设置

实例的权重控制

1. Nacos控制台可以设置实例的权重值,0~1之间

2. 同集群内的多个实例,权重越高被访问的频率越高

3. 权重设置为0则完全不会被访问

Nacos环境隔离

1. namespace用来做环境隔离

2. 每个namespace都有唯一id

3. 不同namespace下的业务不可见

server:
  port: 8080
spring:
  application:
    name: orderservice
  datasource:
    url: jdbc:mysql://localhost:3306/cloud_order?useSSL=false
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
        cluster-name: HZ # 集群名称  代替杭州
        namespace: eebd708a-506e-47e1-97b0-05e491a1982a # dev环境
userservice:
  ribbon:
    NFLoadBalancerRuleClassName: com.alibaba.cloud.nacos.ribbon.NacosRule # 负载均衡规则
mybatis:
  type-aliases-package: cn.itcast.user.pojo
  configuration:
    map-underscore-to-camel-case: true
logging:
  level:
    cn.itcast: debug
  pattern:
    dateformat: MM-dd HH:mm:ss:SSS
#eureka:
#  client:
#    service-url:  # eureka的地址信息
#      defaultZone: http://127.0.0.1:10086/eureka

Nacos与eureka的共同点

共同点

1. 都支持服务注册和服务拉取

2. 都支持服务提供者心跳方式做健康测试

区别

1. nacos支持服务端主动检测提供者状态:临时实例采用心跳模式,非临时实例采用主动检测模式

2. 临时实例心跳不正常会被剔除,非临时实例则不会被剔除

3. nacos支持服务列表变更的消息推送模式,服务列表更新更及时

4. nacos集群采用AP方式,当集群中存在菲临时实例时,采用CP模式,Eureka采用AP方式

nacos配置管理

配置的步骤

nacos的配置管理依赖

<!--nacos配置管理依赖-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>

然后,在user-service中添加一个bootstrap.yaml文件,内容如下:

spring:
  application:
    name: userservice # 服务名称
  profiles:
    active: dev #开发环境,这里是dev 
  cloud:
    nacos:
      server-addr: localhost:8848 # Nacos地址
      config:
        file-extension: yaml # 文件后缀名

 测试一下

   @Value("${pattern.dateformat}")
    private String dateformat;

    @GetMapping("/now")
    public String now(){
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
    }

Nacos配置管理-配置热更新

在用的类上添加热更新注解


@Slf4j
@RestController
@RequestMapping("/user")
@RefreshScope
public class UserController {

    @Autowired
    private UserService userService;

   @Value("${pattern.dateformat}")
    private String dateformat;

    @GetMapping("/now")
    public String now(){
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern(dateformat));
    }

第二种方式:使用@ConfigurationPoperties注解

package cn.itcast.user.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * @author Mtz
 * @version 1.0
 * @2023/10/619:21
 * @function
 * @comment
 */
@Data
@Component
@ConfigurationProperties(prefix = "pattern")
public class PatternProperties {
    private String dateformat;
}
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

//   @Value("${pattern.dateformat}")
//    private String dateformat;

    @Autowired
    private PatternProperties patternProperties;

    @GetMapping("/now")
    public String now() {
        return LocalDateTime.now().format(DateTimeFormatter.ofPattern(patternProperties.getDateformat()));
    }

Nacos配置管理

多环境配置共享

微服务启动时会从nacos读取多个配置文件:

多种配置的优先级:

* 服务名-profile.yaml > 服务名称.yaml > 本地配置

Fegin远程调用

在消费者服务引入依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

启动类添加注解

package cn.itcast.order.service;

import cn.itcast.order.client.UserClient;
import cn.itcast.order.mapper.OrderMapper;
import cn.itcast.order.pojo.Order;
import cn.itcast.order.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private UserClient userClient;

    public Order queryOrderById(Long orderId) {
        // 1.查询订单
        Order order = orderMapper.findById(orderId);

        // 2.
        User user = userClient.findById(order.getUserId());

        order.setUser(user);


        // 4.返回
        return order;
    }
}

自定义Feign配置

基于配置文件修改feign的日志级别可以针对单个服务:

feign:  
  client:
    config: 
      userservice: # 针对某个微服务的配置
        loggerLevel: FULL #  日志级别 

也可以针对所有服务:

feign:  
  client:
    config: 
      default: # 这里用default就是全局配置,如果是写服务名称,则是针对某个微服务的配置
        loggerLevel: FULL #  日志级别 

而日志的级别分为四种:

  • NONE:不记录任何日志信息,这是默认值。

  • BASIC:仅记录请求的方法,URL以及响应状态码和执行时间

  • HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息

  • FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。

Http客户端Feign

Feign的性能优化

Feign底层的客户端实现:

* URLConnection:默认实现,不支持连接池

* Apache HttpClient:支持连接池

* OKHTTP :支持连接池

在消费者引入依赖

<!--httpClient的依赖 -->
<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-httpclient</artifactId>
</dependency>

配置连接池

feign:
  client:
    config:
      default: # default全局的配置
        loggerLevel: BASIC # 日志级别,BASIC就是基本的请求和响应信息
  httpclient:
    enabled: true # 开启feign对HttpClient的支持
    max-connections: 200 # 最大的连接数
    max-connections-per-route: 50 # 每个路径的最大连接数

抽取FeignClient

步骤:

1. 创建一个module,命名为feign-api,引入feign的srarter依赖

2.将order-servivce中编写的UserClient , User ,DefaultFeignConfiguration都复制到feign-api项目中

3. 将order-service中引入feign-api的依赖

4.修改order-service的所有与上述三个组件有关的import部分,改成导入feign-api中的包

5. 重启测试

依赖


        <!--引入HttpClient依赖-->
        <dependency>
            <groupId>io.github.openfeign</groupId>
            <artifactId>feign-httpclient</artifactId>
        </dependency>

由于在不同的包下,扫描时注意UserClient的位置

@MapperScan("cn.itcast.order.mapper")
@SpringBootApplication
@EnableFeignClients(clients = UserClient.class)
public class OrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(OrderApplication.class, args);
    }

}

统一网关Gateway

为什么需要网关?

权限控制:网关作为微服务入口,需要校验用户是是否有请求资格,如果没有则进行拦截。

路由和负载均衡:一切请求都必须先经过gateway,但网关不处理业务,而是根据某种规则,把请求转发到某个微服务,这个过程叫做路由。当然路由的目标服务有多个时,还需要做负载均衡。

限流:当请求流量过高时,在网关中按照下流的微服务能够接受的速度来放行请求,避免服务压力过大。

搭建网关步骤步骤:

模块服务所需的依赖


        <!--网关-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!--nacos服务发现依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

yml配置

server:
  port: 10010 # 网关端口
spring:
  application:
    name: gateway # 服务名称
  cloud:
    nacos:
      server-addr: localhost:8848 # nacos地址
    gateway:
      routes: # 网关路由配置
        - id: user-service # 路由id,自定义,只要唯一即可
          # uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
          uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称
          predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
            - Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求

路由配置包括:

1.路由id:路由的唯一标识

2. 路由目标(uri):路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡

3.路由断言(predicates):判断路由的规则。

 路由断言工厂Route Predicate Factory

* 我们在配置文件中写的断言规则只是字符串,这些字符串会被PrdicateFactory读取并处理,转变为路由判断的条件

网址参考:Spring Cloud Gateway

路由过滤器GatewayFilter

SpringCloud网址参考:Spring Cloud Gateway

GatewayFilter是网关中提供的一种过滤器,可以对进入网关的请求和微服务返回的响应做处理:

yml配置

server:
  port: 10010 # 网关端口
spring:
  application:
    name: gateway # 服务名称
  cloud:
    nacos:
      server-addr: localhost:8848 # nacos地址
    gateway:
      routes: # 网关路由配置
        - id: user-service # 路由id,自定义,只要唯一即可
          # uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
          uri: lb://userservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称
          predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
            - Path=/user/** # 这个是按照路径匹配,只要以/user/开头就符合要求
          filters: # 过滤器
            - AddRequestHeader=Truth, Itcast is freaking awesome! # 添加请求头
        - id: order-service # 路由id,自定义,只要唯一即可
          # uri: http://127.0.0.1:8081 # 路由的目标地址 http就是固定地址
          uri: lb://orderservice # 路由的目标地址 lb就是负载均衡,后面跟服务名称
          predicates: # 路由断言,也就是判断请求是否符合路由规则的条件
            - Path=/order/** # 这个是按照路径匹配,只要以/user/开头就符合要求
            - After=2025-01-20T17:42:47.789-07:00[America/Denver]

默认配置

spring:
  cloud:
    gateway:
      routes:
      - id: user-service 
        uri: lb://userservice 
        predicates: 
        - Path=/user/**
      default-filters: # 默认过滤项
      - AddRequestHeader=Truth, Itcast is freaking awesome! 

全局过滤器GlobalFilter

GlobalFilter的逻辑需要自己写代码实现,定义方式是接口GlobalFilter接口。

package cn.itcast.gateway;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Order(-1)
@Component
public class AuthorizeFilter implements GlobalFilter {
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1.获取请求参数
        MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();
        // 2.获取authorization参数
        String auth = params.getFirst("authorization");
        // 3.校验
        if ("admin".equals(auth)) {
            // 放行
            return chain.filter(exchange);
        }
        // 4.拦截
        // 4.1.禁止访问,设置状态码
        exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
        // 4.2.结束处理
        return exchange.getResponse().setComplete();
    }
}

过滤器执行顺序

请求进入网关会碰到三类过滤器:当前路由的过滤器,DefaultFilter,GlobalFIlter

请求路由后,会将当前路由过滤器和DefaultFilter,GlobalFilter,合并到一个过滤器链(集合)中,排序后依次执行每个过滤器。

跨域问题处理

spring:
  cloud:
    gateway:
      # 。。。
      globalcors: # 全局的跨域处理
        add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
        corsConfigurations:
          '[/**]':
            allowedOrigins: # 允许哪些网站的跨域请求 
              - "http://localhost:8090"
            allowedMethods: # 允许的跨域ajax的请求方式
              - "GET"
              - "POST"
              - "DELETE"
              - "PUT"
              - "OPTIONS"
            allowedHeaders: "*" # 允许在请求中携带的头信息
            allowCredentials: true # 是否允许携带cookie
            maxAge: 360000 # 这次跨域检测的有效期

Docker

项目部署的问题

大型项目组件较多,运行环境较为复杂,部署时会碰到一些问题:

*  依赖关系复杂,容易出现兼容问题

*  开发,测试,生产环境有差异

Docker如何解决依赖的兼容问题?

* 就应用的libs(函数库),Deps(依赖),配置与应用一起打包。

* 将每个应用放到一个隔离容器去运行,避免互相打扰

Docke如何解决不同系统环境的问题?

* Docker将用户程序与所需调用的系统(比如Ubuntu)函数库一起打包

*Docker运行到不同操作系统时,借助于操作系统的linux内核来运行

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值