最全面的springMVC的快速入门

SpringMVC

1.什么 是SpringMVC
  1. SpringMVC是一个基于MVC模式的WEB/表现层框架,它解决WEB开发中常见的问题:参数接收、文件上传/下载、表单验证、国际化等等; model2:servlet+jsp+javabean

  2. SpringMVC作为Spring框架一个非常重要的功能模块,可以与Spring无缝集成,提高开发效率;

  3. Spring 框架是一个轻量级的Java 开发框架,为了解决企业应用开发的复杂性而创建。SpringMVC以Spring框架为核心,为应用程序中的Web层(表现层)提出的一套优秀的解决方案;

  4. 目前很多公司都使用SpringMVC,90%的招聘单位要求熟悉使用SpringMVC;

    注意:SpringMvc的功能就是之前Servlet的功能,可以理解为使用SpringMVC代替了Servlet;

2.SpringMVC的优缺点

一、优点

  1. Spring MVC拥有强大的灵活性,非侵入性和可配置性

2. Spring MVC提供了一个前端控制器DispatcherServlet,开发者无需额外开发控制器对象

3. Spring MVC分工明确,包括控制器,验证器,命令对象,模型对象,处理程序映射视图解析器,等等,每一个功能实现由一个专门的对象负责完成

4. Spring MVC可以自动绑定用户输入,并正确的转换数据类型。例如:Spring MVC能自动解析字符串,并将去设置为模型的int或float类型的属性

5. Spring MVC使用一个名称/值的Map对象实现更加灵活的模型数据传输

6. Spring MVC内置常见的校验器,可以校验用户输入,如果校验不通过,则重定向回输入表单。输入校验是可选的,并且支持编程方式及声明方式

7. Spring MVC支持国际化,支持根据用户区域显示多国语言,并且国际化的配置非常简单

8. Spring MVC支持多种视图技术(JSP,Velocity,FreeMarker)

  1. Spring提供了一个简单而强大的JSP标签库,支持数据绑定功能,使得编写JSP页面更加容易。

二.SpringMVC的缺点:

(1)Spring与MVC 的Servlet API 耦合,难以脱离容器独立运行

(2)太过于细分,开发效率低

(3)过度追求完美,有过度设计的危险解决的问题领域是:网站应用程序或者服务开发—— URL路由、Session、模板引擎、静态Web资源等等。

3.SpringMVC的执行流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pi7SCyTg-1691375892202)(D:\my-notes-gitee\荣华-培训\1-java_web\images\a5f0a81b44b54ef28ac6d03224ad60cd.png)]

1.用户点击某个请求路径,发起一个HTTP request请求,该请求会被提交到Dispatcher Servlet(前端控制器);
2.由Dispatcher Servlet请求一个或多个Handler Mapping(处理器映射器),并返回一个执行链(Handler Execution Chain);
3.Dispatcher Servlet将Handler Mapping返回的执行链中的Handler信息发送给Handler Adapter(处理器适配器);
4.HandlerAdapter 根据 Handler 信息找到并执行相应的 Handler(常称为 Controller);
5.Handler 执行完毕后会返回给 HandlerAdapter 一个 ModelAndView 对象(Spring MVC的底层对象,包括 Model 数据模型和 View 视图信息);
6.HandlerAdapter 接收到 ModelAndView 对象后,将其返回给 DispatcherServlet ;
7.DispatcherServlet 接收到 ModelAndView 对象后,会请求 ViewResolver(视图解析器)对视图进行解析;
8.ViewResolver 根据 View 信息匹配到相应的视图结果,并返回给 DispatcherServlet;
9.DispatcherServlet 接收到具体的 View 视图后,进行视图渲染,将 Model 中的模型数据填充到 View 视图中的 request 域,生成最终的 View(视图);
10视图负责将结果显示到浏览器(客户端)
————————————————

4.SpringMVC项目
导入jar包
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.4.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.4.RELEASE</version>
</dependency>
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.0.1</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>jsp-api</artifactId>
    <version>2.2</version>
    <scope>provided</scope>
</dependency>
配置核心控制器(核心分发器)

核心控制器其实就是一个Servlet,只不过这个Servlet是由SpringMVC框架提供的

只需要配置,交给容器去管理。在web.xml配置核心控制器(容器启动时就创建sevlet实例对象,并加载classpath下的一个名为spring-mvc.xml文件);

  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcherServlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
SpringMVC配置文件

和Spring的配置差不多,复制一份applicationContext.xml修改为spring-mvc.xml;

再讲spring-mvc。xml作为初始化参数加载进dispatcherServlet

<!--配置核心分发器-->
<!--  拦截所有的请求,-->
  <servlet>
    <servlet-name>dispatcherServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!--  将映射配置文件当做初始化参数配置进dispatcherServlet中-->
  <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>

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

现在有一个hellocontroller

public class HelloController implements Controller {

    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //获取传入的参数
        System.out.println(request.getParameter("id"));
        System.out.println(request.getParameter("name"));

        // 响应页面
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("/WEB-INF/jsps/hello.jsp");

        modelAndView.addObject("pwd","123");

        return modelAndView;
    }
}

spring-mvc.xml中配置

<!--    映射控制文件-->
<!--    name: 请求路径 ,class是其对应的controller-->
    <bean name="/hello" class="com.yn.controller.HelloController"></bean>

通过浏览器就可以请求成功

前端控制器配置 url-pattern

Spring的前端控制器拦截匹配规则(即 <url-pattern>…</url-pattern> )可以自己定义:

1 *.xxx(后缀匹配或扩展名匹配)
  1. *.xxx这个拦截固定结尾的url,常见的如*.do、*.json、.action等等,这是最传统的方式;

  2. 在实际项目开发中一般会要求遵守restful风格,后缀匹配是匹配不了的;

    例如:查看id为1001的用户信息:/users/1001

  3. 不会导致静态文件(.jpg、.js、.css)被拦截,只有以.xxx结尾的请求才会经过DispatcherServlet处理;

2 /*
  1. <url-pattern>/*</url-pattern>会拦截所有url(包括/login、*.jsp、*.js、*png和*.html等),一般只用在过滤器上;
  2. 既然<url-pattern>/*</url-pattern>会拦截*.jsp,会出现访问jsp视图时被Spring的DispatcherServlet拦截,导致找不到请求资源引入的404错误,其他资源也一样,所以在SpringMVC的前端控制器中配置/*,是一个错误的配置方式;

3 /(现在最流行的配置方式)
  1. <url-pattern>/</url-pattern>也是拦截所有,只不过不会拦截*.jsp;

  2. <url-pattern>/</url-pattern>会拦截.js、.css、.png、.html等后缀型url;

  3. 但是/匹配覆盖了Tomcat的默认控制器,导致静态资源访问失效,静态资源也被拦截,只有.jsp不会被拦截;

  4. 如果想访问静态资源,必须要开启静态资源访问权限;

    注意:Tomcat本身自带有一个默认控制器,匹配方式也是"/“,这个默然控制器用于处理静态资源请求。如果我们配置SpringMVC前端控制器为”/",那么Tomcat的默认控制器就不生效,导致静态资源无法访问;

spring-mvc中配置

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

<!-- 静态资源放行:相当于Tomcat默认控制器的功能 -->
<mvc:default-servlet-handler />
controller实现的三种方式

实现Controller接口

public class MyController01 implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {

//        获取参数
        System.out.println(httpServletRequest.getParameter("id"));
//        返回页面
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("/WEB-INF/jsps/hello.jsp");
        modelAndView.addObject("msg","YES");
        return modelAndView;
    }
}

spring-mvc.xml中配置

<bean name="/test01" class="com.yn.controller.MyController01"></bean>

第二种 实现 HttpRequestHandler

public class MyControllr02 implements HttpRequestHandler {
    @Override
    public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //        获取参数
        System.out.println(request.getParameter("id"));
//        设置参数
        request.setAttribute("msg","YeS02");
//        返回页面
        request.getRequestDispatcher("/WEB-INF/jsps/hello.jsp").forward(request,response);

    }
}

配置文件

<bean name="/test02" class="com.yn.controller.MyControllr02"></bean>

第三种方法 注解

<!--    \开启spring对mvc的支持:即能够使用\@RequestMapping注解 \>-->
    <mvc:annotation-driven/>
\<!\-- 扫描包路径:会扫描cn.ronghuanet包及其子包下所有的类,如果类上面有实例化Bean的注解(例如:\@Controller),容器就会创建该类的实例,并交给容器管理。否则不创建 \--\>

    <context:component-scan base-package="com.yn.controller" />
@Controller
@RequestMapping("/test03")
public class MyController03 {

    @RequestMapping("/add")
    public void test1(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //        获取参数
        System.out.println(request.getParameter("id"));
//        设置参数
        request.setAttribute("msg","YeS03_add");
//        返回页面
        request.getRequestDispatcher("/WEB-INF/jsps/hello.jsp").forward(request,response);
    }

    @RequestMapping("/del")
    public void test2(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //        获取参数
        System.out.println(request.getParameter("id"));
//        设置参数
        request.setAttribute("msg","YeS03_del");
//        返回页面
        request.getRequestDispatcher("/WEB-INF/jsps/hello.jsp").forward(request,response);
    }

}
字符集过滤器

spring框架为我们提供了一个编码过滤器。开发者只需要配置一个spring内置请求编码过滤器即可。在web.xml配置一个请求编码过滤器:

<!-- 支持UTF-8编码 -->
<filter>
  	<filter-name>characterEncoding</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>
<!--强制指定字符编码,即使request或response设置了字符编码,也会强制使用当前设置的,任何情况下强制使用此编码,一般不配置,太暴力-->
<init-param>
	<param-name>forceEncoding</param-name>
	<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
  	<filter-name>characterEncoding</filter-name>
  	<url-pattern>/*</url-pattern>
</filter-mapping>
接收参数的方式

方式一:request.getParameter() 获取参数

方法二:直接当做方法的入参,

/*方法二:直接当做方法的入参,
* */
@RequestMapping("/test02")
public void test2(Integer id,String name){
    System.out.println(id);
    System.out.println(name);
}    

方式三: 用对象去接收参数

/*方法三:用对象去接收,
* */
@RequestMapping("/test03")
public void test2(User user){
    System.out.println(user);
}
URL地址提取数据

使用@PathVariable(“id”)

/**
 * 接收url中参数的请求,接收用户请求参数值 
 * http://localhost/params05/delete/100/tom
 * 	注:这种方式为RESTful风格,参数不易过多
 */
@RequestMapping("/params05/delete/{id}/{username}")
public ModelAndView params05(@PathVariable("id")Long id,@PathVariable("username")String name){
	System.out.println(id);
     System.out.println(name);
	return null;
}
视图解析器

用于统一管理响应视图的匹配

使用之后,响应的数据就会交给这个视图解析器进行解析,然后转向响应的页面,控制器中响应视图写法就比较简单了;

 <!-- 设置视图路径的前后缀,该配置可以让我们写视图路径的时候更简单 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
	 <property name="prefix" value="/WEB-INF/jsps/" />
	 <property name="suffix" value=".jsp" />
</bean>
@RequestMapping("/test01")
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        //获取传入的参数
        System.out.println(request.getParameter("id"));
        System.out.println(request.getParameter("name"));

        // 响应页面
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("hello");

        modelAndView.addObject("pwd","123");

        return modelAndView;
    }

页面传值

传递数据就是Controller往前台(页面)传递数据;

  1. 通过传统的HttpServletRequest传递;略

  2. 通过ModelAndView对象传递;

    //通用ModelAndView方式
    @RequestMapping("/test03")
    public ModelAndView data02(){
        ModelAndView mdv = new ModelAndView();

        mdv.addObject("name", "sd!~");
        User u = new User();
        u.setName("asaas");
        mdv.addObject(u);

        mdv.setViewName("/WEB-INF/jsps/hello.jsp");
        return mdv;
    }
  1. 通过Model对象传递;
    @RequestMapping("/test02")
    public String data01(Model model){
        model.addAttribute("name","ftv!");
        User u = new User();
        u.setName("asd");
//添加模型数据,key的值为对象的类型首字母小写
        model.addAttribute(u);
        return "/WEB-INF/jsps/hello.jsp";
    }
跳转方式

请求转发:forward

return "forward:/data.jsp";

请求重定向:redirect

return "redirect:/data.jsp";

sps/hello.jsp");
return mdv;
}




1.  通过Model对象传递;

```jav
    @RequestMapping("/test02")
    public String data01(Model model){
        model.addAttribute("name","ftv!");
        User u = new User();
        u.setName("asd");
//添加模型数据,key的值为对象的类型首字母小写
        model.addAttribute(u);
        return "/WEB-INF/jsps/hello.jsp";
    }
跳转方式

请求转发:forward

return "forward:/data.jsp";

请求重定向:redirect

return "redirect:/data.jsp";

JSON

什么是json

JSON 指的是 JavaScript 对象表示法(JavaScript Object Notation)
JSON 是轻量级的文本数据交换格式
JSON 独立于语言:JSON 使用 Javascript语法来描述数据对象,但是 JSON 仍然独立于语言和平台。JSON 解析器和 JSON 库支持许多不同的编程语言。 目前非常多的动态(PHP,JSP,.NET)编程语言都支持JSON。
JSON 具有自我描述性,更易理解
————————————————

json的优缺点

支持缓存

优点:

  1. 可读性强:JSON使用简洁的文本格式表示数据,易于阅读和理解,且支持多种编程语言。
  2. 轻量级:JSON格式相比其他复杂的数据交换格式(如XML)更加轻巧,减少了数据传输的大小和网络带宽的消耗。
  3. 易于解析和生成:JSON数据可以通过大多数编程语言轻松解析和生成,提供了广泛的支持库和工具。
  4. 跨平台和跨语言支持:由于JSON是一种文本格式,可以在不同平台和编程语言之间进行数据交换,方便实现系统之间的集成和通信。
  5. 支持缓存

缺点:

  1. 不适合处理复杂结构:JSON对于处理嵌套和复杂的数据结构有一定的限制,对于大型数据集合或树状结活。
  2. 不支持注释:JSON规范中没有提供注释的语法,这在某些情况下可能导致不便。
  3. 不支持原生日期和二进制数据类型:JSON仅支持基本数据类型(如字符串、数字、布尔值和null),对于日期和二进制数据,需要进行额外的处理。
json的语法

数据类型:JSON支持以下数据类型:

  • 字符串(用双引号括起来)
  • 数字(整数或浮点数)
  • 布尔值(true或false)
  • 数组(用方括号括起来,元素之间用逗号分隔)
  • 对象(用花括号括起来,键值对表示)

json表示对象

    var user={
        id:1,
        name:"yyy",
        adder:{
            "北京":"awerftgyhuk",
            "HK":"sadddd",
            "CD":"sadsda"
        }
    }

json表示数组

var list=[
    {"id":"sad",name:"yy"},
    {"id":"ad",name:"das"},
    {"id":"d",name:"dav"}
]
SpringMVC返回json
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.9.10.8</version>
</dependency>

controller使用@ResponseBody注解)

@RequestMapping("/test01")
@ResponseBody
public String test01(){
    return "sdsssd";
}
时间格式处理

在时间字段上加格式化注解(时间格式化/前后端时间格式统一)

    @JsonFormat(pattern="yyyy-MM-dd HH:mm:ss",timezone="GMT+8")//后端向前台传值
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")//前端向后端传入的时间
    private Date creatTime;

这样这个字段的时间数据就被格式化了

当然可以写一个工具类来格式化时间

public class DateUtils {

public static String formatDateTime(Date date) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    String dateStr=sdf.format(date);
    return dateStr;
}
文件上传下载
文件的上传

添加文件上传的jar包

<dependency>
  <groupId>commons-fileupload</groupId>
  <artifactId>commons-fileupload</artifactId>
  <version>1.3.3</version>
</dependency>

jsp页面

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

enctype=“multipart/form-data” 一定要设置这个类型

配置上传解析器

SpringMVC使用MultipartFile来进行文件上传,所以我们首先要配置MultipartResolver,用于处理表单中的file

配置MultipartResolver:注意id="multipartResolver"的id值不能乱写

<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
	<!-- 设置上传文件的最大尺寸为1MB -->
	<property name="maxUploadSize">
		<!-- spring el写法:1MB -->
		<value>#{1024*1024}</value>
	</property>
     <!-- 效果同上 -->
<property name="maxUploadSize" value="1048576" />
</bean>

controller

@Controller
public class UploadController {

    @RequestMapping("/upload")
    @ResponseBody
    public String uploadFile(@RequestParam("myfile") MultipartFile fileUpload, HttpServletRequest request) throws IOException {

        System.out.println("上传文件是否为空:" + fileUpload.isEmpty());
        System.out.println("上传文件的大小(字节):" + fileUpload.getSize());
        System.out.println("上传文件的类型:" + fileUpload.getContentType());
        System.out.println("上传表单name属性值:" + fileUpload.getName());
        System.out.println("上传文件名:" + fileUpload.getOriginalFilename());

        String fileName = fileUpload.getOriginalFilename(); //文件名
        String realPath=request.getServletContext().getRealPath("/uploadFile");//真实路径(文件保存的位置)
        File file = new File(realPath);
        if (!file.exists()) {// 如果upload文件夹不存在,就创建
            file.mkdirs();
        }

        String prefix= UUID.randomUUID().toString().replaceAll("-","");//uuid来保证文件名的唯一性

        InputStream in= fileUpload.getInputStream();;//声明输入输出流
        OutputStream out=new FileOutputStream(new File(realPath+"/"+prefix+fileName));//指定输出流的位置;
        //工具方法 来实现文件的上传
        IOUtils.copy(in,out);

        out.close();
        in.close();
        return "成功!!";
    }

注意:tomact部署工件

选择的是war exploded包 文件上传到项目的target目录下

选择的是war包 文件上传到tomact所在的文件夹中

文件的下载

文件下载可以用get请求

一般使用超链接

<body>
<a href="/download?filename=17.png" >点击下载,17.png</a>
</body>
@RequestMapping("/download")
public void download(String filename, HttpServletRequest request, HttpServletResponse response) throws IOException {
    //获取服务器的路径
    String realPath = request.getServletContext().getRealPath("/download");
    File file = new File(realPath, filename);
    if (file.exists()){
        //如果文件存在
        //
        FileInputStream fileInputStream = new FileInputStream(file);

        // 设置文件下载的名字  -- 附件表示做下载或上传操作,浏览器就不会将文件的内容直接显示出来了
        response.setHeader("Content-Disposition", "attachment; filename=" + filename);

        //获取输出流
        ServletOutputStream outputStream = response.getOutputStream();

        //实现下载
        IOUtils.copy(fileInputStream,outputStream);

        outputStream.close();
        fileInputStream.close();
    }
}

文件名中文乱码问题

	if(req.getHeader("User-Agent").toUpperCase().indexOf("TRIDENT")!=-1){
				filename = URLEncoder.encode(filename, "utf-8");
				//电脑自带edge【edʒ】浏览器	
			}else if(req.getHeader("User-Agent").toUpperCase().indexOf("EDGE")!=-1){		
				filename = URLEncoder.encode(filename, "utf-8");
			}else{//其他浏览器
				filename = new String(filename.getBytes("UTF-8"),"ISO-8859-1");//转码的方式
			};
MVC拦截器

不拦截静态资源

实现 HandlerInterceptor接口

并重写方法 boolean preHandle,void postHandle,void afterCompletion

boolean preHandle:前置拦截器

void postHandle:后置拦截器

void afterCompletion:最终拦截器

public class MyInterceptor implements HandlerInterceptor {
    //preHandle()方法在业务处理器处理请求之前被调用
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("进入拦截器!!!");
        //可以判断用户是否登录(登录拦截器)
        //不继续执行返回false,否则返回true
        return true;
    }
    // postHandle()方法在业务处理器处理请求之后被调用
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }
    // afterCompletion()方法在DispatcherServlet完全处理完请求后被调用
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }
}

拦截器配置

<!-- 配置拦截器组 -->
<mvc:interceptors>
    <!-- 拦截器 -->
    <mvc:interceptor>
        <!-- 要拦截的配置,该配置必须写在不拦截的上面,/*拦截一级请求,/**拦截多级请求 -->
        <mvc:mapping path="/**"  />
        <!-- 设置不拦截的配置 -->
        <mvc:exclude-mapping path="/login"/>
        <!-- 配置拦截器 -->
        <bean class="com.yn.utils.MyInterceptor" />
    </mvc:interceptor>
</mvc:interceptors>
SpringMVC执行流程

在这里插入图片描述

  1. 客户端发送请求到前端控制器DispatcherServlet。
  2. DispatcherServlet接收到请求后,根据配置的处理器映射器HandlerMapping,将请求映射到对应的处理器Handler。
  3. HandlerAdapter将请求交给对应的处理器Handler进行处理。
  4. 处理器Handler执行业务逻辑,并返回ModelAndView对象,包含要渲染的数据模型和视图。
  5. HandlerAdapter将处理器Handler返回的ModelAndView对象传递给DispatcherServlet。
  6. DispatcherServlet通过视图解析器ViewResolver解析出具体的视图View。
  7. DispatcherServlet将处理结果Model和View传递给渲染器ViewRenderer进行视图渲染。
  8. 渲染后的视图响应给客户端。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值