您的位置:首页 > 其它

CXF-08:自定义CXF拦截器来进行权限控制

2016-10-24 23:09 337 查看
自定义拦截器:

需要实现Interceptor接口,实际上,我们一般会继承AbstractPhaseInterceptor;

做一个权限控制,有用户名和密码的时候才允许调用 Web Service:

         * 1 . 在服务器端将系统提供的In拦截器改为自定义拦截器

                 在运行起来的CXF服务端不需要做任何的改动,只需要修改In拦截器:

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import javax.xml.ws.Endpoint;
import org.fjava.cxf.ws.HelloWorld;
import org.fjava.cxf.ws.auth.AuthInterceptor;
import org.fjava.cxf.ws.impl.HelloWorldWs;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.apache.cxf.jaxws.EndpointImpl;
//发布Web Service
public class ServiceMain {
public static void main(String[] args) throws IOException{
HelloWorld hw = new HelloWorldWs();
//调用Endpoint的publish("本机地址","服务的提供者:一个Web Service对象")方法发布Web Service
EndpointImpl ep = (EndpointImpl) Endpoint.publish("http://192.168.0.159:6786/sayHello", hw);
//添加In拦截器,该AuthInterceptor负责检查用户名、密码是否正确
ep.getInInterceptors().add(new AuthInterceptor());
System.out.println("Web Service暴露成功!");
//暴露成功后可以被任何平台的任何语言调用
//检查调用地址http://192.168.*.*/sayHello?wsdl
}
}
                 增加你自己写的AuthInterceptor拦截器继承AbstractPhaseInterceptor:
import java.util.ArrayList;
import java.util.List;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.headers.Header;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
//通过PhaseInterceptor,可以指定拦截器在哪个阶段起作用。
public class AuthInterceptor extends AbstractPhaseInterceptor<SoapMessage> {
public AuthInterceptor(){
//super表示显示调用父类有参数的构造器。
//显示调用父类构造器,程序将不会隐式调用父类无参的构造器。
//---父类构造器里方法AbstractPhaseInterceptor(String phase)
//---phase是指一个拦截阶段
//---CXF文档里有Phase类,这个类里有各个阶段
super(Phase.PRE_INVOKE);//该拦截器将会在"调用之前"拦截Soap消息。
}
//实现自己的拦截器时,需要实现handleMessage方法。
//handleMessage方法中的形参就是被拦截到的Soap消息。
//一旦程序获得了Soap消息,剩下的事情就可以解析Soap消息,或修改Soap消息。
@Override
public void handleMessage(SoapMessage msg) throws Fault{
//下面代码显示"调用之前"成功拦截了信息
System.out.println("----------------"+ msg);
//得到Soap消息的所有Header
List<Header> headers = new ArrayList<Header>();
try {
headers = msg.getHeaders();
} catch (Exception e) {
throw new Fault(new IllegalArgumentException("没有Header,禁止调用!"));
}
//如果根本没有Header
if(headers == null || headers.size() < 1 ){
throw new Fault(new IllegalArgumentException("没有Header,禁止调用!"));
}
System.out.println("headers" + headers);
//假如要求第一个Header携带了用户名、密码信息
Header firstHeader = headers.get(0);
Element ele = (Element)firstHeader.getObject();
NodeList userIds = ele.getElementsByTagName("userId");
NodeList passwords = ele.getElementsByTagName("password");
if(userIds == null || userIds.getLength() != 1){
throw new Fault(new IllegalArgumentException("用户名格式不正确!"));
}
if(passwords == null || passwords.getLength() != 1){
throw new Fault(new IllegalArgumentException("密码格式不正确!"));
}
System.out.println("userIds" + userIds);
System.out.println("passwords" + passwords);
//得到userId元素里的文本内容,以该内容作为用户名
String userId = userIds.item(0).getTextContent();
String password = passwords.item(0).getTextContent();
//实际项目中,是去查询数据库,该用户名、密码是否被授权访问该Web Service。
if(!"admin".equals(userId) && !"admin".equals(password)){
throw new Fault(new IllegalArgumentException("用户名、密码不正确!"));
}
}
}


[b]           若不写客户端拦截器则会报错:没有用户名与密码无法正常调用该接口(用户名、密码填写错误也会报错)
[/b]

[b]服务端输出:[/b]



十月 24, 2016 10:53:45 下午 org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromClass
信息: Creating Service {http://impl.ws.cxf.fjava.org/}HelloWorldWs from class org.fjava.cxf.ws.HelloWorld
十月 24, 2016 10:53:46 下午 org.apache.cxf.endpoint.ServerImpl initDestination
信息: Setting the server's publish address to be http://192.168.0.159:6786/sayHello 2016-10-24 22:53:46.368:INFO::jetty-7.4.2.v20110526
2016-10-24 22:53:46.440:INFO::Started SelectChannelConnector@192.168.0.159:6786 STARTING
2016-10-24 22:53:46.458:INFO::started o.e.j.s.h.ContextHandler{,null}
Web Service暴露成功!
----------------{javax.xml.ws.wsdl.port={http://impl.ws.cxf.fjava.org/}HelloWorldWsPort, org.apache.cxf.service.model.MessageInfo=[MessageInfo INPUT: {http://ws.cxf.fjava.org/}getAllFoods], org.apache.cxf.message.Message.PROTOCOL_HEADERS={Accept=[*/*], Cache-Control=[no-cache], connection=[keep-alive], Content-Length=[164], content-type=[text/xml; charset=UTF-8], Host=[192.168.0.159:6786], Pragma=[no-cache], SOAPAction=[""], User-Agent=[Apache CXF 2.4.1]}, HTTP_CONTEXT_MATCH_STRATEGY=stem, org.apache.cxf.request.url=http://192.168.0.159:6786/sayHello, javax.xml.ws.wsdl.interface={http://ws.cxf.fjava.org/}HelloWorld, org.apache.cxf.request.uri=/sayHello, HTTP.REQUEST=(POST /sayHello)@2066284745 org.eclipse.jetty.server.Request@7b2900c9, org.apache.cxf.transport.https.CertConstraints=null, HTTP.CONFIG=null, Accept=*/*, org.apache.cxf.headers.Header.list=[], org.apache.cxf.message.Message.BASE_PATH=/sayHello, org.apache.cxf.message.Message.PATH_INFO=/sayHello, org.apache.cxf.continuations.ContinuationProvider=org.apache.cxf.transport.http_jetty.continuations.JettyContinuationProvider@40f2cb4a, javax.xml.ws.wsdl.service={http://impl.ws.cxf.fjava.org/}HelloWorldWs, org.apache.cxf.message.Message.IN_INTERCEPTORS=[org.apache.cxf.transport.https.CertConstraintsInterceptor@4008896], org.apache.cxf.message.Message.ENCODING=UTF-8, org.apache.cxf.message.Message.QUERY_STRING=null, HTTP.RESPONSE=HTTP/1.1 200

, org.apache.cxf.security.SecurityContext=org.apache.cxf.transport.http.AbstractHTTPDestination$2@3f0731e7, org.apache.cxf.configuration.security.AuthorizationPolicy=null, org.apache.cxf.async.post.response.dispatch=true, org.apache.cxf.request.method=POST, javax.xml.ws.wsdl.operation={http://ws.cxf.fjava.org/}getAllFoods, org.apache.cxf.transport.Destination=org.apache.cxf.transport.http_jetty.JettyHTTPDestination@715d5504, org.apache.cxf.message.MessageFIXED_PARAMETER_ORDER=false, javax.xml.ws.wsdl.description=http://192.168.0.159:6786/sayHello?wsdl, org.apache.cxf.service.model.BindingMessageInfo=org.apache.cxf.service.model.BindingMessageInfo@2678b3c1, HTTP.CONTEXT=ServletContext@o.e.j.s.h.ContextHandler{,null}, Content-Type=text/xml; charset=UTF-8}
十月 24, 2016 10:53:57 下午 org.apache.cxf.phase.PhaseInterceptorChain doDefaultLogging
警告: Interceptor for {http://impl.ws.cxf.fjava.org/}HelloWorldWs#{http://ws.cxf.fjava.org/}getAllFoods has thrown exception, unwinding now
org.apache.cxf.interceptor.Fault: 没有Header,禁止调用!
at org.fjava.cxf.ws.auth.AuthInterceptor.handleMessage(AuthInterceptor.java:38)
at org.fjava.cxf.ws.auth.AuthInterceptor.handleMessage(AuthInterceptor.java:1)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:263)
at org.apache.cxf.transport.ChainInitiationObserver.onMessage(ChainInitiationObserver.java:118)
at org.apache.cxf.transport.http_jetty.JettyHTTPDestination.serviceRequest(JettyHTTPDestination.java:318)
at org.apache.cxf.transport.http_jetty.JettyHTTPDestination.doService(JettyHTTPDestination.java:286)
at org.apache.cxf.transport.http_jetty.JettyHTTPHandler.handle(JettyHTTPHandler.java:72)
at org.eclipse.jetty.server.handler.ContextHandler.doHandle(ContextHandler.java:939)
at org.eclipse.jetty.server.handler.ContextHandler.doScope(ContextHandler.java:875)
at org.eclipse.jetty.server.handler.ScopedHandler.handle(ScopedHandler.java:117)
at org.eclipse.jetty.server.handler.ContextHandlerCollection.handle(ContextHandlerCollection.java:247)
at org.eclipse.jetty.server.handler.HandlerWrapper.handle(HandlerWrapper.java:110)
at org.eclipse.jetty.server.Server.handle(Server.java:346)
at org.eclipse.jetty.server.HttpConnection.handleRequest(HttpConnection.java:589)
at org.eclipse.jetty.server.HttpConnection$RequestHandler.content(HttpConnection.java:1065)
at org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:823)
at org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:214)
at org.eclipse.jetty.server.HttpConnection.handle(HttpConnection.java:411)
at org.eclipse.jetty.io.nio.SelectChannelEndPoint.handle(SelectChannelEndPoint.java:535)
at org.eclipse.jetty.io.nio.SelectChannelEndPoint$1.run(SelectChannelEndPoint.java:40)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:529)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.IllegalArgumentException: 没有Header,禁止调用!
... 22 more


[b]客户端输出:[/b]



十月 24, 2016 10:53:57 下午 org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromWSDL
信息: Creating Service {http://impl.ws.cxf.fjava.org/}HelloWorldWs from WSDL: http://192.168.0.159:6786/sayHello?wsdl 十月 24, 2016 10:53:57 下午 org.apache.cxf.interceptor.AbstractLoggingInterceptor log
信息: Outbound Message
---------------------------
ID: 1
Address: http://192.168.0.159:6786/sayHello Encoding: UTF-8
Content-Type: text/xml
Headers: {Accept=[*/*], SOAPAction=[""]}
Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><ns2:getAllFoods xmlns:ns2="http://ws.cxf.fjava.org/"/></soap:Body></soap:Envelope>
--------------------------------------
十月 24, 2016 10:53:57 下午 org.apache.cxf.interceptor.AbstractLoggingInterceptor log
信息: Inbound Message
----------------------------
ID: 1
Response-Code: 500
Encoding: UTF-8
Content-Type: text/xml;charset=UTF-8
Headers: {Content-Length=[225], content-type=[text/xml;charset=UTF-8], Server=[Jetty(7.4.2.v20110526)]}
Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Body><soap:Fault><faultcode>soap:Server</faultcode><faultstring>没有Header,禁止调用!</faultstring></soap:Fault></soap:Body></soap:Envelope>
--------------------------------------
Exception in thread "main" javax.xml.ws.soap.SOAPFaultException: 没有Header,禁止调用!
at org.apache.cxf.jaxws.JaxWsClientProxy.invoke(JaxWsClientProxy.java:146)
at com.sun.proxy.$Proxy23.getAllFoods(Unknown Source)
at lee.ClientMain2.main(ClientMain2.java:26)
Caused by: org.apache.cxf.binding.soap.SoapFault: 没有Header,禁止调用!
at org.apache.cxf.binding.soap.interceptor.Soap11FaultInInterceptor.unmarshalFault(Soap11FaultInInterceptor.java:75)
at org.apache.cxf.binding.soap.interceptor.Soap11FaultInInterceptor.handleMessage(Soap11FaultInInterceptor.java:46)
at org.apache.cxf.binding.soap.interceptor.Soap11FaultInInterceptor.handleMessage(Soap11FaultInInterceptor.java:35)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:263)
at org.apache.cxf.interceptor.AbstractFaultChainInitiatorObserver.onMessage(AbstractFaultChainInitiatorObserver.java:104)
at org.apache.cxf.binding.soap.interceptor.CheckFaultInterceptor.handleMessage(CheckFaultInterceptor.java:69)
at org.apache.cxf.binding.soap.interceptor.CheckFaultInterceptor.handleMessage(CheckFaultInterceptor.java:34)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:263)
at org.apache.cxf.endpoint.ClientImpl.onMessage(ClientImpl.java:762)
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.handleResponseInternal(HTTPConduit.java:1582)
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.handleResponse(HTTPConduit.java:1467)
at org.apache.cxf.transport.http.HTTPConduit$WrappedOutputStream.close(HTTPConduit.java:1375)
at org.apache.cxf.io.CacheAndWriteOutputStream.postClose(CacheAndWriteOutputStream.java:47)
at org.apache.cxf.io.CachedOutputStream.close(CachedOutputStream.java:188)
at org.apache.cxf.transport.AbstractConduit.close(AbstractConduit.java:56)
at org.apache.cxf.transport.http.HTTPConduit.close(HTTPConduit.java:623)
at org.apache.cxf.interceptor.MessageSenderInterceptor$MessageSenderEndingInterceptor.handleMessage(MessageSenderInterceptor.java:62)
at org.apache.cxf.phase.PhaseInterceptorChain.doIntercept(PhaseInterceptorChain.java:263)
at org.apache.cxf.endpoint.ClientImpl.doInvoke(ClientImpl.java:510)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:440)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:343)
at org.apache.cxf.endpoint.ClientImpl.invoke(ClientImpl.java:295)
at org.apache.cxf.frontend.ClientProxy.invokeSync(ClientProxy.java:73)
at org.apache.cxf.jaxws.JaxWsClientProxy.invoke(JaxWsClientProxy.java:124)
... 2 more


         * 2 . 在客户端Out拦截器改为自定义拦截器,增加Header元素来进行识别

                 在运行起来的CXF客户端不需要做任何的改动,只需要修改out拦截器:


import java.util.List;
import org.apache.cxf.endpoint.Client;
import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.interceptor.LoggingInInterceptor;
import org.apache.cxf.interceptor.LoggingOutInterceptor;
import org.apache.cxf.jaxws.endpoint.dynamic.JaxWsDynamicClientFactory;
import org.fjava.cxf.ws.Cat;
import org.fjava.cxf.ws.Entry;
import org.fjava.cxf.ws.Food;
import org.fjava.cxf.ws.HelloWorld;
import org.fjava.cxf.ws.StringFood;
import org.fjava.cxf.ws.User;
import org.fjava.cxf.ws.auth.AddHeaderInterceptor;
import org.fjava.cxf.ws.impl.HelloWorldWs;
public class ClientMain {
public static void main(String[] args) {
//这是命令生成的类,该类的实例可当成工厂来使用
HelloWorldWs factory = new HelloWorldWs();
//无参的方法,返回的是远程Web Service服务端的代理,服务端不能关闭。
HelloWorld helloWorld = factory.getHelloWorldWsPort();
Client client = ClientProxy.getClient(helloWorld);
client.getOutInterceptors().add(new AddHeaderInterceptor("admin","admin"));
client.getOutInterceptors().add(new LoggingOutInterceptor());
StringFood allFoods = helloWorld.getAllFoods();
List<Entry> entries = allFoods.getEntries();
for (Entry entry : entries) {
System.out.println(entry.getKey() + " " + entry.getValue().getDescribe());
}
}
}
                 增加你自己写的AddHeaderInterceptor拦截器继承AbstractPhaseInterceptor:
import java.util.List;
import javax.xml.namespace.QName;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.headers.Header;
import org.apache.cxf.helpers.DOMUtils;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
public class AddHeaderInterceptor extends AbstractPhaseInterceptor<SoapMessage> {
private String userId;
private String password;
public AddHeaderInterceptor(String userId, String password) {
super(Phase.PREPARE_SEND);//在准备发送Soap消息时启用该拦截器
this.userId = userId;
this.password = password;
}
@Override
public void handleMessage(SoapMessage msg) throws Fault {
List<Header> headers = msg.getHeaders();
//创建Document对象
Document document = DOMUtils.createDocument();
Element eleAuthHeader = document.createElement("authHeader");
//此处创建的元素应该与服务器的拦截元素相同
Element eleUserId = document.createElement("userId");
eleUserId.setTextContent(userId);
Element elePassword = document.createElement("password");
elePassword.setTextContent(password);
eleAuthHeader.appendChild(eleUserId);
eleAuthHeader.appendChild(elePassword);
/*
* 上面代码生成了一个如下XML文档片段:
* <authHeader>
*	<userId></userId>
*	<password></password>
*</authHeader>
*/
//把ele元素包装成Header,并添加到SOAP消息的Header列表中。
headers.add(new Header(new QName("fjava"),eleAuthHeader));
}
}


           自定义客户端拦截器后运行客户端


服务端输出:

十月 24, 2016 10:57:22 下午 org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromClass
信息: Creating Service {http://impl.ws.cxf.fjava.org/}HelloWorldWs from class org.fjava.cxf.ws.HelloWorld
十月 24, 2016 10:57:22 下午 org.apache.cxf.endpoint.ServerImpl initDestination
信息: Setting the server's publish address to be http://192.168.0.159:6786/sayHello 2016-10-24 22:57:22.875:INFO::jetty-7.4.2.v20110526
2016-10-24 22:57:22.947:INFO::Started SelectChannelConnector@192.168.0.159:6786 STARTING
2016-10-24 22:57:22.964:INFO::started o.e.j.s.h.ContextHandler{,null}
Web Service暴露成功!
----------------{javax.xml.ws.wsdl.port={http://impl.ws.cxf.fjava.org/}HelloWorldWsPort, org.apache.cxf.service.model.MessageInfo=[MessageInfo INPUT: {http://ws.cxf.fjava.org/}getAllFoods], org.apache.cxf.message.Message.PROTOCOL_HEADERS={Accept=[*/*], Cache-Control=[no-cache], connection=[keep-alive], Content-Length=[264], content-type=[text/xml; charset=UTF-8], Host=[192.168.0.159:6786], Pragma=[no-cache], SOAPAction=[""], User-Agent=[Apache CXF 2.4.1]}, HTTP_CONTEXT_MATCH_STRATEGY=stem, org.apache.cxf.request.url=http://192.168.0.159:6786/sayHello, javax.xml.ws.wsdl.interface={http://ws.cxf.fjava.org/}HelloWorld, org.apache.cxf.request.uri=/sayHello, HTTP.REQUEST=(POST /sayHello)@1572523437 org.eclipse.jetty.server.Request@5dbacdad, org.apache.cxf.transport.https.CertConstraints=null, HTTP.CONFIG=null, Accept=*/*, org.apache.cxf.headers.Header.list=[org.apache.cxf.binding.soap.SoapHeader@131548ee], org.apache.cxf.message.Message.BASE_PATH=/sayHello, org.apache.cxf.message.Message.PATH_INFO=/sayHello, org.apache.cxf.continuations.ContinuationProvider=org.apache.cxf.transport.http_jetty.continuations.JettyContinuationProvider@5a670b0b, javax.xml.ws.wsdl.service={http://impl.ws.cxf.fjava.org/}HelloWorldWs, org.apache.cxf.message.Message.IN_INTERCEPTORS=[org.apache.cxf.transport.https.CertConstraintsInterceptor@23062d8b], org.apache.cxf.message.Message.ENCODING=UTF-8, org.apache.cxf.message.Message.QUERY_STRING=null, HTTP.RESPONSE=HTTP/1.1 200

, org.apache.cxf.security.SecurityContext=org.apache.cxf.transport.http.AbstractHTTPDestination$2@6a9812a3, org.apache.cxf.configuration.security.AuthorizationPolicy=null, org.apache.cxf.async.post.response.dispatch=true, org.apache.cxf.request.method=POST, javax.xml.ws.wsdl.operation={http://ws.cxf.fjava.org/}getAllFoods, org.apache.cxf.transport.Destination=org.apache.cxf.transport.http_jetty.JettyHTTPDestination@25efc201, org.apache.cxf.message.MessageFIXED_PARAMETER_ORDER=false, javax.xml.ws.wsdl.description=http://192.168.0.159:6786/sayHello?wsdl, org.apache.cxf.service.model.BindingMessageInfo=org.apache.cxf.service.model.BindingMessageInfo@1fa9e31c, HTTP.CONTEXT=ServletContext@o.e.j.s.h.ContextHandler{,null}, Content-Type=text/xml; charset=UTF-8}
headers[org.apache.cxf.binding.soap.SoapHeader@131548ee]
userIdscom.sun.org.apache.xerces.internal.dom.DeepNodeListImpl@51b621a3
passwordscom.sun.org.apache.xerces.internal.dom.DeepNodeListImpl@c5a7e3e


客户端输出:

十月 24, 2016 11:04:14 下午 org.apache.cxf.service.factory.ReflectionServiceFactoryBean buildServiceFromWSDL
信息: Creating Service {http://impl.ws.cxf.fjava.org/}HelloWorldWs from WSDL: http://192.168.0.159:6786/sayHello?wsdl 十月 24, 2016 11:04:14 下午 org.apache.cxf.interceptor.AbstractLoggingInterceptor log
信息: Outbound Message
---------------------------
ID: 1
Address: http://192.168.0.159:6786/sayHello Encoding: UTF-8
Content-Type: text/xml
Headers: {Accept=[*/*], SOAPAction=[""]}
Payload: <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"><soap:Header><authHeader><userId>admin</userId><password>admin</password></authHeader></soap:Header><soap:Body><ns2:getAllFoods xmlns:ns2="http://ws.cxf.fjava.org/"/></soap:Body></soap:Envelope>
--------------------------------------
蟹王汉堡 橙色,亮金色,我的宝贝,我的爱!
海绵金币 吃着金币样的甜甜饼,想着海绵宝宝赚的钱被扣了,哈哈哈,爽气!
一个汉堡 是三层的,有夹层哦!
火腿肠 这是章鱼哥从岸上偷运来的,据说很美味!


[b]做一个权限控制,有用户名和密码的时候才允许调用 Web Service 该功能完成!
[/b]

希望对你有帮助,祝你有一个好心情,加油!

若有错误、不全、可优化的点,欢迎纠正与补充;转载请注明出处!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息