• Servlet 入门


    Servlet: server applet

    运行在服务器端的小程序

    • Servlet就是一个接口,定义了Java类被浏览器访问到(tomcat识别)的规则。
    • 自定义一个类,实现Servlet接口,复写方法。

    快速入门

    1. 创建JavaEE项目

    2. 定义一个类,实现Servlet接口

      package cn.web;
      
      import javax.servlet.*;
      import java.io.IOException;
      
      public class ServletDemo1 implements Servlet {
          @Override
          public void init(ServletConfig servletConfig) throws ServletException {
      
          }
      
          @Override
          public ServletConfig getServletConfig() {
              return null;
          }
      
          @Override
          public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
      
          }
      
          @Override
          public String getServletInfo() {
              return null;
          }
      
          @Override
          public void destroy() {
      
          }
      }
      
    3. 实现接口中的抽象方法

    4. 配置Servlet
      web.xml中配置, 写在</web-app>标签里面

      <!--配置Servlet -->
          <servlet>
              <servlet-name>demo1</servlet-name>
              <servlet-class>cn.web.ServletDemo1</servlet-class>
          </servlet>
          
          <servlet-mapping>
              <servlet-name>demo1</servlet-name>
              <url-pattern>/demo1</url-pattern>
          </servlet-mapping>
      

    执行原理

    1. 当服务器接受到客户端浏览器的请求后,会解析请求URL路径,获取访问的Servlet的资源路径
    2. 查找web.xml文件,是否有对应的<url-pattern>标签体内容。
    3. 如果有,则在找到对应的<servlet-class>全类名
    4. tomcat会将字节码文件加载进内存,并且创建其对象
    5. 调用service方法

    生命周期

    1. 被创建:执行·方法,只执行一次
      • Servlet什么时候被创建?

        • 默认情况下,第一次被访问时,Servlet被创建
        • 可以配置执行Servlet的创建时机。
          • 标签下配置
            1. 第一次被访问时,创建
              • 的值为负数
            2. 在服务器启动时,创建
              • 的值为0或正整数
      • Servlet的init方法,只执行一次,说明一个Servlet在内存中只存在一个对象,Servlet是单例的

        • 多个用户同时访问时,可能存在线程安全问题。
        • 解决:尽量不要在Servlet中定义成员变量。即使定义了成员变量,也不要对修改值
    2. 提供服务:执行service方法,执行多次
      • 每次访问Servlet时,Service方法都会被调用一次
    3. 被销毁:执行destroy方法,只执行一次
      • Servlet被销毁时执行。服务器关闭时,Servlet被销毁
      • 只有服务器正常关闭时,才会执行destroy方法。
      • destroy方法在Servlet被销毁之前执行,一般用于释放资源

    Servlet3.0

    • 好处:

      • 支持注解配置。可以不需要web.xml了。
    • 步骤:

      1. 创建JavaEE项目,选择Servlet的版本3.0以上,可以不创建web.xml

      2. 定义一个类,实现Servlet接口

      3. 复写方法

      4. 在类上使用@WebServlet注解,进行配置
        @WebServlet("资源路径")

            @Target({ElementType.TYPE})
            @Retention(RetentionPolicy.RUNTIME)
            @Documented
            public @interface WebServlet {
                String name() default "";//相当于<Servlet-name>
            
                String[] value() default {};//代表urlPatterns()属性配置
            
                String[] urlPatterns() default {};//相当于<url-pattern>
            
                int loadOnStartup() default -1;//相当于<load-on-startup>
            
                WebInitParam[] initParams() default {};
            
                boolean asyncSupported() default false;
            
                String smallIcon() default "";
            
                String largeIcon() default "";
            
                String description() default "";
            
                String displayName() default "";
            }
        

    urlpartten:Servlet访问路径

    1. 一个Servlet可以定义多个访问路径 : @WebServlet({"/d4","/dd4","/ddd4"})
    2. 路径定义规则:
      1. /xxx:路径匹配
      2. /xxx/xxx:多层路径,目录结构
      3. *.do:扩展名匹配

    HttpServlet

    Servlet的体系结构

    • GenericServlet:将Servlet接口中其他的方法做了默认空实现,只将service()方法作为抽象, 将来定义Servlet类时, 可以继承GenericServlet,实现service()方法即可
    • HttpServlet:对http协议的一种封装,简化操作
      1. 定义类继承HttpServlet
      2. 复写doGet/doPost方法

    Request

    request对象和response对象的原理

    1. `request`和`response`对象是由服务器创建的。我们来使用它们
    2. `request`对象是来获取请求消息,`response`对象是来设置响应消息
    

    request对象继承体系结构

        ServletRequest	-- 接口
            |	继承
        HttpServletRequest	-- 接口
            |	实现
        org.apache.catalina.connector.RequestFacade 类(tomcat)
    

    request功能

    获取请求消息数据

        @WebServlet("/demo2")
        public class ServletDemo2 extends HttpServlet {
            @Override
            protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    
            }
    
            @Override
            protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
                String contextPath = request.getContextPath();
                System.out.println("contextPath = " + contextPath);
    
                String servletPath = request.getServletPath();
                System.out.println("servletPath = " + servletPath);
    
                String method = request.getMethod();
                System.out.println("method = " + method);
    
                String queryString = request.getQueryString();
                System.out.println("queryString = " + queryString);
    
                String requestURI = request.getRequestURI();
                System.out.println("requestURI = " + requestURI);
    
                StringBuffer requestURL = request.getRequestURL();
                System.out.println("requestURL = " + requestURL);
    
                String remoteAddr = request.getRemoteAddr();
                System.out.println("remoteAddr = " + remoteAddr);
            }
        }
    
        contextPath = /UserDemo_war_exploded
        servletPath = /demo2
        method = GET
        queryString = name=IIce
        requestURI = /UserDemo_war_exploded/demo2
        requestURL = http://localhost:8080/UserDemo_war_exploded/demo2
        remoteAddr = 0:0:0:0:0:0:0:1
    

    其他功能

    1. 获取请求参数通用方式:不论get还是post请求方式都可以使用下列方法来获取请求参数

      1. String getParameter(String name):根据参数名称获取参数值 username=zs&password=123
      2. String[] getParameterValues(String name):根据参数名称获取参数值的数组 hobby=xx&hobby=game
      3. Enumeration<String> getParameterNames():获取所有请求的参数名称
      4. Map<String,String[]> getParameterMap():获取所有参数的map集合
      5. 中文乱码问题
        • 解决:在获取参数前,设置request的编码request.setCharacterEncoding("utf-8");
    2. 请求转发:一种在服务器内部的资源跳转方式

      1. 步骤:

        1. 通过request对象获取请求转发器对象:RequestDispatcher getRequestDispatcher(String path)
        2. 使用RequestDispatcher对象来进行转发:forward(ServletRequest request, ServletResponse response)
      2. 特点:

        1. 浏览器地址栏路径不发生变化
        2. 只能转发到当前服务器内部资源中。
        3. 转发是一次请求
    3. 共享数据:

      • 域对象:一个有作用范围的对象,可以在范围内共享数据
      • request域:代表一次请求的范围,一般用于请求转发的多个资源中共享数据
      • 方法:
        1. void setAttribute(String name,Object obj):存储数据
        2. Object getAttitude(String name):通过键获取值
        3. void removeAttribute(String name):通过键移除键值对
    4. 获取ServletContext

      • ServletContext getServletContext()

    JavaBean

    用于封装JavaBean的

    1. JavaBean:标准的Java类

      1. 要求:
        1. 类必须被public修饰
        2. 必须提供空参的构造器
        3. 成员变量必须使用private修饰
        4. 提供公共settergetter方法
      2. 功能:封装数据
    2. 概念:
      成员变量:
      属性:settergetter方法截取后的产物, 例如:getUsername() --> Username--> username

    3. 方法:

      1. setProperty()
      2. getProperty()
      3. populate(Object obj , Map map):将map集合的键值对信息,封装到对应的JavaBean对象中

    HTTP

    响应头

    常见的响应头:

    1. Content-Type:服务器告诉客户端本次响应体数据格式以及编码格式
    2. Content-disposition:服务器告诉客户端以什么格式打开响应体数据
      • 值:
        • in-line:默认值,在当前页面内打开
        • attachment;filename=xxx:以附件形式打开响应体。文件下载

    Response对象

    设置响应消息

    设置响应行

    1. 格式:HTTP/1.1 200 ok
    2. 设置状态码:setStatus(int sc)

    设置响应头

    setHeader(String name, String value)

    设置响应体

    1. 获取输出流
      • 字符输出流:PrintWriter getWriter()
      • 字节输出流:ServletOutputStream getOutputStream()
    2. 使用输出流,将数据输出到客户端浏览器

    重定向

        //简单的重定向方法
        response.sendRedirect("/responseDemo2");
    
    • 重定向的特点:redirect
      1. 地址栏发生变化
      2. 重定向可以访问其他站点(服务器)的资源
      3. 重定向是两次请求。不能使用request对象来共享数据
    • 转发的特点:forward
      1. 转发地址栏路径不变
      2. 转发只能访问当前服务器下的资源
      3. 转发是一次请求,可以使用request对象来共享数据

    路径问题

    1. 相对路径:通过相对路径不可以确定唯一资源
      • 如:./index.html

      • 不以/开头,以.开头路径

      • 规则:找到当前资源和目标资源之间的相对位置关系

        • ./:当前目录
        • ../:后退一级目录
    2. 绝对路径:通过绝对路径可以确定唯一资源
      • 如:http://localhost/responseDemo2 /responseDemo2
      • /开头的路径
      • 规则:判断定义的路径是给谁用的?判断请求将来从哪儿发出
        • 给客户端浏览器使用:需要加虚拟目录(项目的访问路径)
          • 建议虚拟目录动态获取:request.getContextPath()
          • <a> , <form> 重定向...
        • 给服务器使用:不需要加虚拟目录
          • 转发路径

    服务器输出字符数据到浏览器

    • 步骤:

      1. 获取字符输出流
      2. 输出数据
    • 乱码问题:

      1. PrintWriter pw = response.getWriter();获取的流的默认编码是ISO-8859-1
      2. 设置该流的默认编码
      3. 告诉浏览器响应体使用的编码
          //简单的形式,设置编码,是在获取流之前设置
          response.setContentType("text/html;charset=utf-8");
      

    ServletContext对象

    概念

    代表整个web应用,可以和程序的容器(服务器)来通信

    获取

    1. 通过request对象获取
      request.getServletContext();
    2. 通过HttpServlet获取
      this.getServletContext();

    功能

    获取MIME类型

    • MIME类型:在互联网通信过程中定义的一种文件数据类型

      格式: 大类型/小类型 text/html image/jpeg

    • 获取:String getMimeType(String file)

    域对象: 共享数据

    1. setAttribute(String name,Object value)
    2. getAttribute(String name)
    3. removeAttribute(String name)

    ServletContext对象范围:所有用户所有请求的数据

    获取文件的真实(服务器)路径

        String getRealPath(String path)  
    
    
        String b = context.getRealPath("/b.txt");//web目录下资源访问
        System.out.println(b);
    
        String c = context.getRealPath("/WEB-INF/c.txt");//WEB-INF目录下的资源访问
        System.out.println(c);
    
        String a = context.getRealPath("/WEB-INF/classes/a.txt");//src目录下的资源访问
        System.out.println(a);
    

    文件下载

    文件下载需求

    1. 页面显示超链接
    2. 点击超链接后弹出下载提示框
    3. 完成图片文件下载

    分析

    1. 超链接指向的资源如果能够被浏览器解析,则在浏览器中展示,如果不能解析,则弹出下载提示框。不满足需求
    2. 任何资源都必须弹出下载提示框
    3. 使用响应头设置资源的打开方式
      • content-disposition:attachment;filename=xxx

    步骤

    1. 定义页面,编辑超链接href属性,指向Servlet,传递资源名称filename
    2. 定义Servlet
      1. 获取文件名称
      2. 使用字节输入流加载文件进内存
      3. 指定response的响应头: content-disposition:attachment;filename=xxx
      4. 将数据写出到response输出流

    Cookie

    保存在客户端

    1. 创建Cookie对象,绑定数据
      new Cookie(String name, String value)
    2. 发送Cookie对象
      response.addCookie(Cookie cookie)
    3. 获取Cookie,拿到数据
      Cookie[] request.getCookies()

    cookie的细节

    1. 一次可不可以发送多个cookie?

      • 可以
      • 可以创建多个Cookie对象,使用response调用多次addCookie方法发送cookie即可。
    2. cookie在浏览器中保存多长时间?

      1. 默认情况下,当浏览器关闭后,Cookie数据被销毁
      2. 持久化存储:
        • setMaxAge(int seconds)
          1. 正数:将Cookie数据写到硬盘的文件中。持久化存储。并指定cookie存活时间,时间到后,cookie文件自动失效
          2. 负数:默认值
          3. 零:删除cookie信息
    3. cookie能不能存中文?

      • 在tomcat 8 之前 cookie中不能直接存储中文数据。
        • 需要将中文数据转码---一般采用URL编码(%E3)
      • 在tomcat 8 之后,cookie支持中文数据。特殊字符还是不支持,建议使用URL编码存储,URL解码解析
    4. cookie共享问题?

      1. 假设在一个tomcat服务器中,部署了多个web项目,那么在这些web项目中cookie能不能共享?

        • 默认情况下cookie不能共享
        • setPath(String path):设置cookie的获取范围。默认情况下,设置当前的虚拟目录
          • 如果要共享,则可以将path设置为"/"
      2. 不同的tomcat服务器间cookie共享问题?

        • setDomain(String path):如果设置一级域名相同,那么多个服务器之间cookie可以共享
          • setDomain(".baidu.com"),那么tieba.baidu.comnews.baidu.com中cookie可以共享

    Cookie的特点和作用

    1. cookie存储数据在客户端浏览器
    2. 浏览器对于单个cookie 的大小有限制(4kb) 以及 对同一个域名下的总cookie数量也有限制(20个)
    • 作用:
      1. cookie一般用于存出少量的不太敏感的数据
      2. 在不登录的情况下,完成服务器对客户端的身份识别

    Session

    依赖于Cookie, 保存在服务器端

    1. 获取HttpSession对象:
      HttpSession session = request.getSession();
    2. 使用HttpSession对象:
          Object getAttribute(String name)  
          void setAttribute(String name, Object value)
          void removeAttribute(String name)  
      

    细节

    1. 当客户端关闭后,服务器不关闭,两次获取session是否为同一个?

      • 默认情况下。不是。
      • 如果需要相同,则可以创建Cookie,键为JSESSIONID,设置最大存活时间,让cookie持久化保存。
          Cookie c = new Cookie("JSESSIONID",session.getId());
          c.setMaxAge(60*60);
          response.addCookie(c);
      
    2. 客户端不关闭,服务器关闭后,两次获取的session是同一个吗?

      • 不是同一个,但是要确保数据不丢失。tomcat自动完成以下工作
        • session的钝化: 在服务器正常关闭之前, 将session对象系列化到硬盘上
        • session的活化: 在服务器启动后, 将session文件转化为内存中的session对象即可。
    3. session什么时候被销毁?

      1. 服务器关闭

      2. session对象调用invalidate()

      3. session默认失效时间 30分钟
        选择性配置修改

            <session-config>
                <session-timeout>30</session-timeout>
            </session-config>
        
    4. session的特点

      1. session用于存储一次会话的多次请求的数据,存在服务器端
      2. session可以存储任意类型,任意大小的数据

    session与Cookie的区别

    1. session存储数据在服务器端,Cookie在客户端
    2. session没有数据大小限制,Cookie有
    3. session数据安全,Cookie相对于不安全

    JSP(Java Server Pages)

    JSP本质上就是一个Servlet

    JSP的脚本: JSP定义Java代码的方式

    1. <% 代码 %>: 定义的java代码,在service方法中。service方法中可以定义什么,该脚本中就可以定义什么。
    2. <%! 代码 %>: 定义的java代码,在jsp转换后的java类的成员位置。
    3. <%= 代码 %>: 定义的java代码,会输出到页面上。输出语句中可以定义什么,该脚本中就可以定义什么。

    JSP的内置对象

    • 在jsp页面中不需要获取和创建,可以直接使用的对象
    • jsp一共有9个内置对象。
    变量名 真实类型 作用
    pageContext PageContext 当前页面共享数据,还可以获取其他八个内置对象
    request HttpServletRequest 一次请求访问的多个资源(转发)
    session HttpSession 一次会话的多个请求间
    application ServletContext 所有用户间共享数据
    response HttpServletResponse 响应对象
    page Object 当前页面(Servlet)的对象,this
    out JspWriter 输出对象,数据输出到页面上
    config ServletConfig Servlet的配置对象
    exception Throwable 异常对象

    response.getWriter()out.write()的区别:

    • 在tomcat服务器真正给客户端做出响应之前,会先找response缓冲区数据,再找out缓冲区数据。
    • response.getWriter()数据输出永远在out.write()之前

    El表达式

    1. 概念:Expression Language 表达式语言
    2. 作用:替换和简化jsp页面中java代码的编写
    3. 语法:${表达式}
    4. 注意:
      • jsp默认支持el表达式的。如果要忽略el表达式
        1. 设置jsp中page指令中:isELIgnored="true" 忽略当前jsp页面中所有的el表达式
        2. ${表达式} :忽略当前这个el表达式

    使用

    运算符

    1. 算数运算符: + - * /(div) %(mod)
    2. 比较运算符: > < >= <= == !=
    3. 逻辑运算符: &&(and) ||(or) !(not)
    4. 空运算符: empty
      • 功能:用于判断字符串、集合、数组对象是否为null或者长度是否为0
      • ${empty list}:判断字符串、集合、数组对象是否为null或者长度为0
      • ${not empty str}:表示判断字符串、集合、数组对象是否不为null 并且 长度>0

    获取值

    1. el表达式只能从域对象中获取值
    2. 语法:
      1. ${域名称.键名}:从指定域中获取指定键的值

        • 域名称:
          1. pageScope --> pageContext
          2. requestScope --> request
          3. sessionScope --> session
          4. applicationScope --> application(ServletContext)
        • 举例:在request域中存储了name=张三
        • 获取:${requestScope.name}
      2. ${键名}:表示依次从最小的域中查找是否有该键对应的值,直到找到为止。

      3. 获取对象、List集合、Map集合的值

        1. 对象:${域名称.键名.属性名}

          • 本质上会去调用对象的getter方法
        2. List集合:${域名称.键名[索引]}

        3. Map集合:

          • ${域名称.键名.key名称}
          • ${域名称.键名["key名称"]}

    隐式对象

    • el表达式中有11个隐式对象
    • pageContext
      • 获取jsp其他八个内置对象
        • ${pageContext.request.contextPath}:动态获取虚拟目录

    JSTL

    概念:JavaServer Pages Tag Library JSP标准标签库

    是由Apache组织提供的开源的免费的jsp标签 <标签>

    作用:用于简化和替换jsp页面上的java代码

    使用步骤:

    1. 导入jstl相关jar包
    2. 引入标签库:taglib指令: <%@ taglib %> 放在jsp头部
    3. 使用标签

    常用的JSTL标签

    1. if:相当于java代码的if语句

      1. 属性:
        • test 必须属性,接受boolean表达式
          • 如果表达式为true,则显示if标签体内容,如果为false,则不显示标签体内容
          • 一般情况下,test属性值会结合el表达式一起使用
        1. 注意:
          • c:if标签没有else情况,想要else情况,则可以在定义一个c:if标签
    2. choose:相当于java代码的switch语句

      1. 使用choose标签声明 相当于switch声明
      2. 使用when标签做判断 相当于case
      3. 使用otherwise标签做其他情况的声明 相当于default
    3. foreach:相当于java代码的for语句

    三层架构:软件设计架构

    1. 界面层(表示层):用户看的得界面。用户可以通过界面上的组件和服务器进行交互
    2. 业务逻辑层:处理业务逻辑的。
    3. 数据访问层:操作数据存储文件。

    MVC
    model: dao(data access object) 操作数据库 包名xx.xx.dao
    view: jsp 包名xx.xx.web, 接受用户参数, 封装数据完成显示
    controller: service 包名xx.xx.service 业务逻辑操作

    Filter: 过滤器

    概念

    • 生活中的过滤器:净水器,空气净化器,土匪、
    • web中的过滤器:当访问服务器的资源时,过滤器可以将请求拦截下来,完成一些特殊的功能。
    • 过滤器的作用:
      • 一般用于完成通用的操作。如:登录验证、统一编码处理、敏感字符过滤...

    快速入门

    步骤

    1. 定义一个类,实现接口`Filter`
    2. 复写方法
    3. 配置拦截路径
        1. `web.xml`
        2. 注解
    

    代码

        @WebFilter("/*")//访问所有资源之前,都会执行该过滤器
        public class FilterDemo1 implements Filter {
            @Override
            public void init(FilterConfig filterConfig) throws ServletException {
        
            }
        
            @Override
            public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
                System.out.println("filterDemo1被执行了....");
        
                //放行
                filterChain.doFilter(servletRequest,servletResponse);
        
            }
        
            @Override
            public void destroy() {
        
            }
        }
    

    过滤器细节

    web.xml配置

        <filter>
            <filter-name>demo1</filter-name>
            <filter-class>cn.itcast.web.filter.FilterDemo1</filter-class>
        </filter>
        <filter-mapping>
            <filter-name>demo1</filter-name>
            <!-- 拦截路径 -->
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    

    过滤器执行流程

    1. 执行过滤器
    2. 执行放行后的资源
    3. 回来执行过滤器放行代码下边的代码

    过滤器生命周期方法

    1. init:在服务器启动后,会创建Filter对象,然后调用init方法。只执行一次。用于加载资源
    2. doFilter:每一次请求被拦截资源时,会执行。执行多次
    3. destroy:在服务器关闭后, Filter对象被销毁。如果服务器是正常关闭,则会执行destroy方法。只执行一次。用于释放资源

    过滤器配置详解

    • 拦截路径配置:

      1. 具体资源路径: /index.jsp 只有访问index.jsp资源时,过滤器才会被执行
      2. 拦截目录: /user/* 访问/user下的所有资源时,过滤器都会被执行
      3. 后缀名拦截: *.jsp 访问所有后缀名为jsp资源时,过滤器都会被执行
      4. 拦截所有资源:/* 访问所有资源时,过滤器都会被执行
    • 拦截方式配置:资源被访问的方式

      • 注解配置:
        • 设置dispatcherTypes属性
          1. REQUEST:默认值。浏览器直接请求资源
          2. FORWARD:转发访问资源
          3. INCLUDE:包含访问资源
          4. ERROR:错误跳转资源
          5. ASYNC:异步访问资源
      • web.xml配置
        • 设置<dispatcher></dispatcher>标签即可

    过滤器链(配置多个过滤器)

    执行顺序:如果有两个过滤器:过滤器1和过滤器2(入栈出栈)

    1. 过滤器1
    2. 过滤器2
    3. 资源执行
    4. 过滤器2
    5. 过滤器1
    • 过滤器先后顺序问题:
      1. 注解配置:按照类名的字符串比较规则比较,值小的先执行
        • 如: AFilterBFilter, AFilter就先执行了。
      2. web.xml配置: <filter-mapping>谁定义在上边,谁先执行

    Listener:监听器

    概念:web的三大组件之一。

    • 事件监听机制
      • 事件 :一件事情
      • 事件源 :事件发生的地方
      • 监听器 :一个对象
      • 注册监听:将事件、事件源、监听器绑定在一起。 当事件源上发生某个事件后,执行监听器代码

    ServletContextListener:监听ServletContext对象的创建和销毁

    • 方法:
      • void contextDestroyed(ServletContextEvent sce)ServletContext对象被销毁之前会调用该方法
      • void contextInitialized(ServletContextEvent sce)ServletContext对象创建后会调用该方法
    • 步骤:
      1. 定义一个类,实现ServletContextListener接口

      2. 复写方法

      3. 配置

        1. web.xml
            <listener>
                <listener-class>cn.web.listener.ContextLoaderListener</listener-class>
            </listener>
        
            * 指定初始化参数<context-param>
        
        1. 注解:
          • @WebListener
  • 相关阅读:
    面试遇到的相关问题
    webpack的学习之旅
    ajax请求数据
    css选择器
    对BFC的理解
    对React的理解
    获取DOM的真实节点
    翻转拼图网页小游戏制作
    acm比赛刷题小技巧
    动态规划 背包九讲的实现。
  • 原文地址:https://www.cnblogs.com/gaoyongjian/p/11928983.html
Copyright © 2020-2023  润新知