【JavaEE】Spring Web MVC

打印 上一主题 下一主题

主题 1353|帖子 1353|积分 4059

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
目录

一、Spring Web MVC
1.1 Spring Web MVC 的界说
1.2 Servlet
1.3 MVC
1.3.1 MVC 的界说
1.3.2 Spring MVC 
1.3.3 Spring MVC 和 SpringBoot 的关系
二、常见的 Spring Web MVC 注解
2.1 @RequestMapping
2.2 @RequestMapping 的请求类型
2.3 @GetMapping

2.4 @PostMapping
三、传递参数
3.1 传递单个参数
3.2 传递多个参数

3.3 传递对象
3.4 后端参数重命名
3.5 传递数组
3.5.1 方式一
3.5.1 方式二
3.6 传递聚集
3.7 传递 JSON 数据

3.8 获取URL中参数@PathVariable
3.9 上传文件 @RequestPart
3.10 获取Cookie/Session
3.10.1 Cookie 和 Session 的区别
3.10.2 传统获取Cookie
3.10.3 简便获取 Cookie
3.10.4 获取Session
3.10.4.1 通过 HttpServletRequest 读取 Session
3.10.4.2 通过 HttpSession 读取 Session
3.10.4.3 通过注解 @SessionAttribute("") 来获取指定 Key 的 Session
3.11 获取Header
3.12 怎样返回静态页面
3.13 返回 JSON
3.14 设置状态码
3.15 设置Header
3.15.1 设置Content-Type
3.15.2 设置其他Header
3.16 约定前后端交互接口


四、应用分层


一、Spring Web MVC

1.1 Spring Web MVC 的界说

Spring Web MVC 基于 Servlet API 构建的原始 Web 框架, 从一开始就包罗在 Spring 框架中。它的正式名称 “Spring Web MVC” 来自其源模块的名称(Spring-webmvc), 但它常被称为"Spring MVC"。
   从上述界说我们可以得出一个信息, Spring Web MVC 是一个 Web 框架。
  
1.2 Servlet

Servlet 是一种实现动态页面的技能, 正确的来讲 Servlet 是一套 Java Web 开发的规范, 或者说是一套 Java Web 开发的技能标准。

1.3 MVC

1.3.1 MVC 的界说

MVC 是 Model View Controller 的缩写, 它是软件工程中的一种软件架构设计模式, 它把软件系统分为模子、视图和控制器三个基本部分。

   (1) View(视图) 指在应用程序中专门用来与浏览器进行交互, 展示数据的资源。
  (2) Model(模子) 是指应用程序的主体部分, 用来处置惩罚程序中数据逻辑的部分。
  (3) Controller(控制器) 用来链接视图和模子, 决定对于视图发送来的请求, 必要用哪一个模子来处置惩罚, 以及处置惩罚完之后必要跳回哪一个视图。
  
1.3.2 Spring MVC 

MVC 是一种架构设计模式, Spring MVC 是对 MVC 头脑的具体实现, 除此之外, Spring MVC 照旧一个 Web 框架。

   总结来说, Spring MVC 是一个实现了 MVC 模式的 Web 框架。
  1.3.3 Spring MVC 和 SpringBoot 的关系

在我们创建 SpringBoot 项目标时间, 勾选的 Spring Web 框架实在就是 Spring MVC 框架。

   SpringBoot 是实现 Spring MVC 的此中一种方式, SpringBoot 可以添加很多依赖, 借助这些依赖来实现不同的功能, SpringBoot 通过添加 Spring Web MVC 框架来实现web功能。
  二、常见的 Spring Web MVC 注解

2.1 @RequestMapping

@RequestMapping 既可以修饰类, 也可以修饰方法, 当修饰类和方法的时间, 访问的地点是类路径+方法路径。在 Spring MVC 中使用 @RequestMapping 来实现 URL 路由映射, 作用就是浏览器连接程序。创建一个 UserController 类, 实现用户通过浏览器和程序的交互, 具体代码如下: 
后端代码实现: 
  1. package com.example.demo.demos.web;
  2. import org.springframework.web.bind.annotation.RequestMapping;
  3. import org.springframework.web.bind.annotation.RestController;
  4. @RestController
  5. @RequestMapping("/user")
  6. public class UserController {
  7.     @RequestMapping("/sayHello")
  8.     public String sayHello(){
  9.         return "sayHello";
  10.     }
  11. }
复制代码

@RequestMapping 是 Spring Web MVC 应用程序中最常被调用的注解之一, 它是用来注册接口的路由映射的。
   路由映射: 当用户访问一个 URL 时, 将用户的请求对应到程序中某个类某个方法的过程就叫做路由映射。
  2.2 @RequestMapping 的请求类型

对于 @RequestMapping 来说, 即支持 GET 请求, 又支持 POST 请求。我们可以通过代码来指定 @RequestMapping 担当的请求类型, 下方代码就是指定 GET 请求, 我们尝试用 POST 请求访问失败。
后端代码实现: 
  1. package com.example.demo.demos.web;
  2. import org.springframework.web.bind.annotation.RequestMapping;
  3. import org.springframework.web.bind.annotation.RequestMethod;
  4. import org.springframework.web.bind.annotation.RestController;
  5. @RestController
  6. @RequestMapping("/user")
  7. public class UserController {
  8.     @RequestMapping(value = "/sayHello",method = RequestMethod.GET)
  9.     public String sayHello(){
  10.         return "sayHello";
  11.     }
  12. }
复制代码


2.3 @GetMapping

只支持 GET 请求
后端代码实现: 
  1. @RestController
  2. @RequestMapping("/user")
  3. public class UserController {
  4.     @GetMapping(value = "/getMapping")
  5.     public String getMapping(){
  6.         return "GetMapping";
  7.     }
  8. }
复制代码



2.4 @PostMapping

只支持 POST 请求
后端代码实现: 
  1. @RestController
  2. @RequestMapping("/user")
  3. public class UserController {
  4.     @PostMapping(value = "/postMapping")
  5.     public String postMapping(){
  6.         return "PostMapping";
  7.     }
  8. }
复制代码

三、传递参数

3.1 传递单个参数

担当单个参数, 在 Spring MVC 中直接用方法中的参数就可以, 比如以下代码。
后端代码实现: 
  1. package com.example.demo.demos.web;
  2. import org.springframework.web.bind.annotation.RequestMapping;
  3. import org.springframework.web.bind.annotation.RestController;
  4. @RestController
  5. @RequestMapping("/param")
  6. public class ParamController {
  7.     @RequestMapping("/m1")
  8.     public String method1(String name){
  9.         return "name : " + name;
  10.     }
  11. }
复制代码

   使用基本类型来担当参数时,  参数必须传(除boolean类型), 否则会报500错误, 类型不匹配时, 会报400错误。
  3.2 传递多个参数

和担当单个参数一样, 直接使用方法的参数接收即可, 使用多个形参。
后端代码实现: 
  1. package com.example.demo.demos.web;
  2. import org.springframework.web.bind.annotation.RequestMapping;
  3. import org.springframework.web.bind.annotation.RestController;
  4. @RestController
  5. @RequestMapping("/param")
  6. public class ParamController {
  7.     @RequestMapping("/m2")
  8.     public String method2(String name,Integer age){
  9.         return "name : " + name + ",age : " + age;
  10.     }
  11. }
复制代码



3.3 传递对象

如果参数比较多的时间, 方法声明就必要又很多形参, 而且后续每次更新一个参数, 也必要修改方法生命, 我们可以吧这些参数封装为一个对象, 当返回的是一个对象的时间, 自动会转为 JSON 格式。Spring 会根据参数名称自动绑定到对象的各个属性上, 如果某个属性没有传递, 则赋值为null(基本类型则为默认初始值)
后端代码实现: 
  1. package com.example.demo.demos.web;
  2. import org.springframework.web.bind.annotation.RequestMapping;
  3. import org.springframework.web.bind.annotation.RestController;
  4. @RestController
  5. @RequestMapping("/user")
  6. public class UserController {
  7.     @RequestMapping("/m1")
  8.     public UserInfo getUser(UserInfo user ){
  9.         return user;
  10.     }
  11. }
复制代码


3.4 后端参数重命名

前段传递的参数 key 和我们后端担当的 key 可以不同等, 我们可以使用@RequestParam 来改变前段传输的数据名称。比如前段传输的 key 名称为 “name”, 我们可以改为 “createName” 字段来接收, 而且我们可以使用 @RequestParam 中的 required = true 来设置这个参数为必传参数。
后端代码实现: 
  1. package com.example.demo.demos.web;
  2. import org.springframework.web.bind.annotation.*;
  3. @RestController
  4. @RequestMapping("/test")
  5. public class TestController {
  6.   
  7.     @RequestMapping("/m1")
  8.     public String getMapping2(@RequestParam(value = "name",required = true) String createName){
  9.         return "createName: " + createName;
  10.     }
  11. }
复制代码


   1. 使用 @RequestParam 进行参数重命名时, 请求参数只能和 @RequestParam 声明的名称同等, 才气进行参数绑定和赋值。
  2. 使用 @RequestParam 进行参数重名时, 参数就变成了必传参数。
  
3.5 传递数组

后端代码实现: 
  1. package com.example.demo.demos.web;
  2. import org.springframework.web.bind.annotation.*;
  3. import java.util.Arrays;
  4. @RestController
  5. @RequestMapping("/test")
  6. public class TestController {
  7.    
  8.     @RequestMapping("/m2")
  9.     public String method5(String[] arrayParam) {
  10.         return Arrays.toString(arrayParam);
  11.     }
  12. }
复制代码
3.5.1 方式一

每次传输的 key 相同, 后端接收到会自动转化成数组。

3.5.1 方式二

只传递一次 key , value 中的值使用 “,” 分割, 后端接收到会自动转化为数组。


3.6 传递聚集

聚集参数: 和数组类似, 同一个请求参数名有多个, 且必要使用 @RequestParam 绑定参数关系
后端代码实现: 
  1. import org.springframework.web.bind.annotation.*;
  2. import java.util.Arrays;
  3. import java.util.List;
  4. @RestController
  5. @RequestMapping("/test")
  6. public class TestController {
  7.     @RequestMapping("/m3")
  8.     public String method6(@RequestParam List<String> listParam){
  9.         return "size:"+listParam.size() + ",listParam:"+listParam;
  10.     }
  11. }
复制代码


3.7 传递 JSON 数据

JSON(JavaScript Object Notation) 是一种轻量级的数据交互格式, 它基于 ECMAScrip 的一个子集, 采用完全独立于编程语言的文本格式来储存和表现数据。
JSON 语法:
1. 数据在 键值对(Key/Value) 中, 键和值用 “:” 分割。
2. 数据由逗号 “,” 分割
3. 对象用 {} 标识
4. 数组用 [] 标识
5. 值可以为对象, 也可以为数组, 数组中可以包罗多个对象。
  1. public class JSON {
  2.     private static ObjectMapper objectMapper = new ObjectMapper();
  3.     public static void main(String[] args) throws JsonProcessingException {
  4.         UserInfo person = new UserInfo();
  5.         person.setAge(5);
  6.         person.setName("zhangsan");
  7.         person.setGender("man");
  8.         //对象转为JSON字符串
  9.         String jsonStr = objectMapper.writeValueAsString(person);
  10.         System.out.println("JSON字符串为:"+jsonStr);
  11.         //JSON字符串转为对象
  12.         UserInfo p = objectMapper.readValue(jsonStr,UserInfo.class);
  13.         System.out.println("转换的对象 age:"+p.getAge()+",name:"+p.getName()+",gender:"+p.getGender());
  14.     }
  15. }
复制代码



3.8 获取URL中参数@PathVariable

path variable : 路径变量
后端代码实现: 
  1. package com.example.demo.demos.web;
  2. import org.springframework.web.bind.annotation.*;
  3. @RestController
  4. @RequestMapping("/test")
  5. public class TestController {
  6.    
  7.     @RequestMapping("/m4/{id}/{name}")
  8.     public String method8(@PathVariable Integer id, @PathVariable("name") String
  9.             userName){
  10.         return "解析参数id:"+id+",name:"+userName;
  11.     }
  12. }
复制代码

   如果方法参数名称和必要绑定的URL中的变量名同等时, 可以简写, 不用给 @PathVariable 的属性赋值, 如上方例子中的 id 变量。
  如果方法参数名称和必要绑定的URL中的变量名称不同等时, 必要 @PathVariable 的属性 value 赋值, 如上方例子中的 userName 变量。
  
3.9 上传文件 @RequestPart

后端代码实现: 
  1. @RestController
  2. @RequestMapping("/test")
  3. public class TestController {
  4.    
  5.     @RequestMapping("/m6")
  6.     public String method9(@RequestPart MultipartFile file) throws IOException {
  7.         String fileName = file.getOriginalFilename();
  8.         file.transferTo(new File("C:\\Users\\LEGION\\Desktop\"+fileName));
  9.         return "接收到文件名称为: " + fileName;
  10.     }
  11. }
复制代码

3.10 获取Cookie/Session

HTTP 协议自身属于“无状态”协议,默认情况下 HTTP 协议的客户端和服务器之间的这次通讯, 和下一次通讯之间没有直接的联系。但我们实际开发中, 很多时间是必要知道请求之间的关联关系的。

上述图中的 “令牌” 通常就存储在 Cookie 字段中, 此时服务器这边就必要记载 “令牌” 对应的用户信息, 这个就是 Session 机制做的工作。换句话来说, Cookie 是客户端这边记载信息的工具, Session 是服务器这边记载信息的工具。Session 是服务器为了保存用户信息而创建的一个特殊对象。
   Session 本质是一个 “哈希表”, 存储一些键值对结构。Key 就是 SessionID , Value 就是用户信息。
  当用户登录的时间, 服务器在 Session 中新增一个新记载, 而且把 sessionId 返回给客户端(通过HTTP 相应中的 Set-Cookie 字段返回)
客户端后续给服务器发送请求的时间, 必要带上 sessionId (通过 HTTP 请求中的 Cookie 字段带上),服务器接收到请求之后, g根据请求中的 sessionId 在 Session 信息中获取相对应的用户信息, 再进行后续操作, 找不到则重新创建 Session , 并返回 SessionID。 
   Session 默认是保存在内存中的, 如果重启服务器 Session 数据就会丢失
  


3.10.1 Cookie 和 Session 的区别

1. Cookie 是客户端保存用户信息的一种机制, Session 是服务器端保存用户信息的一种机制。
2. Cookie 和 Session 之间重要是通过 sessionId 关联起来的, sessionId 是 Cookie 和 Session 之间的桥梁。
3. Cookie 和 Session 经常会在一起配合使用, 但是不是必须配合
        <1>完全可以用 Cookie 来保存一些数据在客户端, 这些数据不一定是用户身份信息, 也不一定是SessionId
        <2>Session 中的 sessionId 也不必要非得通过 Cookie/Set-Coolie 传递, 可以通过URL传递。

3.10.2 传统获取Cookie

通过使用 Servlet 提供的 HttpServletRequest 类来获取 Cookie; 当客户端通过HTTP协议访问服务器的时间, HTTP 请求中的所有信息就封装在 HttpServletRequest 这个类的对象中, 通过这个对象提供的方法, 可以获得客户端请求的所有信息。
  1. @RestController
  2. @RequestMapping("/test")
  3. public class TestController {
  4.    
  5.     @RequestMapping("/m7")
  6.     public String method10(HttpServletRequest request) throws IOException {
  7.         Cookie[] cookies = request.getCookies();
  8.         StringBuilder sb = new StringBuilder();
  9.         if (cookies!=null){
  10.             for (Cookie cookie : cookies) {
  11.                 sb.append(cookie.getName()).append("=").append(cookie.getValue()).append(";");
  12.             }
  13.         }
  14.         return sb.toString();
  15.     }
  16. }
复制代码

   可以看到 Cookie 是可以伪造的, 也就不是安全的, 以是后端必要进行 Cookie 校验, 上方展示的是通过 HttpServletRequest 来获取多个 Cookie 。
  
3.10.3 简便获取 Cookie

我们可以通过注解 @CookieValue("") 来单独获取一个指定 Key 的 Cookie
  1. @RestController
  2. @RequestMapping("/test")
  3. public class TestController {
  4.    
  5.     @RequestMapping("/m8")
  6.     public String method11(@CookieValue("name") Integer name) throws IOException {
  7.         return "" + name;
  8.     }
  9. }
复制代码


3.10.4 获取Session

由于 Session 是服务器的机制, 以是我们必要先储存到本地, 才气获取, 下方展示怎样储存Session
  1. @RestController
  2. @RequestMapping("/test")
  3. public class TestController {
  4.    
  5.     @RequestMapping("/setSession")
  6.     public String method12(HttpServletRequest request) throws IOException {
  7.         HttpSession session = request.getSession();
  8.         if(session!=null){
  9.             session.setAttribute("userName","java");
  10.         }
  11.         assert session != null;
  12.         return "设置成功 session: " + session.getAttribute("userName");
  13.     }
  14. }
复制代码
两种获取 Session 的方式
  1. HttpSession getSession(boolean create);
  2. HttpSession getSession();
复制代码
当不携带参数或者参数为 true 时, 则当不存在会话时新建会话, 参数如果为 false , 则当不存在会话时返回 null。
   这个代码中看不见 SessionId 如许的概念, getSession 操作内部提取到请求中 Cookie 里的SessionId , 然后根据 SessionId 获取到对应的 Session 对象, Session 对象使用 HttpSession 来描述。
  

3.10.4.1 通过 HttpServletRequest 读取 Session

  1.     @RequestMapping("/getSession")
  2.     public String getSession(HttpServletRequest request) {
  3.         HttpSession session = request.getSession();
  4.         if(session!=null){
  5.             return (String) session.getAttribute("userName");
  6.         }
  7.         return null;
  8.     }
复制代码
  Http 相应中, 通过 Set-Cookie 告知客户端, 并把 SessionID 储存在 Cookie 中。
  
3.10.4.2 通过 HttpSession 读取 Session

  1.     @RequestMapping("/getSession2")
  2.     public String getSession2(HttpSession session) {
  3.         if(session!=null){
  4.             return (String) session.getAttribute("userName");
  5.         }
  6.         return null;
  7.     }
复制代码
  Session 不存在的话, 会自动进行创建,把 sessionID 通过Set-Cookie 返回给客户端 ,只能拿到Session。
  
3.10.4.3 通过注解 @SessionAttribute("") 来获取指定 Key 的 Session

  1.     @RequestMapping("/getSession3")
  2.     public String getSession3(@SessionAttribute(value = "userName",required = true) String username) {
  3.         return "username:"+username;
  4.     }
复制代码

3.11 获取Header

传统方式获取 Header, 也是从 HttpServletRequest 中获取
  1.     @RequestMapping("/getHeader")
  2.     public String getHeader(HttpServletRequest request) {
  3.         String userAgent = request.getHeader("User-Agent");
  4.         return "userAgent: " + userAgent;
  5.     }
复制代码

简介方法获取 Header , 通过@RequestHeader 来获取指定的 Header
  1. @RequestMapping("/header")
  2.     public String header(@RequestHeader("User-Agent") String userAgent) {
  3.         return "userAgent:"+userAgent;
  4. }
复制代码

3.12 怎样返回静态页面

html代码如下: 
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <meta charset="UTF-8">
  5.     <title>Index⻚⾯</title>
  6. </head>
  7. <body>
  8.     Hello,Spring MVC,我是Index⻚⾯.
  9. </body>
  10. </html>
复制代码
当我们访问http://127.0.0.1:8080/index时会返回

我们必要把类中的 @RestController 改为 @Controller , 正确代码如下: 
  1. @Controller
  2. public class IndexController {
  3.     @RequestMapping("/index")
  4.     public Object index(){
  5.         return "/index.html";
  6.     }
  7. }
复制代码
再次运行http://127.0.0.1:8080/index 结果如下: 

@RestController = @Controller + @ResponseBody
   @Controller: 界说一个控制器, Spring 框架启动时加载, 把这个对象交给 Spring 管理。
  @ResponseBody: 界说返回的数据格式为非视图, 返回一个 text/html 信息。即是类注解, 又是方法注解, 如果作用在类上, 就表明该类所有的方法都返回的是数据, 如果作用在方法上, 表现该方法返回的是数据。
  如果想返回视图的话, 只必要把 @ResponseBody 去掉就可以了, 也就是留 @Controller。
  
3.13 返回 JSON

返回 HashMap 类的会自动转成 JSON 格式
  1. @RequestMapping("/returnJson")
  2. @ResponseBody
  3. public HashMap<String, String> returnJson() {
  4. HashMap<String, String> map = new HashMap<>();
  5. map.put("Java", "Java Value");
  6. map.put("MySQL", "MySQL Value");
  7. map.put("Redis", "Redis Value");
  8. return map;
  9. }
复制代码
  当返回类对象的时或者用 @RequestBody 修饰之后都会返回 JSON 格式
  
3.14 设置状态码

Spring MVC 会根据我们方法的返回值自动设置想听状态码, 程序员也可以手动指定状态码。通过Spring MVC 的内置对象 HttpServletResponse 提供的方法来进行设置。
  1.     @RequestMapping(value = "/setStatus")
  2.     public String setStatus(HttpServletResponse response) {
  3.         response.setStatus(401);
  4.         return "设置状态码成功";
  5.     }
复制代码


3.15 设置Header

Http 相应报头也会向客户端传递一些附加信息, 如 Content-Type, Local 等
   1. value: 制定映射的URL
  2. method: 制定请求的 method 类型, 如GET,POST等
  3. consumes: 指定处置惩罚请求(request)的提交内容类型(Content-Type), 如application/jsond等
  4. produces: 指定返回的内容类型, 还可以同时设置返回值的字符编码。
  5. Params: 指定request中必须包罗某些参数值时, 才让该方法处置惩罚。
  6. headers: 指定request中必须包罗某些指定的header值, 才气让该方法处置惩罚请求。
  
3.15.1 设置Content-Type

通过设置 produces 属性的值, 设置相应的报头 Content-Type
  1.     @RequestMapping(value = "/returnJson2",produces = "application/json")
  2.     public String returnJson2() {
  3.         return "{"success":true}";
  4.     }
复制代码


   如果不设置 produces, 方法返回结果为 String 时, Spring MVC 默认返回类型是 text/html
  
3.15.2 设置其他Header

设置其他 Header 必要使用 Spring MVC 的内置对象 HttpServletResponse 提供的方法来设置
  1.     @RequestMapping(value = "/setHeader")
  2.     public String setHeader(HttpServletResponse response) {
  3.         response.setHeader("MyHeader","MyHeaderValue");
  4.         return "设置Header成功";
  5.     }
复制代码


3.16 约定前后端交互接口

约定“前后端交互接口”是进行 Web 开发中的关键环节, 接口又叫 API , 我们一样平常降到接口或者 API, 指的都是同一个东西。API 是指应用程序对外提供的服务的描述, 用于交换信息和执行任务, 就是允许客户端给服务发送那些 HTTP 请求, 而且每种请求预期获取什么样的 HTTP 相应, 简朴来说就是规定格式。




四、应用分层


应用分层的作用
如果没有应用分层, 会出现逻辑不清、各模块相互依赖、代码拓展性差、改动一处就必要大刀阔斧等问题。

MVC
MVC 就是把整体分成了 Model , View , Controller 三个条理, 也就是将用户视图和业务处置惩罚隔离开, 而且通过控制器连接起来, 很好的实现了体现和逻辑的解耦合, 是一种标准的软件分层架构。




三层架构


   对于目前主流的开发方式, 后端开发不再必要关注前端的实现, 以是又有了一种新的分层架构: 把整体分为体现层、业务逻辑层和数据层。
  1、Controller: 控制层, 展示数据结果和担当用户指令,担当前端发送的请求, 对请求进行处置惩罚, 并相应数据;
  2、Service: 负责处置惩罚业务逻辑, 内里有复杂业务的具体实现;
  3、Dao: 数据访问层, 也被称为恒久层, 负责数据访问操作, 包括数据的增编削查;
  

MVC和三层架构是两种东西, MVC 强调数据和视图分离, 将数据展示和数据处置惩罚分开, 通过控制器对两者进行组合。三层架构强调不同维度数据处置惩罚的高内聚和低耦合, 将交互页面, 业务处置惩罚和数据库操作的逻辑分开。





免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

光之使者

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表