微服务架构 基础(一)

微服务架构 基础(一)

待续…

SpringCloud技术栈大致概览

在这里插入图片描述

在这里插入图片描述

单体架构和分布式架构

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

微服务初识

在这里插入图片描述

微服务拆分原则
  • 不同微服务,不要重复开发相同的业务
  • 微服务数据独立,不要访问其它微服务的数据库
  • 微服务可以将自己的业务暴露为接口,供其它微服务调用

SpringCloud版本选择

 "spring-cloud": {
   "Hoxton.SR12": "Spring Boot >=2.2.0.RELEASE and <2.4.0.M1",
   "2020.0.4": "Spring Boot >=2.4.0.M1 and <2.5.7-SNAPSHOT",
   "2020.0.5-SNAPSHOT": "Spring Boot >=2.5.7-SNAPSHOT and <2.6.0-M1",
   "2021.0.0-M1": "Spring Boot >=2.6.0-M1 and <2.6.0-M3",
   "2021.0.0-M3": "Spring Boot >=2.6.0-M3 and <2.6.0-SNAPSHOT",
   "2021.0.0-SNAPSHOT": "Spring Boot >=2.6.0-SNAPSHOT"
 }

创建总父工程

选择Maven
在这里插入图片描述

编码设置

在这里插入图片描述

注解生效

在这里插入图片描述

选择Java编译版本为8

在这里插入图片描述

为了使得界面更加简洁,建议配置将.idea以及.iml文件进行忽略,但是在此之前我们首先进入Maven父工程下找到workspace.xml配置文件,添加如下代码(作用是可以更好管理子模块,后续会有作用的… ):

<component name="RunDashboard">
    <option name="configurationTypes">
      <set>
        <option value="SpringBootApplicationConfigurationType" />
      </set>
    </option>
    <option name="ruleStates">
      <list>
        <RuleState>
          <option name="name" value="ConfigurationTypeDashboardGroupingRule" />
        </RuleState>
        <RuleState>
          <option name="name" value="StatusDashboardGroupingRule" />
        </RuleState>
      </list>
    </option>
</component>

然后开始文件过滤:

在这里插入图片描述

然后重启…

在这里插入图片描述

修改父级pom.xml文件

<?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">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.wu</groupId>
    <artifactId>Project</artifactId>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>micro-service-8001</module>
        <module>micro-service-8002</module>
        <module>main-service-80</module>
    </modules>
    <!-- 规定为父Maven -->
    <packaging>pom</packaging>
    
    <build>
        <finalName>Project</finalName>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.2.6.RELEASE</version>
                <configuration>
                    <fork>true</fork>
                    <addResources>true</addResources>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

最后删除src目录

添加子模块

持久层设计

8001/8002端口数据库结构图

在这里插入图片描述

数据表结构图

在这里插入图片描述

在这里插入图片描述

子模块创建以及结构

在这里插入图片描述

在这里插入图片描述

micro-service-8001子模块

Maven配置

<?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>Project</artifactId>
        <groupId>cn.wu</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>micro-service-8001</artifactId>
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.3.7.RELEASE</spring-boot.version>
        <spring-cloud.version>Hoxton.SR9</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>cn.wu</groupId>
            <artifactId>api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.22</version>
        </dependency>
    </dependencies>

    <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>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.7.RELEASE</version>
                <configuration>
                    <mainClass>cn.wu.Application8001</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

application.yml:

server:
  port: 8001
spring:
  application:
    name: micro-service-8001
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://192.168.123.188:3333/DB_1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
    name: defaultDataSource
    username: root
    password: root

mybatis-plus:
  mapper-locations: classpath:/mappers/*.xml
  type-aliases-package: cn.wu.entity

logging:
  level:
    cn.wu.service: debug
    cn.wu.dao: debug

控制层:

package cn.wu.cotroller;

import cn.wu.entity.Book;
import cn.wu.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class BookController {
    private BookService bookService;
    @Autowired
    @Qualifier("bookServiceBean")
    public void setBookService(BookService bookservice) {
        this.bookService = bookservice;
    }
    @PostMapping(value = "/book/add")
    public boolean addBook(Book book){
        try {
            bookService.addBook(book);
            return true;
        }catch(Exception e){
            return false;
        }
    }


    @GetMapping(value = "/book/get/{id}")
    public Book getBookById(@PathVariable("id") String id){
        try {
            return bookService.getBookById(id);
        }catch(Exception e){
            e.printStackTrace();
            return null;
        }
    }

}

持久层:

package cn.wu.dao;

import cn.wu.entity.Book;
import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;

@Repository("bookDaoBean")
public interface BookDao {
    public void addBook(Book book);
    public Book getBookById(@Param("id") String id);
}

实体类:

package cn.wu.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Book implements Serializable {
    private String bookId;
    private String bookName;
    private float bookPrice;
}

业务层:

package cn.wu.service;

import cn.wu.dao.BookDao;
import cn.wu.entity.Book;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

@Service("bookServiceBean")
public class BookService {
    private BookDao bookDao;
    @Autowired
    @Qualifier("bookDaoBean")
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
    public void addBook(Book book){
        bookDao.addBook(book);
    }
    public Book getBookById(String id){
        return bookDao.getBookById(id);
    }
}

主启动类:

package cn.wu;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@MapperScan("cn.wu.dao")
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class,args);
    }
}

映射配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace = "cn.wu.dao.BookDao">
    <resultMap id="bookResultMap" type="cn.wu.entity.Book">
        <id property="bookId" column="book_id"></id>
        <result property="bookName" column="book_name"></result>
        <result property="bookPrice" column="book_price"></result>
    </resultMap>
    <insert id="addBook" parameterType="Book">
        insert into books(book_id,book_name,book_price)
        values(#{bookId},#{bookName},#{bookPrice})
    </insert>
    <select id="getBookById" parameterType="String" resultMap="bookResultMap">
        select * from books where book_id = #{id}
    </select>
</mapper>

micro-service-8002子模块大致代码同上
main-service-80主模块核心代码

依赖配置:

<?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>Project</artifactId>
        <groupId>cn.wu</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>main-service-80</artifactId>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-dependencies</artifactId>
            <version>2.5.1</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.4.2</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.18</version>
        </dependency>
    </dependencies>

</project>

配置类:

package cn.wu.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class ApplicationConfig {
    @Bean("restTemplateBean") // 注册RestTemplate对象到Spring容器中
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

控制层:

package cn.wu.controller;

import cn.wu.entity.Book;
import cn.wu.entity.Student;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;


@RestController
public class MainController {
    private static final String URL8001 = "http://localhost:8001";
    private static final String URL8002 = "http://localhost:8002";

    private RestTemplate restTemplate;
    @Autowired
    @Qualifier("restTemplateBean")
    public void setRestTemplate(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @GetMapping("/student/id/{id}")
    public String findStudentById(@PathVariable("id") String id){
        try{
            return restTemplate.
            getForObject(URL8002+"/student/id/"+id, String.class);
        }catch(Exception e){
            e.printStackTrace();
            return null;
        }
    }
    @GetMapping("/book/id/{id}")
    public String findBookById(@PathVariable("id") String id){
        try{
            return restTemplate.
                    getForObject(URL8001+"/book/id/"+id, String.class);
        }catch(Exception e){
            return null;
        }
    }

    @PostMapping("/student/add")
    public boolean addStudent(Student student){
        try{
            return restTemplate.
            postForObject(URL8002+"/student/add",student,Boolean.class);
        }catch(Exception e){
            return false;
        }
    }

    @PostMapping("/book/add")
    public boolean addBook(Book book){
        try{
            return restTemplate.
                    postForObject(URL8001+"/book/add",book,Boolean.class);
        }catch(Exception e){
            return false;
        }
    }

}

如此就完成了最基本的模块通信,当然最后一步不要忘记测试每个API了…



完成以上的基本过程后,我们可以找到一个问题,那就是发现entity包下是代码冗余的,因此,接下来我们进行重构过程,一步一步优化,一步一步完善…

添加子模块

在这里插入图片描述

删除所有子模块的实体类,并在pom.xml引入实体类坐标

<!-- 引入api-commons子模块 -->
 <dependency>
     <groupId>cn.wu</groupId>
     <artifactId>api-commons</artifactId>
     <version>1.0-SNAPSHOT</version>
 </dependency>

最后不要忘记了 Reimport All Maven Project


服务注册与发现

Eureka

Eureka架构

Eureka主要角色

  • EurekaServer:服务器,注册中心
    • 记录服务信息
    • 心跳记录
  • EurekaClient:客户端
    • Provider:服务提供者
      • 注册自己的信息到EurekaServe
      • 每间隔30秒向EurekaServer发送心跳
    • Consumer:服务消费者
      • 根据服务名称从EurekaServer拉取服务列表
      • 基于服务列表做负载均衡,选中一个微服务后发起远程调用
Eureka子模块

Eureka服务端模块

通过 Spring Initializr 创建子模块后,查看pom.xml文件:

<?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>
    <artifactId>eureka-server-7001</artifactId>
    <parent>
        <artifactId>Project</artifactId>
        <groupId>cn.wu</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <spring-boot.version>2.3.7.RELEASE</spring-boot.version>
        <spring-cloud.version>Hoxton.SR9</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>cn.wu</groupId>
            <artifactId>api-commons</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- 服务注册 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <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>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>2.3.7.RELEASE</version>
                <configuration>
                    <mainClass>cn.wu.MainEurekaApplication</mainClass>
                </configuration>
                <executions>
                    <execution>
                        <id>repackage</id>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

</project>

修改application.yml:

server:
  port: 7001
eureka:
  instance:
    hostname: localhost # Eureka服务端实例名
  client:
    register-with-eureka: false # 设置不向注册中心注册该模块
    fetch-registry: false # 设置该模块为注册中心,职责是维护服务实例,不需要像客户端检索服务
    service-url:
      # 设置与服务端交互和注册的地址
      defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/

最后在主启动类中添加注解@EnableEurekaServer,完成

在这里插入图片描述
Eureka客户端配置

除Eureka服务端外对其余所有的子模块进行如下操作

pom.xml配置添加:

<!-- 服务注册 -->
 <dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
 </dependency>

applicaint.yml添加:

eureka:
  client:
    register-with-eureka: true # 注册
    fetch-registry: true  # 是否从Eureka服务端中抓取已有的注册信息
    service-url:
      defaultZone: http://localhost:7001/eureka # 服务端网址

最后在主启动类中添加注解@EnableEurekaClient,完成

在这里插入图片描述

可以发现已经多了三个实例

构建Eureka集群

再次创建一个Eureka并且端口为7002的服务端

如下为当前文件的整体结构:

在这里插入图片描述

修改application.yml文件

server:
  port: 7001
eureka:
  instance:
    hostname: eureka7001 # Eureka服务端实例名
  client:
    register-with-eureka: false # 设置不向注册中心注册该模块
    fetch-registry: false # 设置该模块为注册中心,职责是维护服务实例,不需要像客户端检索服务
    service-url:
      # 设置与服务端交互和注册的地址
      defaultZone: http://eureka7002.cn:7002/eureka/ # 集群填写其余的服务端口地址

模拟不同主机,修改本地host表,添加如下内容

windows系统host表位置为:C:\Windows\System32\drivers\etc,记得修改完毕保存

在这里插入图片描述

然后修改所的非eureka服务端的子模块的application.yml文件

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      # 集群版本的服务端口地址,直接填写所有的服务端地址即可
      defaultZone: http://eureka7001.cn:7001/eureka,http://eureka7002.cn:7002/eureka

最后测试即可,首先应该启动Eureka服务端,其次才能启动Eureka客户端,如下:

在这里插入图片描述

负载均衡测试,这里将8001端口业务同9001业务重合(如提高系统的稳定性…),构建子模块

在这里插入图片描述

这里不要忘记将端口号改为9001,并且将spring.application.name设置为与8001模块的一致,同时为了便于测试,需要稍微修改一下8001和9001模块的controller层

@Value("${server.port}")
private String port;
...
return bookService.getBookById(id).toString()+
                    ">>>>> 当前应用端口为:"+port;

修改80端口应用已经固定的单机访问URL,改为集群URL

// 这里为集群spring.application.name名称
private static final String URL1 = "http://micro-service-1";

修改80端口模块的配置类型信息,添加注解@LoadBalanced

@Configuration
public class ApplicationConfig {
    @Bean("restTemplateBean") // 注册RestTemplate对象到Spring容器中
    @LoadBalanced // 用于负载均衡注解,默认为轮询策略
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}

最终所有模块依次启动,测试情况如下:

在这里插入图片描述

在这里插入图片描述

多次测试同一个URL,可以发现是不同的访问端口:
在这里插入图片描述

在这里插入图片描述

以上就起到了负载均衡的效果,不需要关心地址和端口号…


补充知识
  • 健康监控
<dependency>
    <!-- 添加监控系统健康情况的工具 -->
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-actuator</artifactId>
</dependency>
eureka:
  instance:
    # 监控健康情况实例名
    instance-id: MicroService9001
  • 服务发现Discovery
@MapperScan("cn.wu.dao")
@SpringBootApplication
@EnableDiscoveryClient
public class Application9001 {
    public static void main(String[] args) {
        SpringApplication.run(Application9001.class,args);
    }
}
...
private DiscoveryClient discoveryClient;
    @Autowired
    public void setDiscoveryClient(DiscoveryClient discoveryClient) {
        this.discoveryClient = discoveryClient;
    }
    @GetMapping("/discovery")
    public String discovery() {
        return discoveryClient.getServices().toString() + "<br>"
                +discoveryClient.getInstances("micro-service-1").toString();
    }
...
  • 自我保护机制

某时刻某一个服务不可用了,Eureka不会立刻清理,依旧会对该为服务的信息进行保护

Eureka首页常常看到以下消息,就是说明进入了自我保护机制:

EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.

关闭自我保护机制配置:

# Eurek服务端
eureka:
  server:
    enable-self-preservation: false # 关闭该服务端自我保护机制
    eviction-interval-timer-in-ms: 4000 # 每4s清理无效节点一次

# Eurek客户端
eureka:
  instance:
    lease-renewal-interval-in-seconds: 3 # Eureka客户端向服务端发送心跳时间的时间间隔,单位为秒
    lease-expiration-duration-in-seconds: 2 # Eureka服务端收到最后一次心跳后等待时间上限,单位为秒


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值