SpringBoot 3.3.1 + Minio 实现极速上传和预览模式

在这里插入图片描述

统一版本管理

<properties>
	<minio.version>8.5.10</minio.version>
	<aws.version>1.12.737</aws.version>
	<hutool.version>5.8.28</hutool.version>
</properties>
<!--minio -->
<dependency>
	<groupId>io.minio</groupId>
	<artifactId>minio</artifactId>
	<version>${minio.version}</version>
</dependency>
<!--aws-s3-->
<dependency>
	<groupId>com.amazonaws</groupId>
	<artifactId>aws-java-sdk-s3</artifactId>
	<version>${aws.version}</version>
</dependency>
<!--hutool -->
<dependency>
	<groupId>cn.hutool</groupId>
	<artifactId>hutool-all</artifactId>
	<version>${hutool.version}</version>
</dependency>

项目配置 application-dev.yml

# 文件系统
minio: 
  #内部地址,可以访问到内网地址
  endpoint: http://172.16.11.110:10087
  access-key: xxxxxx
  secret-key: xxxxxx
  bucket-name: public-example-xxxx
  public-bucket-name: public-example-xxx
  #外网,互联网地址
  preview-domain: http://116.201.11.xxx:30087

创建 MinioConfig

package com.example.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import io.minio.MinioClient;
import lombok.AllArgsConstructor;

/**
 * aws-s3 通用存储操作 支持所有兼容s3协议的云存储: 阿里云OSS、腾讯云COS、华为云、七牛云、,京东云、minio 
 * @author weimeilayer@gmail.com
 * @date 2021年2月3日
 */
@Configuration
@AllArgsConstructor
public class MinioConfig {
	private final MinioProperties minioProperties;
	
	@Bean
	public MinioClient minioClient() {
		MinioClient minioClient = MinioClient.builder().endpoint(minioProperties.getEndpoint()).credentials(minioProperties.getAccessKey(), minioProperties.getSecretKey()).build();
		return minioClient;
	}
}

创建 MinioProperties

package com.example.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;

/**
 * aws 配置信息bucket 设置公共读权限
 * @author weimeilayer@gmail.com
 * @date 💓💕2021年4月1日🐬🐇 💓💕
 */
@Data
@Configuration
@ConfigurationProperties(prefix = "minio")
public class MinioProperties {

	/**
	 * 对象存储服务的URL
	 */
	@Schema(description = "对象存储服务的URL")
	private String endpoint;

	/**
	 * 自定义域名
	 */
	@Schema(description = "自定义域名")
	private String customDomain;

	/**
	 * 反向代理和S3默认支持
	 */
	@Schema(description = "反向代理和S3默认支持")
	private Boolean pathStyleAccess = true;

	/**
	 * 应用ID
	 */
	@Schema(description = "应用ID")
	private String appId;

	/**
	 * 区域
	 */
	@Schema(description = "区域")
	private String region;
	
	/**
	 * 预览地址
	 */
	@Schema(description = "预览地址")
	private String previewDomain;

	/**
	 * Access key就像用户ID,可以唯一标识你的账户
	 */
	@Schema(description = "Access key就像用户ID,可以唯一标识你的账户")
	private String accessKey;

	/**
	 * Secret key是你账户的密码
	 */
	@Schema(description = "Secret key是你账户的密码")
	private String secretKey;

	/**
	 * 默认的存储桶名称
	 */
	@Schema(description = "默认的存储桶名称")
	private String bucketName;
	/**
     * 公开桶名
     */
	@Schema(description = "公开桶名")
    private String publicBucketName;

    /**
     * 物理删除文件
     */
	@Schema(description = "物理删除文件")
    private boolean physicsDelete;
    
	/**
	 * 最大线程数,默认: 100
	 */
	@Schema(description = "最大线程数,默认: 100")
	private Integer maxConnections = 100;
}

创建 MinioTemplate

package com.example.config;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;
import java.util.Optional;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.annotation.Configuration;

import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.client.builder.AwsClientBuilder;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.Bucket;
import com.amazonaws.services.s3.model.ObjectListing;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectResult;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.s3.model.S3ObjectSummary;
import com.amazonaws.util.IOUtils;

import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;

/**
 * aws-s3 通用存储操作 支持所有兼容s3协议的云存储: {阿里云OSS,腾讯云COS,七牛云,京东云,minio 等}
 * @author weimeilayer@gmail.com ✨
 * @date 💓💕2024年3月7日🐬🐇 💓💕
 */
@Configuration
@RequiredArgsConstructor
public class MinioTemplate implements InitializingBean {
	private final MinioProperties ossProperties;
	private AmazonS3 amazonS3;

	/**
	 * 创建bucket
	 * 
	 * @param bucketName bucket名称
	 */
	@SneakyThrows
	public void createBucket(String bucketName) {
		if (!amazonS3.doesBucketExistV2(bucketName)) {
			amazonS3.createBucket((bucketName));
		}
	}

	/**
	 * 获取全部bucket API Documentation</a>
	 */
	@SneakyThrows
	public List<Bucket> getAllBuckets() {
		return amazonS3.listBuckets();
	}

	/**
	 * @param bucketName bucket名称 API Documentation</a>
	 */
	@SneakyThrows
	public Optional<Bucket> getBucket(String bucketName) {
		return amazonS3.listBuckets().stream().filter(b -> b.getName().equals(bucketName)).findFirst();
	}

	/**
	 * @param bucketName bucket名称
	 * @see <a href= Documentation</a>
	 */
	@SneakyThrows
	public void removeBucket(String bucketName) {
		amazonS3.deleteBucket(bucketName);
	}

	/**
	 * 根据文件前置查询文件
	 * 
	 * @param bucketName bucket名称
	 * @param prefix     前缀
	 * @param recursive  是否递归查询
	 * @return S3ObjectSummary 列表 API Documentation</a>
	 */
	@SneakyThrows
	public List<S3ObjectSummary> getAllObjectsByPrefix(String bucketName, String prefix, boolean recursive) {
		ObjectListing objectListing = amazonS3.listObjects(bucketName, prefix);
		return new ArrayList<>(objectListing.getObjectSummaries());
	}

	/**
	 * 获取文件外链
	 * 
	 * @param bucketName bucket名称
	 * @param objectName 文件名称
	 * @param expires    过期时间 <=7
	 * @return url
	 */
	@SneakyThrows
	public String getObjectURL(String bucketName, String objectName, Integer expires) {
		Date date = new Date();
		Calendar calendar = new GregorianCalendar();
		calendar.setTime(date);
		calendar.add(Calendar.DAY_OF_MONTH, expires);
		URL url = amazonS3.generatePresignedUrl(bucketName, objectName, calendar.getTime());
		return url.toString();
	}

	/**
	 * 获取文件
	 * 
	 * @param bucketName bucket名称
	 * @param objectName 文件名称
	 * @return 二进制流 API Documentation</a>
	 */
	@SneakyThrows
	public S3Object getObject(String bucketName, String objectName) {
		return amazonS3.getObject(bucketName, objectName);
	}

	/**
	 * 上传文件
	 * 
	 * @param bucketName bucket名称
	 * @param objectName 文件名称
	 * @param stream     文件流
	 * @throws Exception
	 */
	public void putObject(String bucketName, String objectName, InputStream stream) throws Exception {
		putObject(bucketName, objectName, stream, (long) stream.available(), "application/octet-stream");
	}

	/**
	 * 上传文件
	 * 
	 * @param bucketName  bucket名称
	 * @param objectName  文件名称
	 * @param stream      文件流
	 * @param size        大小
	 * @param contextType 类型
	 * @throws Exception
	 */
	public PutObjectResult putObject(String bucketName, String objectName, InputStream stream, long size,
			String contextType) throws Exception {
		byte[] bytes = IOUtils.toByteArray(stream);
		ObjectMetadata objectMetadata = new ObjectMetadata();
		objectMetadata.setContentLength(size);
		objectMetadata.setContentType(contextType);
		ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
		// 上传
		return amazonS3.putObject(bucketName, objectName, byteArrayInputStream, objectMetadata);

	}

	/**
	 * 获取文件信息
	 * 
	 * @param bucketName bucket名称
	 * @param objectName 文件名称
	 * @throws Exception API Documentation</a>
	 */
	public S3Object getObjectInfo(String bucketName, String objectName) throws Exception {
		return amazonS3.getObject(bucketName, objectName);
	}

	/**
	 * 删除文件
	 * 
	 * @param bucketName bucket名称
	 * @param objectName 文件名称
	 * @throws Exception
	 */
	public void removeObject(String bucketName, String objectName) throws Exception {
		amazonS3.deleteObject(bucketName, objectName);
	}

	@Override
	public void afterPropertiesSet() {
		ClientConfiguration clientConfiguration = new ClientConfiguration();
		clientConfiguration.setMaxConnections(ossProperties.getMaxConnections());
		AwsClientBuilder.EndpointConfiguration endpointConfiguration = new AwsClientBuilder.EndpointConfiguration(
				ossProperties.getEndpoint(), ossProperties.getRegion());
		AWSCredentials awsCredentials = new BasicAWSCredentials(ossProperties.getAccessKey(),
				ossProperties.getSecretKey());
		AWSCredentialsProvider awsCredentialsProvider = new AWSStaticCredentialsProvider(awsCredentials);
		this.amazonS3 = AmazonS3Client.builder().withEndpointConfiguration(endpointConfiguration)
				.withClientConfiguration(clientConfiguration).withCredentials(awsCredentialsProvider)
				.disableChunkedEncoding().withPathStyleAccessEnabled(ossProperties.getPathStyleAccess()).build();
	}
}

创建Result

package com.example.utils;

import java.util.HashMap;

/**
 * 响应信息主体
 * @author weimeilayer@gmail.com ✨
 * @date 💓💕2021年6月28日 🐬🐇 💓💕
 */
public class Result extends HashMap<String, Object> {
	private static final long serialVersionUID = 1L;

	/** 状态码 */
	public static final String CODE_TAG = "code";

	/** 返回内容 */
	public static final String MSG_TAG = "msg";

	/** 数据对象 */
	public static final String DATA_TAG = "data";

	/**
	 * 初始化一个新创建的 Result 对象,使其表示一个空消息。
	 */
	public Result() {
	}

	/**
	 * 初始化一个新创建的 Result 对象
	 *
	 * @param code 状态码
	 * @param msg  返回内容
	 */
	public Result(int code, String msg) {
		super.put(CODE_TAG, code);
		super.put(MSG_TAG, msg);
	}

	/**
	 * 初始化一个新创建的 Result 对象
	 *
	 * @param code 状态码
	 * @param msg  返回内容
	 * @param data 数据对象
	 */
	public Result(int code, String msg, Object data) {
		super.put(CODE_TAG, code);
		super.put(MSG_TAG, msg);
		if (data != null) {
			super.put(DATA_TAG, data);
		}
	}

	/**
	 * 返回成功消息
	 *
	 * @return 成功消息
	 */
	public static Result success() {
		return Result.success("操作成功");
	}

	/**
	 * 返回成功数据
	 *
	 * @return 成功消息
	 */
	public static Result success(Object data) {
		return Result.success("操作成功", data);
	}

	/**
	 * 返回成功消息
	 *
	 * @param msg 返回内容
	 * @return 成功消息
	 */
	public static Result success(String msg) {
		return Result.success(msg, null);
	}

	/**
	 * 返回成功消息
	 *
	 * @param msg  返回内容
	 * @param data 数据对象
	 * @return 成功消息
	 */
	public static Result success(String msg, Object data) {
		return new Result(HttpStatus.SUCCESS, msg, data);
	}

	/**
	 * 返回警告消息
	 *
	 * @param msg 返回内容
	 * @return 警告消息
	 */
	public static Result warn(String msg) {
		return Result.warn(msg, null);
	}

	/**
	 * 返回警告消息
	 *
	 * @param msg  返回内容
	 * @param data 数据对象
	 * @return 警告消息
	 */
	public static Result warn(String msg, Object data) {
		return new Result(HttpStatus.WARN, msg, data);
	}

	/**
	 * 返回错误消息
	 *
	 * @return 错误消息
	 */
	public static Result error() {
		return Result.error("操作失败");
	}

	/**
	 * 返回错误消息
	 *
	 * @param msg 返回内容
	 * @return 错误消息
	 */
	public static Result error(String msg) {
		return Result.error(msg, null);
	}

	/**
	 * 返回错误消息
	 *
	 * @param msg  返回内容
	 * @param data 数据对象
	 * @return 错误消息
	 */
	public static Result error(String msg, Object data) {
		return new Result(HttpStatus.ERROR, msg, data);
	}

	/**
	 * 返回错误消息
	 *
	 * @param code 状态码
	 * @param msg  返回内容
	 * @return 错误消息
	 */
	public static Result error(int code, String msg) {
		return new Result(code, msg, null);
	}

	/**
	 * 方便链式调用
	 *
	 * @param key   键
	 * @param value 值
	 * @return 数据对象
	 */
	@Override
	public Result put(String key, Object value) {
		super.put(key, value);
		return this;
	}
}

创建 HttpStatus

package com.example.utils;

/**
 * http请求状态
 * @author weimeilayer@gmail.com ✨
 * @date 💓💕2024年6月28日 🐬🐇 💓💕
 */
public class HttpStatus {
	/**
	 * 操作成功
	 */
	public static final int SUCCESS = 200;

	/**
	 * 对象创建成功
	 */
	public static final int CREATED = 201;

	/**
	 * 请求已经被接受
	 */
	public static final int ACCEPTED = 202;

	/**
	 * 操作已经执行成功,但是没有返回数据
	 */
	public static final int NO_CONTENT = 204;

	/**
	 * 资源已被移除
	 */
	public static final int MOVED_PERM = 301;

	/**
	 * 重定向
	 */
	public static final int SEE_OTHER = 303;

	/**
	 * 资源没有被修改
	 */
	public static final int NOT_MODIFIED = 304;

	/**
	 * 参数列表错误(缺少,格式不匹配)
	 */
	public static final int BAD_REQUEST = 400;

	/**
	 * 未授权
	 */
	public static final int UNAUTHORIZED = 401;

	/**
	 * 访问受限,授权过期
	 */
	public static final int FORBIDDEN = 403;

	/**
	 * 资源,服务未找到
	 */
	public static final int NOT_FOUND = 404;

	/**
	 * 不允许的http方法
	 */
	public static final int BAD_METHOD = 405;

	/**
	 * 资源冲突,或者资源被锁
	 */
	public static final int CONFLICT = 409;

	/**
	 * 不支持的数据,媒体类型
	 */
	public static final int UNSUPPORTED_TYPE = 415;

	/**
	 * 系统内部错误
	 */
	public static final int ERROR = 500;

	/**
	 * 接口未实现
	 */
	public static final int NOT_IMPLEMENTED = 501;

	/**
	 * 系统警告消息
	 */
	public static final int WARN = 601;
}

创建 Constants

package com.example.utils;

/**
 * 通用常量信息
 * @author weimeilayer@gmail.com ✨
 * @date 💓💕2024年6月28日 🐬🐇 💓💕
 */
public class Constants {
	/**
	 * UTF-8 字符集
	 */
	public static final String UTF8 = "UTF-8";

	/**
	 * GBK 字符集
	 */
	public static final String GBK = "GBK";

	/**
	 * www主域
	 */
	public static final String WWW = "www.";

	/**
	 * http请求
	 */
	public static final String HTTP = "http://";

	/**
	 * https请求
	 */
	public static final String HTTPS = "https://";

	/**
	 * 通用成功标识
	 */
	public static final String SUCCESS = "0";

	/**
	 * 通用失败标识
	 */
	public static final String FAIL = "1";

	/**
	 * 登录成功
	 */
	public static final String LOGIN_SUCCESS = "Success";

	/**
	 * 注销
	 */
	public static final String LOGOUT = "Logout";

	/**
	 * 注册
	 */
	public static final String REGISTER = "Register";

	/**
	 * 登录失败
	 */
	public static final String LOGIN_FAIL = "Error";

	/**
	 * 验证码有效期(分钟)
	 */
	public static final Integer CAPTCHA_EXPIRATION = 2;

	/**
	 * 令牌
	 */
	public static final String TOKEN = "token";

	/**
	 * 令牌前缀
	 */
	public static final String TOKEN_PREFIX = "Bearer ";

	/**
	 * 令牌前缀
	 */
	public static final String LOGIN_USER_KEY = "login_user_key";

	/**
	 * 用户头像
	 */
	public static final String JWT_AVATAR = "avatar";

	/**
	 * 创建时间
	 */
	public static final String JWT_CREATED = "created";

	/**
	 * 用户权限
	 */
	public static final String JWT_AUTHORITIES = "authorities";

	/**
	 * 资源映射路径 前缀
	 */
	public static final String RESOURCE_PREFIX = "/profile";

	/**
	 * RMI 远程方法调用
	 */
	public static final String LOOKUP_RMI = "rmi:";

	/**
	 * LDAP 远程方法调用
	 */
	public static final String LOOKUP_LDAP = "ldap:";

	/**
	 * LDAPS 远程方法调用
	 */
	public static final String LOOKUP_LDAPS = "ldaps:";
}

数据库表

CREATE TABLE `sys_file` (
  `id` varchar(32) NOT NULL COMMENT '主键',
  `name` varchar(200) DEFAULT NULL COMMENT '原文件名',
  `group_id` varchar(32) DEFAULT NULL COMMENT '分组编号,对应多文件',
  `file_type` varchar(200) DEFAULT NULL COMMENT '文件类型',
  `suffix` varchar(200) DEFAULT NULL COMMENT '文件后缀',
  `size` int(11) DEFAULT NULL COMMENT '文件大小,单位字节',
  `preview_url` varchar(1000) DEFAULT NULL COMMENT '预览地址',
  `storage_type` varchar(200) DEFAULT NULL COMMENT '存储类型',
  `storage_url` varchar(200) DEFAULT NULL COMMENT '存储地址',
  `bucket_name` varchar(200) DEFAULT NULL COMMENT '桶名',
  `object_name` varchar(200) DEFAULT NULL COMMENT '桶内文件名',
  `visit_count` int(11) DEFAULT NULL COMMENT '访问次数',
  `sort` int(11) DEFAULT '0' COMMENT '排序值',
  `remarks` varchar(200) DEFAULT NULL COMMENT '备注',
  `gmt_create` timestamp NULL DEFAULT NULL COMMENT '创建时间',
  `gmt_modified` timestamp NULL DEFAULT NULL COMMENT '更新时间',
  `create_by` varchar(32) DEFAULT NULL COMMENT '创建人ID',
  `update_by` varchar(32) DEFAULT NULL COMMENT '修改人ID',
  `del_flag` varchar(32) DEFAULT '0' COMMENT '逻辑删除(0:未删除;null:已删除)',
  `tenant_id` int(11) DEFAULT NULL COMMENT '所属租户',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='系统基本信息--文件管理信息';

实体类 SysFile

package com.example.entity;

import java.io.Serial;
import java.time.LocalDateTime;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.extension.activerecord.Model;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * 系统基础信息--文件管理表
 * @author weimeilayer@gmail.com ✨
 * @date 💓💕2021年2月28日 🐬🐇 💓💕
 */
@Data
@TableName("sys_file")
@EqualsAndHashCode(callSuper = false)
@JsonIgnoreProperties(ignoreUnknown = true)
@Schema(description = "系统基础信息--文件管理表")
public class SysFile extends Model<SysFile> {
    @Serial
    private static final long serialVersionUID = 1L;
	/**
     * 主键
     */
    @TableId(value = "id", type = IdType.ASSIGN_ID)
	@Schema(description = "主键ID")
    private String id;
    /**
     * 原文件名
     */
    @Schema(description = "原文件名")
    private String name;
	/**
	 * 存储桶名称
	 */
    @Schema(description = "原始文件名")
	private String original;
    /**
     * 分组编号,用于对应多文件
     */
    @Schema(description = "分组编号,用于对应多文件")
    private String groupId;
    /**
     * 文件类型
     */
    @Schema(description = "文件类型")
    private String fileType;
    /**
     * 文件后缀
     */
    @Schema(description = "文件后缀")
    private String suffix;
    /**
     * 文件大小,单位字节
     */
    @Schema(description = "文件大小,单位字节")
    private Integer size;
    /**
     * 预览地址
     */
    @Schema(description = "预览地址")
    private String previewUrl;
    /**
     * 存储类型
     */
    @Schema(description = "存储类型")
    private String storageType;
    /**
     * 存储地址
     */
    @Schema(description = "存储地址")
    private String storageUrl;
    /**
     * 桶名
     */
    @Schema(description = "桶名")
    private String bucketName;
    /**
     * 桶内文件名
     */
    @Schema(description = "桶内文件名")
    private String objectName;
    /**
     * 访问次数
     */
    @Schema(description = "访问次数")
    private Integer visitCount;
    /**
     * 排序
     */
    @Schema(description = "排序")
    private Integer sort;
    /**
     * 备注
     */
    @Schema(description = "备注")
    private String remarks;
    /**
     * 逻辑删除(0:未删除;null:已删除)
     */
    @TableLogic
    @Schema(description = "逻辑删除(0:未删除;null:已删除)")
    @TableField(fill = FieldFill.INSERT)
    private String delFlag;
    /**
     * 创建人
     */
    @Schema(description = "创建人")
    @TableField(fill = FieldFill.INSERT)
    private String createBy;
    /**
     * 编辑人
     */
    @Schema(description = "编辑人")
    @TableField(fill = FieldFill.UPDATE)
    private String updateBy;
    /**
	 * 创建时间
	 */
	@TableField(fill = FieldFill.INSERT)
	@Schema(description = "创建时间")
	private LocalDateTime gmtCreate;
	/**
	 * 编辑时间
	 */
	@Schema(description = "编辑时间")
	@TableField(fill = FieldFill.UPDATE)
	private LocalDateTime gmtModified;

	/**
	 * 所属租户
	 */
	@Schema(description = "所属租户")
	private String tenantId;
}

创建接口类 SysFileService
package com.example.service;

import java.util.List;

import org.springframework.web.multipart.MultipartFile;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.example.dto.SysFileDto;
import com.example.entity.SysFile;
import com.example.utils.Result;
import com.example.vo.SysFileSelVo;
import com.example.vo.SysFileSortVo;

import jakarta.servlet.http.HttpServletResponse;

/**

  • 系统基础信息–文件管理服务类

  • @author weimeilayer@gmail.com ✨

  • @date 💓💕 2023年5月20日 🐬🐇 💓💕
    /
    public interface SysFileService extends IService {
    /
    *

    • 上传文件
    • @param files
    • @param groupId
    • @param isPreview
    • @param isPublic
    • @param sort
    • @return
      /
      Result uploadFile(MultipartFile[] files, String groupId, Boolean isPreview, Boolean isPublic, Integer sort);
      /
      *
    • 预览
    • @param groupId
    • @return
      /
      Result preview(String groupId);
      /
      *
    • 分组预览
    • @param groupId
    • @param previewList
    • @return
      /
      boolean preview(String groupId, List previewList);
      /
      *
    • 下载
    • @param response
    • @param id
      /
      void download(HttpServletResponse response, String id);
      /
      *
    • 删除文件
    • @param id
    • @return
      /
      Result delete(String id);
      /
      *
    • 排序
    • @param vo
    • @return
      */
      Result sort(SysFileSortVo vo);

    /**

    • 分页查询SysFile
    • @param selvo 查询参数
    • @return
      /
      public IPage getSysFileDtoPage(Page page,SysFileSelVo selvo);
      /
      *
    • 上传文件
    • @param file
    • @return
      */
      public Result uploadFile(MultipartFile file);

    /**

    • 读取文件
    • @param bucket 桶名称
    • @param fileName 文件名称
    • @param response 输出流
      */
      public void getFile(String bucket, String fileName, HttpServletResponse response);

    /**

    • 删除文件
    • @param id
    • @return
      */
      public Boolean deleteFile(String id);
      }

实现类 SysFileServiceImpl

package com.example.service.impl;

import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import com.amazonaws.services.s3.model.S3Object;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.example.config.MinioProperties;
import com.example.config.MinioTemplate;
import com.example.dto.SysFileDto;
import com.example.dto.SysFileSelDto;
import com.example.entity.SysFile;
import com.example.mapper.SysFileMapper;
import com.example.service.SysFileService;
import com.example.utils.Result;
import com.example.vo.SysFileSelVo;
import com.example.vo.SysFileSortVo;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Console;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import io.minio.GetObjectArgs;
import io.minio.GetPresignedObjectUrlArgs;
import io.minio.MinioClient;
import io.minio.PutObjectArgs;
import io.minio.RemoveObjectArgs;
import io.minio.StatObjectArgs;
import io.minio.StatObjectResponse;
import io.minio.http.Method;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import lombok.SneakyThrows;
/**
 * 系统基础信息--文件管理服务实现类
 *
 * @author weimeilayer@gmail.com ✨
 * @date 💓💕 2023年5月20日 🐬🐇 💓💕
 */
@Service
@AllArgsConstructor
public class SysFileServiceImpl extends ServiceImpl<SysFileMapper, SysFile> implements SysFileService {
    private final MinioClient minioClient;
    private final MinioTemplate minioTemplate;
    private final MinioProperties minioProperties;
    /**
     * 上传文件
     *
     * @param file
     * @return
     */
    @Override
    public Result uploadFile(MultipartFile file) {
        String fileId = IdUtil.simpleUUID();
        String originalFilename = new String(Objects.requireNonNull(file.getOriginalFilename()).getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
        String fileName = IdUtil.simpleUUID() + StrUtil.DOT + FileUtil.extName(originalFilename);
        Map<String, String> resultMap = new HashMap<>(4);
        resultMap.put("bucketName", minioProperties.getBucketName());
        resultMap.put("fileName", fileName);
        resultMap.put("originalFilename", originalFilename);
        resultMap.put("fileId", fileId);
        resultMap.put("url", String.format("/sysfile/%s/%s", minioProperties.getBucketName(), fileName));
        try (InputStream inputStream = file.getInputStream()) {
            minioTemplate.putObject(minioProperties.getBucketName(), fileName, inputStream, file.getSize(), file.getContentType());
            // 文件管理数据记录,收集管理追踪文件
            fileLog(file, fileName, fileId);
        } catch (Exception e) {
            log.error("上传失败", e);
            return Result.error(e.getLocalizedMessage());
        }
        return Result.success(resultMap);
    }

    /**
     * 读取文件
     *
     * @param bucket
     * @param fileName
     * @param response
     */
    @Override
    public void getFile(String bucket, String fileName, HttpServletResponse response) {
        try (S3Object s3Object = minioTemplate.getObject(bucket, fileName)) {
            response.setContentType("application/octet-stream; charset=UTF-8");
            IoUtil.copy(s3Object.getObjectContent(), response.getOutputStream());
        } catch (Exception e) {
            Console.log("文件读取异常: {}", e.getLocalizedMessage());
        }
    }

    /**
     * 删除文件
     *
     * @param id
     * @return
     */
    @Override
    @SneakyThrows
    @Transactional(rollbackFor = Exception.class)
    public Boolean deleteFile(String id) {
        SysFile file = this.getById(id);
        minioTemplate.removeObject(minioProperties.getBucketName(), file.getName());
        return file.updateById();
    }
    
    /**
     * 文件管理数据记录,收集管理追踪文件
     *
     * @param file     上传文件格式
     * @param fileName 文件名
     */
    private void fileLog(MultipartFile file, String fileName, String fileId) {
        String originalFilename = new String(Objects.requireNonNull(file.getOriginalFilename()).getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8);
        SysFile sysFile = new SysFile();
        sysFile.setId(fileId);
        sysFile.setName(fileName);
        sysFile.setOriginal(originalFilename);
        sysFile.setSize((int) file.getSize());
        sysFile.setFileType(FileUtil.extName(file.getOriginalFilename()));
        sysFile.setBucketName(minioProperties.getBucketName());
        this.save(sysFile);
    }
    /**
     * 分页查询SysFile
     * @param page
     * @param selvo 查询参数
     * @return
     */
    @Override
    public IPage<SysFileDto> getSysFileDtoPage(Page page, SysFileSelVo selvo) {
        return baseMapper.getSysFileDtoPage(page, selvo);
    }
    @Override
    public Result uploadFile(MultipartFile[] files, String groupId, Boolean isPreview, Boolean isPublic, Integer sort) {
        if (files == null || files.length == 0) {
            return Result.error("上传文件不能为空!");
        }
        // 是否公开
        isPublic = isPublic != null && isPublic;
        // 是否预览
        isPreview = isPreview != null && isPreview;
        // 桶名
        String bucketName = isPublic ? minioProperties.getPublicBucketName() : minioProperties.getBucketName();
        // 文件目录
        String dir = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM/dd/"));
        // 预览列表
        List<SysFileSelDto> previewList = new ArrayList<>();
        // 分组编号,用于对应多文件
        if (StringUtils.hasText(groupId)) {
            // 排序
            if (sort == null) {
                sort = baseMapper.getMaxSort(groupId);
                if (sort != null) {
                    sort++;
                } else {
                    sort = 0;
                }
            }
        } else {
            groupId = IdUtil.simpleUUID();
            sort = 0;
        }
        for (int i = 0; i < files.length; i++) {
            MultipartFile file = files[i];
            InputStream in = null;
            try {
                // 原文件名
                String oriFileName = new String(file.getOriginalFilename().getBytes("ISO-8859-1"), "UTF-8");
                // 后缀
                String suffix = "";
                if (StringUtils.hasText(oriFileName)) {
                    int index = oriFileName.lastIndexOf(StrPool.DOT);
                    if (index != -1) {
                        suffix = oriFileName.substring(index + 1);
                    }
                }
                // minio文件名
                String objectName = dir + IdUtil.simpleUUID() + StrPool.DOT + suffix;
                in = file.getInputStream();
                // 上传文件
                minioClient.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(in, file.getSize(), -1).contentType(file.getContentType()).build());
                long size = file.getSize();
                String id = IdUtil.simpleUUID();
                String previewUrl = null;
                if (isPreview) {
                    // 返回预览地址
                    previewUrl = getPreviewUrl(bucketName, objectName);
                    if (!StringUtils.hasText(previewUrl)) {
                        continue;
                    }
                    // 去掉后缀
                    if (isPublic) {
                        previewUrl = previewUrl.substring(0, previewUrl.indexOf("?"));
                    }
                    previewList.add(new SysFileSelDto(id, oriFileName, suffix, formatFileSize(size), previewUrl, i));
                }
                // minio文件信息插入数据库
                minioInsertToDb(id, oriFileName, groupId, file.getContentType(), suffix, (int) size, bucketName, objectName, previewUrl, i + sort);
            } catch (Exception e) {
                log.error(e.getMessage());
                return Result.error("上传失败!");
            } finally {
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        log.error(e.getMessage());
                    }
                }
            }
        }
        return Result.success("上传成功!",isPreview ? previewList : groupId);
    }

    
    @Override
    public Result preview(String groupId) {
        List<SysFileSelVo> previewList = new ArrayList<>();
        boolean preview = preview(groupId, previewList);
        return preview ? Result.success(previewList) : Result.error("预览失败!");
    }

    /**
     * 文件下载
     */
    @Override
    public void download(HttpServletResponse response, String id) {
        SysFile sysFile = baseMapper.selectOne(Wrappers.<SysFile>lambdaQuery().select(SysFile::getBucketName, SysFile::getObjectName, SysFile::getName).eq(SysFile::getDelFlag, 0).eq(SysFile::getId, id));
        if (sysFile == null) {
            return;
        }
        String objectName = sysFile.getObjectName();
        if (CharSequenceUtil.isBlank(objectName)) {
            return;
        }
        InputStream in = null;
        try {
            String bucketName = sysFile.getBucketName();
            // 获取对象信息
            StatObjectResponse stat = minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
            response.setContentType(stat.contentType());
            response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(sysFile.getName(), "UTF-8"));
            // 文件下载
            in = minioClient.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
            IoUtil.copy(in, response.getOutputStream());
        } catch (Exception e) {
            log.error(e.getMessage());
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    log.error(e.getMessage());
                }
            }
        }
    }

    @Override
    public Result delete(String id) {
        SysFile sysFile = baseMapper.selectOne(Wrappers.<SysFile>lambdaQuery().select(SysFile::getId, SysFile::getBucketName, SysFile::getObjectName).eq(SysFile::getDelFlag, 0).eq(SysFile::getId, id));
        if (sysFile == null) {
            return Result.error("未找到文件!");
        }
        String objectName = sysFile.getObjectName();
        if (CharSequenceUtil.isBlank(objectName)) {
            return Result.error("未找到文件!");
        }
        // 数据库删除文件
        int update = baseMapper.update(null, Wrappers.<SysFile>lambdaUpdate().set(SysFile::getDelFlag, null).set(SysFile::getGmtModified, LocalDateTime.now()).set(SysFile::getUpdateBy, sysFile.getId()).eq(SysFile::getId, id));
        if (update == 0) {
        	Result.error("删除失败!");
        }
        // 是否物理删除minio上文件
        if (minioProperties.isPhysicsDelete()) {
            try {
                minioClient.removeObject(RemoveObjectArgs.builder().bucket(sysFile.getBucketName()).object(objectName).build());
                // minio文件信息数据库逻辑删除
                minioDeleteToDb(objectName);
            } catch (Exception e) {
                log.error(e.getMessage());
                return Result.error("删除失败!");
            }
        }
        return Result.success("删除成功!");
    }

    @Override
    public Result sort(SysFileSortVo vo) {
        String id = vo.getId();
        Integer sort = vo.getSort();
        if (!StringUtils.hasText(id) || sort == null) {
            return Result.error("参数错误!");
        }
        SysFile sysFile = new SysFile();
        sysFile.setId(id);
        sysFile.setSort(sort);
        sysFile.updateById();
        return Result.success("编辑成功!");
    }

    /**
     * 文件大小处理
     *
     * @param fileSize 文件大小,单位B
     * @param fileSize
     * @return
     */
    private String formatFileSize(long fileSize) {
        DecimalFormat df = new DecimalFormat("#.00");
        String fileSizeizeString;
        String wrongSize = "0B";
        if (fileSize == 0) {
            return wrongSize;
        }
        if (fileSize < 1024) {
            fileSizeizeString = df.format((double) fileSize) + " B";
        } else if (fileSize < 1048576) {
            fileSizeizeString = df.format((double) fileSize / 1024) + " KB";
        } else if (fileSize < 1073741824) {
            fileSizeizeString = df.format((double) fileSize / 1048576) + " MB";
        } else {
            fileSizeizeString = df.format((double) fileSize / 1073741824) + " GB";
        }
        return fileSizeizeString;
    }

    /**
     * 获取预览地址路径
     *
     * @param bucketName 桶名
     * @param objectName minio文件名
     */
    private String getPreviewUrl(String bucketName, String objectName) {
        String previewUrl = null;
        try {
            // 预览地址
            previewUrl = minioClient.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().method(Method.GET).bucket(bucketName).object(objectName)
            // 24小时,默认7天
            .expiry(60 * 60 * 24).expiry(15).build());
            if (StrUtil.isNotBlank(minioProperties.getPreviewDomain())) {
                int count = 0;
                int index = -1;
                for (int i = 0; i < previewUrl.length(); i++) {
                    if (previewUrl.charAt(i) == '/') {
                        count++;
                        if (count == 3) {
                            index = i;
                            break;
                        }
                    }
                }
                if (index != -1) {
                    previewUrl = minioProperties.getPreviewDomain() + previewUrl.substring(index);
                }
            }
        } catch (Exception e) {
            Console.log(e.getMessage());
        }
        return previewUrl;
    }

    /**
     * minio文件信息插入数据库
     *
     * @param id         主键
     * @param name       原文件名
     * @param groupId    分组编号,用于对应多文件
     * @param fileType   fileType
     * @param suffix     suffix
     * @param size       文件大小,单位字节
     * @param objectName 桶内文件名
     */
    private void minioInsertToDb(String id, String name, String groupId, String fileType, String suffix, Integer size, String bucketName, String objectName, String previewUrl, int sort) {
        SysFile sysFile = new SysFile();
        sysFile.setId(id);
        sysFile.setName(name);
        sysFile.setGroupId(groupId);
        sysFile.setFileType(fileType);
        sysFile.setSuffix(suffix);
        sysFile.setSize(size);
        sysFile.setStorageType("minio");
        sysFile.setBucketName(bucketName);
        sysFile.setObjectName(objectName);
        sysFile.setVisitCount(0);
        sysFile.setPreviewUrl(previewUrl);
        sysFile.setSort(sort);
        baseMapper.insert(sysFile);
    }

    /**
     * minio文件信息数据库逻辑删除
     *
     * @param objectName 桶内文件名
     */
    private void minioDeleteToDb(String objectName) {
        SysFile sysFile = baseMapper.selectOne(Wrappers.<SysFile>lambdaQuery().select(SysFile::getId).eq(SysFile::getObjectName, objectName).eq(SysFile::getDelFlag, 0));
        if (sysFile != null) {
            baseMapper.update(null, Wrappers.<SysFile>lambdaUpdate().set(SysFile::getDelFlag, null).set(SysFile::getGmtModified, LocalDateTime.now()).eq(SysFile::getId, sysFile.getDelFlag()));
        }
    }

    /**
     * 预览
     *
     * @param groupId 分组id
     */
    @Override
    public boolean preview(String groupId, List<SysFileSelVo> previewList) {
        List<SysFile> sysFiles = baseMapper.selectList(Wrappers.<SysFile>lambdaQuery().select(SysFile::getId, SysFile::getName, SysFile::getBucketName, SysFile::getObjectName, SysFile::getSuffix, SysFile::getSize, SysFile::getSort).eq(SysFile::getDelFlag, 0).eq(SysFile::getGroupId, groupId).orderByAsc(SysFile::getSort));
        if (CollUtil.isEmpty(sysFiles)) {
            return false;
        }
        for (SysFile sysFile : sysFiles) {
            try {
                // 预览地址
                String previewUrl = getPreviewUrl(sysFile.getBucketName(), sysFile.getObjectName());
                // 文件大小并格式化
                String size = formatFileSize(sysFile.getSize());
                previewList.add(new SysFileSelVo(sysFile.getId(), sysFile.getName(), sysFile.getSuffix(), size, previewUrl, sysFile.getSort()));
            } catch (Exception e) {
                Console.log(e.getMessage());
            }
        }
        return true;
    }
}

创建SysFileMapper

package com.example.mapper;

import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cqcloud.platform.common.data.datascope.DynamicBaseMapper;
import com.cqcloud.platform.dto.SysFileDto;
import com.cqcloud.platform.entity.SysFile;
import com.cqcloud.platform.vo.SysFileSelVo;

/**
 * 系统基础信息--文件管理信息 Mapper 接口
 * @author weimeilayer@gmail.com ✨
 * @date 💓💕 2021年5月20日 🐬🐇 💓💕
 */
@Mapper
public interface SysFileMapper extends BaseMapper<SysFile> {
	/**
	 * 排序
	 * @param groupId
	 * @return
	 */
	public Integer getMaxSort(@Param("groupId") String groupId);
	/**
	 * 分页查询SysFile
	 * @param selvo 查询参数
	 * @return
	 */
	public IPage<SysFileDto> getSysFileDtoPage(@Param("page")Page page,@Param("query")SysFileSelVo selvo);
}

创建 SysFileMapper.xml

<?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.cqcloud.platform.mapper.SysFileMapper">
	<resultMap id="sysFileMap" type="com.example.dto.SysFileDto" >
	        <result property="id" 				column="id"/>
	        <result property="name" 			column="name"/>
	        <result property="groupId" 			column="group_id"/>
	        <result property="fileType" 		column="file_type"/>
	        <result property="suffix" 			column="suffix"/>
	        <result property="size" 			column="size"/>
	        <result property="previewUrl" 		column="preview_url"/>
	        <result property="storageType" 		column="storage_type"/>
	        <result property="storageUrl" 		column="storage_url"/>
	        <result property="bucketName" 		column="bucket_name"/>
	        <result property="objectName" 		column="object_name"/>
	        <result property="visitCount" 		column="visit_count"/>
	        <result property="sort" 			column="sort"/>
	        <result property="remarks" 			column="remarks"/>
	        <result property="gmtCreate" 		column="gmt_create"/>
	        <result property="gmtModified" 		column="gmt_modified"/>
	        <result property="createBy" 		column="create_by"/>
	        <result property="updateBy" 		column="update_by"/>
	        <result property="delFlag" 			column="del_flag"/>
	        <result property="tenantId" 		column="tenant_id"/>
	    </resultMap>
	<sql id="sysFileSql">
t.id,t.name,t.group_id,t.file_type,t.suffix,t.size,t.preview_url,t.storage_type,t.storage_url,t.bucket_name,t.object_name,t.visit_count,t.sort,t.remarks,t.gmt_create,t.gmt_modified,t.create_by,t.update_by,t.del_flag,t.tenant_id
	</sql>
	<select id="getSysFileDtoPage" resultMap="sysFileMap">
		select <include refid="sysFileSql" />
		from sys_file t
		<where>
			t.del_flag='0'
			<if test="query.name !=null and query.name !=''">
				and t.name LIKE '%' || #{name} || '%'
			</if>
		</where>
		order by t.gmt_create desc
	</select>
	<select id="getMaxSort" resultType="java.lang.Integer">
        select
        	max(sort)
        from
        	sys_file
        where
        	group_id = #{groupId}
        and del_flag = '0'
    </select>
</mapper>

创建 SysFileController

package com.example.controller;

import org.springdoc.core.annotations.ParameterObject;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.cqcloud.platform.common.log.annotation.SysLog;
import com.cqcloud.platform.service.SysFileService;
import com.cqcloud.platform.utils.Result;
import com.cqcloud.platform.vo.SysFileSelVo;
import com.cqcloud.platform.vo.SysFileSortVo;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;

/**
 * 系统基础信息--文件管理模块
 * @author weimeilayer@gmail.com
 * @date 2021-12-13 16:28:32
 */
@RestController
@AllArgsConstructor
@RequestMapping("/sysfile")
@SecurityRequirement(name = HttpHeaders.AUTHORIZATION)
public class SysFileController {

	private final SysFileService sysFileService;
	/**
	 * 上传文件 文件名采用uuid,避免原始文件名中带"-"符号导致下载的时候解析出现异常
	 * @param file 资源
	 * @return R(/bucketName/filename)
	 */
	@PostMapping("/uploadOnToken")
	public Result upload(@RequestParam("file") MultipartFile file) {
		if (file.isEmpty()) {
			return Result.error("文件上传失败");
		}
		return sysFileService.uploadFile(file);
	}

	/**
	 * 获取文件
	 * 
	 * @param bucket   桶名称
	 * @param fileName 文件空间/名称
	 * @param response
	 * @return
	 */
	@GetMapping("/{bucket}/{fileName}")
	public void file(@PathVariable String bucket, @PathVariable String fileName, HttpServletResponse response) {
		sysFileService.getFile(bucket, fileName, response);
	}
	
	/**
	 * 分页查询文件信息列表
	 * @param page
	 * @return
	 */
	@GetMapping("/pagelist")
	public Result getSysFileDtoPage(@ParameterObject Page page,@ParameterObject SysFileSelVo selvo) {
		return Result.success(sysFileService.getSysFileDtoPage(page, selvo));
	}
	
	/**
	 * 上传文件
	 * @param file    多文件
	 * @param groupId 分组id,用于文件追加
	 */
	@PostMapping("/upload")
	@Parameters({
		@Parameter(name = "groupId", description = "分组编号,用于对应多文件",example = "1"),
		@Parameter(name = "isPreview", description = "是否预览", required = true,example = "1"),
		@Parameter(name = "isPublic", description = "是否公开", required = true,example = "1"),
		@Parameter(name = "sort", description = "排序", required = true,example = "1")})
	public Result upload(@RequestParam MultipartFile[] file, String groupId, Boolean isPreview, Boolean isPublic,Integer sort) {
		return sysFileService.uploadFile(file, groupId, isPreview, isPublic, sort);
	}

	/**
	 * 批量预览文件
	 * @param groupId 文件名
	 */
	@GetMapping("/preview/{groupId}")
	public Result preview(@PathVariable("groupId") String groupId) {
		return sysFileService.preview(groupId);
	}

	/**
	 * 下载文件
	 * @param id 主键
	 */
	@GetMapping("/download/{id}")
	public void download(HttpServletResponse response, @PathVariable("id") String id) {
		sysFileService.download(response, id);
	}

	/**
	 * 删除文件
	 * @param id 主键
	 */
	@DeleteMapping("/delete/{id}")
	public Result delete(@PathVariable("id") String id) {
		return sysFileService.delete(id);
	}
	/**
	 * 文件排序
	 * @param vo 排序封装
	 */
	@PostMapping("/sort")
	public Result sort(@RequestBody SysFileSortVo vo) {
		return sysFileService.sort(vo);
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/752997.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

慢动作视频怎么制作?5种方法,轻松制作慢动作视频

在短视频风靡的当下&#xff0c;慢动作视频凭借其独特的视觉效果和引人入胜的节奏感&#xff0c;成为了吸引观众眼球的利器。你是否也想知道如何制作这种令人心动的慢动作视频呢&#xff1f;下面教大家5种能够制作出慢动作视频的方法&#xff0c;一起来学习下吧。 方法一&#…

python(二)手把手导入导出工程

目录 一、导入工程 二、安装相关库 1、打开requirements.txt 文件所在目录 2、ctrlshift鼠标右键&#xff0c;点击&#xff1a; 在此处打开PowerShell窗口 3、pip install -r requirements.txt &#xff0c;回车 三、导出环境 1、使用 requirements.txt导出环境中所有使用…

Spring AI之后,阿里推出Spring Cloud Alibaba AI,接入体验篇——Java也能方便用 AI

阿里推出Spring Cloud Alibaba AI&#xff0c;接入体验篇——Java也能方便用 AI 1.Spring AI2.Spring Cloud Alibaba AI3. 接入体验 1.Spring AI Spring AI 是 Spring 官方社区项目&#xff0c;旨在简化 Java AI 应用程序开发&#xff0c;让 Java 开发者像使用 Spring 开发普通…

【从零开始实现联邦学习】

1. 环境配置如下 python3.7pip install torchpip install torchvision 2. 代码如下 原书的代码存在一点bug&#xff0c;现已被作者修复 Client端代码如下 import torch.utils.dataclass Client(object):def __init__(self,conf,model,train_dataset,id1):self.conf conf …

【系统架构设计师】七、信息安全技术基础知识(网络安全技术|网络与信息安全风险|网络安全协议)

目录 一、网络安全技术 1.1 防火墙 1.2 入侵检测系统IDS 1.3 入侵防御系统IPS 1.4 杀毒软件 1.5 蜜罐系统 二、网络与信息安全风险 三、网络安全协议 四、相关推荐 五、历年真题练习 一、网络安全技术 1.1 防火墙 防火墙是在内部网络和外部因特网之间增加的一道安全…

使用自定义的shiro密码匹配器CredentialsMatcher完成密码验证

今天突然想研究一下shiro怎么匹配用户的密码。 我们使用shiro的API登录时&#xff0c;会先创建一个令牌对象&#xff0c;而经常用的令牌对象是UsernamePasswordToken&#xff0c;把用户输入的用户名和密码作为参数构建一个UsernamePasswordToken&#xff0c;然后通过Subject.l…

宏集物联网工控屏通过 S7 ETH 协议采集西门子 1200 PLC 数据

前言 为了实现和西门子PLC的数据交互&#xff0c;宏集物联网HMI集成了S7 PPI、S7 MPI、S7 Optimized、S7 ETH等多个驱动来适配西门子200、300、400、1200、1500、LOGO等系列PLC。 本文主要介绍宏集物联网HMI如何通过S7 ETH协议采集西门子1200 PLC的数据&#xff0c;文中详细介…

办公软件WPS与Office的区别

临近计算机考试很多同学在纠结我是报wps好&#xff1f;还是ms office好&#xff1f;下面就来详细说说。 1、wps属于国内金山公司的办公软件&#xff0c;里面包含word、Excel和PPT。考试是2021年开始的&#xff01; 2、MS&#xff08;Microsoft 微软&#xff09; office属于美…

网易游戏如何基于 Apache Doris 构建全新湖仓一体架构

导读&#xff1a;随着网易游戏品类及产品的快速发展&#xff0c;游戏数据分析场景面临着越来越多的挑战&#xff0c;为了保证系统性能和 SLA&#xff0c;要求引入新的组件来解决特定业务场景问题。为此&#xff0c;网易游戏引入 Apache Doris 构建了全新的湖仓一体架构。经过不…

精益生产转型攻略:如何平稳过渡,避免业务震荡?

在当今快速变化的市场环境中&#xff0c;越来越多的企业开始关注并尝试实施精益生产&#xff0c;以提升生产效率、降低成本并增强竞争力。然而&#xff0c;转型并非一蹴而就&#xff0c;如何在确保精益生产实施效果的同时&#xff0c;又避免对企业的现有业务流程和组织结构产生…

【C++进阶9】异常

一、C语言传统的处理错误的方式 终止程序&#xff0c;如assert 如发生内存错误&#xff0c;除0错误时就会终止程序返回错误码 需要程序员自己去查找对应的错误 z如系统的很多库的接口函数都是通 过把错误码放到errno中&#xff0c;表示错误 二、C异常概念 异常&#xff1a;函…

anaconda卸载过程中出现fail to run pre-unistall报错

问题&#xff1a; 在使用Uninstall-Anaconda3.exe卸载程序时&#xff0c;出现报错&#xff1a; 解决方案&#xff1a; 把文件夹移动到C盘用户文件夹后再运行卸载程序。即可正常运行程序。

ping 出现的结果判断

ICMP协议发送包的时候 常见的ping反馈结果&#xff1a; 连接建立成功&#xff0c;Reply from 目标地址 目标主机不可达&#xff0c;Destination host unreachable 直接不能出交换机&#xff0c;到达不了交换机 请求时间超时&#xff0c;Request timed out 服务器到交换机…

一名HR,在招聘嵌入式开发岗位,为什么感觉一年比一年难?

在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「嵌入式的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01; 1.嵌入式学用不一致, 高…

MySQL基础查询与复杂查询

基础查询 1、查询用户信息&#xff0c;仅显示用户的姓名与手机号&#xff0c;用中文显示列名。中文显示姓名列与手机号列。 2、根据商品名称进行模糊查询&#xff0c;模糊查询需要可以走索引&#xff0c;需要给出explain语句。使用explain测试给出的查询语句&#xff0c;需要显…

如何把mkv转成mp4?介绍一下将mkv转成MP4的几种方法

如何把mkv转成mp4&#xff1f;如果你有一个MKV格式的视频文件&#xff0c;但是需要将其转换为MP4格式以便更广泛地在各种设备和平台上播放和共享&#xff0c;你可以通过进行简单的文件格式转换来实现。转换MKV到MP4格式可以提供更好的兼容性&#xff0c;并确保你的视频文件能够…

vue2(vue-cli3x[vue.config.js])使用cesium新版(1.117.0)配置过程

看来很多解决方法都没有办法&#xff0c;最后终于。呜呜呜呜 这里我用的是vue-cli去搭建的项目的vue2 项目&#xff0c;其实不建议用vue2搭配cesium。因为目前cesium停止了对vue2的版本更新&#xff0c;现在默认安装都是vue3版本&#xff0c;因此需要控制版本&#xff0c;否则…

Rockchip RK3588 - Rockchip Linux Recovery rkupdate升级

---------------------------------------------------------------------------------------------------------------------------- 开发板 &#xff1a;ArmSoM-Sige7开发板eMMC &#xff1a;64GBLPDDR4 &#xff1a;8GB 显示屏 &#xff1a;15.6英寸HDMI接口显示屏u-boot &a…

【产品经理】订单处理9-台账库存管理

在订单处理过程中&#xff0c;台账库存的具体设计怎么做&#xff1f; 在订单处理过程中&#xff0c;分配仓库成功后要扣除仓库库存并计算商品缺货情况&#xff0c;仓库库存就是台账库存。 1&#xff0c;台账库存是针对某个仓库的库存&#xff0c;且台账库存只计算此商品SKU的库…

小马搬运物品-第13届蓝桥杯省赛Python真题精选

[导读]&#xff1a;超平老师的Scratch蓝桥杯真题解读系列在推出之后&#xff0c;受到了广大老师和家长的好评&#xff0c;非常感谢各位的认可和厚爱。作为回馈&#xff0c;超平老师计划推出《Python蓝桥杯真题解析100讲》&#xff0c;这是解读系列的第89讲。 小马搬运物品&…