深入Apache Thrift IDL:接口定义语言详解

深入Apache Thrift IDL:接口定义语言详解

【免费下载链接】thrift Thrift是一个跨语言的远程过程调用框架,主要用于构建分布式系统。它的特点是高效、可靠、易于使用等。适用于分布式系统通信和接口定义场景。 【免费下载链接】thrift 项目地址: https://gitcode.com/GitHub_Trending/thr/thrift

Apache Thrift IDL(接口定义语言)是构建跨语言RPC服务的核心,提供了一套简洁而强大的语法结构来定义数据类型、服务和接口。本文深入解析Thrift IDL的语法结构与基本元素,包括基本数据类型、容器类型、复杂类型定义、服务接口、异常处理机制,以及命名空间与常量定义的最佳实践。通过掌握这些核心概念,开发者可以高效定义跨语言的接口契约,为构建可靠的分布式系统奠定坚实基础。

Thrift IDL语法结构与基本元素

Apache Thrift IDL(接口定义语言)是构建跨语言RPC服务的核心,它提供了一套简洁而强大的语法结构来定义数据类型、服务和接口。掌握IDL的基本元素是高效使用Thrift框架的关键基础。

基本语法结构

Thrift IDL文件采用类C语言的语法风格,主要由以下几个核心部分组成:

// 注释示例 - 单行注释
/*
 * 多行注释示例
 * 用于详细说明
 */

// 命名空间声明
namespace java com.example.thrift
namespace cpp example
namespace py example

// 包含其他thrift文件
include "shared.thrift"

// 常量定义
const i32 MAX_USERS = 1000
const string DEFAULT_NAME = "guest"

// 类型定义
typedef i64 UserId

// 枚举定义
enum UserStatus {
  ACTIVE = 1,
  INACTIVE = 2,
  SUSPENDED = 3
}

// 结构体定义
struct User {
  1: required UserId id,
  2: string username,
  3: string email,
  4: UserStatus status = UserStatus.ACTIVE,
  5: optional i32 age
}

// 服务定义
service UserService {
  User getUser(1: UserId id),
  list<User> listUsers(),
  bool updateUser(1: User user) throws (1: UserNotFoundError error)
}

// 异常定义
exception UserNotFoundError {
  1: string message,
  2: UserId requestedId
}

核心语法元素详解

1. 基本数据类型

Thrift支持丰富的基本数据类型,每种类型在不同语言中都有对应的原生类型映射:

类型关键字描述大小示例
bool布尔值1字节true, false
byte有符号字节1字节127, -128
i1616位有符号整数2字节32767, -32768
i3232位有符号整数4字节2147483647
i6464位有符号整数8字节9223372036854775807
double64位浮点数8字节3.1415926535
stringUTF-8编码字符串可变"hello world"
binary二进制数据可变[0x48, 0x65, 0x6C, 0x6C, 0x6F]
2. 容器类型

Thrift提供了三种主要的容器类型,用于处理复杂数据结构:

// 列表 - 有序元素集合
list<i32> numberList = [1, 2, 3, 4, 5]
list<string> nameList = ["Alice", "Bob", "Charlie"]

// 集合 - 无序唯一元素集合
set<string> uniqueNames = {"Alice", "Bob", "Charlie"}

// 映射 - 键值对集合
map<string, i32> ageMap = {
  "Alice": 25,
  "Bob": 30,
  "Charlie": 28
}
3. 复杂类型定义

枚举类型 (enum)

enum ErrorCode {
  SUCCESS = 0,
  NOT_FOUND = 1,
  PERMISSION_DENIED = 2,
  INTERNAL_ERROR = 3
}

enum DayOfWeek {
  MONDAY = 1,
  TUESDAY = 2,
  WEDNESDAY = 3,
  THURSDAY = 4,
  FRIDAY = 5,
  SATURDAY = 6,
  SUNDAY = 7
}

结构体 (struct)

struct Address {
  1: required string street,
  2: string city,
  3: string state,
  4: string zipCode,
  5: string country = "USA"
}

struct Person {
  1: required i32 id,
  2: required string firstName,
  3: required string lastName,
  4: i32 age,
  5: Address address,
  6: list<string> phoneNumbers,
  7: map<string, string> attributes
}

联合类型 (union)

union Result {
  1: string successMessage,
  2: ErrorCode errorCode,
  3: i32 numericResult
}

异常 (exception)

exception DatabaseError {
  1: string message,
  2: i32 errorCode,
  3: string sqlState
}

exception ValidationError {
  1: string fieldName,
  2: string validationMessage,
  3: string suggestedValue
}
4. 服务定义

服务是Thrift的核心概念,定义了可远程调用的方法接口:

service CalculatorService {
  // 简单方法
  i32 add(1: i32 a, 2: i32 b),
  
  // 复杂参数和返回值
  list<i32> processNumbers(1: list<i32> numbers),
  
  // 带异常声明的方法
  double divide(1: double numerator, 2: double denominator) 
    throws (1: DivisionByZeroError error),
  
  // 异步方法(oneway)
  oneway void logMessage(1: string message)
}

exception DivisionByZeroError {
  1: string message = "Division by zero is not allowed"
}

字段修饰符与语义

Thrift提供了灵活的字段修饰符来控制序列化和验证行为:

struct UserProfile {
  // required - 必须字段,序列化时总是包含
  1: required i32 userId,
  
  // optional - 可选字段,仅在设置时序列化
  2: optional string nickname,
  
  // 默认修饰符 - 推荐使用,提供最佳版本兼容性
  3: string email,
  4: i32 age = 0,           // 默认值
  5: bool isActive = true   // 布尔默认值
}

类型系统流程图

mermaid

最佳实践与注意事项

  1. 命名规范:使用驼峰命名法,类型名首字母大写,字段名首字母小写
  2. 版本兼容性:避免使用required修饰符,优先使用默认修饰符
  3. 默认值设置:为字段提供合理的默认值,增强接口的健壮性
  4. 文档注释:为所有定义添加详细的注释说明
  5. 模块化设计:使用include语句组织相关的类型定义
// 良好的实践示例
namespace java com.example.user.thrift

/**
 * 用户状态枚举
 * 定义系统中用户可能的状态
 */
enum UserStatus {
  ACTIVE = 1,      // 活跃状态
  INACTIVE = 2,    // 非活跃状态
  SUSPENDED = 3,   // 暂停状态
  DELETED = 4      // 已删除状态
}

/**
 * 用户基本信息结构体
 * 包含用户的核心属性信息
 */
struct UserInfo {
  1: i64 id,                    // 用户唯一标识
  2: string username,           // 用户名
  3: string email,              // 邮箱地址
  4: UserStatus status = UserStatus.ACTIVE,  // 用户状态,默认活跃
  5: optional string avatarUrl, // 头像URL,可选
  6: i64 createTime             // 创建时间戳
}

/**
 * 用户服务接口
 * 提供用户相关的CRUD操作
 */
service UserService {
  /**
   * 根据ID获取用户信息
   * @param id 用户ID
   * @return 用户信息对象
   * @throws UserNotFoundError 当用户不存在时抛出
   */
  UserInfo getUserById(1: i64 id) throws (1: UserNotFoundError error),
  
  /**
   * 创建新用户
   * @param userInfo 用户信息
   * @return 创建后的用户ID
   */
  i64 createUser(1: UserInfo userInfo),
  
  /**
   * 批量获取用户信息
   * @param ids 用户ID列表
   * @return 用户信息映射表
   */
  map<i64, UserInfo> batchGetUsers(1: list<i64> ids)
}

exception UserNotFoundError {
  1: string message = "User not found",
  2: i64 requestedId
}

通过掌握Thrift IDL的这些基本语法结构和元素,开发者可以高效地定义跨语言的接口契约,为构建可靠的分布式系统奠定坚实基础。IDL的简洁性和表达力使得它成为现代微服务架构中接口定义的首选工具之一。

数据类型定义:基本类型与复杂类型

Apache Thrift IDL(接口定义语言)提供了丰富的数据类型系统,支持从简单的标量类型到复杂的容器类型,为跨语言RPC通信提供了强大的数据建模能力。Thrift的数据类型设计既考虑了类型安全性,又兼顾了跨语言兼容性和序列化效率。

基本数据类型(Base Types)

Thrift支持9种基本数据类型,这些类型在所有支持的编程语言中都有对应的原生类型表示:

类型关键字描述大小对应语言示例
bool布尔值1字节Java: boolean, C++: bool, Python: bool
byte有符号8位整数1字节Java: byte, C++: int8_t, Python: int
i8有符号8位整数1字节Java: byte, C++: int8_t, Python: int
i16有符号16位整数2字节Java: short, C++: int16_t, Python: int
i32有符号32位整数4字节Java: int, C++: int32_t, Python: int
i64有符号64位整数8字节Java: long, C++: int64_t, Python: long
double64位浮点数8字节Java: double, C++: double, Python: float
stringUTF-8编码字符串变长Java: String, C++: std::string, Python: str
binary二进制数据(字节数组)变长Java: byte[], C++: std::string, Python: bytes

注意bytei8是同义词,但推荐使用i8以获得更好的可读性和一致性。

复杂数据类型(Complex Types)

Thrift提供了三种容器类型,用于构建复杂的数据结构:

1. 列表(List)

列表表示有序的元素集合,支持相同类型的元素。

// 定义整数列表
list<i32> numberList

// 定义字符串列表  
list<string> nameList

// 在结构体中使用列表
struct UserProfile {
  1: i32 userId,
  2: string username,
  3: list<string> tags,        // 用户标签列表
  4: list<i64> loginTimestamps // 登录时间戳列表
}
2. 集合(Set)

集合表示无序的唯一元素集合,不允许重复值。

// 定义整数集合
set<i32> uniqueNumbers

// 定义字符串集合
set<string> uniqueNames

// 在服务中使用集合
struct SocialNetwork {
  1: i64 networkId,
  2: set<i64> memberIds,      // 成员ID集合(去重)
  3: set<string> hashtags     // 话题标签集合
}
3. 映射(Map)

映射表示键值对集合,键必须是基本类型,值可以是任意类型。

// 简单映射定义
map<string, string> stringMap
map<i32, double> scoreMap

// 嵌套映射示例
map<string, list<i32>> userScores  // 用户到分数列表的映射
map<i64, map<string, i32>> complexData

// 实际应用示例
struct Configuration {
  1: map<string, string> settings,      // 配置键值对
  2: map<string, list<string>> options, // 选项到值列表的映射
  3: map<i32, set<string>> categories   // 分类ID到标签集合的映射
}

类型定义的最佳实践

使用typedef增强可读性
// 为常用类型创建别名
typedef i64 UserId
typedef i64 Timestamp
typedef string Email
typedef list<string> StringList
typedef map<string, string> StringMap

// 使用typedef后的结构体定义
struct User {
  1: UserId id,
  2: Email email,
  3: Timestamp createdAt,
  4: StringList permissions,
  5: StringMap metadata
}
复杂嵌套类型示例
// 多层嵌套的复杂类型
typedef map<i32, list<set<string>>> ComplexNestedType

struct AnalyticsData {
  1: map<string, list<map<i32, double>>> metrics,      // 指标数据
  2: map<i64, set<list<string>>> userBehaviorPatterns, // 用户行为模式
  3: list<map<string, map<i32, bool>>> featureFlags    // 功能标志
}

数据类型的内存布局示意图

mermaid

跨语言类型映射表

Thrift类型JavaC++PythonGoJavaScript
boolbooleanboolboolboolboolean
i8/bytebyteint8_tintint8number
i16shortint16_tintint16number
i32intint32_tintint32number
i64longint64_tlongint64number
doubledoubledoublefloatfloat64number
stringStringstd::stringstrstringstring
binarybyte[]std::stringbytes[]byteBuffer
list<T>List<T>std::vector<T>list[]TArray
set<T>Set<T>std::set<T>setmap[T]struct{}Set
map<K,V>Map<K,V>std::map<K,V>dictmap[K]VMap

实际应用案例

电商系统数据类型定义
// 基本类型定义
typedef i64 ProductId
typedef i64 UserId
typedef i64 OrderId
typedef double Price
typedef i32 Quantity

// 复杂类型定义
typedef map<ProductId, Quantity> CartItems
typedef list<OrderId> OrderHistory
typedef set<string> ProductCategories

// 数据结构定义
struct Product {
  1: ProductId id,
  2: string name,
  3: Price price,
  4: ProductCategories categories,
  5: map<string, string> attributes
}

struct ShoppingCart {
  1: UserId userId,
  2: CartItems items,
  3: Price totalPrice,
  4: Timestamp lastUpdated
}

struct UserOrder {
  1: OrderId orderId,
  2: UserId userId,
  3: list<Product> products,
  4: map<ProductId, Quantity> quantities,
  5: Price totalAmount,
  6: Timestamp orderDate
}

Thrift的数据类型系统通过这种层次化的设计,既保证了类型的丰富性,又确保了跨语言的一致性,使得开发者可以专注于业务逻辑而不是底层的数据序列化细节。

服务接口定义与异常处理机制

Apache Thrift的服务接口定义是其核心功能之一,它允许开发者定义跨语言的远程过程调用(RPC)接口。服务定义不仅包括方法签名,还支持复杂的异常处理机制,确保分布式系统的健壮性和可靠性。

服务定义基础语法

Thrift服务使用service关键字定义,可以包含多个方法。每个方法都有明确的返回类型、参数列表和可选的异常声明。以下是基本的服务定义语法:

service ServiceName {
    ReturnType methodName(1:ParamType1 param1, 2:ParamType2 param2) 
        throws (1:ExceptionType1 ex1, 2:ExceptionType2 ex2)
}
服务继承机制

Thrift支持服务继承,允许一个服务扩展另一个服务的功能。这在构建分层服务架构时非常有用:

service BaseService {
    void commonMethod(1:string input)
}

service ExtendedService extends BaseService {
    i32 specializedMethod(1:i32 param)
}

异常定义与处理

异常在Thrift中使用exception关键字定义,语法与结构体类似,但专门用于错误处理场景:

exception CustomException {
    1: required i32 errorCode,
    2: optional string errorMessage,
    3: map<string, string> context
}
异常类型系统

Thrift异常支持丰富的类型系统,包括:

异常特性描述示例
必需字段必须提供的错误信息1: required string message
可选字段可选的上下文信息2: optional i32 retryAfter
复杂类型支持map、list等复杂类型3: map<string, string> metadata

方法异常声明

在服务方法中,可以使用throws关键字声明该方法可能抛出的异常类型:

service DataService {
    DataResponse getData(1:DataRequest request) 
        throws (1:DataNotFoundException notFound, 
                2:PermissionDeniedException denied,
                3:SystemOverloadException overload)
}
异常处理最佳实践
// 定义错误代码枚举
enum ErrorCode {
    SUCCESS = 0,
    NOT_FOUND = 1,
    PERMISSION_DENIED = 2,
    SYSTEM_ERROR = 3
}

// 定义基础异常
exception BaseException {
    1: required ErrorCode code,
    2: required string message,
    3: optional i64 timestamp
}

// 定义具体业务异常
exception BusinessException extends BaseException {
    4: optional string businessContext,
    5: optional list<string> suggestions
}

service BusinessService {
    BusinessResult process(1:BusinessRequest request)
        throws (1:BusinessException bizError)
}

Oneway方法支持

Thrift支持oneway方法,这些方法不返回任何响应,适用于异步操作场景:

service AsyncService {
    // 普通方法,等待响应
    SyncResponse syncCall(1:SyncRequest request)
    
    // Oneway方法,不等待响应
    oneway void asyncCall(1:AsyncRequest request)
}
Oneway方法使用场景

mermaid

复杂异常处理模式

在实际应用中,异常处理往往需要更复杂的模式:

// 分层异常体系
exception ApplicationException {
    1: required string requestId,
    2: required i32 statusCode,
    3: required string errorType
}

exception ValidationException extends ApplicationException {
    4: map<string, string> fieldErrors
}

exception BusinessRuleException extends ApplicationException {
    4: string ruleName,
    5: string ruleDescription
}

service ComplexService {
    ComplexResult execute(1:ComplexRequest request)
        throws (1:ValidationException validationError,
                2:BusinessRuleException businessError,
                3:ApplicationException systemError)
}

异常传播与处理策略

Thrift的异常处理机制支持多种传播策略:

策略类型描述适用场景
声明式异常在接口中明确声明预期的业务异常
运行时异常未声明的系统异常意外的系统错误
传输异常网络或序列化错误通信故障
异常处理序列图

mermaid

服务版本兼容性

异常定义也需要考虑版本兼容性:

// 版本1的异常定义
exception OldException {
    1: string message
}

// 版本2扩展异常定义
exception NewException extends OldException {
    2: i32 errorCode,
    3: optional string stackTrace
}

service VersionedService {
    // 向后兼容的方法签名
    Result compatibleMethod(1:Request request)
        throws (1:OldException error)  // 兼容旧客户端
}

实际应用示例

以下是一个完整的服务定义示例,展示了异常处理的最佳实践:

namespace java com.example.service

enum UserErrorCode {
    USER_NOT_FOUND = 1001,
    PERMISSION_DENIED = 1002,
    INVALID_CREDENTIALS = 1003
}

enum SystemErrorCode {
    DATABASE_ERROR = 2001,
    NETWORK_ERROR = 2002,
    CONFIGURATION_ERROR = 2003
}

exception UserException {
    1: required UserErrorCode code,
    2: required string message,
    3: optional string userId,
    4: optional i64 timestamp = ${currentTimeMillis}
}

exception SystemException {
    1: required SystemErrorCode code,
    2: required string message,
    3: optional string component,
    4: optional string stackTrace
}

struct User {
    1: required string id,
    2: required string username,
    3: optional string email,
    4: optional i32 age
}

service UserService {
    User getUser(1:string userId) 
        throws (1:UserException userError, 
                2:SystemException systemError),
    
    void updateUser(1:User user) 
        throws (1:UserException userError,
                2:SystemException systemError),
    
    oneway void auditUserAction(1:string userId, 2:string action)
}

这种异常处理机制确保了分布式系统的健壮性,同时提供了清晰的错误信息和处理策略。

命名空间与常量定义的最佳实践

在Apache Thrift IDL中,命名空间和常量定义是构建可维护、可扩展分布式系统的关键要素。合理的命名空间组织能够避免命名冲突,而精心设计的常量定义则能提高代码的可读性和可维护性。本节将深入探讨这两个核心概念的最佳实践。

命名空间的组织策略

命名空间在Thrift IDL中通过namespace关键字定义,它为生成的代码提供了语言特定的包/模块结构。合理的命名空间设计应该遵循以下原则:

多语言命名空间映射

Thrift支持为不同目标语言指定不同的命名空间,这是其跨语言能力的重要体现:

namespace cl tutorial
namespace cpp tutorial  
namespace d tutorial
namespace dart tutorial
namespace java tutorial
namespace php tutorial
namespace perl tutorial
namespace haxe tutorial
namespace netstd tutorial

这种设计允许每个语言使用最适合其生态系统的包命名约定,例如Java使用反向域名,而C++使用下划线分隔。

命名空间层次结构设计

对于大型项目,建议采用分层的命名空间结构:

mermaid

版本化命名空间

在API演进过程中,版本化命名空间可以有效管理兼容性:

namespace java com.company.api.v1
namespace java com.company.api.v2

// 或者使用包内版本
namespace java com.company.api
const string API_VERSION = "2.0.0"

常量定义的最佳实践

常量在Thrift中使用const关键字定义,支持基本类型和复杂数据结构。合理的常量设计能够显著提升代码质量。

基本类型常量定义

// 数值常量
const i32 MAX_RETRY_COUNT = 3
const i32 DEFAULT_TIMEOUT_MS = 5000
const double PI = 3.1415926535
const i32 HEX_FLAG = 0x0001F
const i32 NEGATIVE_HEX = -0x0001F

// 科学计数法支持
const double LARGE_NUMBER = 1e10
const double NEGATIVE_LARGE = -1e10

// 字符串和二进制常量
const string DEFAULT_ENCODING = "UTF-8"
const binary MAGIC_BYTES = "\x00\x01\x02\x03"

复杂数据结构常量

Thrift支持定义复杂的常量数据结构,这在配置管理和默认值设置中非常有用:

// 映射常量
const map<string, string> ERROR_CODES = {
  'NOT_FOUND': '资源未找到',
  'UNAUTHORIZED': '未授权访问', 
  'INTERNAL_ERROR': '内部服务器错误'
}

const map<i32, i32> STATUS_MAPPING = { 
  200: 1, 
  404: 2, 
  500: 3 
}

// 列表和集合常量
const list<i32> PRIME_NUMBERS = [2, 3, 5, 7, 11, 13]
const set<string> SUPPORTED_LANGUAGES = ['zh-CN', 'en-US', 'ja-JP']

// 嵌套数据结构
const map<i32, map<i32, i32>> CONFIG_LAYERS = {
  1: { 100: 1000, 200: 2000 },
  2: { 300: 3000, 400: 4000 }
}

枚举和自定义类型常量

enum ErrorCode {
  SUCCESS = 0,
  NOT_FOUND = 1,
  UNAUTHORIZED = 2,
  INTERNAL_ERROR = 3
}

typedef i32 UserId
typedef string Email

const ErrorCode DEFAULT_ERROR = ErrorCode.INTERNAL_ERROR
const UserId SYSTEM_USER_ID = 0
const Email ADMIN_EMAIL = "admin@example.com"

UUID和GUID常量

Thrift支持UUID类型的常量定义,支持带花括号和不带花括号两种格式:

const uuid DEFAULT_UUID = '00000000-4444-CCCC-ffff-0123456789ab'
const uuid GUID_CONSTANT = '{00112233-4455-6677-8899-aaBBccDDeeFF}'

typedef uuid TransactionId
const TransactionId DEFAULT_TX_ID = '550e8400-e29b-41d4-a716-446655440000'

常量引用和表达式

Thrift允许常量引用其他常量,支持基本的表达式计算:

const i32 BASE_TIMEOUT = 1000
const i32 CONNECTION_TIMEOUT = BASE_TIMEOUT * 2
const i32 READ_TIMEOUT = BASE_TIMEOUT * 3

const double TAX_RATE = 0.08
const double DISCOUNT_RATE = 0.1
const double FINAL_RATE = 1 - TAX_RATE - DISCOUNT_RATE

结构体常量

可以定义结构体类型的常量,用于提供默认配置或样板数据:

struct ConnectionConfig {
  1: string host = "localhost"
  2: i32 port = 8080
  3: i32 timeout = 5000
  4: bool ssl = false
}

const ConnectionConfig DEFAULT_CONFIG = {
  'host': 'api.example.com',
  'port': 443,
  'timeout': 10000,
  'ssl': true
}

跨文件常量引用

Thrift支持通过include机制引用其他文件中定义的常量:

include "common_constants.thrift"

const i32 MY_CONSTANT = common_constants.BASE_VALUE + 100
const string APP_NAME = common_constants.APP_PREFIX + "-service"

命名规范和文档

良好的命名规范和文档是常量定义的重要组成部分:

/**
 * 系统级配置常量
 * 这些常量在整个应用程序中共享使用
 * 修改时需要谨慎,可能影响多个模块
 */
namespace java com.company.core.constants

// 使用大写蛇形命名法
const i32 MAX_CONCURRENT_REQUESTS = 100
const i32 DEFAULT_CACHE_SIZE = 1024

/**
 * API版本常量
 * 用于控制接口版本和兼容性
 */
const string API_VERSION = "v2.1.0"
const i32 API_VERSION_CODE = 20100

性能考虑和最佳实践

  1. 常量初始化:复杂常量在编译时初始化,避免运行时计算开销
  2. 内存占用:大型常量数据结构可能增加内存使用,需要权衡
  3. 版本兼容性:常量修改可能破坏向后兼容性,需要谨慎处理
  4. 类型安全:充分利用Thrift的类型系统确保常量类型安全

通过遵循这些最佳实践,可以构建出结构清晰、易于维护且高性能的Thrift IDL定义,为分布式系统开发奠定坚实基础。

总结

Apache Thrift IDL通过其丰富的数据类型系统、强大的服务定义能力和灵活的异常处理机制,为构建跨语言RPC服务提供了完整的解决方案。合理的命名空间组织和常量定义策略进一步增强了代码的可维护性和可扩展性。遵循本文介绍的最佳实践,开发者可以创建出结构清晰、性能优异且易于维护的分布式系统接口,充分发挥Thrift在微服务架构中的价值。

【免费下载链接】thrift Thrift是一个跨语言的远程过程调用框架,主要用于构建分布式系统。它的特点是高效、可靠、易于使用等。适用于分布式系统通信和接口定义场景。 【免费下载链接】thrift 项目地址: https://gitcode.com/GitHub_Trending/thr/thrift

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值