当前位置: 首页 > 工具软件 > http-request > 使用案例 >

http_request

孙成益
2023-12-01

今日内容

1. Servlet
2. Http协议
3. Request

一、Servlet

Servlet大体了解

  1. 概念

    是执行在tomcat服务器上面的小程序。 后台中,每一个实现了Servlet接口的类都是一个个的小程序。

  2. 使用步骤

    (1)首先需要实现Servlet类,重写里面的方法。service()方法是最重要的,我们访问的内容就是在这里面定义的。

    (2)必须在web.xml里面注册servlet

    
            <servlet-name>Servlet1</servlet-name>
           //这是通过反射,把这个类的对象给了Servlet1
            <servlet-class>servlet.HelloServlet</servlet-class>
        </servlet>
       
         //定义路径访问规则
        <servlet-mapping>
            <servlet-name>HServlet1</servlet-name>
            <url-pattern>/servlet/HelloServlet</url-pattern>
        </servlet-mapping>
    

    tomcat是一个容器,servlet是容器里面的一部分,如果你不在web.xml里面注册其信息,则tomcat找不到这个servlet。

  3. 执行原理

  4. 生命周期

  5. Servlet3.0以上注解配置

    ==@WebServlet完全可以替代web.xml里面对servlet的配置。==极大的简化了我们的servlet开发的效率。 不过他是servlet3.0以上才出现的,开发的时候注意版本。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rqzESbw3-1599290168005)(img/QQ截图20191007211411.png)]

  6. Servlet的体系结构
    Servlet – 接口
    |继承
    GenericServlet – 抽象类
    |继承
    HttpServlet – 抽象类

    展示的都是父子关系。这三个类说明了我们开发servlet程序的方式可以有三个,不过我们常用的就是继承HttpServlet。

    • GenericServlet:把servlet接口中的方法做了空的实现,把service方法做了抽象,目的就可以让程序员只实现service即可

    • HttpServlet :对http协议做了一种封装,简化操作

      1. 继承了GenericServlet
      2. 需要复写doGet/doPost等方法
  7. Servlet相关的配置

    1. urlPatterns:Servlet的访问路径
      1. 一个Servlet可以定义多个访问路径:

      2. 路径定义的规则:

        1. /xxx : 路径匹配
        2. /xxx/xxx: 可以多层,类似于目录结构的
        3. *.do : 扩展名匹配
      3. 路径匹配分类

        1. 精确匹配
        2. 扩展名匹配: *.do
        3. 路径匹配: /kata/*
      4. 错误的路径匹配写法
        /kata/.jsp
        /.jsp
        he*.jsp

      5. 匹配所有
        /
        /*

      6. 优先级的问题

        /hdeasy/*
        /hdeasy/test
        /*
        

        优先级为:/hdeasy/test>/hdeasy/*> / *

        总结:精确路径 > 最长路径>扩展名

虚拟路径

我们使用idea建立web项目的时候,如果添加了虚拟路径

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OOSbwraK-1599290168009)(img/QQ截图20191007201250.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xf6cwIoY-1599290168011)(img/QQ截图20191007201608.png)]

前台发送请求必须有虚拟路径

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EqTRrZt1-1599290168015)(img/QQ截图20191007201800.png)]

当然,前台的访问路径你不光可以写URI, URL也是正确的(不过有点浪费,因为我们都是在本程序中运行)。

URL:统一资源定位标识,此处完整的URL是http://localhost:8080/day13_servlet_01/requestDemo1。

我们这里action指向的是URI,因为这个页面是在我们本程序里面运行的,省略了http://localhost:8080/;

如果你action非要指向URLhttp://localhost:8080/day13_servlet_01/requestDemo1也是可以的。

注意:当你前后台分离的时候,肯定不能使用URI,必须使用URL,毫无疑问的,那是两个程序了。

后台转发禁止写虚拟路径

转发是在后台(服务器端内部)进行的,没有涉及前台(客户端);在服务器内部转发请求的时候虚拟路径是默认带着的。 因为在服务器内部转发的时候,程序自己会给我们添加虚拟路径,所以如果你想转发到百度、搜狐等网页是不可能成功的:/虚拟路径/https://www.baidu.com;前面自己给你加上本程序的虚拟路径了

后台重定向根据需要带上虚拟路径

重定向是客户端再一次发送请求,表面是在后台,实际上相当于在前台发送的请求,前台客户端发送请求的时候根据带不带虚拟路径判断即将请求的页面是本程序的还是外部的。 后台中如果有虚拟路径:则请求的是本程序;如果没有虚拟路径那么请求的是外部的。

tomcat项目目录结构

目录结构

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sMhTM61s-1599290168017)(img/QQ截图20191007205006.png)]

我们在浏览器客户端访问的项目是部署在tomcat上面的项目。

所以,我们的所有的对静态资源的访问路径全部遵守项目在tomcat上部署的结构。

out目录下面的项目才是真正部署到tomcat上面的项目。

我们在tomcat的webapps下面是找不到我们的这个项目的,那是因为每一个在idea中运行的项目都会有一个配置文件,里面配置了tomcat应该去哪里找我们的项目。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V7PqAQvz-1599290168018)(img/QQ%E6%88%AA%E5%9B%BE20191007210609.png)]

静态资源的访问

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FiFTY32N-1599290168020)(img/QQ截图20191007205524.png)]

我们的静态资源放到了和WEB-INF同级位置,也就是项目的根目录下面,所以在浏览器中直接访问即可。

当然,在进行web开发的时候我们其实完全也可以配置静态资源的虚拟路径的;此处我们没有讲。在后面

​ 的框架开发的时候,我们会经常配置静态资源的访问路径方式。

注意

部署在tomcat的webapps下面,必须是war包。

普通的web项目和使用框架开发的项目,都需要打成war包发布到webapps下面。

二、HTTP协议

http协议是需要两端同时遵守的规则:客户端浏览器发送给服务端的数据会在浏览器端被封装,服务器端返回客户端浏览器的数据会在服务器端被封装。

  • 概念:Hyper Text Transfer Protocol 超文本传输协议

  • 传输协议:定义了客户端和服务器发送数据的格式

  • 特点:

    1. 基于tcp/ip协议
    2. 默认的端口号:80
    3. 基于请求/响应模型的,发一次请求做一次响应
    4. 无状态:每次请求相互独立,不能交互数据
  • 历史版本:

    • 1.0 :每次请求都会重新建立连接–短链接
    • 1.1 :复用连接—长连接
  • 请求消息的数据格式

    http严格定义了数据以什么格式发送,再以什么格式响应。

    1.请求行

    请求方式 请求url 请求协议/版本
    GET /day13_servlet_01/request.html HTTP/1.1
    POST /day13_servlet_01/requestDemo1 HTTP/1.1

    • 请求方式
      • HTTP协议请求有7种方式,常用的有两种

      • GET:
        1. 请求参数直接在地址后,请求参数直接在请求行种
        2. 请求url传参长度是有限制的
        3. 不太安全

        • POST:
          1. 请求参数在请求体中
          2. 传参长度是没有限制
          3. 相对安全

2.请求头

Host: localhost:8080
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Cookie: Idea-dd3bbdc1=6d1574f3-502f-4739-a6db-20ec4e96f752; shengji='340000','d0'; shiji='bafb489553834ef2b28ec8893c75663b'; yunweiban='e88fa8e78c38485983d488f411964eaf'; sdgongsi=''; sdxlgq=''; sdjxb=''; DefaultID=c18190fed09d40bea4b8cc569f01faf6
If-None-Match: W/"341-1570345708402"

If-Modified-Since: Sun, 06 Oct 2019 07:08:28 GMT

请求头名称:对应的值
* 常用的请求头:
1. User-Agent:浏览器告诉服务器,我访问服务器的时候后采用的浏览器版本信息
* 可以在服务器端做一些浏览器的兼容性处理
例如chrome:User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.100 Safari/537.36
例如IE:
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko
2. Refer: 告诉服务器,我从哪里来
例子:Referer: http://localhost:8080/day13_servlet_01/request.html
* 作用:
1. 防盗链

               > 就是我规定必须从我的网站出发,才能访问我的页面。你想在你的网站上面
               >
           >    用我的页面,不允许(通过设置防盗链就不让你用)。  如何判断是否从我的网站出发呢?--利用contanins()方法,判断url里面是否存在我的域名、虚拟地址。

            2. 统计

主要的:session 、cookie、 编码、请求路径

3.请求空行

  • 空行,用于分割POST请求头和请求体,在两者中间添加一个空行

     > ==post请求才有请求体。get请求没有请求体==
    

    4.请求体:

    • 封装了POST请求消息的参数,例如:name=zhangsan&age=30

三、Request

request对象response对象的来源

  1. request和response对象是由服务器(tomcat)创建的,然后当做servlet的入参,可以直接使用

    tomcat给我们提供了两个用于使用http的对象request和response。通过这两个对象我们可以获取请求和响应任意的数据。

  2. request对象处理获取到的请求消息,response对象设置响应消息

  3. request对象的体系结构
    ServletRequest – 接口
    | 继承
    HttpServletRequest – 接口
    |
    org.apache.catalina.connector.RequestFacade 类(tomcat)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZTCt3UPF-1599290168022)(img/QQ截图20191007215011.png)]

request 功能:

  1. 获取请求消息的数据
    1. 获取请求行数据:
      • GET /day13_servlet_01/request.html HTTP/1.1
      • 方法
        1. 获取请求方式:
          • String getMethod()
        2. 获取虚拟目录
          • String getContextPath()
        3. 获取Servlet路径
          • String getServletPath()
        4. 获取get方式请求参数
          • String getQueryString()
        5. 获取URI
          • String getRequestURI()
          • StringBuffer getRequestURL()
            URI :在某一个范围内唯一标识一个资源的字符串 (短的)
            /day13_servlet_01/requestDemo2
            URL:始终唯一标识一个资源的字符串 (长的)
            http://localhost:8080/day13_servlet_01/requestDemo2
        6. 获取协议和版本
          • String getProtocol()
        7. 获取客户端的IP地址
          • String getRemoteAddr()
    2. 获取请求头数据:
      • 方法:
        • String getHeader(String name)

        • Enumeration getHeaderNames()

        • refer :

          • 从网址直接输入是null
          • 从自己的其他网页跳转 http://localhost:8080/day13_servlet_01/
          • 从别人的网站跳转 http://localhost:8888/day12/
        • 防盗链代码

@WebServlet("/requestDemo4")
public class RequestDemo4 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        this.doPost(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println(req.getHeader("Referer"));
//  http://localhost:8080/day13_servlet_01/index.html
        String strURL = req.getHeader("Referer");
        //如果是从本网站出发的就允许播放电影。
        if(strURL.contains("day13_servlet_01")){
//            System.out.println("开始看电影");
            resp.setContentType("text/html;charset=UTF-8");
            resp.getWriter().write("播放电影");
        }else{
//            System.out.println("您盗链了,请来优酷看电影");
            resp.setContentType("text/html;charset=UTF-8");
            resp.getWriter().write("您盗链了,请来优酷看电影");
        }

    }
}

获取请求体参数:

* 请求体,只有Post请求才有。get请求的参数从地址栏上传递。
    * 步骤:
    1. 获取流对象
        * BufferedReader getReader():获取字符输入流,**只能操作字符数据**
        * ServletInputStream getInputStream() :获取字节输入流,**可以操作所有类型的数据**,文件的上传
    2. 从流中读取数据
@WebServlet("/requestDemo05")
public class RequestDemo5 extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        this.doPost(request,response);
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // 1. 获取字节流
        BufferedReader br = request.getReader();
        //2.读取数据
        String line = null;
        while((line= br.readLine()) != null){
            System.out.println(line);
        }
    }
}

所有请求通用功能:

根据post请求和get请求等其他的请求方式不同,在获取其传递的参数值的时候调用 的方法不同。这样太不方便了,于是tomcat帮我们提供了统一的方法可以达到任意请求都可以调用。

获取请求的参数

以下方法对所有请求有效

  1. String getParameter(String name) :获取单个–单个值

    String[] getParameterValues(String name):获取name属性对应的所有值–多选

    @WebServlet("/requestDemo06")
    public class RequestDemo6 extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            this.doPost(request,response);
        }
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            //根据参数不同,request提供了不同的接收方法
    //        request.getParameter();
    //        request.getParameterNames();
    //        request.getParameterMap();
    //        request.getParameterValues();
            System.out.println(request.getParameter("username"));
          
            //sex和cars是单选的
            System.out.println(request.getParameter("sex"));
            System.out.println(request.getParameter("cars"));
            
            //兴趣是多选的
            String[] hobby = request.getParameterValues("hobby");
            for(String hb : hobby){
                System.out.println(hb);
            }
            System.out.println("---------------------------------------");
           //演示获取提交的参数
            //获取消息体的name属性值:如sex、cars、hobby、、、
            Enumeration<String> paras =  request. getParameterNames();
    
            while(paras.hasMoreElements()){
                System.out.println(paras.nextElement());
            }
    
        }
    }
    
  2. Enumeration getParameterNames()

    
    @WebServlet("/requestDemo07")
    public class RequestDemo7 extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            this.doPost(request,response);
        }
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            //打印所有的参数值
           //演示获取提交的参数
            Enumeration<String> paras =  request. getParameterNames();
            while(paras.hasMoreElements()){
                String key = paras.nextElement();
                // 这个获取数组的值只能获取一个,但是我的hobby是多选的
    //            System.out.println(request.getParameter(key));
                //如果想要获取数组的值
                String[] values = request.getParameterValues(key);
                for(String v : values){
                    System.out.println(v);
                }
            }
        }
    }
    
  3. Map<String,String[]> getParameterMap()
    BeanUtils可以直接把Map封装为对象

    @WebServlet("/requestDemo08")
    public class RequestDemo8 extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            this.doPost(request,response);
        }
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            //打印所有的参数值
    
            //如果你想省事一点,request已经提供了这个功能
            Map<String, String[]> mapvalues =  request.getParameterMap();
    
            Set<String> keys = mapvalues.keySet();
            for(String key :keys){
                String[] values = mapvalues.get(key);
                for(String v :values){
                    System.out.println(v);
                }
            }
    
    //       //演示获取提交的参数
    //        Enumeration<String> paras =  request. getParameterNames();
    //
    //        while(paras.hasMoreElements()){
    //
    //            String key = paras.nextElement();
    //            // 这个获取数组的值只能获取一个
                System.out.println(request.getParameter(key));
    //
    //            //如果想要获取数组的值
    //            String[] values = request.getParameterValues(key);
    //            for(String v : values){
    //                System.out.println(v);
    //            }
    //
    //        }
    
        }
    }
    

中文乱码的问题:

get请求的参数是在地址栏上面的,那么在传递的时候会在tomcat中进行一些其他的编码(url编码)处理。

post请求的参数在请求体中,是直接传递给服务器端的,没有被tomcat做其他的处理。

1. 正是因为get请求参数通过地址栏,进行了其他的编码处理(url编码),所以难控制。

​ 这个控制指的是 当你前台使用GBK编码,后台我们也是设置了request.setCharacterEncoding(“GBK”);

​ 但是结果还 是乱码 。不过如果是utf-8就会成功,因为tomcat对utf-8做了特殊处理。

  1. 还有,这个get请求不仅仅指的是java中的,get请求是对所有的语言,在通过url地址栏传递

​ 参数的时候,都会发生url编码。

get方式

get乱码在tomcat8的时候不存在. 这句话是在前台是utf-8的时候才是正确的,因为我们后台的默认编码也是

​ utf-8.两端达到了统一。如果你把前台改为gbk,那么就会出现乱码。

提交中文:
GBK http://localhost:8080/day13_servlet_01/requestDemo09?username=%D6%D0

          UTF-8: http://localhost:8080/day13_servlet_01/requestDemo09?   username=**%E4%B8%AD**

我们get请求提交的汉字的时候,地址栏上面参数显示的是编码后的。

%是用来分割的,实际上:中–>D6D0 中–>E4B8AD. 这都是十六进制的。

地址栏上参数传递必须是十六进制,这是浏览器统一规定的。

  username=%D6%D0  D6D0
  username=%E4%B8%AD  E4B8AD

一个中文汉字在gbk中占两个字节,在utf-8占三个字节。乱码的时候,往往发现乱码里面也会有中文,那是因为恰巧utf-8的前两个字节对应上了gbk的编码。

post方式

post方式提交,需要在接收数据之前添加正确的字符编码设置
//来解决Post提交时候的字符编码问题
request.setCharacterEncoding(“GBK”);
System.out.println(request.getParameter(“username”));

作业:
编码转换: 字符编码 字 转 数字
url编码 在url地址中,如果有非英文,会全部转为英文再发送

乱码总结:

服务器端接收客户端请求时由客户端传递过来的参数编码处理

**模式一:**客户端在地址栏直接填写URL及参数
==>此方式无法解决中文乱码问题,因为这种方式不可取
**模式二:**客户端通过请求页面的表单或者超链接来请求并传递参数
==>此方式需分两种情况来处理

1): 通过Get方式请求处理方法
方法:服务端每次获取客户端传递过来的参数之后,必须重新反编译到原始字节码,并重现用UTF-8编码.

String userName = request.getParameter("username"); 
//ISO-8859-1编码是tomcat内部编码,因为get请求的参数再地址栏上面,项目部署再tomcat上面,由tomcat解析
// 参数。  所以使用对应的解码得到字节数组。
Byte[] bytes = userName.getBytes(userName,"ISO-8859-1"); 
userName = new String(bytes,"utf-8"); 

这样获取到的传递过来的userName值才不会乱码.
2): 通过Post方式请求处理方法
方法:服务端在获取客户端传递过来的参数之前,设定一次编码格式为UTF-8编码就行了.

request.setCharacterEncoding="utf-8";
String userName  = request.getParameter("username");

这样获取到的传递过来的userName值才不会乱码.

**请求转发:一种在服务器内部资源跳转的方式 **

  1. 步骤:

    1. 通过request获取请求转发对象:
      RequestDispatcher requestDispatcher = request.getRequestDispatcher(String path);

    2. 然后使用forward对象进行转发操作:
      requestDispatcher.forward(request,response);

      @WebServlet("/requestDemo10")
      public class RequestDemo10  extends HttpServlet{
          @Override
          protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
              System.out.println("您请求了Demo10");
              /*
              服务器内部转发:虚拟路径默认会有。
                            如果你想通过转发跳转到别人的网站一定出错;会多出虚拟路径。
              */
              
              RequestDispatcher requestDispatcher = request.getRequestDispatcher("/requestDemo11"); //虚拟路径默认有
      //        RequestDispatcher requestDispatcher = request.getRequestDispatcher("http://www.sohu.com"); //出错,多出个虚拟路径
              
              //仅仅在一次请求中生效。
              request.setAttribute("name","world");
              //会在整个网站生效,哪怕不是同一个请求。
              ServletContext sv = request.getServletContext();
              sv.setAttribute("sv","hello");
      
      //        request.removeAttribute("name");
      
              System.out.println(request.getAttribute("name"));
              requestDispatcher.forward(request,response);
          }
      
  2. 特点:

    1. 浏览器的地址栏不发生变化
    2. 只能转发当前服务器内部的资源
    3. 转发只发送了一次请求
  3. 共享数据

    域对象:一个作用范围的对象,可以在此范围内共享数据

    可以上网搜索web四大域对象。

    1. request域:一次请求的范围
  • 方法
    * request.setAttribute();
    * reuqest.getAttribute();
    * request.removeAttribute();

    1. ServletContext域
    • ServletContext sv = request.getServletContext();
    • sv.setAttribute(“sv”,“hello”);

这个域对象是全局的,对整个网站起作用,不仅仅是一次请求

1.PageContext域:作用范围是整个JSP页面,是四大作用域中最小的一个;生命周期是当对JSP的请求时开始,当响应结束时销毁。

2.ServletRequest域:作用范围是整个请求链(请求转发也存在);生命周期是在service方法调用前由 服务器  创建,传入service方法。整个请求结束,request生命结束。

3.HttpSession域:作用范围是一次会话。生命周期是在第一次调用request.getSession()方法时,服务器会检查是否已经有对应的session,如果没有就在内存中创建一个session并返回。当一段时间内session没有被使用(默认为30分钟),则服务器会销毁该session。如果服务器非正常关闭(强行关闭),没有到期的session也会跟着销毁。如果调用session提供的invalidate() ,可以立即销毁session。

注意:服务器正常关闭,再启动,Session对象会进行钝化和活化操作。同时如果服务器钝化的时间在session 默认销毁时间之内,则活化后session还是存在的。否则Session不存在。  如果JavaBean 数据在session钝化时,没有实现Serializable 则当Session活化时,会消失。

4.ServletContext域:作用范围是整个Web应用。当Web应用被加载进容器时创建代表整个web应用的ServletContext对象,当服务器关闭或Web应用被移除时,ServletContext对象跟着销毁。 (  this.getServletContext().getAttribute("***");   )

2.ServletRequest域:作用范围是整个请求链(请求转发也存在);生命周期是在service方法调用前由 服务器  创建,传入service方法。整个请求结束,request生命结束。

3.HttpSession域:作用范围是一次会话。生命周期是在第一次调用request.getSession()方法时,服务器会检查是否已经有对应的session,如果没有就在内存中创建一个session并返回。当一段时间内session没有被使用(默认为30分钟),则服务器会销毁该session。如果服务器非正常关闭(强行关闭),没有到期的session也会跟着销毁。如果调用session提供的invalidate() ,可以立即销毁session。

注意:服务器正常关闭,再启动,Session对象会进行钝化和活化操作。同时如果服务器钝化的时间在session 默认销毁时间之内,则活化后session还是存在的。否则Session不存在。  如果JavaBean 数据在session钝化时,没有实现Serializable 则当Session活化时,会消失。

4.ServletContext域:作用范围是整个Web应用。当Web应用被加载进容器时创建代表整个web应用的ServletContext对象,当服务器关闭或Web应用被移除时,ServletContext对象跟着销毁。 (  this.getServletContext().getAttribute("***");   )








 类似资料:

相关阅读

相关文章

相关问答