第九章 分布式文件存储解决方案MinIO部署+阿里云OSS落地整合

第1集 分布式文件存储讲解 和 高性能-高可用面试题

简介:分布式文件存储解决核心知识介绍和面试题

  • 数据爆炸的时代,产生的数据量不断地在攀升,基本都离不开文件存储

    • 存储单位从KB、MB、GB、TB、PB到ZB级别的数据
    • 图片、文档、素材、静态化页面、长短视频、安装包等一系列文件
  • 业务应用内存储

    • 传统的javaweb项目, 文件数量达到一定后占据大量的内存、磁盘和带宽, 无法满足海量请求的业务
    • 开发容易-扩容难
  • 分布式文件系统(Distributed File System)

    • 海量数据对存储提出了新的要求,从而诞生了分布式文件存储

    • 是文件系统管理的物理存储资源不一定直接连接在本地节点上,而是通过计算机网络与节点(可简单的理解为一台计算机) 相连,或是若干不同的逻辑磁盘分区组合在一起而形成的完整的有层次的文件系统

    • 自研:扩容容易-开发难

  • 面试题:做分布式文件存储,主要是想实现文件存储访问的高性能与高可用,如何保证分布式存储的高性能与高可用?

在这里插入图片描述

  • 大家可以想到基本就是副本备份、双活、多活这种架构

  • 在系统中通过复制协议将数据同步到多个存储节点,并确保多个副本之间的数据一致性,当某个存储节点出故障时,系统能够自动将服务切换到其他的副本

  • 在分布式存储中高性能与高可用是矛盾的,比如要设计一个分布式存储系统,CAP定理也可以推断出来

    • 对性能的考虑,记录数据时先写一个份数据到某个机器上并立即返回,然后异步发起多个数据备份过程。这种设计的性能最好,但存在“容错性”的风险,加入返回后,还没来得及同步给其它节点就宕机了,则数据就丢失(异步复制,也存在是写主节点到内存还是落到磁盘)

    • 如果同时写多个副本,每个副本写成功以后再返回,则又导致性能下降,这个过程取决于最慢的那台机器的性能 (同步多写,是同步每个副本节点还是一个副本先)

      • 那应该如何选择呢?
        • 根据业务而定,如果要求性能更高,偶尔出现文件丢失或访问出错则可以异步复制
      • 要求文件系统一定要高可用,则用同步多写的策略,牺牲一定的性能也要保证高可用数据一致性
    • 基于上述的,大家还知道有一个很类似的消息队列就是支持这种操作

      • RocketMQ消息高可用里面的

        • 同步双写、异步刷盘,即同时写到两个节点上的内存才返回,然后异步持久化到磁盘里面
第2集 分布式文件存储业界常见解决方案介绍

简介:分布式文件存储常见解决方案介绍

  • 目前业界比较多这个解决方案,这边就挑选几个介绍下

    • MinIO
    是在 Apache License v2.0 下发布的对象存储服务器,学习成本低,安装运维简单,主流语言的客户端整合都有, 号称最强的对象存储文件服务器,且可以和容器化技术docker/k8s等结合,社区活跃但不够成熟,业界参考资料较少
    
    官网:https://docs.min.io/cn/
    
    • FastDFS
    一个开源的轻量级分布式文件系统,比较少的客户端可以整合,目前主要是C和java客户端,在一些互联网创业公司中有应用比较多,没有官方文档,社区不怎么活跃.
    架构+部署结构复杂,出问题定位比较难定位,可以说是fastdfs零件的组装过程,需要去理解fastDFS的架构设计,才能够正确的安装部署
    
    • 云厂商

      • 阿里云OSS

      • 七牛云

      • 腾讯云

      • 亚马逊云

      • CDN最强:Akamai https://www.akamai.com/cn

  • 选云厂商理由

    • 优点:开发简单,功能强大,容易维护(不同网络下图片质量、水印、加密策略、扩容、加速)
    • 缺点:要钱, 个性化处理,未来转移比较复杂,不排除有些厂商会提供一键迁移工具
  • 选开源MinIO的理由

    • 优点:功能强大、可以根据业务做二次的定制,新一代分布式文件存储系统,容器化结合强大,更重要的是免费(购买磁盘、内存、带宽)
    • 缺点:自己需要有专门的团队进行维护、扩容等
第3集 自建分布式文件存储MinIO容器化部署初体验

简介:分布式文件存储MinIO容器化部署初体验

  • Docker容器化部署(用于测试体验)

    • 官网:https://docs.min.io/cn/
    docker run -p 9000:9000 \
      --name minio_xdclass \
      -v /Users/xdclass/Desktop/test:/data \
      -e "MINIO_ROOT_USER=AKIAIOSFODNN7EXAMPLE" \
      -e "MINIO_ROOT_PASSWORD=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" \
      minio/minio server /data
    
  • 步骤

    • 访问控制台
    • 创建bucket
    • 上传文件
    • 预览
  • 总结

总体操作很流畅,支持单机和集群部署,多个方面都是目前比较强的,
对于有需求不能或不使用云厂商提供的存储服务,例如阿里云的oss、七牛云的对象存储等,可以通过自建minio对象存储集群的方式
第4集 阿里云OSS分布式对象存储介绍开通

简介:阿里云OSS对象存储介绍和开通

  • 阿里云OSS介绍
对象存储OSS(Object Storage Service)是阿里云提供的海量、安全、低成本、高持久的云存储服务。其数据设计持久性不低于99.9999999999%(12个9),服务设计可用性不低于99.995%。

OSS具有与平台无关的RESTful API接口,您可以在任何应用、任何时间、任何地点存储和访问任意类型的数据。

提供标准、低频访问、归档和冷归档四种存储类型,全面覆盖从热到冷的各种数据存储场景:
标准存储类型高持久、高可用、高性能的对象存储服务,支持频繁的数据访问。是各种社交、分享类的图片、音视频应用、大型网站、大数据分析的合适选择。
低频访问存储类型适合长期保存不经常访问的数据(平均每月访问频率1到2次)。存储单价低于标准类型,适合各类移动应用、智能设备、企业数据的长期备份,支持实时数据访问。
归档存储类型适合需要长期保存(建议半年以上)的归档数据,在存储周期内极少被访问,数据进入到可读取状态需要1分钟的解冻时间。适合需要长期保存的档案数据、医疗影像、科学资料、影视素材。
冷归档存储类型适合需要超长时间存放的极冷数据。例如因合规要求需要长期留存的数据、大数据及人工智能领域长期积累的原始数据、影视行业长期留存的媒体资源、在线教育行业的归档视频等。
  • 开通阿里云OSS

    • 有阿里云账号、实名认证

    • OSS介绍:https://www.aliyun.com/product/oss

    • OSS控制台:https://oss.console.aliyun.com/bucket

    • 学习路径:https://help.aliyun.com/learn/learningpath/oss.html

  • 开通后的操作

    • 创建Bucket
    • 上传文件
    • 访问文件
第5集 阿里云权限管理RAM介绍-玩转云产品核心知识

简介:阿里云ram权限管理介绍

  • 文件上传流程

    • web控制台
    • 前端->后端程序->阿里云OSS

在这里插入图片描述

  • 云账号 AccessKey

    • 访问阿里云 API 的密钥,具有该账户完全的权限
  • RAM权限介绍

    • 阿里云用于各个产品的权限,基于RBAC模型,进行简单管理账号、统一分配权限、集中管控资源,从而建立安全、完善的资源控制体系。

    • 众多产品,一般采用子账号进行分配权限,防止越权

在这里插入图片描述

  • 建立用户,勾选编程访问(保存accessKey和accessSecret,只出现一次)

    • accessKey: xxxxxxxxxxxxxxxx
    • accessSecret: xxxxxxxxxxxxxxxxxxxxxxx

在这里插入图片描述

  • 为新建用户授权OSS全部权限

在这里插入图片描述

第6集 阿里云OSS客户端SDK集成和测试存储服务

简介:阿里云OSS对象存储客户端集成和测试服务

  • 添加阿里云OSS的SDK

    • 地址:https://help.aliyun.com/document_detail/32008.html

    • 添加maven依赖

      • 底层聚合工程添加版本
       <!-- OSS各个项目单独加依赖,根据需要进行添加-->
                  <dependency>
                      <groupId>com.aliyun.oss</groupId>
                      <artifactId>aliyun-sdk-oss</artifactId>
                      <version>3.10.2</version>
                  </dependency>
      
      • 用户微服务添加
       <!-- OSS各个项目单独加依赖,根据需要进行添加-->
              <dependency>
                  <groupId>com.aliyun.oss</groupId>
                  <artifactId>aliyun-sdk-oss</artifactId>
              </dependency>
      
  • 用户微服务配置OSS

#阿里云OSS配置
aliyun:
  oss:
    endpoint: oss-cn-beijing.aliyuncs.com
    access-key-id: LTAI4GKz7Z1RRjzMYeHG3FCe
    access-key-secret: QilitkUsr0PMJYEK8MAPFdI4cFOLVq
    bucketname: xd-test1
  • 新建配置类 (配置里面的横杠会,自动转驼峰)
@ConfigurationProperties(prefix = "aliyun.oss")
@Configuration
@Data
public class OSSConfig {

    private String endpoint;
    private String accessKeyId;
    private String accessKeySecret;
    private String bucketname;
}
第7集 用户微服务注册头像上传阿里云OSS服务层开发

简介:用户微服务上传用户头像到oss服务层开发

  • 开发service
@Service
@Slf4j
public class FileServiceImpl implements FileService {


    @Autowired
    private OSSConfig ossConfig;


    @Override
    public String uploadUserHeadImg( MultipartFile file) {

        String originalFilename = file.getOriginalFilename();


        //获取相关配置
        String bucketName = ossConfig.getBucketname();
        String endpoint = ossConfig.getEndpoint();
        String accessKeyId = ossConfig.getAccessKeyId();
        String accessKeySecret = ossConfig.getAccessKeySecret();
        //创建oss对象
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        //JDK8新特性写法,构建路径
        LocalDateTime ldt = LocalDateTime.now();
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy/MM/dd");
        String folder = dtf.format(ldt);
        String fileName = CommonUtil.generateUUID();
        String extension = originalFilename.substring(originalFilename.lastIndexOf("."));
        //在oss上创建文件夹test路径
        String newFileName = "test/" + folder + "/" + fileName + extension;
        try {
            PutObjectResult result = ossClient.putObject(bucketName, newFileName, file.getInputStream());
            //返回访问路径
            if (null != result) {
                //https://xd-test1.oss-cn-beijing.aliyuncs.com/test/1.jpg
                String imgUrl = "https://"+bucketName+"."+endpoint+"/"+newFileName;
                return imgUrl;
            }
        } catch (Exception e) {
            log.error("上传头像失败:{}",e);
        } finally {
            // 关闭OSS服务
            ossClient.shutdown();
        }

        return null;
    }
}
第8集 用户微服务头像上传阿里云OSS接口和SwaggerUI提效

简介:用户微服务头像上传阿里云OSS接口和SwaggerUI提效

  • 文件上传流程

    • 先上传文件,返回url地址,再和普通表单一并提交(推荐这种,更加灵活,失败率低)
    • 文件和普通表单一并提交(设计流程比较多,容易超时和失败)
  • 注意:默认SpringBoot最大文件上传是1M,大家测试的时候记得关注下

  • 开发controller

    • @requestPart注解 接收文件以及其他更为复杂的数据类型
    • 比如 XXX(@RequestPart(“file”) MultipartFile file, @RequestPart(“userVO”) UserVO userVO) 复杂协议
     /**
         * 上传用户头像
         *
         * 默认文件大小 1M,超过会报错
         *
         * @param file
         * @return
         */
     @ApiOperation("用户头像上传")
     @PostMapping(value = "upload")
     public JsonData uploadHeaderImg(@ApiParam(value = "文件上传",required = true) @RequestPart("file") MultipartFile file){
    
            String result = fileService.uploadUserHeadImg(file);
    
            return result != null?JsonData.buildSuccess(result):JsonData.buildResult(BizCodeEnum.FILE_UPLOAD_USER_IMG_FAIL);
    
        }
    
  • Postman测试 (http的header是)

在这里插入图片描述

  • SwaggerUI接口测试
    • ui界面可以直接测试文件上传

第十章 工业级用户微服务注册-登录功能开发完善

第1集 用户微服务注册功能业务介绍和代码编写

简介:用户微服务注册接口介绍和业务代码编写

  • 微服务注册接口开发

    • 请求实体类编写
    • controller
    • service
      • 邮箱验证码验证
      • 密码加密(TODO)
      • 账号唯一性检查(TODO)
      • 插入数据库
      • 新注册用户福利发放(TODO)
    • mapper
第2集 密码学的那些事情

简介:介绍信息安全,密码学的那些事情

  • 信息安全的基本目标

在这里插入图片描述

  • 常见加密算法的分类

在这里插入图片描述

  • hash算法-单项加密

在这里插入图片描述

加密过程中不需要使用密钥,输入明文后由系统直接经过加密算法处理成密文,密文无法解密。
只有重新输入明文,并经过同样的加密算法处理,得到相同的密文并被系统重新识别后,才能真正解密		

算法:MD5/SHA1/SHA224/SHA256/

优点:快速计算m,具有单向性 one-way,不可由散列值推出原消息

场景:文件完整性校验和(Checksum)算法、常规密码等

  • 密码存储常用方式
    • 双重MD5
    • MD5+加盐
    • 双重MD5+加盐
  • 有些人会说为啥不直接用最强的Hash加密算法 ?
    • 更安全的算法,加密解密更复杂,接口性能下降更严重

在这里插入图片描述

第3集 用户微服务注册功能核心加密逻辑完善

简介:用户微服务注册接口加密逻辑完善

  • 聚合工程pom文件和common项目添加依赖检查
       <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
       <dependency>
           <groupId>org.apache.commons</groupId>
           <artifactId>commons-lang3</artifactId>
       </dependency>

       <!--用于加密-->
       <dependency>
           <groupId>commons-codec</groupId>
           <artifactId>commons-codec</artifactId>
       </dependency>
  • 工具类编写
		/**
     * 生成指定长度随机字母和数字
     *
     * @param length
     * @return
     */
    private static final String ALL_CHAR_NUM = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    public static String getStringNumRandom(int length) {
        //生成随机数字和字母,
        Random random = new Random();
        StringBuilder saltString = new StringBuilder(length);
        for (int i = 1; i <= length; ++i) {
            saltString.append(ALL_CHAR_NUM.charAt(random.nextInt(ALL_CHAR_NUM.length())));
        }
        return saltString.toString();
    }
  • 加盐,每个人的一般都不一样
			  UserDO userDO = new UserDO();
        BeanUtils.copyProperties(registerRequest, userDO);

        userDO.setCreateTime(new Date());
        userDO.setSlogan("人生需要动态规划,学习需要贪心算法");
        //生成秘钥
        userDO.setSecret("$1$" + CommonUtil.getStringNumRandom(8));
        //密码 + 加盐处理
        String cryptPwd = Md5Crypt.md5Crypt(registerRequest.getPwd().getBytes(), userDO.getSecret());
        userDO.setPwd(cryptPwd);
第4集 高并发下问题发现扩大思维锻炼 + 账号唯一性保证方案

简介:高并发下账号唯一性安全保证方案

  • 注册业务

    • 同个时刻注册,需要保证账号在数据库里唯一
  • 高并发下问题发现扩大

    • 万分之一的时间,放大100万倍
    • 不是你的代码安全,而是你的并发量过少,几个几十个并发量发现不了问题
    • 几十万几百万并发 ,线下难模拟
      • 代码暂停思维:假如非原子性代码运行到某一行暂停,其他线程重新操作是否会出问题
      • 时间扩大思维:1纳秒的时间,扩大到1分钟,代码逻辑是否会有问题
  • Redis:先看redis是否有,然后没的话则是新的注册

    • key -value 存储, 配置60秒过期
    • 非原子性操作,存在不一致
  • 数据库唯一索引(建表的时间已经添加)

 ALTER TABLE user ADD unique(`mail`)
第5集 用户微服务开发之登录模块逻辑和解密

简介:用户微服务登录模块开发

  • 核心逻辑
    • 通过mail找数据库记录
    • 获取盐,和当前传递的密码就行加密后匹配
    • 生成token令牌
  • controller
/**
     * 登录
     * @param loginRequest
     * @return
     */
    @PostMapping("login")
    public JsonData register(@RequestBody UserLoginRequest loginRequest){

        JsonData jsonData = userService.login(loginRequest);
        return jsonData;
    }

  • service
/**
     * 登录
     *
     * @param vo
     * @return
     */
    @Override
    public JsonData login(UserLoginRequest loginRequest) {

        List<UserDO> list = userMapper.selectList(
                new QueryWrapper<UserDO>().eq("mail", loginRequest.getMail()));

        if (list != null && list.size() == 1) {
            UserDO userDO = list.get(0);
            String cryptPwd = Md5Crypt.md5Crypt(loginRequest.getPwd().getBytes(), userDO.getSecret());
            if (cryptPwd.equals(userDO.getPwd())) {
                //生成token令牌
               
                return JsonData.buildSuccess();
            }
            //密码错误
            return JsonData.buildResult(BizCodeEnum.ACCOUNT_PWD_ERROR);
        } else {
            //未注册
            return JsonData.buildResult(BizCodeEnum.ACCOUNT_UNREGISTER);
        }

    }
第6集 分布式应用下登录检验解决方案 JWT讲解

简介:分布式应用的登录检验解决方案 JWT讲解 json web token

  • 什么是JWT

    • JWT 是一个开放标准,它定义了一种用于简洁,自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法。 可以使用 HMAC 算法或者是 RSA 的公钥密钥对进行签名
    • 简单来说: 就是通过一定规范来生成token,然后可以通过解密算法逆向解密token,这样就可以获取用户信息
          	  {
                    id:888,
                    name:'小D',
                    expire:10000
                }
                
                funtion 加密(object, appsecret){
                    xxxx
                    return base64( token);
                }
    
                function 解密(token ,appsecret){
    
                    xxxx
                    //成功返回true,失败返回false
                }
    
    • 优点

      • 生产的token可以包含基本信息,比如id、用户昵称、头像等信息,避免再次查库
      • 存储在客户端,不占用服务端的内存资源
    • 缺点

      • token是经过base64编码,所以可以解码,因此token加密前的对象不应该包含敏感信息,如用户权限,密码等

      • 如果没有服务端存储,则不能做登录失效处理,除非服务端改秘钥

  • JWT格式组成 头部、负载、签名

    • header+payload+signature
      • 头部:主要是描述签名算法
      • 负载:主要描述是加密对象的信息,如用户的id等,也可以加些规范里面的东西,如iss签发者,exp 过期时间,sub 面向的用户
      • 签名:主要是把前面两部分进行加密,防止别人拿到token进行base解密后篡改token
  • 关于jwt客户端存储

    • 可以存储在cookie,localstorage和sessionStorage里面
第7集 登录校验Json Web Token实战之封装通用方法

讲解:引入相关依赖并开发JWT工具类, 开发生产token和校验token的办法

  • 聚合工程加入版本依赖,common项目加入相关依赖

        <!-- JWT相关 -->
        <dependency>
          <groupId>io.jsonwebtoken</groupId>
          <artifactId>jjwt</artifactId>
          <version>0.7.0</version>
        </dependency>
    
  • common项目中封装生产token方法

         /**
         * 根据用户信息,生成令牌
         *
         * @param user
         * @return
         */
        public static String geneJsonWebToken(LoginUser user) {
    
            Long userId = user.getId();
            String token = Jwts.builder().setSubject(SUBJECT)
                    .claim("head_img", user.getHeadImg())
                    .claim("id", userId)
                    .claim("name", user.getName())
                    .claim("mail", user.getMail())
                    .setIssuedAt(new Date())
                    .setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
                    .signWith(SignatureAlgorithm.HS256, SECRET).compact();
    
            token = TOKEN_PREFIX + token;
    
            return token;
        }
    
  • 封装校验token方法

     /**
         * 校验token的方法
         *
         * @param token
         * @return
         */
        public static Claims checkJWT(String token) {
    
            try {
    
                final Claims claims = Jwts.parser().setSigningKey(SECRET)
                        .parseClaimsJws(token.replace(TOKEN_PREFIX, "")).getBody();
    
                return claims;
    
            } catch (Exception e) {
                return null;
            }
    
        }
    
    

在这里插入图片描述

  • 登录整合

                    //生成token令牌
                    LoginUser userDTO = new LoginUser();
                    BeanUtils.copyProperties(userDO, userDTO);
                    String token = JWTUtil.geneJsonWebToken(userDTO);
                    return JsonData.buildSuccess(token);
    
第8集 【加餐扩展】JWT登录过期-自动刷新token方案介绍

讲解:JWT过期自动刷新方案介绍

  • 背景
在前后分离场景下,越来越多的项目使用jwt token作为接口的安全机制,但存在jwt过期后,用户无法直接感知,假如在用户操作页面期间,突然提示登录,则体验很不友好,所以就有了token自动刷新需求

但是这个自动刷新方案,基本都离不开服务端状态存储,JWT推出思想是:去中心化,无状态化,所以有所违背

类似这样的业务,有阿里云首页,没有做token刷新令牌维护,但是符合对应的思想

在这里插入图片描述

  • 方案:前端控制检测token,无感知刷新
用户登录成功的时候,一次性给他两个Token,分别为AccessToken和RefreshToken
AccessToken有效期较短,比如1天或者5天,用于正常请求
RefreshToken有效期可以设置长一些,例如10天、20天,作为刷新AccessToken的凭证

刷新方案:当AccessToken即将过期的时候,例如提前30分钟,客户端利用RefreshToken请求指定的API获取新的AccessToken并更新本地存储中的AccessToken

核心逻辑
1、登录成功后,jwt生成AccessToken; UUID生成RefreshToken并存储在服务端redis中,设置过期时间
2、接口返回3个字段AccessToken/RefreshToken/访问令牌过期时间戳
3、由于RefreshToken存储在服务端redis中,假如这个RefreshToken也过期,则提示重新登录; 


老王的疑问:RefreshToken有效期那么长,和直接将AccessToken的有效期延长有什么区别

答:RefreshToken不像AccessToken那样在大多数请求中都被使用,主要是本地检测accessToken快过期的时候才使用,
一般本地存储的时候,也不叫refreshToken,前端可以取个别名,混淆代码让不能直接识别这个就是刷新令牌


缺点:前端每次请求需要判断token距离过期时间
优点:后端压力小,代码逻辑改动不大
  • 方案二:后端存储判断过期时间
后端存储AccessToken,每次请求过来都判断是否要过期,如果快要过期则重新生成新的token,并返回给前端重新存储,比如距离1天就过期的情况,如果用户访问对应的接口则会更新,但假如没访问则token已经过期则需要重新登录


优点:前端改动小,只需要存储响应http头里面是否有新的令牌产生,有的话就重新存储
缺点:后端实现复杂,且泄露后容易存在一直保活状态,且前端会存在并发请求,当并发请求收到多个jwt token时,容易生成多个token混乱使用
第9集 【加餐扩展】JWT令牌token泄露解决方案

讲解:JWT令牌泄露解决方案-避免盗用

  • 解密:使用互联网大厂的产品时经常遇到这个情况

    • 比如阿里云或者淘宝,你现在登录了然后换个网络或者地域就需要重新登录
    • 就是对应的token令牌,不只简单的算法加密,还包括了客户端属性、地理网络位置信息等,一起组成一个token令牌
  • 如何避免token令牌泄露

    • ip绑定方式
    生成token的时候,加密的payload加入当前用户ip。
    
    拦截器解密后,获取payload的ip和当前访问ip判断是否同个,如果不是则提示重新登录
    
    优点:服务端无需存储相关内容,性能高,假如用户广州登录,泄露了token,依旧用不了
    
    缺点:如果用户用使用过程中ip变动频繁,则操作会经常提示重新登录,体验不友好
    
    当然也可以让用户开启安全模式和非安全模式,让用户自己知道这个情况,一些区块链、比特币交易所里面就会让用户自己选择控制这个token令牌安全是否和ip、终端、地理网络信息进行绑定
    
    					{
                    id:888,
                    name:'小D',
                    ip:“128.23.12.31”
                    expire:10000
                }
                
                funtion 加密(object, appsecret){
                    xxxx
                    return base64( token);
                }
    
                function 解密(token ,appsecret){
    					
                    xxxx
                    //成功返回true,失败返回false
                }
    

第十一章 微服务登录拦截器开发和ThreadLocal原理+经典应用

第1集 用户微服务之通用登录拦截器开发

简介:用户微服务登录拦截器开发

  • 开发登录拦截器

    • 解密JWT
    • 传递登录用户信息
      • attribute传递
      • threadLocal传递
  • SpringBoot拦截器代码开发

@Slf4j
public class LoginInterceptor implements HandlerInterceptor {


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
try {
            String accessToken = request.getHeader("token");
            if (accessToken == null) {
                accessToken = request.getParameter("token");
            }

            if (StringUtils.isNotBlank(accessToken)) {
                Claims claims = JWTUtil.checkJWT(accessToken);
                if (claims == null) {
                    //告诉登录过期,重新登录
                    CommonUtil.sendJsonMessage(response, JsonData.buildError("登录过期,重新登录"));
                    return false;
                }

                Long id = Long.valueOf( claims.get("id").toString());
                String headImg = (String) claims.get("head_img");
                String mail = (String) claims.get("mail");
                String name = (String) claims.get("name");

								//TODO 用户信息传递
								
                return true;

            }

        } catch (Exception e) {
            log.error("拦截器错误:{}",e);
        }

        CommonUtil.sendJsonMessage(response, JsonData.buildError("token不存在,重新登录"));
        return false;

    }



    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

    }



}

第2集 核心知识-ThreadLocal介绍+经典应用场景

简介:玩转ThreadLocal核心知识和应用场景

  • 什么是ThreadLocal
全称thread local variable(线程局部变量)功用非常简单,使用场合主要解决多线程中数据因并发产生不一致问题。

ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享,这样的结果是耗费了内存,但大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。 

总结起来就是:同个线程共享数据

注意:ThreadLocal不能使用原子类型,只能使用Object类型
  • 核心应用场景
ThreadLocal 用作每个线程内需要独立保存信息,方便同个线程的其他方法获取该信息的场景。

每个线程获取到的信息可能都是不一样的,前面执行的方法保存了信息后,后续方法可以通过ThreadLocal 直接获取到,避免了传参,类似于全局变量的概念,比如用户登录令牌解密后的信息传递(还有用户权限信息、从用户系统获取到的用户名、用户ID)

在这里插入图片描述

  • 用户微服务配置token解密信息传递
public static ThreadLocal<LoginUser> threadLocal = new ThreadLocal<>();


                LoginUser loginUser = new LoginUser();
                loginUser.setId(id);
                loginUser.setName(name);
                loginUser.setMail(mail);
                loginUser.setHeadImg(headImg);
                threadLocal.set(loginUser);
第3集 轻松掌握ThreadLocal底层源码解读+原理

简介:ThreadLocal底层源码+原理讲解

  • 原理+源码介绍

    • ThreadLocal中的一个内部类ThreadLocalMap,这个类没有实现map接口,就是一个普通的Java类,但是实现的类似map的功能

    • 每个数据用Entry保存,其中的Entry继承与WeakReference,用一个键值对存储,键为ThreadLocal的引用。

    • 每个线程持有一个ThreadLocalMap对象,每一个新的线程Thread都会实例化一个ThreadLocalMap并赋值给成员变量threadLocals,使用时若已经存在threadLocals,则直接使用已经存在的对象。

在这里插入图片描述

第4集 连环问 大厂面试-P6高级工程师-ThreadLocal常见核心面试题

简介:ThreadLocal常见核心面试题

  • P6面试题:ThreadLocal和Synchronized的区别

    • 都是为了解决多线程中相同变量的访问冲突问题
    • Synchronized是通过线程等待,牺牲时间来解决访问冲突
    • ThreadLocal是通过每个线程单独一份存储空间,牺牲空间来解决冲突,
    • 对比Synchronized,ThreadLocal具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值
  • P6面试题:为啥什么ThreadLocal的键是弱引用,如果是强引用有什么问题?

Java中除了基础的数据类型以外,其它的都为引用类型。
而Java根据其生命周期的长短将引用类型又分为强引用 、 软引用 、 弱引用 、 虚引用
正常情况下我们平时基本上我们只用到强引用类型,而其他的引用类型我们也就在面试中,或者平日阅读类库或其他框架源码的时候才能见到

1、强引用 new了一个对象就是强引用  Object obj = new Object();

2、软引用的生命周期比强引用短一些,通过SoftReference类实现,当内存空间足够,垃圾回收器就不会回收它; 当JVM认为内存空间不足时,就会去试图回收软引用指向的对象,也就是说在JVM抛出OutOfMemoryError之前,会去清理软引用对象
主要用来描述一些【有用但并不是必需】的对象
使用场景:适合用来实现缓存,内存空间充足的时候将数据缓存在内存中,如果空间不足了就将其回收掉


3、弱引用是通过WeakReference类实现的,它的生命周期比软引用还要短,在GC的时候,不管内存空间足不足都会回收这个对象
使用场景:一个对象只是偶尔使用,希望在使用时能随时获取,但也不想影响对该对象的垃圾收集,则可以考虑使用弱引用来指向该对象。


ThreadLocal为什么是WeakReference呢?

如果是强引用,即使把ThreadLocal设置为null,但是ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏

如果是弱引用
引用ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set、get、remove的时候会被清除。
第5集 登录拦截器InterceptorConfig拦截和放行路径开发配置

简介:用户微服务登录拦截器路径配置和开发

  • 拦截器配置
    • 拦截路径
    • 不拦截路径
@Configuration
@Slf4j
public class InterceptorConfig  implements WebMvcConfigurer {


    public  LoginInterceptor loginInterceptor(){
        return new LoginInterceptor();
    }


    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(loginInterceptor())
                //拦截的路径
                .addPathPatterns("/api/user/*/**","/api/address/*/**")

                //排查不拦截的路径
                .excludePathPatterns("/api/user/*/send_code","/api/user/*/captcha",
                        "/api/user/*/register","/api/user/*/login","/api/user/*/upload");

    }
}

第十二章 用户信息查询和地址模块功能开发

第1集 用户微服务之个人信息查询接口开发

简介:用户微服务个人信息查询接口开发

  • 接口开发
    • controller开发
    • service开发
    • UserVO类编写
第2集 用户微服务之新增收货地址模块接口开发

简介:用户微服务新增收货地址模块开发

  • 需求

    • 用户收货地址,可以进行新增
    • 有个默认的收货地址

在这里插入图片描述

  • 新增收货地址接口开发和默认配置
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    @Override
    public int add(AddressAddRequest addressAddRequest) {
        LoginUser loginUser = LoginInterceptor.threadLocal.get();
        AddressDO addressDO = new AddressDO();
        addressDO.setCreateTime(new Date());
        BeanUtils.copyProperties(addressAddRequest, addressDO);
        addressDO.setUserId(loginUser.getId());
        //是否有默认收货地址
        if (addressDO.getDefaultStatus() == 1) {
            AddressDO defaultAddressDO = addressMapper.selectOne(new QueryWrapper<AddressDO>().eq("user_id", loginUser.getId()).eq("default_status", 1));
            if (defaultAddressDO != null) {
                //修改为非默认地址
                defaultAddressDO.setDefaultStatus(0);
                addressMapper.update(defaultAddressDO, new QueryWrapper<AddressDO>().eq("id", defaultAddressDO.getId()));
            }
        }
        int rows = addressMapper.insert(addressDO);
        return rows;
    }
第3集 收货地址模块-查找接口开发和删除地址接口开发

简介:用户微服务收货地址查找接口开发和删除地址接口

  • 查找收货地址接口开发
@Override
    public AddressVO findById(long addressId) {
        
        AddressDO addressDO = addressMapper.selectOne(new QueryWrapper<AddressDO>().eq("id", addressId)
        if (addressDO == null) {
            return null;
        }
        AddressVO addressVO = new AddressVO();
        BeanUtils.copyProperties(addressDO, addressVO);
        return addressVO;
    }
  • 删除指定收货地址
    @Override
    public int del(long addressId) {
        LoginUser loginUser = LoginInterceptor.threadLocal.get();
        int rows = addressMapper.delete(new QueryWrapper<AddressDO>().eq("id", addressId).eq("user_id", loginUser.getId()));
        return rows;

    }
  • 通用返回状态码
    /**
     * 通用操作码
     */
    OPS_REPEAT(110001,"重复操作"),


    /**
     * 购物车
     */
    CART_FAIL(220001,"添加购物车失败"),


    /**
     *验证码
     */
    CODE_TO_ERROR(240001,"接收号码不合规"),
    CODE_LIMITED(240002,"验证码发送过快"),
    CODE_ERROR(240003,"验证码错误"),
    CODE_CAPTCHA(240101,"图形验证码错误"),



    /**
     * 账号
     */
    ACCOUNT_REPEAT(250001,"账号已经存在"),
    ACCOUNT_UNREGISTER(250002,"账号不存在"),
    ACCOUNT_PWD_ERROR(250003,"账号或者密码错误"),


    /**
     * 优惠券
     */
    COUPON_CONDITION_ERROR(270001,"优惠券条件错误"),
    COUPON_UNAVAILABLE(270002,"没有可用的优惠券"),
    COUPON_NO_EXITS(270003,"优惠券不存在"),
    COUPON_NO_STOCK(270005,"优惠券库存不足"),
    COUPON_OUT_OF_LIMIT(270006,"优惠券领取超过限制次数"),
    COUPON_OUT_OF_TIME(270407,"优惠券不在领取时间范围"),
    COUPON_GET_FAIL(270407,"优惠券领取失败"),
    COUPON_RECORD_LOCK_FAIL(270409,"优惠券锁定失败"),


    /**
     * 订单
     */
    ORDER_CONFIRM_COUPON_FAIL(280001,"创建订单-优惠券使用失败,不满足价格条件"),
    ORDER_CONFIRM_PRICE_FAIL(280002,"创建订单-验价失败"),
    ORDER_CONFIRM_LOCK_PRODUCT_FAIL(280003,"创建订单-商品库存不足锁定失败"),
    ORDER_CONFIRM_ADD_STOCK_TASK_FAIL(280004,"创建订单-新增商品库存锁定任务"),
    ORDER_CONFIRM_TOKEN_NOT_EXIST(280008,"订单令牌缺少"),
    ORDER_CONFIRM_TOKEN_EQUAL_FAIL(280009,"订单令牌不正确"),
    ORDER_CONFIRM_NOT_EXIST(280010,"订单不存在"),
    ORDER_CONFIRM_CART_ITEM_NOT_EXIST(280011,"购物车商品项不存在"),

    /**
     * 收货地址
     */
    ADDRESS_ADD_FAIL(290001,"新增收货地址失败"),
    ADDRESS_DEL_FAIL(290002,"删除收货地址失败"),
    ADDRESS_NO_EXITS(290003,"地址不存在"),

    /**
     * 支付
     */
    PAY_ORDER_FAIL(300001,"创建支付订单失败"),
    PAY_ORDER_CALLBACK_SIGN_FAIL(300002,"支付订单回调验证签失败"),
    PAY_ORDER_CALLBACK_NOT_SUCCESS(300003,"创建支付订单失败"),
    PAY_ORDER_NOT_EXIST(300005,"订单不存在"),
    PAY_ORDER_STATE_ERROR(300006,"订单状态不正常"),
    PAY_ORDER_PAY_TIMEOUT(300007,"订单支付超时"),




    /**
     * 流控操作
     */

    CONTROL_FLOW(500101,"限流控制"),
    CONTROL_DEGRADE(500201,"降级控制"),
    CONTROL_AUTH(500301,"认证控制"),


    /**
     * 文件相关
     */
    FILE_UPLOAD_USER_IMG_FAIL(600101,"用户头像文件上传失败");

第4集 列举指定用户全部收货地址接口开发

简介:用户微服务列举指定用户全部收货地址接口开发

  • 列举指定用户全部收获地址
    @Override
    public List<AddressVO> listUserAllAddress() {
        LoginUser loginUser = LoginInterceptor.threadLocal.get();
        List<AddressDO> list = addressMapper.selectList(new QueryWrapper<AddressDO>().eq("user_id", loginUser.getId()));

        List<AddressVO> addressVOList = list.stream().map(obj -> {
            AddressVO addressVO = new AddressVO();
            BeanUtils.copyProperties(obj, addressVO);
            return addressVO;
        }).collect(Collectors.toList());
        return addressVOList;
    }
第5集 多数人容易忽视数据安全问题

简介:基于原先编写的接口,指出存在问题

  • 查看他人的数据
  • 删除他人的数据

在这里插入图片描述

第6集 水平-垂直代码防范

简介:介绍水平-垂直代码防范

  • 介绍

    是Web应用程序中一种常见的漏洞,由于其存在范围广、危害大, 列为Web应用十大安全隐患的第二名
    
    指应用在检查授权时存在纰漏,使获得低权限用户账户后,利用一些方式绕过权限检查,访问或者操作其他用户或者更高权限.
    
    产生原因:主要是因为开发人员在对数据进行增、删、改、查询时对客户端请求的数据过分相信,而遗漏了权限的判定
    
    • 水平
      • 尝试访问与他拥有相同权限的用户的资源
      • 例子:A用户可以直接操作到B用户的数据
    • 垂直
      • 一个低级别者尝试访问高级别用户的资源
      • 例子:普通管理员登录,拼接浏览器地址,直接访问高级管理员的页面
  • 大家检查下自己项目和公司是否有这个问题

  • 防范水平

建立用户和可操作资源的绑定关系,用户对任何资源进行操作时,通过该绑定关系确保该资源是属于该用户所有的
  • 防范垂直
基于RBAC角色访问控制机制来防止纵向越,定义不同的权限角色,为每个角色分配不同的权限,当用户执行某个动作或产生某种行为时,通过用户所在的角色判定该动作或者行为是否允许。
第7集 用户微服务-水平权限问题修复

简介:修复用户微服务下水平权限问题

  • 防范水平越
建立用户和可操作资源的绑定关系,用户对任何资源进行操作时,通过该绑定关系确保该资源是属于该用户所有的

ride
public List listUserAllAddress() {
LoginUser loginUser = LoginInterceptor.threadLocal.get();
List list = addressMapper.selectList(new QueryWrapper().eq(“user_id”, loginUser.getId()));

    List<AddressVO> addressVOList = list.stream().map(obj -> {
        AddressVO addressVO = new AddressVO();
        BeanUtils.copyProperties(obj, addressVO);
        return addressVO;
    }).collect(Collectors.toList());
    return addressVOList;
}



Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐