目录
1、简介
2、数据响应方式
2.1、页面跳转
2.1.1、直接返回字符串
2.1.2、ModelAndView
2.1.3、request域
2.2、回写数据
2.2.1、直接返回字符串
2.2.2、返回对象或集合
3、获得请求数据
3.1、基本类型参数
3.2、获得POJO类型参数
3.3、获得数组类型参数
3.4、获得集合类型参数
3.4.1、VO对象
3.4.2、Ajax提交
3.4.3、注意事项
3.5、参数绑定
3.6、获得Restful风格的参数
3.7、自定义类型转换器
3.8、Servlet相关API
3.9、文件上传
3.9.1、单文件上传
3.9.2、多文件上传
3.10、请求乱码问题
4、重温三层架构
⭐作者介绍:大二本科网络工程专业在读,持续学习Java,努力输出优质文章
⭐作者主页:@逐梦苍穹
⭐所属专栏:JavaEE、Spring
所有代码都推到gitee了:https://gitee.com/xzl-it/spring
1、简介
Spring MVC 是 Spring Framework 的一个模块,用于开发基于 MVC(Model-View-Controller)架构的 Web 应用程序。它主要负责处理来自客户端的请求并返回响应。在 Spring MVC 中,请求和响应的处理是通过控制器(Controller)来实现的。下面我们将对 Spring MVC 的请求和响应进行简要介绍:
请求(Request): 在 Spring MVC 中,请求是指来自客户端(通常是浏览器)发送给服务器的信息。请求通常包含以下内容:
- URL: 请求的 URL 表示客户端想要访问的资源路径。例如:http://example.com/products。
- 请求方法(Request Method): 客户端发起请求时使用的方法,常见的有 GET、POST、PUT、DELETE 等。
- 请求参数(Request Parameters): 客户端通过 URL 查询参数、表单数据或请求体中的数据传递给服务器,用于请求处理的输入数据。
- 请求头部(Request Headers): 包含了关于请求的一些元信息,例如用户代理、授权信息等。
- 请求体(Request Body): 通常在 POST 或 PUT 请求中包含,用于传递更复杂的数据,例如 JSON 或 XML 数据。
在 Spring MVC 中,请求的处理由 DispatcherServlet 负责,DispatcherServlet 根据请求的 URL 找到对应的控制器进行处理。
响应(Response): 响应是服务器返回给客户端的数据,用于呈现给用户或供客户端进一步处理。通常,响应的内容由控制器生成并包含以下信息:
- 响应状态码(Response Status Code): 表示服务器对请求的处理结果,例如 200 表示成功,404 表示资源未找到,500 表示服务器错误等。
- 响应头部(Response Headers): 包含了关于响应的一些元信息,例如响应的数据类型、编码方式等。
- 响应体(Response Body): 实际的响应内容,通常是 HTML 页面、JSON 数据或其他类型的数据。
在 Spring MVC 中,控制器处理请求后,通过视图解析器(View Resolver)来确定响应应该使用哪个视图来呈现数据。视图可以是 JSP、Thymeleaf、FreeMarker 等模板引擎,或者是其他类型的视图。
总结起来,Spring MVC 的请求和响应是通过控制器来处理的,请求包含来自客户端的信息,而响应包含将要返回给客户端的数据。
2、数据响应方式
SpringMVC的数据响应方式
- 页面跳转
-
- 直接返回字符串
- 通过ModelAndView对象返回
- request域
- 回写数据
-
- 直接返回字符串
- 返回对象或集合
2.1、页面跳转
首先是在spring-mvc.xml中配置了视图解析器:
然后下面是各个方法:
2.1.1、直接返回字符串
返回带有前缀的字符串:
转发:forward:/WEB-INF/views/index.jsp
重定向:redirect:/index.jsp
转发可以访问WEB-INF,但是重定向不行,WEB-INF是受保护文件
2.1.2、ModelAndView
@RequestMapping("/method5")
//该参数由 Spring MVC 自动注入
public String model(Model model) {
model.addAttribute("name", "model模型");
return "success";
}
@RequestMapping("/method2")
public ModelAndView modelAndView1() {
ModelAndView modelAndView = new ModelAndView();
//配置的内部资源解析器依旧生效
//"forward:/JSP/userJSP.jsp"和"redirect:/JSP/userJSP.jsp"不能配置内部资源解析器
modelAndView.setViewName("userJSP");
return modelAndView;
}
2.1.3、request域
向request域存数据:
①使用HttpServletRequest:通过SpringMVC框架注入的request对象setAttribute()方法设置
@RequestMapping("/method6")
public String http_servlet_request(HttpServletRequest httpServletRequest) {
httpServletRequest.setAttribute("name", "http_servlet_request");
return "success";
}
②通过ModelAndView的addObject()方法设置
@RequestMapping("/method3")
public ModelAndView modelAndView2() {
/*
Model:模型 作用封装数据
View:视图 作用展示数据
*/
ModelAndView modelAndView = new ModelAndView();
//默认添加到request域
modelAndView.addObject("name", "xzl");
//配置的内部资源解析器依旧生效
modelAndView.setViewName("success");
return modelAndView;
}
2.2、回写数据
Web基础阶段,客户端访问服务器端,如果想直接回写字符串作为响应体返回的话,只需要使用
response.getWriter().print("hello world") 即可,那么在Controller中想直接回写字符串该怎样呢?
2.2.1、直接返回字符串
①HttpServletResponse
通过SpringMVC框架注入的response对象,使用response.getWriter().print(“hello world”) 回写数
据,此时不需要视图跳转,业务方法返回值为void。
@RequestMapping("/method7")
public void http_servlet_response(HttpServletResponse response) throws IOException {
response.getWriter().print("print http_servlet_response");
//也可以这么写,显示的页面就不是黑白的:
response.getWriter().print("<html><body><h1>Hello, HTML!</h1></body></html>");
}
②@ResponseBody
将需要回写的字符串直接返回,但此时需要通过@ResponseBody
注解告知SpringMVC框架,方法
返回的字符串不是跳转是直接在http响应体中返回。
在异步项目中,客户端与服务器端往往要进行json格式字符串交互,此时我们可以手动拼接json字符串返回
@RequestMapping("/method9")
@ResponseBody
public String response_JSON() {
return "{\"username\":\"xzl\",\"age\":21}";
}
@RequestMapping("/method8")
@ResponseBody
public String noJumpView1() {
return "userJSP";
}
③jackson
上述方式手动拼接json格式字符串的方式很麻烦,开发中往往要将复杂的java对象转换成json格式的字符串,可以使用web阶段学习过的json转换工具jackson进行转换,导入jackson坐标。
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.0</version>
</dependency>
编写代码:
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
@RequestMapping("/method10")
@ResponseBody
public String response_JSON_utils() throws JsonProcessingException {
User user = new User();
user.setUsername("XuZiLin");
user.setAge(21);
return new ObjectMapper().writeValueAsString(user);
}
2.2.2、返回对象或集合
通过SpringMVC帮助我们对对象或集合进行json字符串的转换并回写,为处理器适配器配置消息转换参数,
指定使用jackson进行对象或集合的转换,因此需要在spring-mvc.xml中进行如下配置:
<!--配置处理器映射器-->
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
<property name="messageConverters">
<list>
<bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter"/>
</list>
</property>
</bean>
@RequestMapping("/method13")
@ResponseBody
public List<User> response_JSON_Auto3(ArrayList<User> list, User user) {
user.setUsername("XZL-ArrayList");
user.setAge(21);
list.add(user);
return list;
}
@RequestMapping("/method12")
@ResponseBody
public User response_JSON_Auto2(User user) {
user.setUsername("XZL-xzl");
user.setAge(21);
return user;
}
@RequestMapping("/method11")
@ResponseBody
public User response_JSON_Auto() {
User user = new User();
user.setUsername("XZL");
user.setAge(21);
return user;
}
这样配置比较麻烦,配置的代码比较多,
因此,我们可以使用mvc的注解驱动代替上述配置。
<!--mvc的注解驱动-->
<mvc:annotation-driven/>
在 SpringMVC 的各个组件中,处理器映射器、处理器适配器、视图解析器称为 SpringMVC 的三大组件。
使用<mvc:annotation-driven>自动加载 RequestMappingHandlerMapping(处理映射器)和
RequestMappingHandlerAdapter( 处 理 适 配 器 ),可用在Spring-xml.xml配置文件中使用
<mvc:annotation-driven>替代注解处理器和适配器的配置。
同时使用<mvc:annotation-driven>默认底层就会集成jackson进行对象或集合的json格式字符串的转换
3、获得请求数据
客户端请求参数的格式是:name=value&name=value… …
服务器端要获得请求的参数,有时还需要进行数据的封装,SpringMVC可以接收如下类型的参数:
1)基本类型参数
2)POJO类型参数
3)数组类型参数
4)集合类型参数
5)日期类型
6)文件类型
3.1、基本类型参数
Controller中的业务方法的参数名称要与请求参数的name一致,参数值会自动映射匹配。
@RequestMapping(value = "/method1")
public void get_basicTypeParameter(String username, int age) {
System.out.println(username);
System.out.println(age);
}
3.2、获得POJO类型参数
Controller中的业务方法的POJO参数的属性名与请求参数的name一致,参数值会自动映射匹配
POJO类:
package com.xzl.domain;
/**
* @author 逐梦苍穹
* @date 2023/7/29 22:33
*/
public class User {
private String username;
private int age;
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", age=" + age +
'}';
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
实现方法:
@RequestMapping(value = "/method2")
//如果属性名一致,SpringMVC会自动封装
public void get_pojoTypeParameter(User user) {
System.out.println(user);
System.out.println(user.getUsername());
System.out.println(user.getAge());
}
3.3、获得数组类型参数
Controller中的业务方法数组名称与请求参数的name一致,参数值会自动映射匹配。
@RequestMapping(value = "/method3")
public void get_arrayTypeParameter(String[] strings) {
System.out.println(Arrays.asList(strings));
}
输出打印结果:[strings, 逐梦苍穹string, age21]
3.4、获得集合类型参数
获得集合参数时,要将集合参数包装到一个POJO中才可以,因为集合需要传入参数类型对象,SpringMVC无法自动识别,需要我们借助POJO进行封装
3.4.1、VO对象
在domain包下创建VO对象(VIEW OBJECT),里面包装了list集合对象:
package com.xzl.domain;
import java.util.List;
/**
* @author 逐梦苍穹
* @date 2023/7/30 10:55
*/
public class VO {
private List<User> list;
@Override
public String toString() {
return "VO{" +
"list=" + list +
'}';
}
public List<User> getList() {
return list;
}
public void setList(List<User> list) {
this.list = list;
}
}
@RequestMapping(value = "/method5")
public void get_arrayList_TypeParameter2(VO vo) {
System.out.println(vo.getList());
}
//这个方法是有问题的,不能直接封装集合对象
@RequestMapping(value = "/method4")
public void get_arrayList_TypeParameter(ArrayList<User> list) {
System.out.println("启动");
for (User user : list) {
System.out.println(user);
}
}
前端post提交:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/Request/method5" method="post">
<%--表明是第几个User对象的username age--%>
<input type="text" name="list[0].username"><br/>
<input type="text" name="list[0].age"><br/>
<input type="text" name="list[1].username"><br/>
<input type="text" name="list[1].age"><br/>
<input type="submit" value="提交">
</form>
</body>
</html>
打印输出结果:[User{username='xzl', age=1}, User{username='逐梦苍穹', age=3}]
3.4.2、Ajax提交
当使用ajax提交时,可以指定contentType为json形式,那么在方法参数位置使用@RequestBody可以
直接接收集合数据而无需使用POJO进行包装
ajax.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
<script src="${pageContext.request.contextPath}/JS/jquery-3.3.1.js"></script>
<script>
var userList = new Array();
userList.push({username:"zhangsan",age:18});
userList.push({username:"xzl逐梦苍穹",age:28});
$.ajax({
type:"POST",
url:"${pageContext.request.contextPath}/Request/method6",
data:JSON.stringify(userList),
contentType:"application/json;charset=utf-8"
});
</script>
</head>
<body>
</body>
</html>
@RequestMapping(value = "/method7")
//显示绑定之后,请求的名字就应该是name
public void request_param(@RequestParam(value = "name",required = false,defaultValue = "xzl") String username) {
System.out.println(username);
}
@RequestMapping(value = "/method6")
public void get_arrayList_TypeParameter3(@RequestBody List<User> userList) {
System.out.println(userList);
}
3.4.3、注意事项
使用ajax发请求的时候,可能会加载失败,因为导入的jquery加载不成功,被SpringMVC的前端控制器
DispatcherServlet拦截,因为url-pattern配置的是/,代表对所有的资源都进行过滤操作,我们可以通过以下两种方式指定放行静态资源:
• 在spring-mvc.xml配置文件中指定放行的资源:
<mvc:resources mapping="/JS/*" location="/JS/"/>
• 使用<mvc:default-servlet-handler/>标签
3.5、参数绑定
当请求的参数名称与Controller的业务方法参数名称不一致时,就需要通过@RequestParam
注解显示的绑定。
注解@RequestParam有如下参数可以使用:
1) value:与请求参数名称
2) required:此在指定的请求参数是否必须包括,默认是true,提交时如果没有此参数则报错
3) defaultValue:当没有指定请求参数时,则使用指定的默认值赋值
@RequestMapping(value = "/method7")
//显示绑定之后,请求的名字就应该是name
public void request_param(@RequestParam(value = "name",required = false,defaultValue = "xzl") String username) {
System.out.println(username);
}
3.6、获得Restful风格的参数
Restful是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。主要用于客户端和服务
器交互类的软件,基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存机制等。
Restful风格的请求是使用“url+请求方式”表示一次请求目的的,HTTP 协议里面四个表示操作方式的动词如下:
1) GET:用于获取资源
2) POST:用于新建资源
3) PUT:用于更新资源
4) DELETE:用于删除资源
例如:
1) /user/1 GET :得到 id = 1 的 user
2) /user/1 DELETE: 删除 id = 1 的 user
3) /user/1 PUT:更新 id = 1 的 user
4) /user POST:新增 user
在业务方法中我们可以使用@PathVariable注解进行占位符的匹配获取工作。
3.7、自定义类型转换器
SpringMVC 默认已经提供了一些常用的类型转换器,例如客户端提交的字符串转换成int型进行参数设置。
但是不是所有的数据类型都提供了转换器,没有提供的就需要自定义转换器,例如:日期类型的数据就需要自定义转换器。
自定义类型转换器的开发步骤:
① 定义转换器类实现Converter接口
② 在配置文件中声明转换器
③ 在<annotation-driven>中引用转换器
转换器类DateConverter:
package com.xzl.converter; import org.springframework.core.convert.converter.Converter; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; /** * @author 逐梦苍穹 * @date 2023/7/30 16:28 */ public class DateConverter implements Converter<String, Date> { @Override public Date convert(String dateString) { //参数String取决于实现接口,重写方法返回的类型也是取决于实现接口 SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); Date date = null; try { date = simpleDateFormat.parse(dateString); } catch (ParseException e) { e.printStackTrace(); } System.out.println(simpleDateFormat.format(date)); return date; } }
spring-mvc.xml配置:
控制器方法:
@RequestMapping(value = "/method10") public void date_converter2(Date date) { System.out.println(date); } @RequestMapping(value = "/method9/{date}") public void date_converter(@PathVariable(value = "date",required = false) Date date) { System.out.println(date); }
3.8、Servlet相关API
SpringMVC支持使用原始ServletAPI对象作为控制器方法的参数进行注入,常用的对象如下:
1) HttpServletRequest
2) HttpServletResponse
3) HttpSession
获得请求头
@RequestHeader
使用@RequestHeader可以获得请求头信息,相当于web阶段学习的request.getHeader(name)
@RequestHeader注解的属性如下:
1) value:请求头的名称
2) required:是否必须携带此请求头
@CookieValue
使用@CookieValue可以获得指定Cookie的值
@CookieValue注解的属性如下:
1) value:指定cookie的名称
2) required:是否必须携带此cookie
@RequestMapping(value = "/method13") public void get_cookieValue(@CookieValue(value = "JSESSIONID",required = false) String jsessionid){ System.out.println(jsessionid); } @RequestMapping(value = "/method12") public void get_requestHeader(@RequestHeader(value = "User-Agent",required = false) String headerValue) { System.out.println(headerValue); } @RequestMapping(value = "/method11") public void getServlet_API(HttpServletRequest request, HttpServletResponse response, HttpSession httpSession) { System.out.println(request); System.out.println(response); System.out.println(httpSession); System.out.println(request.getHeader("User-Agent")); //获取Cookie Cookie[] cookies = request.getCookies(); for (Cookie cookie : cookies) { System.out.println("name:" + cookie.getName() + " -> " + "value:" + cookie.getValue()); } }
3.9、文件上传
文件上传客户端三要素
表单项type=“file”
表单的提交方式是post
表单的enctype属性是多部分表单形式,及enctype=“multipart/form-data”
upload.jsp:
<%-- Created by IntelliJ IDEA. User: 逐梦苍穹 Date: 2023/7/31 Time: 1:07 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>file upload</title> </head> <body> <form action="${pageContext.request.contextPath}/filesUpload/method1" method="post" enctype="multipart/form-data"> 名称:<input type="text" name="name"><br> 文件:<input type="file" name="file"><br> <input type="submit" value="提交"><br> </form> </body> </html>
文件上传原理
当form表单修改为多部分表单时,request.getParameter()将失效。
enctype=“application/x-www-form-urlencoded”时,form表单的正文内容格式是:
key=value&key=value&key=value
当form表单的enctype取值为Mutilpart/form-data时,请求正文内容就变成多部分形式:
3.9.1、单文件上传
单文件上传步骤:
① 导入fileupload和io坐标
② 配置文件上传解析器
③ 编写文件上传代码
① 导入fileupload和io坐标
<dependency> <groupId>commons-fileupload</groupId> <artifactId>commons-fileupload</artifactId> <version>1.2.2</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency>
② 配置文件上传解析器
这里的id固定取值为multipartResolver,我也不知道为什么,取别的id要报错……🥹
③ 编写文件上传代码
<%--
Created by IntelliJ IDEA.
User: 逐梦苍穹
Date: 2023/7/31
Time: 1:07
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>file upload</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/filesUpload/method1" method="post" enctype="multipart/form-data">
名称:<input type="text" name="name"><br>
文件:<input type="file" name="file"><br>
<input type="submit" value="提交"><br>
</form>
</body>
</html>
@RequestMapping(value = "/method1")
public void fileUpload(String name, MultipartFile file) throws IOException {
System.out.println(name);
String originalFilename = file.getOriginalFilename();
File filePath = new File("src/main/java/com/xzl/controller/" + originalFilename);
file.transferTo(filePath);
System.out.println(filePath.getAbsolutePath());
}
3.9.2、多文件上传
多文件上传,只需要将页面修改为多个文件上传项,将方法参数MultipartFile类型修改为MultipartFile[]即可
<%--
Created by IntelliJ IDEA.
User: 逐梦苍穹
Date: 2023/7/31
Time: 1:07
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>files upload</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/filesUpload/method2" method="post" enctype="multipart/form-data">
名称:<input type="text" name="name"><br>
文件1:<input type="file" name="files"><br>
文件2:<input type="file" name="files"><br>
文件3:<input type="file" name="files"><br>
<input type="submit" value="提交"><br>
</form>
</body>
</html>
@RequestMapping(value = "/method2")
public void filesUpload(String name, MultipartFile[] files) throws IOException {
System.out.println(name);
for (MultipartFile file : files) {
String originalFilename = file.getOriginalFilename();
File filePath = new File("src/main/java/com/xzl/controller/" + originalFilename);
file.transferTo(filePath);
System.out.println(filePath.getAbsolutePath());
}
}
3.10、请求乱码问题
post请求基本上都伴随着乱码问题,这里可以在web.xml中配置过滤器对资源先进行过滤初始化,这里的过滤器优先级是高于DispatcherServlet前端控制器的:
<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>/Request/*</url-pattern>
</filter-mapping>
4、重温三层架构
三层架构是一种常见的软件架构模式,旨在将软件系统划分为三个主要的层级,以便实现模块化、可维护性和可扩展性。这种架构模式将应用程序划分为三个独立的层级,每个层级都有特定的职责和功能,彼此之间通过接口进行交互。文章来源:https://www.toymoban.com/news/detail-625970.html
三层架构的三个层级通常是:文章来源地址https://www.toymoban.com/news/detail-625970.html
- 表示层(Presentation Layer): 表示层是用户与系统交互的界面层,通常是前端界面(如 Web 页面或移动端界面)。它负责接收用户输入、展示数据和结果,以及向用户展示信息。在这一层级中,主要的组件是用户界面和控制器(Controller)。
-
- 用户界面:负责向用户展示信息和接收用户输入,通常由 HTML、CSS、JavaScript 和其他前端技术构成。
- 控制器:负责接收用户输入,处理用户请求,并调用业务逻辑层的服务来获取数据和处理业务。
- 业务逻辑层(Business Logic Layer): 业务逻辑层是处理系统的业务逻辑和业务规则的核心层。它包含了业务实体、业务规则和业务逻辑。在这一层级中,主要的组件是服务(Service)和领域对象(Domain Object)。
-
- 服务(Service):负责实现业务逻辑和业务规则,协调领域对象之间的交互。它是表示层和数据访问层之间的接口,向表示层提供业务功能的接口,并调用数据访问层来获取和存储数据。
- 领域对象(Domain Object):代表业务领域中的实体对象,包含对象的属性和方法,用于描述对象的特性和行为。
- 数据访问层(Data Access Layer): 数据访问层负责与数据库或其他数据源进行交互,对数据进行增删改查操作。它提供了访问数据库的接口,以便在业务逻辑层中使用。在这一层级中,主要的组件是数据访问对象(DAO)和数据库。
-
- 数据访问对象(DAO):负责封装对数据库的访问和操作,提供数据的持久化和检索功能,隐藏数据库细节,使业务逻辑层能够与数据库解耦。
- 数据库:持久化存储数据的地方,可以是关系型数据库(如 MySQL、PostgreSQL)或非关系型数据库(如 MongoDB、Redis)等。
到了这里,关于SpringMVC请求和响应的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!