微服务架构搭建

1.创建一个父类工程并引入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <modules>
        <module>common</module>
        <module>product</module>
        <module>order</module>
    </modules>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.12.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.aaa</groupId>
    <artifactId>springcloud</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <!-- 只要是父工程打包方式都是pom. 把src删除 -->
    <packaging>pom</packaging>
    <name>springcloud</name>
    <description>springcloud</description>
    <!-- 定义版本号 -->
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <!-- springcloud的版本 -->
        <spring-cloud.version>Hoxton.SR12</spring-cloud.version>
        <!-- 阿里巴巴的版本  springboot springcloud  springcloudalibaba 他们的版本必须对应
                https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E
                 -->
        <spring-cloud-alibaba.version>2.2.8.RELEASE</spring-cloud-alibaba.version>
    </properties>
    <!--
        dependencyManagement: 只负责jar的管理不负责jar的下载。
         -->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring-cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba.cloud</groupId>
                <artifactId>spring-cloud-alibaba-dependencies</artifactId>
                <version>${spring-cloud-alibaba.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Spring Cloud和Spring Boot的依赖关系:https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E

2.创建一个公共模块

引入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud</artifactId>
        <groupId>com.aaa</groupId>
        <version>0.0.1-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>common</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.2</version>
        </dependency>
    </dependencies>
</project>

创建公共实体类

product商品

@TableName(value ="tbl_product")
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Product implements Serializable {

    @TableId(type = IdType.AUTO)
    private Integer pid;

    private String pname;

    private BigDecimal price;

    private Integer stock;

    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
}

order订单

@TableName(value ="tbl_order")
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Order implements Serializable {
    /**
     * 订单id
     */
    @TableId(type = IdType.AUTO)
    private Integer id;
    /**
     * 订单号
     */
    private String orderNo;
    /**
     * 购买的商品id
     */
    private Integer productId;
    /**
     * 购买时的商品价格
     */
    private BigDecimal price;
    /**
     * 购买时的商品名
     */
    private String pname;
    /**
     * 购买的数量
     */
    private Integer num;
    /**
     * 购买者的id
     */
    private Integer userid;
    /**
     * 状态(0未支付,1已支付 2已发货 3确认收货 4.取消)
     */
    private Integer status;
    /**
     * 下单时间
     */
    //订单创建时间--Date存在线程安全问题--默认按照驼峰命名
    private LocalDateTime createTime;

    @TableField(exist = false)
    private static final long serialVersionUID = 1L;
}

3.创建商品微服务

引入依赖

<dependencies>
        <!--公共依赖-->
        <dependency>
            <groupId>com.aaa</groupId>
            <artifactId>common</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <!--数据库的依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--springboot-web依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

application.properties文件创建

server.port=8001

spring.application.name=aaa-product

spring.datasource.username=
spring.datasource.password=
spring.datasource.url=jdbc:mysql://localhost:3306/tbl_product?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

mybatis-plus.mapper-locations=classpath*:mapper/*.xml

编写启动类

@SpringBootApplication
@MapperScan("com.aaa.mapper")
public class ProductApp {
    public static void main(String[] args) {
        SpringApplication.run(ProductApp.class,args);
    }
}

mapper层

public interface ProductMapper extends BaseMapper<Product> {
}

service层

public interface ProductService {
    public Product getById(Integer pid);
}
@Service
public class ProductServiceImpl implements ProductService {
    @Autowired
    private ProductMapper productMapper;
    @Override
    public Product getById(Integer pid) {
        Product product = productMapper.selectById(pid);
        return product;
    }
}

controller层

@RestController
@RequestMapping("/product")
public class ProductController {
    @Autowired
    private ProductService productService;
    @GetMapping("/getById/{pid}")
    public Product getById(@PathVariable Integer pid){
        Product product = productService.getById(pid);
        return product;
    }
}

4.创建订单微服务

引入依赖

<dependencies>
        <!--公共依赖-->
        <dependency>
            <groupId>com.aaa</groupId>
            <artifactId>common</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <!--数据库的依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--springboot-web依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-core</artifactId>
            <version>5.8.16</version> <!-- 检查最新版本 -->
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

application.properties文件、启动文件、mapper层略

service层

public interface OrderService {
    public Order createOrder(Order order);
}
@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    private OrderMapper orderMapper;
    @Override
    public Order createOrder(Order order) {
        orderMapper.insert(order);
        return order;
    }
}

controller层

@RestController
@RequestMapping("/order")
public class OrderController {

    @Autowired
    private OrderService orderService;

    @Autowired
    private RestTemplate restTemplate;

    @PostMapping("/saveOrder")
    public Order saveOrder(Integer pid, Integer num) {
        Order order = new Order();
        order.setOrderNo(IdUtil.getSnowflakeNextIdStr());//订单编号: 时间戳+随机数  雪花算法: 生成一个唯一的id
        order.setNum(num);
        order.setStatus(0);
        order.setCreateTime(LocalDateTime.now());
        order.setUserid(1);

        order.setProductId(pid);
        //根据商品id查询商品信息---远程调用商品微服务的接口。
        // 基于Http协议调用。[1]可以自己封装HttpClient工具类【适合所有场景】 [2] spring框架提高了一个HttpClient的工具类【适合spring工程】RestTemplate。
        //String url, Class<T> responseType, Object... uriVariables
        Product product = restTemplate.getForObject("http://localhost:8001/product/getById/" + pid,Product.class);
        if(product==null||product.getStock()<num){
            throw new RuntimeException("商品不存在或库存不足");
        }
        order.setPname(product.getPname());
        order.setPrice(product.getPrice());
        Order order1 = orderService.createOrder(order);

        return order1;
    }
}

config

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

Nacos Discovery--服务治理

什么是服务治理

服务治理是微服务架构中最核心最基本的模块。用于实现各个微服务的自动化注册与发现

服务注册:在服务治理框架中,都会构建一个注册中心,每个服务单元向注册中心登记自己提供服

务的详细信息。并在注册中心形成一张服务的*清单*,服务注册中心需要以*心跳30s 90s*的方式去监测清单中 的服务是否可用,如果不可用,需要在服务清单中剔除不可用的服务。

服务发现:服务调用方向服务注册中心咨询服务,并获取*所有服务*的实例清单,实现对具体服务实例的访问。

除了微服务,还有一个组件是服务注册中心,它是微服务架构非常重要

的一个组件,在微服务架构里主要起到了协调者的一个作用。注册中心一般包含如下几个功能:

\1. 服务发现:

服务注册:保存服务提供者和服务调用者的信息

服务订阅:服务调用者订阅服务提供者的信息,注册中心向订阅者推送提供者的信息

\2. 服务配置:

配置订阅:服务提供者和服务调用者订阅微服务相关的配置

配置下发:主动将配置推送给服务提供者和服务调用者

\3. 服务健康检测

检测服务提供者的健康情况,如果发现异常,执行服务剔除

常见的注册中心

Zookeeper

zookeeper是一个分布式服务框架,是Apache Hadoop 的一个子项目,它主要是用来解决分布式

应用中经常遇到的一些数据管理问题,如:统一命名服务、状态同步服务、集群管理、分布式应用

配置项的管理等。

Eureka

Eureka是Springcloud Netflix中的重要组件,主要作用就是做服务注册和发现。但是现在已经闭

源 ,停更不停用。

Consul

Consul是基于GO语言开发的开源工具,主要面向分布式,服务化的系统提供服务注册、服务发现

和配置管理的功能。Consul的功能都很实用,其中包括:服务注册/发现、健康检查、Key/Value

存储、多数据中心和分布式一致性保证等特性。Consul本身只是一个二进制的可执行文件,所以

安装和部署都非常简单,只需要从官网下载后,在执行对应的启动脚本即可。

Nacos(服务治理 配置中心)

Nacos是一个更易于构建云原生应用的动态服务发现、配置管理和服务管理平台。它是 Spring

Cloud Alibaba 组件之一,负责服务注册发现和服务配置.

nacos实战入门

下载nacos软件

Releases · alibaba/nacos · GitHuban easy-to-use dynamic service discovery, configuration and service management platform for building cloud native applications. - Releases · alibaba/nacosicon-default.png?t=N7T8https://github.com/alibaba/nacos/releases

发布历史 | Nacos 官网icon-default.png?t=N7T8https://nacos.io/download/release-history/

选择对应下载版本解压

打开bin目录--修改startup.cmd文件---默认按照集群模式启动----修改为单机启动

启动nacos服务并访问nacos页面

http://localhost:8848/nacos

默认账号密码均为nacos

微服务接入nacos

引入nacos依赖包

<!--nacos的jar包-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

配置文件中指定nacos服务地址

修改订单微服务代码

负载均衡

通俗的讲, 负载均衡就是将负载(工作任务,访问请求)进行分摊到多个操作单元(服务器,组件)上进行执行。

手动实现负载均衡

手动实现负载均衡使用的策略:随机策略。 如果我想改变策略--修改源代码。第三方公司提高了一个组件。ribbon.

介绍ribbon

什么是Ribbon
是 Netflix 发布的一个负载均衡器,有助于控制 HTTP 和 TCP客户端行为。在 SpringCloud 中, nacos一般配合Ribbon进行使用,Ribbon提供了客户端负载均衡的功能,Ribbon利用从nacos中读 取到的服务信息,在调用服务节点提供的服务时,会合理(策略)的进行负载。 在SpringCloud中可以将注册中心和Ribbon配合使用,Ribbon自动的从注册中心中获取服务提供者的 列表信息,并基于内置的负载均衡算法,请求服务。

ribbon是netflix公司的组件,作用自动从注册中心拉取服务器信息,并根据相应的策略完成负载均衡的调用。

使用ribbon

nacos依赖自带ribbon包

bean方法上增加注解

在RestTemplate所在的bean方法上加入一个注解。

修改controller代码

完成了负载均衡,策略默认为轮询策略

ribbon负载均衡策略

Ribbon内置了多种负载均衡策略,内部负载均衡的顶级接口为com.netflix.loadbalancer.IRule , 具体的负载策略如下图所示:

1.3 自定义ribbon负载均衡策略 - 盛开的太阳 - 博客园 (cnblogs.com)icon-default.png?t=N7T8https://www.cnblogs.com/ITPower/p/13295955.html 第一种:局部使用

shop-product:  # 这里使用服务的名称
  ribbon:
    NFLoadBalancerRuleClassName: com.netflix.loadbalancer.RandomRule #使用的的负载均衡策略
# 指定调用商品微服务使用的负载均衡的策略
qy174-product.ribbon.NFLoadBalancerRuleClassName=com.netflix.loadbalancer.RandomRule

第二种:全局使用

Openfeign远程调用组键

之前我们使用RestTemplate+ribbon完成负载均衡。 语法不符合我们调用的习惯。

Controller----Service----->Dao.

@autoWire注入一个Service对象,对象调用类中的方法,习惯该方法需要什么参数就参数参数类型。

openfeign概述

OpenFeign是Spring Cloud提供的一个声明式的伪Http客户端, 它使得调用远程服务就像调用本地服务一样简单, 只需要创建一个接口并添加一个注解即可。 
Nacos很好的兼容了OpenFeign, OpenFeign负载均衡默认集成了 Ribbon, 所以在Nacos下使用Fegin默认就实现了负载均衡的效果。

使用openfeign

引入依赖

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

创建接口

@FeignClient(value = "qy174-product")   //openfeign为该接口生成代理实现类。
public interface ProductFeign {

    //这里的方法的请求方式以及参数类型必须和提供者一致。
    @GetMapping("/product/getById/{pid}")
    public Product getById(@PathVariable Integer pid);
}

开启注释驱动

@SpringBootApplication
@MapperScan("com.aaa.mapper")
@EnableFeignClients//开启openfeign的注解驱动
public class OrderApp {
    public static void main(String[] args) {
        SpringApplication.run(OrderApp.class,args);
    }
}

修改controller代码

nacos集群模式

1.修改conf/application.properties文件

2.创建数据库nacos并导入sql语句

3.修改cluster.conf.example文件

4.启动三台nacos服务

5.微服务连接

gateway网关

大家都都知道在微服务架构中,一个系统会被拆分为很多个微服务。那么作为客户端(pc androud ios 平板)要如何去调用这么多的微服务呢?如果没有网关的存在,我们只能在客户端记录每个微服务的地址,然后分别去调用。

这样的架构,会存在着诸多的问题:

l 客户端多次请求不同的微服务,增加客户端代码或配置编写的复杂性 。

l 认证复杂,每个服务都需要独立认证。

l 存在跨域请求,在一定场景下处理相对复杂。

常用网关组件

l Ngnix+lua

使用nginx的反向代理和负载均衡可实现对api服务器的负载均衡及高可用

lua是一种脚本语言,可以来编写一些简单的逻辑, nginx支持lua脚本

l Kong

基于Nginx+Lua开发,性能高,稳定,有多个可用的插件(限流、鉴权等等)可以开箱即用。 问题:

只支持Http协议;二次开发,自由扩展困难;提供管理API,缺乏更易用的管控、配置方式。

l Zuul 1.0(慢 servlet 2.0 ) zuul2.0 没出来。

Netflix开源的网关,功能丰富,使用JAVA开发,易于二次开发 问题:缺乏管控,无法动态配

置;依赖组件较多;处理Http请求依赖的是Web容器,性能不如Nginx

l Spring Cloud Gateway

Spring公司为了替换Zuul而开发的网关服务,将在下面具体介绍。

注意:**SpringCloud alibaba技术栈中并没有提供自己的网关,我们可以采用Spring Cloud**

概述gateway网关

Spring Cloud Gateway是Spring公司基于Spring 5.0,Spring Boot 2.0 和 Project Reactor 等术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。它的目标是替代 Netflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控和限流。

优点:

l 性能强劲:是第一代网关Zuul的1.6倍

l 功能强大:内置了很多实用的功能,例如转发、监控、限流等

l 设计优雅,容易扩展.

缺点:

l 其实现依赖Netty与WebFlux,不是传统的Servlet编程模型,学习成本高

l 不能将其部署在Tomcat、Jetty等Servlet容器里,只能打成jar包执行 web.Jar

l 需要Spring Boot 2.0及以上的版本,才支持

gateway内置了服务器 netty服务器。千万不要在使用tomcat作为服务器。

使用gateway网关

1.创建网关微服务

2.添加依赖

<dependencies>
        <!--如果引入了gateway的依赖不能在引用spring-boot-starter-web,否则会报错。因为web内置了tomcat服务器,而gateway内置netty服务器-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
    </dependencies>

3.配置文件

server:
  port: 80

#spring:
#  application:
#    name: aaa-gateway

  #配置路由转发
  cloud:
    gateway:
      routes:
        - id: aaa-product #路由id,没有实际意义。如果不定义UUID随机生成
          uri: http://localhost:8001 # 表示路由真实转发的微服务的地址
          predicates: #断言: 如果断言满足要求则转发到对应uri地址   http://localhost:80/product/getById/1==>http://localhost:8001/product/getByIid/1
            - Path=/product/**


        - id: aaa-order
          uri: http://localhost:9001
          predicates:
            - Path=/order/**

测试

增强版

现在在配置文件中写死了转发路径的地址, 前面我们已经分析过地址写死带来的问题, 接下来我们从注册中心获取此地址。

思考: gateway网关它也是一个微服务,那么它也可以从注册中心拉取服务器清单列表。

引入依赖

<!--nacos的依赖-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

修改配置文件

认证校验

package com.aaa.filter;

import com.aaa.vo.UrlVo;
import com.alibaba.fastjson.JSON;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

import static org.apache.logging.log4j.message.MapMessage.MapFormat.JSON;

@Component
public class LoginFilter implements GlobalFilter,Ordered {

    @Autowired
    private UrlVo urlVo;

    @Override
    public int getOrder() {
        return 0;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();
        //1.获取请求路径
        String path = request.getPath().toString();
        path = path.substring(1);
        path = path.substring(path.lastIndexOf("/"));
        //2.判断该路径是否属于放行路径--白名单
        if (urlVo.getWhite().contains(path)){
            return chain.filter(exchange);
        }
        //3.判断用户是否登录
        String token = request.getHeaders().getFirst("token");
        //4.校验token是否为空,以及是否合法
        if (StringUtils.hasText(token)&&"admin".equals(token)){
            return chain.filter(exchange);
        }
        //3.2封装返回数据
        Map<String,Object> map = new HashMap<>();
        map.put("msg", "未登录");
        map.put("code", 501);
        //3.3做json转换
        byte[] bytes = com.alibaba.fastjson.JSON.toJSONString(map).getBytes(StandardCharsets.UTF_8);
        //3.4调用buffer Factory方法,生成DataBuffer对象
        DataBuffer buffer = response.bufferFactory().wrap(bytes);
        //4.调用Mono中的just方法,返回要写给前端的JSON数据
        return response.writeWith(Mono.just(buffer));
    }
}

跨域解决gateway

第一种:配置类

package com.aaa.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.cors.reactive.CorsWebFilter;

@Configuration
public class CorsConfig {
    @Bean
    public CorsWebFilter coreFilter() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.setAllowCredentials(true);//允许认证
        config.addAllowedOrigin("*");//允许任何源
        config.addAllowedHeader("*");//允许任何头
        config.addAllowedMethod("*");//允许任何方法
        source.registerCorsConfiguration("/**", config);
        return new CorsWebFilter(source);
    }
}

第二种:通过配置文件

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

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值