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

news2024/11/25 18:45:04

在这里插入图片描述

统一版本管理

<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);
	}
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

谷歌如何进行失效链接建设?

失效链接建设是一种高效的外链建设策略&#xff0c;通过发现并利用失效链接来提升自己网站的SEO。以下是详细的步骤&#xff1a; 寻找失效页面&#xff1a;你需要使用SEO工具&#xff0c;如Ahrefs&#xff0c;来查找与你的网站内容相关的失效页面。这些页面可能是竞争对手的失…

【进阶篇-Day6:JAVA中Arrays工具类、排序算法、正则表达式的介绍】

目录 1、Arrays工具类2、排序算法2.1 冒泡排序2.2 选择排序2.3 二分查找&#xff08;折半查找&#xff09;&#xff08;1&#xff09;概念&#xff1a;&#xff08;2&#xff09;步骤&#xff1a; 3、正则表达式3.1 正则表达式的概念&#xff1a;3.2 正则表达式的格式&#xff…

CST--如何在PCB三维模型中自由创建离散端口

在使用CST电磁仿真软件进行PCB的三维建模时&#xff0c;经常会遇到不能自动创建离散端口的问题&#xff0c;原因有很多&#xff0c;比如&#xff1a;缺少元器件封装、开路端口、多端子模型等等&#xff0c;这个时候&#xff0c;很多人会选择手动进行端口创建&#xff0c;但是&a…

【C语言】--操作符详解

&#x1f32d;个人主页: 起名字真南 &#x1f37f;个人专栏:【数据结构初阶】 【C语言】 目录 1 算术操作符1.1 和 -1.2 *1.3 /1.4 % 2 赋值操作符 &#xff1a;2.1 复合赋值符 3 单目操作符3.1 和- - 4 强制类型转换5 printf 和 scanf5.1 printf5.1.1 基本用法5.1.2 占位符5.…

Processing入门教程

目录&#xff1a; 课程前言认识PROCESSING 关于像素图形代码色彩与填充练习交互关于setup()和draw()第一次进行移动进一步复杂的交互操作代码实现如下&#xff1a;进一步了解PROCESSING 变量使用变量系统内置变量条件语句逻辑运算符循环语句while循环for循环结构化 函数实参对…

信息系统项目管理师(项目整合管理)补充

项目管理信息系统&#xff1a;给项目提供了IT软件工具&#xff0c;例如进度计划软件工具、工作授权系统、配置管理系统、信息收集与发布系统&#xff0c;或其他基于IT技术的工具。以及进入其他在线信息系统&#xff08;如知识库&#xff09;的登录界面&#xff0c;支持自动收集…

如何解决跨国视频会议卡顿问题

在全球化日益加深的今天&#xff0c;跨国视频会议已成为企业沟通协作的重要工具。然而&#xff0c;许多企业在使用跨国视频会议时&#xff0c;经常会遇到卡顿和延迟问题。这不仅影响会议的顺利进行&#xff0c;还可能对企业的业务决策和项目进度造成不良影响。本文将探讨跨国视…

如何正确使用C#短信接口发送招生短信

群发短信对教育机构来讲虽然是个不错的招生工具,但怎么使用决定着生源转化效率,如果是为了单纯的发短信而发短信效率当然不好,那么如何正确使用招生群发短信呢?技巧才是关键! 教育短信发送较多的就是招生群发短信内容,而运营商对教育行业内容审核一般比较严格,需要短信公司特殊…

前端通过ResizeObserver来监听dom大小动态渲染echarts

export const GlobalResizeObserver (function () {const ATTR_NAME global-resizeobserver-keyconst attrValueToCallback {}function antiShake(fn, delay, immediate false) {let timer null//不能用箭头函数return function () {//在时间内重复调用的时候需要清空之前…

独立开发者系列(12)——下单与支付

做业务有个绕不开的业务逻辑&#xff0c;就是支付。这里总结一个基础的支付电商逻辑闭环流程&#xff0c;完成支付基础体系的实现。这里假定我们要实现的是一个独立的电商平台上允许用户在平台充值&#xff0c;其他的类似多多购物或者淘宝购物的流程逻辑。 数据表结构的逻辑设…

ElementUI搭建使用过程

1.ElementUI概述 Element&#xff0c;一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的桌面端组件库&#xff0c;相当于是css的框架 2.安装ElementUI 第一步&#xff1a;创建一个vue-cil项目 &#xff01;&#xff01;&#xff01;&#xff08;上篇文章已详细讲述搭建过…

线性代数知识点搜刮

求你别考太细... 目录 异乘变零定理 行列式转置 值不变 重要关系 中间相等&#xff0c;取两头 特征值公式 向量正交 点积为0 拉普拉斯定理 矩阵的秩 特征值和特征向量 |A|特征值的乘积 & tr(A)特征值的和 要记要背 增广矩阵 异乘变零定理 某行&#xff08;…

智能工业网络,需要何种工业以太网交换机作为支撑?

随着工业企业数字化及信息化的进一步深化升级&#xff0c;工业领域相关控制及信息系统的业务类型不断增加、复杂性不断提升&#xff0c;工业控制网络与工业信息网络也呈现融合趋势&#xff0c;具备支持多业务、多协议、多厂商设备和数据的互联互通、共网承载以及高质量传输能力…

C++ | Leetcode C++题解之第203题移除链表元素

题目&#xff1a; 题解&#xff1a; class Solution { public:ListNode* removeElements(ListNode* head, int val) {struct ListNode* dummyHead new ListNode(0, head);struct ListNode* temp dummyHead;while (temp->next ! NULL) {if (temp->next->val val) {…

p6spy 组件打印完整的 SQL 语句、执行耗时

一、前言 我们来配置一下 Mybatis Plus 打印 SQL 功能&#xff08;包括执行耗时&#xff09;&#xff0c;一方面可以了解到每个操作都具体执行的什么 SQL 语句&#xff0c; 另一方面通过打印执行耗时&#xff0c;也可以提前发现一些慢 SQL&#xff0c;提前做好优化&#xff0c…

CVPR 2024 | 双手协作双物体的数据集TACO:引领可泛化手物交互的新方向

论文题目&#xff1a; TACO: Benchmarking Generalizable Bimanual Tool-ACtion-Object Understanding 论文链接&#xff1a; https://arxiv.org/pdf/2401.08399.pdf 项目主页&#xff1a; https://taco2024.github.io/ 视频链接&#xff1a; https://www.youtube.com/watch…

机器学习笔记 人脸识别技术全面回顾和小结(1)

一、简述 人脸识别是视觉模式识别的一个细分问题。人类一直在识别视觉模式&#xff0c;我们通过眼睛获得视觉信息。这些信息被大脑识别为有意义的概念。对于计算机来说&#xff0c;无论是图片还是视频&#xff0c;它都是许多像素的矩阵。机器应该找出数据的某一部分在数据中代表…

OBD诊断(ISO15031) 02服务

文章目录 功能简介请求和响应1、read-supported PIDs1.1、请求1.2、肯定响应 2、read PID value1.1、请求1.2、肯定响应 3、同时请求多个PID4、同时读取多个PID数据 Parameter definition报文示例1、单个PID请求和读取2、多个PID请求和读取 功能简介 02服务&#xff0c;即 Req…

【技术追踪】UNest:一种用于非配对医学图像合成的新框架(MICCAI-2024)

前天看了一篇文章图像分割用diffusion&#xff0c;今天看了篇文章图像合成不用diffusion&#xff0c;你说说这~ 传送门&#xff1a;【技术追踪】SDSeg&#xff1a;医学图像的 Stable Diffusion 分割&#xff08;MICCAI-2024&#xff09; UNest&#xff1a;UNet结构的Transforme…

收银系统源码-千呼新零售【分销商城】

千呼新零售2.0系统是零售行业连锁店一体化收银系统&#xff0c;包括线下收银线上商城连锁店管理ERP管理商品管理供应商管理会员营销等功能为一体&#xff0c;线上线下数据全部打通。 适用于商超、便利店、水果、生鲜、母婴、服装、零食、百货、宠物等连锁店使用。 详细介绍请…