SpringMVC

一.SpringMVC介绍

SpringMVC属于SpringFrameWork的产品,是一个实现MVC的轻量级Web框架

优点:

  • 轻量级、高效
  • 与Spring兼容性好,无缝结合
  • 功能强大:RESTful、数据验证、格式化dengs
SpringMvc是以请求为驱动,围绕DispatcherServlet设计的,DispatcherServlet是一个中心控制器,它的作用是将不同的请求分发到不同的处理器,然后对请求作出不同的响应

二.SpringMVC执行原理

SpringMvc执行流程图 引用:狂神

spring初始化的时候会把控制器处理器和适配器提供好,以及Controller被控制器处理器携带着,放在一个map中,key为路径,value为对象

1.用户发送请求到DispatcherServlet前端控制器
2.DispatcherServlet收到请求调用HandlerMapping处理器映射器
		为什么要获取映射器?这是因为映射器不止一个,我们配置Controller有xml和注解两种方式
		1.怎么查找的呢
				1.进入RequestMappingHandlerMapping的afterPropertiesSet方法,从applicationContext中扫描beans,然后将bean查找判断有没有@RquestMapping注解,有的话会将类注解和方法注解合并,注册处理器方法
3.HandlerMapping根据请求url找到Handler处理器,Handler封装了控制器(我们自己写的Controller)和一个拦截器返回给DispatcherServlet
4.DisPatcherServlet获取HandlerAdapter处理器的适配器
		为什么要获取适配器呢?与映射器一样,有不同的适配器,有xml和注解两种方式
5.HandlerAdapter执行Handler
7.Controller执行后返回信息给HandlerAdapter,比如ModelAndView
8.HandlerAdapter将ModelAndView返回给DispatcherServlet
9.DispatcherServlet将ModelAndView传递给ViewResolver视图解析器
10.ViewResolver解析后将具体的视图名返回给DispatcherServlet
11.DispatcherServlet对View进行视图渲染
12.将View视图响应给用户

代码演示

真实开发并不会这样操作。。。还是使用注解

1.导入jar包

<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-webmvc</artifactId>
   <version>5.1.9.RELEASE</version>
</dependency>

2.配置web.xml

<!--配置DispatcherServlet前端控制器,用于接收请求、拦截请求、分发请求给处理器-->
<servlet>
    <servlet-name>springmcv</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!--DispatcherServlet 绑定Spring的配置文件  -->
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:springmvc-servlet.xml</param-value>
    </init-param>
</servlet>

<!--
 url-pattern / 和 /*的区别:
        / :匹配所有的请求,不匹配jsp页面
        /*:匹配所有的请求,包括jsp页面
-->
<servlet-mapping>
    <servlet-name>springmcv</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

3.配置springmvc-servlet.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

        <!--处理映射器:根据请求url查找映射器(Handler)-->
        <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"></bean>

        <!--处理适配器:去执行控制器(Controller)-->
        <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"></bean>

        <!--视图解析器:解析视图的名字 DispatcherServlet调用并且传给视图名,根据前缀和后缀解析好视图名返回具体视图,然后DispatcherServlet调用具体的视图并且显示-->
        <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <!--配置好前缀和后缀就只用在代码写视图的名字了-->
            <!--视图的前缀 jsp放在WEB-INF下用户不可以直接访问-->
                <property name="prefix" value="/WEB-INF/jsp/"></property>
            <!--视图的后缀-->
                <property name="suffix" value=".jsp"></property>
        </bean>

				<!--自己编写的Controller-->
        <bean id="/hello" class="com.djn.hellomvc.controller.HelloController"></bean>

</beans>

4.编写Controller

实现Controller是一种比较老的方法,而且每个类只能处理一个功能,假如有几百个功能,需要写几百个类?

public class HelloController implements Controller {

    public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {

        ModelAndView modelAndView = new ModelAndView();

        String result ="hello mvc!";

        modelAndView.addObject("msg",result);

        //会将ModelAndView传给HandlerAdpter,然后传给DispatcherServlet,然后将视图名字传给视图解析器处理
        modelAndView.setViewName("hello");
        return null;
    }
}

问题

部署成功,访问404

  • 可能是因为jar包没有导入进去,需要将jar包放进去

配置lib

三.注解开发

1.导入jar包

<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-webmvc</artifactId>
   <version>5.1.9.RELEASE</version>
</dependency>

2.配置web.xml

<!--配置DispatcherServlet前端控制器,用于接收请求、拦截请求、分发请求给处理器-->
<servlet>
    <servlet-name>springmcv</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!--DispatcherServlet 绑定Spring的配置文件  -->
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:springmvc-servlet.xml</param-value>
    </init-param>
</servlet>

<!--
 url-pattern / 和 /*的区别:
        / :匹配所有的请求,不匹配jsp页面
        /*:匹配所有的请求,包括jsp页面
-->
<servlet-mapping>
    <servlet-name>springmcv</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

3.配置springmvc-servlet.xml

<!--配置Spring创建容器时要扫描的包 注解扫描-->
<context:component-scan base-package="com.djn.hellomvc"></context:component-scan>

<!--让SpringMVC不处理静态资源-->
<mvc:default-servlet-handler/>

<!--
    在spring中一般采用@RequestMapping注解来完成映射关系
    要想使@RequestMapping注解生效
    必须向上下文中注册DefaultAnnotationHandlerMapping
    和一个AnnotationMethodHandlerAdapter实例
    这两个实例分别在类级别和方法级别处理。
    而annotation-driven配置帮助我们自动完成上述两个实例的注入。
 -->
<mvc:annotation-driven/>


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

4.编写Controller

@Controller必须要有,因为底层会先扫描Spring容器中的bean,然后再去找@RequestMapping注解,具体的流程可以在博客中搜SpringMVC源码分析

@Controller
@RequestMapping("/hello")
public class HelloController {
    @RequestMapping("/hello")
    public String  hello(Model model) {
        model.addAttribute("msg","hello mvc...");
        return "hello";
    }
}

四.RESTFul

RESTFul是一种风格,通过不同的请求方式来实现不同的功能

优点:

  • 简洁
  • 高效:可以做缓存
  • 安全:可以隐藏参数

@PathVariable:将参数映射到url路径上

@Controller
public class RESTFulController {
    //原先    :http://locahost:8080/restful/test?a=1&b=2
    //RESTFul:http://locahost:8080/restful/test/1/2
    @RequestMapping(value = "/restful/test/{a}/{b}",method = RequestMethod.GET)
    public String test(@PathVariable int a,@PathVariable int b, Model model){
        model.addAttribute("msg","GET结果为"+(a+b));
        return "hello";
    }

    @PostMapping(value = "/restful/test/{a}/{b}")
    public String test2(@PathVariable int a,@PathVariable int b, Model model){
        model.addAttribute("msg","POST结果为"+(a+b));
        return "hello";
    }
}

转发与重定向

使用SpringMVC转发重定向有两种方式,一种使用HttpServletRequest和HttpServletResponse,一种是return

注意:

  • 默认就是使用的转发方式
  • 使用重定向不能将jsp页面放在WEB-INF下,因为重定向相当于用户第二次请求,而WEB-INF下的是直接请求不到的
@Controller
public class RESTFulController {
    //重定向: redirect:/jsp页面
    @RequestMapping(value = "/restful/test/{a}/{b}",method = RequestMethod.GET)
    public String test(@PathVariable int a,@PathVariable int b, Model model){
        model.addAttribute("msg","GET结果为"+(a+b));
        return "redirect:/hello.jsp";
    }
}

五.接受参数

正常接受参数:

传递的参数名与接受的参数名一致,可以直接定义

//http://localhost:8080/param/test?name=djn
@GetMapping("/param/test")
public void test(String name){
    System.out.println(name);
}

传递参数与接受参数名称不一致:

使用@RequstParam注解,用于匹配传递的参数

//http://localhost:8080/param/test?username=djn
@GetMapping("/param/test")
public void test(@RequestParam("username") String name){
    System.out.println(name);
}

接受对象参数

传递的参数必须和对象的属性名一致,参数直接使用对象,如果不一致会为null

//http://localhost:8080/param/test2?name=djn&age=22&sex=男
@GetMapping("/param/test")
public void test(User user){
    System.out.println(user);
}
@Data
public class User {
    private String name;
    private String age;
    private String sex;
}

六.字符乱码

使用SpringMVC提供的CharacterEncodingFilter字符编码过滤器

web.xml

注意:

  • /*会匹配jsp,如果是/不会解决jsp乱码的
   <!--字符编码过滤器-->
    <filter>
        <filter-name>encoding</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>false</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encoding</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

七.返回JSON数据

json是轻量级数据交换格式,以键值对形式来存储和表示数据,可读性高、容易解析,传输效率高

1.使用@ResponseBody注解
2.使用jackson/fastjson将对象转换为json并return
3.处理json乱码

使用jackson

1.导入jar包

 <dependency>
     <groupId>com.fasterxml.jackson.core</groupId>
     <artifactId>jackson-core</artifactId>
     <version>2.9.9</version>
 </dependency>
 <dependency>
     <groupId>com.fasterxml.jackson.core</groupId>
     <artifactId>jackson-databind</artifactId>
     <version>2.9.9</version>
 </dependency>
 <dependency>
     <groupId>com.fasterxml.jackson.core</groupId>
     <artifactId>jackson-annotations</artifactId>
     <version>2.9.8</version>
 </dependency>

2.配置web.xml

<!--配置DispatcherServlet前端控制器,用于接收请求、拦截请求、分发请求给处理器-->
    <servlet>
        <servlet-name>springmcv</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--DispatcherServlet 绑定Spring的配置文件  -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:springmvc-servlet.xml</param-value>
        </init-param>
    </servlet>

    <!--
     url-pattern / 和 /*的区别:
            / :匹配所有的请求,不匹配jsp页面
            /*:匹配所有的请求,包括jsp页面
    -->
    <servlet-mapping>
        <servlet-name>springmcv</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>


    <!--字符编码过滤器-->
    <filter>
        <filter-name>encoding</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>false</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encoding</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

3.配置springmvc-servlet.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:aop="http://www.springframework.org/schema/aop" 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/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">

      <!--配置Spring创建容器时要扫描的包 注解扫描-->
      <context:component-scan base-package="com.djn"></context:component-scan>

      <!--让SpringMVC不处理静态资源-->
      <mvc:default-servlet-handler/>

      <!--
          在spring中一般采用@RequestMapping注解来完成映射关系
          要想使@RequestMapping注解生效
          必须向上下文中注册DefaultAnnotationHandlerMapping
          和一个AnnotationMethodHandlerAdapter实例
          这两个实例分别在类级别和方法级别处理。
          而annotation-driven配置帮助我们自动完成上述两个实例的注入。
       -->
      <mvc:annotation-driven/>

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


</beans>

4.配置Controller

@ResponseBody表示不会被视图解析器解析

@Controller//当前类是一个控制器,可以被处理器适配器执行
public class JsonController {

    @SneakyThrows
    @GetMapping("/json/test")
    @ResponseBody //表示不会被视图解析器解析
    public String test() {
        //创建jackson
        ObjectMapper objectMapper = new ObjectMapper();
        User user = new User("丁江楠", 22, "男");
        //将对象转为json字符串
        String json = objectMapper.writeValueAsString(user);
        return json;
    }
}

5.处理json乱码

 <!--json乱码问题-->
      <mvc:annotation-driven>
            <mvc:message-converters register-defaults="true">
                  <!--解决乱码-->
                  <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                        <constructor-arg value="UTF-8"/>
                  </bean>
                  <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
                        <property name="objectMapper">
                              <bean class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean">
                                    <property name="failOnEmptyBeans" value="false"/>
                              </bean>
                        </property>
                  </bean>
            </mvc:message-converters>
      </mvc:annotation-driven>

使用fastjson

1.导入jar包

<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.60</version>
</dependency>

2.省略配置。。。。

3.测试

 @Test
 public void testFastjson(){
     User user1 = new User("丁江楠", 22, "男");
   
     System.out.println("****对象转json*****");
     String toJSONString = JSON.toJSONString(user1);
     System.out.println(toJSONString);

     System.out.println("******json转对象*******");
     User user = JSON.parseObject(toJSONString,User.class);
     System.out.println(user);
 }

八.拦截器

SpringMVC中的拦截器类似于servlet中的过滤器,用于拦截请求进行预处理和后处理,和过滤器不同的是,拦截器只会拦截Controller中的方法,自动过滤掉jsp/html/js等资源

基于AOP实现的

自定义拦截器:

1.实现HandlerInterceptor接口
2.处理逻辑
3.注册拦截器

案例

一个登录案例,没有登录不能访问主页,通过拦截器实现

登录拦截器

public class LoginInterceptor implements HandlerInterceptor {

    //返回true放行
    //放回false不放行
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("====处理前====");

      	//如果是登录相关的 直接放行
        if (request.getRequestURI().contains("login")) {
            return true;
        }
	
      	//有session,说明登录过,直接放行
        if (request.getSession().getAttribute("userinfo") != null) {
            return true;
        }

        //需要转发,因为重定向访问不到WEB-INF下的资源
        request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request,response);
        return false;
    }

    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("====处理后====");
    }

    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("====清理====");
    }
}

配置拦截器

<!--拦截器-->
<mvc:interceptors>
    <mvc:interceptor>
        <!--/**表示拦截所有请求-->
        <mvc:mapping path="/**"/>
        <!--拦截类-->
        <bean class="com.djn.intercepter.LoginInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

登录Controller类

@Controller
public class TestController {

    //登录处理
    @RequestMapping("/user/login")
    public String  login(String username, String password, HttpSession session){
        System.out.println("login==>"+username+","+password);
        session.setAttribute("userinfo",username);
        return "main";
    }

    //前往主页面
    @RequestMapping("/gomain")
    public String gomain(){
        return "main";
    }

    //前往登录页面
    @RequestMapping("/gologin")
    public String gologin(){
        return "login";
    }

    //注销处理
    @RequestMapping("/goout")
    public String goout(HttpSession session){
        session.removeAttribute("userinfo");
        return "login";
    }

}

jsp页面

<!--index.jsp-->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title></title>
  </head>
  <body>
      <a href="${pageContext.request.contextPath}/gologin">去登录</a>
      <a href="${pageContext.request.contextPath}/gomain">去首页</a>
  </body>
</html>

<!--login.jsp-->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>登录</title>
</head>
<body>
        <form action="/user/login" method="post">
            <label for="name">用户名:</label><input id="name" name="username" type="text">
            <label for="password">密码:</label><input id="password" name="password" type="text">
            <input type="submit" value="登录">
        </form>
</body>
</html>

<!--main.jsp-->
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>主页</title>
</head>
<body>
    <h1> ${sessionScope.get("userinfo")},欢迎回来</h1>
    <a href="${pageContext.request.contextPath}/goout">注销</a>
</body>
</html>

九.文件上传和下载

基于SpringMVC实现文件上传和下载

前端:
		1.请求方式要为post
		2.将enctype设置为multipart/form-data,一旦设置了multipart/form-data,浏览器会以二进制流的形式来处理表单数据
后端
		1.SpringMVC需要配置MultipartResolver解析器
		2.使用CommonsMultipartFile接受文件
		3.获取文件名、创建上传路径,读取文件并写入文件
		3.使用file.transferTo(new File(realPath +"/"+ file.getOriginalFilename()));不需要读,直接写(更简单)

1.导入jar包

因为MultipartResolver是基于commons-fileupload实现的

<!--文件上传-->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.3</version>
</dependency>

2.配置MultipartResolver解析器

<!--文件上传配置-->
    <bean id="multipartResolver"  class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!-- 请求的编码格式,必须和jSP的pageEncoding属性一致,以便正确读取表单的内容,默认为ISO-8859-1 -->
        <property name="defaultEncoding" value="utf-8"/>
        <!-- 上传文件大小上限,单位为字节(10485760=10M) -->
        <property name="maxUploadSize" value="10485760"/>
        <property name="maxInMemorySize" value="40960"/>
    </bean>

3.上传jsp页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
  <head>
    <title>$Title$</title>
  </head>
  <body>
      <form action="${pageContext.request.contextPath}/upload" method="post" enctype="multipart/form-data">
          <input type="file" name="file">
          <input type="submit" value="上传">
      </form>
  </body>
</html>

4.上传 方式一:

@Controller
public class FileController {
  
    //@RequestParam("file") 将name=file控件得到的文件封装成CommonsMultipartFile 对象
    //批量上传CommonsMultipartFile则为数组即可
    @RequestMapping("/upload")
    public String fileUpload(@RequestParam("file") CommonsMultipartFile file , HttpServletRequest request) throws IOException {

        //获取文件名 : file.getOriginalFilename();
        String uploadFileName = file.getOriginalFilename();

        //如果文件名为空,直接回到首页!
        if ("".equals(uploadFileName)){
            return "redirect:/index.jsp";
        }
        System.out.println("上传文件名 : "+uploadFileName);

        //上传路径保存设置
        String path = request.getServletContext().getRealPath("/upload");
        //如果路径不存在,创建一个
        File realPath = new File(path);
        if (!realPath.exists()){
            realPath.mkdir();
        }
        System.out.println("上传文件保存地址:"+realPath);

        InputStream is = file.getInputStream(); //文件输入流
        OutputStream os = new FileOutputStream(new File(realPath,uploadFileName)); //文件输出流

        //读取写出
        int len=0;
        byte[] buffer = new byte[1024];
        while ((len=is.read(buffer))!=-1){
            os.write(buffer,0,len);
            os.flush();
        }
        os.close();
        is.close();
        return "redirect:/index.jsp";
    }
}

5.上传 方式二 更简单:

@Controller
public class FileController {
    /*
     * 采用file.Transto 来保存上传的文件
     */
    @RequestMapping("/upload2")
    public String  fileUpload2(@RequestParam("file") CommonsMultipartFile file, HttpServletRequest request) throws IOException {
        //上传路径保存设置
        String path = request.getServletContext().getRealPath("/upload");
        File realPath = new File(path);
        if (!realPath.exists()){
            realPath.mkdir();
        }
        //上传文件地址
        System.out.println("上传文件保存地址:"+realPath);

        //通过CommonsMultipartFile的方法直接写文件(注意这个时候)
        file.transferTo(new File(realPath +"/"+ file.getOriginalFilename()));

        return "redirect:/index.jsp";
    }
}

6.下载

    @RequestMapping(value="/download")
    public String downloads(HttpServletResponse response , HttpServletRequest request) throws Exception{
        //要下载的图片地址
        String  path = request.getServletContext().getRealPath("/upload");
        String  fileName = "map.jpg";

        //1、设置response 响应头
        response.reset(); //设置页面不缓存,清空buffer
        response.setCharacterEncoding("UTF-8"); //字符编码
        response.setContentType("multipart/form-data"); //二进制传输数据
        //设置响应头
        response.setHeader("Content-Disposition",
                "attachment;fileName="+ URLEncoder.encode(fileName, "UTF-8"));

        File file = new File(path,fileName);
        //2、 读取文件--输入流
        InputStream input=new FileInputStream(file);
        //3、 写出文件--输出流
        OutputStream out = response.getOutputStream();

        byte[] buff =new byte[1024];
        int index=0;
        //4、执行 写出操作
        while((index= input.read(buff))!= -1){
            out.write(buff, 0, index);
            out.flush();
        }
        out.close();
        input.close();
        return null;
    }
posted @ 2020-04-21 01:49  范特西-  阅读(163)  评论(0)    收藏  举报