From ccd87b6ac99259a01b9099a4d570f6d9e7dbb506 Mon Sep 17 00:00:00 2001
From: liding <756868258@qq.com>
Date: 星期五, 30 五月 2025 14:48:54 +0800
Subject: [PATCH] Merge branch 'master' of http://114.132.189.42:9002/r/zd-after into li

---
 ruoyi-common/src/main/java/com/ruoyi/basic/service/StorageBlobService.java                                   |   35 +
 ruoyi-common/src/main/java/com/ruoyi/basic/service/impl/StorageBlobServiceImpl.java                          |  105 +++++
 ruoyi-common/src/main/java/com/ruoyi/basic/entity/dto/StorageBlobDTO.java                                    |    9 
 ruoyi-admin/src/main/resources/application.yml.example                                                       |    9 
 ruoyi-common/src/main/java/com/ruoyi/common/constant/StorageAttachmentConstants.java                         |   17 
 ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java                              |   15 
 ruoyi-common/src/main/java/com/ruoyi/common/core/domain/MinioResult.java                                     |   16 
 ruoyi-admin/src/main/resources/db/beforeSql/postgresql/beforeSQL__sys.sql                                    |    4 
 pom.xml                                                                                                      |   24 +
 ruoyi-common/src/main/resources/mapper/StorageAttachmentMapper.xml                                           |   22 +
 ruoyi-common/src/main/resources/db/migration/postgresql/V20250525003447__create_table_storage_attachment.sql |   25 +
 ruoyi-common/src/main/java/com/ruoyi/common/enums/StorageAttachmentRecordType.java                           |   25 +
 ruoyi-common/src/main/java/com/ruoyi/basic/entity/StorageBlob.java                                           |   65 +++
 ruoyi-admin/src/main/java/com/ruoyi/PlusCodeGenerator.java                                                   |    6 
 ruoyi-common/src/main/java/com/ruoyi/basic/service/StorageAttachmentService.java                             |   43 ++
 ruoyi-common/src/main/java/com/ruoyi/basic/service/impl/StorageAttachmentServiceImpl.java                    |   92 ++++
 ruoyi-admin/pom.xml                                                                                          |    6 
 ruoyi-common/src/main/java/com/ruoyi/basic/mapper/StorageBlobMapper.java                                     |   18 
 ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MinioUtils.java                                       |  306 ++++++++++++++++
 ruoyi-common/src/main/java/com/ruoyi/basic/mapper/StorageAttachmentMapper.java                               |   18 
 ruoyi-common/src/main/java/com/ruoyi/common/config/MinioConfig.java                                          |   27 +
 ruoyi-common/src/main/resources/mapper/StorageBlobMapper.xml                                                 |   22 +
 ruoyi-common/pom.xml                                                                                         |   27 +
 /dev/null                                                                                                    |   25 -
 ruoyi-common/src/main/resources/db/migration/postgresql/V20250525003427__create_table_storage_blob.sql       |   24 +
 ruoyi-common/src/main/java/com/ruoyi/common/handler/MyMetaObjectHandler.java                                 |   43 ++
 ruoyi-common/src/main/java/com/ruoyi/basic/entity/StorageAttachment.java                                     |   73 +++
 ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml                                              |    4 
 28 files changed, 1,072 insertions(+), 33 deletions(-)

diff --git a/pom.xml b/pom.xml
index 78114f5..6f6211f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -37,6 +37,8 @@
         <postgresql.version>42.7.3</postgresql.version>
         <mybatis-plus.version>3.5.12</mybatis-plus.version>
         <freemarker.version>2.3.30</freemarker.version>
+        <minio.version>8.4.3</minio.version>
+        <okhttp.version>4.9.0</okhttp.version>
     </properties>
 
     <!-- 渚濊禆澹版槑 -->
@@ -72,7 +74,7 @@
                 <artifactId>pagehelper-spring-boot-starter</artifactId>
                 <version>${pagehelper.boot.version}</version>
             </dependency>
-            
+
             <dependency>
                 <groupId>com.mysql</groupId>
                 <artifactId>mysql-connector-j</artifactId>
@@ -181,6 +183,26 @@
                 <version>${postgresql.version}</version>
             </dependency>
 
+            <!-- minio -->
+            <dependency>
+                <groupId>io.minio</groupId>
+                <artifactId>minio</artifactId>
+                <version>${minio.version}</version>
+                <exclusions>
+                    <exclusion>
+                        <groupId>com.squareup.okhttp3</groupId>
+                        <artifactId>okhttp</artifactId>
+                    </exclusion>
+                </exclusions>
+            </dependency>
+
+            <!-- minio渚濊禆okhttp 涓嶇劧鎶ラ敊 -->
+            <dependency>
+                <groupId>com.squareup.okhttp3</groupId>
+                <artifactId>okhttp</artifactId>
+                <version>${okhttp.version}</version>
+            </dependency>
+
             <!--鍩虹妯″潡-->
             <dependency>
                 <groupId>com.ruoyi</groupId>
diff --git a/ruoyi-admin/pom.xml b/ruoyi-admin/pom.xml
index 22bf60f..559a649 100644
--- a/ruoyi-admin/pom.xml
+++ b/ruoyi-admin/pom.xml
@@ -93,6 +93,12 @@
             <version>2.3</version>
         </dependency>
 
+        <!-- minio渚濊禆okhttp 涓嶇劧鎶ラ敊 -->
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+        </dependency>
+
     </dependencies>
 
     <build>
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/PlusCodeGenerator.java b/ruoyi-admin/src/main/java/com/ruoyi/PlusCodeGenerator.java
index eeb12f5..9945e44 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/PlusCodeGenerator.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/PlusCodeGenerator.java
@@ -38,8 +38,8 @@
 
     public static void main(String[] args) {
         String projectPath = System.getProperty("user.dir"); // 鑾峰彇椤圭洰鏍硅矾寰�
-        String path = "basic-server"; // 妯″潡鍚嶇О
-        String table = "test"; // 琛ㄥ悕锛屽涓〃閫楀彿闅斿紑
+        String path = "ruoyi-common"; // 妯″潡鍚嶇О
+        String table = "storage_attachment"; // 琛ㄥ悕锛屽涓〃閫楀彿闅斿紑
 
         // 浠g爜杈撳嚭璺緞閰嶇疆
         String outputBasePath = Paths.get(projectPath, path, "src", "main", "java").toString();
@@ -48,7 +48,7 @@
         // 浠g爜鐢熸垚鏍稿績閰嶇疆
         FastAutoGenerator.create(DB_URL, DB_USERNAME, DB_PASSWORD)
                 .globalConfig(builder -> {
-                    builder.author("ruoyi") // 浣滆�呬俊鎭�
+                    builder.author("chen") // 浣滆�呬俊鎭�
                             .outputDir(outputBasePath) // 浠g爜杈撳嚭鐩綍
                             .dateType(DateType.ONLY_DATE) // 鏃ユ湡绫诲瀷
                             .commentDate("yyyy-MM-dd") // 娉ㄩ噴鏃ユ湡鏍煎紡
diff --git a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java
index b7fad0c..31fc707 100644
--- a/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java
+++ b/ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java
@@ -2,6 +2,9 @@
 
 import java.util.ArrayList;
 import java.util.List;
+
+import com.ruoyi.basic.service.StorageBlobService;
+import com.ruoyi.common.core.domain.R;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 import org.slf4j.Logger;
@@ -36,6 +39,9 @@
     private ServerConfig serverConfig;
 
     private static final String FILE_DELIMETER = ",";
+
+    @Autowired
+    private StorageBlobService  storageBlobService;
 
     /**
      * 閫氱敤涓嬭浇璇锋眰
@@ -133,6 +139,15 @@
     }
 
     /**
+     * minio閫氱敤涓婁紶璇锋眰锛堝涓級
+     */
+    @PostMapping("/minioUploads")
+    public R minioUploadFiles(List<MultipartFile> files, String bucketName) throws Exception
+    {
+        return R.ok(storageBlobService.updateStorageBlobs(files, bucketName));
+    }
+
+    /**
      * 鏈湴璧勬簮閫氱敤涓嬭浇
      */
     @GetMapping("/download/resource")
diff --git a/ruoyi-admin/src/main/resources/application.yml.example b/ruoyi-admin/src/main/resources/application.yml.example
index a173945..4ebf163 100644
--- a/ruoyi-admin/src/main/resources/application.yml.example
+++ b/ruoyi-admin/src/main/resources/application.yml.example
@@ -1,3 +1,12 @@
+minio:
+    endpoint: lunor.cn
+    port: 9000
+    secure: false
+    accessKey: admin
+    secretKey: Admin123!
+    preview-expiry: 24 # 棰勮鍦板潃榛樿24灏忔椂
+    default-bucket: ruoyi #  榛樿瀛樺偍妗�
+
 # 椤圭洰鐩稿叧閰嶇疆
 ruoyi:
   # 鍚嶇О
diff --git a/ruoyi-admin/src/main/resources/db/beforeSql/postgresql/beforeSQL__sys.sql b/ruoyi-admin/src/main/resources/db/beforeSql/postgresql/beforeSQL__sys.sql
index 346927a..1c42f2e 100644
--- a/ruoyi-admin/src/main/resources/db/beforeSql/postgresql/beforeSQL__sys.sql
+++ b/ruoyi-admin/src/main/resources/db/beforeSql/postgresql/beforeSQL__sys.sql
@@ -227,8 +227,8 @@
     component   VARCHAR(255) DEFAULT NULL,
     query       VARCHAR(255) DEFAULT NULL,
     route_name  VARCHAR(50)  DEFAULT '',
-    is_frame    INTEGER      DEFAULT 1,
-    is_cache    INTEGER      DEFAULT 0,
+    is_frame    VARCHAR(10)  DEFAULT 1,
+    is_cache    VARCHAR(10)  DEFAULT 0,
     menu_type   CHAR(1)      DEFAULT '',
     visible     CHAR(1)      DEFAULT '0',
     status      INTEGER      DEFAULT 0,
diff --git a/ruoyi-common/pom.xml b/ruoyi-common/pom.xml
index 518d06b..370b34d 100644
--- a/ruoyi-common/pom.xml
+++ b/ruoyi-common/pom.xml
@@ -131,6 +131,33 @@
             <version>3.5.12</version>
         </dependency>
 
+        <!-- minio -->
+        <dependency>
+            <groupId>io.minio</groupId>
+            <artifactId>minio</artifactId>
+        </dependency>
+
+        <!-- minio渚濊禆okhttp 涓嶇劧鎶ラ敊 -->
+        <dependency>
+            <groupId>com.squareup.okhttp3</groupId>
+            <artifactId>okhttp</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+        </dependency>
+
+
     </dependencies>
 
 </project>
\ No newline at end of file
diff --git a/ruoyi-common/src/main/java/com/ruoyi/basic/entity/StorageAttachment.java b/ruoyi-common/src/main/java/com/ruoyi/basic/entity/StorageAttachment.java
new file mode 100644
index 0000000..dddde62
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/basic/entity/StorageAttachment.java
@@ -0,0 +1,73 @@
+package com.ruoyi.basic.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.ruoyi.basic.entity.dto.StorageBlobDTO;
+import lombok.Data;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭� 瀹炰綋绫�
+ *
+ * @author ruoyi
+ * @date 2025-05-29
+ */
+@Data
+@TableName("storage_attachment")
+public class StorageAttachment implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     *
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /** 鍒涘缓鏃堕棿 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @TableField(fill = FieldFill.INSERT)
+    private Date createTime;
+
+    /** 鏇存柊鏃堕棿 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    @TableField(fill = FieldFill.INSERT_UPDATE)
+    private Date updateTime;
+
+    /**
+     * 閫昏緫鍒犻櫎
+     */
+    @TableField(value = "deleted")
+    private Long deleted;
+    /**
+     * 鍏宠仈鐨勮褰曠被鍨�
+     */
+    @TableField(value = "record_type")
+    private Long recordType;
+    /**
+     * 鍏宠仈鐨勮褰昳d
+     */
+    @TableField(value = "record_id")
+    private Long recordId;
+    /**
+     * 绫诲瀷鍚嶇О, 濡�: file, avatar (鍖哄垎鍚屼竴鏉¤褰曚笉鍚岀被鍨嬬殑闄勪欢)
+     */
+    @TableField(value = "name")
+    private String name;
+    /**
+     * 鍏宠仈storage_blob璁板綍id
+     */
+    @TableField(value = "storage_blob_id")
+    private Long storageBlobId;
+
+    private StorageBlobDTO storageBlobDTO;
+
+    public StorageAttachment(String fileType, Long recordType, Long recordId) {
+        this.name = fileType;
+        this.recordType = recordType;
+        this.recordId = recordId;
+    }
+}
\ No newline at end of file
diff --git a/ruoyi-common/src/main/java/com/ruoyi/basic/entity/StorageBlob.java b/ruoyi-common/src/main/java/com/ruoyi/basic/entity/StorageBlob.java
new file mode 100644
index 0000000..e793031
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/basic/entity/StorageBlob.java
@@ -0,0 +1,65 @@
+package com.ruoyi.basic.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+import com.ruoyi.common.core.domain.BaseEntity;
+
+import java.io.Serializable;
+import java.util.Date;
+
+/**
+ * 閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭� 瀹炰綋绫�
+ *
+ * @author ruoyi
+ * @date 2025-05-29
+ */
+@Data
+@TableName("storage_blob")
+public class StorageBlob implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     *
+     */
+    @TableId(value = "id", type = IdType.AUTO)
+    private Long id;
+
+    /** 鍒涘缓鏃堕棿 */
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+//    @TableField(fill = FieldFill.INSERT)
+    private Date createTime;
+
+    /**
+     * 璧勬簮id
+     */
+    @TableField(value = "key")
+    private String key;
+    /**
+     * 璧勬簮绫诲瀷锛屼緥濡侸PG鍥剧墖鐨勮祫婧愮被鍨嬩负image/jpg
+     */
+    @TableField(value = "content_type")
+    private String contentType;
+    /**
+     * 鍘熸枃浠跺悕
+     */
+    @TableField(value = "original_filename")
+    private String originalFilename;
+
+    /**
+     * 瀛樺偍妗朵腑
+     */
+    @TableField(value = "bucket_filename")
+    private String bucketFilename;
+    /**
+     * 瀛樺偍妗跺悕
+     */
+    @TableField(value = "bucket_name")
+    private String bucketName;
+    /**
+     * 璧勬簮灏哄(瀛楄妭)
+     */
+    @TableField(value = "byte_size")
+    private Long byteSize;
+}
\ No newline at end of file
diff --git a/ruoyi-common/src/main/java/com/ruoyi/basic/entity/dto/StorageBlobDTO.java b/ruoyi-common/src/main/java/com/ruoyi/basic/entity/dto/StorageBlobDTO.java
new file mode 100644
index 0000000..2ab3d2c
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/basic/entity/dto/StorageBlobDTO.java
@@ -0,0 +1,9 @@
+package com.ruoyi.basic.entity.dto;
+
+import com.ruoyi.basic.entity.StorageBlob;
+import lombok.Data;
+
+@Data
+public class StorageBlobDTO extends StorageBlob {
+    private String url;
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/basic/mapper/StorageAttachmentMapper.java b/ruoyi-common/src/main/java/com/ruoyi/basic/mapper/StorageAttachmentMapper.java
new file mode 100644
index 0000000..9216656
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/basic/mapper/StorageAttachmentMapper.java
@@ -0,0 +1,18 @@
+package com.ruoyi.basic.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.basic.entity.StorageAttachment;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * <p>
+ * 閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭� Mapper 鎺ュ彛
+ * </p>
+ *
+ * @author ruoyi
+ * @since 2025-05-29
+ */
+@Mapper
+public interface StorageAttachmentMapper extends BaseMapper<StorageAttachment> {
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/basic/mapper/StorageBlobMapper.java b/ruoyi-common/src/main/java/com/ruoyi/basic/mapper/StorageBlobMapper.java
new file mode 100644
index 0000000..a832c80
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/basic/mapper/StorageBlobMapper.java
@@ -0,0 +1,18 @@
+package com.ruoyi.basic.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.ruoyi.basic.entity.StorageBlob;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * <p>
+ * 閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭� Mapper 鎺ュ彛
+ * </p>
+ *
+ * @author ruoyi
+ * @since 2025-05-29
+ */
+@Mapper
+public interface StorageBlobMapper extends BaseMapper<StorageBlob> {
+
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/basic/service/StorageAttachmentService.java b/ruoyi-common/src/main/java/com/ruoyi/basic/service/StorageAttachmentService.java
new file mode 100644
index 0000000..bcf9d31
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/basic/service/StorageAttachmentService.java
@@ -0,0 +1,43 @@
+package com.ruoyi.basic.service;
+
+import com.ruoyi.basic.entity.StorageAttachment;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.ruoyi.common.constant.StorageAttachmentConstants;
+import com.ruoyi.common.enums.StorageAttachmentRecordType;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭� 鏈嶅姟绫�
+ * </p>
+ *
+ * @author ruoyi
+ * @since 2025-05-29
+ */
+public interface StorageAttachmentService extends IService<StorageAttachment> {
+    /**
+     * 鏌ヨ閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭�
+     * @param recordId 鍏宠仈璁板綍id
+     * @param recordType 鍏宠仈璁板綍绫诲瀷
+     * @param fileType 鏂囦欢绫诲瀷
+     * @return 鏂囦欢淇℃伅鍒楄〃
+     */
+    List<StorageAttachment> selectStorageAttachments(Long recordId, StorageAttachmentRecordType recordType, StorageAttachmentConstants fileType);
+
+    /**
+     * 淇濆瓨閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭�
+     * @param attachments 鏂囦欢淇℃伅鍒楄〃
+     * @param recordId 绠$悊璁板綍id
+     * @param recordType 鍏宠仈璁板綍绫诲瀷
+     * @param fileType 鏂囦欢绫诲瀷
+     */
+    public void saveStorageAttachment(List<StorageAttachment> attachments, Long recordId, StorageAttachmentRecordType recordType, StorageAttachmentConstants fileType);
+
+    /**
+     * 鍒犻櫎閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭�
+     * @param storageAttachment 鏂囦欢淇℃伅
+     * @return 鍒犻櫎缁撴灉
+     */
+    public int deleteStorageAttachment(StorageAttachment storageAttachment);
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/basic/service/StorageBlobService.java b/ruoyi-common/src/main/java/com/ruoyi/basic/service/StorageBlobService.java
new file mode 100644
index 0000000..b242c26
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/basic/service/StorageBlobService.java
@@ -0,0 +1,35 @@
+package com.ruoyi.basic.service;
+
+import com.ruoyi.basic.entity.StorageAttachment;
+import com.ruoyi.basic.entity.StorageBlob;
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.ruoyi.basic.entity.dto.StorageBlobDTO;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭� 鏈嶅姟绫�
+ * </p>
+ *
+ * @author ruoyi
+ * @since 2025-05-29
+ */
+public interface StorageBlobService extends IService<StorageBlob> {
+
+    /**
+     * 鏂囦欢涓婁紶鎺ュ彛
+     * @param files 鏂囦欢淇℃伅
+     * @param bucketName 瀛樺偍妗跺悕绉�
+     * @return 涓婁紶缁撴灉
+     */
+    List<StorageBlobDTO> updateStorageBlobs(List<MultipartFile> files, String bucketName);
+
+    /**
+     * 鎵归噺鍒犻櫎鏂囦欢
+     * @param attachment
+     * @return
+     */
+    public int deleteStorageBlobs(StorageAttachment attachment);
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/basic/service/impl/StorageAttachmentServiceImpl.java b/ruoyi-common/src/main/java/com/ruoyi/basic/service/impl/StorageAttachmentServiceImpl.java
new file mode 100644
index 0000000..d4e87bf
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/basic/service/impl/StorageAttachmentServiceImpl.java
@@ -0,0 +1,92 @@
+package com.ruoyi.basic.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.ruoyi.basic.entity.StorageAttachment;
+import com.ruoyi.basic.entity.StorageBlob;
+import com.ruoyi.basic.entity.dto.StorageBlobDTO;
+import com.ruoyi.basic.mapper.StorageAttachmentMapper;
+import com.ruoyi.basic.mapper.StorageBlobMapper;
+import com.ruoyi.basic.service.StorageAttachmentService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.basic.service.StorageBlobService;
+import com.ruoyi.common.constant.StorageAttachmentConstants;
+import com.ruoyi.common.enums.StorageAttachmentRecordType;
+import com.ruoyi.common.utils.file.MinioUtils;
+import io.minio.MinioClient;
+import org.springframework.beans.BeanUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import lombok.RequiredArgsConstructor;
+
+import java.util.List;
+
+/**
+ * <p>
+ * 閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭� 鏈嶅姟瀹炵幇绫�
+ * </p>
+ *
+ * @author ruoyi
+ * @since 2025-05-29
+ */
+@Service
+@RequiredArgsConstructor
+public class StorageAttachmentServiceImpl extends ServiceImpl<StorageAttachmentMapper, StorageAttachment> implements StorageAttachmentService {
+    @Autowired
+    private StorageBlobMapper storageBlobMapper;
+
+    @Autowired
+    private StorageAttachmentMapper storageAttachmentMapper;
+
+    @Autowired
+    private StorageBlobService storageBlobService;
+
+    @Autowired
+    private MinioUtils minioUtils;
+
+    @Override
+    public List<StorageAttachment> selectStorageAttachments(Long recordId, StorageAttachmentRecordType recordType, StorageAttachmentConstants fileType) {
+        List<StorageAttachment> storageAttachments = storageAttachmentMapper.selectList(new LambdaQueryWrapper<StorageAttachment>()
+                .eq(StorageAttachment::getRecordId, recordId)
+                .eq(StorageAttachment::getRecordType, recordType.ordinal())
+                .eq(StorageAttachment::getName, fileType.toString()));
+        if (storageAttachments != null) {
+            for (StorageAttachment storageAttachment : storageAttachments) {
+                StorageBlob storageBlob = storageBlobMapper.selectById(storageAttachment.getStorageBlobId());
+                StorageBlobDTO storageBlobDTO = new StorageBlobDTO();
+                BeanUtils.copyProperties(storageBlob, storageBlobDTO);
+                storageBlobDTO.setUrl(minioUtils.getPreviewUrl(storageBlob.getBucketName(), storageBlob.getBucketName(), true));
+                storageAttachment.setStorageBlobDTO(storageBlobDTO);
+            }
+        }
+
+        return storageAttachments;
+    }
+
+    @Override
+    public void saveStorageAttachment(List<StorageAttachment> attachments, Long recordId, StorageAttachmentRecordType recordType, StorageAttachmentConstants fileType) {
+        // 鍒犻櫎鏃у浘
+        deleteStorageAttachment(new StorageAttachment(fileType.toString(), (long) recordType.ordinal(), recordId));
+        for (StorageAttachment attachment : attachments) {
+            // 鑾峰彇鍏宠仈璁板綍
+            StorageBlob storageBlob = attachment.getStorageBlobDTO();
+            attachment.setName(fileType.toString());
+            attachment.setRecordType((long) recordType.ordinal());
+            attachment.setRecordId(recordId);
+            attachment.setStorageBlobId(storageBlob.getId());
+            storageAttachmentMapper.insert(attachment);
+        }
+
+    }
+
+    @Override
+    public int deleteStorageAttachment(StorageAttachment storageAttachment) {
+        // 鍏堝垹闄ゆ槑缁嗚〃
+        storageBlobService.deleteStorageBlobs(storageAttachment);
+
+
+        return storageAttachmentMapper.delete(new LambdaQueryWrapper<StorageAttachment>()
+                .eq(StorageAttachment::getRecordId, storageAttachment.getRecordId())
+                .eq(StorageAttachment::getRecordType, storageAttachment.getRecordType())
+                .eq(StorageAttachment::getName, storageAttachment.getName()));
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/basic/service/impl/StorageBlobServiceImpl.java b/ruoyi-common/src/main/java/com/ruoyi/basic/service/impl/StorageBlobServiceImpl.java
new file mode 100644
index 0000000..dc7acff
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/basic/service/impl/StorageBlobServiceImpl.java
@@ -0,0 +1,105 @@
+package com.ruoyi.basic.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
+import com.ruoyi.basic.entity.StorageAttachment;
+import com.ruoyi.basic.entity.StorageBlob;
+import com.ruoyi.basic.entity.dto.StorageBlobDTO;
+import com.ruoyi.basic.mapper.StorageAttachmentMapper;
+import com.ruoyi.basic.mapper.StorageBlobMapper;
+import com.ruoyi.basic.service.StorageBlobService;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.ruoyi.common.config.MinioConfig;
+import com.ruoyi.common.constant.Constants;
+import com.ruoyi.common.core.domain.MinioResult;
+import com.ruoyi.common.exception.file.InvalidExtensionException;
+import com.ruoyi.common.utils.DateUtils;
+import com.ruoyi.common.utils.StringUtils;
+import com.ruoyi.common.utils.file.MinioUtils;
+import com.ruoyi.common.utils.uuid.IdUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import lombok.RequiredArgsConstructor;
+import org.springframework.web.multipart.MultipartFile;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <p>
+ * 閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭� 鏈嶅姟瀹炵幇绫�
+ * </p>
+ *
+ * @author ruoyi
+ * @since 2025-05-29
+ */
+@Service
+@RequiredArgsConstructor
+public class StorageBlobServiceImpl extends ServiceImpl<StorageBlobMapper, StorageBlob> implements StorageBlobService {
+    @Autowired
+    private StorageAttachmentMapper storageAttachmentMapper;
+
+    @Autowired
+    private StorageBlobMapper storageBlobMapper;
+
+    @Autowired
+    private MinioUtils minioUtils;
+
+    @Override
+    public List<StorageBlobDTO> updateStorageBlobs(List<MultipartFile> files, String bucketName) {
+
+        // 鑻ユ病浼犲叆bucketName锛屽垯浣跨敤榛樿bucketName
+        if (StringUtils.isEmpty(bucketName)) {
+            bucketName  = minioUtils.getDefaultBucket();
+        }
+
+        List<StorageBlobDTO> storageBlobDTOs = new ArrayList<>();
+        for (MultipartFile file : files) {
+            try {
+                MinioResult res = minioUtils.upload(bucketName, file, false);
+                StorageBlobDTO dto = new StorageBlobDTO();
+                dto.setContentType(file.getContentType());
+                dto.setBucketFilename(res.getBucketFileName());
+                dto.setOriginalFilename(res.getOriginalName());
+                dto.setByteSize(file.getSize());
+                dto.setKey(IdUtils.simpleUUID());
+                dto.setBucketName(bucketName);
+                dto.setCreateTime(DateUtils.getNowDate());
+                dto.setUrl(minioUtils.getPreviewUrl(res.getBucketFileName(), bucketName, false));
+                // 鎻掑叆鏁版嵁搴�
+                storageBlobMapper.insert(dto);
+
+                storageBlobDTOs.add(dto);
+            } catch (InvalidExtensionException e) {
+                throw new RuntimeException("minio鏂囦欢涓婁紶寮傚父锛�" +  e);
+            }
+        }
+
+
+        return storageBlobDTOs;
+    }
+
+    @Override
+    public int deleteStorageBlobs(StorageAttachment attachment) {
+        List<StorageAttachment> attachments = storageAttachmentMapper.selectList(new LambdaQueryWrapper<StorageAttachment>()
+                .eq(StorageAttachment::getRecordId, attachment.getRecordId())
+                .eq(StorageAttachment::getRecordType, attachment.getRecordType())
+                .eq(StorageAttachment::getName, attachment.getName()));
+        List<Long> ids = attachments.stream().map(StorageAttachment::getStorageBlobId).toList();
+        List<StorageBlob> storageBlobs = storageBlobMapper.selectList(new LambdaQueryWrapper<StorageBlob>()
+                .in(StorageBlob::getId, ids));
+        if (!storageBlobs.isEmpty()) {
+            for (StorageBlob storageBlob : storageBlobs) {
+                // 绉婚櫎妗跺唴鏂囦欢
+                minioUtils.removeObjectsResult(storageBlob.getBucketName(), storageBlob.getBucketName());
+            }
+        }
+
+        if (!ids.isEmpty()) {
+            return storageBlobMapper.delete(new QueryWrapper<StorageBlob>().lambda().in(StorageBlob::getId, ids));
+        }
+
+        return 0;
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/config/MinioConfig.java b/ruoyi-common/src/main/java/com/ruoyi/common/config/MinioConfig.java
new file mode 100644
index 0000000..28f489f
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/config/MinioConfig.java
@@ -0,0 +1,27 @@
+package com.ruoyi.common.config;
+
+import io.minio.MinioClient;
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.stereotype.Component;
+
+@Configuration
+@Component
+@ConfigurationProperties(prefix = "minio")
+@Data
+public class MinioConfig {
+    private String endpoint;
+    private int port;
+    private String accessKey;
+    private String secretKey;
+    private Boolean secure;
+
+    @Bean
+    public MinioClient getMinioClient() {
+        return MinioClient.builder().endpoint(endpoint, port, secure)
+                .credentials(accessKey, secretKey)
+                .build();
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/constant/StorageAttachmentConstants.java b/ruoyi-common/src/main/java/com/ruoyi/common/constant/StorageAttachmentConstants.java
new file mode 100644
index 0000000..1bc8123
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/constant/StorageAttachmentConstants.java
@@ -0,0 +1,17 @@
+package com.ruoyi.common.constant;
+
+/**
+ * 闄勪欢甯搁噺
+ */
+public class StorageAttachmentConstants {
+
+    /**
+     * 鏂囦欢
+     */
+    public static final String StorageAttachmentFile = "file";
+
+    /**
+     * 鍥剧墖
+     */
+    public static final String StorageAttachmentImage = "image";
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/MinioResult.java b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/MinioResult.java
new file mode 100644
index 0000000..ebb29a9
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/core/domain/MinioResult.java
@@ -0,0 +1,16 @@
+package com.ruoyi.common.core.domain;
+
+
+import lombok.Data;
+
+@Data
+public class MinioResult {
+    // minio涓殑鏂囦欢鍚嶇О
+    private String bucketFileName;
+
+    // 婧愭枃浠跺悕绉�
+    private String originalName;
+
+    // 棰勮璺緞
+    private String previewExpiry;
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/enums/StorageAttachmentRecordType.java b/ruoyi-common/src/main/java/com/ruoyi/common/enums/StorageAttachmentRecordType.java
new file mode 100644
index 0000000..8b0597e
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/enums/StorageAttachmentRecordType.java
@@ -0,0 +1,25 @@
+package com.ruoyi.common.enums;
+
+import lombok.AllArgsConstructor;
+
+/**
+ * 闄勪欢璁板綍绫诲瀷鏋氫妇
+ *
+ */
+@AllArgsConstructor
+public enum StorageAttachmentRecordType {
+    // 渚嬪瓙 瀹為檯寮�鍙戣鍒犻櫎
+    Template("Template","鑼冧緥");
+
+
+    private final String code;
+    private final String info;
+
+    public String getCode() {
+        return code;
+    }
+
+    public String getInfo() {
+        return info;
+    }
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/handler/MyMetaObjectHandler.java b/ruoyi-common/src/main/java/com/ruoyi/common/handler/MyMetaObjectHandler.java
new file mode 100644
index 0000000..46811dd
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/handler/MyMetaObjectHandler.java
@@ -0,0 +1,43 @@
+package com.ruoyi.common.handler;
+
+import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
+import org.apache.ibatis.reflection.MetaObject;
+import org.springframework.stereotype.Component;
+
+import java.time.LocalDateTime;
+
+/**
+ * @Author: zhangxy
+ * @Date: 2020-08-05 14:40
+ */
+@Component
+public class MyMetaObjectHandler implements MetaObjectHandler {
+
+
+	@Override
+	public void insertFill(MetaObject metaObject) {
+		this.setFieldValByName("createTime", LocalDateTime.now(), metaObject);
+		this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
+		// 寮�绾跨▼锛屽彇涓嶅埌user
+		try {
+//			SysUser currentUser = SecurityUtils.getLoginUser();
+//			if (currentUser != null) {
+//				this.setFieldValByName("createUser", currentUser.getUsername(), metaObject);
+//				this.setFieldValByName("updateUser", currentUser.getUsername(), metaObject);
+//			}
+		} catch (Exception e) {
+		}
+	}
+
+	@Override
+	public void updateFill(MetaObject metaObject) {
+		this.setFieldValByName("updateTime", LocalDateTime.now(), metaObject);
+		try {
+//			ZttUser currentUser = SecurityUtils.getUser();
+//			if (currentUser != null) {
+//				this.setFieldValByName("updateUser", currentUser.getUsername(), metaObject);
+//			}
+		} catch (Exception e) {
+		}
+	}
+}
diff --git a/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MinioUtils.java b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MinioUtils.java
new file mode 100644
index 0000000..4b87a2e
--- /dev/null
+++ b/ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MinioUtils.java
@@ -0,0 +1,306 @@
+package com.ruoyi.common.utils.file;
+
+import com.baomidou.mybatisplus.core.toolkit.IdWorker;
+import com.baomidou.mybatisplus.core.toolkit.StringUtils;
+import com.ruoyi.common.core.domain.MinioResult;
+import com.ruoyi.common.exception.UtilException;
+import com.ruoyi.common.exception.file.InvalidExtensionException;
+import io.minio.*;
+import io.minio.http.Method;
+import io.minio.messages.DeleteError;
+import io.minio.messages.DeleteObject;
+import lombok.Getter;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+import org.springframework.util.FastByteArrayOutputStream;
+import org.springframework.web.multipart.MultipartFile;
+
+import jakarta.servlet.ServletOutputStream;
+import jakarta.servlet.http.HttpServletResponse;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+
+@Component
+public class MinioUtils {
+    @Autowired
+    private MinioClient minioClient;
+
+    @Value("${minio.preview-expiry}")
+    private Integer previewExpiry;
+
+    /**
+     * -- GETTER --
+     *  鑾峰彇榛樿瀛樺偍妗跺悕绉�
+     *
+     * @return
+     */
+    @Getter
+    @Value("${minio.default-bucket}")
+    private String defaultBucket;
+
+    /**
+     * 鍒ゆ柇瀛樺偍妗舵槸鍚﹀瓨鍦紝涓嶅瓨鍦ㄥ垯鍒涘缓
+     *
+     * @param bucketName 瀛樺偍妗跺悕绉�
+     */
+    public void existBucket(String bucketName) {
+        try {
+            boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
+            if (!exists) {
+                minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * 鍒涘缓瀛樺偍妗�
+     *
+     * @param bucketName 瀛樺偍妗跺悕绉�
+     * @return 鏄惁鍒涘缓鎴愬姛
+     */
+    public Boolean makeBucket(String bucketName) {
+        try {
+            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 鍒犻櫎瀛樺偍妗�
+     *
+     * @param bucketName 瀛樺偍妗跺悕绉�
+     * @return 鏄惁鍒犻櫎鎴愬姛
+     */
+    public Boolean removeBucket(String bucketName) {
+        try {
+            minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 鍒ゆ柇瀵硅薄鏄惁瀛樺湪
+     *
+     * @param bucketName 瀛樺偍妗跺悕绉�
+     * @param originalFileName MinIO涓瓨鍌ㄥ璞″叏璺緞
+     * @return 瀵硅薄鏄惁瀛樺湪
+     */
+    public boolean existObject(String bucketName, String originalFileName) {
+        try {
+            minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(originalFileName).build());
+        } catch (Exception e) {
+            e.printStackTrace();
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * 鏂囦欢涓婁紶
+     *
+     * @param bucketName 瀛樺偍妗跺悕绉�
+     * @param file       鏂囦欢
+     * @return 妗朵腑浣嶇疆
+     */
+    public MinioResult upload(String bucketName, MultipartFile file, Boolean isPreviewExpiry) throws InvalidExtensionException {
+        MultipartFile[] fileArr = {file};
+        List<MinioResult> fileNames = upload(bucketName, fileArr, isPreviewExpiry);
+        return fileNames.isEmpty() ? null : fileNames.get(0);
+    }
+
+    /**
+     * 涓婁紶鏂囦欢
+     *
+     * @param bucketName 瀛樺偍妗跺悕绉�
+     * @param fileList   鏂囦欢鍒楄〃
+     * @return 妗朵腑浣嶇疆鍒楄〃
+     */
+    public List<MinioResult> upload(String bucketName, List<MultipartFile> fileList, Boolean isPreviewExpiry) throws InvalidExtensionException {
+        MultipartFile[] fileArr = fileList.toArray(new MultipartFile[0]);
+        return upload(bucketName, fileArr, isPreviewExpiry);
+    }
+
+    /**
+     * description: 涓婁紶鏂囦欢
+     *
+     * @param bucketName 瀛樺偍妗跺悕绉�
+     * @param fileArr    鏂囦欢鍒楄〃
+     * @return 妗朵腑浣嶇疆鍒楄〃
+     */
+    public List<MinioResult> upload(String bucketName, MultipartFile[] fileArr, Boolean isPreviewExpiry) throws InvalidExtensionException {
+        for (MultipartFile file : fileArr) {
+            FileUploadUtils.assertAllowed(file, MimeTypeUtils.DEFAULT_ALLOWED_EXTENSION);
+        }
+        // 淇濊瘉妗朵竴瀹氬瓨鍦�
+        existBucket(bucketName);
+        // 鎵ц姝e父鎿嶄綔
+        List<MinioResult> bucketFileNames = new ArrayList<>(fileArr.length);
+        for (MultipartFile file : fileArr) {
+            // 鑾峰彇鍘熷鏂囦欢鍚嶇О
+            String originalFileName = file.getOriginalFilename();
+            // 鑾峰彇褰撳墠鏃ユ湡锛屾牸寮忎緥濡傦細2020-11
+            String datePath = new SimpleDateFormat("yyyy-MM").format(new Date());
+            // 鏂囦欢鍚嶇О
+            String uuid = IdWorker.get32UUID();
+            // 鑾峰彇鏂囦欢鍚庣紑
+            String suffix = originalFileName.substring(originalFileName.lastIndexOf("."));
+            String bucketFilePath = datePath + "/" + uuid + suffix;
+
+            // 鎺ㄩ�佹枃浠跺埌MinIO
+            try (InputStream in = file.getInputStream()) {
+                minioClient.putObject(PutObjectArgs.builder()
+                        .bucket(bucketName)
+                        .object(bucketFilePath)
+                        .stream(in, in.available(), -1)
+                        .contentType(file.getContentType())
+                        .build()
+                );
+            } catch (Exception e) {
+                throw new UtilException("MinioUtils锛氫笂浼犳枃浠跺伐鍏风被寮傚父:" + e);
+            }
+            MinioResult minioResult = new MinioResult();
+            minioResult.setBucketFileName(bucketFilePath);
+            // 杩斿洖姘镐箙棰勮鍦板潃
+            if (isPreviewExpiry) {
+                String previewUrl = getPreviewUrl(bucketFilePath, bucketName, isPreviewExpiry);
+                minioResult.setPreviewExpiry(previewUrl);
+            }
+            minioResult.setOriginalName(originalFileName);
+            bucketFileNames.add(minioResult);
+        }
+        return bucketFileNames;
+    }
+
+    /**
+     * 鏂囦欢涓嬭浇
+     *
+     * @param bucketName       瀛樺偍妗跺悕绉�
+     * @param bucketFileName   妗朵腑鏂囦欢鍚嶇О
+     * @param originalFileName 鍘熷鏂囦欢鍚嶇О
+     * @param response         response瀵硅薄
+     */
+    public void download(String bucketName, String bucketFileName, String originalFileName, HttpServletResponse response) {
+        GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(bucketName).object(bucketFileName).build();
+        try (GetObjectResponse objResponse = minioClient.getObject(objectArgs)) {
+            byte[] buf = new byte[1024];
+            int len;
+            try (FastByteArrayOutputStream os = new FastByteArrayOutputStream()) {
+                while ((len = objResponse.read(buf)) != -1) {
+                    os.write(buf, 0, len);
+                }
+                os.flush();
+                byte[] bytes = os.toByteArray();
+                response.setCharacterEncoding("utf-8");
+                //璁剧疆寮哄埗涓嬭浇涓嶆墦寮�
+                response.setContentType("application/force-download");
+                // 璁剧疆闄勪欢鍚嶇О缂栫爜
+                originalFileName = new String(originalFileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
+                // 璁剧疆闄勪欢鍚嶇О
+                response.addHeader("Content-Disposition", "attachment;fileName=" + originalFileName);
+                // 鍐欏叆鏂囦欢
+                try (ServletOutputStream stream = response.getOutputStream()) {
+                    stream.write(bytes);
+                    stream.flush();
+                }
+            }
+        } catch (Exception e) {
+            throw new UtilException("MinioUtils锛氫笂浼犳枃浠跺伐鍏风被寮傚父");
+        }
+    }
+
+    /**
+     * 鑾峰彇宸蹭笂浼犲璞$殑鏂囦欢娴侊紙鍚庣鍥犱负涓氬姟闇�瑕佽幏鍙栨枃浠舵祦鍙互璋冪敤璇ユ柟娉曪級
+     *
+     * @param bucketName     瀛樺偍妗跺悕绉�
+     * @param bucketFileName 妗朵腑鏂囦欢鍚嶇О
+     * @return 鏂囦欢娴�
+     */
+    public InputStream getFileStream(String bucketName, String bucketFileName) throws Exception {
+        GetObjectArgs objectArgs = GetObjectArgs.builder().bucket(bucketName).object(bucketFileName).build();
+        return minioClient.getObject(objectArgs);
+    }
+
+    /**
+     * 鎵归噺鍒犻櫎鏂囦欢瀵硅薄缁撴灉
+     *
+     * @param bucketName      瀛樺偍妗跺悕绉�
+     * @param bucketFileName 妗朵腑鏂囦欢鍚嶇О
+     * @return 鍒犻櫎缁撴灉
+     */
+    public DeleteError removeObjectsResult(String bucketName, String bucketFileName) {
+        List<DeleteError> results = removeObjectsResult(bucketName, Collections.singletonList(bucketFileName));
+        return !results.isEmpty() ? results.get(0) : null;
+    }
+
+    /**
+     * 鎵归噺鍒犻櫎鏂囦欢瀵硅薄缁撴灉
+     *
+     * @param bucketName      瀛樺偍妗跺悕绉�
+     * @param bucketFileNames 妗朵腑鏂囦欢鍚嶇О闆嗗悎
+     * @return 鍒犻櫎缁撴灉
+     */
+    public List<DeleteError> removeObjectsResult(String bucketName, List<String> bucketFileNames) {
+        Iterable<Result<DeleteError>> results = removeObjects(bucketName, bucketFileNames);
+        List<DeleteError> res = new ArrayList<>();
+        for (Result<DeleteError> result : results) {
+            try {
+                res.add(result.get());
+            } catch (Exception e) {
+                throw new UtilException("MinioUtils锛氫笂浼犳枃浠跺伐鍏风被寮傚父");
+            }
+        }
+        return res;
+    }
+
+    /**
+     * 鎵归噺鍒犻櫎鏂囦欢瀵硅薄
+     *
+     * @param bucketName      瀛樺偍妗跺悕绉�
+     * @param bucketFileNames 妗朵腑鏂囦欢鍚嶇О闆嗗悎
+     */
+    private Iterable<Result<DeleteError>> removeObjects(String bucketName, List<String> bucketFileNames) {
+        List<DeleteObject> dos = bucketFileNames.stream().map(DeleteObject::new).collect(Collectors.toList());
+        return minioClient.removeObjects(RemoveObjectsArgs.builder().bucket(bucketName).objects(dos).build());
+    }
+
+    /**
+     * 鏌ヨ棰勮url
+     * @param bucketFileName minio鏂囦欢鍚嶇О
+     * @param bucketName 瀛樺偍妗跺悕绉�
+     * @param isPreviewExpiry 鏄惁闇�瑕佽繃鏈熸椂闂� 榛樿24灏忔椂
+     * @return
+     */
+    public String getPreviewUrl(String bucketFileName, String bucketName, Boolean isPreviewExpiry) {
+        if (StringUtils.isNotBlank(bucketFileName)) {
+            try {
+                minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(bucketFileName).build());
+                // 涓篺alse鍙敓鎴�24灏忔椂鏈夋晥鏃堕暱鐨剈rl閾炬帴锛屽彲浠ヨ闂鏂囦欢
+                if (isPreviewExpiry){
+                    return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().method(Method.GET).bucket(bucketName).object(bucketFileName).build());
+                }else {
+                    return minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().method(Method.GET).bucket(bucketName).object(bucketFileName).expiry(previewExpiry, TimeUnit.HOURS).build());
+                }
+            } catch (Exception e) {
+                throw new UtilException("MinioUtils锛氫笂浼犳枃浠跺伐鍏风被寮傚父");
+            }
+        }
+        return null;
+    }
+
+}
diff --git a/ruoyi-common/src/main/resources/db/migration/postgresql/V20250525003427__create_table_storage_blob.sql b/ruoyi-common/src/main/resources/db/migration/postgresql/V20250525003427__create_table_storage_blob.sql
new file mode 100644
index 0000000..d1ad350
--- /dev/null
+++ b/ruoyi-common/src/main/resources/db/migration/postgresql/V20250525003427__create_table_storage_blob.sql
@@ -0,0 +1,24 @@
+DROP TABLE IF EXISTS storage_blob;
+
+CREATE TABLE storage_blob
+(
+    id                bigserial PRIMARY KEY,
+    create_time        timestamp    default now() NOT NULL,
+    key               varchar(150) DEFAULT ''    NOT NULL,
+    content_type      varchar(100) DEFAULT ''    NOT NULL,
+    original_filename varchar(255) DEFAULT ''    NOT NULL,
+    bucket_filename   varchar(255) DEFAULT ''    NOT NULL,
+    bucket_name       varchar(255) DEFAULT ''    NOT NULL,
+    byte_size         bigint       DEFAULT 0     NOT NULL,
+    UNIQUE (key)
+);
+
+COMMENT ON TABLE storage_blob IS '閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭�';
+
+COMMENT ON COLUMN storage_blob.key IS '璧勬簮id';
+COMMENT ON COLUMN storage_blob.content_type IS '璧勬簮绫诲瀷锛屼緥濡侸PG鍥剧墖鐨勮祫婧愮被鍨嬩负image/jpg';
+COMMENT ON COLUMN storage_blob.original_filename IS '鍘熸枃浠跺悕绉�';
+COMMENT ON COLUMN storage_blob.bucket_filename IS '瀛樺偍妗朵腑鏂囦欢鍚�';
+COMMENT ON COLUMN storage_blob.bucket_name IS '瀛樺偍妗跺悕';
+COMMENT ON COLUMN storage_blob.byte_size IS '璧勬簮灏哄(瀛楄妭)';
+
diff --git a/ruoyi-common/src/main/resources/db/migration/postgresql/V20250525003447__create_table_storage_attachment.sql b/ruoyi-common/src/main/resources/db/migration/postgresql/V20250525003447__create_table_storage_attachment.sql
new file mode 100644
index 0000000..98a73de
--- /dev/null
+++ b/ruoyi-common/src/main/resources/db/migration/postgresql/V20250525003447__create_table_storage_attachment.sql
@@ -0,0 +1,25 @@
+-- ----------------------------
+-- 闄勪欢琛�
+-- ----------------------------
+drop table if exists storage_attachment;
+CREATE TABLE storage_attachment
+(
+    id              bigserial PRIMARY KEY,
+    create_time     timestamp    default now() NOT NULL,
+    update_time     timestamp    default now() NOT NULL,
+    deleted         bigint       DEFAULT 0     NOT NULL,
+    record_type     smallint     DEFAULT 0     NOT NULL,
+    record_id       bigint       DEFAULT 0     NOT NULL,
+    name            varchar(100) DEFAULT ''    NOT NULL,
+    storage_blob_id bigint       DEFAULT 0     NOT NULL
+);
+
+COMMENT ON TABLE storage_attachment IS '閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭�';
+
+COMMENT ON COLUMN storage_attachment.record_type IS '鍏宠仈鐨勮褰曠被鍨�';
+COMMENT ON COLUMN storage_attachment.record_id IS '鍏宠仈鐨勮褰昳d';
+COMMENT ON COLUMN storage_attachment.name IS '鍚嶇О, 濡�: file, avatar (鍖哄垎鍚屼竴鏉¤褰曚笉鍚岀被鍨嬬殑闄勪欢)';
+COMMENT ON COLUMN storage_attachment.storage_blob_id IS '鍏宠仈storage_blob璁板綍id';
+
+CREATE INDEX idx_storage_attachment_on_record
+    ON storage_attachment (record_type, record_id);
\ No newline at end of file
diff --git a/ruoyi-common/src/main/resources/mapper/StorageAttachmentMapper.xml b/ruoyi-common/src/main/resources/mapper/StorageAttachmentMapper.xml
new file mode 100644
index 0000000..60830e9
--- /dev/null
+++ b/ruoyi-common/src/main/resources/mapper/StorageAttachmentMapper.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.basic.mapper.StorageAttachmentMapper">
+
+        <!-- 閫氱敤鏌ヨ鏄犲皠缁撴灉 -->
+        <resultMap id="BaseResultMap" type="com.ruoyi.basic.entity.StorageAttachment">
+                    <id column="id" property="id" />
+                    <result column="create_time" property="createTime" />
+                    <result column="update_time" property="updateTime" />
+                    <result column="deleted" property="deleted" />
+                    <result column="record_type" property="recordType" />
+                    <result column="record_id" property="recordId" />
+                    <result column="name" property="name" />
+                    <result column="storage_blob_id" property="storageBlobId" />
+        </resultMap>
+
+        <!-- 閫氱敤鏌ヨ缁撴灉鍒� -->
+        <sql id="Base_Column_List">
+            id, create_time, update_time, deleted, record_type, record_id, name, storage_blob_id
+        </sql>
+
+</mapper>
\ No newline at end of file
diff --git a/ruoyi-common/src/main/resources/mapper/StorageBlobMapper.xml b/ruoyi-common/src/main/resources/mapper/StorageBlobMapper.xml
new file mode 100644
index 0000000..eace5ea
--- /dev/null
+++ b/ruoyi-common/src/main/resources/mapper/StorageBlobMapper.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
+<mapper namespace="com.ruoyi.basic.mapper.StorageBlobMapper">
+
+        <!-- 閫氱敤鏌ヨ鏄犲皠缁撴灉 -->
+        <resultMap id="BaseResultMap" type="com.ruoyi.basic.entity.StorageBlob">
+                    <id column="id" property="id" />
+                    <result column="create_time" property="createTime" />
+                    <result column="key" property="key" />
+                    <result column="content_type" property="contentType" />
+                    <result column="original_filename" property="originalFilename" />
+                    <result column="bucket_filename" property="bucketFilename" />
+                    <result column="bucket_name" property="bucketName" />
+                    <result column="byte_size" property="byteSize" />
+        </resultMap>
+
+        <!-- 閫氱敤鏌ヨ缁撴灉鍒� -->
+        <sql id="Base_Column_List">
+            id, create_time, key, content_type, original_filename,bucket_filename,bucket_name,  byte_size
+        </sql>
+
+</mapper>
\ No newline at end of file
diff --git a/ruoyi-system/src/main/resources/db/migration/postgresql/postgresql/V20250525003427__create_table_storage_blob.sql b/ruoyi-system/src/main/resources/db/migration/postgresql/postgresql/V20250525003427__create_table_storage_blob.sql
deleted file mode 100644
index 05cf963..0000000
--- a/ruoyi-system/src/main/resources/db/migration/postgresql/postgresql/V20250525003427__create_table_storage_blob.sql
+++ /dev/null
@@ -1,20 +0,0 @@
-DROP TABLE IF EXISTS storage_blob;
-
-CREATE TABLE storage_blob
-(
-    id           bigserial PRIMARY KEY,
-    created_at   timestamp    default now() NOT NULL,
-    key          varchar(150) DEFAULT ''    NOT NULL,
-    content_type varchar(100) DEFAULT ''    NOT NULL,
-    filename     varchar(255) DEFAULT ''    NOT NULL,
-    byte_size    bigint       DEFAULT 0     NOT NULL,
-    UNIQUE (key)
-);
-
-COMMENT ON TABLE storage_blob IS '閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭�';
-
-COMMENT ON COLUMN storage_blob.key IS '璧勬簮id';
-COMMENT ON COLUMN storage_blob.content_type IS '璧勬簮绫诲瀷锛屼緥濡侸PG鍥剧墖鐨勮祫婧愮被鍨嬩负image/jpg';
-COMMENT ON COLUMN storage_blob.filename IS '鏂囦欢鍚�';
-COMMENT ON COLUMN storage_blob.byte_size IS '璧勬簮灏哄(瀛楄妭)';
-
diff --git a/ruoyi-system/src/main/resources/db/migration/postgresql/postgresql/V20250525003447__create_table_storage_attachment.sql b/ruoyi-system/src/main/resources/db/migration/postgresql/postgresql/V20250525003447__create_table_storage_attachment.sql
deleted file mode 100644
index d743faa..0000000
--- a/ruoyi-system/src/main/resources/db/migration/postgresql/postgresql/V20250525003447__create_table_storage_attachment.sql
+++ /dev/null
@@ -1,25 +0,0 @@
--- ----------------------------
--- 闄勪欢琛�
--- ----------------------------
-drop table if exists storage_attachment;
-CREATE TABLE storage_attachments
-(
-    id              bigserial PRIMARY KEY,
-    created_at      timestamp    default now() NOT NULL,
-    updated_at      timestamp    default now() NOT NULL,
-    deleted_at      bigint       DEFAULT 0     NOT NULL,
-    record_type     smallint     DEFAULT 0     NOT NULL,
-    record_id       bigint       DEFAULT 0     NOT NULL,
-    name            varchar(100) DEFAULT ''    NOT NULL,
-    storage_blob_id bigint       DEFAULT 0     NOT NULL
-);
-
-COMMENT ON TABLE storage_attachments IS '閫氱敤鏂囦欢涓婁紶鐨勯檮浠朵俊鎭�';
-
-COMMENT ON COLUMN storage_attachments.record_type IS '鍏宠仈鐨勮褰曠被鍨�';
-COMMENT ON COLUMN storage_attachments.record_id IS '鍏宠仈鐨勮褰昳d';
-COMMENT ON COLUMN storage_attachments.name IS '鍚嶇О, 濡�: file, avatar (鍖哄垎鍚屼竴鏉¤褰曚笉鍚岀被鍨嬬殑闄勪欢)';
-COMMENT ON COLUMN storage_attachments.storage_blob_id IS '鍏宠仈storage_blob璁板綍id';
-
-CREATE INDEX idx_storage_attachments_on_record
-    ON storage_attachments (record_type, record_id);
\ No newline at end of file
diff --git a/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml b/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml
index 94e1d06..1d06e9d 100644
--- a/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml
+++ b/ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml
@@ -56,7 +56,7 @@
 	</select>
 	
 	<select id="selectMenuListByUserId" parameterType="SysMenu" resultMap="SysMenuResult">
-		select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m."query", m.route_name, m.visible, m.status, COALESCE(m.perms,'') as perms, m.is_frame, m.is_cache, m.menu_type, m.icon, m.order_num, m.create_time
+		select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m.query, m.route_name, m.visible, m.status, COALESCE(m.perms,'') as perms, m.is_frame, m.is_cache, m.menu_type, m.icon, m.order_num, m.create_time
 		from sys_menu m
 		left join sys_role_menu rm on m.menu_id = rm.menu_id
 		left join sys_user_role ur on rm.role_id = ur.role_id
@@ -75,7 +75,7 @@
 	</select>
     
     <select id="selectMenuTreeByUserId" parameterType="Long" resultMap="SysMenuResult">
-		select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m."query", m.route_name, m.visible, m.status, COALESCE(m.perms,'') as perms, m.is_frame, m.is_cache, m.menu_type, m.icon, m.order_num, m.create_time
+		select distinct m.menu_id, m.parent_id, m.menu_name, m.path, m.component, m.query, m.route_name, m.visible, m.status, COALESCE(m.perms,'') as perms, m.is_frame, m.is_cache, m.menu_type, m.icon, m.order_num, m.create_time
 		from sys_menu m
 			 left join sys_role_menu rm on m.menu_id = rm.menu_id
 			 left join sys_user_role ur on rm.role_id = ur.role_id

--
Gitblit v1.9.3