已添加28个文件
已修改10个文件
已删除2个文件
1551 ■■■■■ 文件已修改
basic-server/pom.xml 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/controller/SupplyController.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/dto/SupplyDto.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/entity/Supply.java 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/mapper/SupplyMapper.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/service/SupplyService.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/java/com/ruoyi/basic/service/impl/SupplyServiceImpl.java 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/src/main/resources/mapper/SupplyMapper.xml 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/pom.xml 11 ●●●●● 补丁 | 查看 | 原始文档 | 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-druid.yml.example 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/db/beforeSql/postgresql/beforeSQL__sys.sql 6 ●●●● 补丁 | 查看 | 原始文档 | 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 104 ●●●●● 补丁 | 查看 | 原始文档 | 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/BaseEntity.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/MinioResult.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/core/domain/MyBaseEntity.java 37 ●●●●● 补丁 | 查看 | 原始文档 | 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 29 ●●●●● 补丁 | 查看 | 原始文档 | 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/db/migration/postgresql/V20250530152701__create_table_supply.sql 57 ●●●●● 补丁 | 查看 | 原始文档 | 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 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
basic-server/pom.xml
@@ -23,10 +23,22 @@
            <artifactId>ruoyi-framework</artifactId>
        </dependency>
        <!-- ç³»ç»Ÿæ¨¡å—-->
        <dependency>
            <groupId>com.ruoyi</groupId>
            <artifactId>ruoyi-system</artifactId>
            <groupId>org.flywaydb</groupId>
            <artifactId>flyway-core</artifactId>
            <version>10.11.0</version> <!-- æˆ–更高版本,如 10.11.0 -->
        </dependency>
        <!--  mysql迁移  -->
        <dependency>
            <groupId>org.flywaydb</groupId>
            <artifactId>flyway-mysql</artifactId>
        </dependency>
        <!--  pgsql迁移  -->
        <dependency>
            <groupId>org.flywaydb</groupId>
            <artifactId>flyway-database-postgresql</artifactId>
        </dependency>
    </dependencies>
basic-server/src/main/java/com/ruoyi/basic/controller/SupplyController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,55 @@
package com.ruoyi.basic.controller;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.basic.dto.SupplyDto;
import com.ruoyi.basic.entity.Supply;
import com.ruoyi.basic.service.SupplyService;
import com.ruoyi.common.core.domain.R;
import org.springframework.web.bind.annotation.*;
import lombok.AllArgsConstructor;
import java.util.List;
/**
 * <p>
 * ä¾›åº”商信息表 å‰ç«¯æŽ§åˆ¶å™¨
 * </p>
 *
 * @author ruoyi
 * @since 2025-05-30
 */
@RestController
@AllArgsConstructor
@RequestMapping("/basic/supply")
public class SupplyController {
    private SupplyService supplyService;
    /**
     * æŸ¥è¯¢
     */
    @GetMapping("/list")
    public R<IPage<Supply>> list(Page page,SupplyDto supplyDto) {
        IPage<Supply> list = supplyService.selectSupplyList(page,supplyDto);
        return R.ok(list);
    }
    /**
     * æ–°å¢žä¿®æ”¹
     */
    @PostMapping("/addOrEditSupply")
    public R addOrEditSupply(@RequestBody SupplyDto supplyDto) {
        return R.ok(supplyService.addOrEditSupply(supplyDto));
    }
    /**
     * åˆ é™¤
     */
    @DeleteMapping("/delSupply")
    public R remove(@RequestBody Long[] ids) {
        return R.ok(supplyService.delSupplyByIds(ids));
    }
}
basic-server/src/main/java/com/ruoyi/basic/dto/SupplyDto.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,8 @@
package com.ruoyi.basic.dto;
import com.ruoyi.basic.entity.Supply;
import lombok.Data;
@Data
public class SupplyDto extends Supply {
}
basic-server/src/main/java/com/ruoyi/basic/entity/Supply.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,74 @@
package com.ruoyi.basic.entity;
import com.baomidou.mybatisplus.annotation.*;
import com.ruoyi.common.core.domain.MyBaseEntity;
import lombok.Data;
/**
 * ä¾›åº”商信息表 å®žä½“ç±»
 *
 * @author ruoyi
 * @date 2025-05-30
 */
@Data
@TableName("supply")
public class Supply extends MyBaseEntity {
    private static final long serialVersionUID = 1L;
    /**
     * ä¾›åº”商唯一标识,采用BIGSERIAL自增主键,确保数据唯一性
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;
    /**
     * ä¾›åº”商全称,业务场景中唯一识别的名称,
     */
    @TableField(value = "supplier_name")
    private String supplierName;
    /**
     * ä¾›åº”商税务登记唯一标识(如统一社会信用代码),用于财税管理
     */
    @TableField(value = "taxpayer_id")
    private String taxpayerId;
    /**
     * ä¾›åº”商营业执照注册地址或主要经营场所,默认空字符串
     */
    @TableField(value = "business_address")
    private String businessAddress;
    /**
     * ä¾›åº”商收款银行账户信息,默认空字符串,用于资金往来
     */
    @TableField(value = "bank_account")
    private String bankAccount;
    /**
     * ä¾›åº”商银行账户的开户机构名称,默认空字符串,
     */
    @TableField(value = "bank_name")
    private String bankName;
    /**
     * ä¾›åº”商业务对接人姓名
     */
    @TableField(value = "contact_person")
    private String contactPerson;
    /**
     * ä¾›åº”商联系人的联系地址
     */
    @TableField(value = "contact_address")
    private String contactAddress;
    /**
     * ä¾›åº”商所在省份的ID(关联地址字典表),默认0
     */
    @TableField(value = "province_id")
    private Long provinceId;
    /**
     * ä¾›åº”商所在城市的ID(关联地址字典表),默认0
     */
    @TableField(value = "city_id")
    private Long cityId;
    /**
     * ä¾›åº”商所在区县的ID(关联地址字典表),默认0,用于地址层级划分
     */
    @TableField(value = "district_id")
    private Long districtId;
}
basic-server/src/main/java/com/ruoyi/basic/mapper/SupplyMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package com.ruoyi.basic.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.ruoyi.basic.entity.Supply;
import org.apache.ibatis.annotations.Mapper;
/**
 * <p>
 * ä¾›åº”商信息表 Mapper æŽ¥å£
 * </p>
 *
 * @author ruoyi
 * @since 2025-05-30
 */
@Mapper
public interface SupplyMapper extends BaseMapper<Supply> {
}
basic-server/src/main/java/com/ruoyi/basic/service/SupplyService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,26 @@
package com.ruoyi.basic.service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.basic.dto.SupplyDto;
import com.ruoyi.basic.entity.Supply;
import com.baomidou.mybatisplus.extension.service.IService;
import java.util.List;
/**
 * <p>
 * ä¾›åº”商信息表 æœåŠ¡ç±»
 * </p>
 *
 * @author ruoyi
 * @since 2025-05-30
 */
public interface SupplyService extends IService<Supply> {
    IPage<Supply> selectSupplyList(Page page, SupplyDto supplyDto);
    int addOrEditSupply(SupplyDto supplyDto);
    int delSupplyByIds(Long[] ids);
}
basic-server/src/main/java/com/ruoyi/basic/service/impl/SupplyServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,78 @@
package com.ruoyi.basic.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.basic.dto.SupplyDto;
import com.ruoyi.basic.entity.Supply;
import com.ruoyi.basic.mapper.SupplyMapper;
import com.ruoyi.basic.service.SupplyService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ruoyi.common.utils.bean.BeanUtils;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
import org.springframework.util.StringUtils;
import java.util.List;
import java.util.Objects;
/**
 * <p>
 * ä¾›åº”商信息表 æœåŠ¡å®žçŽ°ç±»
 * </p>
 *
 * @author ruoyi
 * @since 2025-05-30
 */
@Service
@RequiredArgsConstructor
public class SupplyServiceImpl extends ServiceImpl<SupplyMapper, Supply> implements SupplyService {
    private final SupplyMapper supplyMapper;
    @Override
    public IPage<Supply> selectSupplyList(Page page, SupplyDto supplyDto) {
        LambdaQueryWrapper<Supply> queryWrapper = new LambdaQueryWrapper<>();
        // ä¾›åº”商名称模糊查询
        if (StringUtils.hasText(supplyDto.getSupplierName())) {
            queryWrapper.like(Supply::getSupplierName, supplyDto.getSupplierName());
        }
        // çº³ç¨Žäººè¯†åˆ«å·ç²¾ç¡®æŸ¥è¯¢
        if (StringUtils.hasText(supplyDto.getTaxpayerId())) {
            queryWrapper.eq(Supply::getTaxpayerId, supplyDto.getTaxpayerId());
        }
        queryWrapper.orderByDesc(Supply::getCreateTime);
        return supplyMapper.selectPage(page, queryWrapper);
    }
    @Override
    public int addOrEditSupply(SupplyDto supplyDto) {
        Supply supply = new Supply();
        BeanUtils.copyProperties(supplyDto, supply);
        if (Objects.isNull(supplyDto.getId())) {
            return supplyMapper.insert(supply);
        } else {
            return supplyMapper.updateById(supply);
        }
    }
    @Override
    public int delSupplyByIds(Long[] ids) {
        // æ£€æŸ¥å‚æ•°
        if (ids == null || ids.length == 0) {
            return 0;
        }
        // æž„造更新条件
        UpdateWrapper<Supply> updateWrapper = new UpdateWrapper<>();
        updateWrapper.in("id", ids)
                .set("deleted", 1);  // è®¾ç½® deleted ä¸º 1 è¡¨ç¤ºå·²åˆ é™¤
        // æ‰§è¡Œæ‰¹é‡é€»è¾‘删除
        return supplyMapper.update(null, updateWrapper);
    }
}
basic-server/src/main/resources/mapper/SupplyMapper.xml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,35 @@
<?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.SupplyMapper">
        <!-- é€šç”¨æŸ¥è¯¢æ˜ å°„结果 -->
        <resultMap id="BaseResultMap" type="com.ruoyi.basic.entity.Supply">
                    <id column="id" property="id" />
                <result column="create_time" property="createTime" />
                <result column="deleted" property="deleted" />
                <result column="update_time" property="updateTime" />
                <result column="create_by" property="createBy" />
                <result column="update_by" property="updateBy" />
                    <result column="supplier_name" property="supplierName" />
                    <result column="taxpayer_id" property="taxpayerId" />
                    <result column="business_address" property="businessAddress" />
                    <result column="bank_account" property="bankAccount" />
                    <result column="bank_name" property="bankName" />
                    <result column="contact_person" property="contactPerson" />
                    <result column="contact_address" property="contactAddress" />
                    <result column="province_id" property="provinceId" />
                    <result column="city_id" property="cityId" />
                    <result column="district_id" property="districtId" />
        </resultMap>
        <!-- é€šç”¨æŸ¥è¯¢ç»“果列 -->
        <sql id="Base_Column_List">
                create_time,
                deleted,
                update_time,
                create_by,
                update_by,
            id, supplier_name, taxpayer_id, business_address, bank_account, bank_name, contact_person, contact_address, province_id, city_id, district_id
        </sql>
</mapper>
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
@@ -68,6 +68,11 @@
            <artifactId>ruoyi-framework</artifactId>
        </dependency>
        <dependency>
            <groupId>com.ruoyi</groupId>
            <artifactId>basic-server</artifactId>
        </dependency>
        <!-- å®šæ—¶ä»»åŠ¡-->
        <dependency>
            <groupId>com.ruoyi</groupId>
@@ -93,6 +98,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
@@ -39,7 +39,7 @@
    public static void main(String[] args) {
        String projectPath = System.getProperty("user.dir"); // èŽ·å–é¡¹ç›®æ ¹è·¯å¾„
        String path = "basic-server"; // æ¨¡å—名称
        String table = "test"; // è¡¨åï¼Œå¤šä¸ªè¡¨é€—号隔开
        String table = "supply"; // è¡¨åï¼Œå¤šä¸ªè¡¨é€—号隔开
        // ä»£ç è¾“出路径配置
        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("ld") // ä½œè€…信息
                            .outputDir(outputBasePath) // ä»£ç è¾“出目录
                            .dateType(DateType.ONLY_DATE) // æ—¥æœŸç±»åž‹
                            .commentDate("yyyy-MM-dd") // æ³¨é‡Šæ—¥æœŸæ ¼å¼
@@ -56,7 +56,7 @@
                })
                .packageConfig(builder -> {
                    builder.parent(BASE_PACKAGE) // åŸºç¡€åŒ…名
                            .moduleName(MODULE_NAME) // æ¨¡å—名
                            .moduleName(MODULE_NAME) // æ¨¡å—名(根据具体修改)
                            .entity("entity") // Entity包名
                            .mapper("mapper") // Mapper包名
                            .service("service") // Service包名
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-druid.yml.example
@@ -62,4 +62,13 @@
                    merge-sql: true
                wall:
                    config:
                        multi-statement-allow: true
                        multi-statement-allow: true
minio:
    endpoint: lunor.cn
    port: 9000
    secure: false
    accessKey: admin
    secretKey: Admin123!
    preview-expiry: 24 # é¢„览地址默认24小时
    default-bucket: ruoyi #  é»˜è®¤å­˜å‚¨æ¡¶
ruoyi-admin/src/main/resources/db/beforeSql/postgresql/beforeSQL__sys.sql
@@ -227,11 +227,11 @@
    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',
    status      INTEGER      DEFAULT 0,
    perms       VARCHAR(100) DEFAULT NULL,
    icon        VARCHAR(100) DEFAULT '#',
    create_by   VARCHAR(64)  DEFAULT '',
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,104 @@
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.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/BaseEntity.java
@@ -4,15 +4,21 @@
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
/**
 * Entity基类
 *
 *
 * @author ruoyi
 */
@Data
public class BaseEntity implements Serializable
{
    private static final long serialVersionUID = 1L;
@@ -21,18 +27,26 @@
    @JsonIgnore
    private String searchValue;
    /** é€»è¾‘删除字段 */
    @TableLogic
    private Integer deleted;
    /** åˆ›å»ºè€… */
    @TableField(fill = FieldFill.INSERT)
    private String createBy;
    /** åˆ›å»ºæ—¶é—´ */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    /** æ›´æ–°è€… */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String updateBy;
    /** æ›´æ–°æ—¶é—´ */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
    /** å¤‡æ³¨ */
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/core/domain/MyBaseEntity.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,37 @@
package com.ruoyi.common.core.domain;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
@Data
public class MyBaseEntity implements Serializable {
    private static final long serialVersionUID = 1L;
    /** é€»è¾‘删除字段 */
    @TableLogic
    private Integer deleted;
    /** åˆ›å»ºè€… */
    @TableField(fill = FieldFill.INSERT)
    private String createBy;
    /** åˆ›å»ºæ—¶é—´ */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;
    /** æ›´æ–°è€… */
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String updateBy;
    /** æ›´æ–°æ—¶é—´ */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;
}
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,29 @@
package com.ruoyi.common.handler;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.ruoyi.common.utils.SecurityUtils;
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) {
        Long userId = SecurityUtils.getLoginUser().getUserId();
        this.strictInsertFill(metaObject, "createBy", Long.class, userId);
        this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
    }
    @Override
    public void updateFill(MetaObject metaObject) {
        Long userId = SecurityUtils.getLoginUser().getUserId();
        this.strictInsertFill(metaObject, "updateBy", Long.class, userId);
        this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
    }
}
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/db/migration/postgresql/V20250530152701__create_table_supply.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,57 @@
DROP TABLE IF EXISTS supply;
CREATE TABLE supply
(
    id               BIGSERIAL PRIMARY KEY,
    supplier_name    VARCHAR(255) NOT NULL,                             -- ä¾›åº”商名称
    taxpayer_id      VARCHAR(255) NOT NULL,                             -- çº³ç¨Žäººè¯†åˆ«å·
    business_address VARCHAR(255) NOT NULL    DEFAULT '',               -- ç»è¥åœ°å€
    bank_account     VARCHAR(255) NOT NULL    DEFAULT '',               -- é“¶è¡Œè´¦å·
    bank_name        VARCHAR(255) NOT NULL    DEFAULT '',               -- å¼€æˆ·è¡Œ
    contact_person   VARCHAR(255) NOT NULL    DEFAULT '',               -- è”系人
    contact_address  VARCHAR(255) NOT NULL    DEFAULT '',               -- è”系地址
    province_id      BIGINT       NOT NULL    DEFAULT 0,
    city_id          BIGINT       NOT NULL    DEFAULT 0,
    district_id      BIGINT       NOT NULL    DEFAULT 0,
    create_time      TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP -- è‡ªåŠ¨å¡«å……åˆ›å»ºæ—¶é—´
);
-- è¡¨æ³¨é‡Šï¼šä¾›åº”商信息表,存储供应商基本信息(含地址层级关联)
COMMENT ON TABLE supply IS '供应商信息表';
-- å­—段注释:主键(自增大整数)
COMMENT ON COLUMN supply.id IS '供应商唯一标识,采用BIGSERIAL自增主键,确保数据唯一性';
-- ä¾›åº”商名称(非空)
COMMENT ON COLUMN supply.supplier_name IS '供应商全称,业务场景中唯一识别的名称,';
-- çº³ç¨Žäººè¯†åˆ«å·ï¼ˆéžç©ºï¼‰
COMMENT ON COLUMN supply.taxpayer_id IS '供应商税务登记唯一标识(如统一社会信用代码),用于财税管理';
-- ç»è¥åœ°å€ï¼ˆé»˜è®¤ç©ºå­—符串,非空)
COMMENT ON COLUMN supply.business_address IS '供应商营业执照注册地址或主要经营场所,默认空字符串';
-- é“¶è¡Œè´¦å·ï¼ˆé»˜è®¤ç©ºå­—符串,非空)
COMMENT ON COLUMN supply.bank_account IS '供应商收款银行账户信息,默认空字符串,用于资金往来';
-- å¼€æˆ·è¡Œï¼ˆé»˜è®¤ç©ºå­—符串,非空)
COMMENT ON COLUMN supply.bank_name IS '供应商银行账户的开户机构名称,默认空字符串,';
-- è”系人(默认空字符串,非空)
COMMENT ON COLUMN supply.contact_person IS '供应商业务对接人姓名';
-- è”系地址(默认空字符串,非空)
COMMENT ON COLUMN supply.contact_address IS '供应商联系人的联系地址';
-- çœä»½ID(默认0,非空,关联地址字典)
COMMENT ON COLUMN supply.province_id IS '供应商所在省份的ID(关联地址字典表),默认0';
-- åŸŽå¸‚ID(默认0,非空,关联地址字典)
COMMENT ON COLUMN supply.city_id IS '供应商所在城市的ID(关联地址字典表),默认0';
-- åŒºåŽ¿ID(默认0,非空,关联地址字典)
COMMENT ON COLUMN supply.district_id IS '供应商所在区县的ID(关联地址字典表),默认0,用于地址层级划分';
-- åˆ›å»ºæ—¶é—´ï¼ˆå¸¦æ—¶åŒºï¼Œè‡ªåŠ¨å¡«å……ï¼‰
COMMENT ON COLUMN supply.create_time IS '记录数据创建的时间戳(带时区),系统自动填充当前时间';
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
@@ -29,7 +29,7 @@
    </resultMap>
    <sql id="selectMenuVo">
        select menu_id, menu_name, parent_id, order_num, path, component, `query`, route_name, is_frame, is_cache, menu_type, visible, status, COALESCE(perms,'') as perms, icon, create_time
        select menu_id, menu_name, parent_id, order_num, path, component, "query", route_name, is_frame, is_cache, menu_type, visible, status, COALESCE(perms,'') as perms, icon, create_time
        from sys_menu
    </sql>
    
@@ -51,12 +51,12 @@
    
    <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
        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>
    
@@ -141,7 +141,7 @@
            <if test="orderNum != null">order_num = #{orderNum},</if>
            <if test="path != null and path != ''">path = #{path},</if>
            <if test="component != null">component = #{component},</if>
            <if test="query != null">`query` = #{query},</if>
            <if test="query != null">"query" = #{query},</if>
            <if test="routeName != null">route_name = #{routeName},</if>
            <if test="isFrame != null and isFrame != ''">is_frame = #{isFrame},</if>
            <if test="isCache != null and isCache != ''">is_cache = #{isCache},</if>
@@ -165,7 +165,7 @@
        <if test="orderNum != null">order_num,</if>
        <if test="path != null and path != ''">path,</if>
        <if test="component != null and component != ''">component,</if>
        <if test="query != null and query != ''">`query`,</if>
        <if test="query != null and query != ''">"query",</if>
        <if test="routeName != null">route_name,</if>
        <if test="isFrame != null and isFrame != ''">is_frame,</if>
        <if test="isCache != null and isCache != ''">is_cache,</if>