[Spring Cloud] (6)gateway整体加解密

文章目录

  • 简述
  • 整体效果
  • 后端
    • 增加配置
      • nacos增加配置
      • GlobalConfig
    • 添加请求整体解密拦截器DecryptionFilter
    • 添加响应整体解密拦截器EncryptionFilter
  • 前端
    • 请求拦截器
      • 添加整体加密逻辑
      • 请求头中添加sessionId
    • 响应拦截器
      • 添加整体解密逻辑

简述

本文网关gateway,微服务,vue已开源到gitee
杉极简/gateway网关阶段学习

在经历前面5章的铺垫下,终于进入到了最重要的环节——拦截器功能实现,此时我们需要看的东西就比较集中而且简单了。

在Java分布式系统中,使用Spring Cloud Gateway进行整体的加解密处理可以起到以下几个关键作用:

  1. 安全性增强:通过加密客户端与服务端之间传输的数据,可以防止敏感信息在网络传输过程中被截获和窃取。即使数据包被拦截,没有相应的密钥也无法解读数据内容,从而保护了用户隐私和系统安全。
  2. 数据完整性校验:在数据传输过程中,除了加密保护数据外,还可以通过签名机制来确保数据的完整性。签名可以验证数据在传输过程中是否被篡改,增强了数据的可靠性。
  3. 减轻后端服务负担:通过在网关层统一进行加解密处理,后端微服务不需要再集成额外的安全模块来进行数据的加解密工作,从而降低了后端服务的复杂性和资源消耗。
  4. 统一的安全管理:网关作为所有请求的入口点,可以实现统一的安全管理策略。所有的加解密操作按照统一的规则进行,便于管理和维护,同时方便对安全策略进行升级和维护。
  5. 提高开发效率:开发者可以专注于业务逻辑的实现,而不必关注数据传输的安全细节,提高了开发效率和项目的迭代速度。
  6. 跨服务数据保护:在微服务架构中,服务间可能需要相互调用和数据交换。网关加解密确保了即使在内部服务间传输的数据也是安全的,防止了潜在的内部安全风险。
  7. 应对合规性要求:某些行业或地区的法律法规要求对用户数据进行加密处理,使用网关加解密可以更好地符合这些合规性要求,避免法律风险。
  8. 灵活的策略调整:网关层的加解密策略可以根据业务需求灵活调整,比如可以针对不同的请求路径或请求方法应用不同的加密算法和密钥,而不需要修改每个微服务的代码。
  9. 减少敏感信息泄露风险:对于某些对数据安全性要求极高的应用场景,如金融交易、个人隐私信息等,网关加解密可以有效减少敏感信息泄露的风险。
  10. 提高系统的可扩展性:随着业务的发展,系统的安全需求可能会变化。在网关层实现加解密可以方便地根据新的安全需求进行扩展和更新,而不影响现有的业务流程和后端服务。

Spring Cloud Gateway的加解密功能为分布式系统提供了一个安全、高效、灵活的数据传输解决方案,有助于提升系统的安全性和开发效率。

整体效果

像不需要加密的接口,请求参数与响应数据都是未加密的
image.png
image.png
像数据接口,发送的参数是加密数据,接收到的都是是加密数据。但是经过前后端的拦截器处理之后,显示出的依然是正常数据
image.png
image.png

后端

增加配置

nacos增加配置

  # 整体对称加解密
  aes: true
  # 整体非对称加解密
  rsa: true

GlobalConfig

    /**
     * 整体对称加解密
     */
    private boolean aes;

    /**
     * 整体非对称加解密
     */
    private boolean rsa;

image.png

添加请求整体解密拦截器DecryptionFilter

package com.fir.gateway.filter.request;

import com.alibaba.fastjson.JSONObject;
import com.fir.gateway.config.GlobalConfig;
import com.fir.gateway.config.exception.CustomException;
import com.fir.gateway.config.result.AjaxStatus;
import com.fir.gateway.dto.ConnectDTO;
import com.fir.gateway.utils.AESUtils;
import com.fir.gateway.utils.RSAUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.net.URI;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Set;

;


/**
 * 请求整理解密-请求拦截器
 *
 * @author fir
 */
@Slf4j
@Component
public class DecryptionFilter implements Ordered, GlobalFilter {


    /**
     * 网关参数配置
     */
    @Resource
    private GlobalConfig globalConfig;


    @Resource
    private RedisTemplate<String, Object> redisTemplate;


    @Override
    public int getOrder() {
        return -280;
    }


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("整体解密:start");
        ServerHttpRequest req = exchange.getRequest();
        String method = req.getMethodValue();

        boolean rsa = globalConfig.isRsa();
        boolean aes = globalConfig.isAes();
        if (rsa || aes) {
            // 白名单路由判断
            ServerHttpRequest request = exchange.getRequest();
            String path = request.getPath().toString();
            List<String> whiteUrls = globalConfig.getWhiteUrls();

            if(!whiteUrls.contains(path)){
                ServerHttpRequest builder = req.mutate().build();
                if (HttpMethod.GET.matches(method)) {
                    log.info("当前请求参数为: {}", req.getQueryParams());
                    builder = changeGet(exchange, builder);
                } else if (HttpMethod.POST.matches(method)) {
                    log.info("当前请求参数为: {}", req.getQueryParams());
                    builder = changeGet(exchange, builder);
                }
                exchange = exchange.mutate().request(builder).build();
                log.info("整体解密:true");
            }else {
                log.info("整体解密:true,白名单");
            }
        }else {
            log.info("整体解密:true,验证已关闭");
        }
        return chain.filter(exchange);
    }


    /**
     * 获取请求参数等信息进行过滤处理
     *
     * @param exchange          请求
     * @param serverHttpRequest 请求
     * @return 处理结束的参数
     */
    @SneakyThrows
    private ServerHttpRequest changeGet(ServerWebExchange exchange, ServerHttpRequest serverHttpRequest) {
        String session = exchange.getRequest().getHeaders().getFirst("s");

        if (session == null) {
            throw new CustomException(AjaxStatus.SESSION_INVALID);
        }

        JSONObject jsonObject = (JSONObject) redisTemplate.opsForValue().get(session);
        if (jsonObject == null) {
            throw new CustomException(AjaxStatus.SESSION_EXPIRE);
        }
        ConnectDTO connectDTO = jsonObject.toJavaObject(ConnectDTO.class);
        String privateKey = connectDTO.getPrivateKey();
        String secretKey = connectDTO.getSecretKey();


        // 获取原参数
        URI uri = serverHttpRequest.getURI();
        String originalQuery = uri.getRawQuery();
        String decodedQuery = null;
        if(StringUtils.isNotBlank(originalQuery)){
            decodedQuery = URLDecoder.decode(originalQuery, "UTF-8");
        }

        // 更改参数
        MultiValueMap<String, String> newQueryParams = new LinkedMultiValueMap<>();
        if (StringUtils.isNotBlank(originalQuery) && org.springframework.util.StringUtils.hasText(decodedQuery)) {
            // 修改请求参数,String[] array只能处理前端特定加密 {data:加密内容的形式}, 传递到后端,会变更为 data=加密内容。
            // 除此以外的所有方式不能通过本方法进行解密
            String[] array = decodedQuery.split("=");
            if (array.length > 1) {
                decodedQuery = array[1];

                if (decodedQuery != null) {
                    boolean rsa = globalConfig.isRsa();
                    boolean aes = globalConfig.isAes();

                    if (rsa) {
                        // 对数据进行非对称解密
                        originalQuery = RSAUtils.decryptSection(decodedQuery, privateKey);
                    }
                    if (aes) {
                        // 对数据进行对称解密
                        originalQuery = AESUtils.decrypt(originalQuery, secretKey);
                    }
                }

                Map<String, Object> dataMap = JSONObject.parseObject(originalQuery, Map.class);
                if (dataMap != null) {
                    Set<String> strings = dataMap.keySet();
                    for (String key : strings) {
                        String encodedString = URLEncoder.encode(dataMap.get(key).toString(), StandardCharsets.UTF_8.toString());
                        newQueryParams.add(key, encodedString);
                    }
                }
            }

        }

        // 替换查询参数
        URI newUri = UriComponentsBuilder.fromUri(uri)
                .query(null)
                .queryParams(newQueryParams)
                .build(true)
                .toUri();

        ServerHttpRequest request = exchange.getRequest().mutate().uri(newUri).build();
        // 将解密后的参数重新设置到请求中

        uri = request.getURI();
        log.info("更改后的当前请求参数为: {}", uri.getRawQuery());
        return request;
    }
}

添加响应整体解密拦截器EncryptionFilter

package com.fir.gateway.filter.request;

import com.alibaba.fastjson.JSONObject;
import com.fir.gateway.config.GlobalConfig;
import com.fir.gateway.config.exception.CustomException;
import com.fir.gateway.config.result.AjaxStatus;
import com.fir.gateway.dto.ConnectDTO;
import com.fir.gateway.utils.AESUtils;
import com.fir.gateway.utils.RSAUtils;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpMethod;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.net.URI;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Set;

;


/**
 * 请求整理解密-请求拦截器
 *
 * @author fir
 */
@Slf4j
@Component
public class DecryptionFilter implements Ordered, GlobalFilter {


    /**
     * 网关参数配置
     */
    @Resource
    private GlobalConfig globalConfig;


    @Resource
    private RedisTemplate<String, Object> redisTemplate;


    @Override
    public int getOrder() {
        return -280;
    }


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        log.info("整体解密:start");
        ServerHttpRequest req = exchange.getRequest();
        String method = req.getMethodValue();

        boolean rsa = globalConfig.isRsa();
        boolean aes = globalConfig.isAes();
        if (rsa || aes) {
            // 白名单路由判断
            ServerHttpRequest request = exchange.getRequest();
            String path = request.getPath().toString();
            List<String> whiteUrls = globalConfig.getWhiteUrls();

            if(!whiteUrls.contains(path)){
                ServerHttpRequest builder = req.mutate().build();
                if (HttpMethod.GET.matches(method)) {
                    log.info("当前请求参数为: {}", req.getQueryParams());
                    builder = changeGet(exchange, builder);
                } else if (HttpMethod.POST.matches(method)) {
                    log.info("当前请求参数为: {}", req.getQueryParams());
                    builder = changeGet(exchange, builder);
                }
                exchange = exchange.mutate().request(builder).build();
                log.info("整体解密:true");
            }else {
                log.info("整体解密:true,白名单");
            }
        }else {
            log.info("整体解密:true,验证已关闭");
        }
        return chain.filter(exchange);
    }


    /**
     * 获取请求参数等信息进行过滤处理
     *
     * @param exchange          请求
     * @param serverHttpRequest 请求
     * @return 处理结束的参数
     */
    @SneakyThrows
    private ServerHttpRequest changeGet(ServerWebExchange exchange, ServerHttpRequest serverHttpRequest) {
        String session = exchange.getRequest().getHeaders().getFirst("s");

        if (session == null) {
            throw new CustomException(AjaxStatus.SESSION_INVALID);
        }

        JSONObject jsonObject = (JSONObject) redisTemplate.opsForValue().get(session);
        if (jsonObject == null) {
            throw new CustomException(AjaxStatus.SESSION_EXPIRE);
        }
        ConnectDTO connectDTO = jsonObject.toJavaObject(ConnectDTO.class);
        String privateKey = connectDTO.getPrivateKey();
        String secretKey = connectDTO.getSecretKey();


        // 获取原参数
        URI uri = serverHttpRequest.getURI();
        String originalQuery = uri.getRawQuery();
        String decodedQuery = null;
        if(StringUtils.isNotBlank(originalQuery)){
            decodedQuery = URLDecoder.decode(originalQuery, "UTF-8");
        }

        // 更改参数
        MultiValueMap<String, String> newQueryParams = new LinkedMultiValueMap<>();
        if (StringUtils.isNotBlank(originalQuery) && org.springframework.util.StringUtils.hasText(decodedQuery)) {
            // 修改请求参数,String[] array只能处理前端特定加密 {data:加密内容的形式}, 传递到后端,会变更为 data=加密内容。
            // 除此以外的所有方式不能通过本方法进行解密
            String[] array = decodedQuery.split("=");
            if (array.length > 1) {
                decodedQuery = array[1];

                if (decodedQuery != null) {
                    boolean rsa = globalConfig.isRsa();
                    boolean aes = globalConfig.isAes();

                    if (rsa) {
                        // 对数据进行非对称解密
                        originalQuery = RSAUtils.decryptSection(decodedQuery, privateKey);
                    }
                    if (aes) {
                        // 对数据进行对称解密
                        originalQuery = AESUtils.decrypt(originalQuery, secretKey);
                    }
                }

                Map<String, Object> dataMap = JSONObject.parseObject(originalQuery, Map.class);
                if (dataMap != null) {
                    Set<String> strings = dataMap.keySet();
                    for (String key : strings) {
                        String encodedString = URLEncoder.encode(dataMap.get(key).toString(), StandardCharsets.UTF_8.toString());
                        newQueryParams.add(key, encodedString);
                    }
                }
            }

        }

        // 替换查询参数
        URI newUri = UriComponentsBuilder.fromUri(uri)
                .query(null)
                .queryParams(newQueryParams)
                .build(true)
                .toUri();

        ServerHttpRequest request = exchange.getRequest().mutate().uri(newUri).build();
        // 将解密后的参数重新设置到请求中

        uri = request.getURI();
        log.info("更改后的当前请求参数为: {}", uri.getRawQuery());
        return request;
    }
}

前端

请求拦截器

添加整体加密逻辑

// 请求整体加密
if (AESKey) {
  const secretKey = this.get("secretKey");
  let date = JSON.stringify(request.params);
  date = this.encryptAES(date, secretKey);
  request.params = {"data": date};
}
if (AESKey && RSAKey) {
  const serverPublicKey = this.get("serverPublicKey");
  let date = request.params.data;
  date = this.rsaEncrypt(date, serverPublicKey);
  request.params = {"data": date};
} else if (RSAKey) {
  const serverPublicKey = this.get("serverPublicKey");
  let date = JSON.stringify(request.params);
  date = this.rsaEncrypt(date, serverPublicKey);
  request.params = {"data": date};
}

image.png

请求头中添加sessionId

            let s = this.get("sessionId")
            // 请求中增加会话信息
            if (s) {
                request.headers.s = s;
            }

image.png

响应拦截器

添加整体解密逻辑

修改一下内容:

return securityUtils.gatewayResponse(response);

image.png

securityUtils.js中添加响应处理函数

/**
 * gateway网关验证信息处理(响应头)
 */
gatewayResponse(response) {
  let key = true;

  // 放置业务逻辑代码
  // response是服务器端返回来的数据信息,与Promise获得数据一致
  let data = response.data
  // config包含请求信息
  let config = response.config

  // 判断 data 是否为对象
  if (typeof data === 'object' && data !== null) {
    // 判断 data 是否匹配特定格式
    if (
      Object.prototype.hasOwnProperty.call(data, 'msg') &&
      Object.prototype.hasOwnProperty.call(data, 'code') &&
      typeof data.msg === 'string' &&
      typeof data.code === 'number'
    ) {
      // 数据匹配特定格式
      if (data.code === 401) {
        sessionStorage.clear()

      }
      return data;
    }
  }


  // 获取当前请求的url
  let url = config.url
  whiteList.find(function (value) {
    if (value === url) {
      key = false;
    }
  });


  // 对非白名单数据进行整体解密处理
  if (key) {
    // 获取加密密钥,并传入解密组件进行解密
    if (RSAKey) {
      const privateKey = this.get("privateKey")
      data = this.rsaDecrypt(data, privateKey)
    }
    if (AESKey) {
      let securityKey = this.get("secretKey")
      data = this.decryptAES(data, securityKey)
    }

    if (data != null && data !== "") {
      data = JSON.parse(data);
    }else {
      data =  new Promise(() => {});
    }


  }

  return data;
},

image.png

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

阅读欣赏推荐之(七)——纪录片《一根绳子有多长》

《一根绳子有多长》是英国广播公司&#xff08;BBC&#xff09;在2009年出品的纪录片&#xff0c;这部纪录片以一跟绳子作为主角&#xff0c;通过运用现代科技手段&#xff0c;结合历史学、文化学、物理学等多个领域的知识&#xff0c;对绳子进行了全方位的研究。在古代&#x…

诺基亚贝尔探访上海斯歌,共探创新合作新机遇

近日&#xff0c;上海斯歌K2 BPM迎来重要客户考察交流活动。来自诺基亚贝尔的首席数字官刘少勇一行莅临了上海斯歌K2 BPM 的武汉研发中心&#xff0c;并对上海斯歌在BPM业务流程管理领域的研发成果及交付能力给予了高度肯定。 此次活动不仅加深了双方的战略合作&#xff0c;也为…

flask 前后台文件多张图片api;streamlit、gradio多图片页面展示

1、flask 前后台文件多张图片api send_file 传递zip&#xff1a; send_file(zip_data, mimetype‘application/zip’, as_attachmentTrue, download_name‘images.zip’) from flask import Flask, Response, request,send_file from PIL import Image import torch import i…

数据库大作业——基于qt开发的图书管理系统 (一)环境的配置与项目需求的分析

前言 博主最近数据库原理结课要做课程设计了,要求开发基于数据库实现的图书管理系统&#xff0c;博主想了想决定做一个基于Qt的图书管理系统,博主在此之前其实也没有用过多少Qt&#xff0c;仅以此专栏记录博主学习与开发的全过程&#xff0c;大家一起学习&#xff0c;一起进步…

使用固定公网地址远程访问开源服务器运维管理面板1Panel管理界面

文章目录 前言1. Linux 安装1Panel2. 安装cpolar内网穿透3. 配置1Panel公网访问地址4. 公网远程访问1Panel管理界面5. 固定1Panel公网地址 前言 1Panel 是一个现代化、开源的 Linux 服务器运维管理面板。高效管理,通过 Web 端轻松管理 Linux 服务器&#xff0c;包括主机监控、…

npm install 会报错npm audit错误,会提示你有多少个漏洞需要结局等

npm install 会报错 npm audit… 错误&#xff0c;会提示你有多少个漏洞需要结局&#xff0c;对应的包版本不应该低于多少等等问题 当使用npm i 命令的时候会出现以下问题 如果是个新手的话&#xff0c;建议直接关闭npm的audit检查。这样可以保证npm的audit不会影响你的初始…

文献速递:深度学习医学影像心脏疾病检测与诊断--从SPECT/CT衰减图中深度学习冠状动脉钙化评分提高了对重大不良心脏事件的预测

Title 题目 Deep Learning Coronary Artery Calcium Scores from SPECT/CT Attenuation Maps Improve Prediction of Major Adverse Cardiac Events 从SPECT/CT衰减图中深度学习冠状动脉钙化评分提高了对重大不良心脏事件的预测 01 文献速递介绍 低剂量非门控CT衰减校正&am…

ASP.NET网络商店销售管理系统的设计与实现

摘 要 随着软件技术的不断进步和发展&#xff0c;信息化的管理方式越来越广泛的应用于各个领域&#xff0c;对于任何网站系统的管理来说开发一套现代化的成员管理软件是十分必要的。通过这样的软件系统&#xff0c;可以做到成员的规范管理和快速查询&#xff0c;从而减少管理…

TPV-W5 24V 48V 系列——正负双输出和单输出,工业级环境温度,用于PCB安装的国际标准结构

TPV-W5系列提供正负双输出和单输出&#xff0c;工业级环境温度&#xff0c;用于PCB安装的国际标准结构。此系列产品小巧&#xff0c;效率高&#xff0c;低输出纹波及能承受3000V以上的耐压&#xff0c;用于需要正负电压或单输出和高隔离电压的场合。封装有SIP和DIP可选。

JAVA基础之线程池原理与源码简读

线程 线程是调度CPU资源的最小单位&#xff0c;线程模型分为KLT和ULT模型&#xff0c;JVM使用的KLT模型java线程与OS线程保持1:1的映射关系&#xff0c;也就是说每一个java线程对应操作系统一个线程。Java线程有以下几种生命状态&#xff1a; NEW&#xff1a;新建状态RUNNABL…

单调栈|503.下一个更大元素II

力扣题目链接 class Solution { public:vector<int> nextGreaterElements(vector<int>& nums) {// 拼接一个新的numsvector<int> nums1(nums.begin(), nums.end());nums.insert(nums.end(), nums1.begin(), nums1.end());// 用新的nums大小来初始化resu…

我独自升级崛起在哪下载 我独自升级电脑PC端下载教程分享

将于5月8日在全球舞台闪亮登场的动作角色扮演游戏《我独自升级崛起》&#xff0c;灵感源自同名热门动画与网络漫画&#xff0c;承诺为充满激情的游戏玩家群体带来一场集深度探索与广阔体验于一身的奇幻旅程。该游戏以独特的网络武侠世界观为基底&#xff0c;展现了一位普通人踏…

[Java、Android面试]_22_APP启动流程(中频问答)

欢迎查看合集&#xff1a; Java、Android面试高频系列文章合集 本人今年参加了很多面试&#xff0c;也有幸拿到了一些大厂的offer&#xff0c;整理了众多面试资料&#xff0c;后续还会分享众多面试资料。 整理成了面试系列&#xff0c;由于时间有限&#xff0c;每天整理一点&am…

【个人博客搭建】(17)使用FluentValidation 参数校验

FluentValidation 是一个用于 .NET 的开源验证库&#xff0c;它提供了一种流畅的接口和强类型验证规则&#xff0c;使得验证逻辑表达得更加清晰和简洁。&#xff08;Apache-2.0&#xff09; FluentValidation 的主要作用包括&#xff1a; 提高代码可读性&#xff1a;通过使用 F…

Python ArcPy批量将大量栅格文件的投影坐标系转为地理坐标系

本文介绍基于Python语言中的ArcPy模块&#xff0c;批量将多个遥感影像由投影坐标系转为地理坐标系的方法。 在之前的文章中&#xff0c;我们介绍过将单独1景遥感影像的投影坐标系转为地理坐标系的方法&#xff0c;大家可以参考文章投影坐标系转为地理坐标系&#xff1a;GDAL命令…

Voice Conversion、DreamScene、X-SLAM、Panoptic-SLAM、DiffMap、TinySeg

本文首发于公众号&#xff1a;机器感知 Voice Conversion、DreamScene、X-SLAM、Panoptic-SLAM、DiffMap、TinySeg Converting Anyones Voice: End-to-End Expressive Voice Conversion with a Conditional Diffusion Model Expressive voice conversion (VC) conducts speak…

【爬虫】爬取A股数据写入数据库(一)

1. 对东方财富官网的分析 步骤&#xff1a; 通过刷新网页&#xff0c;点击等操作&#xff0c;我们发现https://datacenter-web.eastmoney.com/api/data/v1/get?请求后面带着一些参数即可以获取到相应数据。我们使用python来模拟这个请求即可。 我们以如下选择的页面为切入点…

滑动窗口 | 1652. 拆炸弹 |LeetCode

文章目录 题目介绍暴力(可以过力扣竟然。不愧是简单题)&#xff1a;滑动窗口 祝你天天开心 题目介绍 你有一个炸弹需要拆除&#xff0c;时间紧迫&#xff01;你的情报员会给你一个长度为 n 的 循环 数组 code 以及一个密钥 k 。 为了获得正确的密码&#xff0c;你需要替换掉每…

关系型数据库MySql分库分表带来的问题以及解决方案

水平分表 水平分表是什么&#xff1f; 将一张表横向拆分为多张表&#xff0c;拆分的表&#xff0c;依然在同一个库中。 例如&#xff0c;user表有400w条记录&#xff0c;将user表拆分成4张表&#xff0c;每张表100w条记录。拆分后的表名&#xff0c;分别叫做user_0、user1、u…

内网用户是如何连接上互联网的?详解NAT网络地址转换技术

背景 https://blog.csdn.net/weixin_43972437/article/details/107344633 不知道你有没有过困惑&#xff0c;都说现在 ipv4 地址耗尽了&#xff0c;但是我们为什么还能上网呢&#xff1f;原来这都要归功于 NAT 网络地址转换技术。 比如我们接入了中国移动的宽带&#xff0c;宽…
最新文章