答题平台面试题(TG自用)

1. 请介绍整个系统后端的架构设计,有哪些模块以及各模块之间的关系

整个系统的后端分为:
·用户模块:提供登录、用户增删改查等管理功能·应用模块:提供应用增删改查、管理、分享等功能·题目模块:提供题目增删改查、管理等功能
·评分模块:提供评分规则定制、答案评分功能
·回答模块:提供回答记录查看、管理功能
·Al模块:提供Al创建题目、Al智能评分功能
·统计分析模块:提供热门应用排行分析、应用回答分布统计分析功能
各模块之间的关系如下:
·用户登录后,使用应用模块创建应用或者获取应用信息·创建应用需要调用题目模块生成题目或利用AI模块生成题目
·用户在答题页面根据应用appld 调用题目模块据得到题目列表,提交答案后调用评分模块,评分模块根据应用的类别执行不同的评分策略,比如自定义评分或调用Al模块评分
·统计分析模块调用应用模块或回答模块得到回答记录,通过自定义SQL统计分析记录数据

本项目一共有三类用户角色,分别是:应用创建者、应用使用者、管理员。
应用创建者可以创建自定义应用,依次填写应用基本信息(名称描述等)、题目和选项、以及不同选项对应的评分结果。支持选择得分类和测评类这2种应用类型,支持选择自定义评分或Al评分这﹖种评分算法。
管理者负责审核应用创建者申请的自定义应用,含上下架应用和分析应用等管理功能。此外,管理员也可以针对本站的各类数据进行管理,比如用户的回答结果。
应用使用者可以在首页搜索和挑选应用进行答题,提交答案后得到评分结果。
注意,应用使用者也可以创建应用,应用创建者也可以参与应用答题,二者的身份不做明确的区分。

2.你在项目中是如何设计库表的?可以从字段、索引、关联等方面回答

根据业务设计用户/应用/题目/评分结果/用户答题表。其中题目表采用JSON存储复杂的嵌套题目和选项,便于维护扩展,并通过给题目表添加appld索引提升检索性能。(感兴趣的同学可以自己测试一下性能的提高比例)
项目中按照业务功能分析,一共设计了用户、应用、题目、评分结果、用户答题五张表。
各表字段统一设置了createTime、updateTime、isDelete必备非业务属性字段,便于维护。
根据查询场景,部分表字段还进行了冗余,例如用户答题表需要展示评分结果的名称和描述,因此冗余了评分结果表的resultName和resultDesc等字段,避免用户查询答题记录时,还需要关联查询评分记录表,提升查询性能。
根据查询场景也做了一些索引设计,例如首页提供通过名称搜索应用功能,因此应用表的 appName建立了索引。
再比如题目表的获取场景经常需要通过应用appld得到题目,因此题目表的appld 字段也建立了索引。

3.为什么使用策略模式来封装不同的应用评分算法?它有哪些好处?具体如何实现?

因为使用不同评分算法进行判题的业务场景天然适配策略模式。
不同算法对应的就是不同策略,所以使用策略模式来封装了评分算法,便于维护和扩展
具体实现方式:
1)定义ScoringStrategy接口,约束了评分的入参和出参。
2)定义ScoringStrategyConfig注解,一共有appType和
scoringStrategy两个属性标识了策略对应的应用属性和评分类型。
3)每种评分算法都实现ScoringStrategy接口,且标注对应的ScoringStrategyConfig 注解。
4)定义ScoringStrategyExecutor方法作为驱动类,内部利用Spring注入了ScoringStrategy '实现类列表,得到所有评分算法。定义了execute方法,遍历评分算法列表,通过反射获取策略实现类注解上的属性即可筛选出对应的评分算法。

4.你是怎么根据应用来选取要执行的评分算法的?是用if else 吗

核心实现是注解。
一开始我想的是if else,但是根据开闭原则,后面要添加新的评分算法需要修改if else 的代码,因此我想到了Spring 自动注入+自定义注解来实现自动选取评分算法。
具体来说,所有评分算法都实现了同一个策略接口,Spring可以自动注入这些实现类,根据注解上定义的 appType和
socringStrategy,就可以从这些实现类中筛选出要执行的评分算法。

5.你的平台支持哪些类型的应用?支持哪些评分算法

平台现在支持得分类应用和测评类两种应用类型。
一共支持3种评分算法,分别是:
1.自定义计算测评类策略
⒉自定义计算得分类策略
3.AI分析测评类策略

6.如何实现测评类应用的评分算法

1)测评类每道题对应的答案都设置了属性,以MBTI为例,内向的答案对应工属性,外向的答案对应E属性。
2)根据用户选择的答案计算出对应属性总数量,例如Ⅰ为10个、E为2个、J为10个、Р为2个,将其构建成一个属性得分map, key为属性值、value为累计数量。
示例Map 结构如下:
{
I: 10,E: 2,: 10,P: 2
}
3)评分结果表的结果也设置了对应的属性,例如ISTJ包含了I、S、 T、J这四个属性,ESTJ则包含了E、S、T、J。对于每种评分结果,
遍历属性集合,从上述Map 结构中获取到属性对应的评分。
比如ISTJ评分结果对应的得分为:10(I)+ 0(S)+ 0(T)+10(J)= 20分
4)遍历所有评分结果,使用上述算法计算得分,最终获取到得分最高的评分结果。通过定义Map的方式,拿空间换时间,减少了算法复杂度,提高了算法的执行效率。

7.你是怎么实现AI生成题目功能的

1)Al生成题目基础功能实现:通过编写Prompt 实现让Al返回JSON格式的题目内容。并通过系统预设、少样本学习、任务拆解等技巧优化Prompt、通过Al开放平台界面调试 Prompt,提高Al生成内容的完善度和准确度。
2)流式优化:由于Al生成题目较慢,选用ChatGLMAI的流式API并通过SSE 实时推送单道题目给前端,提高用户体验。
3)具体实现:在AI平台没有完整返回单道题目对应的字符时,不应该返回给前端。为了拼接单道题目,我基于RxJava的操作符链式调用处理Al异步数据流,先通过map 获取并处理字符串 (过滤特殊字符如’\n’) 、 filter过滤空值、flatMap 映射字符串为单个字符,再通过括号平衡算法准确拼接出单道题目,使得逻辑简单清晰。

8.你是如何封装通用AI模块的?提供了哪些方法

1)基于智谱Al SDK 实现Al调用大模型的能力
2)通过创建配置类,项目启动时自动读取配置文件中的秘钥来初始化Al客户端实例
3)编写Manager Bean来调用AI客户端,提供了多种对Al的请求封装方法,并提供了统一的异常处理能力。
比如随机性低的同步调用Al、随机性高的流式调用、简化消息传参的方法等。

9.如何流式调用AI并实时获取到AI返回的内容

ChatGLM的 Al SDK提供了流式调用的能力,我自主封装了AlManager简化Al流式调用的传参,返回值是 Flowable响应式对象。我利用RxJava框架来处理Al实时返回的Stream流数据,将Al返回的字符串结果拼接成"一道一道”题目,并通过SSE 实时推送给前端。

10.你是如何实现“一道一道”流式生成题目的,用到了什么算法?

首先流式调用AI接口,利用RxJava的操作符链式调用并处理Al实时返回的异步数据流。先通过map获取并处理字符串(过滤特殊字符如’\n’) 、 filter过滤空值、flatMap映射字符串为单个字符,再通过括号平衡算法准确拼接出单道题目,使得逻辑简单清晰。
括号平衡算法的解释:因为每道题目是JSON结构,左括号数一定等于右括号数。所以可以遍历当前字符串,用一个计数器统计括号数量,出现一次左括号则计数加—,出现一次右括号则计数减一,当计数为零,说明完整的一道题已返回完毕,此时通过SSE返回给前端。

11.什么是RxJava?为什么使用RxJava来处理流?它有什么优点?

RxJava是一个基于事件驱动的、利用可观测序列来实现异步编程的类库,是响应式编程在Java语言上的实现。
RxJava可以使得异步编程更加简洁、灵活和易于维护:1))它提供了响应式编程模型,使得代码更具可读性。比如doOnNext、doOnError。
181392403
2)提供了丰富的操作符,简化异步操作、事件处理、数据转化等。比如map、filter、flatMap。
3)简化线程管理,可以很容易地在不同线程中进行数据流的处理。比如observeOn、subscribeOn。
它的一个核心应用场景就是U场景,像Android开发都会用到RxJava。UI场景天然涉及到响应和事件这两点,比如我们在手机app上某个按钮,对应app就会弹出某个界面,点击按钮其实就是一个事件,那么弹出界面就是对应的响应。

12.什么是SSE技术?它有什么优点和不足?适用于哪些场景?

SSE服务器发送事件(Server-Sent Events)是一种用于从服务器到客户端的单向、实时数据传输技术,基于HTTP协议实现。
它有几个重要的特点:
1.单向通信:SSE只支持服务器向客户端的单向通信,客户端不能向服务器发送数据。
2文本格式: SSE使用纯文本格式传输数据,使用HTTP响应的text/event-stream MIME类型。
3.保持连接:SSE通过保持一个持久的 HTTP连接,实现服务器向客户端推送更新,而不需要客户端频繁轮询。
4.自动重连:如果连接中断,浏览器会自动尝试重新连接,确保数据流的连续性。
它的优点:
1.简单易用:SSE在实现上相对简单,无需复杂的配置和建立连接过程。
2.实时性:可以实现服务器向客户端的实时数据推送,使得客户端能够及时获得更新。
3.标准化:基于标准的HTTP协议,因此具有广泛的浏览器支持。4.无需握手机制:与其他技术(如WebSocket)相比,SSE不需要握手机制,可以降低连接的延迟。
不足:
1.单向通信: SSE是服务器向客户端的单向通信模式,客户端无法向服务器发送数据。
⒉连接维持:SSE 需要保持持续连接,可能会造成服务器资源占用。
适用场景:
1.实时更新:股票价格、体育比赛比分、新闻更新等需要实时推送的应用。
2日志监控:实时监控服务器日志或应用状态。3.通知系统:向客户端推送系统通知或消息。4.Al对话:常见AI模型页面对话。

13.为什么用SSE技术将生成的题目实时返回给前端?有没有其他实现方案?

因为SSE具有如下优点:
1.简单易用:SSE在实现上相对简单,无需复杂的配置和建立连接过程。
2.实时性:可以实现服务器向客户端的实时数据推送,使得客户端能够及时获得更新。
3.标准化:基于标准的HTTP协议,因此具有广泛的浏览器支持。
4.无需握手机制:与其他技术(如WebSocket)相H比KSF不主要握手机制,可以降低连接的延迟。
其他实现方案:
1)轮询(前端主动去要数据)
前端间隔一定时间就调用后端提供的结果接口,比如200ms 一次,后端处理一些结果就累加放置在缓存中。
2) WebSocket
全双工协议,前端能实时推送数据给后端(或者从后端缓存拿数据),后端也可以实时推送数据给前端。

14.你是怎么实现AI评分功能的?

  1. ChatGLM的Al SDK提供了流式调用的能力,我自主封装了AlManager简化AI调用的传参和编码。
    2)获取需要提供给Al的参数列表,包括应用信息、题目信息和用户答案。但是需要注意,题目数量可能会很多,如果将完整的题目结构(包括选项列表)输入给Al,可能会超出最大token限制,所以可以进行优化,只保留用户选项对应的答案。
    示例结构如下:
    {
    “appName” : “MBTI性格测试”,“appDesc”:“测试你的MBTI性格",“question”: [
    “title”:“你喜欢和人交流”,“answer”:“喜欢”
    }
    ]}
    3)利用上述参数来编写Prompt,让Al返回JSON格式的评分结果名称和详细描述。
    4)调用Al并保存评分结果到数据库中,返回给前端用户即可。

15.你为什么要使用缓存来优化AI评分功能?这么做有什么好处?

之前的AI评分功能存在2个问题:
1.AI调用需要费用,如果用户对同样的题目做出同样的选择,理论会得到一样的解答,不需要每次都询问Al,存在资源的浪费。
2.Al评分的响应时间较长,效率有待提升。
考虑到“减少响应时长"和“数据复用”,我选择缓存技术来优化Al评分功能。由于项目前期采用单机部署,所以选用了成本相对较低的Caffeine本地缓存来缓存用户答案Hash对应的Al评分结果,提高评分性能(10s到 5ms)的同时大幅节约成本;并通过Redisson分布式锁解决缓存击穿问题。

16.你了解哪些缓存技术?在项目中又是如何运用缓存的?比如怎么设计缓存?

有哪些缓存技术?
缓存技术在项目中一般可以分为两种:本地缓存和分布式缓存。
本地缓存可以使用哈希表、Ehcache、Caffeine等实现。
分布式缓存可以使用Redis、Memcached等实现。
项目中如何运用缓存?怎么设计?
对于我的项目,由于不考虑分布式或扩容、且不要求持久化、不用保证多台机器缓存间的一致性,所以选择成本较低的本地缓存Caffeine来实现。
1)缓存key 设计
回归到需求"用户对同样的题目做出同样的选择,理论会得到一样的解答”
所以可以将应用id和用户的答案列表作为key。
但答案列表可能很长,可以利用哈希算法(md5)来压缩key,节省5空间。
注意,如果是分布式缓存,还需要在key开头拼接业务前缀。此处我们可以单独为每个业务创建本地缓存,相互隔离,所以key 可以简单一些。
2)缓存value设计
缓存Al回答的结果,为了可读性可以存JSON结构,为了压缩空间可以存二进制等其他结构。
3)缓存过期时间设置
必须设置缓存过期时间!假设有20道题目,那么不同选择累计总次数一共是2的20次方,100多万。
过期时间根据实际业务场景和缓存空间的大小、数据的一致性的要求设置,合适即可,此处设置为1天。
4)业务流程
1.在AI回答前,哈希处理用户答题选择,得到摘要,拼接缓存key。
⒉.通过摘要查找缓存,若命中则直接返回答题结果。3.若缓存中未找到,则请求Al回答。
4.正确解析AI返回的JSON后,将其放置在缓存中。
5)缓存相关问题的解决
还需要注意缓存过期后的处理,防止产生缓存击穿、雪崩等问题。我在项目中通过Redisson分布式锁解决了缓存击穿。

17.什么是缓存击穿?你如何解决缓存击穿问题?

什么是缓存击穿?
缓存击穿是指在高并发的情况下,针对一个不存在于缓存中但是访问量很高的 key,每次请求都要去数据库查询,导致数据库压力过大,性能下降的情况。当某个热点key的缓存过期时,大量请求同时访问这个key,由于缓存为空,导致所有请求直接访问数据库,引起数据库压力骤增。
对于本项目,多个用户同时提交答案交给AI测评时,如果缓存过期,那么会有大量同时调用AI接口的并发请求,导致被AI服务器限制。
如何解决缓存击穿问题?
常见解决缓存击穿思路:
1.设置热点缓存数据永不过期,并且提前预热。
2.缓存不存在时,通过加锁让操作串行执行,并设置缓存。
3.定时更新热点数据的缓存。
此处我选择第二种方案——加锁。如果服务部署在多个机器上,就必须要使用分布式锁。
分布式锁的实现细节很多,所以我直接使用Redisson 实现分布式锁。Redisson为 Redis提供了多种数据结构的支持,并提供了线程安全的操作,简化了在Java 中使用Redis的复杂度。

18.什么是Redisson?你在项目中如何使用Redisson 实现了分布式锁?

Redisson是一个基于Redis的Java调用类库,提供了丰富的分布式对象和服务,其中包括分布式锁、分布式集合、分布式对象等功能。主要为了简化Java开发人员在分布式环境中使用Redis 的复杂性,提供了易于使用的API以及各种分布式实现。

我在项目中实现分布式锁的方法:
1.编写配置类,从配置文件中读取Redis配置并初始化 Redisson客户端Bean。
⒉.通过Redisson客户端的getLock(“myLock”)方法获取分布式锁
3.通过tryLock方法尝试获得锁,并设置抢锁等待时间(3秒)和超时释放时间(15秒)。
4.利用try. …finally…保证执行操作结束后,锁会被当前线程释放。

19.什么是分布式锁?使用分布式锁时有哪些注意事项?

分布式锁是在分布式系统中用于控制对共享资源或临界区的访问的一种锁机制。它可以确保在多个节点或实例上同一时间只有一个进程能够获取锁,从而保证数据的一致性和避免并发访问下的数据竞争和冲突。
注意事项如下:
1.需要合理设置超时释放时间,避免造成死锁。2.业务处理完毕后及时释放锁,防止业务阻塞。
3.必须由当前线程释放锁,不能释放其他线程加的锁,防止业务执行受到影响。
4.合理设置抢锁等待时间,避免长时间无效等待。
5.可以利用看门狗机制实现锁的续期,防止由于业务处理时间大于锁超时释放时间,导致一把锁被多个线程拥有,从而出现错误。

20.什么是分库分表?为什么你要在项目中使用分库分表?

什么是分库分表?
分库分表是指将一个数据库按照一定规则分成多个库((分库),或将一个表按照一定规则分成多个表(分表)的数据库架构设计方法。通常在数据量大、访问频繁的业务系统中会采用分库分表的方式来提高数据库的性能、可扩展性、可用性。
为什么要在项目中使用分库分表?
如果我们的平台发展迅速,用户量激增,用户提交的答案数也会越来越多。从数据库层面去思考,用户答题记录表可能会比较庞大,查询的性能也会下降。因为数据越多,底层存储的B+树就越高,树越高则查询I/O的次数就越多,那么性能也就越差。
因此我选择应用的appld 作为分片键,运用取模算法,对用户答题记录表进行分表,分为了2张表。
其实这里也是出于学习的目的,实践了下Sharding JDBC框架。

21.你为什么使用Sharding JDBC技术实现分库分表?它的大致原理是什么?

因为Sharding JDBC是Apache旗下的开源项目,生态好、社区活跃、简单易用、容易上手。
它的大致原理可以用几个字总结:改写SQL。
比如我们想根据appld来将对应的用户答题记录表进行分表。将appld %2等于0的应用的所有用户的答题记录都划分到
user_answer_0,等于1的应用的所有用户的答题记录都划分到user_answer_1.
按照我们正常的想法处理逻辑就是:
if ( appId % 2 == 0){
userAnsweroService.save(userAnswer);
}else {
userAnswer1Service.save(userAnswer);
而用了Sharding-JDBC后,我们只要写好配置,Sharding-JDBC就会根据配置,执行我们上面的逻辑,在业务代码上我们就可以透明化分库分表的存在,减少了很多重复逻辑!
它会解析SQL,根据我们指定的分片键,按照我们设置的分片策略来计算得到对应的路由分片(数据库或者表),最终改写SQL后进行SQL的执行。

22.你是如何使用Sharding JDBC实现分库分表的?具体怎么分表、用了什么分表算法?

Sharding JDBC开箱即用,只要在配置文件中填写好数据源信息、分片算法、分表依赖的列等,就能自动实现业务透明的分库分表。2
本项目中,我对数据增长较快的用户答题记录表进行分表,选择应用appld 作为分表键,采用取模分片算法,将用户答题记录表拆分为2个表(user_answer_0和user_answer_1),提高了单表查询性能和可扩展性。(感兴趣的同学可以自己测试一下百万行数据的性能提高比例)

23.什么是幂等设计?你项目的哪个功能使用了幂等设计

幂等设计在编程场景指的是:使用相同参数多次调用同一接口,产生的结果或影响和单次调用是一致的。
为防止用户多次提交重复答案,基于雪花算法为每次答题分配唯一id,并通过数据库主键实现幂等设计,避免了重复的脏数据。

24.有哪些实现幂等设计的方法?你在项目中具体又是怎么实现的?

有4种常用的幂等设计方法:
1.利用数据库唯一索引的一致性保证幂等性⒉.利用乐观锁在某些场景下也能实现幂等性
3.—些天然的幂等操作,比如delete操作(因为删除一次和多次都是一样的)
4.利用分布式锁防止并发,通过逻辑判断可以实现幂等性
在项目中为防止用户多次提交重复答案,后端基于雪花算法为每次答题分配唯一id,并且在用户进入答题页时返回给前端。用户提交答案时会携带该id,利用数据库主键可以防止重复id数据的插入,如果id重复,会抛出 DuplicateKeyException异常,直接忽略即可。这样就避免了重复的脏数据。

25.什么是雪花算法?为什么它能生成分布式全局唯一id?

雪花算法(Snowflake Algorithm)是一种用来生成分布式系统中全局唯一的ID的算法。由Twitter 开发的,用于满足分布式系统中生成唯一ID的需求。
雪花算法生成的唯一ID通常是一个64位的整数,按照以下结构组成:
·首位符号位(固定为0)∶符号位始终为0,保证生成的是正整数。
。41位时间戳(毫秒级):表示生成ID的时间戳,可以支持约69年的时间范围。
。10位机器标识(分布式部署时的机器ID):可以支持1024台不同的机器。
·12位序列号(同一机器同一毫秒内的自增序列)∶表示同一台机器同一毫秒内生成的不同ID的序列号。
雪花算法能生成分布式全局唯一ID的原因:
雪花算法允许在同一毫秒内生成多个不同的ID,通过序列号的自增保证在高并发情况下生成的ID唯一性。
通过机器部分的标识符保证了在不同的机器上生成ID时不会发生冲突。
利用时间戳部分的信息,确保生成的ID按时间递增,可以方便地对ID进行排序和分析。

26.什么是线程池隔离?你在项目中为什么要使用线程池隔离,有什么好处?

什么是线程池隔离?
线程池隔离是一种通过为不同的任务类型或者不同的资源分配独立的线程池来隔离不同类型的任务或资源的执行的机制。这种机制可以有效地控制各种任务之间的相互影响,提高系统的稳定性和可靠性。
项目中为什么要使用线程池隔离,有什么好处?
如果所有业务操作都使用─一个线程池,最大的问题就是相互影响。
所以,在一些业务敏感的场景,需要隔离线程池,它有以下几点好处:
1.故障隔离,缩小事故范围。
⒉资源隔离,防止业务之间抢占资源。同时支持更精细化地管理资源,比如不重要的场景给小一点的线程池,核心场景配置大线程池。
3.性能优化,一些业务场景的任务是CPU密集型,一些是/O密集型,不同任务类型需要配置不同的线程池。
回到本项目,因为服务器资源有限,对于AI批量生成题目功能,我为会员创建了核心线程数更大的隔离线程池,尽量保证普通用户的操作不会影响到会员使用Al的体验。

27.你具体怎么实现线程池隔离?怎么设置线程池的参数?主观回答

怎么实现线程池隔离?
我针对普通用户和会员用户定制了两个不同的线程池,使用RxJava处理响应式数据流时,可以通过observeOn(scheduler)方法,根据用户的身份指定使用的线程池。
怎么设置线程池的参数?
普通用户线程池:核心线程数和最大线程数都为1,即单线程,任务队列长度为1000,超过则拒绝提交。这样可以有效限制普通用户调用AI的频率。
会员用户线程池:核心线程数为10,最大线程数为20,任务队列长度为5000,超过则拒绝提交。即允许更多会员同时使用Al。
我还通过编写单元测试,验证了线程池隔离的有效性。

28.你的项目支持哪些统计分析功能?后端如何查询出要统计分析的数据?

项目支持哪些统计分析功能?
支持2种统计分析功能:热门应用排行统计和应用回答分布统计。
作用分别是:
·热门应用排行统计:在AI答题应用平台中,我们可以分析哪个App 用户使用的最多,后期功能迭代时,可根据统计结果,把热门的应用排在首页的靠前位置,并且添加缓存以提升访问速度。
·应用回答分布统计:根据用户测评结果的分布情况,可优先对群体大的用户进行定制化开发或广告投放,吸引更多用户。
后端如何查询出要统计分析的数据?
编写SQL语句,通过group by聚合语法实现对数据的分组统计。在Java程序中,通过MyBatis 的自定义SQL注解实现(如@Select)复杂SQL语句的执行。
后端查出数据后,前端可以通过ECharts 实现可视化。

29.在开发过程中,你遇到过比较复杂的技术问题或挑战吗?如果有,请谈谈你是如何解决这些问题的?

可以从以上任意一道主观的面试题出发去讲,比如你是怎么实现Al生成题目功能并使用SSE + RxJava进行优化的?如何准确地根据实时字符串流拼接出一道完整的题目并立即返回给前端?如何一步步封装了通用的Al模块?发现有多种评分算法(if else)时,使用策略模式优化代码;发现用户可能同时提交多个重复回答时,采用幂等设计并通过缓存防止Al资源的浪费。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值