基于 JAXB 注解方式解析 XML 文件与批量导入实现

news2024/12/1 13:46:05

基于 JAXB 注解方式解析 XML 文件与批量导入实现

本文以实际的项目需求为例,分享如何基于 JAXB 实现 XML 文件的解析,并将解析后的数据导入数据库

本文主要使用的 javax.xml 是 Java 自带的包,通常用来处理 XML 和 Java 对象之间的转换。具体来说,这些类属于 JAXB(Java Architecture for XML Binding),用于将 Java 对象序列化为 XML,或者将 XML 反序列化为 Java 对象。

需要注意:在 Java 9 及以后javax.xml.bind 被迁移到了模块化系统,并且从 JDK 11 开始 移除了 默认支持。如果你使用的 JDK 是 11 或以上版本,需要通过添加外部依赖来使用这些类,比如引入 javax.xml.bind 的实现库,例如:

Maven 依赖:

<dependency>
    <groupId>javax.xml.bind</groupId>
    <artifactId>jaxb-api</artifactId>
    <version>2.3.1</version>
</dependency>

或者将 com.sun.xml.bind 实现的库加入项目中:

<dependency>
    <groupId>com.sun.xml.bind</groupId>
    <artifactId>jaxb-impl</artifactId>
    <version>2.3.1</version>
</dependency>

如果使用的是 JDK 8,则这些类是默认包含在标准库中的,无需额外配置

一、XML 文件格式及对应的 JavaBean 设计

假设有以下 XML 文件内容:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<Record>
    <RecordID>2921725428</RecordID>
    <RecordLastUpdateDate>20240204173652</RecordLastUpdateDate>
    <RecordTitle>String类型</RecordTitle>
    <Publication>
        <PublicationID>2026366</PublicationID>
        <Title>出版社题名</Title>
    </Publication>
    <AlphaPubDate>1976</AlphaPubDate>
    <NumericPubDate>19760101</NumericPubDate>
    <Contributor>
        <ContribRole>Author</ContribRole>
        <LastName>József</LastName>
        <FirstName>Terjéki</FirstName>
        <OriginalForm>József, Terjéki</OriginalForm>
    </Contributor>
    <Contributor>
        <ContribRole>Advisor</ContribRole>
        <LastName>Lajos</LastName>
        <FirstName>Pintér</FirstName>
        <OriginalForm>Lajos, Pintér</OriginalForm>
    </Contributor>
    <Contributor>
        <ContribRole>Advisor</ContribRole>
        <LastName>László</LastName>
        <FirstName>Hatvani</FirstName>
        <OriginalForm>László, Hatvani</OriginalForm>
    </Contributor>
    <Products>
        <Product>
            <ProductID>1006523</ProductID>
            <HasFullText>true</HasFullText>
        </Product>
        <Product>
            <ProductID>1007587</ProductID>
            <HasFullText>false</HasFullText>
        </Product>
        <Product>
            <ProductID>1007945</ProductID>
            <HasFullText>true</HasFullText>
        </Product>
    </Products>
    <Terms>
        <ClassTerm>
            <ClassTermType>DissSubject</ClassTermType>
            <ClassCode>0642</ClassCode>
            <ClassExpansion>Theoretical Mathematics</ClassExpansion>
        </ClassTerm>
        <ClassTerm>
            <ClassTermType>DissSubject</ClassTermType>
            <ClassCode>0405</ClassCode>
            <ClassExpansion>Mathematics</ClassExpansion>
        </ClassTerm>
        <GenSubjTerm>
            <GenSubjValue>Theoretical mathematics</GenSubjValue>
        </GenSubjTerm>
        <GenSubjTerm>
            <GenSubjValue>Applied mathematics</GenSubjValue>
        </GenSubjTerm>
        <FlexTerm>
            <FlexTermName>DissPaperKwd</FlexTermName>
            <FlexTermValue>Differential equation</FlexTermValue>
        </FlexTerm>
        <FlexTerm>
            <FlexTermName>DissPaperKwd</FlexTermName>
            <FlexTermValue>Non-equilibrium function </FlexTermValue>
        </FlexTerm>
    </Terms>
</Record>

1. Record

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.cxstar.test.LocalDateAdapter;
import com.cxstar.test.LocalDateTimeAdapter;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import javax.xml.bind.annotation.*;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;

@Data
@XmlRootElement(name = "Record") // 定义 XML 根元素的名称为 <Record>
@XmlAccessorType(XmlAccessType.FIELD) // 指定通过字段映射到 XML
public class Record {

    /**
     * 映射为 XML 节点 <RecordID>
     * 示例:
     * <RecordID>2921725428</RecordID>
     */
    @XmlElement(name = "RecordID")
    private Long recordId;

    /**
     * 映射为 XML 节点 <RecordLastUpdateDate>
     * 使用 LocalDateTimeAdapter 自定义适配器处理 LocalDateTime 类型
     * 示例:
     * <RecordLastUpdateDate>20240204173652</RecordLastUpdateDate>
     */
    @XmlElement(name = "RecordLastUpdateDate")
    @XmlJavaTypeAdapter(LocalDateTimeAdapter.class)
    private LocalDateTime recordLastUpdateDate;

    /**
     * 映射为 XML 节点 <RecordTitle>
     * 示例:
     * <RecordTitle>String类型</RecordTitle>
     */
    @XmlElement(name = "RecordTitle")
    private String recordTitle;

    /**
     * 映射为 XML 节点 <AlphaPubDate>
     * 示例:
     * <AlphaPubDate>1976</AlphaPubDate>
     */
    @XmlElement(name = "AlphaPubDate")
    private Integer alphaPubDate;

    /**
     * 映射为 XML 节点 <NumericPubDate>
     * 使用 LocalDateAdapter 自定义适配器处理 LocalDate 类型
     * 示例:
     * <NumericPubDate>19760101</NumericPubDate>
     */
    @XmlElement(name = "NumericPubDate")
    @XmlJavaTypeAdapter(LocalDateAdapter.class)
    private LocalDate numericPubDate;

    /**
     * 映射为 XML 节点 <Publication>
     * 该字段对应 RecordPublicationBean 类型
     * 示例:
     * <Publication>
     *     <PublicationID>2026366</PublicationID>
     *     <Title>出版社题名</Title>
     * </Publication>
     */
    @XmlElement(name = "Publication")
    private RecordPublicationBean publications;

    /**
     * 映射为多个 XML 节点 <Contributor>
     * 示例:
     * <Contributor>
     *     <ContribRole>Author</ContribRole>
     *     <LastName>József</LastName>
     *     <FirstName>Terjéki</FirstName>
     *     <OriginalForm>József, Terjéki</OriginalForm>
     * </Contributor>
     * <Contributor>
     *     <ContribRole>Advisor</ContribRole>
     *     <LastName>Lajos</LastName>
     *     <FirstName>Pintér</FirstName>
     *     <OriginalForm>Lajos, Pintér</OriginalForm>
     * </Contributor>
     */
    @XmlElement(name = "Contributor")
    private List<RecordContributorBean> contributors;

    /**
     * 映射为 XML 节点 <Products>,并包含多个 <Product> 子节点
     * 示例:
     * <Products>
     *     <Product>
     *         <ProductID>1006523</ProductID>
     *         <HasFullText>true</HasFullText>
     *     </Product>
     *     <Product>
     *         <ProductID>1007587</ProductID>
     *         <HasFullText>false</HasFullText>
     *     </Product>
     * </Products>
     */
    @XmlElementWrapper(name = "Products")
    @XmlElement(name = "Product") // 必须指定子节点的名称为 <Product>
    private List<RecordProductBean> products;

    /**
     * 映射为 XML 节点 <Terms>,包含多种类型的子节点
     * 子节点类型: <ClassTerm>, <GenSubjTerm>, <FlexTerm>
     * 示例:
     * <Terms>
     *     <ClassTerm>
     *         <ClassTermType>DissSubject</ClassTermType>
     *         <ClassCode>0642</ClassCode>
     *         <ClassExpansion>Theoretical Mathematics</ClassExpansion>
     *     </ClassTerm>
     *     <GenSubjTerm>
     *         <GenSubjValue>Theoretical mathematics</GenSubjValue>
     *     </GenSubjTerm>
     *     <FlexTerm>
     *         <FlexTermName>DissPaperKwd</FlexTermName>
     *         <FlexTermValue>Differential equation</FlexTermValue>
     *     </FlexTerm>
     * </Terms>
     */
    @TableField(exist = false) // MyBatis-Plus 标注,该字段不对应数据库表字段
    @XmlElementWrapper(name = "Terms")
    @XmlElements({
        @XmlElement(name = "ClassTerm", type = RecordTermsBean.class), // 映射为 <ClassTerm>
        @XmlElement(name = "GenSubjTerm", type = RecordTermsBean.class), // 映射为 <GenSubjTerm>
        @XmlElement(name = "FlexTerm", type = RecordTermsBean.class) // 映射为 <FlexTerm>
    })
    private List<RecordTermsBean> terms;
}

1.1 类级注解

  1. @XmlRootElement(name = “Record”)
    定义了当前类在生成 XML 文档时的根元素名称为 Record,即生成的 XML 中的最外层节点将被命名为 <Record>
  2. @XmlAccessorType(XmlAccessType.FIELD)
    指定了将类中的字段(field)直接映射为 XML 的节点或属性,而不是使用类的方法(如 getter 和 setter)

1.2 字段级注解

  1. @XmlElement(name = “RecordID”)
    将字段 recordId 映射为 XML 节点 <RecordID>

  2. @XmlJavaTypeAdapter(LocalDateTimeAdapter.class)
    指定在处理 recordLastUpdateDate 字段时使用自定义适配器 LocalDateTimeAdapter,用于在序列化和反序列化 XML 时格式化 LocalDateTime 类型

    示例适配器 LocalDateTimeAdapter
    package com.test.util;
    
    import javax.xml.bind.annotation.adapters.XmlAdapter;
    import java.time.LocalDateTime;
    import java.time.format.DateTimeFormatter;
    
    public class LocalDateTimeAdapter extends XmlAdapter<String, LocalDateTime> {
        private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
    
        @Override
        public LocalDateTime unmarshal(String v) throws Exception {
            return (v == null || v.isEmpty()) ? null : LocalDateTime.parse(v, formatter);
        }
    
        @Override
        public String marshal(LocalDateTime v) throws Exception {
            return (v == null) ? null : v.format(formatter);
        }
    }
    
  3. @XmlElementWrapper
    用于将一个集合字段包裹在外层节点内,其功能是为集合字段生成一个外层的包装节点
    例如,当 products 是一个集合时:

    @XmlElementWrapper(name = "Products")
    @XmlElement(name = "Product")
    private List<RecordProductBean> products;
    
    • @XmlElementWrapper(name = “Products”):生成一个 <Products> 包装节点作为集合的外层。
    • @XmlElement(name = “Product”):指定集合中的每个元素用 <Product> 节点表示。
    示例 XML
    <Products>
        <Product>
            <ProductID>1006523</ProductID>
            <HasFullText>true</HasFullText>
        </Product>
        <Product>
            <ProductID>1007587</ProductID>
            <HasFullText>false</HasFullText>
        </Product>
    </Products>
    
    适用场景

    在 XML 表现中,集合字段需要一个外层节点包裹时,使用 @XmlElementWrapper 比直接使用 @XmlElement 更符合语义,且更易于生成符合要求的 XML

  4. @XmlElements
    当集合中的元素有多种类型时,@XmlElements 可以指定每种类型的对应节点名称。例如:

    @XmlElementWrapper(name = "Terms")
    @XmlElements({
        @XmlElement(name = "ClassTerm", type = RecordTermsBean.class),
        @XmlElement(name = "GenSubjTerm", type = RecordTermsBean.class),
        @XmlElement(name = "FlexTerm", type = RecordTermsBean.class)
    })
    private List<RecordTermsBean> terms;
    
    • @XmlElement(name = “ClassTerm”, type = RecordTermsBean.class):为 RecordTermsBean 类型的元素生成 <ClassTerm> 节点
    • @XmlElementWrapper(name = “Terms”):将集合包装在 <Terms> 节点内
    示例 XML
    <Terms>
        <ClassTerm>
            <ClassTermType>DissSubject</ClassTermType>
            <ClassCode>0642</ClassCode>
            <ClassExpansion>Theoretical Mathematics</ClassExpansion>
        </ClassTerm>
        <GenSubjTerm>
            <GenSubjValue>Theoretical mathematics</GenSubjValue>
        </GenSubjTerm>
    </Terms>
    

2. 子实体类设计

  • RecordPublicationBean:表示出版物信息
  • RecordContributorBean:表示贡献者信息
  • RecordProductBean:表示产品信息
  • RecordTermsBean:表示记录术语信息

示例:

package com.test.entity;

import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;

@Data
@XmlRootElement(name = "Publication")
@XmlAccessorType(XmlAccessType.FIELD)
public class RecordPublicatio implements com.cxstar.common.entity.Bean {

    private static final long serialVersionUID = 1L;

    @TableId(value = "id", type = IdType.INPUT)
    private Long id;

    /**
     * 关联的记录ID
     */
    private Long recordId;

    /**
     * 出版物ID
     */
    @XmlElement(name = "PublicationID")
    private Long publicationId;

    /**
     * 出版物标题
     */
    @XmlElement(name = "Title")
    private String title;

}

二、基于 JAXB 的 XML 解析工具

为简化 XML 字符串与 Java 对象的转换,创建了一个通用的 XML 工具类 XmlUtil

package com.test.util;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import java.io.StringReader;

/**
 * @author zhouquan
 * @date 2024年11月27日 14:07
 */
public class XmlUtil {

    /**
     * 将 XML 字符串转换为指定的 POJO 对象
     *
     * @param clazz  需要转换的类
     * @param xmlStr XML 数据
     * @return 转换后的对象
     * @throws JAXBException 如果 XML 解析失败
     */
    public static <T> T xmlStrToObject(Class<T> clazz, String xmlStr) throws JAXBException {
        // 校验输入参数
        if (clazz == null || xmlStr == null || xmlStr.trim().isEmpty()) {
            throw new IllegalArgumentException("输入的类或 XML 字符串不能为空");
        }

        // 创建 JAXB 上下文和反序列化器
        JAXBContext context = JAXBContext.newInstance(clazz);
        Unmarshaller unmarshaller = context.createUnmarshaller();

        // 使用 try-with-resources 自动关闭资源
        try (StringReader reader = new StringReader(xmlStr)) {
            T result = (T) unmarshaller.unmarshal(reader);
            return result;
        }
    }

}

三、批量导入 XML 文件的实现

以下为批量解析并导入 XML 文件的核心方法:

@Override
public String importBatchXML(MultipartFile[] files) {
    if (files == null || files.length == 0) {
        log.warn("上传的文件列表为空");
        return "上传文件为空,请重新上传";
    }

    int successCount = 0;
    int totalFiles = files.length;

    for (MultipartFile file : files) {
        String fileName = file.getOriginalFilename();

        // 校验文件是否为 XML 格式
        if (fileName == null || !fileName.toLowerCase().endsWith(".xml")) {
            log.warn("文件 {} 不是有效的 XML 文件,已跳过", fileName);
            continue;
        }

        try (BufferedReader br = new BufferedReader(new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8))) {
            // 读取 XML 文件内容
            StringBuilder buffer = new StringBuilder();
            String line;
            while ((line = br.readLine()) != null) {
                buffer.append(line);
            }

            // XML 转换为 Java 对象
            Record record = XmlUtil.xmlStrToObject(Record.class, buffer.toString());

            // 保存到数据库 
            recordService.save(recordBean);

            successCount++;
        } catch (Exception e) {
            log.error("解析文件 {} 失败", fileName, e);
        }
    }

    return String.format("总文件数:%d,成功导入数:%d", totalFiles, successCount);
}

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

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

相关文章

Linux内核4.14版本——ccf时钟子系统(3)——ccf一些核心结构体

目录 1. struct clk_hw 2. struct clk_ops 3. struct clk_core 4. struct clk_notifier 5. struct clk 6. struct clk_gate 7. struct clk_divider 8. struct clk_mux 9. struct clk_fixed_factor 10. struct clk_fractional_divider 11. struct clk_multiplier 12…

点云处理中obb算法原理和法向量求解方法

主要数学原理PCA PCA&#xff08;Principal Component Analysis&#xff0c;主成分分析&#xff09;是数据分析中的一种重要技术&#xff0c;通过它可以将高维数据投影到低维空间&#xff0c;找到数据的主要结构。在点云分析中&#xff0c;PCA 可以帮助我们提取点云数据中的主…

shell编程7,bash解释器的 for循环+while循环

声明&#xff01; 学习视频来自B站up主 泷羽sec 有兴趣的师傅可以关注一下&#xff0c;如涉及侵权马上删除文章&#xff0c;笔记只是方便各位师傅的学习和探讨&#xff0c;文章所提到的网站以及内容&#xff0c;只做学习交流&#xff0c;其他均与本人以及泷羽sec团队无关&#…

【人工智能】Python常用库-TensorFlow常用方法教程

TensorFlow 是一个广泛应用的开源深度学习框架&#xff0c;支持多种机器学习任务&#xff0c;如深度学习、神经网络、强化学习等。以下是 TensorFlow 的详细教程&#xff0c;涵盖基础使用方法和示例代码。 1. 安装与导入 安装 TensorFlow&#xff1a; pip install tensorflow…

wxFormBuilder:可视化设计、学习wxWidgets自带UI控件的好工具

wxFormBuilder很快就能拼出一个界面&#xff0c;而且可以直接出对应的代码&#xff0c;拷贝到项目里小改一下就能用。

Vim操作

1. Vim的模式 2.正常模式->编辑模式 在上⽅插⼊⼀⾏&#xff1a; O在下⽅插⼊⼀⾏&#xff1a; o (open)在当前光标前插⼊&#xff1a; i在⾏⾸插⼊&#xff1a; I在当前光标后插⼊&#xff1a; a在⾏尾插⼊&#xff1a; A 3.常见命令行 1、拷贝当前行 yy ,拷贝当前行向下…

阿里云服务器(centos7.6)部署前后端分离项目(MAC环境)

Jdk17安装部署 下载地址&#xff1a;https://www.oracle.com/java/technologies/downloads/ 选择自己需要的jdk版本进行下载。 通过mac终端scp命令上传下载好的jdk17到服务器的/usr/local目录下 scp -r Downloads/jdk-17.0.13_linux-x64_bin.tar.gz 用户名服务器ip地址:/us…

「Mac畅玩鸿蒙与硬件33」UI互动应用篇10 - 数字猜谜游戏

本篇将带你实现一个简单的数字猜谜游戏。用户输入一个数字&#xff0c;应用会判断是否接近目标数字&#xff0c;并提供提示“高一点”或“低一点”&#xff0c;直到用户猜中目标数字。这个小游戏结合状态管理和用户交互&#xff0c;是一个入门级的互动应用示例。 关键词 UI互…

Python系列 - MQTT协议

Python系列 - MQTT协议 资源连接 MQTT的介绍和应用场景的示例说明 一、什么是MQTT 百度关于MQTT的介绍如下&#xff1a; MQTT(消息队列遥测传输)是ISO 标准(ISO/IEC PRF 20922)下基于发布订阅范式的消息协议。它工作在 TCP/IP协议之上&#xff0c;是为硬件性能低下的远程设…

ipad项目 蓝湖宽度

ipad项目 横屏状态时 蓝湖宽度设置930px media screen and (orientation: portrait) {/* 竖屏时的样式 */ } media screen and (orientation: landscape) {/* 默认是 横屏时的样式 */ }

理解Java集合的基本用法—Collection:List、Set 和 Queue,Map

本博文部分参考 博客 &#xff0c;强烈推荐这篇博客&#xff0c;写得超级全面&#xff01;&#xff01;&#xff01; 图片来源 Java 集合框架 主要包括两种类型的容器&#xff0c;一种是集合&#xff08;Collection&#xff09;&#xff0c;存储一个元素集合&#xff08;单列…

搭建深度学习框架+nn.Module

一、搭建项目框架&#xff08;YOLO框架的简约版本&#xff09; 最终成品的项目框架如下图&#xff0c;最终实现的效果&#xff0c;通过自己配置的框架函数&#xff0c;对模型进行定义与参数调配。同时在参数配置的过程中可选择是否进行模型初始化参数的使用。适用于YOLO框架的…

FFmpeg 简介与编译

1. ffmpeg 简介&#xff1a; FFmpeg是一套可以用来记录、转换数字音频、视频&#xff0c;并能将其转化为流的开源计算机程序。采用LGPL或GPL许可证。它提供了录制、转换以及流化音视频的完整解决方案。它包含了非常先进的音频/视频编解码库libavcodec&#xff0c;为了保证高可移…

打latex公式可以练到像手写一样快吗?

这里分享两个Python Latex工具latexify和handcalcs。 latexify生成LaTeX 数学公式 import math import latexify @latexify.with_latex #调用latexify的装饰器 def solve(a, b, c):return (-b + math.sqrt(b**2 - 4*a*c)) / (2*a)solve 更多例子.......

【Linux】磁盘 | 文件系统 | inode

&#x1fa90;&#x1fa90;&#x1fa90;欢迎来到程序员餐厅&#x1f4ab;&#x1f4ab;&#x1f4ab; 主厨&#xff1a;邪王真眼 主厨的主页&#xff1a;Chef‘s blog 所属专栏&#xff1a;青果大战linux 总有光环在陨落&#xff0c;总有新星在闪烁 模电好难啊&#xff…

AntFlow 0.20.0版发布,增加多数据源多租户支持,进一步助力企业信息化,SAAS化

传统老牌工作流引擎比如activiti,flowable或者camunda等虽然功能强大&#xff0c;也被企业广泛采用&#xff0c;然后也存着在诸如学习曲线陡峭&#xff0c;上手难度大&#xff0c;流程设计操作需要专业人员&#xff0c;普通人无从下手等问题。。。引入工作流引擎往往需要企业储…

Scrapy管道设置和数据保存

1.1 介绍部分&#xff1a; 文字提到常用的Web框架有Django和Flask&#xff0c;接下来将学习一个全球范围内流行的爬虫框架Scrapy。 1.2 内容部分&#xff1a; Scrapy的概念、作用和工作流程 Scrapy的入门使用 Scrapy构造并发送请求 Scrapy模拟登陆 Scrapy管道的使用 Scrapy中…

洛谷 B3626 跳跃机器人 C语言 记忆化搜索

题目&#xff1a; https://www.luogu.com.cn/problem/B3626 题目描述 地上有一排格子&#xff0c;共 n 个位置。机器猫站在第一个格子上&#xff0c;需要取第 n 个格子里的东西。 机器猫当然不愿意自己跑过去&#xff0c;所以机器猫从口袋里掏出了一个机器人&#xff01;这…

docker快速部署gitlab

文章目录 场景部署步骤默认账号密码效果 场景 新增了一台机器, 在初始化本地开发环境&#xff0c;docker快速部署gitlab 部署步骤 编写dockerfile version: 3.7services:gitlab:image: gitlab/gitlab-ce:latestcontainer_name: gitlabrestart: alwayshostname: gitlabenviron…

计算机视觉工程师紧张学习中!

在当今这个日新月异的科技时代&#xff0c;计算机视觉作为人工智能的重要分支&#xff0c;正以前所未有的速度改变着我们的生活和工作方式。为了紧跟时代步伐&#xff0c;提升自我技能&#xff0c;一群怀揣梦想与热情的计算机视觉设计开发工程师们聚集在了本次线下培训活动中。…