从零RPC(六) 诸多框架整和Spring的秘密

手写 RPC (二) 集成 Spring 【诗90:10】我们一生的年日是七十岁,若是强壮可到八十岁;但其中所矜夸的不过是劳苦愁烦,转眼成空,我们便如飞而去。 背景 在第一篇「手写RPC(一)基于BIO」我们已经完成了 RPC 的实现,那么服务的发布和调用怎样交给 spring 管理呢? 这正是下面要探讨完成的事情 阅读详情

本章内容:

1、Spring FactoryBean、InitializingBean的作用。

2、自定义xsd文件。用来自定义Spring xml文件的标签。

3、扩展BeanDefinitionParser建立自定义标签解析成为我们自定义bean的过程。

4、自定义bean的后置操作完成Netty服务开启(后面会介绍)、注册中心注册等动作。

 

一、Spring FactoryBean、InitializingBean的作用。

Spring的生命周期后面单独写Spring时介绍。现在只介绍 FactoryBean、InitializingBean这两个接口。

1、InitializingBean。 当我们把一个类实例化的过程交给IOC容器托管时,希望在该类实例化后做一些操作。就可以实现这个接口。

并在afterPropertiesSet()方法中写入逻辑。

2、FactoryBean。当我们自定义一个类,在这个类中得到另一个对象、类型。可以用这个接口。这样说有点抽象。比如一个具体场景:装饰者类(装饰者模式)。 我们希望在真正执行逻辑的类外面有个包装类,来做一些事情。就可以用这个接口,把装饰类(当前类本身)与被装饰的类(getObject()得到的对象)之间绑定。从而做一些逻辑。

在Rpc里利用这两个特性,后置动作中放入Netty服务的开启、注册中心的注册。装饰类来装饰对应的每个接口的实现类。

/**
 * 
 * <p>Title: ProviderFactoryBean.java</p>  
* <p>Description: </p>  
* @author zhaojunjie  
* @date 2020年3月28日  
* @version 1.0
 */
public class ProviderFactoryBean implements InitializingBean,FactoryBean{

	private Class<?> serviceInterface;
	private Object serviceObject;
	private String servicePort;
	private long timeout;
	private Object serviceProxyObject;
	private String appKey;
	private String groupName;
	private int weight = 1;
	private int workerThread = 10;
	
	@Override
	public Object getObject() throws Exception {
		return serviceObject;
	}

	@Override
	public Class getObjectType() {
		return serviceInterface;
	}
	
	@Override
	public void afterPropertiesSet() throws Exception {
		//1、开启NettyServer
		System.out.println(this.toString());
		//2、注册到zookeeper注册中心
		List<ProviderService> serviceMetaData = buildProviderServiceInfo();
		RegisterCenter.singleton().registerProvider(serviceMetaData);
	}

	private List<ProviderService> buildProviderServiceInfo() {
		List<ProviderService> providerList = Lists.newArrayList();
		Method[] methods = serviceObject.getClass().getDeclaredMethods();
		for (Method method : methods) {
			ProviderService providerService = new ProviderService();
			providerService.setServiceInterface(serviceInterface);
			providerService.setServiceObject(serviceObject);
			providerService.setServerIp(IPHelper.getCurrentIp());
			providerService.setServicePort(Integer.parseInt(servicePort));
			providerService.setTimeout(timeout);
			providerService.setServiceMethod(method);
			providerService.setWeight(weight);
			providerService.setWorkerThread(workerThread);
			providerService.setAppKey(appKey);
			providerService.setGroupName(groupName);
			providerList.add(providerService);
		}
		return providerList;
	}

	public Class<?> getServiceInterface() {
		return serviceInterface;
	}

	public void setServiceInterface(Class<?> serviceInterface) {
		this.serviceInterface = serviceInterface;
	}

	public Object getServiceObject() {
		return serviceObject;
	}

	public void setServiceObject(Object serviceObject) {
		this.serviceObject = serviceObject;
	}

	public String getServicePort() {
		return servicePort;
	}

	public void setServicePort(String servicePort) {
		this.servicePort = servicePort;
	}

	public long getTimeout() {
		return timeout;
	}

	public void setTimeout(long timeout) {
		this.timeout = timeout;
	}

	public Object getServiceProxyObject() {
		return serviceProxyObject;
	}

	public void setServiceProxyObject(Object serviceProxyObject) {
		this.serviceProxyObject = serviceProxyObject;
	}

	public String getAppKey() {
		return appKey;
	}

	public void setAppKey(String appKey) {
		this.appKey = appKey;
	}

	public String getGroupName() {
		return groupName;
	}

	public void setGroupName(String groupName) {
		this.groupName = groupName;
	}

	public int getWeight() {
		return weight;
	}

	public void setWeight(int weight) {
		this.weight = weight;
	}

	public int getWorkerThread() {
		return workerThread;
	}

	public void setWorkerThread(int workerThread) {
		this.workerThread = workerThread;
	}

	@Override
	public String toString() {
		return "ProviderFactoryBean [serviceInterface=" + serviceInterface + ", serviceObject=" + serviceObject
				+ ", servicePort=" + servicePort + ", timeout=" + timeout + ", serviceProxyObject=" + serviceProxyObject
				+ ", appKey=" + appKey + ", groupName=" + groupName + ", weight=" + weight + ", workerThread="
				+ workerThread + "]";
	}
}

二、自定义xsd文件。用来自定义Spring xml文件的标签。

1、xsd文件的定义。文件放在resources/META-INF/ 目录下.比如 resources/META-INF/back-service.xsd

xmlns中定义地址:http://www.back.com/schema/back-service

定义Element的name 这个一定注意。解析时会根据这个name,找对应的解析类。再下面就是属性名称和类型的定义。相信一目了然,不再介绍。

<xsd:schema xmlns="http://www.back.com/schema/back-service"
	xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
	xmlns:beans="http://www.springframework.org/schema/beans"
	targetNamespace="http://www.back.com/schema/back-service" 
		elementFormDefault="qualified" attributeFormDefault="unqualified">
	
	<xsd:import namespace="http://www.springframework.org/schema/beans"/>

	<xsd:element name="service">
	<xsd:complexType>
	<xsd:complexContent>
		<xsd:extension base="beans:identifiedType">
			<xsd:attribute name="interface" type="xsd:string" use="required"/>
			<xsd:attribute name="timeout" type="xsd:int" use="required"/>
			<xsd:attribute name="servicePort" type="xsd:int" use="required"/>
			<xsd:attribute name="ref" type="xsd:string" use="required"/>
			<xsd:attribute name="weight" type="xsd:int" use="optional"/>
			<xsd:attribute name="workerThreads" type="xsd:int" use="optional"/>
			<xsd:attribute name="appKey" type="xsd:string" use="required"/>
			<xsd:attribute name="groupName" type="xsd:string" use="optional"/>
		</xsd:extension>
	</xsd:complexContent>
	</xsd:complexType>
	</xsd:element>
</xsd:schema>

2、编写完成xsd后新建Spring的xml 我们来看下我们自定义标签的效果。xml上方要引入我们自定义的地址与xsd.同时把地址与本地xsd文件绑定。eclipse中的步骤是:window -> preferences -> XML -> XML CataLog ->Add    Location中选定我们自定义的xsd路径。key那里把我们的xsd文件地址放入。例:http://www.back.com/schema/back-service.xsd

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:backService="http://www.back.com/schema/back-service" 
    xmlns:backReference="http://www.back.com/schema/back-reference" 
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.back.com/schema/back-service 
        http://www.back.com/schema/back-service.xsd
        http://www.back.com/schema/back-reference
        http://www.back.com/schema/back-reference.xsd" >

	<bean id="serRemote" class="com.back.spring.test.RemoteServiceImpl" />
	<backService:service id="aaa" interface="com.back.spring.test.RemoteService" timeout="10"
	servicePort="9999" ref="serRemote" appKey="dodp" groupName="unps"/>

</beans>

三、扩展BeanDefinitionParser建立自定义标签解析成为我们自定义bean的过程。

1、 NamespaceHandlerSupport接口扩展,指定真正解析的类。根据xsd文件中的Element的name作为key,value就是我们第二个要扩展的真正解析的类

public class BackServiceNamespaceHandler extends NamespaceHandlerSupport{

	@Override
	public void init() {
		registerBeanDefinitionParser("service", new ProviderFactoryBeanDefinitionParser());
	}
}

2、AbstractSingleBeanDefinitionParser扩展。解析自定义标签的属性。然后将属性植入IOC创建的对象中。

public class ProviderFactoryBeanDefinitionParser extends AbstractSingleBeanDefinitionParser{

	@Override
	protected Class<?> getBeanClass(Element element) {
		return ProviderFactoryBean.class;
	}
	
	@Override
	protected void doParse(Element element, BeanDefinitionBuilder builder) {
		try {
			String timeout = element.getAttribute("timeout");
			String serviceInterface = element.getAttribute("interface");
			String servicePort = element.getAttribute("servicePort");
			String ref = element.getAttribute("ref");
			String weight = element.getAttribute("weight");
			String workerThread = element.getAttribute("workerThread");
			String appKey = element.getAttribute("appKey");
			String groupName = element.getAttribute("groupName");
			
			builder.addPropertyValue("timeout", Integer.parseInt(timeout));
			builder.addPropertyValue("servicePort", Integer.parseInt(servicePort));
			builder.addPropertyValue("serviceInterface", Class.forName(serviceInterface));
			builder.addPropertyReference("serviceObject", ref);
			builder.addPropertyValue("appKey", appKey);
			if(NumberUtils.isNumber(weight)){
				builder.addPropertyValue("weight", Integer.parseInt(weight));
			}
			if(NumberUtils.isNumber(workerThread)){
				builder.addPropertyValue("workerThread", Integer.parseInt(workerThread));
			}
			if(StringUtils.isNotBlank(groupName)){
				builder.addPropertyValue("groupName", groupName);
			}
		} catch (ClassNotFoundException e) {
			throw new RuntimeException(e);
		}
	}
}

3、xsd文件解析必须创建两个文件。作用是schame指定本地文件、xsd指定对应的namespaceHandler类

1)resources/META-INF下新建文件spring.handlers  内容如下

http\://www.back.com/schema/back-service=com.back.spring.provider.BackServiceNamespaceHandler

2)resources/META-INF下新建文件spring.schemas  内容如下

http\://www.back.com/schema/back-service.xsd=META-INF/back-service.xsd

 

Ok,做到上面的几部其实就把我们需要的类整合进入Spring了,类似Dubbo等框架所说的整合Spring就是在做这些事,开发者只需要在Spring中使用自定义标签或者自定义类,就能完成自己想做的事了。

最后我们写个测试类来测试下上面的xml中的配置加载成为bean后是否帮我们做了向注册中心注册的动作(注册中心代码在上一篇博客中)。

public class TestSpringXsd {

	
	public static void main(String[] args) throws InterruptedException {
		
		ClassPathResource resource = new ClassPathResource("spring.xml");
		DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
		XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
		reader.loadBeanDefinitions(resource); 
		beanFactory.getBean("aaa");
		
		TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);
	}
}

我们去观察下对应地址的zookeeper,发现如下:接口地址下发现已经将ip和端口以临时节点的形式注册到zookeeper

搞懂Spring Bean的一生,这篇文章就够了 基本概念 「Spring是一个IOC容器」 当我们不用Spring进行开发时,我们需要在代码中设置对象的依赖关系。当我们用了Spring之后,由Spring来管理这种依赖关系,当我们想使用对象时,直接从Spring容器中获取即可 「BeanDefinition」 在Spring中对象被叫做Bean,因为Spring Bean在Java类的基础上增加了很多概念,比如scope(作用域),isLazyInit(是否延迟初始化),isSingleton(是否单例),此时Java类不能完整的描述,所以需要新 阅读详情

相关推荐

因内存不足原因导致fragment空白解决方案

在androidx之前,一般我们防止Fragment重建是在actvitity的onCreate方法中使用savedInstanceState?.remove(“android:support:fragments”)或者savedInstanceState?但是在androidx中,使用这两行代码不生效,但是看FragmentActivity中的FRAGMENT_TAG还是android:support:fragments,那为什么会不生效呢,后来点进去看发现。

qq_39431405的博客 547

spring整理(四)Spring RPC

spring RPC Spring对RPC有丰富的支持 给出了以下几种方案 RMI 基于RMI协议,使用java的序列化机制,客户端服务端都必须时java,RMI协议不被防

六月长安的博客 2136

ST7102驱动移植与调试实战

本文详细介绍了在君正T40平台上移植和调试ST7102 LCD驱动芯片的实战经验。内容涵盖硬件接口配置、时序参数调整、寄存器初始化序列编写及常见问题解决,重点解析了ST7102单芯片集成显示与触控功能的特性,为嵌入式设备显示驱动开发提供实用指南。

k6l7m8n9的博客 1412

从0开始写一个基于注解的轻量级分布式RPC框架(4)自定义Spring的IOC,自定义属性注入bean的过程

代码已经发布在GIT 源代码 自定义注解@SReference注解@Documented @Retention(RetentionPolicy.RUNTIME) @Target({ ElementType.FIELD, ElementType.METHOD }) public @interface SReference { }如何让这个注解所标注的属性可以被Spring注入相关的bean?方法1

kkgbn的博客 786

innovus实战攻略 | 用setUsefulSkewMode解锁时序优化的“隐藏技能”!

就像时序优化的“瑞士军刀”,参数虽多,但掌握了分类逻辑后,用起来其实很简单!如果你有更绝的用法,欢迎在评论区分享,大家一起卷起来!简单来说,它就是时序优化的“乾坤大挪移”。通过调整触发器的时钟到达时间,把关键路径的建立时间“借”给非关键路径,从而实现全局时序收敛。别急,这个命令就是你的“时序救星”!(工具最多只能借1.2ns的裕量,防止“借过头”导致新问题!这个参数能帮你兜底!(对关键路径“下死手”,确保优化到位!(设置上限,给工具套上“紧箍咒”!(直接跳过边界,避免优化反噬!(全局管理,防止“借过头”!

weixin_50820814的博客 1355

HDMI协议介绍(二)--DataIsland Packets

前文简单介绍了HDMI传输的三个周期:控制周期、DataIsland周期和Video周期。DataIsland传输音频和辅助数据,这些数据以Packet的形式传输。本文简单介绍DataIsland Packet。

SlamDunk的博客 5673

轻松理解Spring、RPC、SOA、集群、分布式、kafka相关概念

本篇主要为个人对于相关技术及框架知识的理解。 内容仅作为交流、分享。

qq_40798777的博客 2876

ADS仿真过孔阻抗(via designer)

目录一、将PCB层叠导入ADS(个人习惯)二、过孔的参数化设置三、过孔的仿真四、仿真结果查看五、导出仿真结果和模型六、过孔的经验法则欢迎交流,欢迎转载,转载请注明出处~ 工具:Via Designer Via Designer 调用的是ADS EMPro 仿真引擎,采用的FEM(三维全波有限元法)。 优点: 1、利用ADS已经做好的参数化和流程化的设计,不需要工程师完成过孔的建模这一繁琐的过程。 2、可以输出EMmodule 完成原理图协仿真。 一、将PCB层叠导入ADS(个人习惯) 个人认为在ADS中设置

luohuo9844的博客 1万+

揭开sofa-boot关于sofa-rpc proxy的秘密

theme: cyanosis highlight: androidstudio “我正在参加「掘金·启航计划」” # 前言 我们在前面几篇文章已经介绍了,sofa-rpc、sofa-bolt、sofa-boot相关知识,接下来我们再深入sofa-boot框架里面一览rpc proxy的面纱,看看框架里面具体是怎么实现代理类的。 在官网有个基础的架构图,这个跟dubbo、其他rp...

大鸡腿的博客 384

KylinOS 离线安装 MinIO【图文步骤】

在国产操作系统 KylinOS 中安装 MinIO,详细步骤...

甘蓝的专栏 1536

GB28181 —— 1、Ubuntu20.04下使用ZLMediaKit+WVP搭建GB28181流媒体监控平台(连接带云台摄像机)

GB28181 —— Ubuntu20.04下使用ZLMediaKit+WVP搭建GB28181流媒体监控平台

5460

RPC client之OpenFeign

本文我们一起看看SpringCloud中的OpenFeign组件,探讨下其整体结构和带给我的启发。内容涵盖启用Feign,创建代理对象,到最终响应处理的整个过程。以上就是今天要讲的内容,本文介绍了OpenFeign在SpringBoot环境下如何启用@FeignClient注解,对应的BeanDefinition是如何注册的,BeanInstance是如何生成的,并简单分析了基于Feign的RPC调用过程。从内容上来看,基本覆盖了FeignClient的骨架内容。

技术进步那些的事 2141

VS2022编译xlnt库

xlnt是一个用于读写Excel文件的C++库,本文介绍如何用源代码编译静态或动态xlnt库

QQVQQ的博客 7288

Hadoop学习(五)——轻量级RPC框架

笔者是一个痴迷于挖掘数据中的价值的学习人,希望在平日的工作学习中,挖掘数据的价值,找寻数据的秘密,笔者认为,数据的价值不仅仅只体现在企业中,个人也可以体会到数据的魅力,用技术力量探索行为密码,让大数据助跑每一个人,欢迎直筒们关注我的公众号,大家一起讨论数据中的那些有趣的事情。 我的公众号为:livandata

livan1234的博客 751

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

手拿几个大厂offer,吐血整理的拿offer的秘密武器!

怎样才能拿到大厂的offer?没有掌握绝对的技术,那么就要不断的学习。 如何拿下阿里等大厂的offer的呢,今天分享一个秘密武器,资深架构师整理的Java核心知识点,面试时面试官必问的知识点,篇章包括了很多知识点,其中包括了有基础知识、Java集合、JVM、多线程并发、spring原理、微服务、Netty 与RPC 、Kafka、日记、设计模式、Java算法、数据库、Zookeeper、分布式缓存、数据结构等等。 由于整个文档比较全面,内容比较多,篇幅的限制,文章中分享没有全部附上详细的解析,但是整理成了一

weixin_48013460的博客 356

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

告别等等等大三奇遇记让代码飞起来的秘密武器高性能框架探索(1750640072358700)

Hyperlane is a lightweight and high-performance Rust HTTP server library designed to simplify network service development. It supports HTTP request parsing, response building, TCP communication, and redirection features, making it ideal for building modern

m0_52796585的博客 679

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

还不会分布式框架?那你就out了,速来看这份阿里P9整理的分布式架构笔记

架构说简单点就是一堆技术、框架、工具的组合,至于怎么组合,这就非常考验架构师的经验和水平。一个优秀的架构,可以让开发效率变得更加高效,为企业节省更多的成本。程序员可将自已更多的精力放在业务需求的实现上,不会被底层的复杂技术细节所干扰。架构师(或从事架构工作的人)就像是探险者一样,他们走在团队的前面,为大家铺路,带领大家找到成功捷径。此外,做架构工作不能照搬别人所谓的最佳实践,而要根据自身实际情况,因地制宜地灵活选择,设计最为合理的架构。架构的目的是为了让业务变得更加容易落地,降低开发成本与统一开发规范。

m0_73311735的博客 82

源码面前没有秘密,推荐 9 个带你阅读源码的开源项目

LevelDB 是一个由 Google 开源的持久化 KV(键值)存储数据库。该项目是 LevelDB 1.23 版本源码阅读笔记,包括:分析 DB 运作流程、STable 等文件格式以及 Compaction 过程。Java。

2401_84103616的博客 1755

大神码了2000页Spring全家桶笔记

Kubernetes简介 Kubernetes(简称K8S,K和S之间有8个字母)是用于自动部署,扩展和管理容器化应用程序的开源系统。它将组成应用程序的容器组合成逻辑单元,以便于管理和服务发现。Kubernetes 源自Google 15 年生产环境的运维经验,同时凝聚了社区的最佳创意和实践。 Kubernetes具有如下特性: 服务发现与负载均衡:无需修改你的应用程序即可使用陌生的服务发现机制。 存储编排:自动挂载所选存储系统,包括本地存储。 Secret和配置管理:部署更新Secrets和应用程序的配

m0_57767600的博客 108

Spring 常犯的十大错误,打死都不要犯!

点击上方“芋道源码”,选择“设为星标”做积极的人,而不是积极废人!源码精品专栏中文详细注释的开源项目RPC 框架 Dubbo 源码解析网络应用框架 Netty 源码解析...

芋道源码 357

吐血整理:手拿几个大厂offer的秘密武器!

怎样才能拿到大厂的offer?没有掌握绝对的技术,那么就要不断的学习。 如何拿下阿里等大厂的offer呢,今天分享一个秘密武器,资深架构师整理的Java核心知识点,面试时面试官必问的知识点,篇章包括了很多知识点,其中包括了有基础知识、Java集合、JVM、多线程并发、spring原理、微服务、Netty 与RPC 、Kafka、日记、设计模式、Java算法、数据库、Zookeeper、分布式缓存、数据结构等等。 整个文档比较全面,内容比较多,由于篇幅的限制,文章中没有全部附上详细的解析,但是整理成了一份.

程序员白楠楠的博客 269

Java面试核心:Spring与微服务深度解析

Java技术栈在互联网大厂面试中占据重要地位,尤其是Spring框架和微服务架构的深度理解。Spring作为Java生态的核心框架,其IoC容器设计、Bean生命周期管理及扩展机制(如BeanPostProcessor)是面试高频考点。理解这些原理不仅需要掌握基础概念,还需通过源码调试(如AbstractAutowireCapableBeanFactory)深入分析。微服务架构则聚焦分布式系统问题,如熔断降级、分布式追踪和云原生技术(如K8s的HPA)。面试中,候选人需平衡技术深度与广度,从JVM内存模型到

weixin_30645617的博客 454

吐血整理:手拿几个大厂offer的秘密武器!(免费领取)

怎样才能拿到大厂的offer?没有掌握绝对的技术,那么就要不断的学习。 如何拿下阿里等大厂的offer的呢,今天分享一个秘密武器,资深架构师整理的Java核心知识点,面试时面试官必问的知识点,篇章包括了很多知识点,其中包括了有基础知识、Java集合、JVM、多线程并发、spring原理、微服务、Netty 与RPC 、Kafka、日记、设计模式、Java算法、数据库、Zookeeper、分布式缓存、数据结构等等。 由于整个文档比较全面,内容比较多,篇幅的限制,文章中分享没有全部附上详细的解析,但是整理成

live202001的博客 4114

Java面试真题解析火爆全网,赶紧收藏备战金三银四!

RPC概述 RPC(Remote Procedure Call)即远程过程调用,允许一台计算机调用另一台计算机上的程序得到结果,而代码中不需要做额外的编程,就像在本地调用一样。 现在互联网应用的量级越来越大,单台计算机的能力有限,需要借助可扩展的计算机集群来完成,分布式的应用可以借助RPC来完成机器之间的调用。 一、掀起Spring的盖头来 Spring框架的由来 Spring框架概述 二、Spring的IoC容器 重头开始认识loC的基本概念:(构造方法注入+scttcr方法注入+接口注入) 运筹帷

m0_57081140的博客 123
上一篇: 从零RPC(五) 使用zookeeper做注册中心,并完成服务发现等功能
下一篇: git快速下载地址
MrBack
博客等级 码龄9年 27粉丝 99原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值