使用 SpringBoot 读取 YAML 文件并将数据转为 Map,在嵌套 Map 中递归获取指定层级下的所有数据

文章目录

    • 引入
    • 认识 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") 的形式获取到指定交易,指定字段的脱敏规则。

主要逻辑实现

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

    Y3800:phone:length: 11rule: '(\\d{3})\\d{4}(\\d{4})'
    Y3801:idCard:length: 18rule: '(?<=\\w{3})\\w(?=\\w{4})'list:email:rule: '(\\w+)\\w{5}@(\\w+)'
    
  2. 定义工具类编写我们的逻辑:

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

    // YAML 文件路径
    private static final String YAML_FILE_PATH = "/sensitive.yml";// 存储解析后的 YAML 数据
    private static Map<String, Object> map;static {// 创建 Yaml 对象Yaml yaml = new Yaml();// 通过 getResourceAsStream 获取 YAML 文件的输入流try (InputStream in = YamlUtils.class.getResourceAsStream(YAML_FILE_PATH)) {// 解析 YAML 文件为 Map 对象map = yaml.loadAs(in, Map.class);} catch (Exception e) {e.printStackTrace();}
    }
    

    在上述代码中,我们首先定义了一个私有静态常量 YAML_FILE_PATH,用于存储 YAML 文件的路径;又定义了一个静态变量 map,用于存储解析后的 YAML 数据。

    接着通过 getResourceAsStream 方法根据指定的 YAML 文件的路径从类路径中获取资源文件的输入流。

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

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

  4. 编写方法通过 Key 获取对应脱敏规则:

    public static void main(String[] args) {// 加载 YAML 文件并获取顶层的 Map 对象Map<String, Object> yamlMap = loadYaml("/sensitive.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={length=11, rule=(\\d{3})\\d{4}(\\d{4})}}, Y3801={idCard={length=18, rule=(?<=\\w{3})\\w(?=\\w{4})}, list={email={rule=(\\w+)\\w{5}@(\\w+)}}}}
    {phone={length=11, rule=(\\d{3})\\d{4}(\\d{4})}}
    {length=11, rule=(\\d{3})\\d{4}(\\d{4})}
    

    转为 JSON 格式显示如下:

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

      {"Y3800": {"phone": {"length": 11,"rule": "(\\\\d{3})\\\\d{4}(\\\\d{4})"}},"Y3801": {"idCard": {"length": 18,"rule": "(?<=\\\\w{3})\\\\w(?=\\\\w{4})"},"list": {"email": {"rule": "(\\\\w+)\\\\w{5}@(\\\\w+)"}}}
      }
      
    • 输出 Y3800 层级下的数据:

      {"phone": {"length": 11,"rule": "(\\\\d{3})\\\\d{4}(\\\\d{4})"}
      }
      
    • 输出 phone 层级下的数据:

      {"length": 11,"rule": "(\\\\d{3})\\\\d{4}(\\\\d{4})"
      }
      

文章到此需要的功能基本实现,理论上该文到这里就结束了,但是我们仔细思考一下,我们通过 Key 获取指定层级下的数据时,需要我们不断的调用 Map.get("Key") 方法,即结构每嵌套一次,就需要一次 getKey,那么这里是否有优化的方法呢?

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

优化方法

优化思路为:通过递归和判断来遍历嵌套的 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("/sensitive.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. 返回结果。返回递归查找的结果。

完整工具类

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

/*** @Project demo1* @ClassName YamlUtils* @Description 读取YAML配置文件工具类* @Author 赵士杰* @Date 2024/1/26 20:15*/
@SuppressWarnings("unchecked")
public class YamlUtils {// YAML 文件路径private static final String YAML_FILE_PATH = "/sensitive.yml";// 存储解析后的 YAML 数据private static Map<String, Object> map;static {// 创建 Yaml 对象Yaml yaml = new Yaml();// 通过 getResourceAsStream 获取 YAML 文件的输入流try (InputStream in = YamlUtils.class.getResourceAsStream(YAML_FILE_PATH)) {// 解析 YAML 文件为 Map 对象map = yaml.loadAs(in, Map.class);} catch (Exception e) {e.printStackTrace();}}/*** 获取嵌套的 Map 数据** @param keys 嵌套键路径* @return 嵌套数据对应的 Map*/public static Map<String, Object> getNestedMap(String... keys) {return getNestedMapValues(map, keys);}/*** 递归获取嵌套 Map 数据** @param map  嵌套数据源的 Map* @param keys 嵌套键路径* @return 嵌套数据对应的 Map*/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;}}}}

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

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

相关文章

nvm安装的node,脚手架安装 vue 项目时报错

npm install -g vue/cli 时报错 解决办法 //修改npm的资源镜像: npm config set registry http://registry.npm.taobao.org重新脚手架安装 npm install -g vue/cli成功到下一步&#xff0c;当执行&#xff1a;vue create my-vue-demo时又报新的错 原因&#xff1a;【HTTPS …

part2. jdk9/10/11/12/16新特性详解

1.jdk9 1.1 模块化机制 定义模块&#xff1a;module-info.java module com.newfeature.test {requires java.se;requires lombok;requires junit; }package com.newfeature.test;import java.lang.reflect.Field;public class Main {public static void main(String[] args) …

mysql字符集

一、查看字符集 //查看数据库字符集 SHOW CREATE DATABASE database; //查看表字符集 SHOW CREATE DATABASE table; //查看指定表全部字段字符集 show full columns from table; 二、修改字符集 将超出utf8字符集范围的字符比如&#x2aa27;插入到utf8字符集的字段上会…

Element UI样式修改之NavMenu导航菜单箭头样式修改

UI设计稿给的菜单箭头样式可能与我们饿了么组件NavMenu的菜单箭头样式不一致,目前我们侧边导航菜单的上下翻转箭头如下所示: 希望得到如下的结果: 找到饿了么Icon里我们想要向下箭头,F12后复制content内容content: “\e790”; content: "\e790";然后将默认的c…

C语言基本概念

目录 2.1 编写一个简单的C程序 2.1.1 编译和链接 2.1.2 集成开发环境 2.2 简单程序的一般形式 2.2.1 指令 2.2.2 函数 2.2.3 语句 2.3 注释 2.4 变量和赋值 2.4.1 类型 2.4.2 声明 2.4.3 赋值 2.4.4 显示变量的值 2.4.5 初始化 2.4.6 显示表达式的值 2.5 读入…

网络通信(15)-C#TCP客户端掉线重连实例

本文上接前面的文章使用Socket在C#语言环境下完成TCP客户端的掉线重连实例。 掉线重连需要使用心跳包发送测试网络的状态,进而进入重连循环线程。 前面实例完成的功能: 客户端与服务器连接,实现实时刷新状态。 客户端接收服务器的数据。 客户端发送给服务器的数据。 客…

pytorch代码实现注意力机制之MLCA

MLCA注意力机制 简要&#xff1a;注意力机制是计算机视觉中使用最广泛的组件之一&#xff0c;可以帮助神经网络强调重要元素并抑制不相关的元素。然而&#xff0c;绝大多数信道注意力机制仅包含信道特征信息而忽略了空间特征信息&#xff0c;导致模型表示效果或目标检测性能较…

docker之部署青龙面板

青龙面板是一个用于管理和监控 Linux 服务器的工具&#xff0c;具有定时运行脚本任务的功能。在实际情况下也可以用于一些定期自动签到等任务脚本的运行。 本次记录下简单的安装与使用&#xff0c;请提前安装好docker&#xff0c;参考之前的文章。 一、安装部署 1、拉取镜像 # …

weak_ptr 与 一个难发现的错误(循环依赖问题)笔记

推荐B站视频&#xff1a;7.weak_ptr与一个非常难发现的错误_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV18B4y187uL/?p7&spm_id_frompageDriver&vd_sourcea934d7fc6f47698a29dac90a922ba5a3一、weak_ptr weak_ptr并不拥有所有权并不能调用 -> 和 解引…

ACL--访问控制列表概述、组成、分类、应用

目录 一、ACL概述 二、ACL的组成 三、ACL分类 四、举例说明 1、基于标准ACL和基础的高级ACL应用 2、基于端口的ACL 一、ACL概述 访问控制列表ACL&#xff08;Access Control List&#xff09;是由一条或多条规则组成的集合。所谓规则&#xff0c;是指描述报文匹配条件的…

热门免费API集合收藏

IP归属地-IPv4区县级&#xff1a;根据IP地址查询归属地信息&#xff0c;包含43亿全量IPv4&#xff0c;支持到中国地区&#xff08;不含港台地区&#xff09;区县级别&#xff0c;含运营商数据。 IP归属地-IPv6区县级&#xff1a;根据IP地址&#xff08;IPv6版本&#xff09;查…

黑马程序员——javase进阶——day02——关键字,接口,代码块,枚举

目录&#xff1a; Java中的关键字 static关键字final关键字Java中的权限修饰符代码块 构造代码块静态代码块接口 接口的介绍接口的定义和特点接口的成员特点接口的案例接口中成员方法的特点枚举随堂小记 继承方法重写抽象类模板设计模式staticfinal权限修饰符接口回顾上午内容…

大数据开发之Spark(完整版)

第 1 章&#xff1a;Spark概述 1.1 什么是spark 回顾&#xff1a;hadoop主要解决&#xff0c;海量数据的存储和海量数据的分析计算。 spark是一种基于内存的快速、通用、可扩展的大数据分析计算引擎。 1.2 hadoop与spark历史 hadoop的yarn框架比spark框架诞生的晚&#xff…

Objective-C方法的声明实现及调用

1.无参数的方法 1)声明 a.位置&#xff1a;在interface括弧的外面 b.语法&#xff1a; - (返回值类型)方法名称; interface Person : NSObject -(void) run; end 2)实现 a.位置&#xff1a;在implementation中实现 b.语法&#xff1a;加大括弧将方法实现的代码写在大括孤之中 …

mybatisPlus apply作用及代码示例

MyBatis Plus 的 apply 方法可以帮助我们在查询条件中使用自定义的 SQL 片段&#xff0c;它允许我们将任意的 SQL 代码片段作为查询条件添加到 SQL 语句中。当我们需要通过 SQL 函数或表达式来构建复杂的查询条件时&#xff0c;就可以使用 apply 方法。 apply 方法的使用方法比…

KernelGPT: LLM for Kernel Fuzzing

KernelGPT: Enhanced Kernel Fuzzing via Large Language Models 1.Introduction2.Background2.1.Kernel and Device Drivers2.2.Kernel Fuzzing2.2.1.Syzkaller规约2.2.2.规约生成 3.Approach3.1.Driver Detection3.2.Specification Generation3.2.1.Command Value3.2.2.Argum…

基于静态顺序表实现通讯录

目录 一、设计框架 1、功能要求​ 2、菜单函数的实现 二、头文件实现​ Contact.h SeqList.h 三、Test.h 四、通讯录的初始化和销毁 五、增加通讯录 六、在通讯录中查找姓名下标 七、删除通讯录 八、显示通讯录 九、查找通讯录 一、设计框架 test.c&#xff1a;通…

《尊思想人文地理环境》新书亮相,叶无为集30年智慧破解环境密码

在探索人与自然和谐共生的今天&#xff0c;叶无为教授的新作《尊思想人文地理环境》应时而生&#xff0c;为读者揭开了地理环境与人文发展之间深刻联系的神秘面纱。本书集结了作者三十多年的实战经验&#xff0c;通过易医体系对大自然的山川河流进行独到解析&#xff0c;融合传…

飞越天空之城

欢迎来到程序小院 飞越天空之城 玩法&#xff1a;左边的按钮是控制小人儿飞起来的方向的&#xff0c;右边的按钮是控制它飞的高度的&#xff0c; 左边控制在正上方时可以让小人儿沿着一个方向飞跃&#xff0c;否则会撞到两边的黑墙&#xff0c; 右边的按钮如果加足够的话&…

GPTs大受欢迎但问题多,企服厂商的AI Agent更被B端客户器重

2023年11月&#xff0c;OpenAI在首届开发者大会上推出了GPTs和Assitant API&#xff0c;不仅改写了AI Agent的构建范式&#xff0c;也把AI智能体的应用推向一个新高潮。 GPTs和GPT商店&#xff0c;使得用户无需编码通过自然语言就能创建并拥有多个专属私人助理&#xff0c;且可…