Cookie和Session的工作流程及区别(附代码案例)

这篇具有很好参考价值的文章主要介绍了Cookie和Session的工作流程及区别(附代码案例)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

一、 HTTP协议

1.1 为什么HTTP协议是无状态的?

1.2 在HTTP协议中流式传输和分块传输编码的区别

二、Cookie和Session

2.1 Cookie

2.2 Session

2.3 Cookie和Session的区别

2.4 浏览器端禁用了cookie,session能否正常使用呢?

三、servlet中与Cookie和Session相关的API

3.1 HttpServletRequest 类中的相关方法:

3.2 HttpServletResponse 类中的相关方法

3.3 HttpSession 类中的相关方法 

3.4 Cookie 类中的相关方法

四、实现模拟登录流程


一、 HTTP协议

我们知道,HTTP协议是一种无状态协议,这意味着每个HTTP请求都是独立的,服务器不会记住之前的任何请求,也不会记录当前请求与之前请求的关系。每个请求都是独立的、不相关的,服务器不会保留任何有关请求或响应的信息,也不会保存客户端的状态。

1.1 为什么HTTP协议是无状态的?

  1. 简化服务器设计: 无状态性使得服务器不需要为每个客户端维护状态信息。这降低了服务器的复杂性,减少了服务器端的资源消耗。服务器可以独立处理每个请求,而不需要关心之前或之后的请求。

  2. 提高可伸缩性: 无状态性有助于提高服务器的可伸缩性。由于服务器不需要维护客户端的状态信息,它可以轻松地处理大量并发请求,而无需担心与之前请求的状态冲突或耦合。

  3. 容错性: 无状态性使得系统更加容错,因为即使某个请求或会话失败,整个系统仍然可以继续工作。客户端可以重新发起一个新的请求,而不必担心服务器的状态。

  4. 缓存支持: 无状态性有助于缓存服务器的实现。由于每个请求都是独立的,可以轻松地缓存和重用响应,从而提高性能和减少网络流量。

  5. 简化客户端: 无状态性也简化了客户端的设计。客户端不需要在请求之间维护服务器的状态信息,因此客户端可以更加简单和通用化。

虽然HTTP本身是无状态的,但为了支持某些应用程序的需要,通常会使用会话管理机制(如Cookie或会话标识符)来在一系列请求之间维护客户端状态。这种会话管理机制允许在HTTP请求之间传递和维护状态信息,以便实现用户身份验证、购物车管理等功能。但这些机制通常是基于无状态的HTTP协议之上构建的,以平衡简化性和功能需求。

需要注意的是。虽然HTTP协议为无状态协议,但是与HTTP支持长连接不冲突。

长连接(也称为持久连接)是一种HTTP/1.1协议引入的技术,它允许客户端和服务器在一个TCP连接上发送多个HTTP请求和响应,而不需要为每个请求/响应都建立一个新的TCP连接。这种技术可以显著减少网络连接的建立和关闭开销,提高网络传输效率,同时也能够更好地支持HTTP协议的特性,如流式传输和分块传输编码等。

在长连接中,客户端和服务器之间的TCP连接在一个HTTP请求/响应完成后不会立即关闭,而是继续保持连接状态,等待下一个HTTP请求/响应。这样,当客户端发送下一个HTTP请求时,可以直接利用之前的TCP连接,不需要再进行TCP握手和挥手等操作,从而节省了网络资源和时间。

因此,虽然HTTP协议是无状态的,但是通过使用长连接技术,可以在保持无状态的前提下提高HTTP协议的效率和性能。需要注意的是,长连接需要服务器和客户端都支持,否则无法建立长连接。同时,长连接也可能会导致资源占用问题,因此需要合理使用和配置。

以下为浏览器请求BIng主页的抓包信息(Connection:keep-alive就为长连接的意思):

在HTTP/1.1中,如果客户端请求头中没有明确指定Connection头字段,那么服务器会默认使用长连接,即保持TCP连接处于打开状态,直到客户端或服务器明确要求关闭连接为止。这种方式可以减少每次请求和响应时建立和关闭TCP连接的开销,提高网络传输效率和性能。但需要注意的是,长连接并不是在所有情况下都适用,有时候也需要根据实际情况关闭连接。

Cookie和Session的工作流程及区别(附代码案例)

1.2 在HTTP协议中流式传输和分块传输编码的区别

流式传输和分块传输编码都是HTTP协议中用于数据传输的技术,它们的区别如下:

  1. 数据分割方式不同:流式传输是将响应数据(如HTML,JSON等)按照一定的块大小进行分割,逐步发送到客户端;而分块传输编码是将数据分成若干个块(Chunk),每个块有独立的长度信息和数据内容,使用分块传输编码方式将数据分成多个块进行传输,每个块之间用一个CRLF(回车换行)分隔符分开。

  2. 响应方式不同:流式传输是一次性发送响应数据,客户端在接收到第一个数据块时就可以开始处理;而分块传输编码则是将响应数据分成多个块逐个发送,客户端需要在接收完所有块后再进行处理。

  3. 大小控制不同:流式传输中每个数据块的大小是固定的,由服务器决定;而分块传输编码中每个块的大小是不固定的,客户端需要通过读取长度信息来确定每个块的大小。

  4. 读取速度,应用场景不同:流式传输是在客户端接收到第一个数据块时就可以开始处理,而不必等待整个响应数据全部接收完毕。这种方式可以减少客户端等待时间,提高响应速度。流式传输常用于需要实时更新的数据场景,如股票行情、新闻快讯等。而分块传输编码客户端接收到每个块后,会先读取块的长度信息,再读取对应长度的数据内容。这种方式可以避免一次性传输大量数据造成的网络拥塞和连接中断,并且可以让客户端在接收到部分数据时就开始处理,从而提高传输效率和响应速度。

总之,流式传输和分块传输编码都可以提高HTTP数据传输的效率和响应速度,但应根据具体的场景和需求进行选择和使用。流式传输适用于需要实时更新的数据场景,如股票行情、新闻快讯等;而分块传输编码适用于需要传输大文件的场景,能够避免一次性传输造成的网络拥塞和连接中断。

二、Cookie和Session

2.1 Cookie

上面我们说到,由于HTTP是无状态的,所以需要引入Cookie来解决这一问题。

Cookie允许服务器在客户端上存储少量的数据,例如:用户的身份,偏好,购物车信息以及与客服对话等。以便在之后的HTTP请求中将该数据发送回服务器,通过Cookie,服务器可以识别和跟踪用户的身份和状态,从而提供更加个性化和定制化的服务。

举个例子:就比如在我们使用淘宝的时候,与客服进行对话,有的时候我们并不是一直在对话界面等待对方回复,而是需要切到其他APP中,过了一会再回来的时候询问客服的时候,客服就会通过客户请求时候所发送的Cookie来查看上下文,进而实现后续的对话。

回顾以下,Cookie是什么,从哪里来,发送到哪去,存储在哪?

  1. 是什么:Cookie是一种由服务器发送到客户端的小型数据文件,它包含了有关用户和网站之间的交互信息,以及有关用户的偏好和状态信息,
  2. 从哪里来:当服务器想要在客户端上创建或者添加一个新的Cookie时候,会将Set-Cookie字段添加到HTTP响应头中,告诉客户端应该如何创建或跟新Cookie(Set-Cookie中是由开发者自己定义的键值对)。
  3. 发送到哪去:客户端在接收到带有Set-Cookie字段的HTTP响应时,会解析该字段,并根据其中的信息在本地创建或更新对应的Cookie。之后,在该客户端向同一服务器发出HTTP请求时,该Cookie信息会被自动添加到HTTP请求头中,从而让服务器能够识别并跟踪用户的身份和状态。
  4. 存储在哪:在客户端,Cookie通常存储在Web浏览器所在的硬盘中,浏览器会根据域名来分别存储。

对于第四点,可以在浏览器的URL处,点击查看:

Cookie和Session的工作流程及区别(附代码案例)

Cookies中的内容通常是纯文本,而且通常是以ASCII字符集编码的,因此在大多数情况下,Cookies中的值是由英文字符和数字组成的: 

Cookie和Session的工作流程及区别(附代码案例)

2.2 Session

Session(会话)是Web应用程序中的一个概念,它指的是一系列相关的HTTP请求和响应,通常用于在客户端和服务器之间维护一些状态信息。在一个会话中,服务器会创建一个唯一的Session ID(会话标识符),并将该Session ID发送给客户端浏览器。客户端在之后的每个HTTP请求中都会携带该Session ID,以便服务器能够识别并跟踪该客户端的身份和状态。

为了加深理解,我们来看一组登录流程:

Cookie和Session的工作流程及区别(附代码案例)Session通常是基于Cookie实现的。具体来说,当服务器创建一个新的Session时,它会在响应头中添加一个Set-Cookie字段,其中包含了一个名为SESSIONID的Cookie值,该值就是新创建的Session ID。客户端在收到该响应时会将该Cookie值保存在浏览器中,并在之后的每个HTTP请求中发送回服务器。服务器在接收到每个HTTP请求时都会检查该请求中是否包含了有效的Session ID,如果存在则说明该请求属于某个已经创建的Session,服务器就可以根据该Session ID来识别和跟踪客户端的身份和状态。

需要注意的是:Cookie是有存储期限的,越敏感的网站存储期限越短。

2.3 Cookie和Session的区别

Cookie和Session是Web开发中常用的两种技术,用于在服务器和客户端之间保持状态信息。它们的区别如下:

  1. 存储位置不同:Cookie是存储在客户端的浏览器中的文本文件,而Session是存储在服务器端的内存中或者数据库中的键值对。

  2. 安全性不同:由于Cookie是存储在客户端中的,所以存在被窃取或者篡改的风险。而Session存储在服务器端,相对来说更加安全。

  3. 存储容量不同:Cookie的存储容量较小,通常为4KB左右,而Session的存储容量可以比较大,但会占用服务器的内存或者存储资源。

  4. 生命周期不同:Cookie可以设置过期时间,可以长期保存在客户端的浏览器中;而Session在用户关闭浏览器或者超过一定时间后,会自动销毁。

  5. 使用方式不同:Cookie是通过在服务器端发送HTTP响应头来设置的,客户端的浏览器会自动保存Cookie,下一次请求时会自动发送Cookie到服务器端。而Session需要在服务器端通过某种方式生成Session ID,并将Session ID存储在Cookie中或者URL参数中,客户端浏览器会将Session ID发送到服务器端,服务器根据Session ID来获取Session数据。

综上所述,Cookie和Session都可以用于在服务器和客户端之间保持状态信息,但它们的应用场景和特点有所不同,具体使用哪种技术要根据实际需求和安全要求来决定。

2.4 浏览器端禁用了cookie,session能否正常使用呢?

       当浏览器禁用了Cookie时,会影响到传统的基于Cookie的Session管理,因为Session通常依赖于浏览器发送的Cookie来标识和维护会话状态。如果浏览器禁用了Cookie,会导致Session无法正常工作,因为无法在客户端存储Session标识信息。

        但是,有一种替代方法可以在浏览器禁用Cookie时维护Session状态,那就是URL重写或称为URL重定向。这种方法将会话标识信息添加到URL中,而不是依赖于Cookie。

以下是如何使用URL重写来维护Session状态的一般步骤:

  1. 当用户访问网站时,服务器会为其创建一个唯一的会话标识,通常是一个长字符串,将其存储在服务器端。

  2. 对于每个后续的请求,服务器会将该会话标识信息附加到URL中,例如:http://example.com/page?sessionid=abcdef123456.

  3. 服务器接收到请求后,会检查URL中的会话标识信息,并将其与服务器端存储的会话状态相关联。

  4. 服务器根据会话标识提供相应的会话数据,如用户登录状态、购物车内容等。

  5. 当用户发出下一个请求时,会话标识信息将继续被传递,以维护会话状态。

使用URL重写来维护会话状态的方法,即使浏览器禁用了Cookie,也能正常工作。然而,需要注意的是,这会导致URL包含会话标识信息,可能会有一些安全和隐私问题。因此,在实现时,需要谨慎处理敏感信息,并采取适当的安全措施,如使用HTTPS来加密通信。

三、servlet中与Cookie和Session相关的API

3.1 HttpServletRequest 类中的相关方法:

方法 描述
HttpSession  getSession() 在服务器中获取会话. 参数如果为 true, 则当不存在会话时新建会话; 参数如果
为 false, 则当不存在会话时返回 null
Cookie[] getCookies() 返回一个数组, 包含客户端发送该请求的所有的 Cookie 对象. 会自动把
Cookie 中的格式解析成键值对.

3.2 HttpServletResponse 类中的相关方法

方法 描述
void addCookie(Cookie cookie) 把指定的 cookie 添加到响应中.

3.3 HttpSession 类中的相关方法 

方法 描述
Object getAttribute(String name) 该方法返回在该 session 会话中具有指定名称的对象,如果没
有指定名称的对象,则返回 null.
void setAttribute(String name, Object value) 该方法使用指定的名称绑定一个对象到该 session 会话
boolean isNew() 判定当前是否是新创建出的会话

3.4 Cookie 类中的相关方法

每个 Cookie 对象就是一个键值对

方法 描述
String getName() 该方法返回 cookie 的名称。名称在创建后不能改变。(这个值是 Set
Cooke 字段设置给浏览器的)
String getValue() 该方法获取与 cookie 关联的值
void setValue(String newValue) 该方法设置与 cookie 关联的值。
  • HTTP 的 Cooke 字段中存储的实际上是多组键值对. 每个键值对在 Servlet 中都对应了一个 Cookie对象.
  • 通过 HttpServletRequest.getCookies() 获取到请求中的一系列 Cookie 键值对.
  • 通过 HttpServletResponse.addCookie() 可以向响应中添加新的 Cookie 键值对.

四、实现模拟登录流程

第一步,约定前后端接口。
我们需要实现两套交互逻辑,一是登录跳转,二是获取主页。
登录跳转约定:
约定使用POST请求,响应采用302重定向。

package login;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

/**
 * Created with IntelliJ IDEA.
 * Description:
 * User: 86136
 * Date: 2023-04-01
 * Time: 18:53
 */
@WebServlet("/loginServlet")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String username = req.getParameter("username");
        String password = req.getParameter("password");
        //因为这里我们主要是演示Cookie和Session,所以为了简化流程,并不将用户名和密码
        //存入到数据库中,而是直接定死。约定用户名合法为 Jay 和 Lin
        //密码合法为123。
        if(!username.equals("Jay") && !username.equals("Lin")) {
            //登录失败
            System.out.println("用户名错误");
            resp.sendRedirect("login.html");
            return;
        }
        if (!password.equals("123")) {
            //登录失败
            System.out.println("密码错误");
            resp.sendRedirect("login.html");
            return;
        }
        //登录成功
        //1.创建一个会话
        HttpSession session = req.getSession(true);
        //2.把当前的用户名保存在会话中
        //void setAttribute(String var1, Object var2);
        session.setAttribute("username",username);
        //初始情况下设置登录次数
        session.setAttribute("count",0);
        //3.重定向到主页
        resp.sendRedirect("indexServlet");
    }
}

分析:

其中的getSession(true):判定当前请求是否已经有对应的会话(拿着来自客户端的请求中Cookie里的sessionId查一下),如果sessionId不存在,或者没有被查到,那么就创建一个新的会话,并插入到哈希表中。如果查到了,就直接返回查到的结果。 

创建新的会话的流程如下:构造一个HttpSession对象,构造唯一的sessionId,把这个键值对插入到哈希表中,最后把sessionId设置到响应报文的Set-Cookie字段中,有服务器发送给浏览器。

获取主页约定:
采用GET请求,响应返回一个页面:

package login;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;

@WebServlet("/indexServlet")
public class IndexServlet extends HttpServlet {
    //因为通过浏览器是通过重定向来发送请求的,所以以下为doGet
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        //需要先判定用户的登录状态
        //如果用户没有登录,要求先登录,如果登录了,则根据 会话 中的用户名,来显示到页面上

        //这个操作不会触发会话的创建
        HttpSession session = req.getSession(false);
        if (session == null) {
            //未登录状态
            System.out.println("用户未登录");
        }
        //已经登录,取出会话信息
        String username = (String) session.getAttribute("username");
        Integer cnt = (Integer) session.getAttribute("count");
        //访问次数加1
        cnt++;
        //写回到会话中
        session.setAttribute("count",cnt);
        //构造页面
        resp.setContentType("text/html; charset=utf8");
        resp.getWriter().write("<h3>欢迎" + username + "</h3> <h4>这个页面已经被访问了"+cnt+"次</h4>");
    }
}

第二步,编写前端交互页面
我们的重点是来学习登录的逻辑,因此登录的界面不需要很好看很复杂,只要能够有两个输入框和一个提交按钮让我们输入账号密码就行。目标页面如下:

Cookie和Session的工作流程及区别(附代码案例)

前面我们约定了登录的跳转采用post请求,由于场景很简单,我们直接使用form表单构造post请求就可以了:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <form action="loginServlet" method="post">
        <input type="text" name="username">
        <br>
        <input type="password" name="password">
        <br>
        <input type="submit" name="提交">
    </form>
</body>
</html>

其中input标签的name属性就对应键值对的key,输入的内容就对应键值对的value。

效果演示:

Cookie和Session的工作流程及区别(附代码案例)

 Cookie和Session的工作流程及区别(附代码案例)

下面利用抓包结果来进一步加深理解:

第一次交互

请求部分:注意,第一次请求是没有Cookie的。

Cookie和Session的工作流程及区别(附代码案例)

 服务器返回给登录界面的响应部分:

Cookie和Session的工作流程及区别(附代码案例)

 第二次交互

在登录状态下,主页向服务器发起访问请求:

Cookie和Session的工作流程及区别(附代码案例)

这个带有Cookie的get请求到达服务器的时候,Servlet会在getSession方法中根据sessionId来查询HttpSession对象:

Cookie和Session的工作流程及区别(附代码案例)

 由于sessionId查的到,于是就返回信息。

 服务器返回给浏览器的响应部分:

Cookie和Session的工作流程及区别(附代码案例)文章来源地址https://www.toymoban.com/news/detail-413726.html

到了这里,关于Cookie和Session的工作流程及区别(附代码案例)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Informatica使用工作流程及案例1

    操作流程 ①定义源 ②定义目标 ③创建映射 ④定义任务 ⑤创建工作流 ⑥工作流调度监控 ⑦查验数据 连接D,并定义源、连接源   D:定义目标 通过源定义目标 D:定义好的目标表的表结构生成到目标数据库EDW层     D:创建映射      W:定义任务 W:执行工作流 M:执行监测  通过

    2024年02月09日
    浏览(43)
  • 【Vue】快速入门案例与工作流程的讲解

      🎉🎉欢迎来到我的CSDN主页!🎉🎉 🏅我是Java方文山,一个在CSDN分享笔记的博主。📚📚 🌟在这里,我要推荐给大家我的专栏《Vue快速入门》。🎯🎯 🚀无论你是编程小白,还是有一定基础的程序员,这个专栏都能满足你的需求。我会用最简单易懂的语言,带你走进V

    2024年02月07日
    浏览(37)
  • HTTP代理与HTTPS代理在工作流程上有哪些区别

         HTTP代理和HTTPS代理都是常见的代理技术,可以实现隐藏客户端IP地址、突破网络封锁、加速网站访问、过滤网络内容等功能。本文将介绍HTTP代理和HTTPS代理在工作流程上的区别。 HTTP代理的工作流程 客户端向代理服务器发送HTTP请求      当客户端需要访问某个网站时,

    2024年02月09日
    浏览(35)
  • 轻松构建低代码工作流程:简化繁琐任务的利器

    前言 近年来,企业一直在寻求简化运营和提高效率的方法,较为流行的一个解决方案是使用低代码自动化工作流。现如今,不管是审批逻辑、并行流程还是动态逻辑,开发人员逐渐会愿意选择引入某种业务流程管理(BPM)与自己的应用程序集成。原因显而易见,因为业务流程

    2024年04月16日
    浏览(34)
  • camunda工作流实战项目(表单设计器+流程编辑器,零代码创建流程)

    基于ruoyi平台和camunda工作流开发而成,结合bpmn.js流程编辑器和vform表单设计器,实现常规流程零代码创建。 具备流程中心的能力,支持外部任务,可协调多个业务系统协同工作 具备SaaS平台的能力,支持多租户,各业务系统可作为租户,创建自己的流程,通过外部任务与自身

    2024年02月12日
    浏览(53)
  • git工作原理、从仓库创建到代码拉取提交整套流程

    1、先看 Git 工作原理图,该图参考 gitee简单使用_gitea新建vue仓库选择什么.gitignore模板-CSDN博客 git 工作原理图理解: (1)Workspace:工作区,也就是我们的代码所在的地方 (2)Index / Stage:暂存区 (3)Repository:仓库区(或本地仓库 .git) (4)Remote:远程仓库 2、创建仓库流程

    2024年03月14日
    浏览(54)
  • 【Python】PySpark 数据计算 ③ ( RDD#reduceByKey 函数概念 | RDD#reduceByKey 方法工作流程 | RDD#reduceByKey 语法 | 代码示例 )

    RDD#reduceByKey 方法 是 PySpark 中 提供的计算方法 , 首先 , 对 键值对 KV 类型 RDD 对象 数据 中 相同 键 key 对应的 值 value 进行分组 , 然后 , 按照 开发者 提供的 算子 ( 逻辑 / 函数 ) 进行 聚合操作 ; 上面提到的 键值对 KV 型 的数据 , 指的是 二元元组 , 也就是 RDD 对象中存储的数据是

    2024年02月14日
    浏览(50)
  • TCP/IP协议工作原理与工作流程

    使用OSI模型来描述一个网络中的各个协议层,如下: TCP/IP协议,英文全称Transmission Control Protocol/Internet Protocol,包含了一系列构成互联网基础的网络协议,是Internet的核心协议。TCP/IP协议是一个协议簇,包含了应用协议、传输协议、网际互联协议和路由控制协议。如下图: 应

    2024年04月25日
    浏览(46)
  • 网络安全等级保护测评:工作流程及工作内容

    **一、** 网络安全等级保护测评过程概述 网络安全等级保护测评工作过程包括四个基本测评活动: 测评准备活动、方案编制活动、现场测评活动、报告编制活动 。而测评相关方之间的沟通与洽谈应贯穿整个测评过程。每一项活动有一定的工作任务。如下表。 01 基本工作流程

    2024年02月06日
    浏览(51)
  • AES工作流程

    工作流程 模式 1:加密 ⚫ 复位EN 重置AES模块 ⚫ 设置模式寄存器mode[1:0]=00,设置流数据处理模式寄存器CHMOD[1:0] ⚫ 写AES_KEYRx寄存器,CTR和CBC模式下写AES_IVRx寄存器 ⚫ 写EN=1,使能AES ⚫ 写AES_DINR 寄存器4次 ⚫ 等待CCF标志置起 ⚫ 从AES_DOUTR分4次读出加密结果 ⚫ 对于同一个key,重

    2024年02月01日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包