一、JSON

  • 二、RESTful
  • 三、示例下载
  • 四、视频
  • 五、作业
  • 六、工具下载
  •  很多时候前端都需要调用后台服务实现交互功能,常见的数据交换格式多是JSON或XML,这里主要讲解Spring MVC为前端提供JSON格式的数据并实现与前台交互。RESTful则是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

    一、JSON

    1.1、概要

    JSON(JavaScript Object Notation, JS 对象标记) 是一种轻量级的数据交换格式。它基于 ECMAScript (w3c制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。

    在 JS 语言中,一切都是对象。因此,任何支持的类型都可以通过 JSON 来表示,例如字符串、数字、对象、数组等。但是对象和数组是比较特殊且常用的两种类型。

    要实现从对象转换为 JSON 字符串,使用 JSON.stringify() 方法:
    var json = JSON.stringify({a: 'Hello', b: 'World'}); //结果是 '{"a": "Hello", "b": "World"}'
    要实现从 JSON 转换为对象,使用 JSON.parse() 方法:
    var obj = JSON.parse('{"a": "Hello", "b": "World"}'); //结果是 {a: 'Hello', b: 'World'}

    示例:

    <!DOCTYPE html>
    <html>
    
        <head>
            <meta charset="UTF-8">
            <title></title>
    <!--[if lte IE 8]>
        <script type="text/javascript" src="//res.wx.qq.com/a/wx_fed/webwx/res/json3.min.js"></script>
        <![endif]-->
        </head>
    
        <body>
            <script type="text/javascript">
                //js对象
                var user = {
                    "name": "张学友",
                    "address": "中国香港"
                };
                //将对象转换成字符
                var str = JSON.stringify(user);
                alert(str);
                //将字符串转换成json对象
                var zxy = JSON.parse(str);
                alert(zxy.name + "," + zxy.address);
            </script>
        </body>
    
    </html>
    View Code

    结果:

    1.2、使用ModelAndView

    修改pom.xml添加对jackson的依赖

            <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
                <version>2.9.2</version>
            </dependency>

    在user控制器中添加一个action

        @RequestMapping(value = "/users")
        public ModelAndView users(){
            ModelAndView mav=new ModelAndView(new MappingJackson2JsonView());
            mav.addObject(userService.queryAllUsers());
            return mav;
        }

    运行结果:

    1.3、使用@ResponseBody与Jackson

    修改pom.xml添加对jackson的依赖

            <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
            <dependency>
                <groupId>com.fasterxml.jackson.core</groupId>
                <artifactId>jackson-databind</artifactId>
                <version>2.9.2</version>
            </dependency>

    添加一个action,使用注解@ResponseBody,响应主体而不是路径

        @RequestMapping(value = "/userJson",produces = "application/json;charset=utf-8")
        @ResponseBody
        public String userJson(){
            ObjectMapper mapper=new ObjectMapper();
            try {
              return  mapper.writeValueAsString(userService.queryAllUsers());
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
            return null;
        }

    结果:

    1.4、乱码问题

    1.4.1、方法一在action上声明编码格式

    @RequestMapping(path="/json",produces = "application/json;charset=UTF-8")

    1.4.2、方法二修改Spring配置文件

    上一种方法比较麻烦,如果项目中有许多action则每一个都要添加,可以通过Spring配置统一指定

    <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>

    1.5、日期格式化问题

    默认日期格式会变成一个数字,是1970年1月1日到当前日期的毫秒数:

    Jackson 默认是转成timestamps形式

    1.5.1、方法一注解字段

    在实体字段上使用@JsonFormat注解格式化日期

    @JsonFormat(locale="zh", timezone="GMT+8", pattern="yyyy-MM-dd HH:mm:ss")

    代码:

        /**
         * 出生日期
         */
        @JsonFormat(locale="zh", timezone="GMT+8", pattern="yyyy-MM-dd HH:mm:ss")
        private Date birthday;

    结果:

    1.5.2、方法二取消timestamps形式

    如果只取消则会得到一个默认的日期格式,效果如下:

    当然自定义输出格式是允许的

        @RequestMapping(value = "/userJson",produces = "application/json;charset=utf-8")
        @ResponseBody
        public String userJson(){
            ObjectMapper mapper=new ObjectMapper();
            //不使用时间差的方式
            mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
    
            //自定义日期格式对象
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            //指定日期格式
            mapper.setDateFormat(sdf);
            try {
              return  mapper.writeValueAsString(userService.queryAllUsers());
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
            return null;
        }

    运行结果:

    1.6、工具类

    工具类可以复用代码,提高开发效率,如上文中的序列化JSON:

    package com.zhangguo.springmvc08.utils;
    
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import com.fasterxml.jackson.databind.SerializationFeature;
    
    import java.text.SimpleDateFormat;
    
    /**
     * JSON工具类,辅助类
     * */
    public class JsonUtil {
        public static String getJson(Object object) {
            return getJson(object,"yyyy-MM-dd HH:mm:ss");
        }
    
        public static String getJson(Object object,String dateFormat) {
            ObjectMapper mapper = new ObjectMapper();
            //不使用时间差的方式
            mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
            //自定义日期格式对象
            SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
            //指定日期格式
            mapper.setDateFormat(sdf);
            try {
                return mapper.writeValueAsString(object);
            } catch (JsonProcessingException e) {
                e.printStackTrace();
            }
            return null;
        }
    }

    调用:

        @RequestMapping(value = "/userJson",produces = "application/json;charset=utf-8")
        @ResponseBody
        public String userJson(){
            return JsonUtil.getJson(userService.queryAllUsers(),"yyyy-MM-dd");
        }

    如对MySQL数据库的访问封装:

    package com.zhangguo.springmvc08.utils; 
    
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.ResultSet;
    import java.sql.ResultSetMetaData;
    import java.sql.SQLException;
    import java.sql.Statement;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    public class JDBCUtil {
    
        public static String DRIVER="com.mysql.jdbc.Driver";
        public static String URL="jdbc:mysql://127.0.0.1:3306/schoolmis?useUnicode=true&characterEncoding=UTF-8";
        public static String USER_NAME="root";
        public static String PASSWORD="pwd";
    
        //加载驱动
        static {
            try {
                Class.forName(DRIVER);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    
        private JDBCUtil() {
    
        }
    
        /**
         * 获得连接
         *
         * @return
         */
        public static Connection getconnnection() {
            Connection con = null;
            try {
                con = DriverManager.getConnection(URL, USER_NAME, PASSWORD);
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return con;
        }
    
        /**
         * 关闭连接
         *
         * @param rs
         * @param st
         * @param con
         */
        public static void close(ResultSet rs, Statement st, Connection con) {
            try {
                try {
                    if (rs != null) {
                        rs.close();
                    }
                } finally {
                    try {
                        if (st != null) {
                            st.close();
                        }
                    } finally {
                        if (con != null)
                            con.close();
                    }
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 关闭连接
         *
         * @param rs
         */
        public static void close(ResultSet rs) {
            Statement st = null;
            Connection con = null;
            try {
                try {
                    if (rs != null) {
                        st = rs.getStatement();
                        rs.close();
                    }
                } finally {
                    try {
                        if (st != null) {
                            con = st.getConnection();
                            st.close();
                        }
                    } finally {
                        if (con != null) {
                            con.close();
                        }
                    }
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 关闭连接
         *
         * @param st
         * @param con
         */
        public static void close(Statement st, Connection con) {
            try {
                try {
                    if (st != null) {
                        st.close();
                    }
                } finally {
                    if (con != null)
                        con.close();
                }
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * insert/update/delete
         * 增加/更新/删除
         *
         * @param sql 数据库语句
         * @param args 可变参数(可以不带参数,可以带0-n个参数)
         * @return
         */
        public static int update(String sql, Object... args) {
            int result = 0;
            Connection con = getconnnection();
            PreparedStatement ps = null;
            try {
                ps = con.prepareStatement(sql);
                if (args != null) {
                    for (int i = 0; i < args.length; i++) {
                        ps.setObject((i + 1), args[i]);
                    }
                }
                result = ps.executeUpdate();
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                close(ps, con);
            }
    
            return result;
        }
    
        /**
         * query, because need to manually close the resource, so not recommended
         * for use it
         *
         * @param sql
         * @param args
         * @return ResultSet
         */
        @Deprecated  //注解
        public static ResultSet query(String sql, Object... args) {
            ResultSet result = null;
            Connection con = getconnnection();
            PreparedStatement ps = null;
            try {
                ps = con.prepareStatement(sql);
                if (args != null) {
                    for (int i = 0; i < args.length; i++) {
                        ps.setObject((i + 1), args[i]);
                    }
                }
                result = ps.executeQuery();
            } catch (SQLException e) {
                e.printStackTrace();
            }
            return result;
        }
    
        /**
         * Query a single record
         * 查询单个记录
         * @param sql
         * @param args
         * @return Map<String,Object>
         */
        public static Map<String, Object> queryForMap(String sql, Object... args) {
            Map<String, Object> result = new HashMap<String, Object>();
            List<Map<String, Object>> list = queryForList(sql, args);
            if (list.size() > 0) {
                result = list.get(0);
            }
            return result;
        }
    
        /**
         * Query a single record
         * 查询单个记录返回强类型对象
         * @param sql
         * @param args
         * @return <T>  //泛型
         */
        public static <T> T queryForObject(String sql, Class<T> clz, Object... args) {
            T result = null;
            List<T> list = queryForList(sql, clz, args);
            if (list.size() > 0) {
                result = list.get(0);
            }
            return result;
        }
    
        /**
         * Query a single record
         *
         * @param sql
         * @param args
         * @return List<Map<String,Object>>
         */
        public static List<Map<String, Object>> queryForList(String sql, Object... args) {
            List<Map<String, Object>> result = new ArrayList<Map<String, Object>>();
            Connection con = null;
            ResultSet rs = null;
            PreparedStatement ps = null;
            try {
                con = getconnnection();
                ps = con.prepareStatement(sql);
                if (args != null) {
                    for (int i = 0; i < args.length; i++) {
                        ps.setObject((i + 1), args[i]);
                    }
                }
                rs = ps.executeQuery();
                ResultSetMetaData rsmd = rs.getMetaData();
                int columnCount = rsmd.getColumnCount();
                while (rs.next()) {
                    Map<String, Object> map = new HashMap<String, Object>();
                    for (int i = 1; i <= columnCount; i++) {
                        map.put(rsmd.getColumnLabel(i), rs.getObject(i));
                    }
                    result.add(map);
                }
            } catch (SQLException e) {
                e.printStackTrace();
            } finally {
                close(rs, ps, con);
            }
            return result;
        }
    
        /**
         * Query records
         * 查询多个对象,返回强类型集合
         * @param sql
         * @param args
         * @return List<T>
         */
        public static <T> List<T> queryForList(String sql, Class<T> clz, Object... args) {
            List<T> result = new ArrayList<T>();
            Connection con = null;
            PreparedStatement ps = null;
            ResultSet rs = null;
            try {
                con = getconnnection();
                ps = con.prepareStatement(sql);
                if (args != null) {
                    for (int i = 0; i < args.length; i++) {
                        ps.setObject((i + 1), args[i]);
                    }
                }
                rs = ps.executeQuery();
                ResultSetMetaData rsmd = rs.getMetaData();
                int columnCount = rsmd.getColumnCount();
                while (rs.next()) {
                    T obj = clz.newInstance();
                    for (int i = 1; i <= columnCount; i++) {
                        String columnName = rsmd.getColumnName(i);
                        String methodName = "set" + columnName.substring(0, 1).toUpperCase()
                                + columnName.substring(1, columnName.length());
                        Method method[] = clz.getMethods();
                        for (Method meth : method) {
                            if (methodName.equals(meth.getName())) {
                                meth.invoke(obj, rs.getObject(i));
                            }
                        }
                    }
                    result.add(obj);
                }
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (SQLException e) {
                e.printStackTrace();
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            } finally {
                close(rs, ps, con);
            }
            return result;
        }
    }
    View Code

    1.7、数据持久化

    上一章的示例中并没有直接访问数据库,数据以集合的形式存放在内存中,这里使用MySQL将数据存储到数据库中。该示例基于第8章的示例,请先熟悉第8章的内容《Spring MVC 学习总结(八)——Spring MVC概要与环境配置(IDEA+Maven+Tomcat7+JDK8、示例与视频)》

    1.7.1、创建数据库与表

    开启MySQL服务

    打开管理工具Navicat

    创建数据库

    新建表

    /*
    Navicat MySQL Data Transfer
    
    Source Server         : localhostMe
    Source Server Version : 50506
    Source Host           : localhost:3306
    Source Database       : mvcdb
    
    Target Server Type    : MYSQL
    Target Server Version : 50506
    File Encoding         : 65001
    
    Date: 2017-12-07 14:08:35
    */
    
    SET FOREIGN_KEY_CHECKS=0;
    
    -- ----------------------------
    -- Table structure for `user`
    -- ----------------------------
    DROP TABLE IF EXISTS `user`;
    CREATE TABLE `user` (
      `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号',
      `name` varchar(32) NOT NULL COMMENT '姓名',
      `birthday` datetime DEFAULT NULL COMMENT '生日',
      `address` varchar(128) DEFAULT NULL COMMENT '地址',
      `phone` varchar(11) DEFAULT NULL COMMENT '电话',
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    
    -- ----------------------------
    -- Records of user
    -- ----------------------------

    1.7.2、添加测试数据

     

    insert into user(name,birthday,address,phone)
    select '张学友','1968-09-08','中国香港','18989890098' union
    select '张惠妹','1969-01-05','中国北京','13345678781' union
    select '张国立',SYSDATE(),'中国珠海','13567453422'
    
    select id,name,birthday,address,phone from user;

     

    1.7.3、添加数据库驱动修改连接信息

    在pom.xml中依赖MySQL驱动包

            <!--mysql驱动包-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.35</version>
            </dependency>

    修改工具类的中数据库连接信息

        public static String DRIVER="com.mysql.jdbc.Driver";
        public static String URL="jdbc:mysql://127.0.0.1:3306/mvcdb?useUnicode=true&characterEncoding=UTF-8";
        public static String USER_NAME="root";
        public static String PASSWORD="pwd";

    1.7.4、新增UserDAOPro

     新增UserDAOPro类,实现MySQL数据库访问,代码如下:

    package com.zhangguo.springmvc08.dao;
    
    import com.zhangguo.springmvc08.entity.User;
    import com.zhangguo.springmvc08.utils.JDBCUtil;
    import org.springframework.stereotype.Repository;
    
    import java.util.List;
    
    @Repository("mysql")
    public class UserDAOPro implements IUserDAO {
        public List<User> getAll() {
            return JDBCUtil.queryForList("select id,name,birthday,address,phone from user", User.class);
        }
    
        public User getUserById(int id) {
            return JDBCUtil.queryForObject("select id,name,birthday,address,phone from user where id=?", User.class, id);
        }
    
        public boolean add(User user) {
            return JDBCUtil.update("insert into user(name,birthday,address,phone) values(?,?,?,?)", user.getName(), user.getBirthday(), user.getAddress(), user.getPhone()) > 0;
        }
    
        public boolean delete(int id) {
            return JDBCUtil.update("delete from user where id=?", id) > 0;
        }
    
        public boolean update(User user) {
            return JDBCUtil.update("update user set name=?,birthday=?,address=?,phone=? where id=?", user.getName(), user.getBirthday(), user.getAddress(), user.getPhone(), user.getId()) > 0;
        }
    }

    1.7.5、修改用户业务类

    因为系统中有两个类实现了IUserDAO,指定名称:

    package com.zhangguo.springmvc08.service;
    
    import com.zhangguo.springmvc08.dao.IUserDAO;
    import com.zhangguo.springmvc08.dao.UserDAO;
    import com.zhangguo.springmvc08.entity.User;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    import java.util.List;
    
    /**用户业务*/
    @Service
    public class UserService {
    
        @Resource(name="mysql")
        IUserDAO userdao;
    
        public List<User> queryAllUsers(){
            return userdao.getAll();
        }
    
        public User getUserById(int id){
            return userdao.getUserById(id);
        }
    
        public boolean deleteUser(int id){
            return userdao.delete(id);
        }
    
        public  boolean addUser(User user){
            return userdao.add(user);
        }
    
        public boolean editUser(User user){
            return userdao.update(user);
        }
    
    }

    1.7.6、彻底解决Spring MVC 中文乱码

    添加用户后发现有乱码,调试发现发送到服务器的数据已经是乱码

    1、页面编码

    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>

    2、URL中的乱码

    改tomcat中server.xml中Connector的port=“8080”,加上一个 URIEncoding=”utf-8”

    3、配置过滤器,指定所有请求的编码

    修改web.xml,添加编码过滤器

        <filter>
            <filter-name>encodingFilter</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>true</param-value>
            </init-param>
        </filter>
        <filter-mapping>
            <filter-name>encodingFilter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>

    4、文件编码

    将文件另存为utf-8格式

    5、数据库编码

    连接字符串指定编码格式

    public static String URL="jdbc:mysql://127.0.0.1:3306/mvcdb?useUnicode=true&characterEncoding=UTF-8"

    创建数据库时指定utf-8编码格式

    最终运行结果正常:

    二、RESTful

    2.1、概要

    REST(英文:Representational State Transfer,简称REST,表述性状态转移)描述了一个架构样式的网络系统,比如 web 应用程序。它首次出现在 2000 年 Roy Fielding 的博士论文中,他是 HTTP 规范的主要编写者之一。在目前主流的三种Web服务交互方案中,REST相比于SOAP(Simple Object Access protocol,简单对象访问协议)以及XML-RPC更加简单明了,无论是对URL的处理还是对Payload的编码,REST都倾向于用更加简单轻量的方法设计和实现。值得注意的是REST并没有一个明确的标准,而更像是一种设计的风格。

    RESTful架构,就是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。

    GET /tickets # 获取ticket列表
    GET /tickets/12 # 查看某个具体的ticket
    POST /tickets # 新建一个ticket
    PUT /tickets/12 # 更新ticket 12.
    DELETE /tickets/12 #删除ticekt 12

    REST特点如下:

    • 基于HTTP协议
    • 是另一种服务架构
    • 传递是JSON、POX(Plain Old XML)而不是SOAP格式的数据
    • 充分利用HTTP谓词(Verb)
    • 侧重数据的传输,业务逻辑交给客户端自行处理

    REST是一种分布式服务架构的风格约束,像Java、.Net(WCF、WebAPI)都有对该约束的实现,使URL变得更加有意义,更加简洁明了,如:

    http://www.zhangguo.com/products/1 get请求 表示获得所有产品的第1个

    http://www.zhangguo.com/products/product post请求 表示添加一个产品

    http://www.zhangguo.com/products/1/price get请求 表示获得第1个产品的价格

    http://www.zhangguo.com/products/1 delete请求 删除编号为1的产品

    REST设计需要遵循的原则:

    • 网络上的所有事物都被抽象为资源(resource);
    • 每个资源对应一个唯一的资源标识符(resource identifier);
    • 通过通用的连接器接口(generic connector interface)对资源进行操作;
    • 对资源的各种操作不会改变资源标识符;
    • 所有的操作都是无状态的(stateless)

    谓词
    GET
    表示查询操作,相当于Retrieve、Select操作
    POST
    表示插入操作,相当于Create,Insert操作
    PUT
    表示修改操作,相当于Update操作
    DELETE
    表示删除操作,相当于Delete操作

    其它还有:

    2.2、@RestController

    Spring 4.0重要的一个新的改进是@RestController注解,它继承自@Controller注解。4.0之前的版本,Spring MVC的组件都使用@Controller来标识当前类是一个控制器servlet。

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Controller
    @ResponseBody
    public @interface RestController {
        String value() default "";
    }

    使用这个注解,我们可以开发REST服务的时候不需要使用@Controller而专门的@RestController。

    当你实现一个RESTful web services的时候,response将一直通过response body发送。为了简化开发,Spring 4.0提供了一个专门版本的controller。

    添加了AsyncRestTemplate类,当开发REST客户端时允许非阻塞异步支持。

    2.2.1、Hello World

    默认控制器与Action

    package com.zhangguo.springmvc08.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller  //声明为控制器
    @RequestMapping(path = {"/","Home","First"})  //请求映射
    public class HomeController {
        @RequestMapping(path = "/index")  //请求映射
        public String index(Model model){
            model.addAttribute("message","Hello Spring MVC!");
            return "home/index";
        }
    
        @RequestMapping(path = "/")  //请求映射
        public String first(Model model){
            model.addAttribute("message","Hello Spring MVC,Welcome Page!");
            return "home/index";
        }
    }

    修改pom.xml添加jackson的引用

    新增一个控制器,代码如下:

    package com.zhangguo.springmvc08.controller;
    
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    @RequestMapping(path="/users")
    public class UsersController {
    
        @RequestMapping(path = "/{name}",method = RequestMethod.GET)
        public String hello(@PathVariable String name){
            return "Hello "+name;
        }
    
        @RequestMapping(path = "/stu/{name}",method = RequestMethod.GET)
        public Student student(@PathVariable String name){
            return new Student("Hello "+name);
        }
    
    }
    
    /**学生*/
    class Student{
        public Student(String name) {
            this.name = name;
        }
    
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    }

    运行结果:

    从上面的示例可以看出,使用@RestController后返回的字符串不再是路径,如果返回的是对象则会直接序列化,可以是JSON或XML;如果返回的是对象类型则直接序列化成JSON格式,请注意添加对Jackson的依赖。

    2.3、RESTful员工管理示例

    假定要为员工(emp)提供对外的REST服务,接口如下:

    /emps  get 获得所有的员工信息

    /emps/1 get 获得编号为1的员工信息

    /emps post 添加

    /emps put 修改

    /emps/1 delete 删除

    2.3.1、获得所有的员工信息服务

    /emps  get 获得所所有的员工信息

    package com.zhangguo.springmvc08.controller;
    
    import com.zhangguo.springmvc08.entity.User;
    import com.zhangguo.springmvc08.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RestController;
    
    import java.util.List;
    
    @RestController
    @RequestMapping(path = "/emps")
    public class EmpController {
    
        @Autowired
        UserService userService;
    
        @RequestMapping(path = "", method = RequestMethod.GET)
        public List<User> getAllemps() {
            return userService.queryAllUsers();
        }
    
    }

     结果:

    2.3.2、获得指定编号的员工信息服务

    /emps/1 get 获得编号为1的员工信息

    代码:

        @RequestMapping(path = "/{id}", method = RequestMethod.GET)
        public User getEmpById(@PathVariable int id) {
            return userService.getUserById(id);
        }

    结果:

    2.3.3、新增员工服务

    /emps post 添加

    代码:

        @RequestMapping(path = "", method = RequestMethod.POST)
        public boolean addEmp(@RequestBody User user) {
            return userService.addUser(user);
        }

    请求:

    返回true

    结果:

    说明:参数中的json格式一定要使用标准格式,注意引号,注意Content-Type,默认的Content-Type类型是:application/x-www-form-urlencoded

    因为我们使用json,则Content-Type的值应该为application/json;charset=utf-8

    2.3.4、修改员工服务

    /emps  修改 put请求

    代码:

        @RequestMapping(path = "", method = RequestMethod.PUT)
        public boolean updateEmp(@RequestBody User user) {
            return userService.editUser(user);
        }

    测试:

    结果:

    2.3.3、删除员工服务

    /emps/1 delete 删除

    代码:

    package com.zhangguo.springmvc08.controller;
    
    import com.zhangguo.springmvc08.entity.User;
    import com.zhangguo.springmvc08.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;
    
    import java.util.List;
    
    @RestController
    @RequestMapping(path = "/emps")
    public class EmpController {
    
        @Autowired
        UserService userService;
    
        @RequestMapping(path = "", method = RequestMethod.GET)
        public List<User> getAllEmps() {
            return userService.queryAllUsers();
        }
    
        @RequestMapping(path = "/{id}", method = RequestMethod.GET)
        public User getEmpById(@PathVariable int id) {
            return userService.getUserById(id);
        }
    
        @RequestMapping(path = "", method = RequestMethod.POST)
        public boolean addEmp(@RequestBody User user) {
            return userService.addUser(user);
        }
    
        @RequestMapping(path = "", method = RequestMethod.PUT)
        public boolean updateEmp(@RequestBody User user) {
            return userService.editUser(user);
        }
    
        @RequestMapping(path = "/{id}", method = RequestMethod.DELETE)
        public AjaxState deleteEmpById(@PathVariable int id) {
            Boolean result=userService.deleteUser(id);
            return new AjaxState(result?"success":"error",id,result?"删除成功!":"删除失败");
        }
    
    }
    
    class  AjaxState{
        public String state;
        public Object data;
        public String message;
    
        public AjaxState(String state, Object data, String message) {
            this.state = state;
            this.data = data;
            this.message = message;
        }
    
        public AjaxState(){}
    }

    测试:

    结果:

    已删除成功,delete请求不需要正文与get请求类似

    2.4、AJAX客户端调用RESTful

    ajax传送json格式数据,关键是指定contentType,data要是json格式

    如果是restful接口,把type改成对应的post(增)、delete(删)、put(改)、get(查)即可

    var post_data={"name":"test001","pass":"xxxx"}; 
    $.ajax({ 
    url: "http://192.168.10.111:8080/uc/login", 
    type: 'post', 
    contentType: "application/json; charset=utf-8", 
    data:JSON.stringify(post_data), 
    success:function (data) { 
    //调用成功 
    }, 
    error: function(data, textStatus, errorThrown){ 
    //调用失败 
    } 
    });

    为了前端统一调用,修改后的控制器如下:

    package com.zhangguo.springmvc08.controller;
    
    import com.zhangguo.springmvc08.entity.User;
    import com.zhangguo.springmvc08.service.UserService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;
    
    import java.util.List;
    
    @RestController
    @RequestMapping(path = "/emps")
    public class EmpController extends BaseController {
    
        @Autowired
        UserService userService;
    
        @RequestMapping(path = "", method = RequestMethod.GET)
        public AjaxState getAllEmps() {
            List<User> users=userService.queryAllUsers();
            boolean result=users!=null;
            return new AjaxState(result?"success":"error",users,result?"获得数据成功!":"获得数据失败!");
        }
    
        @RequestMapping(path = "/{id}", method = RequestMethod.GET)
        public AjaxState getEmpById(@PathVariable int id) {
            User user=userService.getUserById(id);
            boolean result=user!=null;
            return new AjaxState(result?"success":"error",user,result?"获得数据成功!":"获得数据失败!");
        }
    
        @RequestMapping(path = "", method = RequestMethod.POST)
        public AjaxState addEmp(@RequestBody User user) {
            boolean result=userService.addUser(user);
            return new AjaxState(result?"success":"error",user,result?"添加成功!":"添加失败");
        }
    
        @RequestMapping(path = "", method = RequestMethod.PUT)
        public AjaxState updateEmp(@RequestBody User user) {
            boolean result=userService.editUser(user);
            return new AjaxState(result?"success":"error",user,result?"修改成功!":"修改失败");
        }
    
        @RequestMapping(path = "/{id}", method = RequestMethod.DELETE)
        public AjaxState deleteEmpById(@PathVariable int id) {
            Boolean result=userService.deleteUser(id);
            return new AjaxState(result?"success":"error",id,result?"删除成功!":"删除失败");
        }
    
    }
    
    class  AjaxState{
        public String state;
        public Object data;
        public String message;
    
        public AjaxState(String state, Object data, String message) {
            this.state = state;
            this.data = data;
            this.message = message;
        }
    
        public AjaxState(){}
    }

    2.4.1、用户列表

    示例:

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8"/>
        <title>Title</title>
    </head>
    <body>
    <h2>员工管理</h2>
    <table border="1" width="100%" id="tabEmps">
        <tr>
            <th>编号</th>
            <th>姓名</th>
            <th>生日</th>
            <th>地址</th>
            <th>电话</th>
            <th>操作</th>
        </tr>
    </table>
    <p>
    
    </p>
    <p class="loading" style="display: none;">
        <img src="img/loading.gif" align="absmiddle">努力加载中...
    </p>
    <p class="message">
    
    </p>
    <script src="js/jquery-1.11.3.min.js"></script>
    <script>
    //    var data = {
    //        "state": "success",
    //        "data": {"id": 1, "name": "张学友", "birthday": -41500800000, "address": "中国香港", "phone": "18989890098"},
    //        "message": "获得数据成功!"
    //    }
        var app = {
            url: "http://localhost:8080/mvc08/emps",
            init:function(){
                this.binddata();
            },
            ajax: function (actionType, callback, path, data) {
                $.ajax({
                    url: app.url + (path||""),
                    contentType: "application/json;charset=utf-8",
                    data: data || {},
                    type: actionType||"get",
                    dataType: "json",
                    success: function (data) {
                        if(data&&data.state=="success"){
                            app.info(data.message);
                        }else if(data&&data.state=="error"){
                            app.info(data.message);
                        }else{
                            app.info(data);
                        }
                        if(callback){
                            callback(data);
                        }
                    },
                    error: function (XMLHttpRequest, textStatus, errorThrown) {
                        info(textStatus+errorThrown);
                    },
                    beforeSend: function () {
                        $(".loading").show(200);
                    }
                    ,
                    complete: function () {
                        $(".loading").hide(200);
                    }
                })
                ;
            },
            binddata: function () {
                this.ajax("get",function(data){
                    $.each(data.data,function(index,emp){
                        var tr=$("<tr/>").appendTo("#tabEmps");
                        $("<td/>").text(emp.id).appendTo(tr);
                        $("<td/>").text(emp.name).appendTo(tr);
                        $("<td/>").text(emp.birthday).appendTo(tr);
                        $("<td/>").text(emp.address).appendTo(tr);
                        $("<td/>").text(emp.phone).appendTo(tr);
                        $("<td/>").html("<a>删除</a>").appendTo(tr);
                    });
                });
            },
            info:function(msg){
                $(".message")[0].innerHTML+=msg+"<br/>";
            }
        };
    
        app.init();
    </script>
    </body>
    </html>

    结果:

    2.4.2、新增用户

    示例:

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8"/>
        <title>Title</title>
    </head>
    <body>
    <h2>员工管理</h2>
    <table border="1" width="100%" id="tabEmps">
        <tr>
            <th>编号</th>
            <th>姓名</th>
            <th>生日</th>
            <th>地址</th>
            <th>电话</th>
            <th>操作</th>
        </tr>
    </table>
    <p class="loading" style="display: none;">
        <img src="img/loading.gif" align="absmiddle">努力加载中...
    </p>
    <form id="formEmps">
        <fieldset>
            <legend>用户信息</legend>
            <p>
                <label for="name">姓名:</label>
                <input name="name" id="name" type="text" required="required" maxlength="32"/>
            </p>
            <p>
                <label for="birthday">生日:</label>
                <input name="birthday" id="birthday" type="date" required="required" maxlength="8"/>
            </p>
            <p>
                <label for="address">地址:</label>
                <input name="address" id="address" type="text" required="required" maxlength="128"/>
            </p>
            <p>
                <label for="phone">电话:</label>
                <input name="phone" id="phone" type="text" required="required" maxlength="11"/>
            </p>
            <p>
                <input id="id" type="hidden" name="id" value=""/>
                <button type="button" id="btnSubmit">保存</button>
            </p>
        </fieldset>
    </form>
    <p class="message">
    </p>
    <script src="js/jquery-1.11.3.min.js"></script>
    <script>
        //    var data = {
        //        "state": "success",
        //        "data": {"id": 1, "name": "张学友", "birthday": -41500800000, "address": "中国香港", "phone": "18989890098"},
        //        "message": "获得数据成功!"
        //    }
        var app = {
            url: "http://localhost:8080/mvc08/emps",
            init: function () {
                $("#btnSubmit").click(app.save);
                this.binddata();
            },
            ajax: function (actionType, callback, path, data) {
                $.ajax({
                    url: app.url + (path || ""),
                    contentType: "application/json;charset=utf-8",
                    data:JSON.stringify(data)||"{}",
                    type: actionType || "get",
                    dataType: "json",
                    success: function (data) {
                        if (data && data.state == "success") {
                            app.info(data.message);
                        } else if (data && data.state == "error") {
                            app.info(data.message);
                        } else {
                            app.info(data);
                        }
                        if (callback) {
                            callback(data);
                        }
                    },
                    error: function (XMLHttpRequest, textStatus, errorThrown) {
                        app.info(textStatus + errorThrown);
                    },
                    beforeSend: function () {
                        $(".loading").show(200);
                    }
                    ,
                    complete: function () {
                        $(".loading").hide(200);
                    }
                })
                ;
            },
            binddata: function () {
                $("#tabEmps tr:gt(0)").remove();
                this.ajax("get", function (data) {
                    $.each(data.data, function (index, emp) {
                        var tr = $("<tr/>").appendTo("#tabEmps");
                        $("<td/>").text(emp.id).appendTo(tr);
                        $("<td/>").text(emp.name).appendTo(tr);
                        $("<td/>").text(emp.birthday).appendTo(tr);
                        $("<td/>").text(emp.address).appendTo(tr);
                        $("<td/>").text(emp.phone).appendTo(tr);
                        $("<td/>").html("<a>删除</a>").appendTo(tr);
                    });
                });
            },
            getEmp:function(){
                return {
                    "id":$("#id").val(),
                    "name":$("#name").val(),
                    "birthday":$("#birthday").val(),
                    "address":$("#address").val(),
                    "phone":$("#phone").val()
                };
            },
            save:function(){
                var emp=app.getEmp();
                if(emp.id){
                    app.update(emp);
                }else{
                    app.add(emp);
                }
            },
            add:function(emp){
                app.ajax("POST",function (data) {
                    app.binddata();
                },"",emp);
            },
            update:function(emp){
                app.ajax("Put",function (data) {
                    app.binddata();
                },"",emp);
            },
            info: function (msg) {
                $(".message")[0].innerHTML += msg + "<br/>";
            }
        };
    
        app.init();
    </script>
    </body>
    </html>

    结果:

    2.4.3、删除用户

    示例:

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8"/>
        <title>Title</title>
    </head>
    <body>
    <h2>员工管理</h2>
    <table border="1" width="100%" id="tabEmps">
        <tr>
            <th>编号</th>
            <th>姓名</th>
            <th>生日</th>
            <th>地址</th>
            <th>电话</th>
            <th>操作</th>
        </tr>
    </table>
    <p class="loading" style="display: none;">
        <img src="img/loading.gif" align="absmiddle">努力加载中...
    </p>
    <form id="formEmps">
        <fieldset>
            <legend>用户信息</legend>
            <p>
                <label for="name">姓名:</label>
                <input name="name" id="name" type="text" required="required" maxlength="32"/>
            </p>
            <p>
                <label for="birthday">生日:</label>
                <input name="birthday" id="birthday" type="date" required="required" maxlength="8"/>
            </p>
            <p>
                <label for="address">地址:</label>
                <input name="address" id="address" type="text" required="required" maxlength="128"/>
            </p>
            <p>
                <label for="phone">电话:</label>
                <input name="phone" id="phone" type="text" required="required" maxlength="11"/>
            </p>
            <p>
                <input id="id" type="hidden" name="id" value=""/>
                <button type="button" id="btnSubmit">保存</button>
            </p>
        </fieldset>
    </form>
    <p class="message">
    </p>
    <script src="js/jquery-1.11.3.min.js"></script>
    <script>
        //    var data = {
        //        "state": "success",
        //        "data": {"id": 1, "name": "张学友", "birthday": -41500800000, "address": "中国香港", "phone": "18989890098"},
        //        "message": "获得数据成功!"
        //    }
        var app = {
            url: "http://localhost:8080/mvc08/emps",
            init: function () {
                $("#btnSubmit").click(app.save);
                $("#tabEmps").on("click", ".del", app.delete);
                this.binddata();
            },
            ajax: function (actionType, callback, path, data) {
                $.ajax({
                    url: app.url + (path || ""),
                    contentType: "application/json;charset=utf-8",
                    data: JSON.stringify(data) || "{}",
                    type: actionType || "get",
                    dataType: "json",
                    success: function (data) {
                        if (data && data.state == "success") {
                            app.info(data.message);
                        } else if (data && data.state == "error") {
                            app.info(data.message);
                        } else {
                            app.info(data);
                        }
                        if (callback) {
                            callback(data);
                        }
                    },
                    error: function (XMLHttpRequest, textStatus, errorThrown) {
                        app.info(textStatus + errorThrown);
                    },
                    beforeSend: function () {
                        $(".loading").show(200);
                    }
                    ,
                    complete: function () {
                        $(".loading").hide(200);
                    }
                })
                ;
            },
            binddata: function () {
                $("#tabEmps tr:gt(0)").remove();
                this.ajax("get", function (data) {
                    $.each(data.data, function (index, emp) {
                        var tr = $("<tr/>").data("emp", emp).appendTo("#tabEmps");
                        $("<td/>").text(emp.id).appendTo(tr);
                        $("<td/>").text(emp.name).appendTo(tr);
                        $("<td/>").text(emp.birthday).appendTo(tr);
                        $("<td/>").text(emp.address).appendTo(tr);
                        $("<td/>").text(emp.phone).appendTo(tr);
                        $("<td/>").html("<a class='del' href='#'>删除</a>").appendTo(tr);
                    });
                });
            },
            getEmp: function () {
                return {
                    "id": $("#id").val(),
                    "name": $("#name").val(),
                    "birthday": $("#birthday").val(),
                    "address": $("#address").val(),
                    "phone": $("#phone").val()
                };
            },
            save: function () {
                var emp = app.getEmp();
                if (emp.id) {
                    app.update(emp);
                } else {
                    app.add(emp);
                }
            },
            add: function (emp) {
                app.ajax("POST", function (data) {
                    app.binddata();
                }, "", emp);
            },
            update: function (emp) {
                app.ajax("Put", function (data) {
                    app.binddata();
                }, "", emp);
            },
            delete: function () {
                if (confirm("删除吗?")) {
                    var tr = $(this).closest("tr");
                    var emp = tr.data("emp");
                    app.ajax("DELETE", function (data) {
                        tr.remove();
                    }, "/" + emp.id);
                }
            },
            info: function (msg) {
                $(".message")[0].innerHTML += msg + "<br/>";
            }
        };
    
        app.init();
    </script>
    </body>
    </html>

    结果:

    2.4.4、更新数据

    示例:

    <!DOCTYPE html>
    <html>
    <head>
        <meta charset="UTF-8"/>
        <title>Title</title>
    </head>
    <body>
    <h2>员工管理</h2>
    <table border="1" width="100%" id="tabEmps">
        <tr>
            <th>编号</th>
            <th>姓名</th>
            <th>生日</th>
            <th>地址</th>
            <th>电话</th>
            <th>操作</th>
        </tr>
    </table>
    <p class="loading" style="display: none;">
        <img src="img/loading.gif" align="absmiddle">努力加载中...
    </p>
    <form id="formEmps">
        <fieldset>
            <legend>用户信息</legend>
            <p>
                <label for="name">姓名:</label>
                <input name="name" id="name" type="text" required="required" maxlength="32"/>
            </p>
            <p>
                <label for="birthday">生日:</label>
                <input name="birthday" id="birthday" type="date" required="required" maxlength="8"/>
            </p>
            <p>
                <label for="address">地址:</label>
                <input name="address" id="address" type="text" required="required" maxlength="128"/>
            </p>
            <p>
                <label for="phone">电话:</label>
                <input name="phone" id="phone" type="text" required="required" maxlength="11"/>
            </p>
            <p>
                <input id="id" type="hidden" name="id" value=""/>
                <button type="button" id="btnSubmit">保存</button>
            </p>
        </fieldset>
    </form>
    <p class="message">
    </p>
    <script src="js/jquery-1.11.3.min.js"></script>
    <script>
        //    var data = {
        //        "state": "success",
        //        "data": {"id": 1, "name": "张学友", "birthday": -41500800000, "address": "中国香港", "phone": "18989890098"},
        //        "message": "获得数据成功!"
        //    }
        var app = {
            url: "http://localhost:8080/mvc08/emps",
            init: function () {
                $("#btnSubmit").click(app.save);
                $("#tabEmps").on("click", ".del", app.delete);
                $("#tabEmps").on("click", ".edit", app.edit);
                this.binddata();
            },
            ajax: function (actionType, callback, path, data) {
                $.ajax({
                    url: app.url + (path || ""),
                    contentType: "application/json;charset=utf-8",
                    data: JSON.stringify(data) || "{}",
                    type: actionType || "get",
                    dataType: "json",
                    success: function (data) {
                        if (data && data.state == "success") {
                            app.info(data.message);
                        } else if (data && data.state == "error") {
                            app.info(data.message);
                        } else {
                            app.info(data);
                        }
                        if (callback) {
                            callback(data);
                        }
                    },
                    error: function (XMLHttpRequest, textStatus, errorThrown) {
                        app.info(textStatus + errorThrown);
                    },
                    beforeSend: function () {
                        $(".loading").show(200);
                    }
                    ,
                    complete: function () {
                        $(".loading").hide(200);
                    }
                })
                ;
            },
            binddata: function () {
                $("#tabEmps tr:gt(0)").remove();
                this.ajax("get", function (data) {
                    $.each(data.data, function (index, emp) {
                        var tr = $("<tr/>").data("emp", emp).appendTo("#tabEmps");
                        $("<td/>").text(emp.id).appendTo(tr);
                        $("<td/>").text(emp.name).appendTo(tr);
                        $("<td/>").text(emp.birthday).appendTo(tr);
                        $("<td/>").text(emp.address).appendTo(tr);
                        $("<td/>").text(emp.phone).appendTo(tr);
                        $("<td/>").html("<a class='del' href='#'>删除</a> | <a class='edit' href='#'>编辑</a>").appendTo(tr);
                    });
                });
            },
            getEmp: function () {
                return {
                    "id": $("#id").val(),
                    "name": $("#name").val(),
                    "birthday": $("#birthday").val(),
                    "address": $("#address").val(),
                    "phone": $("#phone").val()
                };
            },
            save: function () {
                var emp = app.getEmp();
                if (emp.id) {
                    $("#id").val("");
                    app.update(emp);
                } else {
                    app.add(emp);
                }
            },
            add: function (emp) {
                app.ajax("POST", function (data) {
                    app.binddata();
                }, "", emp);
            },
            update: function (emp) {
                app.ajax("Put", function (data) {
                    app.binddata();
                }, "", emp);
            },
            delete: function () {
                if (confirm("删除吗?")) {
                    var tr = $(this).closest("tr");
                    var emp = tr.data("emp");
                    app.ajax("DELETE", function (data) {
                        tr.remove();
                    }, "/" + emp.id);
                }
            },
            edit:function(){
                var emp = $(this).closest("tr").data("emp");
                $("#id").val(emp.id);
                $("#name").val(emp.name);
                $("#birthday").val(emp.birthday);
                $("#address").val(emp.address);
                $("#phone").val(emp.phone);
            },
            info: function (msg) {
                $(".message")[0].innerHTML += msg + "<br/>";
            }
        };
    
        app.init();
    </script>
    </body>
    </html>

    结果:

    三、示例下载

    https://zhangguo5.coding.net/public/SpringMVC08/SpringMVC08/git

    四、视频

    https://www.bilibili.com/video/av16991874/

    五、作业

    5.1、请练习上课示例

    5.2、请使用Spring MVC对外提供商品(Product)的管理接口,如:

    product/list 获得所有商品 get

    product/1 获得编号为1的商品 get

    product/delete/1 删除编号为1的商品 get

    product/insert 新增商品 post

    product/edit 编辑商品 post

    使用AJAX调用发布的服务,实现如下功能,验证、搜索、多删除功能选作。

    5.3、请完成一个前后台分离的汽车管理系统(CarSystem),需要管理汽车的(车牌、颜色、价格、出厂日期、排量),要求完成CRUD功能,界面美观大方。

    a)、请使用MySQL数据库创建库与表(CarSystem)

    b)、使用Spring MVC定义5个RESTful服务,注意路径格式,先用fiddler测试通过。

    c)、定义car.html页面,使用jQuery插件中的ajax功能消费RESTful服务实现功能,反复测试。

    六、工具下载

    Fiddler2(汉化版) 链接: https://pan.baidu.com/s/1mhNTg1M 密码: qiib

    点赞(0)

    评论列表 共有 0 条评论

    暂无评论
    立即
    投稿
    发表
    评论
    返回
    顶部