返回介绍

增加 Stripes

发布于 2025-04-21 21:42:16 字数 19923 浏览 0 评论 0 收藏

为了让我们的项目可以在 Web 环境中运行,需要对 compile 构建任务进行一些修改。到现在为止,我们一直在用 Ant 来启动应用程序,所以要为 Ant 提供正确的代码类路径信息。目前一切运行正常,但是 Tomcat 的类加载管理机制要复杂得多,因此,将所有需要的依赖文件直接复制到应用程序的 WEB-INF/lib 目录下是最简单的方法。我们也希望编译任务可以将文件放在 WEB-INF 中,这样 Tomcat 就可以直接找到它们。参见例 14-9。

例 14-9:针对 Web 应用程序而修改 Compile 构建任务

……

<property name="source.root"value="src"/>

<property name="class.root"value="webapp/WEB-INF/classes"/>❶

<property name="data.dir"value="webapp/WEB-INF/data"/>❷

……

<target name="compile"depends="prepare"

description="Compiles all Java classes">

<javac srcdir="${source.root}"

destdir="${class.root}"

debug="on"

optimize="off"

deprecation="on">

<classpath refid="project.class.path"/>

</javac>

<filter token="docroot"value="${basedir}/webapp"/>❸

<copy todir="webapp/WEB-INF"filtering="true"overwrite="true">

<fileset dir="src"includes="applicationContext.xml"/>

</copy>❹

<copy todir="webapp/WEB-INF/lib"flatten="true">

<fileset refid="dependency.fileset"/>

</copy>❺

</target>

……

❶因为我们正在将应用程序移植到 J2EE Web 应用程序,所以需要把 Java 类放到 webapp/WEB-INF/classes 目录下。最简单的实现方法就是修改 class.root 属性值。

❷由于当前的工作目录还不能确定,所以 HSQLDB 也不能够在数据的相对路径中找到数据库。因此,我们需要为 Hibernate 提供数据库的完整路径。为此,创建一个 data.dir 属性,当复制 applicationContext.xml 文件时会用到这个属性。

❸ant 的 filter 构建任务指定当复制 applicationContext.xml 时,需要用 Web 应用程序的实际路径来替换 @docroot@标记。

❹需要将 Spring 的 applicationContext.xml 文件复制到 webapp/WEB-INF 目录下,以便 Tomcat 可以找到它。

❺既然应用程序不再由 Ant 来启动了,我们也就不能再依赖本地 Maven 仓库中找到的 Java 库。J2EE 规范要求把 Web 应用程序的 JAR 文件放到 WEB-INF/lib 目录中,所以这一步就是将我们需要的文件从 Maven 本地仓库复制到 WEB-INF/lib 目录中。

为了让用 docroot 配置的过滤器(filter)对任何目录都有效,我们需要在 src/application-Context.xml(第 13 章中创建的)中添加 @docroot@标记,例 14-10 演示了这一修改。

例 14-10:applicationContext.xml 中数据库配置文件位置的变化

……

<property name="hibernateProperties">

<props>

<prop key="hibernate.show_sql">true</prop>

<prop key="hibernate.format_sql">true</prop>

<prop key="hibernate.transaction.factory_class">org.hibernate.

transaction.JDBCTransactionFactory</prop>

<prop key="hibernate.dialect">org.hibernate.dialect.HSQLDialect</prop>

<prop key="hibernate.connection.autocommit">false</prop>

<prop key="hibernate.connection.release_mode">after_transaction</prop>

<prop key="hibernate.connection.shutdown">true</prop>

<prop key="hibernate.connection.driver_class">org.hsqldb.jdbcDriver

</prop>

<prop key="hibernate.connection.url">jdbc:hsqldb:@docroot@/WEB-INF

/data/music</prop>

<prop key="hibernate.connection.username">sa</prop>

<prop key="hibernate.connection.password"></prop>

<prop key="hibernate.current_session_context_class">thread</prop>

<prop key="hibernate.jdbc.batch_size">0</prop>

</props>

</property>

……

现在,我们的构建环境已经修改好,可以开始使用 Stripes 了。为了让 Spring、Hibernate 以及 Stripes 协同工作,还需要对 web.xml 进行多处修改。例 14-11 中演示了很多内容,但每个配置项都有一定的作用。稍后将介绍一下其中重要的配置。

例 14-11:针对 Stripes 集成而修改后的 web.xml

<?xml version="1.0"encoding="UTF-8"?>

<web-app xmlns="http://java.sun.com/xml/ns/j2ee"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee

http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"

version="2.4">

<listener>

<listener-class>

org.springframework.web.context.ContextLoaderListener

</listener-class>❶

</listener>

<context-param>

<param-name>contextConfigLocation</param-name>

<param-value>/WEB-INF/applicationContext.xml</param-value>

</context-param>

<!--Hibernate OpenSession Filter-->

<filter>

<filter-name>hibernateFilter</filter-name>❷

<filter-class>

org.springframework.orm.hibernate3.support.OpenSessionInViewFilter

</filter-class>

<init-param>

<param-name>singleSession</param-name>

<param-value>true</param-value>

</init-param>

<init-param>

<param-name>sessionFactoryBeanName</param-name>

<param-value>sessionFactory</param-value>

</init-param>

<init-param>

<param-name>flushMode</param-name>

<param-value>ALWAYS</param-value>

</init-param>

</filter>

<filter>

<display-name>Stripes Filter</display-name>❸

<filter-name>StripesFilter</filter-name>

<filter-class>

net.sourceforge.stripes.controller.StripesFilter

</filter-class>

<init-param>

<param-name>ActionResolver.PackageFilters</param-name>

<param-value>com.oreilly.*</param-value>

</init-param>

<init-param>

<param-name>ActionResolver.UrlFilters</param-name>

<param-value>WEB-INF/classes</param-value>

</init-param>

<init-param>

<param-name>Interceptor.Classes</param-name>

<param-value>

net.sourceforge.stripes.integration.spring.SpringInterceptor,

net.sourceforge.stripes.controller.BeforeAfterMethodInterceptor

</param-value>

</init-param>

</filter>

<filter-mapping>

<filter-name>hibernateFilter</filter-name>

<url-pattern>*.jsp</url-pattern>

<dispatcher>REQUEST</dispatcher>

</filter-mapping>

<filter-mapping>

<filter-name>hibernateFilter</filter-name>

<url-pattern>*.action</url-pattern>

<dispatcher>REQUEST</dispatcher>

</filter-mapping>

<filter-mapping>

<filter-name>StripesFilter</filter-name>

<url-pattern>*.jsp</url-pattern>

<dispatcher>REQUEST</dispatcher>

</filter-mapping>

<filter-mapping>

<filter-name>StripesFilter</filter-name>

<url-pattern>*.action</url-pattern>

<dispatcher>REQUEST</dispatcher>

</filter-mapping>

<servlet>

<servlet-name>StripesDispatcher</servlet-name>❹

<servlet-class>

net.sourceforge.stripes.controller.DispatcherServlet

</servlet-class>

<load-on-startup>1</load-on-startup>

</servlet>

<servlet-mapping>

<servlet-name>StripesDispatcher</servlet-name>

<url-pattern>*.action</url-pattern>

</servlet-mapping>

</web-app>

❶ContextLoaderListener 过滤器用于初始化 Web 应用程序的 Spring Framework。

❷hibernateFilter 是由 Spring 提供的,用于获取一个封装了所有请求处理的 Hibernate 会话。使用这个功能,我们就可以不必再自己管理 Hibernate 会话。这一功能之所以精彩,是因为正确的会话管理可能是编写支持 Hibernate 的 Web 应用程序时最具技巧性的处理之一。

❸将 Stripes Filter 映射到*.action 和*.jsp HTTP 请求。它提供了调用 JSP 或 ActionBeans 时所需的一些基本表单处理和配置服务。

❹StripesDispatcher servlet 映射到了*.action,负责决定应该调用哪个 ActionBean 中的哪个方法,以及处理由这些事件方法返回的 Resolution。

图 14-2 Stripes 下载页面

在开始使用 Stripes 以前,为了让它正常运行,还有最后一个需要安装的配置。Stripes-Resources.properties 文件位于 classes 类目录中,它为 Stripes 提供一些必需的格式化字符串。对于这个例子来说,最简单的办法就是用下载的随书示例代码,直接从 examples/ch14/src 目录中复制 StripesResources.properties 文件,不过也可以从 Stripes 下载包( [1] )中得到这个文件。在 Stripes 1.4.3 下载页面的中间,点击"Download"按钮,如图 14-2 所示,继续链接到 SourceForge 下载页面,对 SourceForge 我们都应该很熟悉了。在下载好 stripes-1.4.3.zip 以后,将它进行解压,再把 stripes-1.4.3/lib/StripesResources.properties 复制到你的 src 目录下。如果愿意,你可以看看这个文件的内容,不过它只是本章示例需要的一个依赖文件而已。

最后就应该编写一点代码了。首先,我们来编写两个 JSP 文件,再接着开发一个 ActionBean。如果你最近几年写过些 JSP 代码,那么我们在这儿编写的 JSP 文件也不会令你感到陌生。不过,你可能不认得的是前缀(prefix)为"stripes"的几个标签。Stripes 标签库可以作为 JSTL 的补充,辅助你的应用程序类和视图协同工作。例 14-12 演示了一个用于编辑曲目专辑的页面源代码。

例 14-12:曲目编辑视图:webapp/albums/edit.jsp

<%@page contentType="text/html;charset=UTF-8"language="java"%>

<%@taglib prefix="c"uri="http://java.sun.com/jsp/jstl/core"%>

<%@taglib prefix="stripes"uri="http://stripes.sourceforge.net/stripes.tld"%>❶

<stripes:useActionBean

beanclass="com.oreilly.hh.web.AlbumActionBean"

var="actionBean"event="edit"/>❷

<h1>Album Edit Page</h1>

<stripes:form action="/Album.action">❸

<stripes:errors/>

<stripes:hidden name="album.id"></stripes:hidden>❹

<table>

<tr>

<td>Title:</td>

<td><stripes:text name="album.title"/></td>❺

</tr>

<tr>

<td>Discs:</td>

<td><stripes:text name="album.numDiscs"/></td>

</tr>

</table>

<h2>Album Comments</h2>

<c:choose>

<c:when test="${actionBean.album.id!=null}">

<stripes:link href="/albums/edit_comment.jsp">

<stripes:param name="album.id"value="${actionBean.album.id}"/>

Add A Comment

</stripes:link>

<c:if test="${empty actionBean.album.comments}">

There are no album comments yet.

</c:if>

</c:when>

<c:otherwise>

Please add the album before entering comments.

</c:otherwise>

</c:choose>

<ul>

<c:forEach items="${actionBean.album.comments}"var="comment">

<li>${comment}</li>

</c:forEach>

</ul>

<br/>

<stripes:submit name="save"value="Save"></stripes:submit>❻

</stripes:form>

可以看到,这是一个外观界面相当普通的 JSP 页面,不过,也有一些东西值得更仔细地研究一下:

❶taglib 声明用于引入 Stripes 标签库,以便页面上的代码可以通过"stripes:"前缀来使用它们。

❷这里使用 useActionBean 标签来告诉 Stripes 初始化 AlbumActionBean,如果它的 edit 事件没有发生的话(例如,浏览器直接请求 JSP 页面,而不是通过 action URL),就运行这个事件。edit 事件将会从数据库中加载 Album 对象,为生成表单做好准备。

❸Stripes 的 form 标签会输出一个普通的 HTML 表单,以及很多隐藏在幕后的东西。它的 action 属性指定表单将要提交到的 ActionBean。

❹Stripes 的 hidden 标签的作用类似于普通 HTML 的 input 标签。使用 Stripes 版本的标签的好处是:它的值是自动生成的。

❺Stripes 的 text 标签可以创建一个文本输入字段(这和你想到的应该一致)。hidden 标签,它可以自动生成其 value 属性的值。

❻Stripes 的 submit 标签会生成一个典型的提交按钮。这里要注意的是:submit 标签的 name 属性值是当表单提交时要调用的 ActionBean 事件处理方法的名称。在这个例子中,调用的是 AlbumActionBean.save()(本章后面在讨论 ActionBean 时将进一步解释事件和它们的处理器)。

Stripes 也有一个 label 标签,可以帮助设置本地化(localization)配置,让代码保持简洁。我们在这没有使用它,是因为想只关注 Hibernate 的东西。幸好 Stripes 也为这些标签提供了很详细的文档( [2] )。

这个页面写好以后,接下来还要编写一个用于列出数据库中的专辑的页面,可以将这个页面作为一个加载页面,通过它能够看到数据库中有什么信息。例 14-13 中的页面代码看起来比 edit.jsp 页面要少一点,不过,要注意一下结尾处的 Stripes link 标签。

例 14-13:专辑列表视图:webapp/albums/list.jsp

<%@page contentType="text/html;charset=UTF-8"language="java"%>

<%@taglib prefix="c"uri="http://java.sun.com/jsp/jstl/core"%>

<%@taglib prefix="stripes"

uri="http://stripes.sourceforge.net/stripes.tld"%>

<stripes:useActionBean beanclass="com.oreilly.hh.web.AlbumActionBean"

var="actionBean"event="list"/>

<table>

<tr>

<th>title</th>

<th>discs</th>

<th>action</th>

</tr>

<c:forEach items="${actionBean.albums}"var="album">

<tr>

<td>${album.title}</td>

<td>${album.numDiscs}</td>

<td><stripes:link href="/albums/edit.jsp">

<stripes:param name="album.id"value="${album.id}"/>

edit

</stripes:link></td>

</tr>

</c:forEach>

</table>

<stripes:link href="/albums/edit.jsp">new</stripes:link>

stripes:link 标签用于将程序和 HTML 锚点(anchor)链接起来,它提供了几个属性,可以构建指向 ActionBean 事件处理器以及 JSP 的 URL。

编写 ActionBean

接下来应该编写一个 ActionBean。可以将 ActionBean 看做是 MVC(Model、View、Controller)模式中的控制器组件。例 14-14 演示了我们的第一个 AlbumActionBean,它可以让你很好地了解编写 ActionBean 需要涉及哪些内容。

这个类中的方法可以分为两大类:属性存取器和事件处理器。属性存取器就像其他 Java Bean 的 setter 和 getter 方法一样,所以它们看起来也差不多。另一方面,那些返回 Resolution 的方法就显得有些陌生了,不过其概念也相当简单。当一个请求到达 StripesDispatcher 时,HTTP 请求中的某个部分就可以指示出一个事件的名称,由该事件的处理器负责处理这个 HTTP 请求。当 Stripes 请求的生命周期经过它的 BindingAndValidation 阶段后,就会调用由请求确定的事件处理器方法(StripesDispatcher 使用反射来查找其名称和事件匹配的方法,该方法返回的就是一个 Resolution)。由事件处理器返回的 Resolution 对象接着再由 StripesDispatcher 进行处理(通常进行转发或重定向)。例 14-14 演示了我们的 AlbumActionBean.java。

例 14-14:专辑控制器:AlbumActionBean.java

package com.oreilly.hh.web;

import java.util.List;

import org.apache.log4j.Logger;

import net.sourceforge.stripes.action.*;

import net.sourceforge.stripes.integration.spring.SpringBean;

import net.sourceforge.stripes.validation.*;

import com.oreilly.hh.dao.AlbumDAO;

import com.oreilly.hh.data.Album;

/**

*Class that implements the web based front end of our Jukebox.

*

*/

public class AlbumActionBean implements ActionBean{

/**

*Logger

*/

private static Logger log=Logger.getLogger(AlbumActionBean.class);

/**

*The ActionBeanContext provided to this class by Stripes DispatcherServlet.

*/

private ActionBeanContext context;

/**

*The list of Album objects we will display on the Album list page.

*/

private List<Album>albums;

/**

*The Album we are providing a form for on the edit page.

*/

private Album album;

/**

*The Data Access Object for our Albums.

*/

private AlbumDAO albumDAO;

public ActionBeanContext getContext(){❶

return context;

}

public void setContext(ActionBeanContext aContext){

context=aContext;

}

/**

*The default event handler that displays a list of Albums.

*@return a forward to the Album list jsp.

*/

@DefaultHandler

public Resolution list(){❷

albums=albumDAO.list();❸

return new ForwardResolution("/albums/list.jsp");

}

/**

*The event handler for handling edits to an Album

*@return a forward to the Album edit jsp.

*/

public Resolution edit(){

if(album!=null){❹

album=albumDAO.get(album.getId());

}

return new ForwardResolution("/albums/edit.jsp");

}

/**

*The event handler for saving an Album.

*@return a redirect to the Album list jsp.

*/

public Resolution save(){

albumDAO.persist(album);

log.debug("Redirecting to list!");

return new RedirectResolution("/albums/list.jsp");

}

/**

*A getter for the view to retrieve the list of Albums.

*@return a list of Albums

*/

public List<Album>getAlbums(){❺

return albums;

}

/**

*A setter for the DispatcherServlet to call that provides the album to

*save.

*/@param anAlbum

@ValidateNestedProperties({❻

@Validate(field="title",required=true, on={"save"}),

@Validate(field="numDiscs",required=true, on={"save"})

})

public void setAlbum(Album anAlbum){

log.debug("setAlbum");

album=anAlbum;

}

/**

*A getter for the edit view to call.

*@return an Album

*/

public Album getAlbum(){

return album;

}

/**

*A method Spring will call that provides this class with an AlbumDAO

*instance.@param anAlbumDAO The AlbumDAO object

*/

@SpringBean("albumDAO")❼

public void injectAlbumDAO(AlbumDAO albumDAO){

this.albumDAO=albumDAO;

}

}

❶ActionBean 接口惟一要求必须实现的是 setContext()和 getContext()方法,ActionBean 可以通过这两个方法来获取有关它的操作所在的 Stripes 环境的信息。

❷这个返回 Resolution 的 public 的方法在 Stripes 中称为事件处理器。它们被自动绑定到浏览器能够访问的 URL 上。例如,对于访问路径/stripesapp/Album.action?save=,Dispatcher 就会选中这个方法。

❸现在还没有 AlbumDao.list()方法,所以我们需要将它增加到 AlbumDAO 接口(其定义位于第 13 章)和 AlbumHibernateDAO 实现类中。

❹“如果专辑不为 null,那么就加载专辑”似乎有点逆向逻辑的意味,但是真正发生的情况是:当 Stripes 将请求绑定到 ActionBean 中的对象时,它看到的只是一个 album.id 参数,用这个 id 值来创建一个新的 Album 对象,接着再调用这个方法。但是我们真正想知道的是如何从数据库中加载一个 Album 对象,再编辑它,所以这就是我们在这里所做的处理。

❺当在请求中发现表单数据与 bean 属性的命名模型匹配时,Stripes 就会调用 ActionBean 中相应的 public 类型的 getter 和 setter 方法。例如,当请求参数中有 album.id、album.title 以及 album.numDiscs 时,Stripes 就会调用 setAlbum()方法。另一方面,当向浏览器生成表单时,就会使用 getter 方法预生成各个值(这也就是例 14-12 中的那些 stripes:text 之类的标签所完成的作用)。

❻Stripes 提供了验证标注,用于标明当调用事件处理器时应该看到哪些字段。我们在此处不准备深入介绍更详细的内容,你可以在 Stripes 的在线文档( [3] )中找到更多的信息。

❼SpringBean 标注是告诉 Stripes:在 Spring 上下文中查找这个对象值,并插入到这里。我们在这里没有使用 Spring 应用程序中典型的 public 类型的 setter 方法,因为黑客(hacker)可能会调用这样的方法来正确地格式化 Web 请求,出于安全原因,我们应该防止类似的访问。为 Spring 要调用( [4] )的方法采用其他命名规范,通常是个好主意。

如前所述,AlbumDAO 需要有一个 list()方法返回所有的 Album 对象,以及一个 get()方法根据专辑的 id 来取回对应的 Album 对象。为此,我们需要调整一下 AlbumDAO 接口和 AlbumHibernateDAO 实现。例 14-15 演示了更新后的 AlbumDAO.java,修改过的部分以粗体突出显示。

例 14-15:在 AlbumDAO 中增加 list()和 get()方法的定义

package com.oreilly.hh.dao;

import java.util.List;

import com.oreilly.hh.data.Album;

public interface AlbumDAO{

public Album persist(Album album);

public void delete(Album album);

public List<Album>list();

public Album get(Integer id);

}

例 14-16 以粗体突出显示了需要对 AlbumHibernateDAO.java 进行的修改。

例 14-16:在 AlbumHibernateDAO 中增加 list()和 get()方法的实现

package com.oreilly.hh.dao.hibernate;

import java.util.List;

import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import com.oreilly.hh.dao.AlbumDAO;

import com.oreilly.hh.data.Album;

public class AlbumHibernateDAO extends HibernateDaoSupport implements AlbumDAO{

public Album persist(Album album){

album=(Album)getHibernateTemplate().merge(album);

getSession().flush();

return album;

}

public void delete(Album album){

getHibernateTemplate().delete(album);

}

@SuppressWarnings("unchecked")

public List<Album>list(){

return getHibernateTemplate().loadAll(Album.class);

}

public Album get(Integer id){

return(Album)getHibernateTemplate().load(Album.class, id);

}

}

例 14-16 中使用的是 HibernateTemplate.loadAll()方法,它与 Session.loadAll()的功能一样,将返回作为参数提供的类的所有持久化对象的列表。第 13 章已经讨论过使用 HibernateTemplate 类的优点。

现在我们已经有了两个视图,一个 ActionBean,也对 DAO 进行了相应的修改。接下来就可以编译和体验这个 Web 应用程序了。相关命令如例 14-17 所示。

例 14-17:编译我们的 Stripes 应用程序

$ant compile

Buildfile:build.xml

Overriding previous definition of reference to project.class.path

prepare:

compile:

[javac]Compiling 20 source files to

/home/rfowler/Hibernate Book/examples/ch12/webapp/WEB-INF/classes

[copy]Copying 1 file to/home/rfowler/Hibernate Book/examples/ch12/webapp/

WEB-INF

BUILD SUCCESSFUL

Total time:2 seconds

当部署例 14-5 所示的应用程序时,我们将 Context 元素的一个属性设置为:reloadable=true。这样设置以后,当为 Web 应用程序的 WEB-INF/classes 目录提供了新版本的类时,Tomcat 将会自动重新加载程序的 context。假设你让 Tomcat 仍然保持运行,如果稍等片刻,context 就会自动加载完成。在这一处理完成以后,就可以用浏览器来访问 http://localhost:8080/stripesapp/albums/list.jsp,看到的页面应该如图 14-3 所示。

图 14-3 我们的 ActionBean 跑起来了

现在我们的数据库中还没有数据,所以这个页面也不会显示任何专辑信息。但是点击"New"链接,将会打开例 14-12 编写的编辑页面,如图 14-4 所示。

图 14-4 加载 Edit(编辑)页面

可以想象到这个页面的功能,在表单中输入有效的数据,点击"Save"提交表单后,将会把数据保存到数据库中,再返回到列表视图,如图 14-5 所示。如果能够按这个流程走下来的话,就表明你已经成功地集成了 Hibernate、Stripes 以及 Spring!花些时间来思考一下本章进行的数据库处理。令人激动的应该是没有花费多少代码就完成了这么多处理。你在 AlbumDAO 和 AlbumHibernateDAO 中添加了 list()和 edit()两个方法,使用 AlbumDAO 对象来加载和持久化 Album 对象。你也会注意到,整个过程中没有用任何代码来直接处理 HTTPServletResponse 或 HTTPServletRequest 对象—Stripes 已经为你处理好了这些繁琐的工作。

图 14-5 显示了一些内容的列表视图

刚才发生了什么

到目前为止,我们已经用 Stripes、Spring 以及 Hibernate 构建好了一个简单的 Web 应用程序。现在我们可以列出所有的专辑、创建新的专辑、对专辑进行编辑。AlbumActionBean 使用 Stripes 和第 13 章写的 Hibernate DAO 来保存对象,为视图提供对象。

下一步要做什么

既然我们已经讨论了如何实现基本的插入、更新、显示数据的处理,我们就回过头来,看看怎么在程序中处理关联。现在我们还根本没有认真思考过它。

[1] http://www.stripesframework.org/display/stripes/Download. [2] http://www.stripesframework.org/display/stripes/Documentation. [3] http://www.stripesframework.org/display/stripes/Validation+Reference. [4] http://www.stripesframework.org/display/stripes/Spring+with+Stripes.

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。