diff --git a/orion-ops-framework/orion-ops-spring-boot-starter-storage/pom.xml b/orion-ops-framework/orion-ops-spring-boot-starter-storage/pom.xml
new file mode 100644
index 00000000..02c192d1
--- /dev/null
+++ b/orion-ops-framework/orion-ops-spring-boot-starter-storage/pom.xml
@@ -0,0 +1,31 @@
+
+
+
+ com.orion.ops
+ orion-ops-framework
+ ${revision}
+
+
+ 4.0.0
+ orion-ops-spring-boot-starter-storage
+ ${project.artifactId}
+ jar
+
+ 项目存储层配置包
+ https://github.com/lijiahangmax/orion-ops-pro
+
+
+
+ com.orion.ops
+ orion-ops-common
+
+
+
+ org.springframework.boot
+ spring-boot-starter
+
+
+
+
\ No newline at end of file
diff --git a/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/java/com/orion/ops/framework/storage/config/OrionStorageAutoConfiguration.java b/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/java/com/orion/ops/framework/storage/config/OrionStorageAutoConfiguration.java
new file mode 100644
index 00000000..32a02c09
--- /dev/null
+++ b/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/java/com/orion/ops/framework/storage/config/OrionStorageAutoConfiguration.java
@@ -0,0 +1,32 @@
+package com.orion.ops.framework.storage.config;
+
+import com.orion.ops.framework.storage.core.client.FileClient;
+import com.orion.ops.framework.storage.core.client.local.LocalFileClient;
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Primary;
+
+/**
+ * 存储配置类
+ *
+ * @author Jiahang Li
+ * @version 1.0.0
+ * @since 2023/6/30 16:21
+ *
+ * TODO 后续添加 FAST MINIO OSS 等
+ */
+@AutoConfiguration
+@EnableConfigurationProperties(StorageConfig.class)
+public class OrionStorageAutoConfiguration {
+
+ /**
+ * 本地文件客户端
+ */
+ @Bean
+ @Primary
+ public FileClient localFileClient(StorageConfig config) {
+ return new LocalFileClient(config.getLocal());
+ }
+
+}
diff --git a/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/java/com/orion/ops/framework/storage/config/StorageConfig.java b/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/java/com/orion/ops/framework/storage/config/StorageConfig.java
new file mode 100644
index 00000000..b3c52bb1
--- /dev/null
+++ b/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/java/com/orion/ops/framework/storage/config/StorageConfig.java
@@ -0,0 +1,23 @@
+package com.orion.ops.framework.storage.config;
+
+import com.orion.ops.framework.storage.core.client.local.LocalFileClientConfig;
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+/**
+ * 存储配置
+ *
+ * @author Jiahang Li
+ * @version 1.0.0
+ * @since 2023/6/30 18:40
+ */
+@Data
+@ConfigurationProperties(prefix = "orion.storage")
+public class StorageConfig {
+
+ /**
+ * 本地文件客户端 配置
+ */
+ private LocalFileClientConfig local;
+
+}
diff --git a/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/java/com/orion/ops/framework/storage/core/client/AbstractFileClient.java b/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/java/com/orion/ops/framework/storage/core/client/AbstractFileClient.java
new file mode 100644
index 00000000..30ab0c93
--- /dev/null
+++ b/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/java/com/orion/ops/framework/storage/core/client/AbstractFileClient.java
@@ -0,0 +1,126 @@
+package com.orion.ops.framework.storage.core.client;
+
+import com.orion.lang.id.UUIds;
+import com.orion.lang.utils.io.Files1;
+import com.orion.lang.utils.io.Streams;
+import com.orion.ops.framework.common.meta.TraceIdHolder;
+
+import java.io.InputStream;
+
+/**
+ * 文件客户端 基类
+ *
+ * @author Jiahang Li
+ * @version 1.0.0
+ * @since 2023/6/30 17:24
+ */
+public abstract class AbstractFileClient implements FileClient {
+
+ protected Config config;
+
+ public AbstractFileClient(Config config) {
+ this.config = config;
+ }
+
+ @Override
+ public String upload(String path, byte[] content) throws Exception {
+ return this.doUpload(path, Streams.toInputStream(content), true, true);
+ }
+
+ @Override
+ public String upload(String path, byte[] content, boolean overrideIfExist) throws Exception {
+ return this.doUpload(path, Streams.toInputStream(content), true, overrideIfExist);
+ }
+
+ @Override
+ public String upload(String path, InputStream in) throws Exception {
+ return this.doUpload(path, in, false, true);
+ }
+
+ @Override
+ public String upload(String path, InputStream in, boolean autoClose) throws Exception {
+ return this.doUpload(path, in, autoClose, true);
+ }
+
+ @Override
+ public String upload(String path, InputStream in, boolean autoClose, boolean overrideIfExist) throws Exception {
+ return this.doUpload(path, in, autoClose, overrideIfExist);
+ }
+
+ @Override
+ public byte[] getContent(String path) throws Exception {
+ try (InputStream in = this.doDownload(path)) {
+ return Streams.toByteArray(in);
+ }
+ }
+
+ @Override
+ public InputStream getContentInputStream(String path) throws Exception {
+ return this.doDownload(path);
+ }
+
+ /**
+ * 执行上传操作
+ *
+ * @param path path
+ * @param in in
+ * @param autoClose autoClose
+ * @param overrideIfExist 文件存在是否覆盖
+ * @return path
+ * @throws Exception Exception
+ */
+ protected abstract String doUpload(String path, InputStream in, boolean autoClose, boolean overrideIfExist) throws Exception;
+
+ /**
+ * 执行下载操作
+ *
+ * @param path path
+ * @return stream
+ * @throws Exception Exception
+ */
+ protected abstract InputStream doDownload(String path) throws Exception;
+
+ /**
+ * 获取返回路径 用于客户端返回
+ *
+ * @param path path
+ * @return returnPath
+ */
+ protected abstract String getReturnPath(String path);
+
+ /**
+ * 获取实际存储路径 用于服务端的存储
+ *
+ * @param returnPath returnPath
+ * @return absolutePath
+ */
+ protected abstract String getAbsolutePath(String returnPath);
+
+ /**
+ * 获取文件路径 拼接前缀
+ *
+ * @param path 路径
+ * @return 文件名称
+ */
+ protected String getFilePath(String path) {
+ // 无需拼接
+ if (!config.isNameAppendTraceId()) {
+ return path;
+ }
+ // 名称前缀
+ String traceId = TraceIdHolder.get();
+ if (traceId == null) {
+ traceId = UUIds.random32();
+ }
+ String prefix = traceId + "_";
+ String name = Files1.getFileName(path);
+ // 只是文件名
+ if (name.equals(path)) {
+ return prefix + name;
+ }
+ // 包含路径
+ String parentPath = Files1.getParentPath(path);
+ return parentPath + prefix + name;
+ }
+
+}
diff --git a/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/java/com/orion/ops/framework/storage/core/client/FileClient.java b/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/java/com/orion/ops/framework/storage/core/client/FileClient.java
new file mode 100644
index 00000000..42375c15
--- /dev/null
+++ b/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/java/com/orion/ops/framework/storage/core/client/FileClient.java
@@ -0,0 +1,103 @@
+package com.orion.ops.framework.storage.core.client;
+
+import java.io.InputStream;
+
+/**
+ * 文件客户端
+ *
+ * @author Jiahang Li
+ * @version 1.0.0
+ * @since 2023/6/30 16:51
+ */
+public interface FileClient {
+
+ /**
+ * 上传文件
+ *
+ * @param path 文件路径
+ * @param content 文件内容
+ * @return 路径
+ * @throws Exception Exception
+ */
+ String upload(String path, byte[] content) throws Exception;
+
+ /**
+ * 上传文件
+ *
+ * @param path 文件路径
+ * @param content 文件内容
+ * @param overrideIfExist 文件存在是否覆盖
+ * @return 路径
+ * @throws Exception Exception
+ */
+ String upload(String path, byte[] content, boolean overrideIfExist) throws Exception;
+
+ /**
+ * 上传文件
+ *
+ * @param path 文件路径
+ * @param in in
+ * @return 路径
+ * @throws Exception Exception
+ */
+ String upload(String path, InputStream in) throws Exception;
+
+ /**
+ * 上传文件
+ *
+ * @param path 文件路径
+ * @param in in
+ * @param autoClose autoClose
+ * @return 路径
+ * @throws Exception Exception
+ */
+ String upload(String path, InputStream in, boolean autoClose) throws Exception;
+
+ /**
+ * 上传文件
+ *
+ * @param path 文件路径
+ * @param in in
+ * @param autoClose autoClose
+ * @param overrideIfExist 文件存在是否覆盖
+ * @return 路径
+ * @throws Exception Exception
+ */
+ String upload(String path, InputStream in, boolean autoClose, boolean overrideIfExist) throws Exception;
+
+ /**
+ * 检测文件是否存在
+ *
+ * @param path path
+ * @return 是否存在
+ */
+ boolean isExists(String path);
+
+ /**
+ * 删除文件
+ *
+ * @param path 路径
+ * @return 是否删除
+ * @throws Exception Exception
+ */
+ boolean delete(String path) throws Exception;
+
+ /**
+ * 获取文件内容
+ *
+ * @param path path
+ * @return bytes
+ * @throws Exception Exception
+ */
+ byte[] getContent(String path) throws Exception;
+
+ /**
+ * 获取文件输入流
+ *
+ * @param path path
+ * @return stream
+ * @throws Exception Exception
+ */
+ InputStream getContentInputStream(String path) throws Exception;
+
+}
diff --git a/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/java/com/orion/ops/framework/storage/core/client/FileClientConfig.java b/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/java/com/orion/ops/framework/storage/core/client/FileClientConfig.java
new file mode 100644
index 00000000..fb834d33
--- /dev/null
+++ b/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/java/com/orion/ops/framework/storage/core/client/FileClientConfig.java
@@ -0,0 +1,20 @@
+package com.orion.ops.framework.storage.core.client;
+
+import lombok.Data;
+
+/**
+ * 文件客户端配置 基类
+ *
+ * @author Jiahang Li
+ * @version 1.0.0
+ * @since 2023/6/30 17:21
+ */
+@Data
+public class FileClientConfig {
+
+ /**
+ * 是否自动拼接 traceId 前缀. 没有则使用 UUID
+ */
+ protected boolean nameAppendTraceId;
+
+}
diff --git a/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/java/com/orion/ops/framework/storage/core/client/local/LocalFileClient.java b/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/java/com/orion/ops/framework/storage/core/client/local/LocalFileClient.java
new file mode 100644
index 00000000..f4675309
--- /dev/null
+++ b/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/java/com/orion/ops/framework/storage/core/client/local/LocalFileClient.java
@@ -0,0 +1,71 @@
+package com.orion.ops.framework.storage.core.client.local;
+
+import com.orion.lang.utils.io.Files1;
+import com.orion.lang.utils.io.Streams;
+import com.orion.ops.framework.storage.core.client.AbstractFileClient;
+
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * 本地文件客户端
+ *
+ * @author Jiahang Li
+ * @version 1.0.0
+ * @since 2023/6/30 17:21
+ */
+public class LocalFileClient extends AbstractFileClient {
+
+ public LocalFileClient(LocalFileClientConfig config) {
+ super(config);
+ }
+
+ @Override
+ protected String doUpload(String path, InputStream in, boolean autoClose, boolean overrideIfExist) throws Exception {
+ // 获取返回文件路径
+ String returnPath = this.getReturnPath(path);
+ // 检测文件是否存在
+ if (!overrideIfExist && this.isExists(returnPath)) {
+ return returnPath;
+ }
+ // 获取实际文件路径
+ String absolutePath = this.getAbsolutePath(returnPath);
+ // 上传文件
+ try (OutputStream out = Files1.openOutputStreamFast(absolutePath)) {
+ Streams.transfer(in, out);
+ } finally {
+ if (autoClose) {
+ Streams.close(in);
+ }
+ }
+ return returnPath;
+ }
+
+ @Override
+ protected InputStream doDownload(String path) throws Exception {
+ return Files1.openInputStreamFast(this.getAbsolutePath(path));
+ }
+
+ @Override
+ public boolean isExists(String path) {
+ return Files1.isFile(this.getAbsolutePath(path));
+ }
+
+ @Override
+ public boolean delete(String path) {
+ return Files1.delete(this.getAbsolutePath(path));
+ }
+
+ @Override
+ protected String getReturnPath(String path) {
+ // 拼接前缀
+ return Files1.getPath(config.getBasePath() + "/" + this.getFilePath(path));
+ }
+
+ @Override
+ protected String getAbsolutePath(String returnPath) {
+ // 拼接存储路径
+ return Files1.getPath(config.getStoragePath() + "/" + returnPath);
+ }
+
+}
diff --git a/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/java/com/orion/ops/framework/storage/core/client/local/LocalFileClientConfig.java b/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/java/com/orion/ops/framework/storage/core/client/local/LocalFileClientConfig.java
new file mode 100644
index 00000000..874b3045
--- /dev/null
+++ b/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/java/com/orion/ops/framework/storage/core/client/local/LocalFileClientConfig.java
@@ -0,0 +1,32 @@
+package com.orion.ops.framework.storage.core.client.local;
+
+import com.orion.ops.framework.storage.core.client.FileClientConfig;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 本地文件客户端 配置
+ *
+ * @author Jiahang Li
+ * @version 1.0.0
+ * @since 2023/6/30 17:47
+ */
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class LocalFileClientConfig extends FileClientConfig {
+
+ /**
+ * 存储路径
+ *
+ * 无需 / 结尾
+ */
+ private String storagePath = "";
+
+ /**
+ * 基础路径
+ *
+ * 无需 / 结尾
+ */
+ private String basePath = "";
+
+}
diff --git a/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/resources/META-INF/spring-configuration-metadata.json b/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/resources/META-INF/spring-configuration-metadata.json
new file mode 100644
index 00000000..5816e482
--- /dev/null
+++ b/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/resources/META-INF/spring-configuration-metadata.json
@@ -0,0 +1,29 @@
+{
+ "groups": [
+ {
+ "name": "orion.storage",
+ "type": "com.orion.ops.framework.storage.config.StorageConfig",
+ "sourceType": "com.orion.ops.framework.storage.config.StorageConfig"
+ }
+ ],
+ "properties": [
+ {
+ "name": "orion.storage.local.nameAppendTraceId",
+ "type": "java.lang.Boolean",
+ "description": "是否自动拼接 traceId 前缀. 没有则使用 UUID.",
+ "defaultValue": false
+ },
+ {
+ "name": "orion.storage.local.storagePath",
+ "type": "java.lang.String",
+ "description": "存储路径.",
+ "defaultValue": ""
+ },
+ {
+ "name": "orion.storage.local.basePath",
+ "type": "java.lang.String",
+ "description": "基础路径.",
+ "defaultValue": ""
+ }
+ ]
+}
\ No newline at end of file
diff --git a/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 00000000..f75d8775
--- /dev/null
+++ b/orion-ops-framework/orion-ops-spring-boot-starter-storage/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+com.orion.ops.framework.storage.config.OrionStorageAutoConfiguration
\ No newline at end of file
diff --git a/orion-ops-framework/pom.xml b/orion-ops-framework/pom.xml
index 93aa07b0..7b9b0ddc 100644
--- a/orion-ops-framework/pom.xml
+++ b/orion-ops-framework/pom.xml
@@ -27,6 +27,7 @@
orion-ops-spring-boot-starter-redis
orion-ops-spring-boot-starter-desensitize
orion-ops-spring-boot-starter-log
+ orion-ops-spring-boot-starter-storage
\ No newline at end of file
diff --git a/orion-ops-launch/pom.xml b/orion-ops-launch/pom.xml
index ca671fe0..3181ff21 100644
--- a/orion-ops-launch/pom.xml
+++ b/orion-ops-launch/pom.xml
@@ -64,6 +64,11 @@
com.orion.ops
orion-ops-spring-boot-starter-log
+