JNLP说明(good)

转自(http://blog.csdn.net/llythh/archive/2009/06/26/4299490.aspx)

Java Web Start 是一个 helper 应用程序,它和 WEB 浏览器关联在一起。当用户点击指向一个特定的 launch 文件 (JNLP 文件 ) 时,会促使浏览器装载 Java Web Start, JAWS 然后自动下载、缓冲、运行给定的基于 JAVA 技术的应用程序。整个过程无需和用户进行交互。

JNLP URLs 也可以从 JAWS 应用程序管理直接打开并且能制成书签 , 此外,它们也可以是 .html 或者 .jnlp 文件。

从技术的观点来看, JAWS 有许多关键的好处使得它在部署应用程序方面成为一个有吸引的平台:

Ø JAWS 专用于装入运行基于 Java 2 SE 平台编写的应用程序。因此,应用程序可以置于 WEB SERVER 中且能被部署在各种平台中包括 WINDOWS 系列、 Linux Unix 等。

Ø  JAWS 支持 J2SE 的不同版本,因此,应用程序可以请求它所需要的特定版本。比如 J2SE1.4.0. 几个基于不同 J2SE 版本的应用程序能同时运行而不会造成冲突。并且在客户端操作系统未安装应用程序所需的 J2SE 版本时, JAWS 能自动下载且安装它。

Ø  JAWS 允许应用不依赖于 WEB 浏览器进行装载和运行。应用程序也可以通过桌面快捷方式启动运行。

Ø JAWS 充分利用了平台内置的安全特性。应用程序默认情况下是运行在沙袋中,限制对本地磁盘以及网络资源进行访问 . 它允许用户安全的运行来自不受信任的源头的应用程序。

Ø  JAWS 装载运行的应用程序,会在本地被缓存起来,因此,运行一个已下载的应用程序和运行一个传统的安装了的应用程序是一样的。

 

Java Web Start 所基于的技术是 Java Network Launching Protocol & API(JNLP). 它目前正由 Java Community Proces(JCP) 进行开发。 Java Web Start JNLP 规格的参考实现。 JNLP 技术定义了一个标准的文件格式以描述怎样通过调用 JNLP 文件来装载运行一个应用程序。

 

客户端机器需要支持 JRE1.2.2 及以后版本。服务器方面来说,由于应用程序能被部署在任何标准的 Web Server 上,所以只要求 Web Server 配置以支持新的 MIME type.

 

1.1 配置 WebSite

为了在客户端运行应用程序,必须确保应用程序所需的的文件可以通过 Web Server 进行访问。典型的做法就是把应用程序所需的所有 JAR 文件以及 JNLP 文件放入 Web Server 特定的目录中,同普通的基于 Html 内容的部署没有什么区别,唯一需要注意的就是在 Web Server 中配置一个新的 MIME type.

 

基本步骤

1. 对所有以 .jnlp 扩展名结尾的文件,设置它的 MIME type application/x-java-jnlp-file

大多数 WEB 浏览器会根据从 Web Server 随同内容一同返回的 MIME type 来决定怎样对内容进行处理。 Server 针对 JNLP 文件则必须返回 application/x-java-jnlp-file 形式的 MIME type, 以便浏览器调用 Java Web Start.

 

不同的 Web Server 指定 MIME type 的方式可能不同。比如 Apache Web Server 必须在 .mime.types 配置文件中增加如下行: application/x-java-jnlp-file JNLP

 

2. 为应用程序创建 JNLP 文件,语法参见后续章节。

3. 确保应用程序的 JAR 文件以及 JNLP 文件是可访问的(通过 JNLP 文件中列出的 URLs 的方式)

4. 创建 WEB PAGE 来装载运行应用程序。见后续章节。

 

1.2 编写装载 Application WebPage

比如希望能从 Web 站点 http://www.yyy.zzz 装载 app.jnlp 。则需要在 Web Page 中应包含如下链接:

<a href=http://www.yyy.zzz/app.jnlp> Launch the application</a>

 

在许多情况下,用户的机器上没有安装 JAWS, 因此, WEB 页还必须包含教本以已考虑 JAWS 没有安装的情况,其逻辑如下:

Ø  如果检测到 JAWS 安装了,则装载运行程序

Ø  如果检测到没有安装 JAWS ,再检测是否是再在 WINDOWS 上的 IE 运行

n 是,提供一个能自动安装 JRE WEB 页面

n  不是,提供一个下载 SDK/JRE 的通用下载页面。

后面有讨论相关的脚本以及自动安装的 HTML

 

Detecting if JAWS is installed on Netscape

 

  1. <SCRIPT LANGUAGE= "JavaScript" >  
  2. var  javawsInstalled = 0;  
  3. var  javaws12Installed = 0;  
  4. var  javaws142Installed=0;  
  5. isIE = "false" ;  
  6. if  (navigator.mimeTypes && navigator.mimeTypes.length) {  
  7. x = navigator.mimeTypes['application/x-java-jnlp-file' ];  
  8. if  (x) {  
  9. javawsInstalled = 1;  
  10. javaws12Installed=1;  
  11. javaws142Installed=1;  
  12. }  
  13.   
  14. }else  {  
  15. isIE = "true" ;  
  16. }  
  17. </SCRIPT>  

上述代码查看 navigator.mimeTypes 对象和 navigator.mimeTypes.length 变量,以决定浏览器是 IE 还是 Netscape. IE 来说,尽管定义了 navigator.mimeTypes 但其长度为 0 ;而对于 Netscape 来说,其长度不为 0 ,而且,在 Netscape 中无法决定哪个 JAWS 的版本安装了。所以三个变量都设为 1

 

Detecting if JavaWeb Start is installed on IE, and if so, the version

 

  1. <SCRIPT LANGUAGE= "VBScript" >  
  2. on error resume next  
  3. If isIE = "true"  Then  
  4. If Not(IsObject(CreateObject("JavaWebStart.isInstalled" ))) Then  
  5. javawsInstalled = 0  
  6. Else  
  7. javawsInstalled = 1  
  8. End If  
  9. If Not(IsObject(CreateObject("JavaWebStart.isInstalled.2" ))) Then  
  10. javaws12Installed = 0  
  11. Else  
  12. javaws12Installed = 1  
  13. End If  
  14. If Not(IsObject(CreateObject("JavaWebStart.isInstalled.3" ))) Then  
  15. javaws142Installed = 0  
  16. Else  
  17. javaws142Installed = 1  
  18. End If  
  19. End If  
  20. </SCRIPT>  

 

 

上面脚本用来检测 IE 中安装的是哪个版本。脚本实例化一个 JavaWebStart.dll 中的 isInstalled COM 对象。这个对象决定几件事情:

l   客户端机器是否有任何的 JAWS 安装了

l   客户端机器是否安装了 JAWS 1.2

l   客户端机器是否安装了 JAWS 1.4.2

 

Launching the application if JAWS is Installed or providing a link for auto-install or general download page

可以编写另外的脚本用来决定是否:

l 提供一个链接给应用程序的 jnlp 文件(在 JAWS 安装了情况下)

l 初始化 JRE1.4.2 的自动下载,它包含了 JAWS( JAWS 没有安装且用户用的是 IE)

l 或者提供一个下载 1.4.2 SDK/JRE 的通用下载页面( JAWS 没有安装且用户不是使用 IE

 

下面的代码处理这种场景:

 

 

  

Java.sun.com 站点提供的 PluginBrowserCheck 程序的作用是检查客户端微软 Windows 平台上使用的是否是 IE 浏览器。如果是用 IE 浏览器,则把用户引向自动安装页面 http://www.yyy.zzz/download.html (下段描述怎样创建自动安装页面)。如果 PluginBrowserCheck 程序检测到客户 WINDOWS 平台上用的不是 IE 浏览器,则用户会被重定向到 java.sun.com 站点上 1.4.2 JRE 通用的下载页面。

 

Creating an auto-install page

 

Java Web Start 一起,一个 ActiveX 控件也会被下载到客户端,这个 ActiveX 控件将使用新安装的 Java Web Start 装载启动应用程序。 PARAM 标签指明了应用程序 jnlp 的位置,以便在 JRE 安装完成之后,可以被自动装载运行。

 

JRE 版本

.cab File for codebase attribute for Autodownload

1.4.0

http://java.sun.com/products/plugin/autodl/jinstall-1_4_0-win.cab

1.4.0 _01

http://java.sun.com/products/plugin/autodl/jinstall-1_4_0_01-win.cab

1.4.0 _02

http://java.sun.com/products/plugin/autodl/jinstall-1_4_0_02-win.cab

1.4.0 _03

http://java.sun.com/products/plugin/autodl/jinstall-1_4_0_03-win.cab

1.4.0 _04

http://java.sun.com/products/plugin/autodl/jinstall-1_4_0_04-win.cab

1.4.1

http://java.sun.com/products/plugin/autodl/jinstall-1_4_1-windows-i586.cab

1.4.1 _01

http://java.sun.com/products/plugin/autodl/jinstall-1_4_1_01-windows-i586.cab

1.4.1 _02

http://java.sun.com/products/plugin/autodl/jinstall-1_4_1_02-windows-i586.cab

1.4.1 _03

http://java.sun.com/products/plugin/autodl/jinstall-1_4_1_03-windows-i586.cab

1.4.2

http://java.sun.com/products/plugin/autodl/jinstall-1_4_2-windows-i586.cab

1.4.2 _01

http://java.sun.com/update/1.4.2/jinstall-1_4_2_01-windows-i586.cab

1.4.2 _02

http://java.sun.com/update/1.4.2/jinstall-1_4_2_02-windows-i586.cab

1.4.2 _03

http://java.sun.com/update/1.4.2/jinstall-1_4_2_03-windows-i586.cab

1.5.0

http://java.sun.com/update/1.5.0/jinstall-1_5_0-windows-i586.cab

1.5.0 _01

http://java.sun.com/update/1.5.0/jinstall-1_5_0_01-windows-i586.cab

1.5.0 _02

http://java.sun.com/update/1.5.0/jinstall-1_5_0_02-windows-i586.cab

1.5.0 _03

http://java.sun.com/update/1.5.0/jinstall-1_5_0_03-windows-i586.cab

 

 

1.3 应用程序开发应考虑的几点

开发基于 Java Web Start 部署技术的应用程序和普通的应用程序几乎没有什么差别,入口依然是

public static void main(String[] argv) . 然而,为支持 WEB 部署-自动下载且启动应用程序-且确保应用程序能在安全的沙袋中运行,有几个额外需要考虑的地方:

1.   应用程序必须由一系列 JAR 文件组成。

2.   应用程序的所有资源,比如文件以及图像必须打包成 JAR 文件。而且他们必须使用 getResource (。。。)方法来进行引用。

3.   如果所写的应用程序在安全的沙袋里面运行,必须遵循下面的约束 :

a)   不能访问本地磁盘

b)   所有 JAR 文件必须从同一台主机上下载

c)   只允许建立到所下载的 JAR 文件所在的主机的网络连接

d)   不能安装安全管理器

e)   不能使用本地库( native libraries

f)   只能访问部分系统属性( system properties , 应用程序有权读写 JNLP 文件中定义的所有系统属性。有权读 Applet 能访问( access )的属性。

g)   应用程序允许调用 System.exit

h)   如果希望应用程序能无限制的访问系统资源,就必须对 JAR 进行签名。每个 JAR 文件里面的所有项都必须签名。

 

Ø   Retrieving Resources from JAR files

Java Web Start 仅从 Web Server 上传输 JAR 文件到客户端机器上, JAWS 来决定把 JAR 文件在本地机器上的存储位置。因此,应用程序无法使用与本地磁盘相关的路径来访问资源,比如图像或者配置文件。

所用应用的资源必须从 JAR 文件中获取,它们在 JNLP 文件中由 resource 段进行描述指定,或者也可以显示地使用一个到 Web Server HTTP request 来获取。但建议还是把资源存储在 JAR 文件中,因为会被缓存在本地机器上。

 

ClassLoader cl = this.getClass().getClassLoader();
   Icon saveIcon  = new ImageIcon(cl.getResource("images/save.gif "));
   Icon cutIcon   = new ImageIcon(cl.getResource("images/cut.gif "));

假定 JAR 文件中存在项目 images/save.gif images/cut.gif

 

 

Ø   Security and Code Signing

1.    保护用户免受恶意代码影响本地文件

2.    保护企业免使代码尝试访问或者破坏网络上的数据。

 

如果应用程序的 JAR 经过了签名,那么 JAWS 将会检查 JAR 文件的内容是否被修改过,如果被修改过,则 JAWS 不会运行应用程序。因为程序被第三方损害过。

 

支持代码签名对用户和应用提供者而言都是很重要。客户可以知道代码来自可以信任的地方。因为代码提供者对代码进行了签名,第三方无法在 WEB 上对代码进行修改。一个签名的应用如果被用户接收信任,那么该应用程序就拥有额外的访问系统的特权了,比如访问本地磁盘。 JAWS 在装载启运应用程序之前,会显示一个对话框,该对话框描述了应用程序来自哪里,所基于的签名者的证书。然后用户可以根据这些信息来决定是否授予特定的权限给下载的代码。

 

如果应用程序的所有 JAR 文件都签名了,那么,在 JNLP 文件中包含如下脚本,就可以允许应用程序任意访问客户系统了。

<security>
     <all-permissions/>
   </security>

 

Java 2 SE JRE 1.4.2 支持 SHA1withDSA and MD5withRSA 算法进行代码签名。 Java 2 SE JRE 1.4.2 提供了的标准工具 jarsigner 用来进行代码签名以及创建证书。

 

用一个测试用的证书来签名 JAR 文件的 步骤如下:

1.    确认有 keytool jarsigner 工具(在 JDK bin 目录下)

2.    keystore 里创建一个新的 key

keytool -genkey  - keystore  myKeystore -alias myself

工具会提示输入一些关于 key 的一些其它信息,比如密码和名称等。这个命令将在磁盘上创建 myKeystore 文件。

3. 创建一个自签名的用来测试用的证书:

keytool -selfcert -alias myself -keystore myKeystore

会提示你输入建立 key 时输入的密码。

4.   检查是否正确:

keytool -list -keystore myKeystore

 

显示如下信息:

Keystore type: jks
Keystore provider: SUN
Your keystore contains 1 entry:
myself , Tue Jan 23 19:29:32 PST 2001, keyEntry,
Certificate fingerprint (MD5):
C2:E9:BF:F9:D3:DF:4C:8F:3C:5F:22:9E:AF:0B:42:9D

5.   最后,用测试证书来签名 JAR 文件

jarsigner -keystore myKeystore test.jar myself

针对所有的 JAR 文件重复这个步骤。

 

Ø  How to Encode JNLP Files

为了编码( encode )一个 JNLP 文件,在 XML prolog 中指定 encoding:

<?xml version="1.0" encoding="utf-16"?>

注意:这个 XML prolog 本身必须是 UTF-8-encoded

 

Ø   Dynamic Download of HTTPS Certificates

未完!

 

1.4 JnlpDownloadServlet Guide

对已打包成 WAR 文件或者为打包的 WEB 程序,自动修改 JNLP 文件 , 自动下载。

 

1.5 JNLP File Syntax

一个完整的例子

 

 

  1. <?xml version= "1.0"  encoding= "utf-8" ?>  
  2. <!-- JNLP File for  SwingSet2 Demo Application -->  
  3. <jnlp  spec="1.5+"   codebase= "http://my_company.com/jaws/apps"   href= "swingset2.jnlp"  mce_href= "swingset2.jnlp" >  
  4.   <information>  
  5.     <title>SwingSet2 Demo Application</title>  
  6.     <vendor>Sun Microsystems, Inc.</vendor>  
  7.     <homepage href="docs/help.html"  mce_href= "docs/help.html" />  
  8.     <description>SwingSet2 Demo Application</description>  
  9.     <description kind="short" >A demo of the capabilities of the Swing Graphical User Interface.</description>  
  10.     <icon href="images/swingset2.jpg"  mce_href= "images/swingset2.jpg" />  
  11.     <icon kind="splash"  href= "images/splash.gif"  mce_href= "images/splash.gif" />  
  12.     <offline-allowed/>   
  13.     <association mime-type="application-x/swingset2-file"  extensions= "swingset2" >  
  14.     <shortcut online="false" >  
  15.       <desktop/>  
  16.       <menu submenu="My Corporation Apps" />  
  17.     </shortcut>  
  18.   </information>  
  19.   
  20.   <information os="linux" >     
  21.     <title> SwingSet2 Demo on Linux </title>  
  22.     <homepage href="docs/linuxhelp.html"  mce_href= "docs/linuxhelp.html" >  
  23.   </information>  
  24.   
  25.   <security>  
  26.       <all-permissions/>  
  27.   </security>  
  28.   
  29.   <resources>  
  30.     <j2se version="1.4.2+"  java-vm-args= "-esa -Xnoclassgc" />  
  31.     <jar href="lib/SwingSet2.jar"  mce_href= "lib/SwingSet2.jar" />  
  32.   </resources>  
  33.   
  34.   <application-desc main-class = "SwingSet2" />  
  35. </jnlp>   

   

根元素是 jnlp ,它包含四个子元素: information , security , resources application-desc . 此外, Java Web Start 也通过 applet-desc 元素支持装载运行 applets

 

Ø  JNLP 元素

spec :指明 JNLP 规格的版本,默认为 "1.0+".

codebase JNLP 文件中所有 href 属性指定的相对 URL 都是以这个属性值为基础的。

Href : 一个 URL, 指定 JNLP 文件本身所在的位置

Ø  INFORMATION 元素

n    Title : 应用程序的名字,必需

n    Vendor : 应用厂商的名字,必需

n    Homepage : 只包含一个 href 属性,用来定位应用程序所在的主页。 JNLP Client 能用它来把用户指向 Web Page, 在哪里用户能找到更多的关于应用程序的信息

n    Description : 简短的对应用程序的描述。此元素是可选的。 kind 属性用来规定怎样使用描述, kind 的值如下三个 One-line short tooltip. 每个这种的元素只能有一个, JNLP 文件中可以包含多个 description 元素。比如某 JNLP 文件种有如下代码:

 

    <description>SkylinkLMS - Logistics Management System</description>

    <description kind="short" >SkylinkLMS</description>

<description kind="one-line" >SkylinkLMS</description>

 

没有指定 kind 属性的元素将被当作默认的,在 Java Web Start 没有找到所需要的 kind 时,就使用默认的

n    Icon : 包含一个指向格式为 GIF 或者 JPEG 的图片的 HTTP URL. 这个图标用来描述以下几个方面:

ü   during launch when Java Web Start presents the application to the user;

ü   in the Java Application Cache Viewer;

ü   in desktop shortcuts.

       下载时,显示 64 × 64 ,在 Java Application Cache Viewer 以及桌面图标为 32x32.Java Web Start 自动调整图标到合适的尺寸。

ü  Icon 元素需要 href 属性用来指定图标文件的目录以及名字。

ü  可选的属性 kind= splash 用来指定在启动应用程序期间显示的“ splash ”屏幕中,需要显示的图标。

如果 JNLP 文件中没有包含 kind 属性的 icon 元素, Java Web Start 仍将用 information 元素中的其它项来构造 splash screen.JNLP 文件中可以包含多个图标元素。

  <icon href="images/swingset2.jpg"/>
      <icon kind="splash" href="images/splash.gif"/>

ü   可选的 WIDTH HEIGHT 属性描述了图标的大小。

 如果 JNLP 文件中没有包含任何的 icon 元素, splash image 将用应用程序的 title vendor 来构造。

 

n                      Ø   Offline-allowed 元素

可选元素,说明应用程序是否可以离线启动。如果指定,则应用程序在离线的情况下也可以装载运行。如果应用离线装载运行,将不会检查程序的更新情况,并且 API BasicService.isOffline() 将返回 true.

Offline-allowed 元素控制 Java Web Start 怎样检查应用程序的更新情况。如果此元素没有出现(也就是没有指定)则应用程序必须在线运行,在启动应用之前将会先检查程序是否存在更新版本,如果有,则会在下载了更新版本之后才启动应用程序。

如果指定了 offline-allowed 元素, Java Web Start 也会检查应用是否存在更新。但是,如果应用已经下载,那么检查动作在几秒之后就会 timeout( 所以应提供一个比较快速 server connection), 如果 timeout, 就会启动 cached application.

n   Shortcut 元素

可选元素。包含可选的 online 属性,为 true, 应用将优先创建一个在线启动的快捷,如果为 false, 应用将优先创建一个离线启动的快捷。

子元素: desktop 指示应用程序是否放置一个快捷方式在桌面上

子元素: menu 指示应用程序是否放置一个菜单在 start menu 上。这个元素有一个 submenu 属性,包含一个字符串,描述是否建立一个子菜单。

n   Association 元素 可选的元素。用来说明是否让操作系统把某扩展和某 mime-type 注册关联。它有两个必需的属性 extensions mime-type

n   Related_content 元素

<information>

...

<related-content href="readme.html">

<title>README</title>

<description>The README file contains  the … product</description>

<icon href="readme.jpg"/>

</related-content>

 

<related-content href="register.html">

<title>Program Registration</title>

<description>Register this product to receive support products</description>

  </related-content>

</information>

        

Ø               Ø   Security 元素

默认情况下,应用程序在受限的执行环境中运行,类似于小应用中沙袋。这个元素允许应用请求不受限制的访问。

如果 all-permissions 子元素被指定了,那么应用就有对客户端机器和本地网络“全部访问权限”。如果应用请求了“全部访问权限”,那么所有的 JAR 文件都必须签名。而且应用在第一次启动时会提示用户是否接收证书。

Ø              Ø   Resources 元素

资源元素用来描述所有的资源,比如 jar 文件、 native 库、系统属性等,它们都是应用的一部分。资源的定义受限于特定的操作系统、 architecture 或者 locale os arch.

资源元素有留个不同的子元素 jar , nativelib , j2se , property , package , and extension . 在这里不讨论 package extension

 n   jar 子元素

指定应用所需的 JAR 文件所在的路径,用 href 属性指明。

<jar href="sound.jar" download ="eager" main=”true”/>

Jar 元素有一个可选的 main 属性,用来指出此包中是否包含 Main 类。在 JNLP 文件中,至少要有一个 jar 元素指明是 main= ”true” 的,如果没有任何 jar 元素指定为 main 的,那么默认第一个 jar 元素就是 main 的。

 

n   nativelib 子元素

指出包含本地库的JAR 文件。本地库文件必须符合操作系统平台命名的习惯,比如WINDOWS 必须时.dllSolaris/Linux 必须

lib*.so 。应用程序负责调用System.loadLibrary 来装载它们。

 

<resources os="Windows"/>

<nativelib href="lib/windows/corelibs.jar"/>

</resources>

 

<resources os="SunOS" arch="SPARC">

<nativelib href="lib/solaris/corelibs.jar"  download ="lazy"/>

</resources>

 

Download 属性有两个值: eager 说明在应用程序启动之前必须下载到本地; lazy 说明在启动之前没有必要下载到本地。

 

n j2se 子元素

此元素指明了应用程序所支持的 JRE 的版本以及传递给 JVM 的标准参数

        <j2se version ="1.3" initial-heap-size="64m" max-heap-size="128m"/>
       <j2se version ="1.4.2+" href ="http://java.sun.com/products/autodl/j2se"

java-vm-args="-esa -Xnoclassgc"/>

默认情况下, version 指的是平台版本,当前 JRE 平台版本有 1.2,1.3,1.4 1.5( 平台版本没有包含微版本号比如 1.4.2 )

 

Version 也可以指定精确的版本号,比如 1.3.1 _07, 1.4.2, or 1.5.0-beta2 但必须包含 href 属性,如:

<j2se version="1.4.2" href="http://java.sun.com/products/autodl/j2se"/

<j2se version="1.4.2_04" href="http://java.sun.com/products/autodl/j2se"/>

 

如果指定了一个平台的版本, Java Web Start 将不会考虑那些 已安装了的 non-FCS (i.e., milestone) JRE ,比如:

<j2se version="1.4+"/>

将不会考虑已安装了的 1.4.1 -ea 或者 1.4.2-beta JRE as a match for the request

 

    n  property 元素

 指定系统属性,可以通过 System.getProperty() System.setProperty() 来访问。这个元素有两个属性: name value.

 

 

 

Application-desc 元素

此元素用来指明装载运行的普通的应用程序而不是 applet. 它有几个属性:

可选属性 main-class 以及一个或多个嵌套的 argument 子元素

  <application-desc main-class="Main">
      <argument>arg1</argument>
      <argument>arg2</argument> 
    </application-desc>

  如果 JNLP 文件包含的第一个 JAR 文件中的 manifest 文件中有 main class, 那么就可以省略 main-class 属性

 

1.6 装载细节

应用程序的 main class 默认情况下,是由 application-desc 元素的 main-class 属性决定的。如果没有指定这个属性,那么 main JAR 文件中的 manifest 项将被使用,其中指出了哪个是 main class.

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章