yml文件上传并映射到实体类

文章目录

      • 功能背景
      • 功能需要
      • 前端开发
        • 组件选用
        • 组件嵌套和参数绑定
        • 上传逻辑示例
      • 后端开发
        • 接收逻辑
        • 解析逻辑
          • 省流
          • 纯手动实现(不建议)

功能背景

开发一个配置文件解析功能,需要兼容老版本的配置文件。

功能需要

  1. 前端:两个配置文件分别上传
  2. 后端:配置文件解析、分版本匹配、配置文件映射到实体类

前端开发

组件选用

选用element-plus的el-pload组件进行上传控制,核心组件代码为:

<el-uploadv-model:file-list="fileListModel":on-remove="handleRemove":before-remove="beforeRemove":limit="1":on-exceed="handleExceed":auto-upload="false"class="upload-location"accept=".yml,.yaml"
/>

核心参数说明:

属性作用
v-model:file-list绑定上传文件列表
:on-remove文件移除时触发的回调函数,移除文件是组件自发的,此处绑的是你想在触发该逻辑时做的操作
:before-remove文件移除前触发的函数,此处我绑定了一个确认弹框
:limit="1"限制最多上传 1 个文件
:on-exceed超出文件数量限制时的回调,我绑了个提示框
:auto-upload="false"不自动上传,手动触发上传,因为我要一次提交两个不同的配置文件
class="upload-location"设置样式类
accept=".yml,.yaml"限制可上传的文件类型,此处我设置的是yaml类型

组件嵌套和参数绑定

此处我选择以el-upload组件为核心,将涉及的提示操作等封装成一个自定义组件。在父组件中使用两次该子组件,
并通过defineModel 实现父组件和子组件间值的双向绑定。
el-upload需要绑定的类型为UploadFile数组,即

子组件:const fileListModel = defineModel<UploadFile[]>("fileList"); 
父组件:const fileList = ref<any[]>([]);  
绑定:v-model:file-list="fileList"

UploadFile参数为:

export interface UploadFile {uid: number | stringname: stringstatus?: 'ready' | 'uploading' | 'success' | 'fail'size?: numberpercentage?: numberraw?: Fileresponse?: anyurl?: stringtype?: string
}

其中raw为我们需要向后端传递的数据部分。

上传逻辑示例
const formData = new FormData()
//实际需要进行判空,此处只写核心部分
formData.append('file1', fileList1.value[0].raw) 
formData.append('file2', fileList2.value[0].raw) 
axios.post('/api/upload', formData, {headers: {'Content-Type': 'multipart/form-data'}
}).then(res => {console.log('上传成功:', res.data)
}).catch(err => {console.error('上传失败:', err)
})

后端开发

接收逻辑
@PostMapping("/upload")
public R uploadTask(@RequestParam(value = "file1", required = false) MultipartFile file1,@RequestParam(value = "file2", required = true) MultipartFile file2,@RequestParam(value = "groupId", required = true) Integer groupId) {if (file2 == null || groupId == null) {return R.fail("IMPORT_NOT_EXIST_PARAMS");}if (file1 != null) {return paramService.importTaskParams(file1, file2, groupId);} else {return paramService.importTaskParams(file2, groupId);}
}
解析逻辑

单纯的解析yaml文件并映射比较简单,无非将传入的文件内容使用YAMLMapper解析一下,但本任务有一个要求:

  1. 兼容之前的yml配置文件写法

注:
之前的配置文件使用了 Spring Boot 提供的“松散绑定(Relaxed Binding)”机制,这是直接上传yaml文件并解析无法直接办到的,由此延伸出几点需求:

  1. 支持单字符串向数组类型的映射(即支持a,b,c,d写法);
  2. 支持单字符串向枚举类型忽略大小写的映射;
  3. 中划线和驼峰类型双兼容(即既能解析中划线写法的配置文件,又能解析驼峰写法的配置文件)
  4. 16进制单字符向char类型的映射
省流

手动调用 Spring Boot 提供的绑定工具,不必自己实现。

高效实现:

try ( InputStream inputStream = multipartFile.getInputStream()){// 1. 读取 YAML 文件Yaml yaml = new Yaml();//假设没有上层需要排除Map<String, Object> yamlMap = yaml.load(inputStream);// 2. 平铺嵌套结构(嵌套结构必须用)Map<String, Object> flatMap = flattenMap(yamlMap, null);// 3. 构造 PropertySource(Spring Boot Binder 需要它)PropertySource<?> propertySource = new MapPropertySource("customYaml", flatMap);StandardEnvironment env = new StandardEnvironment();env.getPropertySources().addFirst(propertySource);// 4. 使用 Binder 绑定Binder binder = Binder.get(env);return binder.bind("", Bindable.of(YourConfig.class)).orElseThrow(() -> new RuntimeException("Binding failed"));} catch (Exception e) {e.printStackTrace();}

辅助方法:

    // 将嵌套结构扁平化成 "a.b.c" 格式,只有这样才能处理子级别的中划线映射驼峰private Map<String, Object> flattenMap(Map<String, Object> source, String parentKey) {Map<String, Object> result = new HashMap<>();for (Map.Entry<String, Object> entry : source.entrySet()) {String key = (parentKey != null ? parentKey + "." : "") + entry.getKey();Object value = entry.getValue();if (value instanceof Map) {result.putAll(flattenMap((Map<String, Object>) value, key));} else {result.put(key, value);}}return result;}
纯手动实现(不建议)
  • 单字符串向数组类型的映射
    • 重写set方法,使得数组类型属性可接受非数组类型参数。
  • 单字符串向枚举类型忽略大小写的映射
    • 手写模糊获取方法,并在set方法中调用 。
  • 中划线和驼峰类型双兼容
    • 重构映射逻辑,在映射之前加一段处理,即将原本的中划线名字修改为驼峰,这样就可和实体类中的属性实现匹配,具体实现为:
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.deser.BeanDeserializer;
import com.fasterxml.jackson.databind.deser.BeanDeserializerModifier;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.fasterxml.jackson.databind.node.ObjectNode;import java.io.IOException;
import java.util.HashMap;
import java.util.Map;public class FlexibleObjectMapper extends ObjectMapper {public FlexibleObjectMapper() {// 注册自定义模块用于字段名转换SimpleModule module = new SimpleModule();module.setDeserializerModifier(new BeanDeserializerModifier() {@Overridepublic JsonDeserializer<?> modifyDeserializer(DeserializationConfig config,BeanDescription beanDesc,JsonDeserializer<?> deserializer) {if (deserializer instanceof BeanDeserializer) {return new FlexibleCaseDeserializer((BeanDeserializer) deserializer);}return deserializer;}});this.registerModule(module);}private static class FlexibleCaseDeserializer extends BeanDeserializer {public FlexibleCaseDeserializer(BeanDeserializer base) {super(base);}@Overridepublic Object deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {//在原逻辑之前加一段处理,即将原本的中划线名字修改为驼峰,这样就可和实体类中的属性实现匹配JsonNode tree = p.getCodec().readTree(p);if (tree.isObject()) {ObjectNode objNode = (ObjectNode) tree;Map<String, JsonNode> newFields = new HashMap<>();objNode.fields().forEachRemaining(entry -> {String fieldName = entry.getKey();String camelCase = toCamelCase(fieldName);newFields.put(camelCase, entry.getValue());});objNode.removeAll();newFields.forEach(objNode::set);JsonParser newParser = objNode.traverse(p.getCodec());newParser.nextToken(); // advance to START_OBJECT//回到原逻辑return super.deserialize(newParser, ctxt);}return super.deserialize(p, ctxt);}private String toCamelCase(String s) {if (!s.contains("_")) return s;StringBuilder sb = new StringBuilder();boolean upper = false;for (char c : s.toCharArray()) {if (c == '_') {upper = true;} else {sb.append(upper ? Character.toUpperCase(c) : c);upper = false;}}return sb.toString();}}
}
  • 16进制单字符向char类型的映射
    • 自定义反序列化逻辑,指定格式字符串向字符的处理。
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;import java.io.IOException;public class CharDeserializer extends JsonDeserializer<Character> {@Overridepublic Character deserialize(JsonParser jsonParser, DeserializationContext context)throws IOException {String text = jsonParser.getText().trim();// 支持 \u 或 u 开头的十六进制 Unicode 字符if (text.startsWith("\\u") || text.startsWith("u")) {text = text.substring(text.indexOf('u') + 1);try {int code = Integer.parseInt(text, 16);return (char) code;} catch (NumberFormatException e) {throw new IOException("Invalid hexadecimal character: " + text);}}throw new IOException("Empty character string");}
}

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

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

相关文章

ElasticSearch中常用的数据类型

一、映射 Elasticsearch中通过映射来指定字段的数据类型&#xff0c;映射方式有2种&#xff0c;静态映射和动态映射。 1.动态映射 使用动态映射时&#xff0c;无须指定字段的数据类型&#xff0c;Elasticshearch会自动根据字段内容来判断映射到哪个数据类型。 比如&#xff…

【神经网络结构的组成】深入理解 转置卷积与转置卷积核

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;《深度学习理论直觉三十讲》_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 …

CSS高度坍塌?如何解决?

一、什么是高度坍塌&#xff1f; 高度坍塌&#xff08;Collapsing Margins&#xff09;是指当父元素没有设置边框&#xff08;border&#xff09;、内边距&#xff08;padding&#xff09;、内容&#xff08;content&#xff09;或清除浮动时&#xff0c;其子元素的 margin 会…

Web前端开发——格式化文本与段落(上)

一、学习目标 网页内容的排版包括文本格式化、段落格式化和整个页面的格式化&#xff0c;这是设计个网页的基础。文本格式化标记分为字体标记、文字修饰标记。字体标记和文字修饰标记包括对于字体样式的一些特殊修改。段落格式化标记分为段落标记、换行记、水平分隔线标记等。…

关于PHP开源CMS系统ModStart的详细介绍及使用指南

关于PHP开源CMS系统ModStart的详细介绍及使用指南&#xff1a; &#x1f50d; ModStart是什么&#xff1f; 基于Laravel框架开发的模块化CMS系统采用Apache 2.0 开源协议&#xff0c;完全免费可商用特别适合需要快速搭建企业级网站/管理系统的开发者 &#x1f680; 核心优势…

TCP标志位抓包

说明 TCP协议的Header信息&#xff0c;URG、ACK、PSH、RST、SYN、FIN这6个字段在14字节的位置&#xff0c;对应的是tcp[13]&#xff0c;因为字节数是从[0]开始数的&#xff0c;14字节对应的就是tcp[13]&#xff0c;因此在抓这几个标志位的数据包时就要明确范围在tcp[13] 示例1…

RK3588S开发板将SPI1接口改成GPIO

参考官方教程&#xff1a;ROC-RK3588S-PC 一.基本知识&#xff1a; 1.GPIO引脚计算&#xff1a; ROC-RK3588S-PC 有 5 组 GPIO bank&#xff1a;GPIO0~GPIO4&#xff0c;每组又以 A0~A7, B0~B7, C0~C7, D0~D7 作为编号区分&#xff0c;常用以下公式计算引脚&#xff1a;GPIO…

Java 设计模式:适配器模式详解

Java 设计模式&#xff1a;适配器模式详解 适配器模式&#xff08;Adapter Pattern&#xff09;是一种结构型设计模式&#xff0c;它通过将一个类的接口转换为客户端期望的另一个接口&#xff0c;使原本不兼容的类能够协同工作。适配器模式就像现实生活中的电源适配器&#xf…

python manimgl数学动画演示_微积分_线性代数原理_ubuntu安装问题[已解决]

1.背景 最近调研python opencv, cuda加速矩阵/向量运算, 对于矩阵的线性变换, 秩, 转秩, 行列式变化等概概念模糊不清. 大概课本依旧是天书, 于是上B站搜索线性代数, 看到 3Blue1Brown 线性变换本质 视频, 点击观看. 惊为天人 --> 豁然开朗 --> 突然顿悟 --> 开心不已…

Git 学习笔记

这篇笔记记录了我在git学习中常常用到的指令&#xff0c;方便在未来进行查阅。此篇文章也会根据笔者的学习进度持续更新。 网站分享 Git 常用命令大全 Learn Git Branching 基础 $ git init //在当前位置配置一个git版本库 $ git add <file> //将文件添加至…

Dynamics365 ExportPdfTemplateExportWordTemplate两个Action调用的body构造

这两天在用ExportPdfTemplate做pdf导出功能时&#xff0c;遇到了如下问题InnerException : Microsoft.OData.ODataException: An unexpected StartArray node was found when reading from the JSON reader. A PrimitiveValue node was expected. 我的场景是使用power automate…

经典算法 判断一个图是不是树

判断一个图是不是树 问题描述 给一个以0 0结尾的整数对列表&#xff0c;除0 0外的每两个整数表示一条连接了这两个节点的边。假设节点编号不超过100000大于0。你只要判断由这些节点和边构成的图是不是树。是输出YES&#xff0c;不是输出NO。 输入样例1 6 8 5 3 5 2 6 4 5…

【嵌入式八股2】C++:STL容器与算法

1. STL常见容器及其内部实现的数据结构 序号 名称 描述 存储结构 常用方法和操作 1vector动态分配的数组顺序数组&#xff08;array&#xff09;v.push_back(), v.pop_back(), v.insert(), v.erase(), v.capacity(), v.size(), v.at(idx), v.front(), v.back()2list双向链表离…

vmcore分析锁问题实例(x86-64)

问题描述&#xff1a;系统出现panic&#xff0c;dmesg有如下打印&#xff1a; [122061.197311] task:irq/181-ice-enp state:D stack:0 pid:3134 ppid:2 flags:0x00004000 [122061.197315] Call Trace: [122061.197317] <TASK> [122061.197318] __schedule0…

在Apple Silicon上部署Spark-TTS:四大核心库的技术魔法解析!!!

在Apple Silicon上部署Spark-TTS&#xff1a;四大核心库的技术魔法解析 &#x1f680; &#xff08;M2芯片实测&#xff5c;Python 3.12.9PyTorch 2.6.0全流程解析&#xff09; 一、核心库功能全景图 &#x1f50d; 在Spark-TTS的部署过程中&#xff0c;pip install numpy li…

leetcode03 -- 武汉旅游查询系统

武汉旅游查询系统 1 界面展示 1.首页地图界面 2.查找功能 在查找框内输入查找的景点名称 查找到的景点在地图上进行定位,右侧展示景点的详细信息。 3.添加景点功能 在地图上点击某个位置,系统弹出一个输入框供用户填写景点的名称和描述。 在弹出的输入框中输入景点名…

玩机进阶教程----MTK芯片设备刷机导致的死砖修复实例解析 连电脑毫无反应 非硬件问题

在高通芯片机型中,我们可以通过短接主板测试点来激活高通芯片特有的9008底层端口来刷写救砖固件。但通常MTK芯片类的设备联机电脑即可触发深刷模式。但有些例外的情况会导致链接电脑毫无反应。遇到类似故障的友友可以参阅此博文尝试解决。 通过博文了解 1💝💝💝-----实…

09-设计模式企业场景 面试题-mk

文章目录 1.工厂(方法)模式1.1.简单工厂模式(不是设计模式,是编程习惯)1.2.工厂方法模式(企业开发中最常见)1.3.抽象工厂模式2.策略模式2.1.登录案例(工厂模式+策略模式)3.责任链设计模式4.单点登录怎么是实现的?5.权限认证是如何实现的6.上传数据的安全性你们怎么控…

BUUCTF-Web(1-20)

目录 一.SQL注入 (1)[极客大挑战 2019]EasySQL 万能密码 (7)[SUCTF 2019]EasySQL 堆叠注入 解一&#xff1a; 解二&#xff1a; (10)[强网杯 2019]随便注 堆叠注入 解一&#xff1a; 解二&#xff1a; 解三&#xff1a; (8)[极客大挑战 2019]LoveSQL 联…

软件包安装管理Gitlab

官方提供了非常详尽的系统及自动化脚本安装教程 Gitlab官网下载地址&#xff1a;https://gitlab.cn/install/ 1、安装配置 今天我们说一下包安装管理&#xff0c;这样方便我们自己更精确的制定符合我们自己需要的Gitlab仓库 配置&#xff1a;ubuntu2004(focal) 4C8G 下载程…