Struts2 MVC基礎介紹


流程介紹


 

我們模擬一個請求/響應的情景,來介紹Struts的工作流程。注意,下面的序號和圖中的序號沒有嚴格的對應關系。

  1. 瀏覽器向系統發出請求,請求的地址是ac.action
  2. 請求被StrutsPreparedExecuteFilter攔截,去掉.action后綴,所得結果ac作為action的name。
    在Struts框架中,負責處理用戶請求的稱為action,這里的name用於獲取action,找到他,讓他干活。
  3. StrutsPreparedExecuteFilter在struts.xml中查找action映射相關的配置,根據ac查到action的類pkg.AcAction。
  4. StrutsPreparedExecuteFilter實例化pkg.AcAction,並調用其execute方法。
  5. pkg.AcAction工作完成之后,匯報工作結果:返回一個字符串success,稱之為result。
  6. StrutsPreparedExecuteFilter使用success去查struts.xml中的結果映射部分,獲取到對應的物理視圖資源是ac-success.jsp。
  7. StrutsPreparedExecuteFilter使用forward的方式,將ac-success.jsp展示給用戶。

仔細看一下上面的圖和內容,在腦海中回憶一下整個流程,最好能夠閉眼將整個流程復述一遍,然后再繼續。

我們的工作

在上述的流程描述中,幾乎都是框架要做的事,但是為了能讓框架順利的工作,我們要提供支持性的工作。使用框架基本都是這樣,我們按照框架的模式提供支持,框架自行工作。
接下來看一下我們需要做的事:

  1. 配置StrutsPreparedExecuteFilter。
    StrutsPreparedExecuteFilter是整個流程中的核心,這個指揮中心不是自行啟動的,我們需要在web.xml中啟動它。這件事只需要做一次。
  2. 創建Action類
    action負責處理用戶的請求,具體怎么處理,需要我們創建一個Action類來實現。
  3. 創建視圖
    Action實現之后,我們要考慮返回怎樣的視圖給用戶。在這里,我們需要創建1到多個jsp文件,擔任視圖的角色。
  4. 配置action映射
    配置name和class的對應關系,讓Struts知道該把哪些請求分派給哪個action。
  5. 配置result映射
    Action返回的是一個普通的字符串,我們稱之為處理結果或者邏輯視圖,不管叫什么,總之它不是物理視圖,不指定任何視圖文件。Struts為了將Action類和視圖文件解耦,將返回結果和物理視圖的對應關系,我們稱之為result映射,在配置文件中配置。

上面五個工作,第一個只需要做一次,相對的,后面四個每創建一個action都需要做一次。

在上面五個工作中,視圖文件是jsp,我們將之視為基本知識,並不打算介紹。result映射一般是在action映射內部配置的,所以配置result將包含在配置actioin中。所以,我計划分下面三個主題,介紹上面的工作:

  1. 配置核心過濾器
  2. 創建Action
  3. 配置action

1.配置核心過濾器


這里使用Maven管理項目,如果要使用Struts框架,你需要引入依賴。你可以在http://mvnrepository.com/中輸入struts2-core來查找可用的版本,從中選擇一個並獲取其依賴配置:

<dependency>
    <groupId>org.apache.struts</groupId>
    <artifactId>struts2-core</artifactId>
    <version>2.3.28</version>
</dependency>

 

 

在web.xml中配置下面的內容:

1 <filter>
2   <filter-name>struts2</filter-name>
3   <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
4 </filter>
5 <filter-mapping>
6   <filter-name>struts2</filter-name>
7   <url-pattern>/*</url-pattern>
8 </filter-mapping>

 

2.創建Action


 

2.1.參數獲取

既然要在Action中處理用戶請求,那么Action就需要獲取用戶的請求參數。我們需要為此在Action中添加對應的屬性,屬性的name和請求參數的name一致,類型是基本類型;另外,我們要為這個屬性添加setter方法。
如果為Action的屬性提供getter方法,在jsp可以從request的屬性中獲取該屬性。

2.2.創建Action

有三種方式可以創建Action。
  1. POJO
    Action不需要實現任何接口,不需要繼承任何類,但是需要包含一個方法:public String execute() throws Exception。這個方法是處理請求的入口,由Struts框架調用。這個方法的名字可以自行定義,但是execute是默認的名字,使用這個可以省去一項屬性配置。
  2. 實現Action接口
    全稱com.opensymphony.xwork2.Action,這個接口定義了幾個字符串常量,作為result;還定義了execute方法。
  3. 繼承ActionSupport
    全稱com.opensymphony.xwork2.ActionSupport,實現了Action。

2.3.demo設計

為了介紹上面3種方式,這里設計一個demo。

以登錄為例,用戶填寫賬號和密碼,提交登錄請求。Action對賬號密碼進行權限驗證,通過則跳轉到首頁,否則依舊跳轉到登錄頁面。
所以對於Action來說,就是獲取請求參數:賬號、密碼。判斷賬號是否存在,密碼是否正確。如果通過返回一個"success"的邏輯結果,否則返回"login"的邏輯結果。
為了添加一點人性化的元素,我們將區分“賬號不存在”和“密碼錯誤”兩種情況,並分別進行提示。
好了,我們開始吧。

2.3.1.POJO

 1 package cn.ljl.note.struts2.login.actions;
 2  
 3 public class LoginPOJO {
 4   private static final String VALID_USER = "admin";
 5   private static final String VALID_PWD = "admin";
 6   
 7   private static final String SUCCESS = "success";
 8   private static final String LOGIN = "login";
 9   
10   private String username;
11   private String password;
12   private String tip;
13   
14   public String getUsername() {
15      return username;
16   }
17   public void setUsername(String username) {
18      this.username = username;
19   }
20   public String getPassword() {
21      return password;
22   }
23   public void setPassword(String password) {
24      this.password = password;
25   }
26   public String getTip() {
27      return tip;
28   }
29   public void setTip(String tip) {
30      this.tip = tip;
31   }
32   
33   public String execute() throws Exception {
34      boolean validUser = VALID_USER.equals(getUsername());
35      boolean validPwd = VALID_PWD.equals(getPassword());
36      
37      if (!validUser) {
38        setTip("用戶不存在!");
39        return LOGIN;
40      }
41      
42      if (!validPwd) {
43        setTip("密碼不正確!");
44        return LOGIN;
45      }
46      
47      setTip(null);
48      return SUCCESS;
49   }
50 }

 

2.3.2.實現Action接口

Action接口的類圖如下:

源代碼:

 1 package cn.ljl.note.struts2.login.actions;
 2  
 3 import com.opensymphony.xwork2.Action;
 4  
 5 public class LoginAction implements Action{
 6   private static final String VALID_USER = "admin";
 7   private static final String VALID_PWD = "admin";
 8   
 9   private String username;
10   private String password;
11   private String tip;
12   
13   public String getUsername() {
14      return username;
15   }
16   public void setUsername(String username) {
17      this.username = username;
18   }
19   public String getPassword() {
20      return password;
21   }
22   public void setPassword(String password) {
23      this.password = password;
24   }
25   public String getTip() {
26      return tip;
27   }
28   public void setTip(String tip) {
29      this.tip = tip;
30   }
31   
32   @Override
33   public String execute() throws Exception {
34      boolean validUser = VALID_USER.equals(getUsername());
35      boolean validPwd = VALID_PWD.equals(getPassword());
36      
37      if (!validUser) {
38        setTip("用戶不存在!");
39        return LOGIN;
40      }
41      
42      if (!validPwd) {
43        setTip("密碼不正確!");
44        return LOGIN;
45      }
46      
47      setTip(null);
48      return SUCCESS;
49   }
50  
51 }
LoginAction

 

2.3.3.繼承ActionSupport類

com.opensymphony.xwork2.ActionSupport是一個復雜的類,它提供了很多其他的功能,而這些我們目前還不需要關注。所以這里只要把它當成Action接口的默認實現類就好了,這里也不再貼出其類圖。
demo的源代碼:
 1 package cn.ljl.note.struts2.login.actions;
 2  
 3 import com.opensymphony.xwork2.ActionSupport;
 4  
 5 public class LoginActionSupport extends ActionSupport {
 6  
 7   private static final long serialVersionUID = 8451980703294866793L;
 8   
 9   private static final String VALID_USER = "admin";
10   private static final String VALID_PWD = "admin";
11   
12   private String username;
13   private String password;
14   private String tip;
15   
16   public String getUsername() {
17      return username;
18   }
19   public void setUsername(String username) {
20      this.username = username;
21   }
22   public String getPassword() {
23      return password;
24   }
25   public void setPassword(String password) {
26      this.password = password;
27   }
28   public String getTip() {
29      return tip;
30   }
31   public void setTip(String tip) {
32      this.tip = tip;
33   }
34   
35   @Override
36   public String execute() throws Exception {
37      boolean validUser = VALID_USER.equals(getUsername());
38      boolean validPwd = VALID_PWD.equals(getPassword());
39      
40      if (!validUser) {
41        setTip("用戶不存在!");
42        return LOGIN;
43      }
44      
45      if (!validPwd) {
46        setTip("密碼不正確!");
47        return LOGIN;
48      }
49      
50      setTip(null);
51      return SUCCESS;
52   }
53   
54 }
LoginActionSupport

2.4.三種方式的比較

比較上述三種方式的源代碼,大部分代碼都是重復的。實現Action接口或者繼承ActionSupport類,可以直接使用已經定義好的邏輯結果,而這些一般是比較常用的。
通過繼承ActionSupport來開發Action,這是建議的方式。

3.配置action


3.1.配置文件

Struts2的常規配置文件是struts.xml,這個文件放在源文件夾的根目錄。比如使用maven,應該把它放在src/main/resources下。
配置文件的結構像下面這樣:
1 <?xml version="1.0" encoding="GBK"?>
2 <!DOCTYPE struts PUBLIC
3     "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
4     "http://struts.apache.org/dtds/struts-2.3.dtd">
5 <struts>
6     
7 </struts>
其中,文檔聲明中的版本號與當前使用的struts2的版本有關。也可以從struts2-core相關jar包中拷貝。
下面將要介紹的內容,基本是在這里配置的,否則會特別說明。另外,如果你修改了這里的配置,編譯部署之后,需要重啟服務器。

3.2.命名空間

一次請求的地址,像這個樣子:

http://localhost:8080/note-struts2/login/check.action
在Struts2看來,/login稱為命名空間;check稱為action-name;.action是后綴,在解析的時候會自動去掉。
命名空間可以多級,就像目錄結構一樣,一層層;action-name就像是文件名。
在struts.xml中,使用<package>的namespace屬性來指定命名空間,所以action的定義都是在<package>下定義的。比如,我們可以這樣定義:
<package name="login" extends="struts-default" namespace="/login">
  ......
</package>

 

上述代碼片段,涉及到3個package屬性:
  • name
    name唯一標識一個package
  • extends
    package有繼承的特性,使用extends指定另一個package的name,就會繼承彼package下所定義的內容。
    這里繼承的struts-default是在struts2-core的struts-default.xml中定義的。通常,建議繼承struts-default。
  • namespace
    配置命名空間。

默認的命名空間

namespace不是必需的屬性,如果沒有配置,那就是默認的命名空間。默認的命名空間有特殊的作用:如果在請求的URL中解析出來的命名空間里找不到對應的action,就到默認的命名空間里找。默認命名空間是一個抽象的概念,不是默認值,你不能說“我可以通過配置namespace等於默認值,來指定默認命名空間”。比如"/"被稱為根命名空間,但是它不是默認命名空間,它也沒有任何特殊的性質,就和其他命名空間一樣。

3.3.action

即配置action映射,struts框架需要查找這個映射,才能根據URL找到實際的處理Action。action是在<package>元素內配置的,下面是一個demo:

<action name="check" class="cn.ljl.note.struts2.login.actions.LoginActionSupport" method="execute">
    ......
</action>
<action>元素有3個基本的屬性:
  • name
    它同時也是action的url請求地址的一部分,同一個命名空間下,action的name要唯一
  • class
    它是Action類,負責處理用戶的請求。這是一個非必需的屬性,默認為com.opensymphony.xwork2.ActionSupport;你可以看下這個類的execute方法,只是直接返回SUCCESS。
  • method
    它是Action的方法名,對於框架來說,相當於回調方法。框架會調用這個方法,以達到通知請求到達的效果。這是一個非必需的屬性,默認值為execute,所以上面的demo完全不用配置這個屬性。

3.4.result

即配置result映射,根據這個映射,struts框架才能根據Action返回的邏輯結果(字符串)找到對應的視圖資源。result是在<action>元素內配置的,下面是一個demo:

<result name="success">/index.jsp</result>
<result name="login">/login/login.jsp</result>

<result>元素的屬性name代表Action返回的邏輯結果;<result>體的內容,代表物理視圖的路徑。其中name的默認值是"success",所以第1行不用配置name屬性。

3.5.異常

在Action中出現異常,可能希望根據不同的異常類型跳轉到不同的物理視圖。
結合Struts2框架的工作流程,我們可以Action中捕獲異常,根據不同的類型返回不同的字符串,並在struts.xml中根據這些返回的結果配置不同的物理視圖。比如我們可能會這樣寫Action的方法:
1 public String execute() {
2     try {
3         // ...
4     } catch(異常1 e1) {
5         return 結果1;
6     } catch(異常2 e2) {
7         return 結果2;
8     }
9 }

然后我們會在struts.xml中這樣配置result映射:

<result name="結果1">視圖1.jsp</result>
<result name="結果2">視圖2.jsp</result>

這樣是可以的,實際上在我們已知的知識上,想到這種方式來解決新的問題,能體現我們是會靈活變通的。不過Struts也提供了正統的配置方法,讓我們只需要配置異常和返回結果的映射關系,而不需要捕獲Action處理方法中拋出的異常。

3.5.1.異常映射

在<action>元素下,使用<exception-mapping>元素來配置,下面上一個demo:

<result name="exception">/exception/exception.jsp</result>
<exception-mapping result="exception" exception="java.lang.Exception" />
這個demo的意思是,如果處理方法拋出了java.lang.Exception異常,就返回"exception"的邏輯結果,它對應的物理視圖是/exception/exception.jsp。
<exception>有兩個屬性:
  • result
    對應的異常類型發生時,要返回什么邏輯結果
  • exception
    配置什么類型的異常
留兩個有趣的問題:
  1. 配置的異常類型,對其子類型異常是否有效?
  2. 兩種異常類型(繼承關系)配置的先后順序,對異常拋出的返回結果是否有影響?
    拋出的異常可以是父類型、子類型、兩者的子類型。
這兩個是比較細節、相對啰嗦的問題,我不喜歡這樣的問題,實際遇到的時候,我會順手測一下,但是目前,我還不關心它們的答案。

3.5.2.攔截器

我們雖然提供了異常到邏輯結果的映射,但是還需要一個攔截器來做這樣的工作:攔截拋出的異常,查映射關系,改為返回對應的邏輯結果。這樣的攔截器已經在struts-default中使用了,所以只需要保證定義的<package>,直接或間接的繼承了struts-default就好了

3.5.3.輸出異常

我們可以在jsp中使用el來輸出異常,像這樣${exception }。不過,Struts2也提供了相關的標簽,下面是一個demo:

1 <%@ page language="java" contentType="text/html; charset=GBK"
2     pageEncoding="GBK"%>
3 <%@ taglib uri="/struts-tags" prefix="s" %>
4 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
5 <html>
6 <body>
7     <s:property value="exceptionStack"/>
8 </body>
9 </html>
line 3,引入了struts的標簽庫,定義了前綴為s。
line 7,使用<property>標簽,輸出了異常的堆棧信息。
<property>標簽在用於輸出異常信息是,value屬性有下面幾個選擇:
  • exception
    輸出異常本身,相當於${exception }
  • exception.message
    輸出異常的信息,相當於${exception.message }
  • exceptionStack
    輸出異常的堆棧信息,正是demo中所用到的。

3.6.總結

好了,讓之前出現過的幾位也上來吧,我們來張合照:struts.xml

 1 <?xml version="1.0" encoding="GBK"?>
 2 <!DOCTYPE struts PUBLIC
 3     "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
 4     "http://struts.apache.org/dtds/struts-2.3.dtd">
 5 <struts>
 6     <package name="login" extends="struts-default" namespace="/login">
 7         <action name="check" class="cn.ljl.note.struts2.login.actions.LoginActionSupport">
 8             <result name="success">/index.jsp</result>
 9             <result name="login">/login/login.jsp</result>
10             <result name="exception">/exception/exception.jsp</result>
11             <exception-mapping result="exception" exception="java.lang.Exception" />
12         </action>
13     </package>
14 </struts>

 

4.使用config-browser查看配置


你可能想瀏覽配置的情況,當然對於一個簡單的項目,直接看struts.xml可能更快、更專業。接下來要出場的是一個插件,config-browser,它可以提供通過前台查看配置情況的功能。這不是它唯一的優點,但是我們不需要為其優點列一個清單,暫時知道這一個吧,剩下的自己體會。

4.1.添加依賴

我們使用的struts2-core是2.3.28版本,我們也使用相同版本的config-browser:

<dependency>
    <groupId>org.apache.struts</groupId>
    <artifactId>struts2-config-browser-plugin</artifactId>
    <version>2.3.28</version>
</dependency>

 

4.2.使用

重新打包、部署,重啟服務,輸入訪問地址:http://localhost:8080/note-struts2/config-browser/actionNames.action,可以看到下面的內容:

注意左側導航欄,Namespaces下是所有的命名空間,其中/config-browser是插件定義的,/login是我們之前定義的。右側默認顯示默認命名空間下的action。

點擊左側Namespaces/login鏈接,可以看到:

右側列出了這個命名空間下的action,點進去會看到:

注意:這里的內容是按照標簽頁組織的,默認顯示的是Results標簽頁,切換到Eception Mappings,可以看到:

我們關於config-browser入門的介紹就到這里了,其他的靠大家自己了。

5.擴展


前面已經對框架的基本流程中涉及到的工作,做了基本的介紹,下面這些內容會深入一下。

5.1.action

5.1.1.多個處理方法

一個Action類中可以有多個處理方法,只需要在配置<action>的時候使用method指定不同的方法名,就可以定義多個action。比如,我們假想一個Action,它的類圖是這樣的:

我們可以在struts.xml中這樣配置:

 1 <package name="imagination" extends="struts-default" namespace="/imagination">
 2     <action name="addUser" class="cn.ljl.note.struts.imaginary.actions.UserAction" method="add">
 3         ...
 4     </action>
 5     <action name="saveUser" class="cn.ljl.note.struts.imaginary.actions.UserAction" method="save">
 6         ...
 7     </action>
 8     <action name="deleteUser" class="cn.ljl.note.struts.imaginary.actions.UserAction" method="delete">
 9         ...
10     </action>
11 </package>

5.1.2.使用通配符

在滿足一定的模式的情況下,使用通配符可以使用最少的配置量,配置多個action。比如對於上面的情況,也可以使用下面的配置:

1 <package name="imagination" extends="struts-default" namespace="/imagination">
2     <action name="*User" class="cn.ljl.note.struts.imaginary.actions.UserAction" method="{1}">
3         ...
4     </action>
5 </package>

line 2,name屬性值中使用了*,method屬性值中的{1}表示使用第1個*所匹配的字符串。這個配置和上文的配置效果是一樣的,但是配置工作更少。

class也可以和name滿足一定的模式,比如下面的配置:

1 <package name="imagination" extends="struts-default" namespace="/imagination">
2     <action name="*User" class="cn.ljl.note.struts.imaginary.actions.{1}UserAction">
3         ...
4     </action>
5 </package>
此時,AddUser將映射到cn.ljl.note.struts.imaginary.actions.AddUserAction。
其實,上述兩種配置,體現了兩種思想:將不同的業務交給Action的不同的處理方法,或者干脆使用不同的Action。
 
優先級
使用通配符會存在這樣的問題:請求的地址為addUser.action,struts.xml中同時有addUser、*User、*可以匹配這個請求,會選擇哪一個來處理請求呢?
如果我們稱不使用通配符的為完全匹配,稱使用通配符的為模式匹配,那么有下面兩個規則:
  • 完全匹配優先於模式匹配
  • 模式匹配中,前面的優先
所以,會選擇addUser。如果沒有這個配置,則在*User、*中,哪一個出現在struts.xml的前面,哪一個被選擇。
所以,建議把范圍更廣的配置放在后面。

5.1.3.默認的action

*可以匹配一切,所以我們可以使用*來配置默認的action。另外,我們還可以像下面這樣配置:

1 <package name="imagination" extends="struts-default" namespace="/imagination">
2     <default-action-ref name="default" />
3     <action name="default" class="...">
4         ...
5     </action>
6     ...
7 </package>

在特定命名空間下配置,只能作為當前命名空間的默認action;在默認的命名空間下配置,可以作為全局的默認action。

5.1.4.默認的class

配置<action>時,默認的class是com.opensymphony.xwork2.ActionSupport,你可以修改這項配置:在<package>下添加<default-class-ref class="" />。

5.1.5.動態方法調用

在form標簽的action屬性,可以同時指定action的name和方法,比如:

<form action="user!add">
    ...
</form>

就指定name為user的action的add方法,來處理請求。

這種方式,前台依賴於服務端的API,這樣是不好的。

5.2.result

Struts2支持多種result-type,基本的result-type都是在struts2-core的struts-default.xml中配置的。

5.2.1.多種結果類型

1.dispatcher

disparcher是默認的result-type,以指定的jsp作為視圖。最原始的配置方式是這樣的:

<result name="success" type="dispatcher">
    <param name="location">/success.jsp</param>
</result>

因為result的name默認就是success,type默認就是dispatcher,而視圖的location可以直接在<result>的體配置。所以可以簡化成這個樣子:

<result>/success.jsp</result>

 

2.plainText

plainText將指定的視圖以文本的形式顯示給瀏覽器。使用這種方式,需要指定視圖的location;如果視圖文件中包含非西歐字符,還要指定charSet。

<result name="success" type="plainText">
    <param name="location">/success.jsp</param>
    <param name="charSet">GBK</param>
</result>

 

3.redirect

重定向。這個類型可以指定一個location,瀏覽器重定向到指定的視圖。

<result name="success" type="redirect">/index.jsp</result>

 

4.redirectAction

重定向到Action。這個類型專門重定向到Action,與redirect算是被包含關系。這個類型可以指定namespace和actionName:

<result type="redirectAction">
    <param name="namespace">/login</param>
    <param name="actionName">check</param>
</result>

暫時,就介紹這幾種吧。

5.2.2.使用通配符

在配置result映射的時候,也可以使用通配符,比如:

<package name="imagination" extends="struts-default" namespace="/imagination">
    <action name="*User" class="cn.ljl.note.struts.imaginary.actions.UserAction" method="{1}">
        <result>/imagination/result-{1}.jsp</result>
    </action>
</package>

按照上面的配置,addUser對應cn.ljl.note.struts.imaginary.actions.UserAction的add方法,返回的視圖是/imagination/result-add.jsp。

5.2.3.使用OGNL表達式

OGNL表達式的具體內容,計划放在后面講,這里只介紹幾個簡單的用法。配置result映射時,可以使用action的屬性值(要提供getter方法),比如:

<result>/imagination/result-add.jsp?username=${username}</result>

在計算物理視圖時,就會使用action的username屬性,替換其中的${username}。

如果屬性是復雜屬性,比如bean,而在result中需要的是屬性bean的屬性,也可以按照這樣的方式獲取:${user.name}。

5.2.4.全局配置

在之前的配置里,都是針對action的一個邏輯結果進行配置;全局配置是在<package>范圍,提供一個配置,對所有action都有效。

全局配置是在<package>下,使用<global-results> - <result>來配置,比如:

1 <package ...>
2     <global-results>
3         <result name="exception">/exception.jsp</result>
4     </global-results>
5     ...
6 </package>

這樣一來,這個package下所有的action,如果沒有指定"exception"的映射,就使用全局的映射;如果指定了,就覆蓋全局的配置。

5.3.異常的全局配置

在<package>范圍,異常也可以使用全局配置。使用<global-exception-mappings> - <exception-mapping>。異常映射是把異常的類型映射到result,所以它依賴於result,全局的異常映射應該只使用全局的result,像下面這樣:

1 <package ...>
2     <global-results>
3         <result name="exception">/exception.jsp</result>
4     </global-results>
5     <global-exception-mappings>
6         <exception-mapping exception="java.lang.Exception" result="exception" />
7     </global-exception-mappings>
8     ...
9 </package>

 

 


免責聲明!

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



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