• HTTP中的Cookie、Set-Cookie


    请求头Cookie、响应头Set-Cookie

    但是,我们又需要在不同请求-响应之间,来区分请求-响应是不是同一个用户发起的。


    比如:用户购买越多,折扣越多的活动,就需要我们分辨出不同请求-响应之间的逻辑关系

    1. // 专门用来发凭证(cookie)的
    2. @GetMapping("/set-cookie")
    3. public void setCookie(HttpServletRequest request, HttpServletResponse response) throws Exception {
    4. // servlet 里的处理方式
    5. // 不要用中文,因为 cookie 的设置和携带是放在请求响应头中的,字符集编码是西文格式,不支持中文
    6. Cookie cookie1 = new Cookie("name", "chen");
    7. Cookie cookie2 = new Cookie("learning", "java");
    8. Cookie cookie3 = new Cookie("time", "2022");
    9. response.addCookie(cookie1);
    10. response.addCookie(cookie2);
    11. response.addCookie(cookie3);
    12. // 设置响应体的字符集编码
    13. response.setCharacterEncoding("utf-8");
    14. // 设置响应体的格式 —— 纯文本
    15. response.setContentType("text/plain");
    16. // 写响应内容
    17. response.getWriter().println("cookie 设置成功,请观察开发者工具的 网络面板 和 应用面板");
    18. }
    19. // 记录用户访问次数
    20. private final Map countMap = new HashMap<>(); // 这个 map 就是就是房子
    21. private final Map> 模拟session的结构 = new HashMap<>();
    22. // 模拟一个 session
    23. @GetMapping("/use-cookie")
    24. public void useCookie(HttpServletRequest request, HttpServletResponse response) throws Exception {
    25. String id = null;
    26. Cookie[] cookies = request.getCookies();
    27. if (cookies != null) {
    28. for (Cookie cookie : cookies) {
    29. if (cookie.getName().equals("id")) {
    30. id = cookie.getValue();
    31. }
    32. }
    33. }
    34. Integer count = null;
    35. if (id == null) {
    36. // cookie 中没有携带 id,认为是用户第一次访问
    37. // 将当前时间戳当作这次用户的访问标识
    38. long ts = System.currentTimeMillis();
    39. System.out.println("是第一次访问");
    40. id = String.valueOf(ts);
    41. Cookie cookie = new Cookie("id", id);
    42. response.addCookie(cookie);
    43. count = 1;
    44. countMap.put(id, count);
    45. } else {
    46. // cookie 中携带了 id,说明之前是访问过的
    47. System.out.println("不是第一次访问");
    48. count = countMap.get(id);
    49. if (count == null) {
    50. // 由于程序重启,内存中的 countMap 的数据丢了
    51. // 所以当成第一次吧
    52. count = 1;
    53. } else {
    54. count += 1;
    55. }
    56. countMap.put(id, count);
    57. }
    58. System.out.println("现在统计的所有人的访问次数:");
    59. for (Map.Entry entry : countMap.entrySet()) {
    60. System.out.printf("%s => %d\r\n", entry.getKey(), entry.getValue());
    61. }
    62. // 设置响应体的字符集编码
    63. response.setCharacterEncoding("utf-8");
    64. // 设置响应体的格式 —— 纯文本
    65. response.setContentType("text/plain");
    66. // 写响应内容
    67. if (count == 1) {
    68. response.getWriter().println("第 1 次访问,您的 id 是: " + id);
    69. } else {
    70. response.getWriter().printf("欢迎 %s 的第 %d 次访问\r\n", id, count);
    71. }
    72. }

    Session会话

     

    Session是保存在服务器的,专属某次会话的,一组数据。可以跨请求访问到(生命周期是跨请求存在的)。通常: Session 中保存的数据也可以视为name-value
    一般利用Cookie设置session-id,这样,客户端和服务器之间仍然使用cookie机制,只是cookie 只传递id即可。主要数据全部保存在服务器。

    Cookie和Session在实际场景应用

    Cookie和Session在实际场景中最常见的应用——在线用户管理(如何在一次HTTP请求-响应中判断用户是否登录,以及登录用户是谁)
    用户管理:用户注册(建档)、用户登录(当前用户在线)、用户退出(当前用户下线)、用户注销(销档)、
    获取当前登录用户(currentUser):判断用户是否在线;如果在线,得到当前用户是谁。

    和Cookie、Session有关系的,其实是在线用户状态的维护:用户登录、用户退出、获取当前登录用户信息;当前用户保存Session中。

    用户注册:为用户建档,为用户信息做持久化保存——在数据库中维护一条用户信息,
    记录用户基本信息。用户登录时通过验证这份基本信息来证明“你”是“你”

    用户名+密码的方案——设计一个最简单的数据库表:
    用户表(users)

    {该用户的唯一标识符(uid) 、

    该用户的用名——登录时唯一验证,需要保持记录的唯一性(username)、

    密码(暂时明文)}

    用户注册示例

    数据库准备

     

    注册场景

    1. // 1. 支持 POST
    2. // 2. 资源路径是 /register.do
    3. @PostMapping("/register.do")
    4. // 3. 希望结果是重定向,所以,不需要使用 @ResponseBody 注解修饰方法
    5. // 4. 方法的返回值类型是 String,到时候会返回 "redirect:..."
    6. // 5. 需要读取用户提交的
    7. // 5. 所以,方法的形参有两个,分别使用 @RequestParam 修饰
    8. public String register(@RequestParam("username") String username, @RequestParam("password") String password) {
    9. System.out.println("用户注册:username = " + username + ", password = " + password);
    10. // 1. TODO: 本来应该要去完成的参数合法性校验
    11. // 2. 执行 SQL 语句
    12. String sql = "insert into users (username, password) values (?, ?)";
    13. try (Connection c = DBUtil.connection()) {
    14. try (PreparedStatement ps = c.prepareStatement(sql)) {
    15. ps.setString(1, username);
    16. ps.setString(2, password);
    17. ps.executeUpdate();
    18. }
    19. } catch (SQLException exc) {
    20. System.out.println("用户注册失败");
    21. exc.printStackTrace(System.out);
    22. return "redirect:/register.html"; // 失败后重定向到 注册页
    23. }
    24. System.out.println("用户注册成功");
    25. return "redirect:/login.html"; // 目前用户最终看到 404
    26. }

    登录场景

    1. // 准备 POST /login.do 动态资源
    2. @PostMapping("/login.do")
    3. // 要重定向,不需要 @ResponseBody 注解修饰方法
    4. // 返回值类型是 String
    5. // 要读取用户输入的用户名 + 密码信息,和注册一样
    6. // 要创建 session 对象,所以,形参中有 HttpServletRequest request
    7. public String login(
    8. @RequestParam("username") String usernameInput,
    9. @RequestParam("password") String passwordInput,
    10. HttpServletRequest request) {
    11. System.out.println("用户登录:username = " + usernameInput + ", password = " + passwordInput);
    12. // TODO: 参数合法性校验
    13. // 通过数据库查询该用户的信息
    14. // Integer uid = null; // 使用 Integer 而不是 int,是 Integer 可以保存 null 这种特殊值
    15. // String username = null;
    16. // String password = null;
    17. User user = null;
    18. try (Connection c = DBUtil.connection()) {
    19. String sql = "select uid, username, password from users where username = ? and password = ?";
    20. try (PreparedStatement ps = c.prepareStatement(sql)) {
    21. ps.setString(1, usernameInput); // 把用户填写的用户名添加到 SQL 参数
    22. ps.setString(2, passwordInput); // 同理,密码
    23. // 带结果的,并且结果只有 1 条 或者 0 条
    24. try (ResultSet rs = ps.executeQuery()) {
    25. if (rs.next()) {
    26. // 查询到了 1 条记录,说明用户名 + 密码正确
    27. int uid = rs.getInt("uid");
    28. String username = rs.getString("username"); // 实际上,这两个字段可以不查询
    29. String password = rs.getString("password"); // 实际上,这两个字段可以不查询
    30. user = new User(uid, username, password);
    31. } else {
    32. // 一条记录都没有查询到,这里什么都不干
    33. // 这里加上 else 的目的是为了写这个注释,以后我就不写了
    34. }
    35. }
    36. }
    37. } catch (SQLException exc) {
    38. System.out.println("登录失败 by 数据库 SQL 执行失败,如果符合 HTTP 语义,应该是 500 错误");
    39. exc.printStackTrace(System.out);
    40. return "redirect:/login.html";
    41. }
    42. // 根据 uid 或者 username 或者 password 值来判断用户是否登录成功了,其中一个就行了
    43. if (user == null) {
    44. // 登录失败了
    45. System.out.println("登录失败 by 用户输入的参数有错误,如果符合 HTTP 语义,应该是 4XX 错误");
    46. return "redirect:/login.html";
    47. }
    48. // 登录成功
    49. // 获取 session 对象
    50. HttpSession session = request.getSession(); // 不带参数,默认是 create = true
    51. // 由于之前很大可能是没有 session 的
    52. // 所以内部所作的工作是
    53. // 1. 创建一个随机的
    54. // 2. 为这个 分配一个 HttpSession 对象(Map 结构)
    55. // 3. 响应中设置 Cookie(Set-Cookie):JSESSIONID=
    56. // 4. 所以,应该能在前端看到浏览器中有个 JSESSIONID 的 cookie
    57. // 把当前用户的信息(uid、username、password)保存到 session 中,key 我们随意指定
    58. // currentUser.uid、currentUser.username、currentUser.password
    59. // session.setAttribute("currentUser.uid", uid);
    60. // session.setAttribute("currentUser.username", username);
    61. // session.setAttribute("currentUser.password", password);
    62. session.setAttribute("currentUser", user);
    63. System.out.println("登录成功");
    64. return "redirect:/"; // 404
    65. }

    1. // GET /
    2. @GetMapping("/")
    3. // 在响应体中输入内容,所以 @ResponseBody 注解修饰方法 + 方法返回值类型是 String
    4. // 响应的 Content-Type 是 HTML,返回值直接当成 HTML 内容在对待
    5. // 由于需要获取 HttpSession 对象,所以,形参中有 HttpServletRequest request
    6. @ResponseBody
    7. public String getCurrentUser(HttpServletRequest request) {
    8. // Integer uid = null;
    9. // String username = null;
    10. // String password = null;
    11. User currentUser = null;
    12. HttpSession session = request.getSession(false);
    13. if (session != null) {
    14. System.out.println("有 session 对象");
    15. // 说明是有 Session 的
    16. // 获取的时候,必须保证 key 和 登录场景下 setAttribute 时的 key 一致
    17. // getAttribute(...) 的返回值类型是 Object
    18. // Object 是 Integer 的上级类型
    19. // Object -> Integer 是向下转型
    20. // 向下转型是不自然的,是有风险的,所以程序员(我们)明确告诉编译器是我要转的,职责我承担(有异常我自己弄)
    21. // 需要通过类型强制转换 (Integer)
    22. // uid = (Integer) session.getAttribute("currentUser.uid");
    23. // username = (String) session.getAttribute("currentUser.username");
    24. // password = (String) session.getAttribute("currentUser.password");
    25. currentUser = (User) session.getAttribute("currentUser");
    26. // 如果当时没有设置,getAttribute 的返回值是 null
    27. } else {
    28. System.out.println("没有 session 对象");
    29. // 反之没有 session,什么都不需要做
    30. }
    31. if (currentUser == null) {
    32. System.out.println("没有拿到 uid");
    33. // session 可能是 null 或者 session.getAttribute("currentUser.uid") 是 null
    34. // 不管是哪种可能,都代表本次请求-响应的用户,没有经历过登录的流程,也就是用户未登录
    35. return "用户未登录,请到 登录页 进行登录";
    36. } else {
    37. System.out.println("拿到 uid 了");
    38. // return String.format("当前登录用户为: uid = %d, username = %s, password = %s", uid, username, password);
    39. return String.format("当前登录用户为: %s", currentUser); // 这里虽然没写,但实际就是调用的 currnetUser.toString()
    40. }
    41. }

    用户退出

  • 相关阅读:
    SpringBoot学习笔记(3)——B站动力节点
    Kafka 3.0 面试 + 基础知识 + 原理
    C++交叉编译grpc
    【Numpy基础(01)NumPy数组的构造】
    第一百七十五回 如何创建放射形状渐变背景
    2022年最新阿里巴巴70道高级面试题,四面斩获阿里offer,直接定级为P7
    Greetings(状压DP,枚举子集转移)
    【2023,学点儿新Java-48】变量与运算符 (阶段性复习):关键字和保留,回顾:标识符的命名规则,变量的基本使用
    一文看懂这些海外社媒平台属性,跨境外贸必看
    uniapp视频播放功能
  • 原文地址:https://blog.csdn.net/XHT117/article/details/126426388