http與websocket(基於SignalR)兩種協議下的跨域基於ASP.NET MVC--竹子整理


這段時間,項目涉及到移動端,這就不可避免的涉及到了跨域的問題。這是本人第一次接觸跨域,有些地方的配置是有點麻煩,導致一開始的不順。

至於websocket具體是什么意義,用途如何:請百度。

簡單說就是建立一個基於互聯網的實時通信。

在這里整理下這些內容,方便日后回顧。

一:介紹了WebSocket下的基於SignalR的跨域與不跨域例子

二:簡單介紹了Http下的跨域問題

 

Ⅰ.WebSocket下的跨域

如果使用原生的方法來開發WebSocket應用,還是比較復雜的,不過好在Asp.net給我們提供了一個框架:

SignalR:

  微軟支持的運行在.NET平台上集客戶端與服務器於一體的庫。簡單來說就是給我們提供了服務端的類庫加上前端的JS庫。

首先拋開跨域的問題,先寫一個SignalR不跨域的例子:

1、NuGet來獲得我們所需要的程序集:

SignalR:我們的框架

 

2、新建個繼承於Hub的類

 1 /// <summary>
 2     /// 對應不跨域的方法
 3     /// </summary>
 4     public class ChatHub : Hub
 5     {
 6         public void Send(string message)
 7         {
 8             //接收數據,再傳輸到指定或全部的客戶端動態方法
 9             Clients.All.addNewMessageToPage(message);
10         }
11 }

3、新建個Startup類:

只需添加一句話:app.MapSignalR();

具體的完整代碼在下面的Startup里都有。

 

4、這樣我們的服務端代碼就完成了,接下來就是客戶端代碼:

 1 $(function() { 
 2 
 3  var chat = $.connection.chat;
 5  $.connection.hub.start();
 9  chat.addMessage = function(message){
11  alert(message);
13 }
14 
15 $("#btn").click(function(){
16 
17   chat.send("給服務端發送信息");
18 
19 }); 
20 
21 });

 

5、好了,現在來分析下客戶端代碼:

前面兩行就是基本的猜也能猜到的獲取實例,然后開啟這個連接。(相當於啟動線程)。第三行定義了一個函數,這個函數是與服務端的ChatHub類里的動態方法addNewMessageToPage對應的回調函數。服務端一執行動態方法,對應的客戶端(這個由服務端發送時的選擇有關,這里選擇就是All,也就是所有的客戶端)就執行對應的回調函數。

現在知道了動態方法跟回調函數是一一對應的,服務端觸發動態方法,客戶端執行回調函數。那么動態方法什么時候執行呢,顯而易見,是客戶端發送請求時,也就是這里“調用”服務端定義的Send方法時,觸發了動態方法。(有點啰嗦了,為了保證人人看懂)。這樣一來,最后這句 chat.send("給服務端發送信息");就很容易理解了。就是請求。

 

二、跨域(一)

先來從NuGet獲取我們需要跨域所需的程序集:

 

有了前面的基礎,對於SignalR也應該有了個基礎的印象了,下面就來講講如何用SignalR實現跨域通信。

SignalR的實現機制與.NET WCF 或Remoting是相似的,都是使用遠程代理來實現,在具體使用上,有兩種不同目的的接口:PersistentConnection和Hub,其中PersistentConnection實現了長時間的輪詢,Hub用來解決實時信息的交換問題。

先來了解一下基於PersistentConnection接口的跨域。

我們的跨域一:

1、注釋掉我們Startup類中的唯一一行代碼:app.MapSignalR();

然后換上我們跨域的代碼:

1 //這個參數"/echo",是我們自己定義的一個路由,與客戶端創建SignalR的實例時對應。
2             app.Map("/echo",
3             map =>
4             {
5                 map.UseCors(CorsOptions.AllowAll);
6                 map.RunSignalR<EchoConnection>();
7             }
8         );

2、新建一個類,繼承於我們的接口PersistentConnection

通信的本質就是建立一個客戶端與服務端的一個連接,SignalR就是我們幫助我們建立這個連接的“中間件”

這個類中就實現了接口中定義的一系列方法,如連接建立,斷開連接,收到信息。這樣一來,我們的chatHub類實際上就已經不起作用了,通信的方法都可以寫在這個EchoConnection類中。

 1 public class EchoConnection:PersistentConnection
 2     {
 3         /// <summary>
 4         /// 當前連接數
 5         /// </summary>
 6         private static int _connections = 0;
 7         /// <summary>
 8         /// 連接建立時執行
 9         /// </summary>
10         /// <param name="request"></param>
11         /// <param name="connectionId"></param>
12         /// <returns></returns>
13         protected override async Task OnConnected(IRequest request, string connectionId)
14         {
15             Interlocked.Increment(ref _connections);
16             await Connection.Send(connectionId, "Hi, " + connectionId + "!");
17             await Connection.Broadcast("新連接 " + connectionId + "開啟. 當前連接數: " + _connections);
18             
19         }
20         /// <summary>
21         /// 連接關閉時執行
22         /// </summary>
23         /// <param name="request"></param>
24         /// <param name="connectionId"></param>
25         /// <returns></returns>
26         protected Task OnDisconnected(IRequest request, string connectionId)
27         {
28             Interlocked.Decrement(ref _connections);
29             return Connection.Broadcast(connectionId + " 連接關閉. 當前連接數: " + _connections);
30         }
31         /// <summary>
32         /// 服務器接收到前台發送的消息時執行 發送請求 connection.send("信息");
33         /// </summary>
34         /// <param name="request"></param>
35         /// <param name="connectionId"></param>
36         /// <param name="data"></param>
37         /// <returns></returns>
38         protected override Task OnReceived(IRequest request, string connectionId, string data)
39         {
40             var message = connectionId + ">> " + data;
41             return Connection.Broadcast(message);
42         }
43 
44 }
View Code

 

3、接下來就是客戶端代碼:

同樣,第一步,獲取實例,建立連接

var connection = $.connection("http://localhost:23013/echo");//echo就是我們之前在Startup類中定義的路由。

connection.start();

建立連接后,就會觸發服務端的OnConnected事件,然后這個事件中又有觸發兩個方法:Broadcast,Send。這個就相當於我們不跨域時自己定義的動態方法,so,我們就應該想到客戶端應有對應的回調函數:Broadcast跟Send。但是!由於這不是我們自定義的,所以,名字就不是我們想的這樣啦~~對於接收信息,我們直接調用connection.received(function (data){})來接收,對於發送信息到服務端也不需要我們手動來先在服務端定義方法如不跨域時在ChatHub中定義的send方法,直接connection(實例名).send()就可以發消息到服務端。而在服務端,是由OnReceived來處理。具體的代碼同樣在本文的最后。

 跨域(一)小結:

  

 1  /// <summary>
 2     /// 對應跨域的第一種方法 
 3     /// 1、前台直接用connection(實例).send()就可以發消息到服務端,用connection.received(function (data) {
 4     ///                                                                              $("body").append(data + "<br />");
 5     ///                                                                           });就能接收服務端放過來的消息
 6     /// 2、后台Connection.Send(connectionId, "消息");可發送消息給指定的客戶端
 7     ///    用Connection.Broadcast("消息");可發送給所有的客戶端
 8     ///    用protected override Task OnReceived(IRequest request, string connectionId, string data)
 9     ///     {
10     ///          var message = connectionId + ">> " + data;
11     ///          return Connection.Broadcast(message);
12     ///     }接收客戶端發送來的消息,進行處理
13     /// </summary>

 

三、跨域(二)

 相比PersistentConnection這種輪詢機制,基於Hub利用js動態載入執行方法才是我們所說的websocket技術。

Let’s start

1、有的朋友應該知道我們的第一步都是為前端可以創建實例准備的了

再一次修改我們的Startup類,之前的注釋掉:

 

 1 app.Map("/signalr", map =>
 2             {
 3                 map.UseCors(CorsOptions.AllowAll);
 4                 var hubConfiguration = new HubConfiguration
 5                 {
 6                     EnableJSONP = true//跨域的關鍵語句
 7                 };
 8                 map.RunSignalR(hubConfiguration);
 9             });
10             app.MapSignalR();

 

上面的/signalR即我們這次設置的路由。一樣,供客戶端匹配

 

1、重新建立我們的MyHub類,跟最開始不跨域的步驟一致,新建類,繼承於Hub。然后實現接口,里面也有連接、斷開連接、重連事件。以及我們自定義的方法也都寫在這里:

 

 1 /// <summary>
 2     /// 對應跨域的第二種方法
 3     /// public IGroupManager Groups { get; set; } hub類中還有個組屬性,尚待開發,待看文檔,再寫demo
 4     /// </summary>
 5     public class MyHub : Hub
 6     {
 7         /// <summary>
 8         /// 連接前執行
 9         /// </summary>
10         /// <returns></returns>
11         public override System.Threading.Tasks.Task OnConnected()
12         {
13             //Clients.All.sayHello("連接成功");
14             Clients.Caller.sayHello("連接成功");//當前請求的用戶
15             return base.OnConnected();
16         }
17 
18         /// <summary>
19         /// 斷開鏈接時執行
20         /// </summary>
21         /// <param name="stopCalled"></param>
22         /// <returns></returns>
23         public override System.Threading.Tasks.Task OnDisconnected(bool stopCalled)
24         {
25             return base.OnDisconnected(stopCalled);
26         }
27 
28         /// <summary>
29         /// 重新建立連接 如服務器重啟的時候,或者前台獲取超時仍在等待,重連上時
30         /// </summary>
31         /// <returns></returns>
32         public override System.Threading.Tasks.Task OnReconnected()
33         {
34             return base.OnReconnected();
35         }
36 
37         public void Hello(string name)//方法名稱首字母大小寫都與前台匹配上 前台首字母必須小寫
38         {
39             //動態方法,與前台的回調函數名稱一致
40             Clients.All.sayHello2("第二次");
41             Clients.All.sayHello3(Context.ConnectionId);//第三個回調函數,返回鏈接的ConnectionId
42         }
43     }
View Code

 

 3、我們的客戶端

第一步:獲取實例,建立連接

var chat = $.connection.myHub;  //獲取服務端實例  首字母小寫,不是跟服務端一致的MyHub

chat.connection.url = "http://localhost:23013/signalr";測試項目的地址

$.connection.hub.start();

第二步、接下來就是跟服務端打個招呼

Chat.server.hello(“hello”);//對應的方法中有兩個動態方法,sayHello2,sayHello3

第三步、接下來就是我們的回調函數,對應的回調函數就可以有兩個,與上面對應。

   chat.client.sayHello2 = function(msg) {

                   alert(msg);

    };

chat.client.sayHello3 = function(msg) {

                   alert(msg);

    };

 

WebSocket小結:基於Hub接口的跨域方法的可擴展性還是很強的,這里有幾個本人總結的注意點(針對Hub跨域):

1、回調函數 函數名要匹配,參數可以不匹配,后台傳過來一個就執行一個,即若后台同時觸發了兩個同名的sayHello 依次執行

 2、一定要有回調函數,不然不算連接成功,可以不調用服務器端的方法。若只是調用服務器端方法,沒寫回調函數,依然不算連接成功

3、在連接成功的情況下,后台先執行OnConnected事件,再執行前台調用的某個方法

4、用回調函數來判斷是否真的連接成功,$.connection.hub.start().done里直接輸出連接成功,是假成功。

5、所以同樣,斷開連接是否成功也應用回調函數來判斷,這個回調函數對應后台代碼應在OnDisconnected事件里

6、第五點失敗,sayGoodBye是在執行完這個事件(OnDisconnected)后才傳輸到前台,而事件中執行完已經把鏈接斷開了,前台是接收不到的。

 

Ⅱ、HTTP下的跨域

相對於websocket協議的跨域,http應該是簡單的,網上也有很多資料。這里就當筆者自己的筆記吧。

http下的跨域指異步的傳輸,也就是說form表單提交這種非異步方式是不存在跨域問題的。網上最多的異步跨域是jsonp方式。

 

Jquery跨域請求 

在JavaScript中,有一個很重要的安全性限制,被稱為“Same- Origin Policy”(同源策略)。這一策略對於JavaScript代碼能夠訪問的頁面內容做了很重要的限制,
即JavaScript只能訪問與包含它的文檔或腳本 在同一域名下的內容。不同域名下的腳本不能互相訪問,即便是子域也不行。關於同源策略,讀者可百度更詳細的解釋,這里不再贅述。
但是有時候又不可避免地需要進行跨域操作,這時候“同源策略”就是一個限制了,怎么辦呢?采用JSONP跨域GET請求是一個常用的解決方案,下面我們來看一下JSONP跨域是如何實現的,
並探討下JSONP跨域的原理。

 

這里提到了JSONP,那有人就問了,它同JSON有什么區別不同和區別呢,接下我們就來看看,百度百科有以下說明:

JSON(JavaScript Object Notation) 是一種輕量級的數據交換格式。它基於JavaScript(Standard ECMA-262 3rd Edition - December 1999)的一個子集。 
JSON采用完全獨立於語言的文本格式,但是也使用了類似於C語言家族的習慣(包括C, C++, C#, Java, JavaScript, Perl, Python等)。這些特性使JSON成為理想的數據交換語言。
易於人閱讀和編寫,同時也易於機器解析和生成(網絡傳輸速度快)。
JSONP(JSON with Padding)是JSON的 一種“使用模式”,可用於解決主流瀏覽器的跨域數據訪問的問題。由於同源策略,一般來說位於 server1.example.com 的網頁無法與
不是 server1.example.com的服務器溝通,而 HTML 的<script> 元素是一個例外。利用 <script> 元素的這個開放策略,網頁可以得到從其他來源動態產生的 JSON 資料,
而這種使用模式就是所謂的 JSONP。用 JSONP 抓到的資料並不是 JSON,而是任意的JavaScript,用 JavaScript 直譯器執行而不是用 JSON 解析器解析。

 

到這里,應該明白了,JSON是一種輕量級的數據交換格式,像xml一樣,是用來描述數據間的。JSONP是一種使用JSON數據的方式,返回的不是JSON對象,是包含JSON對象的javaScript腳本。
那JSONP是如何工作的呢,我們知道,由於同源策略的限制,XmlHttpRequest只允許請求當前源(域名、協議、端口)的資源。若要跨域請求出於安全性考慮是不行的,但是我們發現,
Web頁面上調用js文件時則不受是否跨域的影響,而且擁有”src”這個屬性的標簽都擁有跨域的能力,比如<script>、<img>、<iframe>,這時候,聰明的程序猿就想到了變通的方法,
如果要進行跨域請求, 通過使用html的script標記來進行跨域請求,並在響應中返回要執行的script代碼,其中可以直接使用JSON傳遞 javascript對象。即在跨域的服務端生成JSON數據,
然后包裝成script腳本回傳,着不就突破同源策略的限制,解決了跨域訪問的問題了么。

下面我們就看下怎么實現:

前端代碼:

function CallWebServiceByJsonp() {

    $("#SubEquipmentDetails").html('');

    $.ajax({

      type: "GET",

      cache: false,

      url: "http://servername/webservice/webservice.asmx/GetSingleInfo",

      data: { strCparent: $("#Equipment_ID").val() },

      dataType: "jsonp",

      //jsonp: "callback",

      jsonpCallback: "OnGetMemberSuccessByjsonp"

   });

}

 

function OnGetMemberSuccessByjsonp(data) {

    //處理data

    alert(data);

}

 

后端的WebService代碼:



[WebMethod]

[ScriptMethod(ResponseFormat = ResponseFormat.Json, UseHttpGet = true)]

public void GetSingleInfo(string strCparent)

{

    string ret = string.Empty;

    HttpContext.Current.Response.ContentType = "application/json;charset=utf-8";

    string jsonCallBackFunName = HttpContext.Current.Request.Params["callback"].ToString();

    //string jsonCallBackFunName1 = HttpContext.Current.Request.QueryString["callback"].Trim();

     

    //上面代碼必須

    //中間代碼執行自己的業務操作,可返回自己的任意信息(多數據類型)

 

    BLL.equipment eq_bll = new BLL.equipment();

    List<Model.equipment> equipmentList = new List<Model.equipment>();

    equipmentList = eq_bll.GetModelEquimentList(strCparent);

    ret = JsonConvert.SerializeObject(equipmentList);

 

    //下面代碼必須

    HttpContext.Current.Response.Write(string.Format("{0}({1})", jsonCallBackFunName, ret));

    HttpContext.Current.Response.End();

}


 
   

 

如上所示,前端的CallWebServiceByJsonp方法采用jQuery的ajax方法調用后端的Web服務GetSingleInfo方法,后台的GetSingleInfo方法,
使用前端的回調方法OnGetMemberSuccessByjsonp包裝后台的業務操作的JSON對象,返回給前端一段javascript片段執行。巧妙的解決了跨域訪問問題。

 

JSONP的缺點:

JSONP不提供錯誤處理。如果動態插入的代碼正常運行,你可以得到返回,但是如果失敗了,那么什么都不會發生。

鏈接:http://www.cnblogs.com/JerryTian/p/4194900.html
View Code

 

  

我比較喜歡下面這種簡潔的方式:

$.ajax({
    type:"GET",                                                                           
url:"http://localhost:6874/Admin/Login/IsLegal", //跨域URL dataType:"json", data:{ par1:”參數1”, par2:”參數2” }, success:function (result){ if(result.id == "123"){ $("#div1").html("驗證成功,可進行跳轉"); }else{ $("#div1").html(result.name); } }, error:function (XMLHttpRequest, textStatus,errorThrown) { alert(errorThrown); // 調用本次AJAX請求時傳遞的options參數 } });

服務端代碼:

1 public ActionResult IsLegal(string par1,string par2)
2     {
3             var str = new { id = "123", name = "joey" };
4 
5             Httpcontext.Response.Appendheader("access-control-allow-origin", "*");
6 
7             return json(str, jsonrequestbehavior.allowget);
8     }

或者定義一個特性標簽,這樣我們在需要跨域的action上打上特性標簽即可。

 

public class CrossSiteAttribute : ActionFilterAttribute
    {
        private const string Origin = "Origin";
        private const string AccessControlAllowOrigin = "Access-Control-Allow-Origin";
        private const string originHeaderdefault = "*";
        public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
        {
             actionExecutedContext.Response.Headers.Add(AccessControlAllowOrigin, originHeaderdefault);
        }
    }

 

這樣就定義了一個特性標簽,[CrossSite]

使用:

[CrossSite]
public ActionResult Test()
{
   return  Content("跨域OK");       
}

 上面均是針對MVC或web api的,即對Access-Control-Allow-Origin進行了一個封裝,本質都是對配置文件進行修改。所以最直接的就是配置文件修改:

webconfig中添加:

 

簡單實用,同時可以進行post傳輸。

其他的跨域策略

 

一是通過Flash插件發送HTTP請求,這種方式可以繞過瀏覽器的安全限制,但必須安裝Flash,並且跟Flash交互。不過Flash用起來麻煩,而且現在用得也越來越少了。

 

二是通過在同源域名下架設一個代理服務器來轉發,JavaScript負責把請求發送到代理服務器:

 

'/proxy?url=http://www.sina.com.cn' 

 

代理服務器再把結果返回,這樣就遵守了瀏覽器的同源策略。這種方式麻煩之處在於需要服務器端額外做開發。

 

第三種方式稱為JSONP,它有個限制,只能用GET請求,並且要求返回JavaScript。這種方式跨域實際上是利用了瀏覽器允許跨域引用JavaScript資源:

JSONP通常以函數調用的形式返回,例如,返回JavaScript內容如下:

Foo('data')

這樣一來,我們如果在頁面中先准備好foo()函數,然后給頁面動態加一個<script>節點,相當於動態讀取外域的JavaScript資源,最后就等着接收回調了。

 

 

 

最后是之前上文一直提到的“本文最后”

  1 #region 不跨域的方法
  2             //app.MapSignalR();
  3             #endregion
  4 
  5             #region 跨域的第一種方法
  6             //這個參數"/echo",是我們自己定義的一個路由,與客戶端創建SignalR的實例時對應。
  7             app.Map("/echo",
  8             map =>
  9             {
 10                 map.UseCors(CorsOptions.AllowAll);
 11                 map.RunSignalR<EchoConnection>();
 12             }
 13         );
 14             #endregion
 15 
 16             #region 第一種方法前端調用代碼
 17             //    <script src="js/jquery-1.8.2.js"></script>
 18             //    <script src="js/jquery.signalR-2.2.0.min.js"></script>
 19             //    <script type="text/javascript">
 20             //    $(function () {
 21             //        var connection = $.connection("http://localhost:23013/echo");//對應的服務器端地址
 22             //        //var connection = $.connection("http://192.168.137.1/q");
 23             //        connection.logging = true;
 24             //        connection.received(function (data) {
 25             //            $("body").append(data + "<br />");
 26             //        });
 27             //        connection.error(function (err) {
 28             //            alert("存在一個錯誤. \n" +
 29             //                "Error: " + err.message);
 30             //        });
 31             //        connection.start().done(function () {
 32             //            $("#send").click(function () {
 33             //                connection.send($("#text").val());
 34             //                $("#text").val("").focus();
 35             //            });
 36             //        });
 37             //    });
 38             //</script>
 39             #endregion
 40 
 41             #region 跨域的第二種方法
 42             //app.Map("/signalr", map =>
 43             //{
 44             //    map.UseCors(CorsOptions.AllowAll);
 45             //    var hubConfiguration = new HubConfiguration
 46             //    {
 47             //        EnableJSONP = true//跨域的關鍵語句
 48             //    };
 49             //    map.RunSignalR(hubConfiguration);
 50             //});
 51             //app.MapSignalR();
 52 
 53             #endregion
 54 
 55             #region 第二種方法前台代碼
 56             //            <script src="js/jquery-1.8.2.js"></script>
 57             //            <script src="js/jquery.signalR-2.2.0.min.js"></script>
 58             //            <script src="http://localhost:23013/signalr/js" type="text/javascript" charset="utf-8"></script>地址是服務器端地址加上/設置的路由/js
 59             //            <script type="text/javascript">
 60             //            var chat = $.connection.myHub;  //獲取服務端實例  首字母小寫,不是跟服務端一致的MyHub
 61             //console.log(chat);
 62             //chat.connection.url = "http://localhost:23013/signalr";測試項目的地址
 63             //            chat.connection.url = "http://localhost:6874/joey";
 64             //1、回調函數 函數名要匹配,參數可以不匹配,后台傳過來一個就執行一個,即若后台同時觸發了兩個同名的sayHello
 65             //依次執行
 66             //2、一定要有回調函數,不然不算連接成功,可以不調用服務器端的方法。若只是調用服務器端方法,沒寫回調函數,
 67             //依然不算連接成功
 68             //3、在連接成功的情況下,后台先執行OnConnected事件,再執行前台調用的某個方法
 69             //4、用回調函數來判斷是否真的連接成功,$.connection.hub.start().done里直接輸出連接成功,是假成功。
 70             //5、所以同樣,斷開連接是否成功也應用回調函數來判斷,這個回調函數對應后台代碼應在OnDisconnected事件里
 71             //6、第五點失敗,sayGoodBye是在執行完這個事件(OnDisconnected)后才傳輸到前台,而事件中執行完已經把鏈接斷開了,前台是接收不到的
 72             //chat.client.sayHello = function(connectionCode) {
 73             //                if(connectionCode == 1)
 74             //                {
 75             //                    $("#IsConnSuc").val("1");//給隱藏字段賦值,1表示連接成功
 76             //                    alert("連接成功");
 77             //                }
 78             //                else
 79             //                {
 80             //                    alert("連接失敗");
 81             //                }
 82             //            };
 83             //-----------注釋掉的---------------------------------------------------------------------
 84             //            chat.client.sayGoodBye = function(connectionCode) {
 85             //                if(connectionCode == 1)
 86             //                {
 87             //                    alert("斷開連接成功");
 88             //                }
 89             //                else
 90             //                {
 91             //                    alert("斷開連接失敗");
 92             //                }
 93             //            };
 94             //            //建立連接
 95             //            $.connection.hub.start().done(function() {
 96             //                // Call the Send method on the hub.
 97             //                chat.server.hello("fd"); //調用服務器端定義的方法  方法名首字母小寫,后台對應的方法首字母大小寫都能匹配上
 98             //            });
 99             //-----------注釋掉的---------------------------------------------------------------------
100             //    $(function(){
101             //        $("#start").click(function(){
102             //            //建立連接
103             //            $.connection.hub.start();
104             //        });
105 
106             //        $("#stop").click(function() {
107             //            if($("#IsConnSuc").val() == "1"){
108             //                //有連接時,才執行斷開連接操作
109             //                $.connection.hub.stop();
110             //                $("#IsConnSuc").val("0");
111             //                alert("斷開連接成功");
112             //            }
113 
114             //        });
115 
116             //    });
117 
118             //</script>
119             #endregion
View Code

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM