portlet_Spring Portlet MVC框架简介

portlet

本文是由三部分组成的文章系列的第一篇文章,“开发供在IBM WebSphere Portal中使用的Spring Portlet MVC Framework应用程序”。 这是一篇高级文章; 尽管不需要任何高级知识,但您应该熟悉开发JSR-168 Portlet的基础知识,并且对Spring框架有基本的了解。

在过去的几年中, Spring框架在J2EE开发人员中越来越受欢迎。 它提供了基础结构代码以简化J2EE开发,因此您可以专注于实现应用程序的业务逻辑,而不必担心诸如实现自己的MVC框架或对象关系映射框架之类的事情。

Spring Framework的1.0版具有Web MVC框架,可用于开发基于servlet的应用程序。 从2.0版开始,Spring Framework随Portlet MVC Framework一起提供,Portlet MVC Framework是其Web MVC Framework的副本。 它可用于开发符合JSR-168的portlet应用程序。

这些是Spring Portlet MVC Framework的一些优点:

  1. 您可以在不进行任何更改的情况下,将使用Portlet MVC框架开发的Portlet部署到任何符合JSR-168的portlet容器中。 另一方面,使用IBM Struts Portlet Framework或IBM JSF Framework开发的Portlet依赖于IBM WebSphere Portal运行时。
  2. 大多数其他Portlet MVC框架都尝试将Portlet的动作封装起来,并将请求处理的各个阶段呈现为一个阶段。 Spring Portlet MVC Framework并非如此。 它了解Portlet的两阶段请求处理比Servlet的一阶段请求处理要好,并且它试图利用这一点。 Controller.java类定义了处理Portlet MVC框架中的请求的基本接口(在Struts框架中等效于操作),它定义了handleActionRequest()和handleRenderRequest()方法。 这种方法在构建应用程序时为您提供了更大的灵活性。
  3. Spring Portlet MVC Framework提供了与流行的视图渲染技术(例如JSTL, Apache TilesApache Velocity)的轻松集成。
  4. Portlet MVC框架提供了一个模拟测试框架,可用于测试驱动的Portlet开发。

本文是有关Spring Portlet MVC框架的分步教程。 尽管不需要任何高级知识,我们假设您已经熟悉Spring Framework的基础知识以及与JSR-168兼容的portlet开发。

在本文中,我们讨论了如何为Spring Portlet MVC Framework设置开发环境,然后讨论了如何在Spring Portlet MVC Framework中开发简单的HelloWorld应用程序。 我们还将研究渲染请求的处理方式。 最后,我们说明如何在Portlet中添加对“帮助”和“编辑”模式的支持。

搭建开发环境

首先,您应该使用IBM Rational Application Developer for WebSphere Software设置开发环境。 请注意,本文中的示例代码与Maven构建脚本一起提供,因此您可以使用自己选择的开发环境,甚至可以使用诸如Microsoft Notepad之类的文本编辑器来开发代码。

该示例代码具有相当多的依赖性,例如Spring Web MVC,Spring Core,Apache Commons Logging,Apache Log4j,Apache Commons File Upload和Apache Tiles。 单独下载这些JAR文件并将它们添加到您的类路径可能是个问题,因此在本文中,我们将Apache Maven 2.0作为构建工具使用,该工具将下载所有必需的依赖项。 请按照以下步骤设置您的开发环境:

  1. 在Rational Application Developer中创建一个名为HelloSpringPortletMVC的动态Web项目。
  2. 因为Maven 2.0建议您对项目使用标准目录结构,所以请在“配置Web模块设置”页面上创建HelloSpringPortletMVC项目。 输入:
    • 内容目录等于/ src / main / webapp
    • Java源目录为/ src / main / java
  3. 下载部分提供的链接中下载本文的示例代码。 在c:/ temp目录中展开示例代码ZIP文件。
  4. 将构建脚本(pom.xml)从SpringHelloPortletMVC示例代码复制到项目目录的根文件夹中。
  5. 如果您的计算机上尚未安装Maven 2.0,请访问Apache Maven网站并下载2.x版本的Maven二进制文件。 请参阅Apache Maven下载页面以获取安装说明。
  6. 打开命令行,然后转到HelloSpringPortletMVC项目的根目录。 您可以通过运行mvn package命令来构建代码。 此命令下载项目的所有必需依赖项。 如果您是第一次运行Maven构建脚本,则可能需要花费几分钟来下载所有依赖项。
  7. 运行mvn eclipse:eclipse命令,以便Maven更新项目的.classpath和.project文件以反映所有依赖性。 返回到Rational Application Developer中的HelloSpringPortletMVC项目,右键单击,然后选择Refresh。 您会看到在项目的类路径中添加了几个JAR文件。
  8. 如果收到缺少的M2_REPO变量错误,请选择Windows-首选项-Java-构建路径,然后添加一个M2_REPO类路径变量,该变量指向您计算机上的Maven 2.0存储库。 在Microsoft Windows中,它是:

    c:\Documents and Settings\<loginname>\.m2\repository.

    在Linux中,它是:

    ~/.m2/repository

按照这些说明进行操作之后,您的HelloSpringPortletMVC项目应类似于开发环境中图1所示的项目。

图1. Rational Application Developer设置
Rational Application Developer设置

准备好开发环境后,您可以转到下一个任务:开发HelloWorld Spring Portlet MVC应用程序。

开发HelloWorld Spring Portlet MVC Framework应用程序

学习任何新框架的最佳方法是使用它来开发HelloWorld应用程序。 在本节中,我们将使用Spring Portlet MVC Framework开发HelloWorld Portlet。 为简单起见,我们的HelloWorld Portlet仅支持查看模式。 每当用户尝试访问HelloWorld Portlet时,我们都会向用户显示View.jsp生成的标记。

开发portlet.xml

让我们通过在/ src / main / webapp / WEB-INF /文件夹中创建portlet.xml文件来开始HelloWorld示例开发,如清单1所示。

清单1. portlet.xml的代码清单
<?xml version="1.0" encoding="UTF-8"?>
<portlet-app
	xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd"
	version="1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd 
	http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd" >
<portlet>
	<description>HelloWorld Portlet using Spring MVC</description>
	<portlet-name>HelloSpringPortletMVC</portlet-name>
<display-name>Hello World Spring Portlet MVC Framework Portlet</display-name>
	<portlet-class>		org.springframework.web.portlet.DispatcherPortlet
</portlet-class>
	<supports>
		<mime-type>text/html</mime-type>
		<portlet-mode>VIEW</portlet-mode>
	</supports>

	<supported-locale>en</supported-locale>
	<portlet-info>
<title>
Hello World Spring Portlet MVC Framework Portlet
</title>
		<short-title>
HelloWorldSpringPortletMVC 
 		</short-title>
		<keywords>
spring portlet
</keywords>
	</portlet-info>	
</portlet>
</portlet-app>

DispatcherPortlet负责处理每个客户端请求。 收到请求时,它将找出应使用哪个Controller类来处理此请求,然后根据请求处理阶段调用其handleActionRequest()或handleRenderRequest()方法。 Controller类执行业务逻辑并返回应用于向用户呈现标记的View名称。 然后,DispatcherPortlet将控制转发到该视图,以实际生成标记。

如您所见,DispatcherPortlet是在Spring Portlet MVC Framework中使用的中央调度程序。 请注意,您的Portlet应用程序可以定义多个DispatcherPortlet。 如果这样做,那么这些portlet中的每一个都将操作其自己的名称空间,从而加载其应用程序上下文和处理程序映射。

DispatcherPortlet还负责为此Portlet加载应用程序上下文(Spring配置文件)。 首先,它尝试检查configLocation portlet初始化参数的值。 如果未指定该参数,它将使用portlet名称(即<portlet-name>元素的值),将ports-portlet.xml附加到其中,然后尝试从/ WEB-INF文件夹中加载该文件。 在portlet.xml文件中,我们没有指定configLocation初始化参数,因此让我们在下一部分中创建HelloWorldPortletMVC-portlet.xml文件。

开发HelloSpringPortletMVC-portlet.xml

在应用程序的/ src / main / webapp / WEB-INF文件夹中创建HelloSpringPortletMVC-portlet.xml文件,如清单2所示。

清单2. HelloSpringPortletMVC-portlet.xml的代码清单
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" 
\"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="viewController" 
class="com.ibm.developerworks.springmvc.ViewController"></bean>
	<bean id="portletModeHandlerMapping" class=
"org.springframework.web.portlet.handler.PortletModeHandlerMapping">
            <property name="portletModeMap">
                <map>
                    <entry key="view">
                      	<ref bean="viewController"/>
                    </entry>
                </map>
            </property>
     </bean>
</beans>

HelloSpringPortletMVC-portlet.xml文件是HelloSpringPortletMVC portlet的应用程序上下文文件。 它有几个bean定义:

  • viewController。 此时,请记住viewController bean定义指向com.ibm.developerworks.springmvc.ViewController.java类。
  • portletModeHandlerMapping。 正如我们在上一节中讨论的那样,每当DispatcherPortlet收到一个客户端请求时,它都会尝试找到合适的Controller类来处理该请求。 那就是PortletModeHandlerMapping进入图片的地方。 PortletModeHandlerMapping类是HandlerMapping接口的简单实现,DispatcherPortlet使用它来为每个请求找到合适的Controller。 PortletModeHandlerMapping类将Portlet模式用于当前请求,以找到合适的Controller类来处理请求。 portletModeHandlerMapping bean的portletModeMap属性是我们将Portlet模式名称映射到Controller类的地方。 在示例代码中,我们表明viewController负责处理View模式请求。

开发ViewController.java

在上一节中,您了解了viewController bean负责处理所有View模式请求。 下一步是创建ViewController.java类,如清单3所示。

通过执行以下步骤来创建com.ibm.developerworks.springmvc.ViewController.java:

  1. 右键单击/ src / main / java,然后选择“新建-Java类”。
  2. 对于Package,输入com.ibm.developerworks.springmvc。
  3. 对于名称,输入ViewController。
  4. 单击完成。
  5. 用清单3中所示的代码替换类内容。
清单3. ViewController.java的代码列表
package com.ibm.developerworks.springmvc;

import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import org.springframework.web.portlet.ModelAndView;
import org.springframework.web.portlet.mvc.AbstractController;

public class ViewController extends AbstractController{
	protected ModelAndView handleRenderRequestInternal(RenderRequest request, 
	RenderResponse response) throws Exception {
		ModelAndView modelAndView = new ModelAndView("View");
		return modelAndView;
	}
}

注意:如果您熟悉Apache Struts Framework,则可以考虑与Apache Struts Framework中的Action类类似的Controller类。

Spring Portlet MVC Framework中的每个控制器类都必须实现org.springframework.web。 portlet.mvc.Controller接口直接或间接。 为了使事情变得简单,Spring Framework提供了AbstractController类,它是Controller接口的默认实现。 作为开发人员,您应该始终从AbstractController或其更具体的子类之一扩展控制器。 Controller类的任何实现都应该是可重用的,线程安全的,并且能够在Portlet的整个生命周期中处理多个请求。

在示例代码中,我们通过从AbstractController扩展它来创建ViewController类。 因为我们不想在HelloSpringPortletMVC Portlet中进行任何操作处理,所以我们仅覆盖AbstractController的handleRenderRequest()方法。 现在,HelloWorldPortletMVC唯一要做的就是在收到用户请求时将View.jsp的标记呈现给用户。 为此,返回具有等于View的view值的ModelAndView对象。

开发web.xml

根据Portlet规范1.0,每个Portlet应用程序也是兼容Servlet规范2.3的Web应用程序,并且它需要Web应用程序部署描述符(即web.xml)。 让我们在/ WEB-INF /文件夹中创建web.xml文件,如清单4所示。请按照下列步骤操作:

  1. 打开位于/src/main/webapp/web.xml.的现有web.xml文件/src/main/webapp/web.xml.
  2. 用清单4中的代码替换该文件的内容。
清单4. web.xml的代码清单
<?xml version="1.0" encoding="UTF-8"?>
<web-app id="WebApp_ID" version="2.4" 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">
	<display-name>
Hello Spring Portlet MVC Framework Application
</display-name>
	<servlet>
		<servlet-name>ViewRendererServlet</servlet-name>
		<servlet-class>
			org.springframework.web.servlet.ViewRendererServlet
		</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>ViewRendererServlet</servlet-name>
		<url-pattern>/WEB-INF/servlet/view</url-pattern>
	</servlet-mapping>
	<listener>
		<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
	</listener>
</web-app>

样本portlet的web.xml文件声明了两件事:

  • ViewRendererServlet。 ViewRendererServlet是支持Portlet的桥梁Servlet。 在呈现阶段,DispatcherPortlet将PortletRequest包装到ServletRequest中,并将控制权转发给ViewRendererServlet进行实际呈现。 通过此过程,Spring Portlet MVC框架可以使用与其Servlet版本相同的View基础结构,即Spring Web MVC Framework。
  • ContextLoaderListener。 ContextLoaderListener类负责在Web应用程序启动时加载Web应用程序上下文。 Web应用程序上下文由Portlet应用程序中的所有Portlet共享。 如果有重复的bean定义,则portlet应用程序上下文中的bean定义优先于Web应用程序上下文。

    ContextLoader类尝试读取contextConfigLocation Web上下文参数的值以找出上下文文件的位置。 如果未设置contextConfigLocation参数,则它将使用默认值/WEB-INF/applicationContext.xml加载上下文文件。

开发applicationContext.xml

如上一节所述,如果未设置contextConfigLocation上下文参数的值,则ViewRendererServlet尝试从/WEB-INF/applicationContext.xml文件读取Web应用程序上下文。 让我们在/ WEB-INF /文件夹中创建applicationContext.xml,如清单5所示。

清单5. applicationContext.xml的代码清单
<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" 
"http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
<bean id="viewResolver" class=
"org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass">
<value>
org.springframework.web.servlet.view.JstlView
</value>
</property>
<property name="prefix">
<value>/WEB-INF/jsp/</value>
</property>
<property name="suffix">
<value>.jsp</value>
</property>
</bean>
</beans>

在示例代码中,applicationContext.xml文件仅定义一个bean,即viewResolver bean。 viewResolver bean用于查找给定视图名称的实际JSP文件的位置。 在示例代码中,您希望JSP文件用于最终视图渲染,因此请使用InternalResourceViewResolver,它支持JSP和Tiles视图。

您可能已经注意到,我们在viewResolver bean中设置了几个属性:

  • 前缀等于/ WEB-INF / jsp /
  • 后缀的值等于JSP

这告诉viewResolver,无论何时获得用于视图分辨率的视图名称,都应添加/ WEB-INF / jsp作为前缀,并添加JSP作为后缀。 结果,当我们从ViewController.handleRenderRequestInternal()返回值等于View的ModelAndView时,它会将/WEB-INF/jsp/View.jsp的内容呈现给用户。

现在,最后一步是在/ WEB-INF / jsp文件夹中创建一个简单的View.jsp页面,并显示消息“ Spring Portlet MVC Framework的Hello”。 跟着这些步骤:

  1. 右键单击WEB-INF文件夹,然后选择“新建-文件夹”。
  2. 将文件夹命名为jsp。
  3. 右键单击新的jsp文件夹,然后选择“新建”“文件”。
  4. 将文件命名为View.jsp。
  5. 在JSP文件的编辑器中,输入“ Spring Portlet MVC Framework的Hello”。

现在您的示例应用程序已准备就绪。 您可以通过执行mvn package命令构建HelloportPortMVC-1.0.war来构建portlet。 完成此操作后,部署在WebSphere Portal的目标文件夹中创建的HelloSpringPortletMVC-1.0 war文件。 当您访问HelloWorldPortletMVCPortlet时,应该在查看模式下看到“来自Spring Portlet MVC Framework的Hello”消息。

渲染如何工作

让我们看一下用户尝试访问HelloSpringPortletMVC portlet时发生的情况。

图2. DispatcherPortlet接收渲染请求时发生的事件序列
当DispatcherPortlet接收渲染请求时发生的事件序列

如您所知,当用户尝试访问带有HelloWorldPortletMVC portlet的页面时,或者当用户对该页面上的任何其他portlet执行某些操作或尝试刷新该页面时,呈现请求都会发送到HelloWorldPortletMVC portlet。 在示例代码中,由于DispatcherPortlet是主要的portlet类,因此WebSphere Portal调用其render()方法,然后发生以下事件序列:

  1. DispatcherPortlet的render()方法调用doDispatch()方法,该方法又调用doRender()方法。
  2. 在doRenderService()方法获得控制后,它首先尝试通过调用PortletRequest.getLocale()方法来查找请求的语言环境。 在做出所有与语言环境相关的决定(例如应加载哪个资源束或应基于语言环境向用户显示哪个JSP)的所有与语言环境有关的决定时,将使用此语言环境。
  3. 之后,doRenderService()方法开始遍历为此portlet配置的所有HandlerMapping类,并调用其getHandler()方法以标识用于处理此请求的适当Controller。
  4. 在示例代码中,我们仅将PortletModeHandlerMapping配置为HandlerMapping类。 PortletModeHandlerMapping类读取当前Portlet模式的值,并根据该值找出应用于处理此请求的Controller类。 在示例代码中,将ViewController配置为处理View模式请求,以便PortletModeHandlerMapping类返回ViewController的对象。
  5. 返回ViewController的对象后,doRenderService()方法将调用其handleRenderRequestInternal()方法。
  6. ViewController.java中handleRenderRequestInternal()方法的实现非常简单。 它记录一条消息,说它得到了控制,然后创建了一个ModelAndView实例,其值等于View,并将其返回给DispatcherPortlet。
  7. 在控件返回到doRenderService()之后,下一个任务是弄清楚如何呈现View。 为此,DispatcherPortlet开始遍历您的Portlet应用程序中配置的所有ViewResolvers,并调用其resolveViewName()方法。
  8. 在示例代码中,我们仅配置了一个ViewResolver,InternalResourceViewResolver。 当使用viewName调用其resolveViewName()方法时,它将尝试将/ WEB-INF / jsp作为视图名称的前缀添加,并将JSP作为后缀添加。 并检查/WEB-INF/jsp/View.jsp是否存在。 如果存在,则返回包装View.jsp的JstlView对象。
  9. 将控制返回给doRenderService()方法后,它将创建对象PortletRequestDispatcher,该对象指向/ WEB-INF / servlet / view –即ViewRendererServlet。 然后,它在请求中设置JstlView的对象,并将请求分派到ViewRendererServlet。
  10. ViewRendererServlet获得控制权后,它将从request属性读取JstlView对象,并创建另一个指向/WEB-INF/jsp/View.jsp URL的RequestDispatcher,并将控制权传递给该URL以进行实际的标记生成。 由View.jsp生成的标记返回给用户。

此时,您可能会质疑是否需要ViewRendererServlet。 为什么DispatcherPortlet无法直接将控件转发到View.jsp? 在两者之间添加ViewRendererServlet可使Spring Portlet MVC Framework重用现有的View基础结构。 当我们讨论将Apache Tiles框架与Spring Portlet MVC框架集成起来有多么容易时,您可能会对此更加赞赏。

添加对帮助和编辑模式的支持

在本节中,我们更改HelloSpringPorteltMVC portlet以添加对“帮助”和“编辑”模式的支持。 为了简单起见,我们创建了三个不同的JSP文件。 这些JSP文件中的每个文件都向用户显示当前portlet模式的名称,然后更改HelloWorldPortletMVC框架以根据portlet的当前模式将控制权转发到这些JSP之一。 跟着这些步骤:

在/ WEB-INF / jsp文件夹中创建两个文件Help.jsp和Edit.jsp。 Help.jsp文件类似于清单6。

清单6.帮助文件的HelpController.java的代码清单
<%@page language="java" contentType="text/html; charset=ISO-8859-1"
	pageEncoding="ISO-8859-1" session="false"%>
<%@taglib uri="http://java.sun.com/portlet" prefix="portlet"%>
<portlet:defineObjects />
<p>Hello from Spring Portlet MVC Framework - Help Mode</p>

同样,创建Edit.jsp。 现在,在com.ibm.developerworks.springmvc包中创建HelpController.java,如清单7所示。

清单7.编辑文件的HelpController.java的代码清单
public class HelpController extends AbstractController{
	private static Log log = LogFactory.getLog(HelpController.class);
	
	protected ModelAndView handleRenderRequestInternal(RenderRequest request, 
	RenderResponse response) throws Exception {
log.debug("Entering HelpController.handleRenderRequestInternal()");
		ModelAndView modelAndView = new ModelAndView("Help");
log.debug("Exiting HelpController.handleRenderRequestInternal() " + modelAndView);
		return modelAndView;
	}
}

如您所见,HelpController.java仅覆盖AbstractController的handleRenderRequestInternal()方法。 handleRenderRequestInternal()方法的实现与ViewController.java的实现类似。 也就是说,首先它记录一条消息“ Entering HelpController.handleRenderRequest Internal()”,然后返回ModelAndView对象,其view值等于Help。 同样,为Edit模式创建EditController类,将JSP文件名更改为Edit。

接下来,告诉Spring Portlet MVC Framework应该使用HelpController处理帮助模式请求,并使用EditController处理编辑模式请求。 您可以通过更改HelloSpringPortletMVC-portlet.xml文件来做到这一点,如清单8所示。

清单8. HelloSpringPortletMVC-portlet.xml的代码清单
<bean id="viewController" class="com.ibm.developerworks.springmvc.ViewController"/>
<bean id="editController" class="com.ibm.developerworks.springmvc.EditController"/>
<bean id="helpController" class="com.ibm.developerworks.springmvc.HelpController"/>
	
<bean id="portletModeHandlerMapping" 
class = "org.springframework.web.portlet.handler.PortletModeHandlerMapping">
     <property name="portletModeMap">
     <map>
      	     <entry key="view">
                   	<ref bean="viewController"/>
                 </entry>
                 <entry key="edit">
                      	<ref bean="editController"/>
                 </entry>
                 <entry key="help">
                      	<ref bean="helpController"/>
                 </entry> 
           </map>
    </property>
</bean>

我们必须在HelloSpringPortletMVC-portlet.xml文件中进行一些更改。 首先,定义EditController和HelpController的bean。 然后,在portletModeHandlerMappingbean中修改portletModeMap属性的值,以将“编辑”模式映射到editController bean,将“帮助”模式映射到helpController bean。 此更改告诉Spring Portlet MVC框架在获得对Edit模式的请求时将控制权传递给editController bean,而在获得Help模式的请求时将控制权传递给helpController bean。

不要忘记更改portlet.xml文件,以在支持下添加HELP和VIEW <portlet-mode>元素。 WebSphere Portal不会为HelloSpringPortletMVC portlet生成“帮助”和“编辑”按钮,并且您以后不能更改请求。

结论

在本文中,我们介绍了如何使用Apache Maven 2.0作为构建工具来设置开发环境。 我们讨论了如何使用Spring Portlet MVC Framework开发简单的HelloWorld Portlet。 我们还讨论了如何处理渲染请求。 到目前为止,您应该已经对Spring Portlet MVC Framework有所了解。 您可能已经注意到Spring Portlet MVC Framework的一个缺点:即使您要创建一个简单的HelloWorld Portlet,也需要创建四个不同的XML配置文件。 但是,在开发复杂的应用程序时,将配置分为多个文件的功能可能会很有帮助。

在本系列的第2部分中,我们将介绍如何处理简单的表单提交并根据用户请求生成动态内容。


翻译自: https://www.ibm.com/developerworks/websphere/library/techarticles/0802_patil-pt1/0802_patil-pt1.html

portlet

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值