SpringMVC

 SpringMVC的组件

一、前端控制器

 DispatcherServlet:前端控制器

用户请求到达前端控制器,它就相当于 mvc 模式中的 c,dispatcherServlet 是整个流程控制的中心,由它调用其它组件处理用户的请求,dispatcherServlet 的存在降低了组件之间的耦合性。

  <!-- 前端控制器 -->
  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- 配置初始化参数,用于读取 SpringMVC 的配置文件 --> 
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <!-- 配置 servlet 的对象的创建时间点:加载时创建DispatcherServlet。    
     取值只能是非 0 正整数,表示启动顺序 --> 
    <load-on-startup>1</load-on-startup>

  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

二、处理器映射器

HandlerMapping:处理器映射器

HandlerMapping 负责根据用户请求找到 Handler 即处理器,SpringMVC 提供了不同的映射器实现不同的映射方式,例如:配置文件方式,实现接口方式,注解方式等。

三、处理器

Handler:处理器

它就是我们开发中要编写的具体业务控制器。由 DispatcherServlet 把用户请求转发到 Handler。由 Handler 对具体的用户请求进行处理。

HandlAdapter:处理器适配器
通过 HandlerAdapter 对处理器进行执行,这是适配器模式的应用,通过扩展适配器可以对更多类型的处理器进行执行。
        
四、视图解析器

View Resolver:视图解析器

View Resolver 负责将处理结果生成 View 视图,View Resolver 首先根据逻辑视图名解析成物理视图名即具体的页面地址,再生成 View 视图对象,最后对 View 进行渲染将处理结果通过页面展示给用户。 

    <!-- 视图解析器对象 -->
    <bean id="internalResourceViewResolver" 
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/pages/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

五、 <mvc:annotation-driven> 开启对注解的支持

    在 SpringMVC 的各个组件中,处理器映射器、处理器适配器、视图解析器称为 SpringMVC 的三大组件。  

    在 SpringMVC.xml 配 置 文 件 中 使 用 <mvc:annotation-driven>替代RequestMappingHandlerMapping处理器和RequestMappingHandlerAdapter适配器的配置。

六、自定义类型转换器

<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="cn.itcase.utils.StringToDateConverter"/>
            </set>
        </property>
    </bean>

七、解决中文乱码

  <!-- 配置解决中文乱码的过滤器 -->
  <filter>
    <filter-name>characterEncodingFilter</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>
  </filter>
  <filter-mapping>
    <filter-name>characterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

SpringMVC的入门案例

一、SpringMVC的入门程序

1. 创建WEB工程,引入依赖的jar包

    <properties>
        <spring.version>5.0.2.RELEASE</spring.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>

        <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>jsp-api</artifactId>
            <version>2.0</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

2. 配置核心的控制器(配置DispatcherServlet)

在web.xml配置文件中核心控制器DispatcherServlet

  <!-- 前端控制器 -->
  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:springmvc.xml</param-value>
    </init-param>
    <!-- 启动服务器时DispatcherServlet对象被创建 -->
    <load-on-startup>1</load-on-startup>

  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

 3. 编写springmvc.xml的配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/mvc
       http://www.springframework.org/schema/mvc/spring-mvc.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">
    <!-- 开启注解扫描 -->
    <context:component-scan base-package="cn.itcase"/>

    <!-- 视图解析器对象 -->
    <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/pages/"/>
        <property name="suffix" value=".jsp"/>
    </bean>

    <!-- 配置自定义类型转换器 -->
    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="cn.itcase.utils.StringToDateConverter"/>
            </set>
        </property>
    </bean>

    <!-- 开启SpringMVC框架注解的支持 -->
    <mvc:annotation-driven conversion-service="conversionService"/>
</beans>

4. 编写index.jsp和HelloController控制器类

5. 在WEB-INF目录下创建pages文件夹,编写success.jsp的成功页面

二、 入门案例的执行过程分析

入门案例的执行流程

     1. 当启动Tomcat服务器的时候,因为配置了load-on-startup标签,所以服务器启动会创建DispatcherServlet对象, 就会加载springmvc.xml配置文件

     2. 开启了注解扫描,那么HelloController对象就会被创建

     3. 从index.jsp发送请求,请求会先到达DispatcherServlet核心控制器,根据配置@RequestMapping注解 找到执行的具体方法

     4. 根据执行方法的返回值,再根据配置的视图解析器,去指定的目录下查找指定名称的JSP文件

     5. Tomcat服务器渲染页面,做出响应

三、 RequestMapping 注解

用于建立请求 URL 和处理请求方法之间的对应关系。

出现位置:

  • 类上:请求 URL 的第一级访问目录。此处不写的话,就相当于应用的根目录。写的话需要以/开头。它出现的目的是为了使我们的 URL 可以按照模块化管理:  

     例如:账户模块:有很多增删改方法  /account/add     /account/update    /account/delete  

  • 方法上:请求 URL 的第二级访问目录。

     属性:

  • path   指定请求路径的url
  • value   value属性和path属性是一样的
  • mthod   指定该方法的请求方式
  • params   指定限制请求参数的条件
  • headers   发送的请求中必须包含的请求头

常用的注解

一、 RequestParam注解

1. 作用:把请求中的指定名称的参数传递给控制器中的形参赋值

2. 属性:

      value:请求参数中的名称

      required:请求参数中是否必须提供此参数,默认值是true,必须提供

二、RequestBody注解

1. 作用:把请求中的指定名称的参数传递给控制器中的形参赋值

2. 属性:

      value:请求参数中的名称

      required:请求参数中是否必须提供此参数,默认值是true,必须提供

    @RequestMapping("/testRequestBody")
    public String testRequestBody(@RequestBody String body){
        System.out.println("执行了...");
        System.out.println(body);
        return "success";
    }

    <form action="anno/testRequestBody" method="post">
        用户姓名:<input type="text" name="username"/><br/>
        用户年龄:<input type="text" name="age"/><br/>
        <input type="submit" name="提交"/>
    </form>

三、RequestHeader注解

1. 作用:获取指定请求头的值

2. 属性:value:请求头的名称

    @RequestMapping(value = "/testRequestHeader")
    public String testRequestHeader(@RequestHeader(value="Accept") String header){
        System.out.println("执行了...");
        System.out.println(header);
        return "success";
    }

 四、CookieValue注解

1. 作用:用于获取指定cookie的名称的值

2. 属性:value:cookie的名称

    @RequestMapping(value = "/testCookieValue")
    public String testCookieValue(@CookieValue(value="JSESSIONID") String cookieValue){
        System.out.println("执行了...");
        System.out.println(cookieValue);
        return "success";
    }

五、 ModelAttribute注解

1. 作用:

    出现在方法上:表示当前方法会在控制器方法执行前线执行。

    出现在参数上:获取指定的数据给参数赋值。

2. 应用场景:当提交表单数据不是完整的实体数据时,保证没有提交的字段使用数据库原来的数据。

 六、SessionAttributes注解

1. 作用:用于多次执行控制器方法间的参数共享

2. 属性:value:指定存入属性的名称

    @RequestMapping(value = "/testSessionAttributes")
    public String testSessionAttributes(Model model){
        System.out.println("testSessionAttributes执行了...");
        //底层会存储到request域对象中
        model.addAttribute("msg","张三");
        return "success";
    }

    /**
     * 得到session域中的值
     * @return
     */
    @RequestMapping(value = "/getSessionAttributes")
    public String getSessionAttributes(ModelMap modelMap){
        String msg = (String) modelMap.get("msg");
        System.out.println(msg);
        return "success";
    }

响应数据和结果视图

一、返回值分类

1.  返回字符串

    Controller方法返回字符串可以指定逻辑视图的名称,根据视图解析器为物理视图的地址。

    /**
     * 返回值类型为字符串
     * @param model
     * @return
     */
    @RequestMapping("/testString")
    public String testString(Model model){
        System.out.println("testString方法执行了");
        //模拟从数据库中查询User对象
        User user = new User();
        user.setUsername("美美");
        user.setPassword("123");
        user.setAge(30);
        //model对象
        model.addAttribute("user",user);
        return "success";
    }

2. 返回值是void

   如果控制器的方法返回值编写成void,执行程序报404的异常,默认查找JSP页面没有找到。

   默认会跳转到@RequestMapping(value="/initUpdate") initUpdate的页面。

   可以使用请求转发或者重定向跳转到指定的页面

**
     * 返回值类型为void
     * 请求转发一次转发,不用编写项目名称
     * 重定向两次请求,写项目名
     */
    @RequestMapping("/testVoid")
    public void testVoid(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        System.out.println("testVoid方法执行了");
        //编写请求转发的程序
//        request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request,response);
                 
        //重定向
//        response.sendRedirect(request.getContextPath()+"/index.jsp");
        //解决中文乱码
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");
        //直接进行响应
        response.getWriter().write("你好");
        return;
    }

3. 返回值是ModelAndView对象

   ModelAndView对象是Spring提供的一个对象,可以用来调整具体的JSP视图

    @RequestMapping("/testModelAndView")
    public ModelAndView testModelAndView(){
        //创建ModelAndView对象
        ModelAndView mv = new ModelAndView();
        System.out.println("testModelAndView方法执行了");
        //模拟从数据库中查询User对象
        User user = new User();
        user.setUsername("科比");
        user.setPassword("2020");
        user.setAge(39);
        //把user对象存储到mv对象中,也会把user对象存入到request对象
        mv.addObject("user",user);
        //跳转到哪个页面
        mv.setViewName("success");
        return mv;
    }

4.  SpringMVC框架提供的转发和重定

    /**
     * 使用关键字的方式进行转发或者重定向
     * @return
     */
    @RequestMapping("/testForwardOrRedirect")
    public String testForwardOrRedirect(){
        System.out.println("testForwardOrRedirect方法执行了");

        //请求的转发
        //return "forward:/WEB-INF/pages/success.jsp";

        //重定向
        return "redirect:/index.jsp";
    }

 二、 ResponseBody响应json数据

1. DispatcherServlet会拦截到所有的资源,导致一个问题就是静态资源(img、css、js)也会被拦截到,从而不能被使用。需要配

置静态资源不进行拦截,在springmvc.xml配置文件添加如下配置

    mvc:resources标签配置不过滤

            location元素表示webapp目录下的包下的所有文件

            mapping元素表示以/static开头的所有请求路径,如/static/a 或者/static/a/b

    <!-- 告诉前端控制器,哪些静态资源不拦截 -->
    <mvc:resources location="/css/" mapping="/css/**"/>
    <mvc:resources location="/images/" mapping="/images/**"/>
    <mvc:resources location="/js/" mapping="/js/**"/>

2.  使用@RequestBody注解把json字符串和JavaBean对象的转换

    /**
     * 模拟异步请求响应
     * @return
     */
    @RequestMapping("/testAjax")
    public @ResponseBody User testAjax(@RequestBody User user){
        System.out.println("testAjax方法执行了");
        //客户端发送ajax的请求,传的是json字符串,后端把json字符串封装到user对象中
        System.out.println(user);
        //做响应,模拟查询数据库
        user.setUsername("haha");
        user.setAge(40);
        //做响应
        return user;
    }
<script src="js/jquery.min.js"></script>
    <script>
        // 页面加载,绑定单击事件
        $(function(){
            $("#btn").click(function(){
                // alert("hello btn");
                // 发送ajax请求
                $.ajax({
                    // 编写json格式,设置属性和值
                    url:"user/testAjax",
                    contentType:"application/json;charset=UTF-8",
                    data:'{"username":"hehe","password":"123","age":30}',
                    dataType:"json",
                    type:"post",
                    success:function(data){
                        // data服务器端响应的json的数据,进行解析
                        alert(data);
                        alert(data.username);
                        alert(data.password);
                        alert(data.age);
                    }
                });

            });
        });

SpringMVC实现文件上传

一、 web阶段文件上传

1. 导入文件上传的jar包

        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.3.1</version>
        </dependency>
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.4</version>
        </dependency>

2. 编写文件上传的jsp页面

    <h3>传统文件上传</h3>
    <form action="user/fileupload1" method="post" enctype="multipart/form-data">
        选择文件:<input type="file" name="upload"/><br>
        <input type="submit" value="上传"/>
    </form>

3. 编写文件上传的Controller控制器

    /**
     * 传统的文件上传
     * @return
     */
    @RequestMapping("/fileupload1")
    public String fileupload1(HttpServletRequest request) throws Exception {
        System.out.println("文件上传....");
        //使用fileupload组件完成文件上传
        //上传的位置,getSession().getServletContext()得到最大域对象
        String path = request.getSession().getServletContext().getRealPath("/uploads/");
        //判断路径是否存在
        File file = new File(path);
        if(!file.exists()){
            //创建改文件夹
            file.mkdirs();
        }
        //解析request对象,获取上传文件项
        DiskFileItemFactory factory = new DiskFileItemFactory();
        ServletFileUpload upload = new ServletFileUpload(factory);
        //解析request
        List<FileItem> items = upload.parseRequest(request);
        //遍历文件项
        for (FileItem item : items) {
            //进行判断,当前item对象是否是上传文件项
            if(item.isFormField()){
                //说明普通表单项
            }else{
                //说明上传文件项
                //获取上传文件的名称
                String filename = item.getName();
                //把文件的名称设置唯一值 uuid
                String uuid = UUID.randomUUID().toString().replace("-", "");
                filename = uuid+"_"+filename;
                //完成文件上传
                item.write(new File(path,filename));
                //删除临时文件
                item.delete();
            }
        }
        System.out.println(path);
        return "success";
    }

二、SpringMVC传统方式文件上传

 SpringMVC框架提供了MultipartFile对象,该对象表示上传的文件,要求变量名称必须和表单file标签的 name属性名称相同。

1. 编写文件上传的jsp页面

name的upload,与方法变量名称upload保持一致

    <h3>SpringMVC文件上传</h3>
    <form action="user/fileupload2" method="post" enctype="multipart/form-data">
        选择文件:<input type="file" name="upload"/><br>
        <input type="submit" value="上传"/>
    </form>

2.  编写文件上传的Controller控制器

    /**
     * springmvc文件上传
     * @return
     */
    @RequestMapping("/fileupload2")
    public String fileupload2(HttpServletRequest request, MultipartFile upload) throws IOException {
        System.out.println("Springmvc文件上传....");
        //使用fileupload组件完成文件上传
        //上传的位置,getSession().getServletContext()得到最大域对象
        String path = request.getSession().getServletContext().getRealPath("/uploads/");
        //判断路径是否存在
        File file = new File(path);
        if(!file.exists()){
            //创建该文件夹
            file.mkdirs();
        }
        //获取上传文件的名称
        String filename = upload.getOriginalFilename();
        //把文件的名称设置唯一值 uuid
        String uuid = UUID.randomUUID().toString().replace("-", "");
        filename = uuid+"_"+filename;
        //完成文件上传
        upload.transferTo(new File(path,filename));

        return "success";
    }

3.  配置文件解析器对象

    <!-- 配置文件解析器对象 -->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="maxUploadSize" value="1048576"/>
    </bean>

三、SpringMVC跨服务器方式文件上传

1.  搭建图片服务器,更改端口号,在webapp目录下创建uploads文件夹,用于保存上传图片

2. 实现SpringMVC跨服务器方式文件上传

  1)导入开发需要的jar包

        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-core</artifactId>
            <version>1.18.1</version>
        </dependency>
        <dependency>
            <groupId>com.sun.jersey</groupId>
            <artifactId>jersey-client</artifactId>
            <version>1.18.1</version>
        </dependency>

   2)编写文件上传的JSP页面

    <h3>跨服务器方式文件上传</h3>
    <form action="user/fileupload3" method="post" enctype="multipart/form-data">
        选择文件:<input type="file" name="upload"/><br>
        <input type="submit" value="上传"/>
    </form>

   3)编写控制器

    /**
     * 跨服务器文件上传
     * @return
     */
    @RequestMapping("/fileupload3")
    public String fileuoload3(MultipartFile upload) throws Exception {
        System.out.println("跨服务器文件上传...");
        // 定义上传文件服务器路径
        String path = "http://localhost:9090/uploads/";
        // 获取上传文件的名称
        String filename = upload.getOriginalFilename();
        // 把文件的名称设置唯一值,uuid
        String uuid = UUID.randomUUID().toString().replace("-", "");
        filename = uuid+"_"+filename;
        // 创建客户端的对象
        Client client = Client.create();
        // 和图片服务器进行连接
        WebResource webResource = client.resource(path + filename);
        // 上传文件
        webResource.put(upload.getBytes());
        return "success";
    }

tomcat服务器不支持put和delete请求,需要在web.xml中进行配置

        <init-param>
		<param-name>readonly</param-name>
		<param-value>false</param-value>
        </init-param>

SpringMVC的异常处理和拦截器

一、异常处理

1.  异常处理逻辑

Controller调用service,service调用dao,异常都是向上抛出的,最终D由ispatcherServlet找异常处理器进行异常的处理。

2. SpringMVC的异常处理

   2.1 自定义异常类

public class SysException extends Exception{
    //异常提示信息
    private String message;
    public String getMessage() {
        return message;
    }
    public void setMessage(String message) {
        this.message = message;
    }
    public SysException(String message) {
        this.message = message;
    }
}

  2.2 自定义异常处理器

在springmvc中配置异常处理器

    <!-- 配置异常处理器 -->
    <bean id="sysExceptionResolver" class="cn.itcast.exception.SysExceptionResolver"/>

自定义异常处理器实现 HandlerExceptionResolver接口  

public class SysExceptionResolver implements HandlerExceptionResolver {

    /**
     * 处理异常业务逻辑
     */
    @Override
    public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        //获取异常对象
        SysException e = null;
        if(ex instanceof SysException){
            e = (SysException) ex;
        }else{
            e = new SysException("系统正在维护...");
        }
        //创建ModelAndView对象
        ModelAndView mv = new ModelAndView();
        mv.addObject("errorMsg",e.getMessage());
        //跳转到error页面
        mv.setViewName("error");
        return mv;
    }
}

二、SpringMVC框架中的拦截器

1. 拦截器概述

  1. SpringMVC框架中的拦截器用于对处理器进行预处理和后处理的技术。
  2. 可以定义拦截器链,连接器链就是将拦截器按着一定的顺序结成一条链,在访问被拦截的方法时,拦截器链中的拦截器会按着定义的顺序执行。
  3. 拦截器和过滤器的功能比较类似,有区别 :
  • 过滤器是Servlet规范的一部分,任何框架都可以使用过滤器技术。
  • 拦截器是SpringMVC框架独有的。
  • 过滤器配置了/*,可以拦截任何资源。
  • 拦截器只会对控制器中的方法进行拦截。

拦截器也是AOP思想的一种实现方式,想要自定义拦截器,需要实现HandlerInterceptor接口。

2.  自定义拦截器

   2.1 创建类,实现HandlerInterceptor接口,重写需要的方法

public class MyInterceptor1 implements HandlerInterceptor {

    /**
     * 预处理,controller方法执行前
     * return true 放行,执行下一个拦截器,如果没有,执行controller中的方法
     * 可以做一些逻辑的判断,例如没有登录转去登陆页面
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("MyInterceptor1执行了...前");
        //出问题跳转页面
        //request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
        return true;
    }

    /**
     * 后处理方法,controller方法执行后,success.jsp执行之前
     * @param request
     * @param response
     * @param handler
     * @param modelAndView
     * @throws Exception
     */
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("MyInterceptor1执行了...后");
    }

    /**
     * success.jsp页面执行后,该方法会执行
     * @param request
     * @param response
     * @param handler
     * @param ex
     * @throws Exception
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("MyInterceptor1执行...最后");
    }
}

2. 在springmvc.xml中配置拦截器类

    <!-- 配置拦截器 -->
    <mvc:interceptors>
        <mvc:interceptor>
            <!-- 要拦截的具体方法 -->
            <mvc:mapping path="/user/*"/>
            <!-- 配置拦截器对象 -->
            <bean class="cn.itcast.interceptor.MyInterceptor1"/>
        </mvc:interceptor>

        <!-- 配置第二个拦截器 -->
        <mvc:interceptor>
            <!-- 要拦截的具体方法 -->
            <mvc:mapping path="/**"/>
            <!-- 配置拦截器对象 -->
            <bean class="cn.itcast.interceptor.MyInterceptor2"/>
        </mvc:interceptor>

3. HandlerInterceptor接口中的方法

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值