liding
4 天以前 ccd87b6ac99259a01b9099a4d570f6d9e7dbb506
Merge branch 'master' of http://114.132.189.42:9002/r/zd-after into li
已修改8个文件
已删除2个文件
已添加19个文件
1125 ■■■■■ 文件已修改
pom.xml 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/pom.xml 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/PlusCodeGenerator.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/common/CommonController.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application.yml.example 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/db/beforeSql/postgresql/beforeSQL__sys.sql 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/pom.xml 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/basic/entity/StorageAttachment.java 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/basic/entity/StorageBlob.java 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/basic/entity/dto/StorageBlobDTO.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/basic/mapper/StorageAttachmentMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/basic/mapper/StorageBlobMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/basic/service/StorageAttachmentService.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/basic/service/StorageBlobService.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/basic/service/impl/StorageAttachmentServiceImpl.java 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/basic/service/impl/StorageBlobServiceImpl.java 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/config/MinioConfig.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/constant/StorageAttachmentConstants.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/MinioResult.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/enums/StorageAttachmentRecordType.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/handler/MyMetaObjectHandler.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/utils/file/MinioUtils.java 306 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/resources/db/migration/postgresql/V20250525003427__create_table_storage_blob.sql 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/resources/db/migration/postgresql/V20250525003447__create_table_storage_attachment.sql 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/resources/mapper/StorageAttachmentMapper.xml 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/resources/mapper/StorageBlobMapper.xml 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/db/migration/postgresql/postgresql/V20250525003427__create_table_storage_blob.sql 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/db/migration/postgresql/postgresql/V20250525003447__create_table_storage_attachment.sql 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/SysMenuMapper.xml 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
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>
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>
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"; // è¡¨åï¼Œå¤šä¸ªè¡¨é€—号隔开
        // ä»£ç è¾“出路径配置
        String outputBasePath = Paths.get(projectPath, path, "src", "main", "java").toString();
@@ -48,7 +48,7 @@
        // ä»£ç ç”Ÿæˆæ ¸å¿ƒé…ç½®
        FastAutoGenerator.create(DB_URL, DB_USERNAME, DB_PASSWORD)
                .globalConfig(builder -> {
                    builder.author("ruoyi") // ä½œè€…信息
                    builder.author("chen") // ä½œè€…信息
                            .outputDir(outputBasePath) // ä»£ç è¾“出目录
                            .dateType(DateType.ONLY_DATE) // æ—¥æœŸç±»åž‹
                            .commentDate("yyyy-MM-dd") // æ³¨é‡Šæ—¥æœŸæ ¼å¼
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")
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:
  # åç§°
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,
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>
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;
    /**
     * å…³è”的记录id
     */
    @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;
    }
}
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;
    /**
     * èµ„源类型,例如JPG图片的资源类型为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;
}
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;
}
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> {
}
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> {
}
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);
}
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);
}
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()));
    }
}
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;
    }
}
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();
    }
}
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";
}
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;
}
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;
    }
}
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) {
        }
    }
}
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);
        // æ‰§è¡Œæ­£å¸¸æ“ä½œ
        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());
                // ä¸ºfalse只生成24小时有效时长的url链接,可以访问该文件
                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;
    }
}
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 '资源类型,例如JPG图片的资源类型为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 '资源尺寸(字节)';
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 '关联的记录id';
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);
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>
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>
ruoyi-system/src/main/resources/db/migration/postgresql/postgresql/V20250525003427__create_table_storage_blob.sql
ÎļþÒÑɾ³ý
ruoyi-system/src/main/resources/db/migration/postgresql/postgresql/V20250525003447__create_table_storage_attachment.sql
ÎļþÒÑɾ³ý
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