spring-boot重头再来 6 分布式理论 RPC远程过程调用 Zookeeper安装 Dubbo SpringBoot + Dubbo + zookeeper Spring Security

spring-boot重头再来 6

分布式理论

分布式系统是建立在网络之上的软件系统,是若干独立计算机的集合,这些计算机对于用户来说就像单个相关系统。

分布式系统是由一组通过网络进行通信、为了完成共同的任务而协调工作的计算机节点组成的系统。分布式系统的出现是为了用廉价的、普通的机器完成单个计算机无法完成的计算、存储任务。其目的是利用更多的机器,处理更多的数据

RPC远程过程调用

RPC【Remote Procedure Call】是指远程过程调用,是一种进程间通信方式,他是一种技术的思想,而不是规范。它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显式编码这个远程调用的细节。即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。

RPC两个核心模块:通讯序列化

Zookeeper安装

由于我经常用的系统依然是windows系统,所以我们在windows环境下安装zookeeper

下载地址:Apache ZooKeeper

这里我安装的是3.4.14版本

  1. 下载tar.gz解压包,下载后解压

  2. 解压后,此时直接运行/bin/zkServer.cmd是无法成功运行的(闪退),没有zoo.cfg配置文件

  3. 这时我们需要修改zoo.cfg配置文件,可是我们conf文件夹下并没有zoo.cfg文件,那该怎么办?我们将zoo_sample.cfg复制一份改名为zoo.cfg,并修改其中的dataDir属性,可见原来是/tmp,这一看就是linux特有的目录位置,我们将其修改为./

    # The number of milliseconds of each tick
    tickTime=2000
    # The number of ticks that the initial 
    # synchronization phase can take
    initLimit=10
    # The number of ticks that can pass between 
    # sending a request and getting an acknowledgement
    syncLimit=5
    # the directory where the snapshot is stored.
    # do not use /tmp for storage, /tmp here is just 
    # example sakes.
    # dataDir=/tmp/zookeeper
    dataDir=./
    # the port at which the clients will connect
    clientPort=2181
    # the maximum number of client connections.
    # increase this if you need to handle more clients
    #maxClientCnxns=60
    #
    # Be sure to read the maintenance section of the 
    # administrator guide before turning on autopurge.
    #
    # http://zookeeper.apache.org/doc/current/zookeeperAdmin.html#sc_maintenance
    #
    # The number of snapshots to retain in dataDir
    #autopurge.snapRetainCount=3
    # Purge task interval in hours
    # Set to "0" to disable auto purge feature
    #autopurge.purgeInterval=1
    
  4. 启动/bin/zkServer.cmd,这时应该可以成功运行,至少我的可以

  5. 使用zkCli.cmd测试

    1. 运行/bin/zkCli.cmd

    2. 输入

      ls /
      

在这里插入图片描述

  1. 输入

    create -e /biang henniubi
    
![在这里插入图片描述](https://img-blog.csdnimg.cn/63fc75163afb4a50b7508dc6ac153b84.png#pic_center)
  1. 输入

    get /biang
    

在这里插入图片描述

  1. 输入

    ls /
    

在这里插入图片描述

  测试成功!

Dubbo

官方文档 Apache Dubbo

Apache Dubbo 是一款高性能、轻量级的开源 Java 服务框架

Apache Dubbo |ˈdʌbəʊ| 提供了六大核心能力:面向接口代理的高性能RPC调用,智能容错和负载均衡,服务自动注册和发现,高度可扩展能力,运行期流量调度,可视化的服务治理与运维。

dubbo-admin安装

dubbo本身并不是一个服务软件。它其实就是一个jar包,能够帮你的java程序连接到zookeeper,并利用zookeeper消费、提供服务。

但是为了让用户更好的管理监控众多的dubbo服务,官方提供了一个可视化的监控程序dubbo-admin,不过这个监控即使不装也不影响使用。

  1. 第一步自然是下载

    地址 :https://github.com/apache/dubbo-admin/tree/master

  2. 打包

    解压进入目录dubbo-admin-master

    运行cmd命令

     mvn clean package -Dmaven.test.skip=true
    

    当然啦,像我就运行命令失败了,出现了版本不对应的错误

    Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.6.0:compile(造成这个错误的原因不只是版本不对应,只是我刚好是这个问题)

    这时候我们可以使用我们的idea来打开dubbo-admin-master文件夹,然后利用IDEA强大的maven功能,在这里插入图片描述

    使用生命周期->package功能来进行打包

dubbo-admin运行与测试

  1. 到dubbo-admin\target 目录下,执行dubbo-admin-0.0.1-SNAPSHOT.jar,也就是执行下列语句

    java -jar dubbo-admin-0.0.1-SNAPSHOT.jar
    

    由于配置dubbo-admin\src\main\resources \application.properties中,dubbo.registry.address属性中默认指定的分布式应用程序协调服务是zookeeper,所以zookeeper服务需要先行打开,不然上述的语句运行后会报错。

  2. 访问localhost:7001

在这里插入图片描述

默认账号密码是root root

  1. 输入密码后进入dubbo-admin界面

在这里插入图片描述

安装完成!

SpringBoot + Dubbo + zookeeper

框架搭建

  1. 启动zookeeper (运行zookeeper文件夹下的 \bin\zkServer.cmd)

  2. 新建项目,再在其中新建新springboot模块 provider-server

在这里插入图片描述

  1. 导入springweb,zookeeper ,dubbo的依赖

    <!-- Dubbo Spring Boot Starter -->
    <dependency>
        <groupId>org.apache.dubbo</groupId>
        <artifactId>dubbo-spring-boot-starter</artifactId>
        <version>2.7.3</version>
    </dependency>
    
    <!-- https://mvnrepository.com/artifact/com.github.sgroschupf/zkclient -->
    <dependency>
        <groupId>com.github.sgroschupf</groupId>
        <artifactId>zkclient</artifactId>
        <version>0.1</version>
    </dependency>
    
    <!-- 引入zookeeper -->
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-framework</artifactId>
        <version>2.12.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.curator</groupId>
        <artifactId>curator-recipes</artifactId>
        <version>2.12.0</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.apache.zookeeper/zookeeper -->
    <dependency>
        <groupId>org.apache.zookeeper</groupId>
        <artifactId>zookeeper</artifactId>
        <version>3.7.0</version>
    </dependency>
    
  2. 在新模块中新建service包并新建新建Service PokemonService

    package com.example.service.impl;
    
    import com.example.service.PokemonService;
    import org.apache.dubbo.config.annotation.Service;
    import org.springframework.stereotype.Component;
    
    /**
     * @author BIANG
     * @Date 2021/7/23 21:13
     */
    //值得注意的是这个Service是Dubbo包下的不是springboot的
    @Service
    //为了和上面的Dubbo包下的Service区分,所以这里使用万能的Component
    @Component
    public class PokemonServiceImpl implements PokemonService {
        /**
         * 获取Pikachu字符串
         *
         * @return Pikachu
         */
        @Override
        public String getPikachu() {
            return "Pikachu";
        }
    }
    
  3. 配置模块的properties

    # 应用名称
    spring.application.name=provider-server
    # 应用服务 WEB 访问端口
    server.port=8081
    
    #当前应用名字
    dubbo.application.name=provider-server
    #注册中心地址
    dubbo.registry.address=zookeeper://127.0.0.1:2181
    #扫描指定包下服务
    dubbo.scan.base-packages=com.example.service
    
  4. 运行主程序 没有报错

  5. 运行Dubbo(到dubbo/dubbo-admin/target下 执行java -jar dubbo-admin-0.0.1-SNAPSHOT.jar),并访问localhost:7001,查看到对应的提供者

在这里插入图片描述

  1. 同样的新建一个消费者模块,导入同样的依赖

  2. 先配置properties

    # 应用名称
    spring.application.name=consumer-server
    # 应用服务 WEB 访问端口
    server.port=8082
    
    #当前应用名字
    dubbo.application.name=consumer-server
    #注册中心地址
    dubbo.registry.address=zookeeper://127.0.0.1:2181
    
  3. 接下来就是编写service

    package com.example.service.impl;
    
    import com.example.service.PokemonService;
    import com.example.service.UserService;
    import org.apache.dubbo.config.annotation.Reference;
    import org.springframework.stereotype.Service;
    
    /**
     * @author BIANG
     * @Date 2021/7/24 1:17
     */
    @Service
    public class UserServiceImpl implements UserService {
        @Reference
        PokemonService pokemonService;
    
        /**
         * 获取宝可梦
         */
        @Override
        public void getPokemon(){
            String ticket = pokemonService.getPikachu();
            System.out.println("得到"+ticket+"啦");
        }
    }
    

    显然,两个类处于不同项目,java是没办法直接获取到PokemonService类的,这时我们可以通过两种方法来解决

    1. pom坐标(很遗憾我并不知道是什么东西)

    2. 按照全类名进行匹配,也就是将提供者的接口搬到消费者项目中的同个路径

      在这里插入图片描述

测试

在消费者项目中编写测试类

package com.example;

import com.example.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class ConsumerServerApplicationTests {
    @Autowired
    UserService userService;
    @Test
    void contextLoads() {
        userService.getPokemon();
    }
}

成功运行

Spring Security

简介

在 Web 开发中,安全一直是非常重要的一个方面。安全虽然属于应用的非功能性需求,但是应该在应用开发的初期就考虑进来。如果在应用开发的后期才考虑安全的问题,就可能陷入一个两难的境地:一方面,应用存在严重的安全漏洞,无法满足用户的要求,并可能造成用户的隐私数据被攻击者窃取;另一方面,应用的基本架构已经确定,要修复安全漏洞,可能需要对系统的架构做出比较重大的调整,因而需要更多的开发时间,影响应用的发布进程。

Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。

用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。身份验证是关于验证您的凭据,如用户名/用户ID和密码,以验证您的身份。身份验证通常通过用户名和密码完成,有时与身份验证因素结合使用。

用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。授权发生在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。

实战基本环境配置

  1. 新建项目,导入spring web依赖、thymeleaf依赖

  2. 随便写点html、css和放点js,包括

    • static

      • css

        • base.css

          * {
              margin: 0;
              padding: 0;
          }
          body {
              background-color: #666666;
          }
          .w {
              margin: 0 auto;
          }
          .center {
              position: absolute;
              top: calc(50% - 150px);
              left: calc(50% - 250px);
          }
          .login{
              width: 500px;
              height: 300px;
          }
          .login-content{
              width: 485px;
              height: 300px;
              border-radius: 20px;
              border:5px solid transparent;
              background-clip:padding-box,border-box;
              background-origin:padding-box,border-box;
              background-image:linear-gradient(#5A5A5A,#5A5A5A),linear-gradient(#3c5cef, #b45b9b);
              text-align: center;
          }
          .login-title {
              width: 100%;
              height: 65px;
              padding-top: 20px ;
              line-height: 65px;
              font-family: 微软雅黑,serif;
              text-align: center;
              color: azure;
              font-size: 24px;
              font-weight: 100;
          }
          .login-input-frame {
              width: 80%;
              height: 60px;
              text-align: center;
          }
          .login-input {
              display: inline-block;
              width: 60%;
              height: 30px;
              margin: 15px 0;
              text-align: center;
              outline:none;
              border-radius: 10px;
              border:2px solid transparent;
              transition: width 1s ease 0s;
              background-clip:padding-box,border-box;
              background-origin:padding-box,border-box;
              background-image:linear-gradient(rgba(90,90,90,.9),rgba(90,90,90,.9)),linear-gradient(#3c5cef, #b45b9b);
              color: white;
          }
          .login-input:hover,
          .login-input:focus {
              width: 80%;
              transition: width 1s ease 0s;
          }
          .placeholder-light:hover::-webkit-input-placeholder{
              color: rgba(255,255,255,.3);
          }
          .login-button-frame {
              width: 80%;
              height: 95px;
              text-align: center;
          }
          .login-button {
              position: relative;
              width: 30%;
              height: 30px;
              margin: 17px 0 47px 0;
              border:2px solid transparent;
              background-clip:padding-box,border-box;
              background-origin:padding-box,border-box;
              background-image:linear-gradient(rgba(90,90,90,.9),rgba(90,90,90,.9)),linear-gradient(#3c5cef, #b45b9b);
              color: rgba(255,255,255,.5);
              transition: color .5s;
              z-index: 0;
          }
          .login-button:before{
              content: '';
              position: absolute;
              left: 0; top: 0; right: 0; bottom: 0;
              background-image:linear-gradient(rgba(90,90,90,.9),rgba(90,90,90,.9)),linear-gradient(#5476fd, #ff82d9);
              opacity: 0;
              transition: opacity .5s;
              z-index: -1;
          }
          .login-button:hover {
              color: rgba(255,255,255,.8);
          }
          .login-button:hover::before {
              opacity: 1;
          }
          
          .login-to-button {
              position: relative;
              display: inline-block;
              width: 40%;
              padding:17.94% 0;
              margin: calc((300px - 40%) * 0.5) 0;
              text-align: center;
              line-height: 0;
              color: rgba(255,255,255,.5);
              border:10px solid transparent;
              border-radius: 50%;
              background-clip:padding-box,border-box;
              background-origin:padding-box,border-box;
              background-image:linear-gradient(rgba(90,90,90,.9),rgba(90,90,90,.9)),linear-gradient(#3c5cef, #b45b9b);
              font-size: 36px;
              transition: color .5s;
              z-index: 0;
          }
          .login-to-button:before{
              content: '';
              position: absolute;
              left: 0; top: 0; right: 0; bottom: 0;
              background-image:linear-gradient(#5476fd, #ff82d9),linear-gradient(rgba(90,90,90,.9),rgba(90,90,90,.9));
              border-radius: 50%;
              opacity: 0;
              transition: opacity .5s;
              z-index: -1;
          }
          .login-to-button:hover {
              color: rgba(255,255,255,.8);
          }
          .login-to-button:hover::before {
              opacity: 1;
          }
          
          a {
              color: #888888;
              text-decoration: none;
              transition: color .5s;
          }
          a:hover {
              color: white;
          }
          .information{
              width: 90%;
              margin: 3px auto;
              border-radius: 10px;
              border:2px solid transparent;
              background-clip:padding-box,border-box;
              background-origin:padding-box,border-box;
              background-image:linear-gradient(#5A5A5A,#5A5A5A),linear-gradient(#3c5cef, #b45b9b);
              text-align: center;
              line-height: 33px;
              color: white;
          }
          
          .level {
              position: absolute;
              width: 40%;
              top: calc(50% + 160px);
              left: 30%;
          }
          
    • templates

      • index.html

        <!DOCTYPE html>
        <html lang="zh_CN" xmlns:th="http://www.thymeleaf.org"
              xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
        <head>
            <meta charset="UTF-8">
            <title>首页</title>
        <!--    <link rel="stylesheet" href="../static/base.css">-->
            <link rel="stylesheet" th:href="@{/css/base.css}">
        </head>
        <body>
            <div class="center login " th:fragment="nav-menu">
                <div class="login-content w" sec:authorize="!isAuthenticated()">
                    <button class="login-to-button">
                        <a th:href="@{/login}">登录</a>
                    </button>
                </div>
                <div class="login-content w" sec:authorize="isAuthenticated()">
                    <div class="information">
                        用户名:<span sec:authentication="principal.username"></span>
                    </div>
                    <div class="information">
                        角色:<span sec:authentication="principal.authorities"></span>
                    </div>
                    <div class="information" sec:authorize="hasRole('vip1')">
                        <div>VIP1</div>
                        <span><a th:href="@{/level1(id=1)}">Level-1-1</a></span>
                        <span><a th:href="@{/level1(id=2)}">Level-1-2</a></span>
                        <span><a th:href="@{/level1(id=3)}">Level-1-3</a></span>
                    </div>
                    <div class="information" sec:authorize="hasRole('vip2')">
                        <div>VIP2</div>
                        <span><a th:href="@{/level2(id=1)}">Level-2-1</a></span>
                        <span><a th:href="@{/level2(id=2)}">Level-2-2</a></span>
                        <span><a th:href="@{/level2(id=3)}">Level-2-3</a></span>
                    </div>
                    <div class="information" sec:authorize="hasRole('vip3')">
                        <div>VIP3</div>
                        <span><a th:href="@{/level3(id=1)}">Level-3-1</a></span>
                        <span><a th:href="@{/level3(id=2)}">Level-3-2</a></span>
                        <span><a th:href="@{/level3(id=3)}">Level-3-3</a></span>
                    </div>
                </div>
            </div>
        </body>
        </html>
        
      • views

        • level1

          • 1.html

            <!DOCTYPE html>
            <html lang="zh_CN" xmlns:th="http://www.thymeleaf.org">
            <head>
                <meta charset="UTF-8">
                <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
                <title>Level-1-1</title>
                <link rel="stylesheet" th:href="@{/css/base.css}">
            </head>
            <body>
            
            <!--主容器-->
            <div class="ui container">
            
                <div th:replace="~{index::nav-menu}"></div>
            
                <div class="information level" style="text-align: center">
                    <h3>Level-1-1</h3>
                </div>
            
            </div>
            </body>
            </html>
            
          • 2.html

          • 3.html

        • level2

          • 1.html
          • 2.html
          • 3.html
        • level3

          • 1.html
          • 2.html
          • 3.html
        • login.html

          <!DOCTYPE html>
          <html lang="zh_CN" xmlns:th="http://www.thymeleaf.org">
          <head>
              <meta charset="UTF-8">
              <title>登录</title>
              <link rel="stylesheet" th:href="@{/css/base.css}">
          </head>
          <body>
              <div class="login center">
                  <div class="login-content w">
                      <form th:action="@{/loginCheck}" method="post">
                          <h3 class="login-title ">登录</h3>
                          <div class="login-input-frame w">
                              <label>
                                  <input type="text" class="login-input placeholder-light " name="username" placeholder="请输入用户名">
                              </label>
                          </div>
                          <div class="login-input-frame w">
                              <label>
                                  <input type="password" class="login-input placeholder-light password" name="password" placeholder="请输入密码">
                              </label>
                          </div>
                          <div class="login-button-frame w">
                              <input type="submit" class="login-button" value="登录"/>
                          </div>
                      </form>
                  </div>
              </div>
          </body>
          </html>
          

    其他地方没什么要求,就不上代码啦

  3. 新建controller包以及RouterController

    package com.example.springbootnine.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    
    
    /**
     * @author BIANG
     * @date 2021/7/24 17:30
     */
    @Controller
    public class RouterController  {
        @RequestMapping({"/","/index"})
        public String index(){
            return "index";
        }
    
        @RequestMapping("/login")
        public String toLogin(){
            return "views/login";
        }
    
        @GetMapping("/level1")
        public String level1(@RequestParam("id") int id){
            System.out.println(id);
            return "views/level1/"+id;
        }
    
        @GetMapping("/level2")
        public String level2(@RequestParam("id") int id){
            System.out.println(id);
            return "views/level2/"+id;
        }
    
        @GetMapping("/level3")
        public String level3(@RequestParam("id") int id){
            System.out.println(id);
            return "views/level3/"+id;
        }
    }
    
  4. 测试一下,可以访问成功即可

添加入Spring Security

Spring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理

记住几个类:

  • WebSecurityConfigurerAdapter:自定义Security策略
  • AuthenticationManagerBuilder:自定义认证策略
  • @EnableWebSecurity:开启WebSecurity模式
  1. 首先我们需要添加依赖

     <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    
    <dependency>
        <groupId>org.thymeleaf.extras</groupId>
        <artifactId>thymeleaf-extras-springsecurity5</artifactId>
        <version>3.0.4.RELEASE</version>
    </dependency>
    

    值得一提的是,下面的依赖是thymeleaf推出的配合springsecurity5的依赖,可以在thymeleaf中直接查找springsecurity5相关的各种属性

  2. 新建config包,新建配置类SecurityConfig

    package com.example.springbootnine.config;
    
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    
    /**
     * @author BIANG
     * @date 2021/7/25 0:26
     */
    // 开启WebSecurity模式
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            // 定制请求的授权规则
            // 首页所有人可以访问
            http.authorizeRequests().antMatchers("/").permitAll()
                    .antMatchers("/level1/**").hasRole("vip1")
                    .antMatchers("/level2/**").hasRole("vip2")
                    .antMatchers("/level3/**").hasRole("vip3");
    
            // 没有权限默认会到登录页面,需要开启登录的页面
            // /login页面
            http.formLogin()
                    .usernameParameter("username")
                    .passwordParameter("password")
                    .loginPage("/login")
                    .loginProcessingUrl("/loginCheck");
    
            //注销,开启了注销功能,跳到首页
            //http.logout().logoutSuccessUrl("/");
    
            // 防止网站工具:get,post
            http.csrf().disable();//关闭csrf功能,登录失败肯定存在的原因
    
            //开启记住我功能: cookie,默认保存两周,自定义接收前端的参数
            http.rememberMe().rememberMeParameter("remember");
        }
    
        //定义认证规则
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
            //这些数据正常应该中数据库中读
            auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                    .withUser("biang").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
                    .and()
                    .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
                    .and()
                    .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1");
        }
    }
    

    这里面的/loginCheck对应 登录表单的action,/login 对应 发送登录表单的url

  3. 运行主程序,成功按需求访问

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值