已修改9个文件
已删除38个文件
已添加25个文件
7927 ■■■■ 文件已修改
LICENSE 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
README.md 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/pom.xml 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
bin/clean.bat 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
bin/package.bat 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
bin/run.bat 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml 82 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
project.md 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/pom.xml 24 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/PlusCodeGenerator.java 162 ●●●●● 补丁 | 查看 | 原始文档 | 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-admin/src/main/resources/templates/controller.Java.ftl 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/templates/entity.java.ftl 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/templates/mapper.xml.ftl 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/templates/serviceImpl.java.ftl 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/pom.xml 29 ●●●● 补丁 | 查看 | 原始文档 | 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-framework/pom.xml 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/pom.xml 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/java/com/ruoyi/generator/PlusCodeGenerator.java 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/java/com/ruoyi/generator/config/GenConfig.java 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/java/com/ruoyi/generator/controller/GenController.java 263 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTable.java 385 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTableColumn.java 373 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableColumnMapper.java 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableMapper.java 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableColumnServiceImpl.java 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableServiceImpl.java 531 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableColumnService.java 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableService.java 130 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/java/com/ruoyi/generator/util/GenUtils.java 257 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityInitializer.java 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java 408 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/resources/generator.yml.example 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml 127 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml 210 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/resources/vm/java/controller.java.vm 115 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/resources/vm/java/domain.java.vm 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/resources/vm/java/mapper.java.vm 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/resources/vm/java/service.java.vm 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm 169 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/resources/vm/java/sub-domain.java.vm 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/resources/vm/js/api.js.vm 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/resources/vm/sql/sql.vm 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm 505 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/resources/vm/vue/index.vue.vm 602 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm 474 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/resources/vm/vue/v3/index.vue.vm 590 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-generator/src/main/resources/vm/xml/mapper.xml.vm 140 ●●●●● 补丁 | 查看 | 原始文档 | 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 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
LICENSE
ÎļþÒÑɾ³ý
README.md
ÎļþÒÑɾ³ý
basic-server/pom.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>ruoyi</artifactId>
        <groupId>com.ruoyi</groupId>
        <version>3.8.9</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>basic-server</artifactId>
    <dependencies>
        <!-- é€šç”¨å·¥å…·-->
        <dependency>
            <groupId>com.ruoyi</groupId>
            <artifactId>ruoyi-common</artifactId>
        </dependency>
        <!-- æ ¸å¿ƒæ¨¡å—-->
        <dependency>
            <groupId>com.ruoyi</groupId>
            <artifactId>ruoyi-framework</artifactId>
        </dependency>
        <!-- ç³»ç»Ÿæ¨¡å—-->
        <dependency>
            <groupId>com.ruoyi</groupId>
            <artifactId>ruoyi-system</artifactId>
        </dependency>
    </dependencies>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
</project>
bin/clean.bat
ÎļþÒÑɾ³ý
bin/package.bat
ÎļþÒÑɾ³ý
bin/run.bat
ÎļþÒÑɾ³ý
pom.xml
@@ -35,6 +35,10 @@
        <jakarta.version>6.0.0</jakarta.version>
        <springdoc.version>2.6.0</springdoc.version>
        <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>
    <!-- ä¾èµ–声明 -->
@@ -69,12 +73,6 @@
                <groupId>com.github.pagehelper</groupId>
                <artifactId>pagehelper-spring-boot-starter</artifactId>
                <version>${pagehelper.boot.version}</version>
            </dependency>
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>${mybatis-spring-boot.version}</version>
            </dependency>
            <dependency>
@@ -158,13 +156,6 @@
                <version>${ruoyi.version}</version>
            </dependency>
            <!-- ä»£ç ç”Ÿæˆ-->
            <dependency>
                <groupId>com.ruoyi</groupId>
                <artifactId>ruoyi-generator</artifactId>
                <version>${ruoyi.version}</version>
            </dependency>
            <!-- æ ¸å¿ƒæ¨¡å—-->
            <dependency>
                <groupId>com.ruoyi</groupId>
@@ -192,16 +183,79 @@
                <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>
                <artifactId>basic-server</artifactId>
                <version>${ruoyi.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <!--  å…¨å±€å¼•å…¥  -->
    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
        <!-- MyBatis-Plus æ ¸å¿ƒä¾èµ– -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-core</artifactId>
            <version>3.5.12</version> <!-- ä¸Žä½ çš„ Spring Boot å…¼å®¹çš„版本 -->
        </dependency>
        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        <!--mybatis-plus代码生成器-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>${freemarker.version}</version>
        </dependency>
    </dependencies>
    <modules>
        <module>ruoyi-admin</module>
        <module>ruoyi-framework</module>
        <module>ruoyi-system</module>
        <module>ruoyi-quartz</module>
        <module>ruoyi-generator</module>
        <module>ruoyi-common</module>
        <module>basic-server</module>
    </modules>
    <packaging>pom</packaging>
project.md
@@ -44,7 +44,7 @@
```
请在每一个模块的src/main/resources/db/migration/postgresql下创建sql文件,没有请手动新增目录
迁移文件命名规则V2023072000000__create_table_note_template.sql
迁移文件命名规则V2023072000000__create_table_note_template.sql æ•°å­—为年月日时分秒,非时间戳
开发时期可修改历史sql脚本,投产请使用新增sql文件修改数据库,禁止直接修改数据库
ruoyi-admin/pom.xml
@@ -74,18 +74,30 @@
            <artifactId>ruoyi-quartz</artifactId>
        </dependency>
        <!-- ä»£ç ç”Ÿæˆ-->
        <dependency>
            <groupId>com.ruoyi</groupId>
            <artifactId>ruoyi-generator</artifactId>
        </dependency>
        <!-- postgresql驱动包 -->
        <dependency>
            <groupId>org.postgresql</groupId>
            <artifactId>postgresql</artifactId>
        </dependency>
        <!-- FreeMarker æ¨¡æ¿å¼•擎 -->
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.31</version> <!-- å¯æ ¹æ®éœ€è¦è°ƒæ•´ç‰ˆæœ¬ -->
        </dependency>
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.3</version>
        </dependency>
        <!-- minio依赖okhttp ä¸ç„¶æŠ¥é”™ -->
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
        </dependency>
    </dependencies>
ruoyi-admin/src/main/java/com/ruoyi/PlusCodeGenerator.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,162 @@
package com.ruoyi;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.rules.DateType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import com.baomidou.mybatisplus.generator.fill.Column;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
 * MyBatis-Plus ä»£ç ç”Ÿæˆå™¨
 * åŠŸèƒ½ï¼šæ ¹æ®æ•°æ®åº“è¡¨ç»“æž„è‡ªåŠ¨ç”ŸæˆEntity、Mapper、Service、Controller等代码
 * ä¿®å¤ï¼šè§£å†³Freemarker模板中generateService变量缺失的问题
 */
@SpringBootApplication
public class PlusCodeGenerator {
    // æ•°æ®åº“配置
    private static final String DB_URL = "jdbc:postgresql://lunor.cn:5431/ruoyi-java";
    private static final String DB_USERNAME = "test";
    private static final String DB_PASSWORD = "chj123456";
    // é¡¹ç›®åŸºç¡€é…ç½®
    private static final String BASE_PACKAGE = "com.ruoyi";
    private static final String MODULE_NAME = "basic"; // æ¨¡å—名
    public static void main(String[] args) {
        String projectPath = System.getProperty("user.dir"); // èŽ·å–é¡¹ç›®æ ¹è·¯å¾„
        String path = "ruoyi-common"; // æ¨¡å—名称
        String table = "storage_attachment"; // è¡¨åï¼Œå¤šä¸ªè¡¨é€—号隔开
        // ä»£ç è¾“出路径配置
        String outputBasePath = Paths.get(projectPath, path, "src", "main", "java").toString();
        String outputResourcePath = Paths.get(projectPath, path, "src", "main", "resources", "mapper").toString();
        // ä»£ç ç”Ÿæˆæ ¸å¿ƒé…ç½®
        FastAutoGenerator.create(DB_URL, DB_USERNAME, DB_PASSWORD)
                .globalConfig(builder -> {
                    builder.author("chen") // ä½œè€…信息
                            .outputDir(outputBasePath) // ä»£ç è¾“出目录
                            .dateType(DateType.ONLY_DATE) // æ—¥æœŸç±»åž‹
                            .commentDate("yyyy-MM-dd") // æ³¨é‡Šæ—¥æœŸæ ¼å¼
                            .disableOpenDir(); // ç¦æ­¢è‡ªåŠ¨æ‰“å¼€è¾“å‡ºç›®å½•
                })
                .packageConfig(builder -> {
                    builder.parent(BASE_PACKAGE) // åŸºç¡€åŒ…名
                            .moduleName(MODULE_NAME) // æ¨¡å—名
                            .entity("entity") // Entity包名
                            .mapper("mapper") // Mapper包名
                            .service("service") // Service包名
                            .serviceImpl("service.impl") // Service实现类包名
                            .controller("controller") // Controller包名
                            .pathInfo(Collections.singletonMap(
                                    OutputFile.xml,
                                    outputResourcePath // Mapper XML输出路径
                            ));
                })
                .strategyConfig(builder -> {
                    builder.addInclude(table) // è¦ç”Ÿæˆçš„表名
                            .addTablePrefix("t_", "sys_") // è¿‡æ»¤è¡¨å‰ç¼€
                            // å®žä½“类配置
                            .entityBuilder()
                            .superClass("com.ruoyi.common.core.domain.BaseEntity") // ç»§æ‰¿åŸºç±»
                            .addSuperEntityColumns("create_by", "create_time", "update_by", "update_time") // æŽ’除基类字段
                            .enableLombok() // å¯ç”¨Lombok
                            .naming(NamingStrategy.underline_to_camel) // è¡¨åè½¬é©¼å³°
                            .columnNaming(NamingStrategy.underline_to_camel) // åˆ—名转驼峰
                            .enableFileOverride() // å…è®¸è¦†ç›–文件
                            .logicDeleteColumnName("deleted") // é€»è¾‘删除字段
                            .addTableFills(
                                    // è‡ªåŠ¨å¡«å……é…ç½®
                                    new Column("create_by", FieldFill.INSERT), // åˆ›å»ºäººï¼Œæ’入时填充
                                    new Column("update_by", FieldFill.INSERT_UPDATE), // æ›´æ–°äººï¼Œæ’入和更新时填充
                                    new Column("create_time", FieldFill.INSERT), // åˆ›å»ºæ—¶é—´ï¼Œæ’入时填充
                                    new Column("update_time", FieldFill.INSERT_UPDATE) // æ›´æ–°æ—¶é—´ï¼Œæ’入和更新时填充
                            )
                            .idType(IdType.AUTO) // ä¸»é”®ç­–ç•¥
                            .enableTableFieldAnnotation() // å¯ç”¨å­—段注解
                            // æŽ§åˆ¶å™¨é…ç½®
                            .controllerBuilder()
                            .enableFileOverride() // å…è®¸è¦†ç›–
                            .enableRestStyle() // ç”ŸæˆRESTful风格控制器
                            // Service配置
                            .serviceBuilder()
                            .formatServiceFileName("%sService") // Service接口命名格式
                            .enableFileOverride() // å…è®¸è¦†ç›–
                            .formatServiceImplFileName("%sServiceImpl") // Service实现类命名格式
                            .enableFileOverride() // å…è®¸è¦†ç›–
                            // Mapper配置
                            .mapperBuilder()
                            .enableFileOverride() // å…è®¸è¦†ç›–
                            .enableMapperAnnotation() // å¯ç”¨@Mapper注解
                            .enableBaseResultMap() // å¯ç”¨åŸºç¡€ResultMap
                            .enableBaseColumnList() // å¯ç”¨åŸºç¡€ColumnList
                            .formatMapperFileName("%sMapper") // Mapper接口命名格式
                            .formatXmlFileName("%sMapper") // Mapper XML命名格式
                            .enableFileOverride(); // å…è®¸è¦†ç›–
                })
                // é…ç½®è‡ªå®šä¹‰æ¨¡æ¿
                .templateConfig(builder -> {
                    builder
                            .entity("/templates/entity.java") // å®žä½“类模板
                            .xml("/templates/mapper.xml") // Mapper XML模板
                            .controller("/templates/controller.java") // æŽ§åˆ¶å™¨æ¨¡æ¿
                            .serviceImpl("/templates/serviceImpl.java"); // Service实现类模板
                })
                // æ³¨å…¥æ¨¡æ¿å˜é‡
                .injectionConfig(builder -> {
                    Map<String, Object> customMap = new HashMap<>();
                    customMap.put("superEntityColumns", Arrays.asList(
                            "create_by", "create_time", "update_by", "update_time"
                    )); // åŸºç±»å­—段
                    customMap.put("idType", "AUTO"); // ä¸»é”®ç±»åž‹
                    customMap.put("superEntityClass", "com.ruoyi.common.core.domain.BaseEntity"); // åŸºç±»å…¨è·¯å¾„
                    customMap.put("author", "ruoyi"); // ä½œè€…信息
                    customMap.put("packageName", BASE_PACKAGE + "." + MODULE_NAME); // åŒ…名
                    customMap.put("tableName", table); // è¡¨å
                    // æ–°å¢žï¼šè§£å†³Freemarker模板中generateService变量缺失的问题
                    // æŽ§åˆ¶Service实现类是否实现Service接口
                    boolean generateService = true;
                    customMap.put("generateService", generateService);
                    builder.customMap(customMap);
                })
                .templateEngine(new FreemarkerTemplateEngine()) // ä½¿ç”¨Freemarker模板引擎
                .execute(); // æ‰§è¡Œä»£ç ç”Ÿæˆ
        // åŽå¤„理:修复实体类的完全限定名问题
        try {
            Path entityPath = Paths.get(outputBasePath, "com/ruoyi/basic/entity/Test.java");
            if (Files.exists(entityPath)) {
                String content = Files.readString(entityPath);
                content = content.replace(
                        "extends com.ruoyi.common.core.domain.BaseEntity",
                        "extends BaseEntity"
                ); // ç®€åŒ–基类引用
                Files.writeString(entityPath, content);
            }
        } catch (IOException e) {
            System.err.println("⚠️ ä¿®å¤å®žä½“类失败: " + e.getMessage());
        }
        System.out.println("✅ ä»£ç ç”Ÿæˆå®Œæˆï¼æ–‡ä»¶å·²è¾“出到:" + outputBasePath);
    }
}
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      CHAR(1)      DEFAULT '0',
ruoyi-admin/src/main/resources/templates/controller.Java.ftl
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,40 @@
package ${package.Controller};
import org.springframework.web.bind.annotation.RequestMapping;
import lombok.AllArgsConstructor;
<#if restControllerStyle>
    import org.springframework.web.bind.annotation.RestController;
<#else>
    import org.springframework.stereotype.Controller;
</#if>
<#if superControllerClassPackage??>
    import ${superControllerClassPackage};
</#if>
/**
* <p>
    * ${table.comment!} å‰ç«¯æŽ§åˆ¶å™¨
    * </p>
*
* @author ${author}
* @since ${date}
*/
<#if restControllerStyle>
@RestController
<#else>
@Controller
</#if>
@AllArgsConstructor
@RequestMapping("<#if package.ModuleName?? && package.ModuleName != "">/${package.ModuleName}</#if>/<#if controllerMappingHyphenStyle>${controllerMappingHyphen}<#else>${table.entityPath}</#if>")
<#if kotlin>
    class ${table.controllerName}<#if superControllerClass??> : ${superControllerClass}()</#if>
<#else>
    <#if superControllerClass??>
        public class ${table.controllerName} extends ${superControllerClass} {
    <#else>
        public class ${table.controllerName} {
    </#if>
    }
</#if>
ruoyi-admin/src/main/resources/templates/entity.java.ftl
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,44 @@
package ${package.Entity};
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
<#if superEntityClass?? && superEntityClass != "">
    import ${superEntityClass};
</#if>
<#list table.fields as field>
    <#if field.propertyType == "LocalDateTime">
        import java.time.LocalDateTime;
        <#break>
    </#if>
</#list>
/**
* ${table.comment!} å®žä½“ç±»
*
* @author ${author!"admin"}
* @date ${.now?string("yyyy-MM-dd")}
*/
@Data
@TableName("${table.name}")
public class ${entity} <#if superEntityClass?? && superEntityClass != "">extends ${superEntityClass?substring(superEntityClass?last_index_of(".") + 1)}</#if> {
private static final long serialVersionUID = 1L;
<#list table.fields as field>
<#-- ä¿®å¤ï¼šæ·»åŠ  superEntityColumns å­˜åœ¨æ€§æ£€æŸ¥ -->
    <#if superEntityColumns?? && !superEntityColumns?seq_contains(field.name)>
        /**
        * ${field.comment!}
        */
        <#if field.keyFlag>
            @TableId(value = "${field.name}", type = IdType.${idType!"AUTO"})
        <#else>
            @TableField(value = "${field.name}"<#if field.fill?? && field.fill != "">, fill = FieldFill.${field.fill?upper_case}</#if>)
        </#if>
        <#if field.logicDeleteField>
            @TableLogic
        </#if>
        private ${field.propertyType} ${field.propertyName};
    </#if>
</#list>
}
ruoyi-admin/src/main/resources/templates/mapper.xml.ftl
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,39 @@
<?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="${package.Mapper}.${table.mapperName}">
    <#if enableCache>
        <!-- å¼€å¯äºŒçº§ç¼“å­˜ -->
        <cache type="${cacheClassName}"/>
    </#if>
    <#if baseResultMap>
        <!-- é€šç”¨æŸ¥è¯¢æ˜ å°„结果 -->
        <resultMap id="BaseResultMap" type="${package.Entity}.${entity}">
            <#list table.fields as field>
                <#if field.keyFlag><#--生成主键排在第一位-->
                    <id column="${field.name}" property="${field.propertyName}" />
                </#if>
            </#list>
            <#list table.commonFields as field><#--生成公共字段 -->
                <result column="${field.name}" property="${field.propertyName}" />
            </#list>
            <#list table.fields as field>
                <#if !field.keyFlag><#--生成普通字段 -->
                    <result column="${field.name}" property="${field.propertyName}" />
                </#if>
            </#list>
        </resultMap>
    </#if>
    <#if baseColumnList>
        <!-- é€šç”¨æŸ¥è¯¢ç»“果列 -->
        <sql id="Base_Column_List">
            <#list table.commonFields as field>
                ${field.columnName},
            </#list>
            ${table.fieldNames}
        </sql>
    </#if>
</mapper>
ruoyi-admin/src/main/resources/templates/serviceImpl.java.ftl
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
package ${package.ServiceImpl};
import ${package.Entity}.${entity};
import ${package.Mapper}.${table.mapperName};
<#if generateService>
    import ${package.Service}.${table.serviceName};
</#if>
import ${superServiceImplClassPackage};
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
/**
* <p>
    * ${table.comment!} æœåŠ¡å®žçŽ°ç±»
    * </p>
*
* @author ${author}
* @since ${date}
*/
@Service
@RequiredArgsConstructor
<#if kotlin>
    open class ${table.serviceImplName} : ${superServiceImplClass}<${table.mapperName}, ${entity}>()<#if generateService>, ${table.serviceName}</#if> {
    }
<#else>
    public class ${table.serviceImplName} extends ${superServiceImplClass}<${table.mapperName}, ${entity}><#if generateService> implements ${table.serviceName}</#if> {
    }
</#if>
ruoyi-common/pom.xml
@@ -128,15 +128,36 @@
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
            <version>3.5.10</version>
            <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>com.baomidou</groupId>
            <artifactId>mybatis-plus-jsqlparser</artifactId>
            <version>3.5.10</version>
            <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-framework/pom.xml
@@ -53,6 +53,18 @@
            <artifactId>oshi-core</artifactId>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
            <version>3.5.12</version>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-extension</artifactId>
            <version>3.5.6</version>
        </dependency>
        <!-- ç³»ç»Ÿæ¨¡å—-->
        <dependency>
            <groupId>com.ruoyi</groupId>
ruoyi-generator/pom.xml
ÎļþÒÑɾ³ý
ruoyi-generator/src/main/java/com/ruoyi/generator/PlusCodeGenerator.java
ÎļþÒÑɾ³ý
ruoyi-generator/src/main/java/com/ruoyi/generator/config/GenConfig.java
ÎļþÒÑɾ³ý
ruoyi-generator/src/main/java/com/ruoyi/generator/controller/GenController.java
ÎļþÒÑɾ³ý
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTable.java
ÎļþÒÑɾ³ý
ruoyi-generator/src/main/java/com/ruoyi/generator/domain/GenTableColumn.java
ÎļþÒÑɾ³ý
ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableColumnMapper.java
ÎļþÒÑɾ³ý
ruoyi-generator/src/main/java/com/ruoyi/generator/mapper/GenTableMapper.java
ÎļþÒÑɾ³ý
ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableColumnServiceImpl.java
ÎļþÒÑɾ³ý
ruoyi-generator/src/main/java/com/ruoyi/generator/service/GenTableServiceImpl.java
ÎļþÒÑɾ³ý
ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableColumnService.java
ÎļþÒÑɾ³ý
ruoyi-generator/src/main/java/com/ruoyi/generator/service/IGenTableService.java
ÎļþÒÑɾ³ý
ruoyi-generator/src/main/java/com/ruoyi/generator/util/GenUtils.java
ÎļþÒÑɾ³ý
ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityInitializer.java
ÎļþÒÑɾ³ý
ruoyi-generator/src/main/java/com/ruoyi/generator/util/VelocityUtils.java
ÎļþÒÑɾ³ý
ruoyi-generator/src/main/resources/generator.yml.example
ÎļþÒÑɾ³ý
ruoyi-generator/src/main/resources/mapper/generator/GenTableColumnMapper.xml
ÎļþÒÑɾ³ý
ruoyi-generator/src/main/resources/mapper/generator/GenTableMapper.xml
ÎļþÒÑɾ³ý
ruoyi-generator/src/main/resources/vm/java/controller.java.vm
ÎļþÒÑɾ³ý
ruoyi-generator/src/main/resources/vm/java/domain.java.vm
ÎļþÒÑɾ³ý
ruoyi-generator/src/main/resources/vm/java/mapper.java.vm
ÎļþÒÑɾ³ý
ruoyi-generator/src/main/resources/vm/java/service.java.vm
ÎļþÒÑɾ³ý
ruoyi-generator/src/main/resources/vm/java/serviceImpl.java.vm
ÎļþÒÑɾ³ý
ruoyi-generator/src/main/resources/vm/java/sub-domain.java.vm
ÎļþÒÑɾ³ý
ruoyi-generator/src/main/resources/vm/js/api.js.vm
ÎļþÒÑɾ³ý
ruoyi-generator/src/main/resources/vm/sql/sql.vm
ÎļþÒÑɾ³ý
ruoyi-generator/src/main/resources/vm/vue/index-tree.vue.vm
ÎļþÒÑɾ³ý
ruoyi-generator/src/main/resources/vm/vue/index.vue.vm
ÎļþÒÑɾ³ý
ruoyi-generator/src/main/resources/vm/vue/v3/index-tree.vue.vm
ÎļþÒÑɾ³ý
ruoyi-generator/src/main/resources/vm/vue/v3/index.vue.vm
ÎļþÒÑɾ³ý
ruoyi-generator/src/main/resources/vm/xml/mapper.xml.vm
ÎļþÒÑɾ³ý
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
@@ -50,13 +50,13 @@
    </select>
    
    <select id="selectMenuTreeAll" 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
        from sys_menu m where m.menu_type in ('M', 'C') and m.status = 0
        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 where m.menu_type in ('M', 'C') and m.status = '0'
        order by m.parent_id, m.order_num
    </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,13 +75,13 @@
    </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
             left join sys_role ro on ur.role_id = ro.role_id
             left join sys_user u on ur.user_id = u.user_id
        where u.user_id = #{userId} and m.menu_type in ('M', 'C') and m.status = 0  AND ro.status = 0
        where u.user_id = #{userId} and m.menu_type in ('M', 'C') and m.status = '0'  AND ro.status = '0'
        order by m.parent_id, m.order_num
    </select>