17.session不共享问题

news2024/9/21 16:39:53

问题


多台Tomcat并不共享session存储空间,当请求切换到不同的tomcat服务时导致数据丢失问题。

考虑到以后微服务部署多个项目,也就是多个tomcat就会出现session不共享问题。

替代方案满足条件


1.数据共享

2.内存存储,因为session就是基于内存的,访问效率高。

3.key,value结构。

解决方案


redis是存在于tomcat以外一个服务,就能实现数据共享。

基于redis实现共享session登录

UserService发送验证码、用户登录功能

package com.xkj.org.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.xkj.org.dto.LoginFormDTO;
import com.xkj.org.dto.Result;
import com.xkj.org.dto.UserDTO;
import com.xkj.org.entity.User;
import com.xkj.org.mapper.UserMapper;
import com.xkj.org.service.IUserService;
import com.xkj.org.utils.RedisConstants;
import com.xkj.org.utils.RegexUtils;
import com.xkj.org.utils.SystemConstants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpSession;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService{

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public Result sendCode(String phone, HttpSession session) {
        //检验手机号
        if (RegexUtils.isPhoneInvalid(phone)) {
            return Result.fail("手机号无效");
        }
        //生成验证码
        String code = RandomUtil.randomString(6);
        //存入Redis,key值定义带上业务功能,防止其他使用手机号的功能模块重复,设置过期时间
        stringRedisTemplate.opsForValue().set(RedisConstants.LOGIN_CODE_KEY + phone, code,
                RedisConstants.LOGIN_CODE_TTL, TimeUnit.MINUTES);
        //发送验证码给用户
        log.info("给用户发送验证码============={}", code);
        //返回
        return Result.ok("发送成功");
    }

    @Override
    public Result login(LoginFormDTO loginFormDTO, HttpSession session) {
        //校验手机号
        if (RegexUtils.isPhoneInvalid(loginFormDTO.getPhone())) {
            return Result.fail("手机号无效");
        }
        //校验验证码
        String cacheCode = stringRedisTemplate.opsForValue().get(RedisConstants.LOGIN_CODE_KEY + loginFormDTO.getPhone());
        String code = loginFormDTO.getCode();
        if (code == null || !code.equals(cacheCode)) {
            return Result.fail("验证码错误");
        }
        //根据手机号查询用户
        User user = query().eq("phone", loginFormDTO.getPhone()).one();
        if (user == null) {
            //用户不存在则创建用户
            user = createUser(loginFormDTO.getPhone());
        }
        //将用户放入redis
        //随机生成token,作为登录令牌
        String token = UUID.randomUUID().toString(true);
        //将user对象转化为Hash存储
        //存储到redis
        UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);

        Map<String, Object> map = BeanUtil.beanToMap(userDTO, new HashMap<>(3),
                CopyOptions.create().ignoreNullValue()
                                    //把所有字段的value都转成String,因为StringRedisTemplate的key-value只支持String类型
                                    .setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));
        String userKey = RedisConstants.LOGIN_USER_KEY + token;
        stringRedisTemplate.opsForHash().putAll(userKey, map);
        //设置有效期,否则大量用户登录,长期占用内存,半个小时过期,需要重新登录
        stringRedisTemplate.expire(userKey, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);
        //返回user的token
        return Result.ok(token);
    }

    private User createUser(String phone) {
        User user = new User();
        user.setPhone(phone);
        user.setNickName(SystemConstants.USER_NICK_NAME_PREFIX + RandomUtil.randomString(6));
        save(user);
        return user;
    }
}

拦截器

package com.xkj.org.interceptor;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.xkj.org.dto.UserDTO;
import com.xkj.org.utils.RedisConstants;
import com.xkj.org.utils.UserHolder;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
import java.util.concurrent.TimeUnit;

public class LoginInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate;

    public LoginInterceptor(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        //获取浏览器传递的token,在请求头上
        String token = request.getHeader("authorization");
        if(StrUtil.isBlank(token)) {
            //未登录,拦截
            response.setStatus(401);
            return false;
        }
        //基于token获取User
        Map<Object, Object> entries = stringRedisTemplate.opsForHash().entries(RedisConstants.LOGIN_USER_KEY + token);
        // 这里map不会为null,只需要判断是否有元素即可
        if(entries.isEmpty()) {
            //未登录,拦截,redis中的token可能失效了,需要重新登录
            response.setStatus(401);
            return false;
        }
        //将map转成UserDTO对象
        UserDTO userDTO = BeanUtil.fillBeanWithMap(entries, new UserDTO(), false);
        //保存用户信息到ThreadLocal
        UserHolder.saveUser(userDTO);
        //刷新token在redis中有效期,访问一次token的有效期重新设置为30分钟
        stringRedisTemplate.expire(RedisConstants.LOGIN_USER_KEY + token, RedisConstants.LOGIN_USER_TTL,
                TimeUnit.MINUTES);
        //5.放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
        //请求执行完毕,释放内存,threadlocal避免内存泄漏
        UserHolder.removeUser();
    }
}

给拦截器传递StringRedisTemplate对象

向拦截器中注入RedisTemplate对象,因为拦截器本身是通过new创建的,所以没有被spring管理,所以只有通过构造器的方式注入。 

package com.xkj.org.config;

import com.xkj.org.interceptor.LoginInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class MvcConfig implements WebMvcConfigurer{

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //添加拦截器,排除一些不需要拦截的url
        registry.addInterceptor(new LoginInterceptor(stringRedisTemplate))
        .excludePathPatterns(
                "/user/code",
                "/user/login",
                "/blog/hot",
                "/shop/**",
                "/shop-type/**",
                "/upload/**",
                "voucher/**"

        );
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2047532.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

NVDLA专题10:具体模块介绍——Planar Data Processor

概述 平面数据处理器(Planar Data Processor, PDP)沿宽x高的前两个维度平面执行操作&#xff0c;在NVDLA版中&#xff0c;PDPD旨在实现池化层&#xff0c;module定义在NV_NVDLA_pdp.v。支持最大、最小和平均池化方法。平面内的几个相邻输入元素将被发送到非线性函数来计算一个…

canvas的基础使用

canvas的基础使用 一、画一条直线二、线的属性设置三、防止多次绘制的样式污染四、闭合五、快捷绘制矩形六、绘制圆形七、绘制文字八、绘制图片js版dom版图片截取 一、画一条直线 画一条直线需要用到三个方法&#xff1a;cxt.moveTo、cxt.lineTo、cxt.stroke <canvas id&qu…

代码随想录训练营 Day32打卡 动态规划 part01 理论基础 509. 斐波那契数 70. 爬楼梯 746. 使用最小花费爬楼梯

代码随想录训练营 Day32打卡 动态规划 part01 一、 理论基础 动态规划中每一个状态一定是由上一个状态推导出来的&#xff0c;这一点就区分于贪心&#xff0c;贪心没有状态推导&#xff0c;而是从局部直接选最优的。 例如&#xff1a;有N件物品和一个最多能背重量为W 的背包…

【leetcode】两数相加-25-4

方法&#xff1a;遍历 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : val(x), next(nullptr) {}* ListNode(int x, ListNode *next) : val(x), ne…

探讨MySQL中 “约束“ 下的查询

目录&#xff1a; 一. 数据库约束 二. 表的设计 三. 聚合查询 四.联合查询 一. 数据库约束&#xff1a; 1.约束类型汇总&#xff1a; 约束类型 说明 NULL约束使用NOT NULL指定列不为 空UNIQUE唯一约束指定列为唯一的、不重复的DEFAULT默认值约 …

Xchart 相关操作

using Newtonsoft.Json; using System.Collections; using System.Collections.Generic; using UnityEngine; using XCharts; /***************************************************************************** Copyright (C) 2013-2023 北京普源瑞新仿真科技有限公司 All Ri…

工作流(低代码)提升工作效率的秘密武器

如何看待“低代码”开发平台的兴起&#xff1f; 在当今快速变化的数字化时代&#xff0c;企业面临着前所未有的挑战和机遇。如何在激烈的市场竞争中脱颖而出&#xff0c;成为每个企业必须思考的问题。而低代码工具&#xff0c;正是帮助企业提升工作效率&#xff0c;实现快速响…

在Windows上用Visual Studio编译OpenCV

在Windows上编译开源项目&#xff0c;有时候让人痛不欲生&#xff0c;有时候却出奇地顺利。OpenCV属于后者。本文记录这次愉快的过程。 注&#xff1a;OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个开源的计算机视觉和机器学习软件库。它提供了大…

移动学习平台小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;教师管理&#xff0c;学生管理&#xff0c;班级管理&#xff0c;课程分类管理&#xff0c;课程信息&#xff0c;作业信息管理&#xff0c;系统管理 微信端账号功能包括&#xff1a;系统首页&#xff0…

全面解析被低估的 Symbiosis — 一站式跨链 AMM DEX,跨链交易的未来

在区块链技术快速发展过程中&#xff0c;互操作性和流动性问题一直是行业面临的核心挑战。随着越来越多的区块链网络&#xff08;无论是 Layer 1 还是 Layer 2&#xff09;&#xff0c;以及不同虚拟机环境&#xff08;EVM 和非 EVM&#xff09;的出现&#xff0c;用户和开发者都…

P37-数据存储

数据类型介绍 前面学习了基本的内置类型&#xff1a; 以及它们所占存储空间的大小。 类型的意义&#xff1a; 1.使用这些类型开辟空间的大小&#xff08;大小决定了使用范围&#xff09; 2.如何看带内存空间的视角 类型的基本归类 整形家族 之所以char也分类在其中是因为实…

云原生时代的数据守护者:Velero 备份与迁移实战

项目背景 在云计算和容器技术飞速发展的今天&#xff0c;Kubernetes 已经成为容器编排和管理的事实标准。然而&#xff0c;随着业务的不断扩展&#xff0c;如何在云原生环境下保护和迁移 Kubernetes 集群资源&#xff0c;成为了摆在运维人员面前的一大挑战。Velero&#xff0c…

RazorSQL for Mac/Win:强大的跨平台多功能SQL数据库编辑器RazorSQL for Mac/Win:功能强大的跨平台 SQL 数据库编辑器

RazorSQL 是一款备受赞誉的多功能 SQL 数据库编辑器&#xff0c;适用于 Mac 和 Windows 操作系统&#xff0c;为用户提供了高效、便捷且强大的数据库管理和操作体验。 首先&#xff0c;RazorSQL 支持多种主流的数据库类型&#xff0c;包括但不限于 MySQL、Oracle、SQL Server、…

搭建内网开发环境(三)|基于nexus搭建docker私服

引言 上一篇教程中演示如果安装和 nexus 的基本使用&#xff0c;本篇教程将演示如果在 nexus 中搭建 docker 私服&#xff0c;并实战如何上传镜像到私服和从私服下载镜像。 搭建内网开发环境&#xff08;一&#xff09;&#xff5c;基于docker快速部署开发环境 搭建内网开发环…

MySQL基础--触发器,锁

触发器 触发器是与表有关的数据库对象&#xff0c;指在 insert/update/delete 之前或之后&#xff0c;触发并执行触发器中定义的 SQL 语句集合&#xff0c;触发器的这种特性可以协助应用在数据库端确保数据的完整性&#xff0c;日志记录&#xff0c;数据校验等操作。 使用别名 …

KNN 图像识别

KNN&#xff08;K-Nearest Neighbors&#xff0c;K最近邻&#xff09;算法是一种简单而有效的分类算法&#xff0c;也可以用于图像识别。它的基本思想是通过计算样本之间的距离&#xff0c;将待分类的样本归为其在训练集中最相近的K个样本所属的类别中最常见的类别 1. 准备工作…

阿里巴巴25校招内推

内推投递链接&#xff1a; http://aidc-jobs.alibaba.com/campus/qrcode/home?codeMx8ppk_s4MjkOFnb6XS3Vw%3D%3D 流程安排 简历投递(网申、内推) 8.16开启笔试&#xff1a;集中笔试为8月-10月面试&#xff1a;启动后持续推进offer发放&#xff1a;启动后持续推进 内推二维…

FPGA 综合笔记

仿真时阻塞赋值和非阻塞赋值 Use of Non-Blocking Assignment in Testbench : Verilog Use of Non-Blocking Assignment in Testbench : Verilog - Stack Overflow non-blocking assignment does not work as expected in Verilog non-blocking assignment does not work a…

华为APP审核,权限说明弹窗

工具类 import android.app.Activity; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.widget.PopupWindow; import android.widget.TextV…