Java课程设计项目实例《基于微服务的在线签到》子系统

一、前言

作为智能考勤管理系统、会议管理系统等多种系统组成部分的“在线签到”子系统从业务逻辑本身来看,并没有什么复杂性。不外乎是前端用户通过手机APP、PC电脑应用程序或者Web浏览器等形式的客户端访问服务器端相关程序中的签到方法,记录用户签到的时间等信息。但问题是如何能够让一个系统同时能够支持多种不同形式的客户端访问?

“在线签到”子系统选用目前比较流行和热门的“微服务”( Microservice)作为系统的技术实现方案,作者在本文中将重点介绍子系统所应用的核心技术——微服务、反射、对象序列化、多线程以及基于TIP/IP的Socket通讯等,此外还通过具体的程序代码实现为读者介绍微服务的底层实现原理、Socket通讯编程实现。

当然,作为课程设计文档的规范格式中所必需的“系统需求”、“系统设计”和“项目总结”等章节的内容在本文中,由于本文的篇幅关系,作者暂不涉及这些章节的内容——本文的写作重点在“技术”和“实现”两方面。感兴趣的读者可以将本文嵌入到自己的课程设计系统中或者引用本文相关内容,以丰富和完善自己的课程设计、毕业设计等文档。

二、系统所应用的核心技术——微服务

1、单体架构的应用

(1)什么是单体架构(Monolithic Architecture)的应用

在传统的软件应用开发中,基本上都是将一个软件项目相关的功能程序代码(包含有系统的DO/DAO,Service,UI等所有逻辑)、资源文件、配置文件和数据库等方面的内容打包为一个JAR程序包或者多个JAR程序包文件(当然也可能为WAR、EAR或其它归档格式的文件),以这样的方式发布的应用程序,一般称为单体架构的应用。如下示例图所示为单体架构的应用结构关系示图,单体架构的应用可以是PC端的应用程序,也可以是基于浏览器的Web应用或者移动端的App应用等形式。

Java课程设计项目实例《基于微服务的在线签到》子系统

 

(2)基于单体架构的应用所体现出的主要优点

首先,便于共享和管理。由于在单个归档文件中包含所有的功能程序及相关的资源文件,便于在团队之间以及不同的部署阶段之间共享,也方便管理。

其次,易于测试。单体应用一旦成功部署,所有的系统功能服务都能够正常地提供,能够简化对它的测试过程。因为高度集中,没有附加的外部依赖,对单体应用的每项测试都可以在部署完成后正常地开展。

最后,易于部署。只需要将它的单个归档文件(JAR、WAR、EAR)复制到发布的某个系统目录中。全部的功能程序都在本地主机中或者Web服务器中,没有分布式的管理和调用的消耗。

(3)基于单体架构的应用所带来的主要缺点

1)系统将会越来越臃肿

随之而来的问题将是部署效率低。因为当越来越多的程序功能模块都集中在同一项目相关的单个归档文件(JAR、WAR、EAR)时,整个系统将会越来越庞大和变得越来越臃肿,维护困难——对单体应用的编译打包、部署、测试的整个过程会非常耗时;系统的扩展性也不够高:而且很难满足高并发应用环境下的业务需求。

2)系统资源无法隔离

由于在整个单体系统中的各个功能模块都依赖于同样的资源文件、系统库、内存等资源,一旦某个功能模块对相关的资源如果使用不当,整个应用系统都有可能会崩溃。扩展能力受限:单体应用只能作为一个整体进行扩展,无法根据业务模块的需要进行伸缩。

3)系统开发成本高

系统项目在早期开发时,由小团队的开发人员进行协作修改代码,打包部署,更新发布等方面的事情是可控的。但是随着团队人员不断地增加,如果此时仍然还是按照早期的方法去开发。而如果测试阶段只要在系统中有某个功能有问题,就得重新编译、打包和部署。此外,所有的开发人员又都得参与其中,效率低下,开发成本极高。

基于单体架构的应用方式比较适合小型的项目,但是对于大型的应用项目不能再应用单体架构,否则将使得系统的稳定性、扩展性和灵活性都很难满足要求。因此,需要分布式的应用开发模式下的微服务架构。

2、微服务架构风格

(1)什么是微服务架构风格

微服务架构(Microservice Architecture)风格是一种将一个单一应用程序(单体程序)开发为一组小型服务的方法,每个服务都运行在自己的进程中,服务间的通信完全可以采用轻量级的通信机制。这些服务围绕业务能力构建并且服务可用不同的编程语言开发和使用不同的数据存储技术。如下示例图所示为微服务架构的应用结构关系示图。

Java课程设计项目实例《基于微服务的在线签到》子系统

 

比如在某系统中有Customer模块和Product模块,但是Customer模块和Product模块两者之间并没有直接的关系,而只是交互一些数据。因此,就可以将这两个模块分开,Customer模块和Product模块各自独立为一个服务,两个服务之间也可以相互调用。

(2)微服务(Microservice)的主要优点

1)易于开发和维护,一个微服务只需要实现一个特定的业务功能,开发和维护单个微服务相对简单。

2)服务粒度按需伸缩,可根据应用的需求,不仅对服务的拆分粒度可以更细,也还可以实现细粒度服务的功能扩展。一个微服务可以小到一个子模块,只要该模块依赖的资源与其它模块无关,则可以设置为一个微服务——每个服务为独立的业务开发。

3)服务独立部署,传统的基于单体架构的应用是以整个系统为单位进行部署。而微服务则是以每一个独立的组件为单位进行部署,非常强调隔离性。局部修改容易部署,对某个微服务进行修改,只需要重新部署这个服务即可。

当然,微服务也并非完美无缺,同样也有它自身的局限——分布式应用环境下的系统容错、网络延迟、分布式事务等都会带来巨大的问题;对微服务相关的接口调整成本也比较高,微服务之间通过接口进行通信。如果对某个微服务的接口进行修改,则所有应用到这个接口的微服务实现类、微服务的使用方客户端程序都需要进行调整和完善。

3、微服务实现的基本原理

微服务实现的基本原理可以参看如下示例图所示的结构关系示图,也就是在服务器端相关的程序建立服务器时,首先要进行的就是包扫描,扫描出可以供远程客户端程序能够调用的类,把该类中的方法“注册”到注册中心,形成一个个服务的键值对;当客户端相关的程序通过Socket远程连接服务器并请求调用服务器中的某个方法(请求调用微服务)时,根据客户请求参数中所包含的“键”,在注册中心中找到对应的包名、类名和对应的服务方法名,再结合客户端发送的请求参数,利用Java反射机制执行目标微服务方法,并由服务器端程序将微服务方法执行的结果再通过Socket发送到请求的客户程序中;客户端相关程序通过Socket通讯接收到微服务调用后返回的结果;最后,再断开和服务器的连接和释放相关的I/O流。实现Socket短连接的一次远程调用。

Java课程设计项目实例《基于微服务的在线签到》子系统

 

在客户端程序中,首先测试目标服务器是否已经启动,如果服务器正常启动了,则请求连接Socket服务器;并向服务器发送请求调用的微服务的接口名(方法名)和调用相关的参数,由服务器来执行目标微服务对应的方法;并把执行的结果发送回客户端程序;客户端接收到返回的结果后,同样也需要断开与服务器的连接和释放相关的I/O流,一次远程微服务方法的调用过程结束。

4、系统中的网络通讯应用Socket 短连接

(1)Socket短连接的应用场景——低频、无状态

“Socket短连接”是指网络通讯中的服务端和客户端各自在进行Socket连接后,发送数据,然后再接收数据后,马上就断开此次的Socket连接。本系统中的网络通讯之所以要应用Socket短连接的通讯方式,主要是基于系统中的客户端程序无需频繁地请求调用服务器端的微服务和访问状态的持久化(Socket长连接的应用场景则是高频、服务端主动推送和有状态),从而在高并发的客户请求应用状况下,能够降低服务器的负担。

(2)在服务器端自动关闭I/O流和销毁Socket对象

在服务器端接收客户端的请求调用微服务的信息、并在向客户端发送对微服务请求调用后的结果信息之后,必须要及时地关闭I/O流和销毁Socket对象。为了能够保证一定能够关闭I/O流和销毁Socket对象以真正地释放所占用的系统资源,必须将相关的程序代码放在finally语句块中(参看如下示例图所示的程序代码中对closeSocketAndIOStream()方法的调用)。

Java课程设计项目实例《基于微服务的在线签到》子系统

 

(3)在客户端也需要自动关闭I/O流和销毁Socket对象

在客户端程序中同样也需要及时地关闭I/O流和销毁Socket对象,当在客户端程序中发送对微服务的请求调用信息(包括参数等)、并接收到服务器返回的调用结果信息之后也需要及时地关闭I/O流和销毁Socket对象。也必须要将相关的程序代码放在finally语句块中(参看如下示例图所示的程序代码)。

Java课程设计项目实例《基于微服务的在线签到》子系统

 

三、系统所应用的核心技术——反射

1、什么是反射(Reflection)

所谓的反射其实是指程序在执行过程中动态获取其它程序类的组成和结构信息、以及动态调用该类的对象实例中的方法(包括操作属性)的功能。也就是说,某个程序在运行状态中,不仅可以获知任何其它类的所有属性和方法等结构信息;也可以动态地调用(只要有权限访问)任意一个对象的成员方法和操作成员属性。

Java语言通过丰富的系统API(包含在java.lang.reflect程序包中)全面地支持反射,使得Java程序员在应用系统中充分地应用反射技术提高程序的灵活性。

2、Java反射机制主要提供了以下方面的功能

(1)在运行时判断任意一个对象所属的类类型

(2)在运行时能够构造任意一个类的对象实例

(3)在运行时判断任意一个类所具有的成员属性和成员方法的定义形式

(4)在运行时调用任意一个对象中的成员方法,通过反射甚至可以调用类中的private类型的成员方法

如下示例图所示的程序示例说明了Java反射机制的基本功能以及如何编程应用反射技术,在示例程序中列出了java.util.ArrayList 类中的各个方法名以及它们的限制符和方法的返回值类型等信息。示例程序使用 Class.forName方法载入指定的类(示例程序为java.util.ArrayList 类),然后调用 getDeclaredMethods方法来获取这个类中定义的方法列表。

Java课程设计项目实例《基于微服务的在线签到》子系统

 

反射其实就是把程序类中的各种组成成分映射成一个个的Java对象,比如java.lang.reflect.Methods 类是用来描述某个类中单个成员方法的一个类,它也是Java反射系统API中的一个重要的类。通过java.lang.reflect.Methods 类不仅可以动态获得类中的某个成员方法的定义信息,也可以动态地调用目标方法——本系统主要是应用它实现对目标微服务所对应的方法进行动态地调用。

除了Method类以外,其中的Class类代表一个类,Field类则代表类的成员变量(类的属性),Constructor类代表类中的构造方法,Array类则提供了动态创建数组,以及访问数组的元素的静态方法。

3、动态加载某个类和创建对象实例

在许多面向对象语言的程序中创建一个类的对象实例,一般都是应用new操作符。这种创建对象实例的方式称为“静态创建”——需要在源程序中明确地给定出类名称(在编译时就需要指定具体的类名),而Java反射则是在运行时动态地创建出类的对象实例。因此,不能再应用new操作符,而需要改用Class.forName方法动态加载目标类。

Class类中的newInstance方法则能够动态地创建出由Class.forName方法动态加载的目标类的对象实例。应用newInstance()方法和new操作符创建对象实例除了一个是方法,一个是操作符外,最主要的区别在于创建对象的方式不一样。使用关键字new创建一个类的对象实例时,这个类可以是没有被加载的(只在编译阶段)。但是使用newInstance()方法创建一个类的对象实例时,由于是在运行期间,目标类必须是已经加载的,而此任务则是由Class.forName方法来实现。参看如下创建java.util.ArrayList类的对象实例的程序代码示例。

String someOneClassName="java.util.ArrayList";
Class arrayListClassInstance = Class.forName(someOneClassName);
List someOneArrayList = (ArrayList) arrayListClassInstance.newInstance();

4、动态调用目标类中的特定的方法

在应用开发中,经常会涉及动态地调用目标类中的成员方法。比如一个程序在执行到某处时才知道需要执行某个方法,这个方法的名称是在程序的运行过程中动态给定出的,但需要动态地调用它——本系统中的微服务的调用即为此应用场景。如下示例图所示的程序代码示例说明如何在程序中动态调用目标类中的特定的方法,该示例在callSubMethod方法中动态调用本类中的另一个如下定义的方法:public int subMethod(int oneDigit, int twoDigit)。

Java课程设计项目实例《基于微服务的在线签到》子系统

 

在示例程序中首先应用Class.forName方法动态加载目标类,从而获得Class类的对象实例targetClassObject;然后应用Class类中的getMethod方法获得指定名称的目标方法所对应的Methods类的对象实例targetMethodObject,它封装和代表待调用的目标方法;最后,通过Methods类中的invoke方法动态地调用目标方法,在调用中当然需要给出目标参数和获得返回参数。

四、系统所应用的核心技术——对象序列化

1、对象的序列化技术

(1)对象流

所谓的对象流也就是将对象的内容进行流化,可以对流化后的对象(如文件IO流)进行读写操作,也可将流化后的对象传输于网络之间。

(2)序列化

序列化就是一种用来处理对象流的机制,该机制将一个对象的状态(各个属性量)保存起来,然后在适当的时候再获得并对其进行恢复的过程。对对象进行序列化的主要目的是为了实现对象的传输,同时也解决了对象引用的问题。

(3)为什么要提供序列化相关的应用技术

由于对象的寿命常随着生成该对象的程序的终止而终止。但有时候可能需要将对象的状态保存下来,在需要时再将对象恢复(精确的副本)以延续其状态;也可能需要将对象从一个应用程序域发送到另一个应用程序域中。

如何满足这些应用需求?应用对象的序列化技术可以实现。

2、序列化分为两大部分——序列化和反序列化

(1)序列化

就是将数据分解成字节流(包括对象类型和版本等头信息),以便存储在文件中或在网络上传输。

(2)反序列化

就是打开字节流并重构对象(JVM用头信息生成对象实例,然后将对象字节流中的数据复制到对象数据成员中)。

(3)编程要求

在Java平台中只有实现Serializable接口的类对象才可以被序列化(因此Serializable接口属于支持序列化的一个接口)。但在Serializable接口中没有定义任何的成员,它只用来说明某个类的对象实例可以被序列化。

3、对象序列化主要的应用

(1)在RMI、Socket、JMS、EJB等网络分布式的编程技术应用方面都要使用该技术

因为它自动屏蔽了操作系统的差异,字节顺序等问题(比如,在Window平台生成一个对象并序列化后,然后通过网络传到一台Unix机器上,最后可以在这台Unix机器上正确地重构出原来的对象)。

(2)不同平台的程序之间相互通讯方面的应用

因为可以用java.io包中的各种字节流类将其保存到文件中,管道到另一线程中或通过网络连接将对象数据发送到另一主机中。

4、处理对象流相关的Java API

(1)序列化实现过程

利用ObjectOutput接口和ObjectOutputStream类实现对象的序列化过程,参看如下示例图所示的程序示例。

Java课程设计项目实例《基于微服务的在线签到》子系统

 

(2)反序列化实现过程

利用ObjectInput接口和ObjectInputStream类实现对象的反序列化过程,参看如下示例图所示的程序示例。前面示例程序中通过对象序列化技术保存的Vector对象通过本示例程序已经加以恢复。

Java课程设计项目实例《基于微服务的在线签到》子系统

 

五、在线签到子系统的功能程序实现

1、MicroServiceServerApp服务器端主程序类

(1)创建MicroServiceServerApp服务器端主程序类

在MyEclipse开发工具中创建出一个类名称为MicroServiceServerApp,程序包名称为
com.bluedream.microservice.server,并且包含有main()方法的Java程序类,该类为在线签到子系统服务器端的主程序类。MicroServiceServerApp程序类的创建过程参看如下示例图所示。

Java课程设计项目实例《基于微服务的在线签到》子系统

 

(2)编程MicroServiceServerApp程序类中的main()方法

在main()方法中首先识别给定的服务器的IP地址是否正确,因为只有在程序中给出了正确的IP地址,在线签到子系统的服务器程序才能正常地启动;然后再测试服务器是否已经启动了,主要考虑到服务器端主程序可能会被重复地执行;如果是重复执行状态,则提示错误信息,并退出当前的执行状态。如果服务器主程序没有重复执行(也就是第1次执行),则创建出ServerSocket类对象实例,并调用
listenToSomeOneClientSocket方法监听在线签到子系统客户端程序的Socket连接请求。main()方法最终的程序代码参看如下示例图所示的程序代码示例。

Java课程设计项目实例《基于微服务的在线签到》子系统

 

(3)编程其中的
listenToSomeOneClientSocket ()方法


listenToSomeOneClientSocket ()方法中应用ServerSocket类中的accept方法监听客户端程序的连接请求,并获得正在与服务器连接的某个客户端的Socket对象实例,然后再为此客户创建出一个线程,在线程中实现与客户端的通讯交互。此外,在程序中还需要对相关的异常进行处理,最终的程序代码参看如下示例图所示的程序代码示例。

Java课程设计项目实例《基于微服务的在线签到》子系统

 

2、MicroServiceServerThread线程类

(1)创建MicroServiceServerThread服务器端线程类

在MyEclipse开发工具中创建出一个类名称为MicroServiceServerThread,程序包名称为
com.bluedream.microservice.server,继承于java.lang.Thread线程类,并且不需要包含main()方法的Java程序类,该类为服务器端的线程程序类。MicroServiceServerThread程序类的创建过程参看如下示例图所示。

Java课程设计项目实例《基于微服务的在线签到》子系统

 

(2)线程体run方法中的功能实现

在线程体run方法中(参看如下示例图所示的程序代码)通过callSomeOneServiceMethod方法获得客户端的请求,然后根据客户端的请求调用的目标微服务方法应用反射技术动态调用微服务方法,并获得方法返回的结果;然后再应用sendResultMessageToClient方法将动态调用的微服务方法的返回结果再传送到客户端;最后,及时关闭I/O流及Socket连接,完成一次请求调用的过程。

Java课程设计项目实例《基于微服务的在线签到》子系统

 

(3)callSomeOneServiceMethod方法

在callSomeOneServiceMethod方法中首先应用
getServiceRequestInfoFromClient方法获得封装客户端请求参数的实体类ServiceMessageInfoPO的对象实例;然后从该对象中获得客户端待请求调用的目标微服务的方法名和对应的参数等信息;最后,应用反射技术动态地调用目标微服务方法,并获得方法的返回结果。

Java课程设计项目实例《基于微服务的在线签到》子系统

 

(4)sendResultMessageToClient方法

在sendResultMessageToClient方法中将微服务方法执行返回的结果再封装到实体类ServiceMessageInfoPO的对象实例中,然后再将此实体类对象应用GZip技术进行压缩,然后再进行对象序列化,最终再通过Socket连接传送到客户端程序。

Java课程设计项目实例《基于微服务的在线签到》子系统

 

3、MicroServiceClientApp客户端主程序类

(1)创建MicroServiceClientApp客户端主程序类

在MyEclipse开发工具中创建出一个类名称为MicroServiceClientApp,程序包名称为
com.bluedream.microservice.client,并且包含有main()方法的Java程序类,该类为客户端的主程序类。MicroServiceClientApp程序类的创建过程参看如下示例图所示。

Java课程设计项目实例《基于微服务的在线签到》子系统

 

(2)MicroServiceClientApp类中的main方法

在main方法中通过调用testSocketServerIsRunning方法测试服务器目前是否已经启动,如果服务器目前还没有启动,则直接退出。如果服务器目前处于正常的工作状态,则通过调用
callOnLineSignInServiceMethod方法向服务器请求调用“在线签到”的微服务方法,并传送相关的请求参数,从而模拟“在线签到”的客户端应用。

Java课程设计项目实例《基于微服务的在线签到》子系统

 

(3)
callOnLineSignInServiceMethod方法


callOnLineSignInServiceMethod方法中首先构建对服务器端微服务方法调用的相关参数,再将这些参数封装到实体类ServiceMessageInfoPO的对象实例中;然后再通过调用
requestCallMicroServiceMethod方法实现对服务器端的微服务方法的请求调用;最终获得微服务方法调用后返回的结果。

Java课程设计项目实例《基于微服务的在线签到》子系统

 

(4)
requestCallMicroServiceMethod方法


requestCallMicroServiceMethod方法中(参看如下示例图所示的程序代码示例)应用对象序列化和GZIP数据压缩技术对待传输到服务器端的请求参数对象进行封装处理,最后通过Socket连接传输到服务器端以实现对目标微服务方法进行调用,同时也获得服务器端返回的微服务方法的调用结果。一次请求调用结束后及时关闭I/O流和销毁Socket对象。

Java课程设计项目实例《基于微服务的在线签到》子系统

 

4、MicroServiceInterface微服务方法的接口

在MyEclipse开发工具中创建出一个接口名称为MicroServiceInterface的接口,程序包名称为
com.bluedream.microservice.server,该接口定义系统中的微服务方法的原型。由对应的微服务实现类加以实现接口中的各个方法,MicroServiceInterface接口的创建过程参看如下示例图所示。

Java课程设计项目实例《基于微服务的在线签到》子系统

 

考虑到本文的篇幅有限,在MicroServiceInterface接口中目前只定义有一个实现在线签到的onLineSignInServiceMethod方法,该方法定义有两个参数。其一为用户账号,另一个参数为密码。参看如下示例图所示的程序代码示例。

Java课程设计项目实例《基于微服务的在线签到》子系统

 

5、MicroServiceImplement实现类

(1)创建MicroServiceImplement实现类

在MyEclipse开发工具中创建出一个类名称为MicroServiceImplement,程序包名称为
com.bluedream.microservice.server,实现MicroServiceInterface微服务方法的接口,并且不需要包含main()方法的Java程序类,该类为服务器端的微服务接口MicroServiceInterface的实现程序类。MicroServiceImplement程序类的创建过程参看如下示例图所示。

Java课程设计项目实例《基于微服务的在线签到》子系统

 

(2)实现接口中的onLineSignInServiceMethod在线签到方法

在onLineSignInServiceMethod在线签到方法中,目前只给出模拟功能的实现,没有访问数据库系统进行身份验证,而只是简单地返回用户签到的时间和签到的状态。

Java课程设计项目实例《基于微服务的在线签到》子系统

 

六、相关问题的解决示例

1、EOFException类型的异常

服务器端程序在执行过程中可能会出现如下类型的异常信息:java.io.EOFException at java.io.ObjectInputStream$PeekInputStream.readFully,出现此异常的主要原因是因为客户端关闭了Socket而导致服务器端程序从Socket流中读出的数据为空数据。

Java课程设计项目实例《基于微服务的在线签到》子系统

 

解决的主要方法是,可以针对EOFException异常单独进行处理,参看如下示例图所示的程序代码示例。

Java课程设计项目实例《基于微服务的在线签到》子系统

 

2、用GZIP压缩Socket传输的对象序列化的数据会造成死锁问题

在本子系统中应用ObjectOutputStream序列化流类对在网络中传输的数据进行对象序列化处理,这也是目前传输比较方便的网络通信方式(在序列化的对象中可以包含多种不同的数据类型)。但是如果网络通讯中的数据量较大时,则会影响到网络数据传输的效率。因此,有必要对传输的数据首先进行压缩,然后再将压缩后的信息进行传输。在Java API系统库中提供有GZIPOutputStream类实现对数据的压缩功能,然后再在接收端应用对应的GZIPInputStream类实现解压数据。

但在发送压缩数据时,不能仅仅应用ObjectOutputStream类中的flush方法更新输出缓冲区,也还必须调用GZIPOutputStream类中的finish方法强制对数据进行压缩,将压缩数据写入输出流而不关闭底层流。因此,在程序中如果应用GZIPOutputStream类实现对数据进行压缩,在完成数据压缩后,必须要同时调用flush方法和finish方法才能真正地完成最终的数据压缩功能(参看如下示例图所示的程序代码),否则压缩数据包为空。

Java课程设计项目实例《基于微服务的在线签到》子系统

 

七、在线签到子系统功能实现演示

1、启动服务器程序

在MyEclipse开发工具中启动“Run As”中的“Java Application”运行器,从而可以在Java虚拟机环境中执行本示例程序,参看如下示例图所示的程序执行结果。

Java课程设计项目实例《基于微服务的在线签到》子系统

 

2、启动客户端程序

由于客户端程序启动后及时连接Socket服务器,并向服务器请求调用在线签到功能的微服务方法,并在控制台中显示输出服务器端返回的执行结果信息,参看如下示例图所示的程序执行结果。

Java课程设计项目实例《基于微服务的在线签到》子系统

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值