SpringMVC

MVC框架

MVC它是一种设计模式,它把应用程序分成三个核心模块:模型、视图、控制器,它们各自处理自己的任务。

 Model(模型):用于存储数据和完成业务逻辑处理

View(视图):用于显示数据和向控制器提交数据请求

Controller(控制器):根据视图请求调用Model完成业务处理,将处理后的结果交由View进行展示

MVC(Model-View-Controller)应用程序结构被用来分析分布式应用程序的特征。这种抽象结构能有助于将应用程序分割成若干逻辑部件,使程序设计变得更加容易。 

MVC结构提供了一种按功能对各种对象进行分割的方法(这些对象是用来维护和表现数据的),其目的是为了将各对象间的耦合程度减至最小。MVC结构本来是为了将传统的输入(input)、处理(processing)、输出(output)任务运用到图形化用户交互模型中而设计的。但是,将这些概念运用于基于Web的企业级多层应用领域也是很适合的。 

基于Servlet的MVC模式实现

Model(模型):一个或多个JavaBean对象(数据访问对象和业务逻辑对象)

View(视图):一个或多个JSP页面,用于展示数据和提交表单请求

Controller(控制器):一个或多个Servlet对象,接收视图请求并交由Model处理,将处理结果输出给View显示

MVC的优点:

(1)多个视图能共享一个模型。同一个模型可以被不同的视图重用,大大提高了代码的可重用性。

(2)由于MVC的三个模块相互独立,改变其中一个不会影响其他两个,所以依据这种设计思想能构造良好的松耦合的构件。

(3)控制器提高了应用程序的灵活性和可配置性。控制器可以用来联接不同的模型和视图去完成用户的需求,这样控制器可以为构造应用程序提供强有力的手段。

MVC的适用范围:

    将MVC运用到你的应用程序,会带来额外的工作量,增加应用的复杂性,但对于开发存在大量用户界面,并且业务逻

    辑复杂的大型应用程序,MVC将会使你的软件在健壮性、代码重用和结构方面上一个新的台阶。尽管在最初构建MVC

    框架时会花费一定的工作量,但从长远角度看,它会大大提高后期软件开发的效率。

案例:登录功能

认识SpringMVC框架

SpringMVC是Spring框架提供的构建Web应用程序的全功能MVC模块

搭建SpringMVC项目

①创建Web工程

②引入Jar包/完成相关pom.xml的配置

③配置文件(web.xml和spring-mvc.xml)

在web.xml中配置“DispatcherServlet”

SpringMVC是基于Servlet的框架,DispatcherServlet是整个SpringMVC框架的核心

DispatcherServlet主要职责是接收所有用户请求,并将其分派给相应的Controller来处理

<!-- 配置DispatcherServlet -->
<servlet>
		<servlet-name>springmvc</servlet-name>
		<servlet-class>
org.springframework.web.servlet.DispatcherServlet
          </servlet-class>
		<load-on-startup>1</load-on-startup>
	</servlet>
<!-- 配置DispatcherServlet接受所有URL请求 -->
	<servlet-mapping>
		<servlet-name>springmvc</servlet-name>
		<url-pattern>/</url-pattern>
	</servlet-mapping>

注:url-pattern是”/”,千万不能写成”/*”

说明:

1、load-on-startup:

(1)当值为0或者大于0时,表示tomcat在应用启动时就加载这个servlet

(2)当是一个负数时或者没有指定时,则表示第一次URL请求的时候加载该servlet

2、servlet-mapping用于配置servlet接受哪些URL请求,<url-pattern>/</url-pattern>表示接受所有请求,注意,千万不要写成<url-pattern>/*</url-pattern>

④在 web.xml添加 SpringMVC的一个过滤器,用于将请求和响应进行编码,以免中文乱码

<!-- 编码过滤器,解决中文乱码问题 -->
	<filter>
		<filter-name>encodingFilter</filter-name>
		<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
		<init-param>
			<param-name>encoding</param-name>
			<param-value>UTF-8</param-value>
		</init-param>
		<init-param>
			<param-name>forceEncoding</param-name>
			<param-value>true</param-value>
		</init-param>
	</filter>
	<filter-mapping>
		<filter-name>encodingFilter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>

⑤创建Spring MVC的配置文件“spring-mvc.xml”

在“resources”下创建配置文件“spring-mvc.xml”

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context 
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc 
http://www.springframework.org/schema/mvc/spring-mvc.xsd">

<!-- 这里添加其他配置 -->

</beans>

⑥修改“web.xml”,配置在tomcat启动的时候自动加载“spring-mvc.xml”配置

<!-- 修改“web.xml”,配置在tomcat启动的时候自动加载“spring-mvc.xml”配置 -->
<!-- 配置DispatcherServlet -->
<servlet>
	<servlet-name>springmvc</servlet-name>
	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
       <!-- 加载类路径下的spring-mvc.xml -->
	<init-param>
		<param-name>contextConfigLocation</param-name>
		<param-value>classpath:spring-mvc.xml</param-value>
	</init-param>
		<load-on-startup>1</load-on-startup>
</servlet>

⑦设计Controller、View和Model
说明:

1、@Controller注解标识该类为控制器

2、@RequestMapping("/login")注解表示用户请求访问login方法的映射路径url

3、控制器中的方法我们称为Action

<!-- 修改“spring-mvc.xml”配置文件,通过扫描将controller配置到Spring容器中 -->
<!-- 将控制器扫描到容器中 -->
<context:component-scan base-package="controller"/>     
<!-- 开启SpringMVC框架的注解驱动 -->
<mvc:annotation-driven/>
<!--在“spring-mvc.xml”配置文件中加入视图解析器配置-->
<bean	class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<property name="prefix" value="/WEB-INF/views/"></property>
		<property name="suffix" value=".jsp"></property>
</bean>

SpringMVC框架工作原理及应用

SpringMVC框架介绍

 1) Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面。

Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。使用 Spring 可插入的 MVC 架构,可以选择是使用内置的 Spring Web 框架还是 Struts 这样的 Web 框架。通过策略接口,Spring 框架是高度可配置的,而且包含多种视图技术,例如 JavaServer Pages(JSP)技术、Velocity、Tiles、iText 和 POI。Spring MVC 框架并不知道使用的视图,所以不会强迫您只使用 JSP 技术。

 Spring MVC 分离了控制器、模型对象、分派器以及处理程序对象的角色,这种分离让它们更容易进行定制。

    2) Spring的MVC框架主要由DispatcherServlet、处理器映射、处理器(控制器)、视图解析器、视图组成。

SpringMVC工作原理

①客户端发送请求至前端控制器DispatcherServlet。

Spring提供的前端控制器,所有的请求都有经过它来统一分发。在DispatcherServlet将请求分发给Spring Controller之前,需要借助于Spring提供的HandlerMapping定位到具体的Controller。

②DispatcherServlet收到请求调用HandlerMapping处理器映射器根据请求的URL找到对应的处理器(Controller)。

③Controller调用业务逻辑后,将ModelAndView对象(封装视图和模型信息)返回给DispatcherServlet  。

需要为并发用户处理上述请求,因此实现Controller接口时,必须保证线程安全并且可重用。

Controller将处理用户请求,这和Struts Action扮演的角色是一致的。一旦Controller处理完用户请求,则返回ModelAndView对象给DispatcherServlet前端控制器,ModelAndView中包含了模型(Model)和视图(View)。

从宏观角度考虑,DispatcherServlet是整个Web应用的控制器;从微观考虑,Controller是单个Http请求处理过程中的控制器,而ModelAndView是Http请求过程中返回的模型(Model)和视图(View)。

④DispatcherServlet将ModelAndView传给ViewReslover视图解析器

⑤ViewReslover视图解析器解析后返回具体的View给DispatcherServlet。

Spring提供的视图解析器(ViewResolver)在Web应用中查找View对象,从而将相应结果渲染给客户。

⑥DispatcherServlet根据View和Model渲染视图响应给客户端。

SpringMVC运行原理

1. 客户端请求提交到DispatcherServlet

2. 由DispatcherServlet控制器查询一个或多个HandlerMapping,找到处理请求的Controller

3. DispatcherServlet将请求提交到Controller

4. Controller调用业务逻辑处理后,返回ModelAndView

5. DispatcherServlet查询一个或多个ViewResoler视图解析器,找到ModelAndView指定的视图

6. 视图负责将结果显示到客户端

案例:登录功能

SpringMVC框架特点

①Spring框架的一部分,无缝集成Spring框架

②分离了控制器、模型对象、分派器以及处理程序对象的角色,更容易定制

③支持注解驱动、数据验证等功能

④高度可配置,支持多种视图技术,而不仅仅局限于JSP

⑤采用松耦合可插入组件结构,比其他MVC框架更具备扩展灵活性

SpringMVC Controller

SpringMVC 框架中,DispatcherServlet负责分发请求到控制器Controller 处理。Controller把用户的请求数据经过业务层处理后封装成一个ModelAndView对象,然后再把该对象返回给对应的View进行展示。在SpringMVC 中定义一个Controller是非常简单的,不需要继承特定的类,也不需要实现相关接口,只需使用@Controller 注解在一个类上进行标记即可。然后使用@RequestMapping等一些注解用定义URL 请求和映射,这样Controller 就能被访问了。

@Controller注解

在Spring MVC 中使用 org.springframework.stereotype.Controller注解类型声明某类的实例是控制器

定义控制器步骤如下:

①为控制器类加注解@Controller

扫描控制器所在的<context:component-scan/>

@RequestMapping注解

①在Spring MVC 中使用 @RequestMapping 将请求与处理方法一一对应

②@RequestMapping负责将不同请求映射到对应的控制器方法中

③@RequestMapping不仅仅可以定义在方法处,还可以定义在类上

④在类级别注解的情况下,请求的url为    类注解url+方法注解的url

⑤为了方便维护程序,建议采用类级别注解,将相关处理放在同一个控制器中

⑥在@RequestMapping指定的URL中可以含有变量参数

使用@PathVariable指定形参接收url中的参数值

⑦@RequestMapping中也可以设置多个映射URL地址

⑧@RequestMapping 可以通过 method 属性来限制请求的类型,如GET、POST等。

method 使用枚举类RequestMethod来定义请求类型

如果URL请求方式和Action中method属性值不相符,则拒绝该请求

⑨也可以使用组合注解来限制方法接收请求的类型。

前后端数据交互

前端是指前端视图,比如:jsp页面,后端指我们的控制器Controller

前后端数据交互包括:

视图向控制器传参

控制器向视图传参

视图向控制器传参

①使用HttpServletRequest接收请求参数

使用HttpServletRequest 作为Action的参数来接收用户请求,从用户请求中获取参数值,如下:

②使用简单数据类型接收请求参数

这种方式要保证方法形参名要和用户请求参数名保持一致

扩展:如果不一致,则使用@RequestParam指定

这种方式@RequestParam里的值要和请求参数名保持一致

③使用实体类对象接收请求参数

(1)在项目下新建“entity”包,包下新建一个实体类“LoginParam”用于接收参数

(2)在Action中使用“LoginParam”对象作为参数来接收表单数据

控制器向视图传参

控制器获取到数据后,需要将数据渲染到视图中

①通过HttpServletRequest传递数据

注:通过request.setAttribute(key,value)将传递到视图的数据放入请求域中

视图端通过EL表达式${键名}来获取数据 在login_succ.jsp页面中通过${param.username}或者${requestScope.param.username}取值

②使用Model或者Map或者ModelMap传递数据

可以使用SpringMVC提供的Model对象来完成控制器和视图之间数据的传递

Model是一个Map的数据结构,也可以使用Map/ModelMap作为入参

注:通过model.addAttribute(key,value)将传递到前端的数据放入请求中

视图端通过EL表达式${键名}来获取数据

在login_succ.jsp页面中通过${param.username}或者${requestScope.param.username}取值

③使用ModelAndView传递数据

视图端通过EL表达式${键名}来获取数据

在login_succ.jsp页面中通过${param.username}或者${requestScope.param.username}取值

SpringMVC数据交互

绑定数组

在实际开发中,可能会遇到前端请求需要传递到后台一个或多个相同名称参数的情况(如批量删除),此时,可以使用绑定数组的方式完成实际需求。

案例:实现批量删除用户,从user表中删除勾选的用户

提示:

为每个复选框设置为相同的name,并设置当单击“批量删除”按钮时,表单提交到“/user/delete”的post请求中

在控制器类“UserController”中,编写接收批量删除用户的方法,在方法中使用数组类型“Integer[]”来绑定请求参数值。

对于请求中多个name相同且类型相同的参数,我们在后台可以使用数组来绑定多个参数。

绑定集合

对于批量新增或者批量更新,前端请求中传递的数据可能会批量包含各种类型的数据,这种情况下无法使用数组绑定,而是要使用绑定集合

案例:实现批量新增用户

①新建一个“User”实体类,用于封装每个用户数据

②新建一个“UserList”包装类,封装用户集合属性

在使用集合绑定时,后台方法中不支持直接使用集合形参进行数据绑定,所以需要定义一个包装类作为形参,然后在包装类中包含一个集合属性。

③在控制器类“UserController”中,编写接收批量新增用户的方法,在方法中使用类型“UserList”来绑定请求参数值。

④批量新增用户表单修改如下:

@ModelAttribute注解

该注解主要的作用是将数据添加到模型对象(Model)中,用于视图页面显示。

有两种使用方式:

①标注在方法的形参上

可以自动将请求参数绑定到数据模型中

②标注在方法上

在调用该控制器所有Action之前,会先逐个调用在方法级上标注了@ModelAttribute 的方法,同时会将该方法的返回值自动绑定到数据模型中

@SessionAttributes注解

如果需要跨请求、跨页面共享数据,就需要将数据存储到session域中,即存储到HttpSession对象中

两种方式:

①使用“HttpSession”对象

②使用“@SessionAttributes”注解

该可以使得数据模型(Model)中的数据存储一份到session域

该注解只能在类上使用,不能在方法上使用

Ajax+JSON数据交互

AJAX 是一种异步数据传输技术,可以在无需重新加载整个网页的情况下,能够更新部分网页内容

AJAX是使用JavaScript技术来实现的

JQuery简化了AJAX技术的使用

JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式

JSON有对象结构和数组结构两种数据结构

SpringMVC默认提供了MappingJackson2HttpMessageConverter实现类默认处理JSON格式请求响应

该实现类利用Jackson开源包读写JSON数据,可以将Java对象转化为JSON,也可以将JSON转化为Java对象

使用Ajax实现用户登陆

①项目中引入Jackson的Jar包或者完成pom.xml的相关配置

②使用JQuery框架发送AJAX请求

在WebContent下新建“statics/scripts”文件夹,加入JQuery框架脚本“jquery-3.6.0.min.js

③修改“login.jsp”页面,发送Ajax请求登录

(1)页面中引入JQuery框架脚本,并为“提交”按钮增加“onclick”事件,在事件中发送Ajax请求

(2)编写函数ajaxLogin,发送AJAX请求

④ 在“LoginController”中,新增Action用于处理Ajax异步请求

⑤需要修改“spring-mvc.xml”配置文件,解决找不到“jquery-3.4.1.min.js”(静态资源访问)的问题

<mvc:resources>用于配置静态资源的访问路径

mapping:指定映射的URL路径匹配

location:指定静态资源所在的文件夹路径

三种解决“静态资源访问”问题的方案

https://blog.csdn.net/ssy052085156/article/details/101036963

SpringMVC文件上传下载

文件上传

Spring MVC的文件上传是基于commons-fileupload组件的文件上传,只不过在原有组件的基础上进行了封装,简化了代码实现

Commons是Apache开放源代码组织中的一个Java子项目,该项目包括文件上传、命令行处理、数据库连接池等模块,fileupload就是其中用来处理文件上传的子项目

commons-fileupload组件性能优良,并支持任意大小文件的上传,该组件依赖于Apache的另一个项目commons-io

说明:

①Spring MVC 提供了一个名为 MultipartResolver 的文件解析器,来实现文件上传功能。MultipartResolver 本身是一个接口,我们需要通过它的实现类来完成对它的实例化工作。

MultipartResolver 接口共有两个实现类,如下表:

以上这两个 MultipartResolver 的实现类,无论使用哪一个都可以实现 Spring MVC 的文件上传功能。

②在SpringMVC框架中上传文件时,使用MultipartFile类型参数即可对被上传文件进行操作,该对象有如下方法:

③MultipartFile参数的名字“myfile”必须和表单上传文件控件的name保持一致,如果不一致,需要使用@RequestParam关联

文件下载

实现文件下载有两种方法:

①通过超链接实现下载

②利用程序编码实现下载

(1)如果要激活下载对话框,必须设置响应头:      

将"Content-Disposition"的值设置为"attachment"

(2)如果要将文件传输到客户端,需要将文件转化为字节流(byte[])放在响应

因此,下载文件其实是要修改http响应

在SpringMVC框架中,可以使用ResponseEntity表示HTTP响应:状态码,响应头和响应内容。因此,我们可以使用它来配置HTTP响应实现文件的下载

LogBack日志框架

日志的作用是用来追踪和记录我们的程序运行中的信息

日志主要分为:trace<debug<info<warn<error五种,级别为由低到高

  • trace:主要为程序的追踪,判断程序执行到哪步
  • debug:调试使用的日志输出
  • info:输出一些信息帮助了解程序运行状态
  • warn:程序发出一些警告信息
  • error:程序出错时发出的信息

Logback 的架构非常通用,可以应用于不同的环境。目前logback分为三个模块,logback-core、logback-classic和logback-access

logback-core 模块为其他两个模块奠定了基础。logback-classic模块原生实现了SLF4J API,因此您可以轻松地在 logback 和其他日志记录框架(例如 log4j 1.x 或 java.util.logging (JUL))之间来回切换。

logback-access 模块与 Tomcat 和 Jetty 等 Servlet 容器集成,以提供 HTTP 访问日志功能。

Logback日志框架使用

logback 基于三个主要组件: Logger、Appender 和 Layout。这三种类的组件协同工作,使开发人员能够通过日志类型和级别来记录日志,并在运行时控制这些日志的格式和报告位置。

①Logger:日志记录器,定义日志内容和日志级别

Logger有三个属性:

name属性:记录器的名称  

level属性(可选):记录器的级别,允许的级别从低到高,TRACE < DEBUG < INFO <  WARN < ERROR

additivity属性(可选):是否允许叠加打印日志, true或false

在Logback中,日志记录器(Logger)是有层级结构的

注意:子记录器会继承父记录器中的属性

Java代码获取日志记录器(Logger)

注意:日志记录器会记录指定Level及以上级别的日志信息

②Appender :附加器,定义日志输出位置,比如控制台,外部文件,数据库等

不同的附加器会将日志输出到不同的地方,比如控制台附加器、文件附加器、网络附加器等等。

常用的附加器

控制台附加器: ch.qos.logback.core.ConsoleAppender

文件附加器:ch.qos.logback.core.FileAppender

滚动文件附加器:ch.qos.logback.core.rolling.RollingFileAppender

滚动策略有两种:

1. 基于时间的滚动策略    

ch.qos.logback.core.rolling.TimeBasedRollingPolicy

2. 基于大小和时间的滚动策略

ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy

③Layout:定义日志输出内容的格式

SpringMVC拦截器

拦截器的基本概述

SpringMVC中的拦截器有点类似于JavaWeb开发中的过滤器Filter,都可以对资源进行前置或者后置处理。可以说是Spring AOP思想的具体呈现。通过定义拦截器我们可以对SpringMVC中的某一个请求路径上的资源进行前置或者后置增强。在实际开发中拦截器的使用场景非常广泛,文哥给大家罗列几个应用场景:

1.权限校验 比如用户在访问某一个应用里面的资源时,可以使用拦截器对用户请求进行拦截,判断当前用户有没有登录,如果没有登录则强制回到登录页面进行登录。
2.性能监控 如果我们想统计用户对某个控制器方法的执行时间。我们可以使用拦截器对当前控制器器方式进行前置拦截和后置拦截,在前置拦截器里面记录用户访问资源的起始时间,在后置拦截器里面记录用户访问资源的结束时间,两个时间的结束之差就是当前用户访问控制器方法是总时长。
3.日志记录 记录请求资源的日志信息,比如请求方式 请求携带的参数 请求返回的数据结果等等,都可以通过拦截器来实现。

拦截器的作用

(1)在指定的方法调用前执行预先设定的代码(类似于AOP的事前通知@Before)
(2)阻止原控制方法的执行

Spring MVC的拦截器(Interceptor)与Java Servlet的过滤器(Filter)类似,主要用于拦截用户的请求并做相应的处理。

拦截器和过滤器的区别

过滤器是Web三大组件之一,过滤器是Servelt技术规范中的一部分,在任何架构的WEB工程里面都可以使用。而拦截器是SpringMVC特有的功能,拦截器的使用只能在SpringMVC的环境里面才可以使用。

其次过滤器和拦截器的实现方式也不一样。如果我们要使用过滤器,必须定义一个类,实现一个Filter接口才可以。过滤器的基本源码如下所示:

public interface Filter {
    void init(FilterConfig var1) throws ServletException;

    void doFilter(ServletRequest var1, ServletResponse var2, FilterChain var3) throws IOException, ServletException;

    void destroy();
}

在Filter接口里面有一个核心的方法叫做doFilter方法,实现这个方法就是在定义具体的过滤业务逻辑。而拦截器的定义必须实现SpringMVC中的HandlerInterceptor接口,这个接口里面有以下几个方法:

public interface HandlerInterceptor {
    default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        return true;
    }
    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
    }
    default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
    }
}

拦截器和过滤器还有一个区别就是过滤器除了对动态资源进行过滤之外,还可以对静态资源,比如HTML、CSS、JS、图片等资源进行过滤,而拦截器只能对控制器方法进行拦截处理。

定义拦截器可以通过两种方式:

通过实现HandlerInterceptor接口

HandlerInterceptor接口里面有三个方法

preHandle方法:该方法在控制器的处理请求方法前执行,其返回值表示是否中断后续操作,返回true表示继续向下执行,返回false表示中断后续操作,可以进行编码、安全控制、权限校验等处理。

postHandle方法:该方法在控制器的处理请求方法调用之后,解析视图之前执行,可以通过此方法对模型和视图做进一步的修改。

afterCompletion方法:该方法在控制器的处理请求方法执行完成后执行,即视图渲染结束后执行,可以通过此方法实现一些资源清理、记录日志等工作。

通过实现WebRequestInterceptor接口

单个拦截器执行流程

多个拦截器执行流程

在Web应用中通常需要有多个拦截器同时工作,这是它们的preHandle方法将按照配置文件中的拦截器顺序执行,而它们的postHandle方法和afterCompletion方法则按照配置反序执行

统一异常处理

目的:客户在使用程序期间,如果出现错误了,会有一个友好的页面显示,不会再出现大堆的异常错误信息。

统一异常处理两种实现方式:

①实现HandlerExceptionResolver接口

(1)创建异常处理类

(2)将异常处理类配置到容器中

②@ExceptionHandler注解

@ControllerAdvice 配合 @ExceptionHandler 实现全局异常处理

(1)创建异常处理控制器

说明:@ControllerAdvice注解可以定义全局数据处理组件, 一般搭配@ExceptionHandler、@ModelAttribute以及@InitBinder使用

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值