diff --git a/file-base64-converter/docs/README.md b/file-base64-converter/docs/README.md new file mode 100644 index 000000000..f4bd62bd8 --- /dev/null +++ b/file-base64-converter/docs/README.md @@ -0,0 +1,66 @@ +# 文件 Base64 转换工具 (File Base64 Converter) + +## 一、背景说明 + +当前 CodeWave 平台不支持 `byte` 字节类型,也不支持二进制字节流的直接处理。为解决此限制,本依赖库提供“通过 URL 下载网络文件并转换为 Base64 字符串”的能力,便于在平台中进行后续传输或存储。 + +该库对应 Maven 制品为 `file-base64-converter`(`version`:`1.0.0`),定位为通用的文件 Base64 转换工具库,支持将文件 URL 批量转换为 Base64 编码字符串,并内置网络下载与编码转换逻辑。 + +## 二、核心功能 + +1. **网络文件下载**:基于 `HttpURLConnection` 以 `GET` 方式拉取远程文件内容。 +2. **Base64 编码转换**:将下载得到的二进制内容编码为 Base64 字符串(使用 `Base64.getEncoder()`)。 +3. **批量转换**:支持传入 URL 列表,按输入顺序逐个转换并返回结果列表。 +4. **超时控制**:内置连接超时 `10` 秒、读取超时 `30` 秒,避免长时间阻塞。 +5. **异常处理**:下载失败或转换异常时记录日志并返回 `null`(批量场景返回列表,失败项为 `null`),保证流程不崩溃。 + +## 三、配置说明 + +本依赖库为无状态、无全局配置设计: + +- 不需要 `application.yml` 等全局配置;连接信息由入参 `fileUrl` / `fileUrls` 动态指定。 +- 依赖库通过 Spring 环境自动扫描注入(无需额外手动装配)。 + +## 四、接口说明 + +### 1. 批量转换 (convertUrlListToBase64) + +批量将网络文件 URL 列表转换为 Base64 字符串列表,保持输入顺序返回。 + +**入参** + +| 字段名称 | 描述 | 类型 | 说明 | +| :--- | :--- | :--- | :--- | +| fileUrls | 文件 URL 地址列表 | List<String> | 可选:允许为 `null`,此时直接返回空列表
建议每个元素为完整 `http://` 或 `https://` 地址,例如:`https://example.com/a.png` | + +**出参** + +| 字段名称 | 描述 | 类型 | 说明 | +| :--- | :--- | :--- | :--- | +| result | Base64 字符串列表 | List<String> | 与输入列表顺序一致
若某个 URL 转换失败,对应位置返回 `null` | + +### 2. 单文件转换 (convertUrlToBase64) + +输入一个文件 URL,下载并返回该文件内容的 Base64 编码字符串。 + +**入参** + +| 字段名称 | 描述 | 类型 | 说明 | +| :--- | :--- | :--- | :--- | +| fileUrl | 文件 URL 地址 | String | 必填:不能为空;若为 `null` 或空串直接返回 `null`
需为 `http://` 或 `https://` 开头的完整地址,例如:`https://example.com/a.png` | + +**出参** + +| 字段名称 | 描述 | 类型 | 说明 | +| :--- | :--- | :--- | :--- | +| result | Base64 字符串 | String | 转换成功返回 Base64 字符串(不包含 `data:` 前缀)
失败返回 `null` | + +## 五、注意事项 + +1. 请确保传入的 URL 为可访问的 `http://` 或 `https://` 地址;本库基于 `HttpURLConnection` 实现,非 HTTP(S) 协议将导致转换失败并返回 `null`。 +2. 仅当响应码为 `200`(`HttpURLConnection.HTTP_OK`)时才认为下载成功;否则会记录错误日志并返回 `null`。 +3. URL 中如包含空格、中文等特殊字符,请先按 URL 规范进行编码(例如按 UTF-8 进行 URL 编码),避免 `new URL(fileUrl)` 解析失败。 +4. 单文件转换在入参为 `null` 或空串时直接返回 `null`;批量转换在入参列表为 `null` 时返回空列表,列表内某个元素为空或下载失败时对应结果为 `null`。 +5. 默认连接超时时间为 `10` 秒,读取超时时间为 `30` 秒;超时或其他异常会被捕获,记录日志后返回 `null`。 +6. 转换过程会将文件完整读入内存后再进行 Base64 编码;大文件会带来较高内存消耗,建议用于图片、PDF 等中小型文件。 +7. 本库不涉及 FTP 场景,因此不包含被动模式、目录切换、目录不存在处理、`FTP.BINARY_FILE_TYPE` 等相关配置;文件内容按 HTTP 输入流字节读取后直接进行 Base64 编码。 \ No newline at end of file diff --git a/file-base64-converter/jar/nasl-metadata-collector-0.15.0/nasl-metadata-collector-0.15.0.jar b/file-base64-converter/jar/nasl-metadata-collector-0.15.0/nasl-metadata-collector-0.15.0.jar new file mode 100644 index 000000000..8a326ed2a Binary files /dev/null and b/file-base64-converter/jar/nasl-metadata-collector-0.15.0/nasl-metadata-collector-0.15.0.jar differ diff --git a/file-base64-converter/jar/nasl-metadata-maven-plugin-1.7.1/nasl-metadata-maven-plugin-1.7.1.jar b/file-base64-converter/jar/nasl-metadata-maven-plugin-1.7.1/nasl-metadata-maven-plugin-1.7.1.jar new file mode 100644 index 000000000..df8c39bca Binary files /dev/null and b/file-base64-converter/jar/nasl-metadata-maven-plugin-1.7.1/nasl-metadata-maven-plugin-1.7.1.jar differ diff --git a/file-base64-converter/jar/nasl-metadata-maven-plugin-1.7.1/nasl-metadata-maven-plugin-1.7.1.pom b/file-base64-converter/jar/nasl-metadata-maven-plugin-1.7.1/nasl-metadata-maven-plugin-1.7.1.pom new file mode 100644 index 000000000..996bdcdc7 --- /dev/null +++ b/file-base64-converter/jar/nasl-metadata-maven-plugin-1.7.1/nasl-metadata-maven-plugin-1.7.1.pom @@ -0,0 +1,240 @@ + + 4.0.0 + + com.netease.lowcode + nasl-metadata-maven-plugin + 1.7.1 + maven-plugin + + Nasl Metadata Maven Plugin + + UTF-8 + 3.2.5 + 9.4.54.v20240208 + 3.3.0 + 1.0.2.v20150114 + 8 + 1.7.36 + 4.9.2 + + + + + + org.apache.maven + maven-artifact + ${mavenVersion} + provided + + + org.apache.maven + maven-plugin-api + ${mavenVersion} + provided + + + org.apache.maven + maven-model + ${mavenVersion} + provided + + + org.apache.maven + maven-core + ${mavenVersion} + provided + + + org.apache.maven + maven-repository-metadata + ${mavenVersion} + provided + + + org.apache.maven + maven-settings + ${mavenVersion} + provided + + + org.apache.maven + maven-aether-provider + ${mavenVersion} + provided + + + + + org.apache.maven.doxia + doxia-sink-api + 1.12.0 + + + org.codehaus.plexus + plexus-container-default + + + + + org.apache.maven.reporting + maven-reporting-api + 3.1.1 + + + org.apache.maven.reporting + maven-reporting-impl + 3.2.0 + + + org.codehaus.plexus + plexus-container-default + + + + + + + org.codehaus.plexus + plexus-archiver + ${plexus-archiver.version} + + + org.codehaus.plexus + plexus-utils + 4.0.0 + + + org.codehaus.plexus + plexus-xml + 3.0.0 + + + org.codehaus.plexus + plexus-io + 3.4.2 + + + org.codehaus.plexus + plexus-i18n + 1.0-beta-10 + + + org.codehaus.plexus + plexus-component-api + + + + + + + org.apache.maven.shared + maven-dependency-analyzer + 1.13.2 + + + org.apache.maven.shared + maven-dependency-tree + 3.2.1 + + + org.apache.maven.shared + maven-common-artifact-filters + 3.3.2 + + + org.apache.maven.shared + maven-artifact-transfer + 0.13.1 + + + org.apache.maven.shared + maven-shared-utils + 3.4.2 + + + + + org.apache.maven.plugin-tools + maven-plugin-annotations + provided + 3.11.0 + + + javax.inject + javax.inject + 1 + + + + org.eclipse.aether + aether-api + ${resolverVersion} + provided + + + org.eclipse.aether + aether-util + ${resolverVersion} + provided + + + org.sonatype.plexus + plexus-build-api + 0.0.7 + compile + + + + + commons-io + commons-io + 2.16.1 + + + + com.fasterxml.jackson.core + jackson-databind + 2.15.3 + + + org.apache.httpcomponents + httpmime + 4.5 + + + junit + junit + 4.12 + test + + + org.mockito + mockito-core + 2.28.2 + test + + + + + + + org.apache.maven.plugins + maven-plugin-plugin + 3.5 + + nasl-metadata-maven-plugin + true + + + + org.apache.maven.plugins + maven-compiler-plugin + + 8 + 8 + + + + + \ No newline at end of file diff --git a/file-base64-converter/pom.xml b/file-base64-converter/pom.xml new file mode 100644 index 000000000..78f83e0fc --- /dev/null +++ b/file-base64-converter/pom.xml @@ -0,0 +1,78 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.2.9.RELEASE + + + + com.yourcompany.converter + file-base64-converter + 1.0.0 + 文件下载并转换为Base64工具库 + 通用的文件Base64转换工具库,支持批量将文件URL转换为Base64编码字符串,提供网络文件下载和编码转换功能 + + + 8 + 8 + UTF-8 + 4.1 + + + + + nasl-metadata-collector + com.netease.lowcode + 0.15.0 + + + + + + com.fasterxml.jackson.core + jackson-databind + + + org.slf4j + slf4j-api + provided + + + + org.springframework.boot + spring-boot-starter-web + provided + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + com.netease.lowcode + nasl-metadata-maven-plugin + 1.7.1 + + false + + + + + archive + + + + + + + \ No newline at end of file diff --git a/file-base64-converter/src/main/java/com/yourcompany/converter/file/base64/FileBase64ConverterBasicSpringEnvironmentConfiguration.java b/file-base64-converter/src/main/java/com/yourcompany/converter/file/base64/FileBase64ConverterBasicSpringEnvironmentConfiguration.java new file mode 100644 index 000000000..1f44a077a --- /dev/null +++ b/file-base64-converter/src/main/java/com/yourcompany/converter/file/base64/FileBase64ConverterBasicSpringEnvironmentConfiguration.java @@ -0,0 +1,12 @@ +package com.yourcompany.converter.file.base64; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +/** + * 加入spring环境配置(在spring.factories中指定)... + */ +@Configuration +@ComponentScan(basePackageClasses = LibraryAutoScan.class) +public class FileBase64ConverterBasicSpringEnvironmentConfiguration { +} diff --git a/file-base64-converter/src/main/java/com/yourcompany/converter/file/base64/LibraryAutoScan.java b/file-base64-converter/src/main/java/com/yourcompany/converter/file/base64/LibraryAutoScan.java new file mode 100644 index 000000000..81c3fea53 --- /dev/null +++ b/file-base64-converter/src/main/java/com/yourcompany/converter/file/base64/LibraryAutoScan.java @@ -0,0 +1,8 @@ +package com.yourcompany.converter.file.base64; + +/** + * 依赖库自动扫描类 + * @author system + */ +public class LibraryAutoScan { +} diff --git a/file-base64-converter/src/main/java/com/yourcompany/converter/file/base64/api/FileBase64ConverterService.java b/file-base64-converter/src/main/java/com/yourcompany/converter/file/base64/api/FileBase64ConverterService.java new file mode 100644 index 000000000..555fc88f2 --- /dev/null +++ b/file-base64-converter/src/main/java/com/yourcompany/converter/file/base64/api/FileBase64ConverterService.java @@ -0,0 +1,79 @@ +package com.yourcompany.converter.file.base64.api; + +import com.netease.lowcode.core.annotation.NaslLogic; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.Base64; +import java.util.List; + +/** + * 文件Base64转换工具库 + */ +@Service +public class FileBase64ConverterService { + + private static final Logger LCAP_LOGGER = LoggerFactory.getLogger("LCAP_EXTENSION_LOGGER"); + + /** + * 批量将网络文件URL转换为Base64编码字符串 + * + * @param fileUrls 文件URL地址列表 + * @return Base64编码后的字符串列表 + */ + @NaslLogic + public List convertUrlListToBase64(List fileUrls) { + if (fileUrls == null) { + return new ArrayList<>(); + } + + List result = new ArrayList<>(); + for (String url : fileUrls) { + result.add(convertUrlToBase64(url)); + } + return result; + } + + public String convertUrlToBase64(String fileUrl) { + if (fileUrl == null || fileUrl.isEmpty()) { + return null; + } + + try { + URL url = new URL(fileUrl); + HttpURLConnection connection = (HttpURLConnection) url.openConnection(); + connection.setConnectTimeout(10000); + connection.setReadTimeout(30000); + connection.setRequestMethod("GET"); + + int responseCode = connection.getResponseCode(); + if (responseCode != HttpURLConnection.HTTP_OK) { + LCAP_LOGGER.error("从URL下载文件失败: " + fileUrl + ", 响应码: " + responseCode); + return null; + } + + try (InputStream inputStream = connection.getInputStream(); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = inputStream.read(buffer)) != -1) { + outputStream.write(buffer, 0, bytesRead); + } + + byte[] fileBytes = outputStream.toByteArray(); + return Base64.getEncoder().encodeToString(fileBytes); + } + + } catch (Exception e) { + LCAP_LOGGER.error("将URL转换为Base64时发生异常: " + e.getMessage(), e); + return null; + } + } +} diff --git a/file-base64-converter/src/main/resources/META-INF/spring.factories b/file-base64-converter/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..1e88e6a81 --- /dev/null +++ b/file-base64-converter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +com.yourcompany.converter.file.base64.FileBase64ConverterBasicSpringEnvironmentConfiguration \ No newline at end of file diff --git a/ftp-file-utils/docs/README.md b/ftp-file-utils/docs/README.md new file mode 100644 index 000000000..3bd781ef3 --- /dev/null +++ b/ftp-file-utils/docs/README.md @@ -0,0 +1,85 @@ +# FTP文件操作工具库 (FTP File Utils) + +## 一、背景说明 +这是一个 **FTP 文件传输** 工具库,基于 Apache Commons Net(`commons-net`)封装 FTP 连接与传输能力,统一使用 **Base64** 作为文件内容的数据交换标准,提供简洁的 API 处理 FTP 服务器上的文件上传与下载操作,屏蔽了底层的连接、登录、目录切换、模式切换等细节。 +当前 Maven 制品信息:`ftp-file-utils`(`1.3.0`),描述为“基于Base64的FTP文件上传工具,支持从指定FTP服务器上传/下载”。 + +## 二、核心功能 +- **FTP 上传**:接收 Base64 编码的文件内容,解码后以二进制方式上传到指定 FTP 路径(`FTP.BINARY_FILE_TYPE`)。 +- **FTP 下载**:从指定 FTP 路径下载文件,并直接返回文件内容的 Base64 编码字符串。 +- **按前缀批量下载**:根据文件名前缀筛选目录下文件并批量下载,返回 `Map`(文件名 → Base64 内容)。 +- **按前缀下载最新文件**:根据文件名前缀在目录中筛选并选择时间最新的文件下载,返回 `Map`(文件名 → Base64 内容)。 +- **智能连接策略**:自动解析 `ftpUrl`(主机/端口/路径/用户信息),默认端口 `21`,自动切换工作目录并开启本地被动模式(Local Passive Mode)。 +- **编码与传输优化**:强制使用 `UTF-8` 控制编码(`setControlEncoding("UTF-8")`)避免中文路径/文件名乱码,并统一二进制传输以适配各类文件。 + +## 三、配置说明 +本依赖库为无状态工具库,无需在 `application.yml` 中进行全局配置。所有连接信息(地址、端口、用户、密码、路径)均由接口调用时通过 `ftpUrl` 入参动态指定。 + +## 四、接口说明 + +### 1. 上传文件 (uploadFileToFtp) +将 Base64 编码的文件上传到指定 FTP 服务器路径。 + +**入参** +| 字段名称 | 描述 | 类型 | 说明 | +| :--- | :--- | :--- | :--- | +| base64Content | 文件内容 | String | 文件的 Base64 编码字符串(必填);为空直接返回 `false`;非合法 Base64 会导致解码异常并返回 `false` | +| ftpUrl | FTP地址 | String | 完整 FTP 地址(必填),用于解析主机/端口/路径/登录信息;
格式:`ftp://user:password@host:port/path`
例如:`ftp://admin:123456@192.168.1.100:21/data/upload`
端口缺省时默认 `21` | +| fileName | 文件名 | String | 保存到 FTP 服务器上的文件名(必填),例如:`order_2023.xml` | + +**出参** +| 字段名称 | 描述 | 类型 | 说明 | +| :--- | :--- | :--- | :--- | +| result | 上传结果 | Boolean | `true` 表示上传成功;`false` 表示失败(包含参数为空、连接/登录失败、传输失败、异常等) | + +### 2. 下载文件 (downloadFileFromFtp) +从指定 FTP 服务器下载文件并返回 Base64 编码内容。 + +**入参** +| 字段名称 | 描述 | 类型 | 说明 | +| :--- | :--- | :--- | :--- | +| ftpUrl | FTP地址 | String | 完整 FTP 地址(必填),用于解析主机/端口/路径/登录信息;
格式:`ftp://user:password@host:port/path`
例如:`ftp://admin:123456@192.168.1.100:21/data/download`
端口缺省时默认 `21` | +| fileName | 文件名 | String | 要下载的文件名(必填),例如:`report.pdf` | + +**出参** +| 字段名称 | 描述 | 类型 | 说明 | +| :--- | :--- | :--- | :--- | +| content | 文件内容 | String | 成功返回文件的 Base64 编码字符串;失败返回 `null` | + +### 3. 按前缀批量下载 (downloadFilesByPrefix) +根据文件名前缀在目标目录内筛选并批量下载文件,返回文件名到 Base64 内容的映射。 + +**入参** +| 字段名称 | 描述 | 类型 | 说明 | +| :--- | :--- | :--- | :--- | +| ftpUrl | FTP地址 | String | 完整 FTP 地址(必填),用于解析主机/端口/路径/登录信息;
格式:`ftp://user:password@host:port/path`
例如:`ftp://admin:123456@192.168.1.100:21/data/download` | +| filePrefix | 文件名前缀 | String | 前缀(必填),用于匹配 `fileName.startsWith(filePrefix)` | + +**出参** +| 字段名称 | 描述 | 类型 | 说明 | +| :--- | :--- | :--- | :--- | +| resultMap | 批量下载结果 | Map | 成功返回 `Map`(Key 为文件名,Value 为 Base64 内容);
无匹配文件时返回空 `Map`;连接失败或发生异常返回 `null` | + +### 4. 按前缀下载最新文件 (downloadLatestFileByPrefix) +根据文件名前缀筛选目录文件,并下载时间最新的一个文件,返回文件名到 Base64 内容的映射。 + +**入参** +| 字段名称 | 描述 | 类型 | 说明 | +| :--- | :--- | :--- | :--- | +| ftpUrl | FTP地址 | String | 完整 FTP 地址(必填),用于解析主机/端口/路径/登录信息;
格式:`ftp://user:password@host:port/path`
例如:`ftp://admin:123456@192.168.1.100:21/data/download` | +| filePrefix | 文件名前缀 | String | 前缀(必填),用于匹配 `fileName.startsWith(filePrefix)` 并筛选候选文件 | + +**出参** +| 字段名称 | 描述 | 类型 | 说明 | +| :--- | :--- | :--- | :--- | +| resultMap | 最新文件下载结果 | Map | 成功返回 `Map`(通常包含 0 或 1 个条目);
未找到匹配文件时返回空 `Map`;连接失败或发生异常返回 `null` | + +## 五、注意事项 +1. **FTP地址格式**:必须使用 `ftp://` 协议头;推荐格式为 `ftp://user:password@host:port/path`。端口缺省时默认 `21`;`path` 用于切换工作目录。 +2. **认证信息处理**:仅当 `ftpUrl` 中包含 `user:password`(即存在 `userInfo` 且包含 `:`)时才会执行登录;未提供时不会主动登录,具体是否允许访问取决于 FTP 服务端策略。 +3. **编码问题**:工具库强制使用 `UTF-8` 控制编码(`setControlEncoding("UTF-8")`)以支持中文文件名与路径;若 FTP 服务器不支持 UTF-8 可能出现乱码或目录切换异常。 +4. **被动模式**:默认开启本地被动模式(`enterLocalPassiveMode()`),以适应大多数防火墙与 NAT 环境。 +5. **目录切换与目录不存在**:工具库会尝试 `changeWorkingDirectory(path)`;若切换失败,仅记录 `warn` 日志并继续在“当前目录/根目录”执行上传或下载,不会自动创建目录。 +6. **二进制传输类型**:所有传输统一使用二进制模式(`FTP.BINARY_FILE_TYPE`),避免文本模式导致的换行/编码转换问题,适用于图片、PDF、压缩包等二进制文件。 +7. **异常处理策略**:接口内部会捕获异常并记录错误日志(`LCAP_EXTENSION_LOGGER`);上传失败返回 `false`,单文件下载失败返回 `null`,批量/最新下载在连接失败或异常时返回 `null`,不会向上抛出异常导致应用崩溃。 +8. **批量与最新下载结果**:按前缀批量下载会跳过下载失败的文件(仅将成功下载的文件写入 `Map`);按前缀下载最新文件以 `FTPFile.getTimestamp()` 的时间对比筛选,若未找到匹配文件将返回空 `Map` 并记录 `warn` 日志。 \ No newline at end of file diff --git a/ftp-file-utils/jar/nasl-metadata-collector-0.15.0/nasl-metadata-collector-0.15.0.jar b/ftp-file-utils/jar/nasl-metadata-collector-0.15.0/nasl-metadata-collector-0.15.0.jar new file mode 100644 index 000000000..8a326ed2a Binary files /dev/null and b/ftp-file-utils/jar/nasl-metadata-collector-0.15.0/nasl-metadata-collector-0.15.0.jar differ diff --git a/ftp-file-utils/jar/nasl-metadata-maven-plugin-1.7.1/nasl-metadata-maven-plugin-1.7.1.jar b/ftp-file-utils/jar/nasl-metadata-maven-plugin-1.7.1/nasl-metadata-maven-plugin-1.7.1.jar new file mode 100644 index 000000000..df8c39bca Binary files /dev/null and b/ftp-file-utils/jar/nasl-metadata-maven-plugin-1.7.1/nasl-metadata-maven-plugin-1.7.1.jar differ diff --git a/ftp-file-utils/jar/nasl-metadata-maven-plugin-1.7.1/nasl-metadata-maven-plugin-1.7.1.pom b/ftp-file-utils/jar/nasl-metadata-maven-plugin-1.7.1/nasl-metadata-maven-plugin-1.7.1.pom new file mode 100644 index 000000000..996bdcdc7 --- /dev/null +++ b/ftp-file-utils/jar/nasl-metadata-maven-plugin-1.7.1/nasl-metadata-maven-plugin-1.7.1.pom @@ -0,0 +1,240 @@ + + 4.0.0 + + com.netease.lowcode + nasl-metadata-maven-plugin + 1.7.1 + maven-plugin + + Nasl Metadata Maven Plugin + + UTF-8 + 3.2.5 + 9.4.54.v20240208 + 3.3.0 + 1.0.2.v20150114 + 8 + 1.7.36 + 4.9.2 + + + + + + org.apache.maven + maven-artifact + ${mavenVersion} + provided + + + org.apache.maven + maven-plugin-api + ${mavenVersion} + provided + + + org.apache.maven + maven-model + ${mavenVersion} + provided + + + org.apache.maven + maven-core + ${mavenVersion} + provided + + + org.apache.maven + maven-repository-metadata + ${mavenVersion} + provided + + + org.apache.maven + maven-settings + ${mavenVersion} + provided + + + org.apache.maven + maven-aether-provider + ${mavenVersion} + provided + + + + + org.apache.maven.doxia + doxia-sink-api + 1.12.0 + + + org.codehaus.plexus + plexus-container-default + + + + + org.apache.maven.reporting + maven-reporting-api + 3.1.1 + + + org.apache.maven.reporting + maven-reporting-impl + 3.2.0 + + + org.codehaus.plexus + plexus-container-default + + + + + + + org.codehaus.plexus + plexus-archiver + ${plexus-archiver.version} + + + org.codehaus.plexus + plexus-utils + 4.0.0 + + + org.codehaus.plexus + plexus-xml + 3.0.0 + + + org.codehaus.plexus + plexus-io + 3.4.2 + + + org.codehaus.plexus + plexus-i18n + 1.0-beta-10 + + + org.codehaus.plexus + plexus-component-api + + + + + + + org.apache.maven.shared + maven-dependency-analyzer + 1.13.2 + + + org.apache.maven.shared + maven-dependency-tree + 3.2.1 + + + org.apache.maven.shared + maven-common-artifact-filters + 3.3.2 + + + org.apache.maven.shared + maven-artifact-transfer + 0.13.1 + + + org.apache.maven.shared + maven-shared-utils + 3.4.2 + + + + + org.apache.maven.plugin-tools + maven-plugin-annotations + provided + 3.11.0 + + + javax.inject + javax.inject + 1 + + + + org.eclipse.aether + aether-api + ${resolverVersion} + provided + + + org.eclipse.aether + aether-util + ${resolverVersion} + provided + + + org.sonatype.plexus + plexus-build-api + 0.0.7 + compile + + + + + commons-io + commons-io + 2.16.1 + + + + com.fasterxml.jackson.core + jackson-databind + 2.15.3 + + + org.apache.httpcomponents + httpmime + 4.5 + + + junit + junit + 4.12 + test + + + org.mockito + mockito-core + 2.28.2 + test + + + + + + + org.apache.maven.plugins + maven-plugin-plugin + 3.5 + + nasl-metadata-maven-plugin + true + + + + org.apache.maven.plugins + maven-compiler-plugin + + 8 + 8 + + + + + \ No newline at end of file diff --git a/ftp-file-utils/pom.xml b/ftp-file-utils/pom.xml new file mode 100644 index 000000000..ae8092a84 --- /dev/null +++ b/ftp-file-utils/pom.xml @@ -0,0 +1,83 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.2.9.RELEASE + + + + com.yourcompany.ftp + ftp-file-utils + 1.3.0 + FTP文件上传工具库 + 基于Base64的FTP文件上传工具,支持从指定FTP服务器上传/下载 + + + 8 + 8 + UTF-8 + 4.1 + + + + + nasl-metadata-collector + com.netease.lowcode + 0.15.0 + + + + + + com.fasterxml.jackson.core + jackson-databind + + + org.slf4j + slf4j-api + provided + + + + org.springframework.boot + spring-boot-starter-web + provided + + + org.springframework.boot + spring-boot-starter-test + test + + + commons-net + commons-net + 3.8.0 + + + + + + com.netease.lowcode + nasl-metadata-maven-plugin + 1.7.1 + + false + + + + + archive + + + + + + + \ No newline at end of file diff --git a/ftp-file-utils/src/main/java/com/yourcompany/ftp/file/utils/FtpFileUtilsBasicSpringEnvironmentConfiguration.java b/ftp-file-utils/src/main/java/com/yourcompany/ftp/file/utils/FtpFileUtilsBasicSpringEnvironmentConfiguration.java new file mode 100644 index 000000000..c84865b4f --- /dev/null +++ b/ftp-file-utils/src/main/java/com/yourcompany/ftp/file/utils/FtpFileUtilsBasicSpringEnvironmentConfiguration.java @@ -0,0 +1,12 @@ +package com.yourcompany.ftp.file.utils; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +/** + * 加入spring环境配置(在spring.factories中指定) + */ +@Configuration +@ComponentScan(basePackageClasses = LibraryAutoScan.class) +public class FtpFileUtilsBasicSpringEnvironmentConfiguration { +} diff --git a/ftp-file-utils/src/main/java/com/yourcompany/ftp/file/utils/LibraryAutoScan.java b/ftp-file-utils/src/main/java/com/yourcompany/ftp/file/utils/LibraryAutoScan.java new file mode 100644 index 000000000..1f2c4ee45 --- /dev/null +++ b/ftp-file-utils/src/main/java/com/yourcompany/ftp/file/utils/LibraryAutoScan.java @@ -0,0 +1,8 @@ +package com.yourcompany.ftp.file.utils; + +/** + * 依赖库自动扫描类 + * @author system + */ +public class LibraryAutoScan { +} diff --git a/ftp-file-utils/src/main/java/com/yourcompany/ftp/file/utils/api/FtpDownloadByPrefixUtilsService.java b/ftp-file-utils/src/main/java/com/yourcompany/ftp/file/utils/api/FtpDownloadByPrefixUtilsService.java new file mode 100644 index 000000000..98e0cf45e --- /dev/null +++ b/ftp-file-utils/src/main/java/com/yourcompany/ftp/file/utils/api/FtpDownloadByPrefixUtilsService.java @@ -0,0 +1,127 @@ +package com.yourcompany.ftp.file.utils.api; + +import com.netease.lowcode.core.annotation.NaslLogic; +import org.apache.commons.net.ftp.FTP; +import org.apache.commons.net.ftp.FTPClient; +import org.apache.commons.net.ftp.FTPFile; +import org.apache.commons.net.ftp.FTPReply; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.URI; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +/** + * FTP文件前缀匹配下载工具 + */ +@Service +public class FtpDownloadByPrefixUtilsService { + + private static final Logger LCAP_LOGGER = LoggerFactory.getLogger("LCAP_EXTENSION_LOGGER"); + + /** + * 根据文件名前缀批量下载文件并返回Base64编码的内容 + * + * @param ftpUrl 完整FTP地址 (包含用户名、密码、主机、端口和路径) + * @param filePrefix 文件名前缀 + * @return 成功返回Map,Key为文件名,Value为文件的Base64编码字符串;如果连接失败或发生异常返回null + */ + @NaslLogic + public Map downloadFilesByPrefix(String ftpUrl, String filePrefix) { + if (ftpUrl == null || filePrefix == null) { + LCAP_LOGGER.error("输入参数不能为空"); + return null; + } + + FTPClient ftpClient = new FTPClient(); + // 设置字符集为UTF-8,防止中文文件名乱码 + ftpClient.setControlEncoding("UTF-8"); + Map resultMap = new HashMap<>(); + + try { + URI uri = new URI(ftpUrl); + String host = uri.getHost(); + int port = uri.getPort() == -1 ? 21 : uri.getPort(); + String userInfo = uri.getUserInfo(); + String username = null; + String password = null; + if (userInfo != null && userInfo.contains(":")) { + String[] parts = userInfo.split(":"); + username = parts[0]; + password = parts[1]; + } + + String path = uri.getPath(); + + LCAP_LOGGER.info("正在连接到FTP服务器: " + host + ":" + port); + ftpClient.connect(host, port); + int reply = ftpClient.getReplyCode(); + if (!FTPReply.isPositiveCompletion(reply)) { + ftpClient.disconnect(); + LCAP_LOGGER.error("FTP服务器拒绝连接。响应码: " + reply); + return null; + } + + if (username != null && password != null) { + if (!ftpClient.login(username, password)) { + LCAP_LOGGER.error("FTP登录失败,用户: " + username); + return null; + } + } + + ftpClient.enterLocalPassiveMode(); + ftpClient.setFileType(FTP.BINARY_FILE_TYPE); + + // 切换目录 + if (path != null && !path.isEmpty() && !"/".equals(path)) { + if (!ftpClient.changeWorkingDirectory(path)) { + LCAP_LOGGER.warn("切换工作目录失败: " + path + "。将在当前根目录查找文件。"); + } + } + + // 获取文件列表并筛选 + FTPFile[] files = ftpClient.listFiles(); + if (files != null) { + for (FTPFile file : files) { + if (file.isFile() && file.getName().startsWith(filePrefix)) { + String fileName = file.getName(); + // 下载文件 + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + boolean downloaded = ftpClient.retrieveFile(fileName, outputStream); + if (downloaded) { + LCAP_LOGGER.info("文件下载成功: " + fileName); + byte[] fileBytes = outputStream.toByteArray(); + String base64Content = Base64.getEncoder().encodeToString(fileBytes); + resultMap.put(fileName, base64Content); + } else { + LCAP_LOGGER.error("文件下载失败: " + fileName + ",FTP响应: " + ftpClient.getReplyString()); + } + } catch (IOException e) { + LCAP_LOGGER.error("下载文件 " + fileName + " 时发生IO异常: " + e.getMessage()); + } + } + } + } + + return resultMap; + + } catch (Exception e) { + LCAP_LOGGER.error("FTP批量下载异常: " + e.getMessage(), e); + return null; + } finally { + if (ftpClient.isConnected()) { + try { + ftpClient.logout(); + ftpClient.disconnect(); + } catch (IOException ex) { + // 忽略 + } + } + } + } +} diff --git a/ftp-file-utils/src/main/java/com/yourcompany/ftp/file/utils/api/FtpDownloadLatestByPrefixUtilsService.java b/ftp-file-utils/src/main/java/com/yourcompany/ftp/file/utils/api/FtpDownloadLatestByPrefixUtilsService.java new file mode 100644 index 000000000..110d09b57 --- /dev/null +++ b/ftp-file-utils/src/main/java/com/yourcompany/ftp/file/utils/api/FtpDownloadLatestByPrefixUtilsService.java @@ -0,0 +1,146 @@ +package com.yourcompany.ftp.file.utils.api; + +import com.netease.lowcode.core.annotation.NaslLogic; +import org.apache.commons.net.ftp.FTP; +import org.apache.commons.net.ftp.FTPClient; +import org.apache.commons.net.ftp.FTPFile; +import org.apache.commons.net.ftp.FTPReply; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.URI; +import java.util.Base64; +import java.util.Calendar; +import java.util.HashMap; +import java.util.Map; + +/** + * FTP文件前缀匹配下载最新文件工具 + */ +@Service +public class FtpDownloadLatestByPrefixUtilsService { + + private static final Logger LCAP_LOGGER = LoggerFactory.getLogger("LCAP_EXTENSION_LOGGER"); + + /** + * 根据文件名前缀下载创建时间最新的文件并返回Base64编码的内容 + * + * @param ftpUrl 完整FTP地址 (包含用户名、密码、主机、端口和路径) + * @param filePrefix 文件名前缀 + * @return 成功返回Map,Key为文件名,Value为文件的Base64编码字符串;如果未找到文件、连接失败或发生异常返回null + */ + @NaslLogic + public Map downloadLatestFileByPrefix(String ftpUrl, String filePrefix) { + if (ftpUrl == null || filePrefix == null) { + LCAP_LOGGER.error("输入参数不能为空"); + return null; + } + + FTPClient ftpClient = new FTPClient(); + // 设置字符集为UTF-8,防止中文文件名乱码 + ftpClient.setControlEncoding("UTF-8"); + Map resultMap = new HashMap<>(); + + try { + URI uri = new URI(ftpUrl); + String host = uri.getHost(); + int port = uri.getPort() == -1 ? 21 : uri.getPort(); + String userInfo = uri.getUserInfo(); + String username = null; + String password = null; + if (userInfo != null && userInfo.contains(":")) { + String[] parts = userInfo.split(":"); + username = parts[0]; + password = parts[1]; + } + + String path = uri.getPath(); + + LCAP_LOGGER.info("正在连接到FTP服务器: " + host + ":" + port); + ftpClient.connect(host, port); + int reply = ftpClient.getReplyCode(); + if (!FTPReply.isPositiveCompletion(reply)) { + ftpClient.disconnect(); + LCAP_LOGGER.error("FTP服务器拒绝连接。响应码: " + reply); + return null; + } + + if (username != null && password != null) { + if (!ftpClient.login(username, password)) { + LCAP_LOGGER.error("FTP登录失败,用户: " + username); + return null; + } + } + + ftpClient.enterLocalPassiveMode(); + ftpClient.setFileType(FTP.BINARY_FILE_TYPE); + + // 切换目录 + if (path != null && !path.isEmpty() && !"/".equals(path)) { + if (!ftpClient.changeWorkingDirectory(path)) { + LCAP_LOGGER.warn("切换工作目录失败: " + path + "。将在当前根目录查找文件。"); + } + } + + // 获取文件列表并筛选最新文件 + FTPFile[] files = ftpClient.listFiles(); + FTPFile latestFile = null; + + if (files != null) { + for (FTPFile file : files) { + if (file.isFile() && file.getName().startsWith(filePrefix)) { + if (latestFile == null) { + latestFile = file; + } else { + Calendar t1 = file.getTimestamp(); + Calendar t2 = latestFile.getTimestamp(); + if (t1 != null && t2 != null && t1.after(t2)) { + latestFile = file; + } + } + } + } + } + + if (latestFile != null) { + String fileName = latestFile.getName(); + LCAP_LOGGER.info("找到最新文件: " + fileName + ", 时间: " + (latestFile.getTimestamp() != null ? latestFile.getTimestamp().getTime() : "未知")); + + // 下载文件 + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + boolean downloaded = ftpClient.retrieveFile(fileName, outputStream); + if (downloaded) { + LCAP_LOGGER.info("文件下载成功: " + fileName); + byte[] fileBytes = outputStream.toByteArray(); + String base64Content = Base64.getEncoder().encodeToString(fileBytes); + resultMap.put(fileName, base64Content); + } else { + LCAP_LOGGER.error("文件下载失败: " + fileName + ",FTP响应: " + ftpClient.getReplyString()); + } + } catch (IOException e) { + LCAP_LOGGER.error("下载文件 " + fileName + " 时发生IO异常: " + e.getMessage()); + } + } else { + LCAP_LOGGER.warn("未找到前缀为 " + filePrefix + " 的文件"); + } + + return resultMap; + + } catch (Exception e) { + LCAP_LOGGER.error("FTP下载最新文件异常: " + e.getMessage(), e); + return null; + } finally { + if (ftpClient.isConnected()) { + try { + ftpClient.logout(); + ftpClient.disconnect(); + } catch (IOException ex) { + // 忽略 + } + } + } + } +} diff --git a/ftp-file-utils/src/main/java/com/yourcompany/ftp/file/utils/api/FtpDownloadUtilsService.java b/ftp-file-utils/src/main/java/com/yourcompany/ftp/file/utils/api/FtpDownloadUtilsService.java new file mode 100644 index 000000000..b357d6644 --- /dev/null +++ b/ftp-file-utils/src/main/java/com/yourcompany/ftp/file/utils/api/FtpDownloadUtilsService.java @@ -0,0 +1,110 @@ +package com.yourcompany.ftp.file.utils.api; + +import com.netease.lowcode.core.annotation.NaslLogic; +import org.apache.commons.net.ftp.FTP; +import org.apache.commons.net.ftp.FTPClient; +import org.apache.commons.net.ftp.FTPReply; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.URI; +import java.util.Base64; + +/** + * FTP文件下载工具 + */ +@Service +public class FtpDownloadUtilsService { + + private static final Logger LCAP_LOGGER = LoggerFactory.getLogger("LCAP_EXTENSION_LOGGER"); + + /** + * 从指定FTP服务器下载文件并返回Base64编码的内容 + * + * @param ftpUrl 完整FTP地址 (包含用户名、密码、主机、端口和路径) + * @param fileName 文件名 + * @return 成功返回文件的Base64编码字符串,失败返回null + */ + @NaslLogic + public String downloadFileFromFtp(String ftpUrl, String fileName) { + if (ftpUrl == null || fileName == null) { + LCAP_LOGGER.error("输入参数不能为空"); + return null; + } + + FTPClient ftpClient = new FTPClient(); + // 设置字符集为UTF-8,防止中文文件名乱码 + ftpClient.setControlEncoding("UTF-8"); + + try { + URI uri = new URI(ftpUrl); + String host = uri.getHost(); + int port = uri.getPort() == -1 ? 21 : uri.getPort(); + String userInfo = uri.getUserInfo(); + String username = null; + String password = null; + if (userInfo != null && userInfo.contains(":")) { + String[] parts = userInfo.split(":"); + username = parts[0]; + password = parts[1]; + } + + String path = uri.getPath(); + + LCAP_LOGGER.info("正在连接到FTP服务器: " + host + ":" + port); + ftpClient.connect(host, port); + int reply = ftpClient.getReplyCode(); + if (!FTPReply.isPositiveCompletion(reply)) { + ftpClient.disconnect(); + LCAP_LOGGER.error("FTP服务器拒绝连接。响应码: " + reply); + return null; + } + + if (username != null && password != null) { + if (!ftpClient.login(username, password)) { + LCAP_LOGGER.error("FTP登录失败,用户: " + username); + return null; + } + } + + ftpClient.enterLocalPassiveMode(); + ftpClient.setFileType(FTP.BINARY_FILE_TYPE); + + // 切换目录 + if (path != null && !path.isEmpty() && !"/".equals(path)) { + if (!ftpClient.changeWorkingDirectory(path)) { + LCAP_LOGGER.warn("切换工作目录失败: " + path + "。尝试在当前根目录下载。"); + } + } + + // 下载文件 + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + boolean downloaded = ftpClient.retrieveFile(fileName, outputStream); + if (downloaded) { + LCAP_LOGGER.info("文件下载成功: " + fileName); + byte[] fileBytes = outputStream.toByteArray(); + return Base64.getEncoder().encodeToString(fileBytes); + } else { + LCAP_LOGGER.error("文件下载失败: " + fileName + ",FTP响应: " + ftpClient.getReplyString()); + return null; + } + } + + } catch (Exception e) { + LCAP_LOGGER.error("FTP下载异常: " + e.getMessage(), e); + return null; + } finally { + if (ftpClient.isConnected()) { + try { + ftpClient.logout(); + ftpClient.disconnect(); + } catch (IOException ex) { + // 忽略 + } + } + } + } +} diff --git a/ftp-file-utils/src/main/java/com/yourcompany/ftp/file/utils/api/FtpUploadUtilsService.java b/ftp-file-utils/src/main/java/com/yourcompany/ftp/file/utils/api/FtpUploadUtilsService.java new file mode 100644 index 000000000..3551a1057 --- /dev/null +++ b/ftp-file-utils/src/main/java/com/yourcompany/ftp/file/utils/api/FtpUploadUtilsService.java @@ -0,0 +1,112 @@ +package com.yourcompany.ftp.file.utils.api; + +import com.netease.lowcode.core.annotation.NaslLogic; +import org.apache.commons.net.ftp.FTP; +import org.apache.commons.net.ftp.FTPClient; +import org.apache.commons.net.ftp.FTPReply; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.util.Base64; + +/** + * FTP文件上传工具库 + */ +@Service +public class FtpUploadUtilsService { + + private static final Logger LCAP_LOGGER = LoggerFactory.getLogger("LCAP_EXTENSION_LOGGER"); + + /** + * 将Base64编码的文件上传到指定FTP服务器 + * + * @param base64Content Base64编码的文件内容 + * @param ftpUrl 完整FTP地址 + * @param fileName 文件名 (如: 海关产地证.xml) + * @return 上传成功返回true, 失败返回false + */ + @NaslLogic + public Boolean uploadFileToFtp(String base64Content, String ftpUrl, String fileName) { + if (base64Content == null || ftpUrl == null || fileName == null) { + LCAP_LOGGER.error("输入参数不能为空"); + return false; + } + + FTPClient ftpClient = new FTPClient(); + // 设置字符集为UTF-8,防止中文文件名乱码 + ftpClient.setControlEncoding("UTF-8"); + + try { + URI uri = new URI(ftpUrl); + String host = uri.getHost(); + int port = uri.getPort() == -1 ? 21 : uri.getPort(); + String userInfo = uri.getUserInfo(); + String username = null; + String password = null; + if (userInfo != null && userInfo.contains(":")) { + String[] parts = userInfo.split(":"); + username = parts[0]; + password = parts[1]; + } + + String path = uri.getPath(); + + LCAP_LOGGER.info("正在连接到FTP服务器: " + host + ":" + port); + ftpClient.connect(host, port); + int reply = ftpClient.getReplyCode(); + if (!FTPReply.isPositiveCompletion(reply)) { + ftpClient.disconnect(); + LCAP_LOGGER.error("FTP服务器拒绝连接。响应码: " + reply); + return false; + } + + if (username != null && password != null) { + if (!ftpClient.login(username, password)) { + LCAP_LOGGER.error("FTP登录失败,用户: " + username); + return false; + } + } + + ftpClient.enterLocalPassiveMode(); + ftpClient.setFileType(FTP.BINARY_FILE_TYPE); + + // 切换目录 + if (path != null && !path.isEmpty() && !"/".equals(path)) { + if (!ftpClient.changeWorkingDirectory(path)) { + LCAP_LOGGER.warn("切换工作目录失败: " + path + "。尝试上传到当前根目录。"); + } + } + + // 解码Base64 + byte[] fileBytes = Base64.getDecoder().decode(base64Content); + try (InputStream inputStream = new ByteArrayInputStream(fileBytes)) { + boolean uploaded = ftpClient.storeFile(fileName, inputStream); + if (uploaded) { + LCAP_LOGGER.info("文件上传成功: " + fileName); + return true; + } else { + LCAP_LOGGER.error("文件上传失败: " + fileName + ",FTP响应: " + ftpClient.getReplyString()); + return false; + } + } + + } catch (Exception e) { + LCAP_LOGGER.error("FTP上传异常: " + e.getMessage(), e); + return false; + } finally { + if (ftpClient.isConnected()) { + try { + ftpClient.logout(); + ftpClient.disconnect(); + } catch (IOException ex) { + // 忽略 + } + } + } + } +} diff --git a/ftp-file-utils/src/main/resources/META-INF/spring.factories b/ftp-file-utils/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..7a34bbdbe --- /dev/null +++ b/ftp-file-utils/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +com.yourcompany.ftp.file.utils.FtpFileUtilsBasicSpringEnvironmentConfiguration \ No newline at end of file diff --git a/xml-base64-converter/docs/README.md b/xml-base64-converter/docs/README.md new file mode 100644 index 000000000..35ade4e57 --- /dev/null +++ b/xml-base64-converter/docs/README.md @@ -0,0 +1,56 @@ +# XML Base64 转换工具 (XML Base64 Converter) + +## 一、背景说明 +`xml-base64-converter`(`com.yourcompany.xml:xml-base64-converter:1.0.0`)是一个专用于 XML 文本与 Base64 编码之间相互转换的工具库,默认以 `UTF-8` 字符集完成编解码,便于在传输与存储场景中保持文本内容的完整性与一致性。 + +## 二、核心功能 +这是一个纯粹的 **格式转换** 工具库,专注于 XML 文本内容与 Base64 编码之间的互相转换。 + +**核心功能**: +1. **XML 转 Base64**:通过 `xmlToBase64` 将 XML 字符串编码为 Base64 字符串。 +2. **Base64 转 XML**:通过 `base64ToXml` 将 Base64 字符串解码还原为 XML 字符串。 +3. **编码处理**:编解码统一使用 `UTF-8`(`StandardCharsets.UTF_8`)。 +4. **空值直通**:入参为 `null` 时直接返回 `null`,避免额外处理。 +5. **失败抛错并记录日志**:转换失败会记录错误日志并抛出 `RuntimeException`。 + +## 三、配置说明 +本依赖库为无配置设计,引入即可使用,无需额外的环境参数配置;不需要 `application.yml` / `application.properties` 等全局配置。 + +## 四、接口说明 + +### 1. XML 转 Base64 (xmlToBase64) +将 XML 字符串转换为 Base64 编码字符串。 + +**入参** + +| 字段名称 | 描述 | 类型 | 说明 | +| :--- | :--- | :--- | :--- | +| xmlContent | XML 字符串内容 | String | 可选
为 `null` 时直接返回 `null` | + +**出参** + +| 字段名称 | 描述 | 类型 | 说明 | +| :--- | :--- | :--- | :--- | +| result | Base64 字符串 | String | 成功返回 Base64 编码字符串
入参为 `null` 时返回 `null` | + +### 2. Base64 转 XML (base64ToXml) +将 Base64 编码字符串还原为 XML 字符串。 + +**入参** + +| 字段名称 | 描述 | 类型 | 说明 | +| :--- | :--- | :--- | :--- | +| base64Content | Base64 编码字符串 | String | 可选
为 `null` 时直接返回 `null` | + +**出参** + +| 字段名称 | 描述 | 类型 | 说明 | +| :--- | :--- | :--- | :--- | +| result | XML 字符串 | String | 成功返回解码后的 XML 文本
入参为 `null` 时返回 `null` | + +## 五、注意事项 +1. **URL/连接与传输模式**:本工具库不涉及 `URL` 格式、连接信息、目录切换、被动模式或二进制传输类型(如 `FTP.BINARY_FILE_TYPE`)等配置与行为。 +2. **字符集**:编解码固定使用 **UTF-8**(`StandardCharsets.UTF_8`)。若业务侧 XML 原始数据不是 UTF-8 编码,请先在上游完成字符集统一,否则可能出现乱码。 +3. **XML 合法性**:接口仅做字符串与 Base64 的编解码转换,不做 XML 结构/格式校验;如需校验 XML 合法性,请在调用前自行处理。 +4. **异常处理策略**:当发生转换异常时(例如 `base64Content` 不是合法 Base64),方法会记录错误日志并直接抛出 `RuntimeException`,不会返回 `null`/`false` 兜底;调用方需自行捕获并处理。 +5. **日志输出**:错误日志使用 `LCAP_EXTENSION_LOGGER` 输出;其中 `xmlToBase64` 的异常日志文案为 `"Base64 to XML conversion failed"`(与方法方向不一致),排查时请以异常栈与抛错信息为准。 \ No newline at end of file diff --git a/xml-base64-converter/jar/nasl-metadata-collector-0.15.0/nasl-metadata-collector-0.15.0.jar b/xml-base64-converter/jar/nasl-metadata-collector-0.15.0/nasl-metadata-collector-0.15.0.jar new file mode 100644 index 000000000..8a326ed2a Binary files /dev/null and b/xml-base64-converter/jar/nasl-metadata-collector-0.15.0/nasl-metadata-collector-0.15.0.jar differ diff --git a/xml-base64-converter/jar/nasl-metadata-maven-plugin-1.7.1/nasl-metadata-maven-plugin-1.7.1.jar b/xml-base64-converter/jar/nasl-metadata-maven-plugin-1.7.1/nasl-metadata-maven-plugin-1.7.1.jar new file mode 100644 index 000000000..df8c39bca Binary files /dev/null and b/xml-base64-converter/jar/nasl-metadata-maven-plugin-1.7.1/nasl-metadata-maven-plugin-1.7.1.jar differ diff --git a/xml-base64-converter/jar/nasl-metadata-maven-plugin-1.7.1/nasl-metadata-maven-plugin-1.7.1.pom b/xml-base64-converter/jar/nasl-metadata-maven-plugin-1.7.1/nasl-metadata-maven-plugin-1.7.1.pom new file mode 100644 index 000000000..996bdcdc7 --- /dev/null +++ b/xml-base64-converter/jar/nasl-metadata-maven-plugin-1.7.1/nasl-metadata-maven-plugin-1.7.1.pom @@ -0,0 +1,240 @@ + + 4.0.0 + + com.netease.lowcode + nasl-metadata-maven-plugin + 1.7.1 + maven-plugin + + Nasl Metadata Maven Plugin + + UTF-8 + 3.2.5 + 9.4.54.v20240208 + 3.3.0 + 1.0.2.v20150114 + 8 + 1.7.36 + 4.9.2 + + + + + + org.apache.maven + maven-artifact + ${mavenVersion} + provided + + + org.apache.maven + maven-plugin-api + ${mavenVersion} + provided + + + org.apache.maven + maven-model + ${mavenVersion} + provided + + + org.apache.maven + maven-core + ${mavenVersion} + provided + + + org.apache.maven + maven-repository-metadata + ${mavenVersion} + provided + + + org.apache.maven + maven-settings + ${mavenVersion} + provided + + + org.apache.maven + maven-aether-provider + ${mavenVersion} + provided + + + + + org.apache.maven.doxia + doxia-sink-api + 1.12.0 + + + org.codehaus.plexus + plexus-container-default + + + + + org.apache.maven.reporting + maven-reporting-api + 3.1.1 + + + org.apache.maven.reporting + maven-reporting-impl + 3.2.0 + + + org.codehaus.plexus + plexus-container-default + + + + + + + org.codehaus.plexus + plexus-archiver + ${plexus-archiver.version} + + + org.codehaus.plexus + plexus-utils + 4.0.0 + + + org.codehaus.plexus + plexus-xml + 3.0.0 + + + org.codehaus.plexus + plexus-io + 3.4.2 + + + org.codehaus.plexus + plexus-i18n + 1.0-beta-10 + + + org.codehaus.plexus + plexus-component-api + + + + + + + org.apache.maven.shared + maven-dependency-analyzer + 1.13.2 + + + org.apache.maven.shared + maven-dependency-tree + 3.2.1 + + + org.apache.maven.shared + maven-common-artifact-filters + 3.3.2 + + + org.apache.maven.shared + maven-artifact-transfer + 0.13.1 + + + org.apache.maven.shared + maven-shared-utils + 3.4.2 + + + + + org.apache.maven.plugin-tools + maven-plugin-annotations + provided + 3.11.0 + + + javax.inject + javax.inject + 1 + + + + org.eclipse.aether + aether-api + ${resolverVersion} + provided + + + org.eclipse.aether + aether-util + ${resolverVersion} + provided + + + org.sonatype.plexus + plexus-build-api + 0.0.7 + compile + + + + + commons-io + commons-io + 2.16.1 + + + + com.fasterxml.jackson.core + jackson-databind + 2.15.3 + + + org.apache.httpcomponents + httpmime + 4.5 + + + junit + junit + 4.12 + test + + + org.mockito + mockito-core + 2.28.2 + test + + + + + + + org.apache.maven.plugins + maven-plugin-plugin + 3.5 + + nasl-metadata-maven-plugin + true + + + + org.apache.maven.plugins + maven-compiler-plugin + + 8 + 8 + + + + + \ No newline at end of file diff --git a/xml-base64-converter/pom.xml b/xml-base64-converter/pom.xml new file mode 100644 index 000000000..76a7e7ee0 --- /dev/null +++ b/xml-base64-converter/pom.xml @@ -0,0 +1,78 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.2.9.RELEASE + + + + com.yourcompany.xml + xml-base64-converter + 1.0.0 + XML Base64转换器 + 专用于XML文件与Base64编码之间相互转换的工具库,支持XML格式验证和编码处理 + + + 8 + 8 + UTF-8 + 4.1 + + + + + nasl-metadata-collector + com.netease.lowcode + 0.15.0 + + + + + + com.fasterxml.jackson.core + jackson-databind + + + org.slf4j + slf4j-api + provided + + + + org.springframework.boot + spring-boot-starter-web + provided + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + com.netease.lowcode + nasl-metadata-maven-plugin + 1.7.1 + + false + + + + + archive + + + + + + + \ No newline at end of file diff --git a/xml-base64-converter/src/main/java/com/yourcompany/xml/base64/converter/LibraryAutoScan.java b/xml-base64-converter/src/main/java/com/yourcompany/xml/base64/converter/LibraryAutoScan.java new file mode 100644 index 000000000..ded062bac --- /dev/null +++ b/xml-base64-converter/src/main/java/com/yourcompany/xml/base64/converter/LibraryAutoScan.java @@ -0,0 +1,8 @@ +package com.yourcompany.xml.base64.converter; + +/** + * 依赖库自动扫描类 + * @author system + */ +public class LibraryAutoScan { +} diff --git a/xml-base64-converter/src/main/java/com/yourcompany/xml/base64/converter/XmlBase64ConverterBasicSpringEnvironmentConfiguration.java b/xml-base64-converter/src/main/java/com/yourcompany/xml/base64/converter/XmlBase64ConverterBasicSpringEnvironmentConfiguration.java new file mode 100644 index 000000000..6c08831a9 --- /dev/null +++ b/xml-base64-converter/src/main/java/com/yourcompany/xml/base64/converter/XmlBase64ConverterBasicSpringEnvironmentConfiguration.java @@ -0,0 +1,12 @@ +package com.yourcompany.xml.base64.converter; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +/** + * 加入spring环境配置(在spring.factories中指定) + */ +@Configuration +@ComponentScan(basePackageClasses = LibraryAutoScan.class) +public class XmlBase64ConverterBasicSpringEnvironmentConfiguration { +} diff --git a/xml-base64-converter/src/main/java/com/yourcompany/xml/base64/converter/api/Base64ToXmlConverterService.java b/xml-base64-converter/src/main/java/com/yourcompany/xml/base64/converter/api/Base64ToXmlConverterService.java new file mode 100644 index 000000000..c68b0611b --- /dev/null +++ b/xml-base64-converter/src/main/java/com/yourcompany/xml/base64/converter/api/Base64ToXmlConverterService.java @@ -0,0 +1,39 @@ +package com.yourcompany.xml.base64.converter.api; + +import com.netease.lowcode.core.annotation.NaslLogic; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +/** + * XML Base64转换器 + */ +@Service +public class Base64ToXmlConverterService { + + //参数使用LCAP_EXTENSION_LOGGER后日志会显示在平台日志功能中 + private static final Logger log = LoggerFactory.getLogger("LCAP_EXTENSION_LOGGER"); + + /** + * Base64字符串转XML + * + * @param base64Content Base64编码字符串 + * @return 解码后的XML字符串 + */ + @NaslLogic + public String base64ToXml(String base64Content) { + if (base64Content == null) { + return null; + } + try { + byte[] decodedBytes = Base64.getDecoder().decode(base64Content); + return new String(decodedBytes, StandardCharsets.UTF_8); + } catch (Exception e) { + log.error("Base64 to XML conversion failed: " + e.getMessage(), e); + throw new RuntimeException("Base64 to XML conversion failed: " + e.getMessage(), e); + } + } +} diff --git a/xml-base64-converter/src/main/java/com/yourcompany/xml/base64/converter/api/XmlToBase64ConverterService.java b/xml-base64-converter/src/main/java/com/yourcompany/xml/base64/converter/api/XmlToBase64ConverterService.java new file mode 100644 index 000000000..f7b1133ad --- /dev/null +++ b/xml-base64-converter/src/main/java/com/yourcompany/xml/base64/converter/api/XmlToBase64ConverterService.java @@ -0,0 +1,38 @@ +package com.yourcompany.xml.base64.converter.api; + +import com.netease.lowcode.core.annotation.NaslLogic; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +/** + * XML Base64转换器 + */ +@Service +public class XmlToBase64ConverterService { + + //参数使用LCAP_EXTENSION_LOGGER后日志会显示在平台日志功能中 + private static final Logger log = LoggerFactory.getLogger("LCAP_EXTENSION_LOGGER"); + + /** + * XML字符串转Base64 + * + * @param xmlContent XML字符串内容 + * @return Base64编码后的字符串 + */ + @NaslLogic + public String xmlToBase64(String xmlContent) { + if (xmlContent == null) { + return null; + } + try { + return Base64.getEncoder().encodeToString(xmlContent.getBytes(StandardCharsets.UTF_8)); + } catch (Exception e) { + log.error("Base64 to XML conversion failed: " + e.getMessage(), e); + throw new RuntimeException("XML to Base64 conversion failed: " + e.getMessage(), e); + } + } +} diff --git a/xml-base64-converter/src/main/resources/META-INF/spring.factories b/xml-base64-converter/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..c7f6acd9f --- /dev/null +++ b/xml-base64-converter/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +com.yourcompany.xml.base64.converter.XmlBase64ConverterBasicSpringEnvironmentConfiguration \ No newline at end of file diff --git a/xml-generator-utils/docs/README.md b/xml-generator-utils/docs/README.md new file mode 100644 index 000000000..6c16ace12 --- /dev/null +++ b/xml-generator-utils/docs/README.md @@ -0,0 +1,84 @@ +# 通用XML生成/解析工具库 (xml-generator-utils) + +## 一、背景说明 +在对接外部系统(如海关、银行、第三方支付平台)时,常需要生成/解析格式严格的 XML 报文。这些报文往往有以下要求: +1. **字段顺序固定**:标准的 XML 解析可能不依赖顺序,但部分老旧系统或特定行业标准要求 XML 标签必须按照文档规定的顺序排列。 +2. **嵌套结构复杂**:报文通常包含多层嵌套的列表或对象。 +3. **数据源多样**:数据可能来自数据库查询结果(`Map`)、前端传递的 JSON 或其他服务的响应。 + +本依赖库(`xml-generator-utils`,版本 `1.5.0`)提供通用的 XML 生成/解析能力,屏蔽底层 XML 拼接与反序列化细节,让开发者聚焦数据本身与必要的顺序控制。 + +## 二、核心功能 +**核心功能**: +1. **JSON 转 XML**:将标准 JSON 字符串转换为 XML 格式字符串,支持自定义根节点名称(`jsonToXml`)。 +2. **Map 转 XML(可控顺序)**:支持将 `Map` 数据转换为 XML,并允许传入 `keyOrder` 列表来**强制指定根节点下一级标签生成顺序**(`mapToXml`)。 +3. **字符串值智能展开**:在 `mapToXml` 模式下,若 `Map` 的值是 JSON 对象/数组字符串,会自动反序列化并生成嵌套 XML 结构。 +4. **XML 特殊字符转义**:生成 XML 时自动转义 `&`、`<`、`>`、`"`、`'`,避免报文破坏。 +5. **通用 XML 解析(反序列化)**:将 XML 字符串反序列化为指定类型的 Java 对象,并提供一定容错能力(`parseXml`)。 + +## 三、配置说明 +本依赖库为无配置设计: +- 不需要 `application.yml` 全局配置;生成/解析所需信息均由入参动态指定。 +- 通过 `spring.factories` 提供 Spring 环境自动装配能力,引入即可被扫描并使用。 + +## 四、接口说明 + +### 1. JSON 字符串转 XML (jsonToXml) +将一个标准的 JSON 字符串转换为 XML 格式字符串,并自动补充 `` 头。 + +**入参** + +| 字段名称 | 描述 | 类型 | 说明 | +| :--- | :--- | :--- | :--- | +| jsonStr | 标准 JSON 格式字符串 | String | 必填。为空(`null`/空串)时返回 `null` | +| rootTagName | 根节点名称 | String | 必填。建议为合法 XML 标签名
示例:`Request`
可携带属性:`Request id="1"` | + +**出参** + +| 字段名称 | 描述 | 类型 | 说明 | +| :--- | :--- | :--- | :--- | +| result | XML 字符串 | String | 成功时返回 XML 字符串(包含 ``)
JSON 解析失败等异常场景会抛出 `RuntimeException` | + +### 2. Map 转 XML (支持指定顺序) (mapToXml) +将 `Map` 转换为 XML;支持通过 `keyOrder` 控制根节点直接子级标签顺序,并对值进行智能嵌套解析。 + +**入参** + +| 字段名称 | 描述 | 类型 | 说明 | +| :--- | :--- | :--- | :--- | +| data | 数据 Map | Map<String, String> | 必填。为 `null` 时返回 `null`
Key 为标签名,Value 为文本或 JSON 字符串(对象/数组) | +| keyOrder | 顺序列表 | List<String> | 可选。用于控制**根节点下一级**标签输出顺序
为空(`null`/空列表)时回退为默认 `Map` 遍历顺序 | +| rootTagName | 根节点名称 | String | 必填。为空(`null`/空串)时返回 `null`
示例:`Message`
可携带属性:`Message type="A"` | + +**出参** + +| 字段名称 | 描述 | 类型 | 说明 | +| :--- | :--- | :--- | :--- | +| result | XML 字符串 | String | 成功时返回 XML 字符串(包含 ``)
入参不合法时返回 `null` | + +### 3. 通用 XML 解析 (parseXml) +将 XML 字符串反序列化为指定类型的 Java 对象(忽略未知字段、属性名大小写不敏感)。 + +**入参** + +| 字段名称 | 描述 | 类型 | 说明 | +| :--- | :--- | :--- | :--- | +| xmlContent | XML 字符串内容 | String | 必填。为 `null` 时直接抛出 `RuntimeException` | +| clazz | 目标类型 Class | Class<T> | 必填。用于指定反序列化目标类型(如 `MyDto.class`) | + +**出参** + +| 字段名称 | 描述 | 类型 | 说明 | +| :--- | :--- | :--- | :--- | +| result | 反序列化结果对象 | T | 成功时返回指定类型对象
解析失败会抛出 `RuntimeException`(包含错误信息) | + +## 五、注意事项 +1. **根节点/标签名格式**:`rootTagName`/标签名支持携带属性(如 `Request id="1"`)。库在闭合标签时会取第一个空白分隔段作为标签名进行闭合(即闭合为 ``)。 +2. **顺序控制范围**:`keyOrder` 仅控制根节点**直接子级**的输出顺序;更深层结构的顺序取决于输入数据结构(如嵌套 `Map` 的遍历顺序)。 +3. **默认遍历顺序不保证**:当 `keyOrder` 为空时,`mapToXml` 采用 `Map` 的遍历顺序生成 XML;若使用非有序 `Map`,输出顺序可能不稳定。 +4. **字符串 JSON 智能解析策略**:`mapToXml` 会对值为字符串且形如 `{...}` / `[...]` 的内容尝试 JSON 反序列化;反序列化失败则按普通字符串写入,不会报错中断。 +5. **编码与 XML 头**:`jsonToXml` 与 `mapToXml` 生成结果均固定包含 ``,用于明确声明 `UTF-8` 编码。 +6. **转义处理**:生成 XML 时会自动转义 `&`、`<`、`>`、`"`、`'`,无需手动处理。 +7. **空值与缺失 Key 行为**:`mapToXml` 中 Value 为 `null` 时生成自闭合标签(如 ``);`keyOrder` 中存在但 `data` 不包含的 Key 会被跳过生成。 +8. **异常处理策略**:`mapToXml` 对关键入参为空采取记录日志并返回 `null`;`jsonToXml` 在 JSON 解析失败等异常场景会抛出 `RuntimeException`;`parseXml` 对空入参与解析失败均会抛出 `RuntimeException` 并记录日志。 +9. **解析容错特性**:`parseXml` 解析时默认忽略未知属性,并对字段名大小写不敏感,适合对接字段不完全一致的 XML 报文。 \ No newline at end of file diff --git a/xml-generator-utils/jar/nasl-metadata-collector-0.15.0/nasl-metadata-collector-0.15.0.jar b/xml-generator-utils/jar/nasl-metadata-collector-0.15.0/nasl-metadata-collector-0.15.0.jar new file mode 100644 index 000000000..8a326ed2a Binary files /dev/null and b/xml-generator-utils/jar/nasl-metadata-collector-0.15.0/nasl-metadata-collector-0.15.0.jar differ diff --git a/xml-generator-utils/jar/nasl-metadata-maven-plugin-1.7.1/nasl-metadata-maven-plugin-1.7.1.jar b/xml-generator-utils/jar/nasl-metadata-maven-plugin-1.7.1/nasl-metadata-maven-plugin-1.7.1.jar new file mode 100644 index 000000000..df8c39bca Binary files /dev/null and b/xml-generator-utils/jar/nasl-metadata-maven-plugin-1.7.1/nasl-metadata-maven-plugin-1.7.1.jar differ diff --git a/xml-generator-utils/jar/nasl-metadata-maven-plugin-1.7.1/nasl-metadata-maven-plugin-1.7.1.pom b/xml-generator-utils/jar/nasl-metadata-maven-plugin-1.7.1/nasl-metadata-maven-plugin-1.7.1.pom new file mode 100644 index 000000000..996bdcdc7 --- /dev/null +++ b/xml-generator-utils/jar/nasl-metadata-maven-plugin-1.7.1/nasl-metadata-maven-plugin-1.7.1.pom @@ -0,0 +1,240 @@ + + 4.0.0 + + com.netease.lowcode + nasl-metadata-maven-plugin + 1.7.1 + maven-plugin + + Nasl Metadata Maven Plugin + + UTF-8 + 3.2.5 + 9.4.54.v20240208 + 3.3.0 + 1.0.2.v20150114 + 8 + 1.7.36 + 4.9.2 + + + + + + org.apache.maven + maven-artifact + ${mavenVersion} + provided + + + org.apache.maven + maven-plugin-api + ${mavenVersion} + provided + + + org.apache.maven + maven-model + ${mavenVersion} + provided + + + org.apache.maven + maven-core + ${mavenVersion} + provided + + + org.apache.maven + maven-repository-metadata + ${mavenVersion} + provided + + + org.apache.maven + maven-settings + ${mavenVersion} + provided + + + org.apache.maven + maven-aether-provider + ${mavenVersion} + provided + + + + + org.apache.maven.doxia + doxia-sink-api + 1.12.0 + + + org.codehaus.plexus + plexus-container-default + + + + + org.apache.maven.reporting + maven-reporting-api + 3.1.1 + + + org.apache.maven.reporting + maven-reporting-impl + 3.2.0 + + + org.codehaus.plexus + plexus-container-default + + + + + + + org.codehaus.plexus + plexus-archiver + ${plexus-archiver.version} + + + org.codehaus.plexus + plexus-utils + 4.0.0 + + + org.codehaus.plexus + plexus-xml + 3.0.0 + + + org.codehaus.plexus + plexus-io + 3.4.2 + + + org.codehaus.plexus + plexus-i18n + 1.0-beta-10 + + + org.codehaus.plexus + plexus-component-api + + + + + + + org.apache.maven.shared + maven-dependency-analyzer + 1.13.2 + + + org.apache.maven.shared + maven-dependency-tree + 3.2.1 + + + org.apache.maven.shared + maven-common-artifact-filters + 3.3.2 + + + org.apache.maven.shared + maven-artifact-transfer + 0.13.1 + + + org.apache.maven.shared + maven-shared-utils + 3.4.2 + + + + + org.apache.maven.plugin-tools + maven-plugin-annotations + provided + 3.11.0 + + + javax.inject + javax.inject + 1 + + + + org.eclipse.aether + aether-api + ${resolverVersion} + provided + + + org.eclipse.aether + aether-util + ${resolverVersion} + provided + + + org.sonatype.plexus + plexus-build-api + 0.0.7 + compile + + + + + commons-io + commons-io + 2.16.1 + + + + com.fasterxml.jackson.core + jackson-databind + 2.15.3 + + + org.apache.httpcomponents + httpmime + 4.5 + + + junit + junit + 4.12 + test + + + org.mockito + mockito-core + 2.28.2 + test + + + + + + + org.apache.maven.plugins + maven-plugin-plugin + 3.5 + + nasl-metadata-maven-plugin + true + + + + org.apache.maven.plugins + maven-compiler-plugin + + 8 + 8 + + + + + \ No newline at end of file diff --git a/xml-generator-utils/pom.xml b/xml-generator-utils/pom.xml new file mode 100644 index 000000000..ba6644a46 --- /dev/null +++ b/xml-generator-utils/pom.xml @@ -0,0 +1,82 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.2.9.RELEASE + + + + com.yourcompany.xml + xml-generator-utils + 1.7.0 + 通用XML生成/解析工具库 + 通用的XML文件生成/解析工具库,支持将Map、JavaBean、JSON等多种数据格式转换为标准XML文件,提供模板化和自定义XML生成能力 + + + 8 + 8 + UTF-8 + 4.1 + + + + + nasl-metadata-collector + com.netease.lowcode + 0.15.0 + + + + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + + + org.slf4j + slf4j-api + provided + + + + org.springframework.boot + spring-boot-starter-web + provided + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + com.netease.lowcode + nasl-metadata-maven-plugin + 1.7.1 + + false + + + + + archive + + + + + + + \ No newline at end of file diff --git a/xml-generator-utils/src/main/java/com/yourcompany/xml/generator/utils/LibraryAutoScan.java b/xml-generator-utils/src/main/java/com/yourcompany/xml/generator/utils/LibraryAutoScan.java new file mode 100644 index 000000000..5d94a649f --- /dev/null +++ b/xml-generator-utils/src/main/java/com/yourcompany/xml/generator/utils/LibraryAutoScan.java @@ -0,0 +1,8 @@ +package com.yourcompany.xml.generator.utils; + +/** + * 依赖库自动扫描类 + * @author system + */ +public class LibraryAutoScan { +} diff --git a/xml-generator-utils/src/main/java/com/yourcompany/xml/generator/utils/XmlGeneratorUtilsBasicSpringEnvironmentConfiguration.java b/xml-generator-utils/src/main/java/com/yourcompany/xml/generator/utils/XmlGeneratorUtilsBasicSpringEnvironmentConfiguration.java new file mode 100644 index 000000000..2552ff5b4 --- /dev/null +++ b/xml-generator-utils/src/main/java/com/yourcompany/xml/generator/utils/XmlGeneratorUtilsBasicSpringEnvironmentConfiguration.java @@ -0,0 +1,12 @@ +package com.yourcompany.xml.generator.utils; + +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; + +/** + * 加入spring环境配置(在spring.factories中指定) + */ +@Configuration +@ComponentScan(basePackageClasses = LibraryAutoScan.class) +public class XmlGeneratorUtilsBasicSpringEnvironmentConfiguration { +} diff --git a/xml-generator-utils/src/main/java/com/yourcompany/xml/generator/utils/api/JsonToXmlGeneratorService.java b/xml-generator-utils/src/main/java/com/yourcompany/xml/generator/utils/api/JsonToXmlGeneratorService.java new file mode 100644 index 000000000..dae7c3c18 --- /dev/null +++ b/xml-generator-utils/src/main/java/com/yourcompany/xml/generator/utils/api/JsonToXmlGeneratorService.java @@ -0,0 +1,97 @@ +package com.yourcompany.xml.generator.utils.api; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.netease.lowcode.core.annotation.NaslLogic; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; + +/** + * JSON转XML工具库 + */ +@Service +public class JsonToXmlGeneratorService { + + private static final Logger LCAP_LOGGER = LoggerFactory.getLogger("LCAP_EXTENSION_LOGGER"); + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + + + /** + * 将JSON字符串转换为XML字符串 + * + * @param jsonStr JSON字符串 + * @param rootTagName 根节点名称 + * @return XML字符串 + */ + @NaslLogic + public String jsonToXml(String jsonStr, String rootTagName) { + if (jsonStr == null || jsonStr.isEmpty()) { + LCAP_LOGGER.error("Input JSON string is empty"); + return null; + } + try { + // Parse JSON to generic map first + Map data = OBJECT_MAPPER.readValue(jsonStr, Map.class); + // Convert to XML using a helper method that handles the generic object structure + // We cannot call mapToXml directly if it expects Map or similar concrete types + // But since we want to expose a NASL logic, we need to handle the conversion internally + return convertMapToXml(data, rootTagName); + } catch (Exception e) { + LCAP_LOGGER.error("Failed to parse JSON string: " + e.getMessage(), e); + throw new RuntimeException("JSON parsing error", e); + } + } + + private String convertMapToXml(Map data, String rootTagName) { + if (data == null) return null; + StringBuilder xml = new StringBuilder(); + xml.append("\n"); + appendTag(xml, rootTagName, data, ""); + return xml.toString(); + } + + private void appendTag(StringBuilder sb, String tagName, Object value, String indent) { + // Extract pure tag name for closing tag (handle attributes in tagName like "Root attr='val'") + String pureTagName = tagName.split("\\s+")[0]; + + if (value == null) { + sb.append(indent).append("<").append(tagName).append("/>\n"); + return; + } + + if (value instanceof Map) { + sb.append(indent).append("<").append(tagName).append(">\n"); + Map map = (Map) value; + for (Map.Entry entry : map.entrySet()) { + if (entry.getKey() != null) { + appendTag(sb, entry.getKey().toString(), entry.getValue(), indent + " "); + } + } + sb.append(indent).append("\n"); + } else if (value instanceof List) { + List list = (List) value; + for (Object item : list) { + appendTag(sb, tagName, item, indent); + } + } else { + sb.append(indent).append("<").append(tagName).append(">"); + String valStr = value.toString(); + valStr = escapeXml(valStr); + sb.append(valStr); + sb.append("\n"); + } + } + + private String escapeXml(String str) { + if (str == null) return ""; + return str.replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace("\"", """) + .replace("'", "'"); + } +} diff --git a/xml-generator-utils/src/main/java/com/yourcompany/xml/generator/utils/api/XmlEntityToXmlGeneratorService.java b/xml-generator-utils/src/main/java/com/yourcompany/xml/generator/utils/api/XmlEntityToXmlGeneratorService.java new file mode 100644 index 000000000..23dea4495 --- /dev/null +++ b/xml-generator-utils/src/main/java/com/yourcompany/xml/generator/utils/api/XmlEntityToXmlGeneratorService.java @@ -0,0 +1,229 @@ +package com.yourcompany.xml.generator.utils.api; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.netease.lowcode.core.annotation.NaslLogic; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 实体(JSON)转XML生成服务 + * + *

将入参的实体JSON按指定类型反序列化后,基于实体字段值生成XML字符串。 + * 生成规则与 {@link XmlGeneratorUtilsService#mapToXml(Map, List, String)} 保持一致: + * 支持XML声明、缩进、List重复标签、Map嵌套标签、以及在标签名包含属性时的闭合标签处理。

+ * + *

说明:由于平台侧入参/返回值不允许使用Object类型,且实体无法添加XML注解,本服务采用: + * JSON字符串 + Class<T> 的方式还原实体,并开启字段名大小写不敏感以增强兼容性。

+ */ +@Service +public class XmlEntityToXmlGeneratorService { + + private static final Logger LCAP_LOGGER = LoggerFactory.getLogger("LCAP_EXTENSION_LOGGER"); + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + static { + OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + OBJECT_MAPPER.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true); + } + + /** + * 将实体JSON转换为XML字符串 + * + *

入参作用:

+ *
    + *
  • entityJson:实体数据(JSON字符串)。用于在不暴露Object类型入参的前提下传递复杂结构。
  • + *
  • clazz:实体类型。用于将JSON反序列化为目标实体,并支持字段名大小写不敏感匹配。
  • + *
  • keyOrder:子节点顺序列表(可为空)。不为空时仅输出列表命中的字段,并按该顺序生成(只控制 根节点下一级 子标签顺序)。
  • + *
  • keyOrderByTag:嵌套节点的子节点顺序配置(可为空)。Key为父节点标签名,Value为其子节点顺序。
  • + *
  • rootTagName:根节点名称(必填)。可包含属性,例如 {@code Certificate xmlns="..."}。
  • + *
+ * + *

大小写处理:

+ *
    + *
  • 反序列化实体时:字段名大小写不敏感。
  • + *
  • 生成XML且指定keyOrder时:会对实体字段名做大小写不敏感匹配,但输出标签名以keyOrder中的key为准。
  • + *
  • 生成XML且指定keyOrderByTag时:会对各层节点的字段名做大小写不敏感匹配,但输出标签名以配置中的key为准。
  • + *
+ * + * @param entityJson 实体JSON字符串 + * @param clazz 目标实体类型 + * @param keyOrder XML子节点顺序列表(可为空) + * @param keyOrderByTag 嵌套节点的子节点顺序配置(可为空) + * @param rootTagName XML根节点名称(必填) + * @param 目标实体泛型类型 + * @return 生成的XML字符串;当必填入参为空时返回null + */ + @NaslLogic + public String entityJsonToXml(String entityJson, Class clazz, List keyOrder, Map> keyOrderByTag, String rootTagName) { + if (entityJson == null || entityJson.trim().isEmpty()) { + LCAP_LOGGER.error("输入实体 JSON 为空"); + return null; + } + if (clazz == null) { + LCAP_LOGGER.error("实体类型 clazz 为空"); + return null; + } + if (rootTagName == null || rootTagName.trim().isEmpty()) { + LCAP_LOGGER.error("根节点名称不能为空"); + return null; + } + + try { + T entity = OBJECT_MAPPER.readValue(entityJson, clazz); + Map dataMap = OBJECT_MAPPER.convertValue(entity, new TypeReference>() {}); + + StringBuilder xml = new StringBuilder(); + xml.append("\n"); + + if (keyOrder != null && !keyOrder.isEmpty()) { + String pureRootTagName = rootTagName.trim().split("\\s+")[0]; + xml.append("<").append(rootTagName).append(">\n"); + + Map lowerKeyToOriginalKey = new HashMap<>(); + for (String key : dataMap.keySet()) { + if (key == null) { + continue; + } + lowerKeyToOriginalKey.putIfAbsent(key.toLowerCase(), key); + } + + for (String key : keyOrder) { + if (key == null) { + continue; + } + String trimmedKey = key.trim(); + if (trimmedKey.isEmpty()) { + continue; + } + + String matchedKey = dataMap.containsKey(trimmedKey) + ? trimmedKey + : lowerKeyToOriginalKey.get(trimmedKey.toLowerCase()); + if (matchedKey == null) { + continue; + } + + Object value = dataMap.get(matchedKey); + appendTag(xml, trimmedKey, value, " ", keyOrderByTag, pureRootTagName + "/" + trimmedKey); + } + + xml.append("\n"); + } else { + String pureRootTagName = rootTagName.trim().split("\\s+")[0]; + appendTag(xml, rootTagName, dataMap, "", keyOrderByTag, pureRootTagName); + } + + return xml.toString(); + } catch (Exception e) { + LCAP_LOGGER.error("实体 JSON 转 XML 失败: " + e.getMessage(), e); + throw new RuntimeException("实体 JSON 转 XML 失败", e); + } + } + + private void appendTag(StringBuilder sb, String tagName, Object value, String indent, Map> keyOrderByTag, String path) { + String pureTagName = tagName.split("\\s+")[0]; + + if (value == null) { + sb.append(indent).append("<").append(tagName).append("/>\n"); + return; + } + + Object processingValue = value; + + if (value instanceof String) { + String strVal = ((String) value).trim(); + if ((strVal.startsWith("{") && strVal.endsWith("}")) || (strVal.startsWith("[") && strVal.endsWith("]"))) { + try { + processingValue = OBJECT_MAPPER.readValue(strVal, Object.class); + } catch (Exception ignored) { + } + } + } + + if (processingValue instanceof Map) { + sb.append(indent).append("<").append(tagName).append(">\n"); + Map map = (Map) processingValue; + + List childKeyOrder = null; + if (keyOrderByTag != null && !keyOrderByTag.isEmpty()) { + childKeyOrder = keyOrderByTag.get(pureTagName); + if ((childKeyOrder == null || childKeyOrder.isEmpty()) && path != null && !path.isEmpty()) { + childKeyOrder = keyOrderByTag.get(path); + } + } + + if (childKeyOrder != null && !childKeyOrder.isEmpty()) { + Map lowerKeyToOriginalKey = new HashMap<>(); + for (Object keyObj : map.keySet()) { + if (keyObj == null) { + continue; + } + String keyStr = keyObj.toString(); + lowerKeyToOriginalKey.putIfAbsent(keyStr.toLowerCase(), keyStr); + } + + for (String key : childKeyOrder) { + if (key == null) { + continue; + } + String trimmedKey = key.trim(); + if (trimmedKey.isEmpty()) { + continue; + } + + Object matchedKeyObj = map.containsKey(trimmedKey) ? trimmedKey : lowerKeyToOriginalKey.get(trimmedKey.toLowerCase()); + if (matchedKeyObj == null) { + continue; + } + Object childValue = map.get(matchedKeyObj); + appendTag(sb, trimmedKey, childValue, indent + " ", keyOrderByTag, pathJoin(path, trimmedKey)); + } + } else { + for (Map.Entry entry : map.entrySet()) { + if (entry.getKey() != null) { + appendTag(sb, entry.getKey().toString(), entry.getValue(), indent + " ", keyOrderByTag, pathJoin(path, entry.getKey().toString())); + } + } + } + sb.append(indent).append("\n"); + } else if (processingValue instanceof List) { + List list = (List) processingValue; + for (Object item : list) { + appendTag(sb, tagName, item, indent, keyOrderByTag, path); + } + } else { + sb.append(indent).append("<").append(tagName).append(">"); + String valStr = escapeXml(processingValue.toString()); + sb.append(valStr); + sb.append("\n"); + } + } + + private String pathJoin(String base, String child) { + if (base == null || base.trim().isEmpty()) { + return child; + } + if (child == null || child.trim().isEmpty()) { + return base; + } + return base + "/" + child.trim(); + } + + private String escapeXml(String str) { + if (str == null) return ""; + return str.replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace("\"", """) + .replace("'", "'"); + } +} diff --git a/xml-generator-utils/src/main/java/com/yourcompany/xml/generator/utils/api/XmlGeneratorUtilsService.java b/xml-generator-utils/src/main/java/com/yourcompany/xml/generator/utils/api/XmlGeneratorUtilsService.java new file mode 100644 index 000000000..452efe816 --- /dev/null +++ b/xml-generator-utils/src/main/java/com/yourcompany/xml/generator/utils/api/XmlGeneratorUtilsService.java @@ -0,0 +1,215 @@ +package com.yourcompany.xml.generator.utils.api; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.netease.lowcode.core.annotation.NaslLogic; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 通用XML生成工具库 + */ +@Service +public class XmlGeneratorUtilsService { + + private static final Logger LCAP_LOGGER = LoggerFactory.getLogger("LCAP_EXTENSION_LOGGER"); + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + /** + * 将Map数据转换为XML字符串 + * + * @param data 包含数据的Map + * @param keyOrder Key 顺序列表 + * @param keyOrderByTag 嵌套节点的子节点顺序配置(可为空) + * @param rootTagName 根节点名称 (如: Certificate) + * @return XML字符串 + */ + @NaslLogic + public String mapToXml(Map data, List keyOrder, Map> keyOrderByTag, String rootTagName) { + if (data == null) { + LCAP_LOGGER.error("输入数据 Map 为空"); + return null; + } + if (rootTagName == null || rootTagName.isEmpty()) { + LCAP_LOGGER.error("根节点名称不能为空"); + return null; + } + + StringBuilder xml = new StringBuilder(); + xml.append("\n"); + + if (keyOrder != null && !keyOrder.isEmpty()) { + LCAP_LOGGER.info("正在按照 Key 顺序生成 XML: " + keyOrder); + + String pureTagName = rootTagName.trim().split("\\s+")[0]; + xml.append("<").append(rootTagName).append(">\n"); + + Map lowerKeyToOriginalKey = new HashMap<>(); + for (String key : data.keySet()) { + if (key == null) { + continue; + } + lowerKeyToOriginalKey.putIfAbsent(key.toLowerCase(), key); + } + + for (String key : keyOrder) { + if (key == null) { + continue; + } + String trimmedKey = key.trim(); + + String matchedKey = data.containsKey(trimmedKey) + ? trimmedKey + : lowerKeyToOriginalKey.get(trimmedKey.toLowerCase()); + if (matchedKey == null) { + continue; + } + String value = data.get(matchedKey); + appendTag(xml, trimmedKey, value, " ", keyOrderByTag, pureTagName + "/" + trimmedKey); + } + + xml.append("\n"); + } else { + LCAP_LOGGER.warn("Key 顺序列表为空,回退到默认 Map 遍历顺序。"); + String pureTagName = rootTagName.trim().split("\\s+")[0]; + appendTag(xml, rootTagName, data, "", keyOrderByTag, pureTagName); + } + + return xml.toString(); + } + + + private void appendTag(StringBuilder sb, String tagName, Object value, String indent, Map> keyOrderByTag, String path) { + String pureTagName = tagName.split("\\s+")[0]; + + if (value == null) { + sb.append(indent).append("<").append(tagName).append("/>\n"); + return; + } + + Object processingValue = value; + + // 尝试解析字符串是否为 JSON 对象或数组 + if (value instanceof String) { + String strVal = ((String) value).trim(); + if ((strVal.startsWith("{") && strVal.endsWith("}")) || (strVal.startsWith("[") && strVal.endsWith("]"))) { + try { + // 尝试反序列化为对象 (Map 或 List) + Object parsed = OBJECT_MAPPER.readValue(strVal, Object.class); + processingValue = parsed; + } catch (Exception e) { + // 解析失败,视为普通字符串 + // LCAP_LOGGER.warn("值看起来像 JSON 但解析失败: " + strVal); + } + } + } + + if (processingValue instanceof Map) { + sb.append(indent).append("<").append(tagName).append(">\n"); + Map map = (Map) processingValue; + + List childKeyOrder = null; + if (keyOrderByTag != null && !keyOrderByTag.isEmpty()) { + childKeyOrder = keyOrderByTag.get(pureTagName); + if ((childKeyOrder == null || childKeyOrder.isEmpty()) && path != null && !path.isEmpty()) { + childKeyOrder = keyOrderByTag.get(path); + } + } + + if (childKeyOrder != null && !childKeyOrder.isEmpty()) { + Map lowerKeyToOriginalKey = new HashMap<>(); + for (Object keyObj : map.keySet()) { + if (keyObj == null) { + continue; + } + String keyStr = keyObj.toString(); + lowerKeyToOriginalKey.putIfAbsent(keyStr.toLowerCase(), keyStr); + } + + for (String key : childKeyOrder) { + if (key == null) { + continue; + } + String trimmedKey = key.trim(); + if (trimmedKey.isEmpty()) { + continue; + } + + Object matchedKeyObj = map.containsKey(trimmedKey) ? trimmedKey : lowerKeyToOriginalKey.get(trimmedKey.toLowerCase()); + if (matchedKeyObj == null) { + continue; + } + Object childValue = map.get(matchedKeyObj); + appendTag(sb, trimmedKey, childValue, indent + " ", keyOrderByTag, pathJoin(path, trimmedKey)); + } + } else { + for (Map.Entry entry : map.entrySet()) { + if (entry.getKey() != null) { + appendTag(sb, entry.getKey().toString(), entry.getValue(), indent + " ", keyOrderByTag, pathJoin(path, entry.getKey().toString())); + } + } + } + sb.append(indent).append("\n"); + } else if (processingValue instanceof List) { + List list = (List) processingValue; + for (Object item : list) { + appendTag(sb, tagName, item, indent, keyOrderByTag, path); + } + } else { + sb.append(indent).append("<").append(tagName).append(">"); + String valStr = processingValue.toString(); + valStr = escapeXml(valStr); + sb.append(valStr); + sb.append("\n"); + } + } + + private String pathJoin(String base, String child) { + if (base == null || base.trim().isEmpty()) { + return child; + } + if (child == null || child.trim().isEmpty()) { + return base; + } + return base + "/" + child.trim(); + } + + private String escapeXml(String str) { + if (str == null) return ""; + return str.replace("&", "&") + .replace("<", "<") + .replace(">", ">") + .replace("\"", """) + .replace("'", "'"); + } + + public static void main(String[] args) { + XmlGeneratorUtilsService service = new XmlGeneratorUtilsService(); + + Map inputMap = new java.util.HashMap<>(); + inputMap.put("AplPromise", "{\"AplPromiseCode\":\"1\"}"); + inputMap.put("ModCertificate", null); + inputMap.put("CertificateList", "{\"Goods\":[{\"HSCode\":\"290219\",\"GoodsItemFlag\":\"N\",\"GoodsName\":\"甲基环己烷\"}]}"); + inputMap.put("CertificateHead", "{\"EntMgrNo\":\"333333053\",\"ApplyType\":\"0\",\"CertStatus\":\"0\",\"CertType\":\"C\",\"CertNo\":\"C251429459072741\"}"); + + List keyOrder = java.util.Arrays.asList("AplPromise", "ModCertificate", "CertificateHead", "CertificateList"); + + Map> keyOrderByTag = new java.util.HashMap<>(); + keyOrderByTag.put("CertificateHead", java.util.Arrays.asList("CertNo", "ApplyType", "CertStatus", "CertType", "EntMgrNo")); + keyOrderByTag.put("CertificateList", java.util.Arrays.asList("Goods")); + keyOrderByTag.put("Goods", java.util.Arrays.asList("GoodsItemFlag", "HSCode", "GoodsName")); + + String rootTagName = "Certificate xmlns=\"http://www.w3.org/2000/09/xmldsig#\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xsi:schemaLocation=\"http://www.w3.org/2000/09/xmldsig#Certificate.xsd\""; + + System.out.println("=== Input Map ==="); + System.out.println(inputMap); + + System.out.println("\n=== Generated XML ==="); + String xml = service.mapToXml(inputMap, keyOrder, keyOrderByTag, rootTagName); + System.out.println(xml); + } +} diff --git a/xml-generator-utils/src/main/java/com/yourcompany/xml/generator/utils/api/XmlParseService.java b/xml-generator-utils/src/main/java/com/yourcompany/xml/generator/utils/api/XmlParseService.java new file mode 100644 index 000000000..ac87fd7f9 --- /dev/null +++ b/xml-generator-utils/src/main/java/com/yourcompany/xml/generator/utils/api/XmlParseService.java @@ -0,0 +1,58 @@ +package com.yourcompany.xml.generator.utils.api; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.dataformat.xml.XmlMapper; +import com.netease.lowcode.core.annotation.NaslLogic; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +/** + * XML解析服务 + * 提供将XML字符串转换为Java对象的功能 + */ +@Service +public class XmlParseService { + + private static final Logger LCAP_LOGGER = LoggerFactory.getLogger("LCAP_EXTENSION_LOGGER"); + + private static final XmlMapper XML_MAPPER = new XmlMapper(); + + static { + // 配置忽略未知属性,增强容错性 + XML_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + // 配置属性名称不区分大小写 + XML_MAPPER.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true); + } + + /** + * 通用XML解析 (反序列化) + * 将 XML 字符串转换回指定类型的 Java Bean + * + * @param xmlContent XML字符串内容 + * @param clazz 目标类型的Class对象 + * @param 泛型类型 + * @return 转换后的Java对象 + */ + @NaslLogic + public T parseXml(String xmlContent, Class clazz) { + if (xmlContent == null) { + LCAP_LOGGER.error("XML解析失败: 输入内容为空"); + throw new RuntimeException("XML解析失败: 输入内容为空"); + } + + try { + return XML_MAPPER.readValue(xmlContent, clazz); + } catch (JsonProcessingException e) { + String errorMsg = String.format("XML解析失败: 无法将内容转换为类型 [%s]. 错误信息: %s", clazz.getName(), e.getMessage()); + LCAP_LOGGER.error(errorMsg, e); + throw new RuntimeException(errorMsg, e); + } catch (Exception e) { + String errorMsg = String.format("XML解析失败: 发生未知错误. 错误信息: %s", e.getMessage()); + LCAP_LOGGER.error(errorMsg, e); + throw new RuntimeException(errorMsg, e); + } + } +} diff --git a/xml-generator-utils/src/main/resources/META-INF/spring.factories b/xml-generator-utils/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..7de295136 --- /dev/null +++ b/xml-generator-utils/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +com.yourcompany.xml.generator.utils.XmlGeneratorUtilsBasicSpringEnvironmentConfiguration \ No newline at end of file