史上最实用的Springmvc技术教程

springmvc

简介

springmvc是一个web层mvc框架, 何谓MVC?

model 模型

view 视图

controller 控制器

这是一种设计模式,将责任进行拆分,不同的组件负责不同的事情。

好处

  • 结构清晰

  • 更好维护(大量使用jsp的年代,<%%>,显示)

坏处

  • 更加复杂了

 

目录(点击选项跳转)

springmvc

简介

入门体验

springmvc分析

/*不能写的原因

*.do的用法

/用法

关于前端控制器的解释

springmvc配置文件名字的问题

视图解析器

控制器的解释

注解开发模式

转发与重定向

关于springmvc访问web元素

注解详解

@RequestMapping

关于请求路径的问题

@GetMapping 、 @PostMaping.....

对于非get post请求的支持

@Responsebody

@ModelAttribute

@SessionAttribute

@SessionAttributes

@SessionAttribute

@RequestParam

@RequestBody

@InitBinder

@PathVariable

 

@RestController

关于静态资源访问的问题

关于post请求中文乱码问题解决

关于form表单提交数据的方式

方式一 通过属性名字绑定

方式二 利用@RequetParam

方式三 直接使用pojo形式传递

关于form表单提交日期格式数据问题的处理

 

json数据交互

额外依赖

JSON数据返回前台以及如何解析

JSON数据如何使用Ajax提交到后台,后台如何解析

关于Form提交数据与Ajax自定义JSON数据提交的区别

XML数据交互



入门体验

  1. 创建web项目(运行环境 IDEA)

  2. 编写web.xml,在其中注册一个特殊的servlet,前端控制器

  3. 编写一个springmvc的配置文件

    1. 注册一个视图解析器

  4. 编写要给控制器

  5. 编写一个结果页面

 

创建maven web项目结构如下:

 

史上最实用的Springmvc技术教程_第1张图片

 

史上最实用的Springmvc技术教程_第2张图片

 

pom.xml





  4.0.0

  com.sz
  springmvc01
  1.0-SNAPSHOT
  war

  springmvc01 Maven Webapp
  
  http://www.example.com

  
    UTF-8
    1.8
    1.8
  

  
   
    
      org.springframework
      spring-webmvc
      5.0.8.RELEASE
    

  

  
    springmvc01
    
      
        
          maven-clean-plugin
          3.0.0
        
        
        
          maven-resources-plugin
          3.0.2
        
        
          maven-compiler-plugin
          3.7.0
        
        
          maven-surefire-plugin
          2.20.1
        
        
          maven-war-plugin
          3.2.0
        
        
          maven-install-plugin
          2.5.2
        
        
          maven-deploy-plugin
          2.8.2
        
      
    
  

 

web.xml





    
  
    
    springmvc
    org.springframework.web.servlet.DispatcherServlet
  
  
  
  
    springmvc
    
    /
  

springmvc-servlet.xml



​
​
    
    
        
        
​
        
        
​
    
​
​
​
    
​
    

 

控制器代码

package com.sz.controller;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

// 实现一个Controller接口的方式
public class HelloController implements Controller {

    @Override
    public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        ModelAndView mav = new ModelAndView();
        mav.addObject("girl","菲菲");
        mav.setViewName("girl");
        return mav;
    }
}

 

视图代码

<%--
  Created by IntelliJ IDEA.
  User: Administrator
  Date: 2018/8/27
  Time: 9:09
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>


    Title


    我的脑海:${girl}


​

springmvc分析

流程图

史上最实用的Springmvc技术教程_第3张图片

组件分析

 

web.xml

注册前端控制器,目的在于,我们希望让springmvc去处理所有的请求,

通过



  springmvc
  
  /

确实是处理所有的请求(不是真的所有)

urlPattern的写法问题

  • /

  • /* (永远都不要这么写)

  • *.do

 

/*不能写的原因

请求 /helloController过去的时候,它的视图名称是girl,girl.jsp页面,它将其当作了一个叫girl.jsp的请求,尝试去匹配对应的controller,但是我的容器当中根本不存在这样的controller,所以无法匹配,导致404.

 

*.do的用法

这种方式,是有的团队习惯将请求的行为加个小尾巴用来区分,do,行为 *.action

 

/用法

处理所有的请求,但是和/*不一样,它处理完之后要出去的时候不会再去将这个girl.jsp当作一个新的请求,将这个渲染的结构直接返回给浏览器

 

 

关于前端控制器的解释

springmvc设计的理念是希望开发者尽量远离原生的servletAPI,API不是很好用,繁琐点,将操作进一步的简化,它将很多东西责任进行了拆分,不希望我们将一些技术点绑定死,可以做到随意的切换。本身还是基于Servlet设计的,分发的servlet.

 

springmvc配置文件名字的问题

默认情况下是用dispatcherServlet的名字当作命名空间

[servletName]-servlet.xml(WEB-INF)之下寻找。

[servletName]-servlet=namespace.

将配置文件移动位置之后,出现了下面的异常。

 Could not open ServletContext resource [/WEB-INF/springmvc-servlet.xml]

史上最实用的Springmvc技术教程_第4张图片

 

 

如果非要重新使用另外一个名字。

默认的规则要求在web-inf下,但是maven项目的标准应该在resources下面,如何解决这个问题呢?

重新制定上下文的配置文件的位置即可。


    
    contextConfigLocation
    classpath:springmvc.xml

此时是在类路径下寻找springmvc.xml这个配置文件,我推荐使用这种。

 

视图解析器

springmvc支持多种视图技术

  • jsp

  • freemaker(模板技术)

内部的资源视图解析器

  • 视图前缀

    • /jsp/ 它是我们的请求响应的资源的路径的配置, viewName : girl /jsp/girl

  • 后缀

    • .jsp 此时我们的前缀+视图名称+后缀= /jsp/girl.jsp

 

request.getDS.forward(request,response);的作用是一样一样的。

 

物理视图由逻辑视图转换而来

物理视图是 webapp/jsp/girl.jsp

 

逻辑视图

  • prefix

  • logicViewName

  • suffix

 

p View = prefix + logicViewName + suffix;

 

控制器的解释

是一种比较传统的实现一个接口的方式完成的,Controller。

如果要给接口只有一个方法,这种接口叫做函数式接口

@Nullable
ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;

他像谁?

servlet里面由doGet doPost 里面入参就是请求与响应。

void

 

设计为ModelAndView 。

在model当中填充数据,然后在具体的视图进行展示。

还需要在配置文件当中配置一下这个bean,要取个名字,就用来充当了这个请求URI。

它就处理一个请求,跟servlet差别不是很大。

注解开发模式

基于实现接口的方式已经是过去式了,采用注解开发很简单

基本注解

  • @Controller

  • @RequestMapping

 

 

开发步骤

  1. 记得配置一下基础扫描的包,这样配置的注解才会生效

  2. 在指定的类上面添加@Controller注解

  3. 添加@RequestMapping 类似于前面的controller的那个名字(不同requesthandler处理的 HandlerMapping)

 

当我们写上Controller之后,就标记了它为spring的一个组件,并且是控制器的组件,此时我们的handlermapping会去扫描寻找这个controller是否与之匹配,如果发现匹配就把这里处理的工作交给它。

 

匹配的规则的又是什么呢?

具体的匹配就是通过请求的路径进行匹配的

@RequestMappint(URI)

此时就是通过这个URI进行匹配

 

@RequestMapping

可以写在方法上

类上(推荐使用二者结合的方式)

转发与重定向

  • 转发到页面 默认到选项

  • 重定向到页面 redirect:path

  • 转发到另外一个控制器 forward:path

 

关于springmvc访问web元素

  • request

  • session

  • application

可以通过模拟的对象完成操作,也可以使用原生的ServletAPI完成,直接在方法当中入参即可。

 

注解详解

@RequestMapping

  • value 写的是路径,是一个数组的形式,可以匹配多个路径

  • path 是value的别名,所以二者任选其一,他们的作用是一样的

  • method 是可以指定可以访问的请求的类型,比如get post 它叶可以写成一个数组的形式。

  • params 可以去指定参数,你还可以去限定这个参数的特征,必须等于某个值,不等于某个值

  • headers 能够影响浏览器的行为

  • consumers 消费者,媒体类型,可以限定必须为application/json;charset=UTF-8

  • produces 产生的响应的类型

关于请求路径的问题

springmvc支持ant风格

  • ? 任意的字符,斜杠例外

  • * 0到n,任意个字符都可以 不能含斜杠

  • ** 支持任意层路径 /m3/** 这样才可以提现出来 /m3** 这样的效果等同于m3后面任意多个字符

 

@GetMapping 、 @PostMaping.....

  • getMapping 限定了只支持get请求

  • postMapping 限定了只支持post请求

 

对于非get post请求的支持

对于非get post请求的支持,需要有额外的内容添加,要增加一个过滤器来额外处理

  • 过滤器

  • 返回的不再是页面而是数据



    hiddenHttpMethodFilter
    org.springframework.web.filter.HiddenHttpMethodFilter


    hiddenHttpMethodFilter
    /*
  • 表单提交里面还要添加一个隐藏的参数

name="_method" value="DELETE"

 

 

@Responsebody

返回数据的,一般情况下返回JSON格式,

 

 

 

@ModelAttribute

使用方式一

// 就是在controller里面的任意一个处理具体的方法之前都会执行
@ModelAttribute
public User init(){
    System.out.println("init.......");
    User u = new User();
    u.setName("王菲");
    return  u;
}

@RequestMapping("/login")
public String login(Model model){
    System.out.println(model.containsAttribute("u"));
    System.out.println(model.containsAttribute("user"));
    System.out.println(model.containsAttribute("sdfdsfd"));
    return "msg";
}

如果某些对象从头到尾每次请求当中都要存在,不消失,就适合这么用。

使用方式二

   @ModelAttribute("user")
    public void init(Model model){
        System.out.println("init.......");
        User user = new User();
        user.setName("王菲");
        model.addAttribute("user",user);
    }

如果没有传递这个模型过来,那么方法上加了@modelattribute的为你提供,如果你传了就用你的

 

 

@SessionAttribute

 

@SessionAttributes

这个用在类上面,它会将模型自动填充到会话里面去。

@SessionAttribute

要求当前这次访问当中的会话里面必须要有某个对象

 

@RequestParam

 

@RequestBody

json数据,不是通过form表单传递

ajax({

data:

})

 

@InitBinder

数据转换,将日期转换处理

 

@PathVariable

restful风格

详情参考并在2000年定义罗伊菲尔丁在他的博士论文。

 

@RestController

Contrller + Response

 

关于静态资源访问的问题

由于我们的servlet设置了URL匹配方式为/ 所以,它将静态资源也当做一个后台的请求

比如http://localhost:8080/s/static/css/index.css

它尝试去匹配一个static/css/index.css的Controller里面的RequestMapping的组合,因为没有,所以404.

解决方式很多,最简单的,是让springmvc单独处理,将这些交给容器的默认的servlet处理,就不让DispatcherServlet来处理了。

解决方式1




 

MIME 类型

解决方式2

通过映射关系描述,一一对编写规则

解决方式3

自行在web.xml定义映射规则

 

关于post请求中文乱码问题解决

我们添加一个过滤器即可,springmvc提供了非常好的字符编码过滤器,所以我们注册即可。


    characterEncodingFilter
    org.springframework.web.filter.CharacterEncodingFilter
    
    
      encoding
      UTF-8
    
    
      forceRequestEncoding
      true
    
  
  
    characterEncodingFilter
    /*
  

 

 

关于form表单提交数据的方式

方式一 通过属性名字绑定

通过属性名称进行绑定,可以完成数据传递.

页面当中表单元素的name值要和后台的形参的名字保持一致。

如果有多个,多个形参按名字绑定即可,当传入的值较多的时候。

<%--
  Created by IntelliJ IDEA.
  User: Administrator
  Date: 2018/8/31
  Time: 8:31
  To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>


    Title


       
   
   
package com.sz.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.HashMap;
import java.util.Map;

@Controller
@RequestMapping("/user")
public class UserController {


    @PutMapping("/put")
    // 需要额外的json包的支持
    @ResponseBody
    public String  put(String name,String password){
        System.out.println(name+password);
//        Map map = new HashMap<>();
//        map.put("msg","ok");
        return "ok";
    }
    

}

 

方式二 利用@RequetParam

jsp页面不变

后台

@PutMapping("/put")
// 需要额外的json包的支持
@ResponseBody
public String  put(@RequestParam("name") String name, @RequestParam("password") String password){
    System.out.println(name+password);
    //        Map map = new HashMap<>();
    //        map.put("msg","ok");
    return "ok";
}

方式三 直接使用pojo形式传递

jsp不变

后台

@PutMapping("/put")
// 需要额外的json包的支持
@ResponseBody
public String  put(User user){
    System.out.println(user.getName() + user.getPassword());

    return "ok";
}

 

关于form表单提交日期格式数据问题的处理

 处理日期(没有时间)

package com.sz.controller;

import com.sz.pojo.User;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Controller
@RequestMapping("/user")
public class UserController {
//
    @InitBinder("user")
    public void init(WebDataBinder dataBinder){
        // 这里指定什么格式,前台就只能传什么格式
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        sdf.setLenient(false);
        dataBinder.registerCustomEditor(Date.class,new CustomDateEditor(sdf,false));
    }


    @PostMapping("/put")
    // 需要额外的json包的支持
    @ResponseBody
    public String  put(@ModelAttribute("user") User user){
        System.out.println(user.getName() + user.getPassword());
        System.out.println(user.getBirth());
        return "ok";
    }


}
// 通过initBinder指定了user名字和modelAttribute里面user绑定

不指定名字,根据数据类型一样可以分析解析转换成功

package com.sz.controller;

import com.sz.pojo.User;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Controller
@RequestMapping("/user")
public class UserController {
//
    @InitBinder
    public void init(WebDataBinder dataBinder){
        // 这里指定什么格式,前台就只能传什么格式
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        sdf.setLenient(false);
        dataBinder.registerCustomEditor(Date.class,new CustomDateEditor(sdf,false));
    }


    @PostMapping("/put")
    // 需要额外的json包的支持
    @ResponseBody
    public String  put(@ModelAttribute User user){
        System.out.println(user.getName() + user.getPassword());
        System.out.println(user.getBirth());
        return "ok";
    }


}

时间+日期

package com.sz.controller;

import com.sz.pojo.User;
import org.springframework.beans.propertyeditors.CustomDateEditor;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.*;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Controller
@RequestMapping("/user")
public class UserController {
//
    @InitBinder
    public void init(WebDataBinder dataBinder){
        // 这里指定什么格式,前台就只能传什么格式
//        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        sdf.setLenient(false);
        dataBinder.registerCustomEditor(Date.class,new CustomDateEditor(sdf,false));
    }


    @PostMapping("/put")
    // 需要额外的json包的支持
    @ResponseBody
    public String  put(@ModelAttribute User user){
        System.out.println(user.getName() + user.getPassword());
        System.out.println(user.getBirth());
        return "ok";
    }


}

在属性上面添加额外的注解


//    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date birth;

 

json数据交互

额外依赖


    com.fasterxml.jackson.core
    jackson-databind
    2.9.3



    com.fasterxml.jackson.core
    jackson-core
    2.9.3


    com.fasterxml.jackson.core
    jackson-annotations
    2.9.3



    net.sf.json-lib
    json-lib
    2.4
    jdk15





    org.codehaus.jackson
    jackson-core-asl
    1.9.2


    org.codehaus.jackson
    jackson-mapper-asl
    1.9.2

另外记得添加

 

JSON数据返回前台以及如何解析

JSON后台返回

 

返回POJO


    @RequestMapping("/m1")
    @ResponseBody // 这个注解将知道现在返回的不是视图,它会将这个数据转换为JSON格式。
    public User  m1(){
        User u  = new User();
        u.setPwd("321312");
        u.setName("许晴");
        return u;
    }

返回Map

  @RequestMapping("/m2")
    @ResponseBody
    public Map m2(){
        Map map = new HashMap<>();
        map.put("name","许晴请");
        map.put("age",28);
        return map;
    }

返回数组

@RequestMapping("/m3")
//    @ResponseBody
public User[] m3(){
    User u = new User();
    u.setName("开玩笑");
    u.setPwd("123");
    User u2 = new User();
    u2.setName("不开玩笑");
    u2.setPwd("321");
    return new User[]{u,u2};
}
 
  1. 返回List

@RequestMapping("/m4")
public List m4(){
    List l = new ArrayList<>();
    User u = new User();
    u.setName("开玩笑");
    u.setPwd("123");
    User u2 = new User();
    u2.setName("不开玩笑");
    u2.setPwd("321");
    l.add(u);
    l.add(u2);
    return l;
}

JSON前台如何解析

  1. 解析返回的POJO

$('#b1').click(function () {
    $.ajax({
        url:'${ctx}/json/m1',
        type:'post',
        success:function (data) {
            alert(data.name);
            alert(data.pwd);
        }
    })
})
  1. 解析返回的Map


$('#b2').click(function () {
    $.ajax({
        url:'${ctx}/json/m2',
        type:'post',
        success:function (data) {
            alert(data.name);
            alert(data.age);
        }
    })
})
  1. 解析返回的数组

$('#b3').click(function () {
    $.ajax({
        url:'${ctx}/json/m3',
        type:'post',
        success:function (data) {
            for(var i = 0 ; i < data.length; i ++){
                alert(data[i].name);
                alert(data[i].pwd);
            }
        }
    })
})
  1. 解析返回的List

$('#b4').click(function () {
    $.ajax({
        url:'${ctx}/json/m4',
        type:'post',
        success:function (data) {
            for(var i = 0 ; i < data.length; i ++){
                alert(data[i].name);
                alert(data[i].pwd);
            }
        }
    })
})
  1. 返回List>

$('#b5').click(function () {
            $.ajax({
                url:'${ctx}/json/m5',
                type:'post',
                success:function (data) {
                    for(var i = 0 ; i < data.length; i ++){
                        alert(data[i].u1.name);
                        alert(data[i].u1.pwd);
                        alert(data[i].u2.name);
                        alert(data[i].u2.pwd);
                    }
                }
            })
        })

 

JSON数据如何使用Ajax提交到后台,后台如何解析

什么是 AJAX ?

AJAX = 异步 JavaScript 和 XML。

AJAX 是一种用于创建快速动态网页的技术。

通过在后台与服务器进行少量数据交换,AJAX 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。

传统的网页(不使用 AJAX)如果需要更新内容,必需重载整个网页面。

$.AJAX()基本参数

 url       默认值: 当前页地址。发送请求的地址。

 contentType 主要设置你发送给服务器的格式,dataType设置你收到服务器数据的格式。

 success   请求成功后的回调函数。

 type         默认值: "GET")。请求方式 ("POST" 或 "GET"), 默认为 "GET"。注意:其它 HTTP 请求方法,如 PUT 和 DELETE 也可以使用,但仅部分浏览器支持。

data     发送到服务器的数据。将自动转换为请求字符串格式。GET 请求中将附加在 URL 后。禁止此自动转换。必须为 Key/Value 格式。如果为数组,jQuery 将自动为不同值对应同一个名称。如 {foo:["bar1", "bar2"]} 转换为 '&foo=bar1&foo=bar2'。

 

ajax 的复杂JSON数据,用 JSON.stringify序列化后,然后发送,在服务器端接到然后用 JSON.parse 进行还原就行了,这样就能处理复杂的对象了。

contentType:"application/json;charset=utf-8"

1 前台写法

$('#b1').click(function () {
    //
    var obj = {
        'name':'叶问',
        'pwd':'怕老婆'
    };
​
    $.ajax({
        url:'${ctx}/json2/add',
        type:'post',
        contentType:'application/json',
        data:JSON.stringify(obj),
        success:function (data) {
​
        }
    })
})

2 后台的写法

@RequestMapping("/add")
// User user入参只能处理表单提交的数据
public String add(@RequestBody  User user){
    System.out.println(user.getName() +user.getPwd());
    return "msg";
}
一定要记得添加@requestBody否则无法解析。

关于Form提交数据与Ajax自定义JSON数据提交的区别

form提交方式请求分析截图:

史上最实用的Springmvc技术教程_第5张图片

 

 

通过ajax组装的json数据发送分析

 

史上最实用的Springmvc技术教程_第6张图片

所以二者发送组装数据的区域存在不同,所以处理方式也不同。

对于form表单提交数据它的contentType是属于Content-Type: application/x-www-form-urlencoded

对于ajax发送JSON则是 application/json.

 

  1. 发送一个POJO

前台

 
$('#b1').click(function () {
    //
    var obj = {
        'name':'叶问',
        'pwd':'怕老婆'
    };
​
    $.ajax({
        url:'${ctx}/json2/add',
        type:'post',
        contentType:'application/json',
        data:JSON.stringify(obj),
        success:function (data) {
​
        }
    })
})

 

后台

// 前台如何提交一个User对象过来
@RequestMapping("/add")
// User user入参只能处理表单提交的数据
@ResponseBody
public String add(@RequestBody User user  ){
    System.out.println(user.getName() +user.getPwd());
    return "msg";
}
​
  1. 发送一组POJO到后台

前台

$('#b2').click(function () {
    //
    var obj = {
        'name':'叶问',
        'pwd':'怕老婆'
    };
    var obj2 ={
        'name':'未知名的小帅哥',
        'pwd':'123123'
    };
    var arr = new Array();
    arr.push(obj);
    arr.push(obj2);
​
​
    $.ajax({
        url:'${ctx}/json2/addList',
        type:'post',
        contentType:'application/json',
        data:JSON.stringify(arr),
        success:function (data) {
            if(data.code == 2000){
                alert("陈工啦");
            }
        }
    })
})
​

 

后台

​
@RequestMapping("/addList")
// User user入参只能处理表单提交的数据
@ResponseBody
public Map addList(@RequestBody List list  ){
    // ma
    Map map = new HashMap<>();
    System.out.println(list);
    map.put("code",2000);
    return map;
}
​

 

XML数据交互

对于很多第三方开发,还是由很多会采用XML作为数据交互,比如微信

1 添加依赖



    com.fasterxml.jackson.dataformat
    jackson-dataformat-xml
    2.9.3

 

2 方法返回数据类型定义

// 描述生产的类型,返回类型的描述,返回什么数据
@RequestMapping(value = "/m1",produces = {MediaType.APPLICATION_XML_VALUE} )
@ResponseBody
public User m1(){
    // 将数据转换为XML形式user
    User u = new User();
    u.setName("张福新");
    u.setPwd("好好学习,记得看书");
    return u;
}

 

持续更新中。。

你可能感兴趣的:(ssm)