框架 day64 WebService(注解),CXF框架(jax-ws,Jax-rs,与spring整合)
来源:程序员人生 发布时间:2016-08-03 08:54:14 阅读次数:2617次
WebService第2天
1 webservice的注解
1.1 案例:
摹拟查询天气信息,返回3天的天气情况。
1.1.1 实现步骤
服务端:
第1步:创建1个天气信息pojo,包括天气的信息、最高、最低温度、日期。
第2步:编写SEI
第3步:编写SEI实现类,返回天气list
第4步:发布Webservice。
1.1.2 代码实现
1.1.2.1 Pojo
public class WeatherModel {
private String info;
private int maxTemp;
private int minTemp;
private Date date;
1.1.2.2 SEI
天气查询SEI,返回3天的天气情况
@WebService
public interface WeatherInterface {
List<WeatherModel> queryWeather(String cityName);
}
1.1.2.3 SEI实现类
实现类使用@WebService实现wsdl文档元素名称的修改
@WebService(
//endpointInterface="com.itheima.weather.service.WeatherInterface" //可以指定SEI接口
name="WeatherInterface", //不使用SEI接口时规范portType的名称
serviceName="WeatherService", //服务视图的名称
portName="WeatherPort", //Service节点中port节点的name属性
targetNamespace="http://weather.itheima.com/" //wsdl的命名空间
)
public class WeatherInterfaceImpl implements WeatherInterface {
@Override
@WebMethod/*(exclude=true)*/
@WebResult(name="WeatherInfo")
public List<WeatherModel> queryWeather(@WebParam(name="cityName")String cityName) {
System.out.println("客户端发送的城市:" + cityName);
//查询天气信息
List<WeatherModel> info = getWeatherInfo(cityName);
//返回天气信息
return info;
}
private List<WeatherModel> getWeatherInfo(String cityName) {
List<WeatherModel> weatherList = new ArrayList<>();
Calendar calendar = Calendar.getInstance();
//第1天
WeatherModel model1 = new WeatherModel();
model1.setInfo("雷阵雨");
model1.setMaxTemp(31);
model1.setMinTemp(22);
model1.setDate(calendar.getTime());
weatherList.add(model1);
//第2天
WeatherModel model2 = new WeatherModel();
model2.setInfo("多云");
model2.setMaxTemp(33);
model2.setMinTemp(25);
calendar.set(Calendar.DATE, calendar.get(Calendar.DATE) + 1);
model2.setDate(calendar.getTime());
weatherList.add(model2);
//第3天
WeatherModel model3 = new WeatherModel();
model3.setInfo("多云");
model3.setMaxTemp(35);
model3.setMinTemp(25);
calendar.set(Calendar.DATE, calendar.get(Calendar.DATE) + 1);
model3.setDate(calendar.getTime());
weatherList.add(model3);
return weatherList;
}
}
1.1.2.4 客户端
public class WeatherClient {
public static void main(String[] args) throws Exception {
//创建服务视图
Service service = Service.create(new URL("http://127.0.0.1:12345/weather"),
new QName("http://service.weather.itheima.com/", "WeatherInterfaceImplService"));
//从服务视图取得protType对象
WeatherInterfaceImpl weatherInterfaceImpl = service.getPort(WeatherInterfaceImpl.class);
//调用服务端方法
List<WeatherModel> list = weatherInterfaceImpl.queryWeather("北京");
//显示天气信息
for (WeatherModel weatherModel : list) {
System.out.println(weatherModel.getDate().toGregorianCalendar().getTime().toLocaleString());
System.out.println(weatherModel.getInfo());
System.out.println(weatherModel.getMaxTemp());
System.out.println(weatherModel.getMinTemp());
}
}
}
1.2 要规范wsdl需要使用到webservice注解
1.2.1 @Webservice
@WebService(
//endpointInterface="com.itheima.weather.service.WeatherInterface"//可以指定SEI接口
name="WeatherInterface",//不使用SEI接口时规范portType的名称
serviceName="WeatherService", //服务视图的名称
portName="WeatherPort", //Service节点中port节点的name属性
targetNamespace="http://weather.itheima.com/" //wsdl的命名空间
)
1.2.2 @WebMethod
如果不指定@WebMethod注解默许是吧实现类中所有的public方法都发布成服务方法。
如果类中有的public方法不想发布成服务,就能够使用@WebMethod(exclude=true)把此方法排除,也就是不发布为webservice方法。
注意:每一个Porttype中,必须有1个public方法并且不能标注为@WebMethod(exclude=true)
1.2.3 @WebParam、@WebResult
可以规范参数的名称
@WebResult(name="xxxx")修改返回值的元素的父标签名字
@WebParam(name="xxxxx")修改传入参数的元素的父标签名字
2 CXF框架
2.1 甚么是CXF
Apache CXF = Celtix + Xfire,开始叫 Apache CeltiXfire,后来更名为 Apache CXF 了,以下简称为 CXF。Apache CXF
是1个开源的 web Services 框架,CXF 帮助您构建和开发 web Services ,它支持多种协议,比如:SOAP1.1,1,2、XML/HTTP、RESTful 或 CORBA。
RESTful:1种风格而不是1个协议。它理念是网络上的所有事物都被抽象为资源,每一个资源对应1个唯1的资源标识符。
CORBA(Common Object Request Broker Architecture公共对象要求代理体系结构,初期语言使用的WS。C,c++,C#)
Cxf是基于SOA总线结构,依托spring完成模块的集成,实现SOA方式。
灵活的部署:可以运行在Tomcat,Jboss,Jetty(内置),weblogic上面。
2.2 CXF的安装及配置
从官网下载:cxf.apache.org
学习使用的版本是:3.0.2
使用的方法,直接把cxf的jar包添加到工程中就能够了。
环境配置
JAVA_HOME 需要jdk的支持
CXF_HOME 解压目录(bin的上层目录) 需要使用bin目录的可履行命令生成客户端代码
path = %JAVA_HOME%\bin;%CXF_HOME%\bin;
2.3 使用CXF实现java-ws规范的webservice
Soap1.1:
2.3.1 服务端:
实现步骤:
第1步:创建1java工程。
第2步:导入jar包导入cxf的jar包共138个。
第3步:编写SEI,在SEI上添加@Webservice注解。
第3步:编写SEI实现类。需要实现SEI接口,可以不加@Webservice注解
第4步:发布服务。
1、创建1个JaxWsServerFactoryBean对象。
2、设置服务的发布地址,是1个http url
3、设置SEI接口
4、设置SEI实现类对象
5、调用create方法发布服务。
2.3.2 代码实现
2.3.2.1 SEI
@WebService
public interface WeatherInterface {
String queryWeather(String cityName);
}
2.3.2.2 实现类
public class WeatherInterfaceImpl implements WeatherInterface {
@Override
public String queryWeather(String cityName) {
System.out.println("城市名称:" + cityName);
String result = "多云";
return result;
}
}
2.3.3 发布服务
服务发布类:后面整合spring依照此步骤配置便可
public class WeatherServer {
public static void main(String[] args) {
//创建1个JaxWsServerFactoryBean对象
JaxWsServerFactoryBean factoryBean = new JaxWsServerFactoryBean();
//设置服务发布的地址
factoryBean.setAddress("http://127.0.0.1:12345/weather");
//设置SEI
factoryBean.setServiceClass(WeatherInterface.class);
//设置实现类对象
factoryBean.setServiceBean(new WeatherInterfaceImpl());
//发布服务
factoryBean.create();
}
}
2.4 客户端
2.4.1 Wsdl2java
可使用wsimport生成客户端调用代码,也能够使用CXF自带的工具生成。Wsdl2java。
可以实现wsimport一样的功能,两个工具生成的代码都是1样,wsdl2java工具的版本高。Wsimport对soap1.2支持的不好。
它包括以下参数:
a) -d参数,指定代码生成的目录。
b) -p参数,指定生成的新的包结构。
例:
在命令行履行
wsdl2java –d . -p cn.test.cxftext http://127.0.0.1:6666/helloworld?wsdl
2.4.2 客户真个代码实现
2.4.2.1 实现步骤
1、可以直接使用生成的代码调用服务端方法。
2、使用CXF提供的工厂类调用。
第1步:创建JaxWsProxyFactoryBean对象
第2步:设置服务的url,服务端地址
第3步:设置SEI(portType)类型
第4步:调用Create方法取得portType对象。
第5步:调用服务端方法。
2.4.2.2 代码实现
使用CXF工厂调用webservice,后面与spring整合配置可以用来参考
public class WeatherClient {
public static void main(String[] args) {
//创建1个JaxWsProxyFactoryBean对象
JaxWsProxyFactoryBean factoryBean = new JaxWsProxyFactoryBean();
//设置服务的url
factoryBean.setAddress("http://127.0.0.1:12345/weather?wsdl");
//设置SEI的类型
factoryBean.setServiceClass(WeatherInterface.class);
//取得porttype对象
WeatherInterface portType = (WeatherInterface) factoryBean.create();
//调用服务端方法
String result = portType.queryWeather("北京");
System.out.println(result);
}
}
2.5 Jax-rs规范的webservice
2.5.1 甚么是rest服务
1句话解释:URL定位资源,用HTTP动词(GET,POST,DELETE,DETC)描写操作。
简单来说,就是可以用httprequest 调用某个function. 比如在阅读器里输入www.chx.site/api/guesswhoisawesome,就会调用后台的某个function得到1个response(可以是Json).
REST 是1种软件架构风格,设计风格而不是标准,只是提供了1组设计原则和束缚条件。
它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更容易于实现缓存等机制。
rest服务采取HTTP 做传输协议,REST 对HTTP 的利用分为以下两种:资源定位和资源操作。
关于JAX-WS与JAX-RS:
二者是不同风格的SOA(面向服务的体系结构)架构。
前者以动词为中心,指定的是每次履行函数。
而后者以名词为中心,每次履行的时候指的是资源。
资源定位:
Rest要求对资源定位更加准确,以下:
非rest方式:http://ip:port/queryUser.action?userType=student&id=001
Rest方式:http://ip:port/user/student/001
Rest方式表示互联网上的资源更加准确,但是也有缺点,可能目录的层级较多不容易理解。
资源操作
利用HTTP 的GET、POST、PUT、DELETE 4种操作来表示数据库操作的SELECT、UPDATE、INSERT、DELETE 操作。
比如:
查询学生方法:
设置Http的要求方法为GET,url以下:
http://ip:port/user/student/001
添加学生方法:
设置http的要求方法为PUT,url以下:
http://ip:port/user/student/001/张3/......
Rest经常使用于资源定位,资源操作方式较少使用。
REST 是1种软件架构理念,现在被移植到Web 服务上,那末在开发Web 服务上,
偏于面向资源的服务适用于REST,REST 简单易用,效力高,
SOAP 成熟度较高,安全性较好。
注意:REST 不是WebService,JAX-RS 只是将REST 设计风格利用到Web 服务开发上。
代码实现
2.5.2 服务端
2.5.2.1 实现步骤
第1步:创建1个pojo,返回值的类型。需要在pojo上添加@XmlRootElement。
第2步:创建1个SEI。也就是1个接口。需要用到的注解
1、@Path:标注要求url
2、@GET、@POST、@PUT、@DELETE:标注操作的方法
3、@Produce:指定返回结果的数据类型,xml或json等。
第3步:创建SEI的实现类。可以不使用任何注解。
第4步:发布rest服务。
1、使用JAXRsServerFactoryBean对象发服务。
2、设置服务发布的地址。设置url
3、设置SEI实现类对象。
4、发布服务,create方法。
2.5.2.2 代码实现
2.5.2.2.1 Pojo
返回值pojo类需要添加1个@XmlRootElement注解
@XmlRootElement
public class WeatherModel {
private String info;
private int maxTemp;
private int minTemp;
private Date date;
2.5.2.2.2 SEI
rest服务SEI
//窄化要求映照
@Path("/weather")
public interface WeatherInterface {
//方法的要求路径{}中的内容就是参数,需要对应的使用@PathParam注解取参数
@Path("/city/{cityName}")
//要求的方法
@GET
//返回结果的数据类型,可以是xml也能够是json
@Produces({MediaType.APPLICATION_JSON+";charset=utf⑻", MediaType.APPLICATION_XML})
List<WeatherModel> queryWeather(@PathParam(value="cityName")String cityName);
}
2.5.2.2.3 SEI实现类
public class WeatherInterfaceImpl implements WeatherInterface{
@Override
public List<WeatherModel> queryWeather(String cityName) {
List<WeatherModel> weatherInfo = getWeatherInfo(cityName);
return weatherInfo;
}
...
2.5.2.2.4 发布服务
使用履行命令生成相应的代码后
public class WeatherServer {
public static void main(String[] args) {
//创建1个JAXRSServerFactoryBean对象
JAXRSServerFactoryBean factoryBean = new JAXRSServerFactoryBean();
//设置服务发布的地址
factoryBean.setAddress("http://127.0.0.1:12345/rest");
//设置实现类对象
factoryBean.setServiceBean(new WeatherInterfaceImpl());
//发布服务
factoryBean.create();
}
}
2.5.3 查看结果
http://127.0.0.1:12345/rest/weather/city/上海
2.5.4 如果是多个参数可使用:
2.5.5 结果数据格式
2.5.5.1 Json
@Produces({MediaType.APPLICATION_JSON+";charset=utf⑻", MediaType.APPLICATION_XML})
其中charset=utf⑻ 为了避免出现乱码
2.5.6 同1个方法返回两种数据类型
支持xml或json数据格式
MediaType.APPLICATION_JSON
MediaType.APPLICATION_XML
默许返回xml格式的数据。
如果想访问json格式的数据需要加参数:?_type=json
http://127.0.0.1:12345/rest/weather/city/%E4%B8%8A%E6%B5%B7?_type=json
2.5.7 客户端
可使用ajax要求json数据。还可使用HttpClient访问数据。需要自己转换成java对象。
3 CXF整合spring
3.1 整合的思路
Spring就是1个容器,服务端让spring容器实现服务的发布。客户端使用spring容器完成取得porttype的进程,直接从spring容器中取得1个porttype对象。
3.2 案例需求
服务端发布手机号查询服务供客户端调用,服务端配置调用公网手机号查询服务客户端,调用公网WebService查询手机号。
3.3 分析
3.4 实现步骤
服务端:有两个身份,1个是调用公网webservice的客户端。1个是对外提供手机号查询服务的服务端。
第1步:实现调用公网webservice的客户端
1、根据公网webservice的wsdl生成客户端调用代码
2、配置spring容器,完成创建服务视图取得porttype的进程。直接从spring容器中取得prottype对象。
3、直接调用porttype对象的服务端方法,查询手机号。
第2步:对外发布服务,实现手机号查询。
1、编写1个SEI接口。需要在SEI上添加@Webservice注解。
2、编写1个SEI实现类,调用公网webservice的porttype实现手机号查询。
3、发布服务,使用spring容器完成。
3.5 开发环境的搭建
第1步:创建1个Web工程
第2步:导入jar包。需要spring的jar包和cxf的jar包。
3.6 代码实现
3.6.1 服务端
3.6.1.1 生成调用公网webservice的客户端调用代码
3.6.1.2 在spring容器中配置porttype
applicationContext.xml
<?xml version="1.0" encoding="UTF⑻"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jaxws="http://cxf.apache.org/jaxws"
xmlns:jaxrs="http://cxf.apache.org/jaxrs"
xmlns:cxf="http://cxf.apache.org/core"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/jaxrs
http://cxf.apache.org/schemas/jaxrs.xsd
http://cxf.apache.org/jaxws
http://cxf.apache.org/schemas/jaxws.xsd
http://cxf.apache.org/core
http://cxf.apache.org/schemas/core.xsd">
<!-- 配置调用公网webservice的客户端 -->
<!-- 相当于1个普通的bean,可以当作对象使用 -->
<jaxws:client id="mobileCodeWSSoap" address="http://webservice.webxml.com.cn/WebServices/MobileCodeWS.asmx"
serviceClass="cn.com.webxml.MobileCodeWSSoap"/>
<!-- 把SEI的实现类配置到spring容器 -->
<bean id="mobileInterfaceImpl" class="com.itheima.mobile.service.MobileInterfaceImpl">
<property name="mobileCodeWSSoap" ref="mobileCodeWSSoap"></property>
</bean>
</beans>
NoClassDefFoundError: org/apache/cxf/logging/FaultListener
解决方法:添加FaultListener。
<cxf:bus>
<cxf:properties>
<entry key="org.apache.cxf.logging.FaultListener">
<!-- 这个类可以换成自己的,如果不换就使用cxf中实现过上述接口的类 -->
<bean class="org.apache.cxf.logging.NoOpFaultListener"/>
</entry>
</cxf:properties>
</cxf:bus>
需要的xmlns和xsd
xmlns:cxf="http://cxf.apache.org/core
http://cxf.apache.org/core
http://cxf.apache.org/schemas/core.xsd
3.6.1.3 SEI
调用公网webservice查询手机号的SEI
@WebService
public interface MobileInterface {
Mobile queryMobile(String code);
}
3.6.1.4 SEI实现类
手机号归属地查询服务
public class MobileInterfaceImpl implements MobileInterface {
//公网webservice的porttype配置注入
private MobileCodeWSSoap mobileCodeWSSoap;
public void setMobileCodeWSSoap(MobileCodeWSSoap mobileCodeWSSoap) {
this.mobileCodeWSSoap = mobileCodeWSSoap;
}
@Override
public Mobile queryMobile(String code) {
//第1个参数:手机号
//第2个参数:免费用户是空串
String info = mobileCodeWSSoap.getMobileCodeInfo(code, "");
Mobile mobile = new Mobile();
mobile.setAddress(info);
mobile.setCode(code);
return mobile;
}
}
3.6.1.5 发布服务
3.6.1.6 Web.xml
配置spring容器,配置cxf使用servlet。
web.xml
<?xml version="1.0" encoding="UTF⑻"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
id="WebApp_ID" version="2.5">
<display-name>B06_spring_cxf</display-name>
<!-- 配置spring容器 -->
<!--
使用spring来加载cxf的服务类,服务类的对象由spring来创建,服务类的对象存在springIoc的容器中
-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- cxf的配置 -->
<!-- servlet负责发布服务类 -->
<servlet>
<servlet-name>cxf</servlet-name>
<servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
<!--设置随着
服务器启动而载servlet,不设置访问时才会加载显示数据慢 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>cxf</servlet-name>
<url-pattern>/ws/*</url-pattern>
</servlet-mapping>
</web-app>
applicationContext.xml发布自己的服务
<?xml version="1.0" encoding="UTF⑻"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jaxws="http://cxf.apache.org/jaxws"
xmlns:jaxrs="http://cxf.apache.org/jaxrs" xmlns:cxf="http://cxf.apache.org/core"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://cxf.apache.org/jaxrs http://cxf.apache.org/schemas/jaxrs.xsd
http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd
http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd">
<!-- 配置调用公网webservice的客户端 -->
<!-- 相当于1个普通的bean,可以当作对象使用 -->
<jaxws:client id="mobileCodeWSSoap" address="http://webservice.webxml.com.cn/WebServices/MobileCodeWS.asmx"
serviceClass="cn.com.webxml.MobileCodeWSSoap"/>
<!-- 把SEI的实现类配置到spring容器 -->
<bean id="mobileInterfaceImpl" class="com.itheima.mobile.service.MobileInterfaceImpl">
<property name="mobileCodeWSSoap" ref="mobileCodeWSSoap"></property>
</bean>
<!-- 发布服务 -->
<!-- address就是服务发布的地址,只是从1个目录开始 -->
<!-- serviceClass是发布服务的sei -->
<jaxws:server address="/mobile" serviceClass="com.itheima.mobile.service.MobileInterface">
<jaxws:serviceBean>
<ref bean="mobileInterfaceImpl"/>
</jaxws:serviceBean>
</jaxws:server>
<!-- rest服务的实现类 -->
<bean id="mobileRestImpl" class="com.itheima.mobile.rest.MobileRestImpl">
<property name="mobileCodeWSSoap" ref="mobileCodeWSSoap"></property>
</bean>
<!-- 发布rest服务 -->
<jaxrs:server address="/rest">
<jaxrs:serviceBeans>
<ref bean="mobileRestImpl"/>
</jaxrs:serviceBeans>
</jaxrs:server>
</beans>
3.6.1.7 访问路径
http://localhost:8081/B06_spring_cxf/ws/mobile?wsdl
3.6.2 客户端
步骤:
1、生成客户端调用代码
2、创建服务视图
3、取得porttype
4、调用服务端方法
3.7 Spring整合cxf发布rest服务
3.7.1 实现步骤
第1步:调用公网webservice查询手机号,需要公网webservice的porttype。
第2步:发布rest服务
1、先写1个pojo,需要加上@xmlrootElement注解。
2、编写1个SEI,需要添加@path、@GET、@Produces
3、编写1个SEI实现类,需要调用公网的webservice查询手机号。
4、发布服务,使用spring容器发布服务。
3.7.2 代码实现
3.7.2.1 SEI
rest风格的服务
@Path("/mobile")
public interface MobileRest {
@Path("/code/{code}")
@GET
@Produces({MediaType.APPLICATION_JSON+";charset=utf⑻", MediaType.APPLICATION_XML})
Mobile queryMobile(@PathParam(value = "code")String code);
}
3.7.2.2 SEI实现类
public class MobileRestImpl implements MobileRest {
//公网webservice的porttype
private MobileCodeWSSoap mobileCodeWSSoap;
public void setMobileCodeWSSoap(MobileCodeWSSoap mobileCodeWSSoap) {
this.mobileCodeWSSoap = mobileCodeWSSoap;
}
@Override
public Mobile queryMobile(String code) {
String address = mobileCodeWSSoap.getMobileCodeInfo(code, "");
Mobile mobile = new Mobile();
mobile.setAddress(address);
mobile.setCode(code);
return mobile;
}
}
3.7.2.3 发布服务
<!-- rest服务的实现类 -->
<bean id="mobileRestImpl" class="com.itheima.mobile.rest.MobileRestImpl">
<property name="mobileCodeWSSoap" ref="mobileCodeWSSoap"></property>
</bean>
<!-- 发布rest服务 -->
<jaxrs:server address="/rest">
<jaxrs:serviceBeans>
<ref bean="mobileRestImpl"/>
</jaxrs:serviceBeans>
</jaxrs:server>
3.7.2.4 Url的构成
使用jquery调用cxf(ws)
$(function(){
$("#mybutton").click(function(){
var data = '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:q0="http://server.web.cxf.rl.com/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'
+'<soapenv:Body>'
+'<q0:sayHello>'
+' <arg0>sss</arg0>'
+' </q0:sayHello>'
+'</soapenv:Body>'
+'</soapenv:Envelope>';
$.ajax({
url:'http://localhost:8080/cxf-web-server/services/hello',
type:'post',
dataType:'xml',
contentType:'text/xml;charset=UTF⑻',
data:data,
success:function(responseText){
alert($(responseText).find('return').text());
},
error:function(){
alert("error");
}
})
})
})
生活不易,码农辛苦
如果您觉得本网站对您的学习有所帮助,可以手机扫描二维码进行捐赠