AJAX入門這一篇就夠了

1、什麼是Ajax

Ajax(Asynchronous JavaScript and XML) 異步JavaScript和XMLjavascript

Ajax其實是下面這幾種技術的融合:html

  • (1)XHTML和CSS的基於標準的表示技術
  • (2)DOM進行動態顯示和交互
  • (3)XML和XSLT進行數據交換和處理
  • (4)XMLHttpRequest進行異步數據檢索
  • (5)Javascript將以上技術融合在一塊兒

客戶端與服務器,能夠在【沒必要刷新整個瀏覽器】的狀況下,與服務器進行異步通信的技術前端

2、爲何咱們須要Ajax?

在咱們以前的開發,每當用戶向服務器發送請求,哪怕只是須要更新一點點的局部內容,服務器都會將整個頁面進行刷新。java

  • 性能會有所下降(一點內容,刷新整個頁面!)
  • 用戶的操做頁面會中斷(整個頁面被刷新了)

Ajax就是可以作到局部刷新node

這裏寫圖片描述


3、XMLHttpRequest

XMLHttpRequest對象是Ajax中最重要的一個對象使用Ajax更多的是編寫客戶端代碼,而不是服務端的代碼。web

3.1XMLHttpRequest 工做原理

傳統的web前端與後端的交互中,瀏覽器直接訪問Tomcat的Servlet來獲取數據。Servlet經過轉發把數據發送給瀏覽器。ajax

當咱們使用AJAX以後,瀏覽器是先把請求發送到XMLHttpRequest異步對象之中,異步對象對請求進行封裝,而後再與發送給服務器。服務器並非以轉發的方式響應,而是以流的方式把數據返回給瀏覽器數據庫

XMLHttpRequest異步對象會不停監聽服務器狀態的變化,獲得服務器返回的數據,就寫到瀏覽器上【由於不是轉發的方式,因此是無刷新就可以獲取服務器端的數據】編程

這裏寫圖片描述


3.2建立XMLHttpRequest對象

要建立XMLHttpRequest對象是要分兩種狀況考慮的:json

  • 在IE6如下的版本
  • 在IE6以上的版本以及其餘內核的瀏覽器(Mozilla)等
<script type="text/javascript">

    var httpRequest;

    if(window.XMLHttpRequest) {

        //在IE6以上的版本以及其餘內核的瀏覽器(Mozilla)等
        httpRequest = new XMLHttpRequest();
    }else if(window.ActiveXObject) {

        //在IE6如下的版本
        httpRequest = new ActiveXObject();
    }


</script>

3.3瞭解XMLHttpRequest對象的屬性和方法

3.3.1方法

  • open()(String method,String url,boolean asynch,String username,String password)
  • send(content)
  • setRequestHeader(String header,String value)
  • getAllResponseHeaders()
  • getResponseHeader(String header)
  • abort()

經常使用的方法就是黑色粗體的前三個

  • open():該方法建立http請求

    • 第一個參數是指定提交方式(post、get)
    • 第二個參數是指定要提交的地址是哪
    • 第三個參數是指定是異步仍是同步(true表示異步,false表示同步)
    • 第四和第五參數在http認證的時候會用到。是可選的
  • setRequestHeader(String header,String value):設置消息頭(使用post方式纔會使用到,get方法並不須要調用該方法)

    • xmlhttp.setRequestHeader("Content-type","application/x-www-form-urlencoded");
  • send(content):發送請求給服務器

    • 若是是get方式,並不須要填寫參數,或填寫null
    • 若是是post方式,把要提交的參數寫上去

3.3.2屬性

  • onreadystatechange:請求狀態改變的事件觸發器(readyState變化時會調用此方法),通常用於指定回調函數
  • readyState:請求狀態readyState一改變,回調函數被調用,它有5個狀態

    • 0:未初始化
    • 1:open方法成功調用之後
    • 2:服務器已經應答客戶端的請求
    • 3:交互中。Http頭信息已經接收,響應數據還沒有接收。
    • 4:完成。數據接收完成

這裏寫圖片描述

  • responseText:服務器返回的文本內容
  • responseXML:服務器返回的兼容DOM的XML內容
  • status:服務器返回的狀態碼
  • statusText:服務器返回狀態碼的文本信息

上面有兩個地方都說起了回調函數,回調函數是什麼??

回調函數就是接收服務器返回的內容!

這裏寫圖片描述


4、編寫第一個Ajax程序

檢測用戶輸入的用戶名是否爲"zhongfucheng",只要不是zhongfucheng,就可使用!

4.1html代碼

  • 建立的div只要用於顯示服務器返回的數據
  • 當用戶點擊按鈕的時候,就觸發事件。
<input type="text" id="username">
<input type="button" onclick="checkUsername()" value="檢測用戶名是否合法">
<div id="result">

</div>

4.2JavaScript代碼

  • 建立XMLHttpRequest對象
  • 建立http請求
  • 把文本框的數據發送給http請求的目標
  • 指定回調函數
  • 編寫回調函數
  • 發送http請求
  • 回調函數獲得http返回的內容,把內容寫在div上
<script type="text/javascript">

    var httpRequest;
    function checkUsername() {

        if(window.XMLHttpRequest) {

            //在IE6以上的版本以及其餘內核的瀏覽器(Mozilla)等
            httpRequest = new XMLHttpRequest();
        }else if(window.ActiveXObject) {

            //在IE6如下的版本
            httpRequest = new ActiveXObject();
        }


        //建立http請求
        httpRequest.open("POST", "Servlet1", true);

        //由於我使用的是post方式,因此須要設置消息頭
        httpRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded");

        //指定回調函數
        httpRequest.onreadystatechange = response22;

        //獲得文本框的數據
        var name = document.getElementById("username").value;

        //發送http請求,把要檢測的用戶名傳遞進去
        httpRequest.send("username=" + name);

    }

    function response22() {

        //判斷請求狀態碼是不是4【數據接收完成】
        if(httpRequest.readyState==4) {

            //再判斷狀態碼是否爲200【200是成功的】
            if(httpRequest.status==200) {

                //獲得服務端返回的文本數據
                var text = httpRequest.responseText;

                //把服務端返回的數據寫在div上
                var div = document.getElementById("result");
                div.innerText = text;
            }

        }
    }
</script>

4.3效果

實現了局部更新,不須要刷新整一個頁面

這裏寫圖片描述


5、XMLHttpRequest解決中文亂碼

在傳統的Web中咱們已經解決過中文亂碼問題了。

  • 服務器傳送給瀏覽器數據發生亂碼:response設置編碼的時候和瀏覽器頁面的編碼一致即可以解決
  • 瀏覽器傳送給服務器數據發生亂碼:若是是post方式,request設置編碼即可以解決。若是是get方式,Tomcat下,使用ISO8859-1編碼獲得本來的二進制數組,再使用UTF-8編碼即可以解決

接下來,要介紹的是:咱們能夠屏蔽任何瀏覽器和任何服務器的編碼格式,瀏覽器發送給服務器的數據不形成亂碼問題!

具體咱們是這樣作的:

  • 發送數據給服務器的時候,JavaScript使用兩次EncodeURI()
  • 服務器獲得數據,使用URLEncode.decode(數據,"utf-8")進行解碼

爲啥我能說這種方式屏蔽任何瀏覽器和服務器的編碼格式,都不會亂碼呢??

這裏寫圖片描述


6、XMLHttpRequest解決緩存問題

在傳統的Web中咱們也解決過緩存的問題,經過設置response的頭信息,返回給瀏覽器就能夠實現不緩存頁面了。

可是呢,如今咱們使用XMLHttpRequest,拿到的不是全新的頁面,僅僅是服務器端發送過來的數據!!

那咱們要怎麼解決緩存的問題呢??產生緩存的緣由就是:咱們請求了同一個地址,作了相同的操做。服務端認爲個人操做並無什麼變化,就直接把緩存的信息給我了。這樣的話,我就不能更換驗證碼圖片了(等等應用)。

咱們能夠這樣作:

  • 在每次請求url中加入一個時間戳參數【每次url就不同了】
  • 加入時間戳參數到url時,也分兩種狀況

    • url自己就帶有參數了,也就是說有"?"號了,那麼添加時間戳的時候就須要用"&"號
    • [x] url沒有參數,直接使用"?"號來添加時間戳
if(url.indexOf("?") >= 0){
url = url + "&t=" + (new Date()).valueOf();
} else{
url = url + "?t=" + (new Date()).valueOf();
}

7、XMLHttpRequest跨域訪問

使用XMLHttpRequest去跨域訪問是會出現錯誤的

這裏寫圖片描述

咱們要怎麼解決呢??這時候就要用代理思想了

  • XMLHttpRequest先把請求提交給同域的Servlet處理
  • 同域Servlet再將XMLHttpRequest的請求提交給跨域的服務器
  • 同域Servlet獲得跨域服務器的返回值,再返回給XMLHttpRequest

這裏寫圖片描述

這個時候,XMLHttpRequest跨域訪問就分兩種(GET和POST)狀況了,由於這兩種提交數據的方式是不同的!

7.1瀏覽器代碼

  • 咱們須要在調用open方法以前判斷一下要鏈接的地址是否是以http開頭的,若是是則認爲要訪問的是跨域的資源
  • 首先將當前url中的」?」變成」&」,這是由於將要鏈接的地址改成」Proxy?url=」 + url之後,若是原來url地址中有參數的話,新的url地址中就會有兩個「?」這會致使服務器端解析參數錯誤,」url=」以後的內容表示原本要訪問的跨域資源的地址。

7.2GET方式

GET方式是直接把參數的信息都放在url地址上,因此處理起來會相對簡單。

步驟:

  • 使用StringBuilder裝載着getParameter("url")【獲取獲得地址,呆會要作拼接,因此用StringBuilder】
  • 獲得其餘參數的時候,作URLEncode.encode(),由於咱們進入Servlet的時候已經被decode了一次【咱們要儘量保留原始請求】(參照解決中文亂碼)
  • 遍歷全部的請求參數,只要名字不是"url",就添加到StringBuilder中【第一個參數爲"?",其餘的參數爲"&"】(http://localhost:8080/url?aa=bb&cc=dd)
  • 建立URL對象,把拼接成的StringBuilder傳遞進去
  • 使用BufferReader讀取遠程服務器返回的數據,要指定輸入流編碼格式,不然會亂碼
BufferedReader reader = new BufferedReader(new InputStreamReader(URL對象.openSteam(),"UTF-8"));
  • 最後,把遠程服務器讀取到的數據再返回給XMLHttpRequest

7.3POST方式

POST方式把參數的信息都封裝到HTTP請求中,在URL進行鏈接的時候,須要把數據寫給遠程服務器

步驟:

  • 獲得url參數,建立StringBuilder
  • 獲得其餘參數的時候,作URLEncode.encode(),由於咱們進入Servlet的時候已經被decode了一次【咱們要儘量保留原始請求】(參照解決中文亂碼)
  • 遍歷全部的請求參數,只要名字不是"url",就添加到StringBuilder中【第一個參數直接給出,其餘的參數爲"&"】(aa=bb&cc=dd&ee=ff)
  • 建立URL對象,建立URL鏈接器,容許寫數據到遠程服務器上
URL url = new URL(url);
URLConnection connection = url.openConnection;
connection.setDoOutPut(true);
  • 獲得寫數據流
OutputSteamWriter writer = new OutputSteamWriter(conncetion.getOutputSteam)
  • 把StringBuilder的數據寫到遠程服務器上,並flush
  • 使用BufferReader讀取遠程服務器返回的數據
BufferedReader reader =  new BufferedReader(new InputSteamReader(conncetion.inputSteamReader,"UTF-8"));

8、AJAX二級下拉聯動案例【XML版】

咱們在購物的時候,經常須要咱們來選擇本身的收貨地址,先選擇省份,再選擇城市...

有沒有發現:當咱們選擇完省份的時候,出現的城市所有都是根據省份來給咱們選擇的。這是怎麼作到的呢???其實就是經過AJAX來完成的。使用AJAX技術讓咱們看起來網頁很是「智能」,會根據省份來給出對應的城市信息。

這裏寫圖片描述

咱們這裏就不讀取數據庫了,直接在Servlet寫死數據來進行模擬測試


8.1分析

咱們知道AJAX與服務器之間的交互經常使用的傳輸載體格式有三種:

  • HTML
  • XML
  • JSON

因爲省份與城市是有層級關係的,所以咱們只能用XML或者JSON

咱們這裏首先就用XML來進行,後面會使用JSON,來看看他倆有什麼不一樣的地方。。

8.1.1前臺分析

當用戶選擇了某個省份以後,就使用AJAX與服務器進行交互,那麼在選擇城市的時候就出現對應的城市信息。

  • 監聽下拉框值變化事件
  • 只要下拉框值變化了,就與服務器進行交互
  • 獲得服務器返回的值,解析XML
  • 使用DOM把數據寫到城市下拉框列表中

8.1.2後臺分析

  • 獲得前臺帶過來的數據
  • 判斷該數據是什麼,返回對應的的XML文件

8.2寫JSP頁面

<%--
  Created by IntelliJ IDEA.
  User: ozc
  Date: 2017/5/17
  Time: 19:38
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>多級聯動</title>
    <script type="text/javascript" src="js/ajax.js"></script>
</head>
<body>

<%--############前臺頁面###################--%>
<select name="province" id="provinceId">
    <option value="-1">請選擇省份</option>
    <option>廣東</option>
    <option>湖南</option>
</select>
<select name="city" id="cityId">
    <option>請選擇城市</option>
</select>

<%--############AJAX###################--%>

<script type="text/javascript">

    document.getElementById("provinceId").onchange = function () {


        /**********定位到下拉框,獲取下拉框的值***************/
        // 獲取選中的下拉框索引值
        var index = this.selectedIndex;
        // 獲得下拉框的值
        var province = this.options[index].innerHTML;

        //下拉框的值要是「請選擇」,那麼咱們是不會訪問服務器的
        if ("請選擇省份" != province) {

            /********因爲每次都會自動添加,所以每次在調用的時候清除***********/
            var citySelect = document.getElementById("cityId");

            //每次都將option變成長度只有1的
            citySelect.options.length = 1;

            /*************ajax代碼*********************/
            //建立AJAX對象
            var ajax = createAJAX();
            //準備發送請求
            var method = "post";
            var url = "${pageContext.request.contextPath}/ProvinceServlet?time=" + new Date().getTime();
            ajax.open(method, url);
            //因爲是POST方式,所以要設置頭
            ajax.setRequestHeader("content-type", "application/x-www-form-urlencoded");

            ajax.send("province=" + province);

            /************ajax回調函數*********************/
            ajax.onreadystatechange = function () {

                if (ajax.readyState == 4) {
                    if (ajax.status == 200) {

                        //獲得服務器端帶過來的XML
                        var XMLDocument = ajax.responseXML;

                        /**********解析XML文檔,使用DOM寫到下拉框中****************/
                        var cities = XMLDocument.getElementsByTagName("city");

                        //獲得每個cities節點的值,動態生成下拉框,添加到下拉框中
                        for (var i = 0; i < cities.length; i++) {
                            var value = cities[i].firstChild.nodeValue;
                            //動態生成下拉框
                            var optionElement = document.createElement("option");
                            optionElement.innerHTML = value;

                            //添加到下拉框中
                            citySelect.appendChild(optionElement);

                        }
                    }
                }
            };

        }

    };

</script>


</body>
</html>

8.3Servlet

import java.io.IOException;
import java.io.PrintWriter;

/**
 * Created by ozc on 2017/5/17.
 */
@javax.servlet.annotation.WebServlet(name = "ProvinceServlet",urlPatterns = "/ProvinceServlet")
public class ProvinceServlet extends javax.servlet.http.HttpServlet {
    protected void doPost(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException {

        //設置中文編碼
        request.setCharacterEncoding("UTF-8");
        String province = request.getParameter("province");

        //這裏是返回的是XML,所以指定XML數據!
        response.setContentType("text/xml;charset=UTF-8");

        PrintWriter printWriter = response.getWriter();

        /****************返回XML文件給前臺**************/
        printWriter.write("<?xml version='1.0' encoding='UTF-8'?>");
        printWriter.write("<root>");
        if("廣東".equals(province)){
            printWriter.write("<city>廣州</city>");
            printWriter.write("<city>深圳</city>");
            printWriter.write("<city>中山</city>");
        }else if("湖南".equals(province)){
            printWriter.write("<city>長沙</city>");
            printWriter.write("<city>株洲</city>");
            printWriter.write("<city>湘潭</city>");
            printWriter.write("<city>岳陽</city>");
        }
        printWriter.write("</root>");

        System.out.println("1111");


        /*******過後操做*******/
        printWriter.flush();
        printWriter.close();


    }

    protected void doGet(javax.servlet.http.HttpServletRequest request, javax.servlet.http.HttpServletResponse response) throws javax.servlet.ServletException, IOException {

        this.doPost(request, response);
    }
}

8.4效果:

這裏寫圖片描述

8.5XML方式總結

  • 監聽下拉框的變化,若是變化了,那麼就使用異步操做去訪問服務器,獲得對應的數據返回給異步對象
  • 異步對象解析服務器帶過來的數據,使用DOM編程把數據動態添加到頁面上

    • 在Servlet上記得要指定返回的是XML的數據!
    • 在前臺解析XML文檔的時候,不能直接使用innerHtml來獲得節點的值,只能經過firstChild.nodeValue的方式獲取。
    • 因爲每次append到下拉框都會連續append,所以在響應事件的時候,把下拉框清零
    • 把下拉框options的長度賦值爲1,那麼就是清零的操做了

9、AJAX二級下拉聯動案例【JSON版】

前面咱們已經使用過了XML做爲數據載體在AJAX中與服務器進行交互。當時候咱們的案例是二級聯動,使用Servlet進行控制

此次咱們使用JSON做爲數據載體在AJAX與服務器交互,使用三級聯動,使用Action進行控制....

  • 省份-城市-區域三級聯動【Struts2 + JSON版】

9.1分析

與上次是同樣的,只不過此次換了用JSON,使用Action控制罷了...

監聽下拉框的變更,使用異步對象與服務器進行交互。

9.1.1前臺分析

  • 監聽下拉框的變更
  • 獲得服務器返回的JSON數據
  • 使用eval()進行解析,獲得具體的對象
  • 使用DOM編程把數據填充到對應的下拉框上

9.1.2後臺分析

  • 獲得前臺發送過來的數據
  • 判斷具體的數據是什麼,給出對應的數據
  • 使用Struts2提供的組件把數據封裝成JSON
  • 返回給瀏覽器

9.2監聽省份JSP頁面

<%--
  Created by IntelliJ IDEA.
  User: ozc
  Date: 2017/5/18
  Time: 13:36
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>使用JSON數據載體與服務器進行交互</title>

      <script type="text/javascript" src="js/ajax.js"></script>
  </head>
  <body>


  <%--############前臺頁面##############################--%>
  <select name="province" id="provinceId">
    <option>請選擇省份</option>
    <option>廣東</option>
    <option>北京</option>
  </select>

  <select name="city" id="cityId">
    <option>請選擇城市</option>
  </select>


  <select name="area" id="areaId">
    <option>請選擇區域</option>
  </select>

  <%--############監聽省份##############################--%>
  <script type="text/javascript">

      document.getElementById("provinceId").onchange= function () {

          // 獲得選中的下拉框的值
          var provinceValue = this.options[this.selectedIndex].innerHTML;


          /***************ajax代碼*************************/
          if("請選擇省份" != provinceValue) {

              //每次訪問的時候,都要清空select的值
              var citySelect = document.getElementById("cityId");
              citySelect.options.length = 1;

              var ajax = createAJAX();
              var method = "post";
              var url = "${pageContext.request.contextPath}/province_findCityByProvince?time=" + new Date().getTime();
              ajax.open(method, url);
              ajax.setRequestHeader("content-type", "application/x-www-form-urlencoded");

              //顧及到發送的key、value值有不少,因而咱們使用對象吧。
              ajax.send("bean.name=" + provinceValue);

              /***************等待服務器的響應,獲得服務器返回的數據************************/
              ajax.onreadystatechange = function () {

                  if(ajax.readyState==4) {
                      if(ajax.status==200) {
                        var jsonJava = ajax.responseText;

                        //解析成是JS類型的JSON
                        var json = eval("(" + jsonJava + ")");

                        //獲得每一個城市的值
                        for(var i=0;i<json.city.length;i++) {
                          var city = json.city[i];

                          //動態建立option控件
                          var option = document.createElement("option");
                          option.innerHTML = city;

                          citySelect.appendChild(option);
                        }
                      }
                  }
              };

          }

      };
    
  </script>

  </body>
</html>

9.3監聽省份Action

要想Struts2可以把Action的數據封裝成JSON,就須要導入Struts2的開發包

  • struts2-json-plugin-2.3.4.1.jar

這裏寫圖片描述

在Action中對應的成員屬性須要給getter方法

import com.opensymphony.xwork2.ActionSupport;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by ozc on 2017/5/18.
 */
public class ProvinceAction  extends ActionSupport{

    //自動封裝數據
    private Bean bean;
    public Bean getBean() {
        return bean;
    }
    public void setBean(Bean bean) {
        this.bean = bean;
    }

    //封裝城市的集合
    private List<String> city = new ArrayList<>();
    public List<String> getCity() {
        return city;
    }

    public String findCityByProvince() throws Exception {

        if ("廣東".equals(bean.getName())) {
            city.add("廣州");
            city.add("珠海");
            city.add("從化");
        } else if ("北京".equals(bean.getName())) {
            city.add("一環");
            city.add("二環");
            city.add("三環");
            city.add("四環");

        } else {
            System.out.println("沒有你選擇的地區");

        }
        return "ok";
    }
}

返回給前端的時候,數據是這樣子的:

這裏寫圖片描述


9.4效果

這裏寫圖片描述


9.5監聽城市JSP

<%--############監聽城市##############################--%>

<script type="text/javascript">
  document.getElementById("cityId").onchange = function () {

      //清空值
      var areaSelect = document.getElementById("areaId");
      areaSelect.options.length = 1;

      //獲得選擇選中的下拉框的值
      var cityValue = this.options[this.selectedIndex].innerHTML;
      if(cityValue!="請選擇城市"){

          var ajax = createAJAX();
          var method = "post";
          var url = "${pageContext.request.contextPath}/province_findAreaByCity?time=" + new Date().getTime();
          ajax.open(method, url);
          ajax.setRequestHeader("content-type", "application/x-www-form-urlencoded");

          //顧及到發送的key、value值有不少,因而咱們使用對象吧。
          ajax.send("bean.name=" + cityValue);

          /***************等待服務器的響應,獲得服務器返回的數據************************/
          ajax.onreadystatechange = function () {

              if(ajax.readyState==4) {
                  if(ajax.status==200) {

                      var jsonJava = ajax.responseText;

                      var json = eval("(" + jsonJava + ")");

                      //獲得每一個地區的值
                      for(var i=0;i<json.area.length;i++) {
                          var area = json.area[i];

                          //動態建立option控件
                          var option = document.createElement("option");
                          option.innerHTML = area;

                          areaSelect.appendChild(option);
                      }

                  }
              }
          }

      };
  };

</script>

9.6Action頁面

public String findAreaByCity() throws Exception {

    if ("廣州".equals(bean.getName())) {
        area.add("白雲區");
        area.add("黃浦區");
        area.add("蘿崗區");
    } else if ("珠海".equals(bean.getName())) {
        area.add("香江");
        area.add("拱北");
        area.add("EE");
        area.add("xx");
    } else {
        System.out.println("沒有你選擇的地區");

    }
    return "ok";
}

9.7最終效果:

這裏寫圖片描述

9.8總結

此次使用的是JSON做爲數據載體與服務器進行交互,和XML本質上是沒有區別的。

只不過JSON是更加輕量級文本數據,在JavaScript可以方便地獲取返回的數據

  • 在Struts2中把Action數據封裝成JSON格式,返回給異步對象

    • 須要導入jar包
    • 在配置文件中配置繼承json包
    • 返回的類型是json
  • 若是使用POST時,發送的key、vaulue太多的話,咱們可使用bean進行封裝
  • 當選中省份時,把城市和區域的下拉框清空,當選擇城市時,把區域的下拉框清空

10、總結圖

這裏寫圖片描述

若是文章有錯的地方歡迎指正,你們互相交流。習慣在微信看技術文章的同窗,能夠關注微信公衆號:Java3y
相關文章
相關標籤/搜索