【数据脱敏方案】不使用 AOP + 注解,使用 SpringBoot+YAML 实现

文章目录

    • 引入
    • 认识 YAML 格式规范
    • 定义脱敏规则格式
    • 脱敏逻辑实现
      • 读取 YAML 配置文件获取脱敏规则
      • 通过键路径获取对应字段规则
        • 原始
        • 优化后
      • 对数据进行脱敏处理
      • 递归生成字段对应的键路径
      • 脱敏测试
    • 完整工具类

引入

在项目中遇到一个需求,需要对交易接口返回结果中的指定字段进行脱敏操作,但又不能使用AOP+注解的形式,于是决定使用一种比较笨的方法:

  1. 首先将所有需要脱敏字段及其对应脱敏规则存储到 Map 中。
  2. 在接口返回时,遍历结果中的所有字段,判断字段名在 Map 中是否存在:
    • 如果不存在:说明该字段不需要脱敏,不做处理即可。
    • 如果存在:说明该字段需要脱敏,从 Map 中获取对应的脱敏规则进行脱敏。
  3. 最后返回脱敏之后的结果。

认识 YAML 格式规范

由于返回的结果涉及到嵌套 Map,所以决定采用 YAML 格式的文件存储脱敏规则,那么为了大家统一维护和开发,就需要大家对 YAML 格式进行了解,遵守规范,不易出错,少走弯路。

YAML(YAML Ain’t Markup Language)与传统的 JSON、XML 和 Properties 文件一样,都是用于数据序列化的格式,常用于配置文件和数据传输。

相比于其他格式,YAML 是一种轻量级的数据序列化格式,它的设计初衷是为了简化复杂性,提高人类可读性,并且易于实现和解析。

  • 与 JSON 相比:YAML 在语法上更为灵活,允许使用更简洁的方式来表示数据结构。

  • 与 XML 相比:YAML 的语法更为简洁,没有繁琐的标签和尖括号。

  • 与 Properties 相比:YAML 支持更复杂的数据结构,包括嵌套的键值对和列表。

除此之外,YAML 还支持跨平台、跨语言,可以被多种编程语言解析,这使得YAML非常适合用于不同语言之间的数据传输和交换。

YAML 文件的语法非常简洁明了,以下是它的语法规范:

  1. 基本语法:

    • 使用 缩进表示层级关系,可以使用空格或制表符进行缩进,但不能混用。
    • 使用冒号(:)表示键值对,键值对之间使用换行分隔。
    • 使用破折号(-)表示列表项,列表项之间也使用换行分隔。
    # 使用缩进表示层级关系
    server:port: 8080# 使用冒号表示键值对
    name: John Smith
    age: 30# 使用破折号表示列表项
    hobbies:- reading- hiking- swimming
    
  2. 注释:

    • 使用井号(#)表示注释,在 # 后面的内容被视为注释,可以出现在行首或行尾。
    # 这是一个注释
    name: John Smith
    age: 30 # 这也是一个注释
    
  3. 字符串:

    • 字符串可以使用单引号或双引号括起来,也可以不使用引号。
    • 使用双引号时,可以使用转义字符(如 \n 表示换行)和转义序列(如 \u 表示 Unicode 字符)。
    # 使用双引号表示字符串
    name: "John Smith"# 使用单引号表示字符串
    nickname: 'Johnny'
    
  4. 键值对:

    • 键值对使用冒号(:)表示,键和值之间使用一个 空格 分隔。
    • 键可以是字符串或纯量(如整数、布尔值等)。
    • 值可以是字符串、纯量、列表或嵌套的键值对。
    # 键和值之间使用一个空格分隔
    name: John Smith# 键可以是字符串或纯量
    age: 30# 值可以是字符串、纯量、列表或嵌套的键值对
    address:city: San Franciscostate: Californiazip: 94107
    
  5. 列表:

    • 使用破折号(-)表示列表项。
    • 列表项可以是字符串、纯量或嵌套的列表或键值对。
    # 使用破折号表示列表项
    hobbies:- reading- hiking- swimming# 列表项可以是字符串、纯量或嵌套的列表或键值对
    people:- name: John Smithage: 30- name: Jane Doeage: 25
    
  6. 引用:

    • 使用&表示引用,使用*表示引用的内容。
    # 使用&表示引用
    address: &myaddresscity: San Franciscostate: Californiazip: 94107# 使用*表示引用的内容
    shippingAddress: *myaddress
    
  7. 多行文本块:

    • 使用|保留换行符,保留文本块的精确格式。
    • 使用>折叠换行符,将文本块折叠成一行,并根据内容自动换行。
    # 使用|保留换行符
    description: |This is amulti-linestring.# 使用>折叠换行符
    summary: >This is a summarythat may containline breaks.
    
  8. 数据类型:

    • YAML支持多种数据类型,包括字符串、整数、浮点数、布尔值、日期和时间等。
    • 可以使用标记来表示一些特殊的数据类型,如 !!str 表示字符串类型、!!int 表示整数类型等。
    # 使用标记表示数据类型
    age: !!int 30
    weight: !!float 65.5
    isMale: !!bool true
    created: !!timestamp '2022-01-01 12:00:00'
    
  9. 多文件:

    • 可以使用—表示多个 YAML 文件之间的分隔符。每个文件可以使用任何 YAML 语法。
    # 第一个YAML文件
    name: John Smith
    age: 30---# 第二个YAML文件
    hobbies:- reading- hiking- swimming
    

定义脱敏规则格式

对于数据结构简单的接口返回结果,脱敏规则格式定义为【交易号->字段->规则】:

交易号:字段名:规则: '/^(1[3-9][0-9])\d{4}(\d{4}$)/'

同时接口返回的结果中可能用有嵌套列表,那么针对这种复杂的结构就定义格式为【交易号->字段(列表)->字段->规则】,即:

交易号:字段名(列表):字段名:规则: '/^(1[3-9][0-9])\d{4}(\d{4}$)/'

使用这种层级结构,我们完全可以通过 Map.get("Key") 的形式获取到指定交易,指定字段的脱敏规则。

脱敏逻辑实现

读取 YAML 配置文件获取脱敏规则

  1. 首先创建 YAML 文件 desensitize.yml 添加对应交易字段的脱敏规则:

    Y3800:phone:rule: "(\\d{3})\\d{4}(\\d{4})"format: "$1****$2"idCard:rule: "(?<=\\w{6})\\w(?=\\w{4})"format: "*"
    Y3801:idCard:rule: "(?<=\\w{3})\\w(?=\\w{4})"format: "+"list:phone:rule: "(\\d{3})\\d{4}(\\d{4})"format: "$1++++$2"
    
  2. 定义脱敏工具类 DataDesensitizationUtils 编写我们的脱敏逻辑:

    public class DataDesensitizationUtils {
    }
    
  3. DataDesensitizationUtils 工具类中,我们需要实现在项目启动时,读取 desensitize.yml 文件中的内容,并转为我们想要的 Map 键值对数据类型:

    /*** 读取yaml文件内容并转为Map* @param yamlFile yaml文件路径* @return Map对象*/
    public static Map<String, Object> loadYaml(String yamlFile) {Yaml yaml = new Yaml();try (InputStream in = DataDesensitizationUtils.class.getResourceAsStream(yamlFile)) {return yaml.loadAs(in, Map.class);} catch (Exception e) {e.printStackTrace();}return null;
    }
    

    在上述代码中,我们通过 getResourceAsStream 方法根据指定的 YAML 文件的路径从类路径中获取资源文件的输入流。

    然后使用 loadAs 方法将输入流中的内容按照 YAML 格式进行解析,并将解析结果转换为指定的 Map.class 类型。

    最后使用 try-with-resources 语句来自动关闭输入流。

通过键路径获取对应字段规则

原始
  1. 在上文中我们已经将 desensitize.yml 文件中所有的脱敏规则都以 key-Value 的形式存储到了 Map 中,因此我们只需要通过 Key 从 Map 中获取即可。接下来编写方法通过 Key 获取指定字段对应脱敏规则:

    public static void main(String[] args) {// 加载 YAML 文件并获取顶层的 Map 对象,路径基于 resources 目录Map<String, Object> yamlMap = loadYaml("/desensitize.yml");System.out.println(yamlMap);// 从顶层的 Map 中获取名为 "Y3800" 的嵌套 MapMap<String, Object> Y3800= (Map<String, Object>) yamlMap.get("Y3800");System.out.println(Y3800);// 从 "Y3800" 的嵌套 Map 中获取名为 "phone" 的嵌套 MapMap<String, Object> phone = (Map<String, Object>) Y3800.get("phone");System.out.println(phone);
    }
    

    输出结果如下:

    {Y3800={phone={rule=(\d{3})\d{4}(\d{4}), format=$1****$2}, idCard={rule=(?<=\w{3})\w(?=\w{4}), format=*}}, Y3801={name={rule=.(?=.), format=+}, idCard={rule=(?<=\w{3})\w(?=\w{4}), format=+}, list={card={rule=\d(?=\d{4}), format=+}}}}
    {phone={rule=(\d{3})\d{4}(\d{4}), format=$1****$2}, idCard={rule=(?<=\w{3})\w(?=\w{4}), format=*}}
    {rule=(\d{3})\d{4}(\d{4}), format=$1****$2}
    

    转为 JSON 格式显示如下:

    • 输出 YAML 文件中的全部数据:

      {"Y3800": {"phone": {"rule": "(\\d{3})\\d{4}(\\d{4})","format": "$1****$2"},"idCard": {"rule": "(?<=\\w{3})\\w(?=\\w{4})","format": "*"}},"Y3801": {"name": {"rule": ".(?=.)","format": "+"},"idCard": {"rule": "(?<=\\w{3})\\w(?=\\w{4})","format": "+"},"list": {"card": {"rule": "\\d(?=\\d{4})","format": "+"}}}
      }
      
    • 输出 Y3800 层级下的数据:

      {"phone": {"rule": "(\\d{3})\\d{4}(\\d{4})","format": "$1****$2"},"idCard": {"rule": "(?<=\\w{3})\\w(?=\\w{4})","format": "*"}
      }
      
    • 输出 phone 层级下的数据:

      {"rule": "(\\d{3})\\d{4}(\\d{4})","format": "$1****$2"
      }
      

在这里,我们需要仔细思考一下,在我们通过 Key 获取指定层级下的数据时,我们需要不断的调用 Map.get("Key") 方法,即结构每嵌套一次,就需要一次 getKey,那么这里是否有优化的方法呢?

答案是:有的,因为有问题就会有答案。

优化后

首先我们需要先了解一个概念:

Y3800:phone:rule: "(\\d{3})\\d{4}(\\d{4})"format: "$1****$2"

当我们要从上述数据中获取 phone 的脱敏规则时,我们需要先从 Map 中 get("Y3800") 获取 Y3800 下的数据,再通过 get("phone") 获取 phone 下的规则,那么 Y3800->phone 就是 phone 的键路径。

基于此,我们可以实现这样一个方法,我们直接给出指定字段的键路径,在方法中通过递归的方式从 Map 中获取到该键路径下的所有数据,然后返回即可。

即优化思路为:通过递归和判断来遍历嵌套的 Map,直到找到键路径所对应的最里层的嵌套 Map,并返回该 Map 对象。

优化后方法如下:

/*** 递归获取嵌套 Map 数据** @param map  嵌套数据源的 Map* @param keys 嵌套键路径* @return 嵌套数据对应的 Map*/
@SuppressWarnings("unchecked")
public static Map<String, Object> getNestedMapValues(Map<String, Object> map, String... keys) {// 如果键路径为空或者第一个键不在 Map 中,则返回 nullif (keys.length == 0 || !map.containsKey(keys[0])) {return null;}// 获取第一个键对应的嵌套对象Object nestedObject = map.get(keys[0]);// 如果键路径长度为 1,说明已经到达最里层的嵌套 Map,直接返回该 Map 对象if (keys.length == 1) {if (nestedObject instanceof Map) {return (Map<String, Object>) nestedObject;} else {return null;}} else {// 如果嵌套对象是 Map,继续递归查找下一个键的嵌套 Mapif (nestedObject instanceof Map) {return getNestedMapValues((Map<String, Object>) nestedObject, Arrays.copyOfRange(keys, 1, keys.length));} else {// 嵌套对象既不是 Map 也不是 List,返回 nullreturn null;}}
}

调用方法时传入 Key 的嵌套路径即可:

public static void main(String[] args) {// 加载 YAML 文件并获取顶层的 Map 对象Map<String, Object> yamlMap = loadYaml("/desensitize.yml");System.out.println(yamlMap);// 获取 Y3800 -> phone 下的数据转为 MapMap<String, Object> y3800PhoneMap = YamlUtils.getNestedMap(yamlMap, "Y3800", "phone");System.out.println("Y3800 -> phone : " + y3800NameMap);
}

具体来说,主要分为以下几步:

  1. 首先判断键路径是否为空或者第一个键是否在 Map 中。如果键路径为空或者第一个键不在 Map 中,则返回 null。
  2. 获取第一个键对应的嵌套对象。通过 get 方法获取第一个键对应的嵌套对象。
  3. 判断是否到达最里层的嵌套 Map。如果键路径长度为 1,说明已经到达最里层的嵌套 Map,直接返回该 Map 对象。
  4. 继续递归查找下一个键的嵌套 Map。如果嵌套对象是 Map,则继续递归查找下一个键的嵌套 Map。
  5. 返回结果。返回递归查找的结果。

对数据进行脱敏处理

获取到字段的脱敏规则后,我们就可以编写方法实现对源数据做脱敏处理,脱敏方法如下:

/*** 使用指定规则对数据进行脱敏处理** @param data 要进行脱敏处理的数据* @param map 包含脱敏规则和格式的参数映射*            - "rule" 表示脱敏规则的正则表达式*            - "format" 表示替换脱敏部分的字符串,默认为 "*"* @return 脱敏后的数据*/
private static String desensitizeLogic(String data, Map<String, Object> map) {if (map.containsKey("rule")) {String rule = (String) map.get("rule");String sign = "*";if (map.containsKey("format")) {sign = (String) map.get("format");}return data.replaceAll(rule, sign);}return data;
}

递归生成字段对应的键路径

目前我们已经实现了通过字段的键路径获取到该字段对应规则的方法 getNestedMapValues(),那么接下来我们只需要生成字段对应的键路径,然后调用方法 getNestedMapValues() 获取到脱敏规则后调用 desensitizeLogic() 对源数据进行脱敏即可。

提供源数据格式如下:

{"txEntity": {"idCard": "130428197001180384","name": "赵士杰","list": [{"phone": "17631007015"},{"phone": "17631007015"}]},"txHeader": {"servNo": "Y3801"}
}

根据上述数据结构,首先我们需要从 txHeader 中获取 servNo,之后递归遍历 txEntity 中的元素即可。

具体方法如下:

/*** 对指定实体数据进行脱敏处理** @param entity 要进行脱敏处理的实体数据* @param servNo 当前交易的服务号,用于记录日志* @param path 当前实体数据在整个数据结构中的路径,用于记录日志*/
public static void parseData(Object entity, String servNo, String path) {if (entity instanceof Map) {for (Map.Entry<String, Object> entry : ((Map<String, Object>) entity).entrySet()) {// 计算当前键值对在整个数据结构中的路径String currentPath = path.isEmpty() ? entry.getKey() : path + "," + entry.getKey();if (entry.getValue() instanceof Map) {// 如果当前值是 Map 类型,则递归处理子节点parseData(entry.getValue(), servNo, currentPath);} else if (entry.getValue() instanceof List) {// 如果当前值是 List 类型,则遍历列表中的每个元素并递归处理子节点for (Object item : (List) entry.getValue()) {if (item instanceof Map) {parseData(item, servNo, currentPath);}}} else {// 如果当前值不是 Map 或 List,则进行脱敏处理String p = servNo + "," +currentPath;String[] keyPaths = p.split(",");// 获取当前节点的脱敏规则和格式Map<String, Object> nestedMap = getNestedMap(keyPaths);if(Objects.nonNull(nestedMap)){// 记录日志log.info("-----------------交易【{}】,字段【{}】开始脱敏-----------------",servNo,currentPath.replace(",","->"));log.info("原始值:【{}:{}】",entry.getKey(),entry.getValue());log.info("脱敏规则:{}",nestedMap);// 对当前节点的值进行脱敏处理String desensitized = desensitizeLogic((String) entry.getValue(), nestedMap);entry.setValue(desensitized);// 记录日志log.info("脱敏值:【{}:{}】",entry.getKey(),entry.getValue());log.info("-----------------交易【{}】,字段【{}】脱敏结束-----------------",servNo,currentPath.replace(",","->"));}}}}
}

该方法接收一个实体数据 entity,一个服务号 servNo 和一个路径 path 作为参数。在方法体内,会遍历实体数据的键值对,并根据具体情况递归处理子节点或进行脱敏处理。

  • 当实体数据的值为 Map 类型时,方法会递归处理子节点;
  • 当值为 List 类型时,方法会遍历列表中的每个元素并递归处理子节点;
  • 当值既不是 Map 也不是 List 时,方法会根据服务号和路径获取脱敏规则,并对当前节点的值进行脱敏处理,并记录脱敏日志。

脱敏处理的具体逻辑和规则通过调用 getNestedMap 方法和 desensitizeLogic 方法来实现,其中 getNestedMap 方法用于获取脱敏规则,desensitizeLogic 方法用于根据脱敏规则对数据进行脱敏处理。

注:请注意本文中提供的数据样例的层次结构是和 YAML 中定义的结构是一样的,再通过上述方法递归后生成的键路径是和从 YAML 中获取规则所需的键路径是一致的,因此可以直接调用 getNestedMapValues() 获取脱敏规则。在实际使用中,其他数据结构需要重写该逻辑。

脱敏测试

编写 Main 方法调用:

public class Demo {public static Map<String, Object> getData() {HashMap<String, Object> phone = new HashMap<>();phone.put("phone", "17631007015");HashMap<String, Object> phone2 = new HashMap<>();phone2.put("phone", "17631007015");List<HashMap<String, Object>> list = new ArrayList<>();list.add(phone);list.add(phone2);HashMap<String, Object> txEntity = new HashMap<>();txEntity.put("name", "赵士杰");txEntity.put("idCard", "130428197001180384");txEntity.put("list", list);HashMap<String, Object> result = new HashMap<>();result.put("txEntity", txEntity);HashMap<String, Object> txHeader = new HashMap<>();txHeader.put("servNo", "Y3801");result.put("txHeader", txHeader);return result;}public static void main(String[] args) {Map<String, Object> data = getData();// 假设data中包含接口返回的数据if (data.containsKey("txHeader") && data.get("txHeader") instanceof Map) {String servNo = ((Map<String, String>) data.get("txHeader")).get("servNo");DataDesensitizationUtils.parseData(data.get("txEntity"), servNo, "");}}}

运行测试,控制台输出如下:

-----------------交易【Y3801】,字段【idCard】开始脱敏-----------------
原始值:【idCard:130428197001180384】
脱敏规则:{rule=(?<=\w{3})\w(?=\w{4}), format=+}
脱敏值:【idCard:130+++++++++++0384】
-----------------交易【Y3801】,字段【idCard】脱敏结束-----------------
-----------------交易【Y3801】,字段【list->phone】开始脱敏-----------------
原始值:【phone:17631007015】
脱敏规则:{rule=(\d{3})\d{4}(\d{4}), format=$1++++$2}
脱敏值:【phone:176++++7015】
-----------------交易【Y3801】,字段【list->phone】脱敏结束-----------------
-----------------交易【Y3801】,字段【list->phone】开始脱敏-----------------
原始值:【phone:17631007015】
脱敏规则:{rule=(\d{3})\d{4}(\d{4}), format=$1++++$2}
脱敏值:【phone:176++++7015】
-----------------交易【Y3801】,字段【list->phone】脱敏结束-----------------

数据脱敏后如下:

{"txEntity": {"idCard": "130+++++++++++0384","name": "赵士杰","list": [{"phone": "176++++7015"},{"phone": "176++++7015"}]},"txHeader": {"servNo": "Y3801"}
}

完整工具类

封装成完整的工具类如下:

/*** @ClassName DataDesensitizationUtils* @Description 数据脱敏工具类* @Author 赵士杰* @Date 2024/1/25 20:15*/
@Slf4j
@SuppressWarnings("unchecked")
public class DataDesensitizationUtils {// YAML 文件路径private static final String YAML_FILE_PATH = "/tuomin.yml";// 存储解析后的 YAML 数据private static Map<String, Object> map;static {// 创建 Yaml 对象Yaml yaml = new Yaml();// 通过 getResourceAsStream 获取 YAML 文件的输入流try (InputStream in = DataDesensitizationUtils.class.getResourceAsStream(YAML_FILE_PATH)) {// 解析 YAML 文件为 Map 对象map = yaml.loadAs(in, Map.class);} catch (Exception e) {e.printStackTrace();}}/*** 获取嵌套的 Map 数据** @param keys 嵌套键路径* @return 嵌套数据对应的 Map*/private static Map<String, Object> getNestedMap(String... keys) {return getNestedMapValues(map, keys);}/*** 递归获取嵌套 Map 数据** @param map  嵌套数据源的 Map* @param keys 嵌套键路径* @return 嵌套数据对应的 Map*/private static Map<String, Object> getNestedMapValues(Map<String, Object> map, String... keys) {// 如果键路径为空或者第一个键不在 Map 中,则返回 nullif (keys.length == 0 || !map.containsKey(keys[0])) {return null;}// 获取第一个键对应的嵌套对象Object nestedObject = map.get(keys[0]);// 如果键路径长度为 1,说明已经到达最里层的嵌套 Map,直接返回该 Map 对象if (keys.length == 1) {if (nestedObject instanceof Map) {return (Map<String, Object>) nestedObject;} else {return null;}} else {// 如果嵌套对象是 Map,继续递归查找下一个键的嵌套 Mapif (nestedObject instanceof Map) {return getNestedMapValues((Map<String, Object>) nestedObject, Arrays.copyOfRange(keys, 1, keys.length));} else {// 嵌套对象既不是 Map 也不是 List,返回 nullreturn null;}}}/*** 对指定实体数据进行脱敏处理** @param entity 要进行脱敏处理的实体数据* @param servNo 当前交易的服务号,用于记录日志* @param path 当前实体数据在整个数据结构中的路径,用于记录日志*/public static void parseData(Object entity, String servNo, String path) {if (entity instanceof Map) {for (Map.Entry<String, Object> entry : ((Map<String, Object>) entity).entrySet()) {String currentPath = path.isEmpty() ? entry.getKey() : path + "," + entry.getKey();if (entry.getValue() instanceof Map) {parseData(entry.getValue(), servNo, currentPath);} else if (entry.getValue() instanceof List) {for (Object item : (List) entry.getValue()) {if (item instanceof Map) {parseData(item, servNo, currentPath);}}} else {String p = servNo + "," + currentPath;String[] keyPaths = p.split(",");Map<String, Object> nestedMap = getNestedMap(keyPaths);if (Objects.nonNull(nestedMap)) {log.info("-----------------交易【{}】,字段【{}】开始脱敏-----------------", servNo, currentPath.replace(",", "->"));log.info("原始值:【{}:{}】", entry.getKey(), entry.getValue());log.info("脱敏规则:{}", nestedMap);String desensitized = desensitizeLogic((String) entry.getValue(), nestedMap);entry.setValue(desensitized);log.info("脱敏值:【{}:{}】", entry.getKey(), entry.getValue());log.info("-----------------交易【{}】,字段【{}】脱敏结束-----------------", servNo, currentPath.replace(",", "->"));}}}}}/*** 脱敏逻辑* @param data 源数据* @param map 脱敏规则* @return 脱敏后的数据*/private static String desensitizeLogic(String data, Map<String, Object> map) {if (map.containsKey("rule")) {String rule = (String) map.get("rule");String sign = "*";if (map.containsKey("format")) {sign = (String) map.get("format");}return data.replaceAll(rule, sign);}return data;}}

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

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

相关文章

【学网攻】 第(17)节 -- 命名ACL访问控制列表

系列文章目录 目录 前言 一、ACL(访问控制列表)是什么&#xff1f; 二、实验 1.引入 总结 文章目录 【学网攻】 第(1)节 -- 认识网络【学网攻】 第(2)节 -- 交换机认识及使用【学网攻】 第(3)节 -- 交换机配置聚合端口【学网攻】 第(4)节 -- 交换机划分Vlan【学网攻】 第…

2024美赛数学建模赛题解读常用模型算法

回归拟合预测 拟合预测是建立一个模型去逼近实际数据序列的过程&#xff0c;适用于发展性的体系。建立模型时&#xff0c;通常都要指定一个有明确意义的时间原点和时间单位。而且&#xff0c;当t趋向于无穷大时&#xff0c;模型应当仍然有意义。将拟合预测单独作为一类体系研究…

Linux--redhat9创建软件仓库

1.插入光盘&#xff0c;挂载镜像 模拟插入光盘: 点击:虚拟机-可移动设备-CD/DVD 设备状态全选&#xff0c;使用ISO影响文件选择当前版本镜像&#xff0c;点击确认。 2.输入: df -h 可以显示&#xff0c;默认/dev/sr0文件为光盘文件&#xff0c;挂载点为/run/media/root/镜像…

SpringBoot 集成 ClickHouse

SpringBoot 集成 ClickHouse 1. 引入maven 依赖 <dependency><groupId>com.clickhouse</groupId><artifactId>clickhouse-jdbc</artifactId><version>${clickhouse.jdbc.version}</version></dependency>最新版本可以 在 mv…

GoLang和GoLand的安装和配置

1. GoLang 1.1 特点介绍 Go 语言保证了既能达到静态编译语言的安全和性能&#xff0c;又达到了动态语言开发维护的高效率&#xff0c;使用一个表达式来形容 Go 语言&#xff1a;Go C Python , 说明 Go 语言既有 C 静态语言程序的运行速度&#xff0c;又能达到 Python 动态语…

Flutter canvas 画一条波浪线 进度条

之前用 Flutter Canvas 画过一个三角三角形&#xff0c;html 的 Canvas 也画过一次类似的&#xff0c; 今天用 Flutter Canvas 试了下 感觉差不多&#xff1a; html 版本 大致效果如下&#xff1a; 思路和 html 实现的类似&#xff1a; 也就是找出点的位置&#xff0c;使用二阶…

OCP NVME SSD规范解读-8.SMART日志要求-4

SMART-21&#xff1a;这段描述解释了一个与设备内部I/O操作非对齐相关的计数器功能。该计数器记录的是由NVMe SSD执行的、起始地址未按照设备内部间接寻址单元&#xff08;IU&#xff0c;Indirection Unit&#xff09;大小进行对齐的写入I/O操作数量。 “Alignment”指的是每次…

15EG使用vivado2023.1建立hello world工程

1:打开软件建立工程 2:使用vivado创建设计模块并生成bit文件 3:导出硬件平台&#xff0c;使用vitis建立工程 4:使用vitis创建应用程序项目 5:硬件设置与调试 1:打开软件建立工程 打开VIVADO2023.1 创建一个新的工程 输入项目名称和地址&#xff0c;下面那个选项为是否…

深度学习-自注意力机制

文字编码 one-hot编码&#xff0c;让模型自己去学习怎么进行编码 常见的输出 1.每个词都有一个输出值 2.每个句子输出一个值&#xff0c;情感分类 3.输入与输出长度不对应&#xff0c;翻译任务&#xff0c;生成任务。 RNN最早的语言处理 RNN解决的是模型去考虑前面的输入…

第38章 补充定义6 正交,紧性

继续之前讲的内容&#xff0c;之前的坐标开始进入到笛卡尔坐标了&#xff0c;这个笛卡尔其实是个半成品的东西&#xff0c;能用但是不够好用&#xff0c;通用性确实好&#xff0c;不过作为各种各样的空间的基础够用了&#xff0c; 还是需要用到点乘和纯量乘 基底&#xff08;…

TS项目实战一:流淌的字符动画界面

使用ts实现虚拟世界&#xff0c;创建ts项目&#xff0c;并编写ts代码&#xff0c;使用tsc编译后直接加载到html界面&#xff0c;实现类似黑客帝国中的流淌的代码界面的效果。 源码下载地址&#xff1a;点击下载 讲解视频 TS实战项目一&#xff1a;数字流界面项目创建 TS实战项…

计算机设计大赛 深度学习 opencv python 实现中国交通标志识别

文章目录 0 前言1 yolov5实现中国交通标志检测2.算法原理2.1 算法简介2.2网络架构2.3 关键代码 3 数据集处理3.1 VOC格式介绍3.2 将中国交通标志检测数据集CCTSDB数据转换成VOC数据格式3.3 手动标注数据集 4 模型训练5 实现效果5.1 视频效果 6 最后 0 前言 &#x1f525; 优质…

useEffect的第二个参数

目录 1、第一个参数&#xff1a; 2、第二个参数&#xff1a; 2.1 不传值&#xff1a;无限循环 2.2 空数组作为依赖&#xff1a;执行一次 2.3 基本类型作为依赖&#xff1a;无限循环 2.4 引用类型 2.4.1 数组作为依赖&#xff1a;无限循环 2.4.2 函数作为依赖&#…

力扣(leetcode)第118题杨辉三角(Python)

118.杨辉三角 题目链接&#xff1a;118.杨辉三角 给定一个非负整数 numRows&#xff0c;生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 示例 1: 输入: numRows 5 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]] …

高光谱图像加载、归一化和增强(jupyter book)

1.获取高光谱图像&#xff1a;我用的是indian_pines的数据集&#xff0c;感兴趣的兄弟可以自行去官方网下载&#xff0c;gt的那个是它的标签哦&#xff0c;别搞错了。 2.图像加载&#xff1a; &#xff08;1&#xff09;从本地路径加载 import scipy.io as sio# 文件路径 fil…

乘积数量(c++题解)

题目描述 给定一个长度为 n 且不包含 0 的整数序列 。 请你计算以下两值&#xff1a; 使得 为负的索引对 l,r 的数量。使得 为正的索引对 l,r 的数量。 输入格式 第一行一个整数 。 第二行包含 个整数 。 输出格式 共一行&#xff0c;输出单个空格隔开的两个整数&a…

蓝桥杯---牌型种数

小明被劫持到X赌城&#xff0c;被迫与其他3人玩牌。一副扑克牌(去掉大小王牌,共52张)&#xff0c;均匀发给4个人&#xff0c;每个人13张。这时&#xff0c;小明脑子里突然冒出一个问题&#xff1a;如果不考虑花色&#xff0c;只考虑点数&#xff0c;也不考虑自己得到的牌的先后…

排序链表---归并--链表OJ

https://leetcode.cn/problems/sort-list/submissions/499363940/?envTypestudy-plan-v2&envIdtop-100-liked 这里我们直接进阶&#xff0c;用时间复杂度O(nlogn)&#xff0c;空间复杂度O(1)&#xff0c;来解决。 对于归并&#xff0c;如果自上而下的话&#xff0c;空间复…

Collections集合工具类-JAVA

java.util.Collections:是个集合工具类它不是集合&#xff0c;而是集合的工具类 常用 API&#xff1a;注意 binarySearch 方法要求元素有序 方法实现&#xff1a; public class Test01 {public static void main(String[] args) {ArrayList<String>list1new ArrayList…

SpringBoot整合RabbitMq企业级使用封装

SpringBoot整合RabbitMq企业级使用封装 1、RabbitMq基础说明2、SpringBoot整合RabbitMq&#xff0c;以及RabbitMq的封装和高级用法2.1、pom.xml2.2、application.yml2.3、Mq配置类MessageQueueConfiguration2.3.1、代码2.3.2、设置replyTimeout2.3.3、publisher-confirm-type和…