文件服务器搭建和多文件系统集成切换

一、文件服务器搭建

1、安装docker
#清理已安装的docker
sudo yum remove docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-engine
#依赖
sudo yum install -y yum-utils device-mapper-persistent-data lvm2
#镜像源
sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
#依赖
sudo yum install docker-ce docker-ce-cli containerd.io

2、docker搭建minio文件服务器

docker pull minio/minio


docker run -it --name minio -p 9000:9000 -p 9001:9001 -d \
-v /usr/local/docker/minio/data:/data \
-v /usr/local/docker/minio/config:/root/.minio \
-e 'MINIO_ROOT_USER=admin' \
-e 'MINIO_ROOT_PASSWORD=admin123' \
minio/minio server /data --console-address ":9001"


9000[API](https://so.csdn.net/so/search?q=API&spm=1001.2101.3001.7020)调用端口
9001:控制台端口,不指定他会随机生成。`注:控制台用9000也能打开,最终会跳转到9001,效果一样的`

二、编写文件服务器代码

1、文件服务器相关的表

CREATE TABLE `sys_oss` (
  `oss_id` bigint NOT NULL AUTO_INCREMENT COMMENT '对象存储主键',
  `file_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '文件名',
  `original_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '原名',
  `file_suffix` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '文件后缀名',
  `url` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'URL地址',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '' COMMENT '上传人',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '' COMMENT '更新人',
  `service` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'minio' COMMENT '服务商',
  PRIMARY KEY (`oss_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=338 DEFAULT CHARSET=utf8mb3 COMMENT='OSS对象存储表';

CREATE TABLE `sys_oss_config` (
  `oss_config_id` bigint NOT NULL COMMENT '主建',
  `config_key` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '配置key',
  `access_key` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '' COMMENT 'accessKey',
  `secret_key` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '' COMMENT '秘钥',
  `bucket_name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '' COMMENT '桶名称',
  `prefix` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '' COMMENT '前缀',
  `endpoint` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '' COMMENT '访问站点',
  `is_https` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT 'N' COMMENT '是否https(Y=是,N=否)',
  `region` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '' COMMENT '域',
  `status` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '1' COMMENT '状态(0=正常,1=停用)',
  `ext1` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '' COMMENT '扩展字段',
  `create_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '' COMMENT '创建者',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT '' COMMENT '更新者',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `remark` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`oss_config_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COMMENT='对象存储配置表';

INSERT INTO `sys_oss_config`(`oss_config_id`, `config_key`, `access_key`, `secret_key`, `bucket_name`, `prefix`, `endpoint`, `is_https`, `region`, `status`, `ext1`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1, 'minio', 'admin', '123456789a', 'childrenlocal', '', 'http://192.168.1.222:9000', 'N', '', '0', '', 'admin', '2022-05-09 13:43:25', 'admin', '2022-05-11 15:09:00', NULL);
INSERT INTO `sys_oss_config`(`oss_config_id`, `config_key`, `access_key`, `secret_key`, `bucket_name`, `prefix`, `endpoint`, `is_https`, `region`, `status`, `ext1`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (2, 'qiniu', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi', '', 'http://XXX.XXXX.com', 'N', 'z0', '1', '', 'admin', '2022-05-09 13:43:25', 'admin', '2022-05-09 13:43:25', NULL);
INSERT INTO `sys_oss_config`(`oss_config_id`, `config_key`, `access_key`, `secret_key`, `bucket_name`, `prefix`, `endpoint`, `is_https`, `region`, `status`, `ext1`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (3, 'aliyun', 'LTAIdZ3EOJrxfJ9q', 'yNSI1dUI4tLkMoStsTPcoE3qOY9s5d', 'taishun-zhym', '', 'http://oss-cn-beijing.aliyuncs.com', 'N', '', '1', '', 'admin', '2022-05-09 13:43:25', 'admin', '2022-05-16 09:53:49', NULL);
INSERT INTO `sys_oss_config`(`oss_config_id`, `config_key`, `access_key`, `secret_key`, `bucket_name`, `prefix`, `endpoint`, `is_https`, `region`, `status`, `ext1`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (4, 'qcloud', 'XXXXXXXXXXXXXXX', 'XXXXXXXXXXXXXXX', 'ruoyi-1250000000', '', 'http://cos.ap-beijing.myqcloud.com', 'N', 'ap-beijing', '1', '', 'admin', '2022-05-09 13:43:25', 'admin', '2022-05-09 13:43:25', NULL);

2、baseEntity

package com.ruoyi.common.core.domain;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.TableField;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * Entity基类
 *
 * @author Lion Li
 */

@Data
public class BaseEntity implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 搜索值
     */
    @ApiModelProperty(value = "搜索值")
    @TableField(exist = false)
    private String searchValue;

    /**
     * 创建者
     */
    @ApiModelProperty(value = "创建者")
    @TableField(fill = FieldFill.INSERT)
    private String createBy;

    /**
     * 创建时间
     */
    @ApiModelProperty(value = "创建时间")
    @TableField(fill = FieldFill.INSERT)
    private Date createTime;

    /**
     * 更新者
     */
    @ApiModelProperty(value = "更新者")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private String updateBy;

    /**
     * 更新时间
     */
    @ApiModelProperty(value = "更新时间")
    @TableField(fill = FieldFill.INSERT_UPDATE)
    private Date updateTime;

    /**
     * 请求参数
     */
    @ApiModelProperty(value = "请求参数")
    @TableField(exist = false)
    private Map<String, Object> params = new HashMap<>();

}

OSS对象存储对象

package com.ruoyi.system.domain;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * OSS对象存储对象
 *
 * @author Lion Li
 */
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_oss")
public class SysOss extends BaseEntity {

    /**
     * 对象存储主键
     */
    @TableId(value = "oss_id")
    private Long ossId;

    /**
     * 文件名
     */
    private String fileName;

    /**
     * 原名
     */
    private String originalName;

    /**
     * 文件后缀名
     */
    private String fileSuffix;

    /**
     * URL地址
     */
    private String url;

    /**
     * 服务商
     */
    private String service;

}

对象存储配置对象

package com.ruoyi.system.domain;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.ruoyi.common.core.domain.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.experimental.Accessors;

/**
 * 对象存储配置对象 sys_oss_config
 *
 * @author Lion Li
 */
@Data
@EqualsAndHashCode(callSuper = true)
@TableName("sys_oss_config")
public class SysOssConfig extends BaseEntity {

    /**
     * 主建
     */
    @TableId(value = "oss_config_id")
    private Long ossConfigId;

    /**
     * 配置key
     */
    private String configKey;

    /**
     * accessKey
     */
    private String accessKey;

    /**
     * 秘钥
     */
    private String secretKey;

    /**
     * 桶名称
     */
    private String bucketName;

    /**
     * 前缀
     */
    private String prefix;

    /**
     * 访问站点
     */
    private String endpoint;

    /**
     * 是否https(0否 1是)
     */
    private String isHttps;

    /**
     * 域
     */
    private String region;

    /**
     * 状态(0正常 1停用)
     */
    private String status;

    /**
     * 扩展字段
     */
    private String ext1;

    /**
     * 备注
     */
    private String remark;

}

对象存储常量

package com.ruoyi.oss.constant;

import java.util.Arrays;
import java.util.List;

/**
 * 对象存储常量
 *
 * @author Lion Li
 */
public interface OssConstant {

    /**
     * OSS模块KEY
     */
    String SYS_OSS_KEY = "sys_oss:";

    /**
     * 对象存储配置KEY
     */
    String OSS_CONFIG_KEY = "OssConfig";

    /**
     * 缓存配置KEY
     */
    String CACHE_CONFIG_KEY = SYS_OSS_KEY + OSS_CONFIG_KEY;

    /**
     * 预览列表资源开关Key
     */
    String PEREVIEW_LIST_RESOURCE_KEY = "sys.oss.previewListResource";

    /**
     * 系统数据ids
     */
    List<Integer> SYSTEM_DATA_IDS = Arrays.asList(1, 2, 3, 4);

    /**
     * https 状态
     */
    String IS_HTTPS = "Y";

}

上传返回体

package com.ruoyi.oss.entity;

import lombok.Builder;
import lombok.Data;

/**
 * 上传返回体
 *
 * @author Lion Li
 */
@Data
@Builder
public class UploadResult {

    /**
     * 文件路径
     */
    private String url;

    /**
     * 文件名
     */
    private String filename;
}

对象存储服务商枚举

package com.ruoyi.oss.enumd;

import com.ruoyi.oss.service.impl.AliyunOssStrategy;
import com.ruoyi.oss.service.impl.MinioOssStrategy;
import com.ruoyi.oss.service.impl.QcloudOssStrategy;
import com.ruoyi.oss.service.impl.QiniuOssStrategy;
import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * 对象存储服务商枚举
 *
 * @author Lion Li
 */
@Getter
@AllArgsConstructor
public enum OssEnumd {

    /**
     * 七牛云
     */
    QINIU("qiniu", QiniuOssStrategy.class),

    /**
     * 阿里云
     */
    ALIYUN("aliyun", AliyunOssStrategy.class),

    /**
     * 腾讯云
     */
    QCLOUD("qcloud", QcloudOssStrategy.class),

    /**
     * minio
     */
    MINIO("minio", MinioOssStrategy.class);

    private final String value;

    private final Class<?> beanClass;

    public static OssEnumd find(String value) {
        for (OssEnumd enumd : values()) {
            if (enumd.getValue().equals(value)) {
                return enumd;
            }
        }
        return null;
    }

}

minio策略配置

/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 ([email protected])
 */
package com.ruoyi.oss.enumd;

import lombok.AllArgsConstructor;
import lombok.Getter;

/**
 * minio策略配置
 *
 * @author Lion Li
 */
@Getter
@AllArgsConstructor
public enum PolicyType {

    /**
     * 只读
     */
    READ("read-only"),

    /**
     * 只写
     */
    WRITE("write-only"),

    /**
     * 读写
     */
    READ_WRITE("read-write");

    /**
     * 类型
     */
    private final String type;

}

OSS异常类

package com.ruoyi.oss.exception;

/**
 * OSS异常类
 *
 * @author Lion Li
 */
public class OssException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    public OssException(String msg) {
        super(msg);
    }

}

文件上传Factory

package com.ruoyi.oss.factory;

import com.ruoyi.common.utils.JsonUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.redis.RedisUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.oss.constant.OssConstant;
import com.ruoyi.oss.enumd.OssEnumd;
import com.ruoyi.oss.exception.OssException;
import com.ruoyi.oss.properties.OssProperties;
import com.ruoyi.oss.service.IOssStrategy;
import com.ruoyi.oss.service.abstractd.AbstractOssStrategy;
import lombok.extern.slf4j.Slf4j;

/**
 * 文件上传Factory
 *
 * @author Lion Li
 */
@Slf4j
public class OssFactory {

    /**
     * 初始化工厂
     */
    public static void init() {
        log.info("初始化OSS工厂");
        RedisUtils.subscribe(OssConstant.CACHE_CONFIG_KEY, String.class, type -> {
            AbstractOssStrategy strategy = getStrategy(type);
            // 未初始化不处理
            if (strategy.isInit) {
                refresh(type);
                log.info("订阅刷新OSS配置 => " + type);
            }
        });
    }

    /**
     * 获取默认实例
     */
    public static IOssStrategy instance() {
        // 获取redis 默认类型
        String type = RedisUtils.getCacheObject(OssConstant.CACHE_CONFIG_KEY);
        if (StringUtils.isEmpty(type)) {
            throw new OssException("文件存储服务类型无法找到!");
        }
        return instance(type);
    }

    /**
     * 根据类型获取实例
     */
    public static IOssStrategy instance(String type) {
        OssEnumd enumd = OssEnumd.find(type);
        if (enumd == null) {
            throw new OssException("文件存储服务类型无法找到!");
        }
        AbstractOssStrategy strategy = getStrategy(type);
        if (!strategy.isInit) {
            refresh(type);
        }
        return strategy;
    }

    private static void refresh(String type) {
        Object json = RedisUtils.getCacheObject(OssConstant.SYS_OSS_KEY + type);
        OssProperties properties = JsonUtils.parseObject(json.toString(), OssProperties.class);
        if (properties == null) {
            throw new OssException("系统异常, '" + type + "'配置信息不存在!");
        }
        getStrategy(type).init(properties);
    }

    private static AbstractOssStrategy getStrategy(String type) {
        OssEnumd enumd = OssEnumd.find(type);
        return (AbstractOssStrategy) SpringUtils.getBean(enumd.getBeanClass());
    }

}

OSS对象存储 配置属性

package com.ruoyi.oss.properties;

import lombok.Data;

/**
 * OSS对象存储 配置属性
 *
 * @author Lion Li
 */
@Data
public class OssProperties {

    /**
     * 域名
     */
    private String endpoint;

    /**
     * 前缀
     */
    private String prefix;

    /**
     * ACCESS_KEY
     */
    private String accessKey;

    /**
     * SECRET_KEY
     */
    private String secretKey;

    /**
     * 存储空间名
     */
    private String bucketName;

    /**
     * 存储区域
     */
    private String region;

    /**
     * 是否https(Y=是,N=否)
     */
    private String isHttps;

}

对象存储策略(支持七牛、阿里云、腾讯云、minio)

package com.ruoyi.oss.service.abstractd;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.IdUtil;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.oss.entity.UploadResult;
import com.ruoyi.oss.enumd.OssEnumd;
import com.ruoyi.oss.properties.OssProperties;
import com.ruoyi.oss.service.IOssStrategy;

import java.io.InputStream;

/**
 * 对象存储策略(支持七牛、阿里云、腾讯云、minio)
 *
 * @author Lion Li
 */
public abstract class AbstractOssStrategy implements IOssStrategy {

    protected OssProperties properties;
    public boolean isInit = false;

    public void init(OssProperties properties) {
        this.properties = properties;
    }

    @Override
    public abstract void createBucket();

    @Override
    public abstract OssEnumd getServiceType();

    public String getPath(String prefix, String suffix) {
        // 生成uuid
        String uuid = IdUtil.fastSimpleUUID();
        // 文件路径
        String path = DateUtils.datePath() + "/" + uuid;
        if (StringUtils.isNotBlank(prefix)) {
            path = prefix + "/" + path;
        }
        return path + suffix;
    }

    @Override
    public abstract UploadResult upload(byte[] data, String path, String contentType);

    @Override
    public abstract void delete(String path);

    @Override
    public UploadResult upload(InputStream inputStream, String path, String contentType) {
        byte[] data = IoUtil.readBytes(inputStream);
        return this.upload(data, path, contentType);
    }

    @Override
    public abstract UploadResult uploadSuffix(byte[] data, String suffix, String contentType);

    @Override
    public abstract UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType);

    /**
     * 获取域名访问链接
     *
     * @return 域名访问链接
     */
    public abstract String getEndpointLink();
}

阿里云存储策略

package com.ruoyi.oss.service.impl;

import com.aliyun.oss.ClientConfiguration;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.common.auth.DefaultCredentialProvider;
import com.aliyun.oss.common.comm.Protocol;
import com.aliyun.oss.model.CannedAccessControlList;
import com.aliyun.oss.model.CreateBucketRequest;
import com.aliyun.oss.model.ObjectMetadata;
import com.aliyun.oss.model.PutObjectRequest;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.oss.constant.OssConstant;
import com.ruoyi.oss.entity.UploadResult;
import com.ruoyi.oss.enumd.OssEnumd;
import com.ruoyi.oss.exception.OssException;
import com.ruoyi.oss.properties.OssProperties;
import com.ruoyi.oss.service.abstractd.AbstractOssStrategy;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.InputStream;

/**
 * 阿里云存储策略
 *
 * @author Lion Li
 */
@Component
public class AliyunOssStrategy extends AbstractOssStrategy {

    private OSSClient client;

    @Override
    public void init(OssProperties ossProperties) {
        super.init(ossProperties);
        try {
            ClientConfiguration configuration = new ClientConfiguration();
            if (OssConstant.IS_HTTPS.equals(ossProperties.getIsHttps())) {
                configuration.setProtocol(Protocol.HTTPS);
            }
            DefaultCredentialProvider credentialProvider = new DefaultCredentialProvider(
                properties.getAccessKey(), properties.getSecretKey());
            client = new OSSClient(properties.getEndpoint(), credentialProvider, configuration);
            createBucket();
        } catch (Exception e) {
            throw new OssException("阿里云存储配置错误! 请检查系统配置:[" + e.getMessage() + "]");
        }
        isInit = true;
    }

    @Override
    public void createBucket() {
        try {
            String bucketName = properties.getBucketName();
            if (client.doesBucketExist(bucketName)) {
                return;
            }
            CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
            createBucketRequest.setCannedACL(CannedAccessControlList.PublicRead);
            client.createBucket(createBucketRequest);
        } catch (Exception e) {
            throw new OssException("创建Bucket失败, 请核对阿里云配置信息:[" + e.getMessage() + "]");
        }
    }

    @Override
    public OssEnumd getServiceType() {
        return OssEnumd.ALIYUN;
    }

    @Override
    public UploadResult upload(byte[] data, String path, String contentType) {
        return upload(new ByteArrayInputStream(data), path, contentType);
    }

    @Override
    public UploadResult upload(InputStream inputStream, String path, String contentType) {
        try {
            ObjectMetadata metadata = new ObjectMetadata();
            metadata.setContentType(contentType);
            client.putObject(new PutObjectRequest(properties.getBucketName(), path, inputStream, metadata));
        } catch (Exception e) {
            throw new OssException("上传文件失败,请检查阿里云配置信息:[" + e.getMessage() + "]");
        }
        return UploadResult.builder().url(getEndpointLink() + "/" + path).filename(path).build();    }

    @Override
    public void delete(String path) {
        path = path.replace(getEndpointLink() + "/", "");
        try {
            client.deleteObject(properties.getBucketName(), path);
        } catch (Exception e) {
            throw new OssException("上传文件失败,请检查阿里云配置信息:[" + e.getMessage() + "]");
        }
    }

    @Override
    public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) {
        return upload(data, getPath(properties.getPrefix(), suffix), contentType);
    }

    @Override
    public UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType) {
        return upload(inputStream, getPath(properties.getPrefix(), suffix), contentType);
    }

    @Override
    public String getEndpointLink() {
        String endpoint = properties.getEndpoint();
        StringBuilder sb = new StringBuilder(endpoint);
        if (StringUtils.containsAnyIgnoreCase(endpoint, "http://")) {
            sb.insert(7, properties.getBucketName() + ".");
        } else if (StringUtils.containsAnyIgnoreCase(endpoint, "https://")) {
            sb.insert(8, properties.getBucketName() + ".");
        } else {
            throw new OssException("Endpoint配置错误");
        }
        return sb.toString();
    }
}

minio存储策略

package com.ruoyi.oss.service.impl;

import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.oss.constant.OssConstant;
import com.ruoyi.oss.entity.UploadResult;
import com.ruoyi.oss.enumd.OssEnumd;
import com.ruoyi.oss.enumd.PolicyType;
import com.ruoyi.oss.exception.OssException;
import com.ruoyi.oss.properties.OssProperties;
import com.ruoyi.oss.service.abstractd.AbstractOssStrategy;
import io.minio.*;
import io.minio.http.HttpUtils;
import okhttp3.HttpUrl;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.InputStream;

/**
 * minio存储策略
 *
 * @author Lion Li
 */
@Component
public class MinioOssStrategy extends AbstractOssStrategy {

    private MinioClient minioClient;

    @Override
    public void init(OssProperties ossProperties) {
        super.init(ossProperties);
        try {
            MinioClient.Builder builder = MinioClient.builder();
            if (OssConstant.IS_HTTPS.equals(ossProperties.getIsHttps())) {
                HttpUrl url = HttpUtils.getBaseUrl(properties.getEndpoint())
                    .newBuilder().scheme("https").build();
                builder.endpoint(url);
            } else {
                builder.endpoint(properties.getEndpoint());
            }
            minioClient = builder.credentials(properties.getAccessKey(), properties.getSecretKey()).build();
            createBucket();
        } catch (Exception e) {
            throw new OssException("Minio存储配置错误! 请检查系统配置:[" + e.getMessage() + "]");
        }
        isInit = true;
    }

    @Override
    public void createBucket() {
        try {
            String bucketName = properties.getBucketName();
            boolean exists = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
            if (exists) {
                return;
            }
            // 不存在就创建桶
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
            minioClient.setBucketPolicy(SetBucketPolicyArgs.builder()
                .bucket(bucketName)
                .config(getPolicy(bucketName, PolicyType.READ))
                .build());
        } catch (Exception e) {
            throw new OssException("创建Bucket失败, 请核对Minio配置信息:[" + e.getMessage() + "]");
        }
    }

    @Override
    public OssEnumd getServiceType() {
        return OssEnumd.MINIO;
    }

    @Override
    public UploadResult upload(byte[] data, String path, String contentType) {
        return upload(new ByteArrayInputStream(data), path, contentType);
    }

    @Override
    public UploadResult upload(InputStream inputStream, String path, String contentType) {
        try {
            // 解决 inputStream.available() 再 socket 下传输延迟问题 导致获取数值不精确
            Thread.sleep(1000);
            minioClient.putObject(PutObjectArgs.builder()
                .bucket(properties.getBucketName())
                .object(path)
                .contentType(StringUtils.blankToDefault(contentType, MediaType.APPLICATION_OCTET_STREAM_VALUE))
                .stream(inputStream, inputStream.available(), -1)
                .build());
        } catch (Exception e) {
            throw new OssException("上传文件失败,请核对Minio配置信息:[" + e.getMessage() + "]");
        }
        return UploadResult.builder().url(getEndpointLink() + "/" + path).filename(path).build();
    }

    @Override
    public void delete(String path) {
        path = path.replace(getEndpointLink() + "/", "");
        try {
            minioClient.removeObject(RemoveObjectArgs.builder()
                .bucket(properties.getBucketName())
                .object(path)
                .build());
        } catch (Exception e) {
            throw new OssException(e.getMessage());
        }
    }

    @Override
    public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) {
        return upload(data, getPath(properties.getPrefix(), suffix), contentType);
    }

    @Override
    public UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType) {
        return upload(inputStream, getPath(properties.getPrefix(), suffix), contentType);
    }

    @Override
    public String getEndpointLink() {
        return properties.getEndpoint() + "/" + properties.getBucketName();
    }

    private String getPolicy(String bucketName, PolicyType policyType) {
        StringBuilder builder = new StringBuilder();
        builder.append("{\n");
        builder.append("    \"Statement\": [\n");
        builder.append("        {\n");
        builder.append("            \"Action\": [\n");
        if (policyType == PolicyType.WRITE) {
            builder.append("                \"s3:GetBucketLocation\",\n");
            builder.append("                \"s3:ListBucketMultipartUploads\"\n");
        } else if (policyType == PolicyType.READ_WRITE) {
            builder.append("                \"s3:GetBucketLocation\",\n");
            builder.append("                \"s3:ListBucket\",\n");
            builder.append("                \"s3:ListBucketMultipartUploads\"\n");
        } else {
            builder.append("                \"s3:GetBucketLocation\"\n");
        }
        builder.append("            ],\n");
        builder.append("            \"Effect\": \"Allow\",\n");
        builder.append("            \"Principal\": \"*\",\n");
        builder.append("            \"Resource\": \"arn:aws:s3:::");
        builder.append(bucketName);
        builder.append("\"\n");
        builder.append("        },\n");
        if (PolicyType.READ.equals(policyType)) {
            builder.append("        {\n");
            builder.append("            \"Action\": [\n");
            builder.append("                \"s3:ListBucket\"\n");
            builder.append("            ],\n");
            builder.append("            \"Effect\": \"Deny\",\n");
            builder.append("            \"Principal\": \"*\",\n");
            builder.append("            \"Resource\": \"arn:aws:s3:::");
            builder.append(bucketName);
            builder.append("\"\n");
            builder.append("        },\n");
        }
        builder.append("        {\n");
        builder.append("            \"Action\": ");
        switch (policyType) {
            case WRITE:
                builder.append("[\n");
                builder.append("                \"s3:AbortMultipartUpload\",\n");
                builder.append("                \"s3:DeleteObject\",\n");
                builder.append("                \"s3:ListMultipartUploadParts\",\n");
                builder.append("                \"s3:PutObject\"\n");
                builder.append("            ],\n");
                break;
            case READ_WRITE:
                builder.append("[\n");
                builder.append("                \"s3:AbortMultipartUpload\",\n");
                builder.append("                \"s3:DeleteObject\",\n");
                builder.append("                \"s3:GetObject\",\n");
                builder.append("                \"s3:ListMultipartUploadParts\",\n");
                builder.append("                \"s3:PutObject\"\n");
                builder.append("            ],\n");
                break;
            default:
                builder.append("\"s3:GetObject\",\n");
                break;
        }
        builder.append("            \"Effect\": \"Allow\",\n");
        builder.append("            \"Principal\": \"*\",\n");
        builder.append("            \"Resource\": \"arn:aws:s3:::");
        builder.append(bucketName);
        builder.append("/*\"\n");
        builder.append("        }\n");
        builder.append("    ],\n");
        builder.append("    \"Version\": \"2012-10-17\"\n");
        builder.append("}\n");
        return builder.toString();
    }
}

腾讯云存储策略

package com.ruoyi.oss.service.impl;

import com.qcloud.cos.COSClient;
import com.qcloud.cos.ClientConfig;
import com.qcloud.cos.auth.BasicCOSCredentials;
import com.qcloud.cos.auth.COSCredentials;
import com.qcloud.cos.http.HttpProtocol;
import com.qcloud.cos.model.*;
import com.qcloud.cos.region.Region;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.oss.constant.OssConstant;
import com.ruoyi.oss.entity.UploadResult;
import com.ruoyi.oss.enumd.OssEnumd;
import com.ruoyi.oss.exception.OssException;
import com.ruoyi.oss.properties.OssProperties;
import com.ruoyi.oss.service.abstractd.AbstractOssStrategy;
import org.springframework.stereotype.Component;

import java.io.ByteArrayInputStream;
import java.io.InputStream;

/**
 * 腾讯云存储策略
 *
 * @author Lion Li
 */
@Component
public class QcloudOssStrategy extends AbstractOssStrategy {

    private COSClient client;

    @Override
    public void init(OssProperties ossProperties) {
        super.init(ossProperties);
        try {
            COSCredentials credentials = new BasicCOSCredentials(
                properties.getAccessKey(), properties.getSecretKey());
            // 初始化客户端配置
            ClientConfig clientConfig = new ClientConfig();
            // 设置bucket所在的区域,华南:gz 华北:tj 华东:sh
            clientConfig.setRegion(new Region(properties.getRegion()));
            if (OssConstant.IS_HTTPS.equals(properties.getIsHttps())) {
                clientConfig.setHttpProtocol(HttpProtocol.https);
            } else {
                clientConfig.setHttpProtocol(HttpProtocol.http);
            }
            client = new COSClient(credentials, clientConfig);
            createBucket();
        } catch (Exception e) {
            throw new OssException("腾讯云存储配置错误! 请检查系统配置:[" + e.getMessage() + "]");
        }
        isInit = true;
    }

    @Override
    public void createBucket() {
        try {
            String bucketName = properties.getBucketName();
            if (client.doesBucketExist(bucketName)) {
                return;
            }
            CreateBucketRequest createBucketRequest = new CreateBucketRequest(bucketName);
            createBucketRequest.setCannedAcl(CannedAccessControlList.PublicRead);
            client.createBucket(createBucketRequest);
        } catch (Exception e) {
            throw new OssException("创建Bucket失败, 请核对腾讯云配置信息:[" + e.getMessage() + "]");
        }
    }

    @Override
    public OssEnumd getServiceType() {
        return OssEnumd.QCLOUD;
    }

    @Override
    public UploadResult upload(byte[] data, String path, String contentType) {
        return upload(new ByteArrayInputStream(data), path, contentType);
    }

    @Override
    public UploadResult upload(InputStream inputStream, String path, String contentType) {
        try {
            ObjectMetadata metadata = new ObjectMetadata();
            metadata.setContentType(contentType);
            client.putObject(new PutObjectRequest(properties.getBucketName(), path, inputStream, metadata));
        } catch (Exception e) {
            throw new OssException("上传文件失败,请检查腾讯云配置信息:[" + e.getMessage() + "]");
        }
        return UploadResult.builder().url(getEndpointLink() + "/" + path).filename(path).build();
    }

    @Override
    public void delete(String path) {
        path = path.replace(getEndpointLink() + "/", "");
        try {
            client.deleteObject(new DeleteObjectRequest(properties.getBucketName(), path));
        } catch (Exception e) {
            throw new OssException("上传文件失败,请检腾讯云查配置信息:[" + e.getMessage() + "]");
        }
    }

    @Override
    public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) {
        return upload(data, getPath(properties.getPrefix(), suffix), contentType);
    }

    @Override
    public UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType) {
        return upload(inputStream, getPath(properties.getPrefix(), suffix), contentType);
    }

    @Override
    public String getEndpointLink() {
        String endpoint = properties.getEndpoint();
        StringBuilder sb = new StringBuilder(endpoint);
        if (StringUtils.containsAnyIgnoreCase(endpoint, "http://")) {
            sb.insert(7, properties.getBucketName() + ".");
        } else if (StringUtils.containsAnyIgnoreCase(endpoint, "https://")) {
            sb.insert(8, properties.getBucketName() + ".");
        } else {
            throw new OssException("Endpoint配置错误");
        }
        return sb.toString();
    }
}

七牛云存储策略

package com.ruoyi.oss.service.impl;

import cn.hutool.core.util.ArrayUtil;
import com.qiniu.http.Response;
import com.qiniu.storage.BucketManager;
import com.qiniu.storage.Configuration;
import com.qiniu.storage.Region;
import com.qiniu.storage.UploadManager;
import com.qiniu.util.Auth;
import com.ruoyi.oss.constant.OssConstant;
import com.ruoyi.oss.entity.UploadResult;
import com.ruoyi.oss.enumd.OssEnumd;
import com.ruoyi.oss.exception.OssException;
import com.ruoyi.oss.properties.OssProperties;
import com.ruoyi.oss.service.abstractd.AbstractOssStrategy;
import org.springframework.stereotype.Component;

import java.io.InputStream;

/**
 * 七牛云存储策略
 *
 * @author Lion Li
 */
@Component
public class QiniuOssStrategy extends AbstractOssStrategy {

    private UploadManager uploadManager;
    private BucketManager bucketManager;
    private Auth auth;


    @Override
    public void init(OssProperties ossProperties) {
        super.init(ossProperties);
        try {
            Configuration config = new Configuration(getRegion(properties.getRegion()));
            // https设置
            config.useHttpsDomains = OssConstant.IS_HTTPS.equals(properties.getIsHttps());
            uploadManager = new UploadManager(config);
            auth = Auth.create(properties.getAccessKey(), properties.getSecretKey());
            bucketManager = new BucketManager(auth, config);
            createBucket();
        } catch (Exception e) {
            throw new OssException("七牛云存储配置错误! 请检查系统配置:[" + e.getMessage() + "]");
        }
        isInit = true;
    }

    @Override
    public void createBucket() {
        try {
            String bucketName = properties.getBucketName();
            if (ArrayUtil.contains(bucketManager.buckets(), bucketName)) {
                return;
            }
            bucketManager.createBucket(bucketName, properties.getRegion());
        } catch (Exception e) {
            throw new OssException("创建Bucket失败, 请核对七牛云配置信息:[" + e.getMessage() + "]");
        }
    }

    @Override
    public OssEnumd getServiceType() {
        return OssEnumd.QINIU;
    }

    @Override
    public UploadResult upload(byte[] data, String path, String contentType) {
        try {
            String token = auth.uploadToken(properties.getBucketName());
            Response res = uploadManager.put(data, path, token, null, contentType, false);
            if (!res.isOK()) {
                throw new RuntimeException("上传七牛出错:" + res.error);
            }
        } catch (Exception e) {
            throw new OssException("上传文件失败,请核对七牛配置信息:[" + e.getMessage() + "]");
        }
        return UploadResult.builder().url(getEndpointLink() + "/" + path).filename(path).build();
    }

    @Override
    public void delete(String path) {
        try {
            path = path.replace(getEndpointLink() + "/", "");
            Response res = bucketManager.delete(properties.getBucketName(), path);
            if (!res.isOK()) {
                throw new RuntimeException("删除七牛文件出错:" + res.error);
            }
        } catch (Exception e) {
            throw new OssException(e.getMessage());
        }
    }

    @Override
    public UploadResult uploadSuffix(byte[] data, String suffix, String contentType) {
        return upload(data, getPath(properties.getPrefix(), suffix), contentType);
    }

    @Override
    public UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType) {
        return upload(inputStream, getPath(properties.getPrefix(), suffix), contentType);
    }

    @Override
    public String getEndpointLink() {
        return properties.getEndpoint();
    }

    private Region getRegion(String region) {
        switch (region) {
            case "z0":
                return Region.region0();
            case "z1":
                return Region.region1();
            case "z2":
                return Region.region2();
            case "na0":
                return Region.regionNa0();
            case "as0":
                return Region.regionAs0();
            default:
                return Region.autoRegion();
        }
    }

}

对象存储策略

package com.ruoyi.oss.service;

import com.ruoyi.oss.entity.UploadResult;
import com.ruoyi.oss.enumd.OssEnumd;

import java.io.InputStream;

/**
 * 对象存储策略
 *
 * @author Lion Li
 */
public interface IOssStrategy {

    /**
     * 创建存储桶
     */
    void createBucket();

    /**
     * 获取服务商类型
     * @return 对象存储服务商枚举
     */
    OssEnumd getServiceType();

    /**
     * 文件上传
     *
     * @param data 文件字节数组
     * @param path 文件路径,包含文件名
     * @param contentType 文件类型
     * @return 返回http地址
     */
    UploadResult upload(byte[] data, String path, String contentType);

    /**
     * 文件删除
     *
     * @param path 文件路径,包含文件名
     */
    void delete(String path);

    /**
     * 文件上传
     *
     * @param data   文件字节数组
     * @param suffix 后缀
     * @param contentType 文件类型
     * @return 返回http地址
     */
    UploadResult uploadSuffix(byte[] data, String suffix, String contentType);

    /**
     * 文件上传
     *
     * @param inputStream 字节流
     * @param path        文件路径,包含文件名
     * @param contentType 文件类型
     * @return 返回http地址
     */
    UploadResult upload(InputStream inputStream, String path, String contentType);

    /**
     * 文件上传
     *
     * @param inputStream 字节流
     * @param suffix      后缀
     * @param contentType 文件类型
     * @return 返回http地址
     */
    UploadResult uploadSuffix(InputStream inputStream, String suffix, String contentType);

}

对象存储控制器

package com.ruoyi.web.controller.system;


import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.http.HttpException;
import cn.hutool.http.HttpUtil;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.validate.QueryGroup;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.file.FileUtils;
import com.ruoyi.system.domain.SysOss;
import com.ruoyi.system.domain.bo.SysOssBo;
import com.ruoyi.system.domain.vo.SysOssVo;
import com.ruoyi.system.service.ISysOssService;
import io.swagger.annotations.*;
import lombok.RequiredArgsConstructor;
import org.springframework.http.MediaType;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import javax.validation.constraints.NotEmpty;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * 文件上传 控制层
 *
 * @author Lion Li
 */
@Validated
@Api(value = "对象存储控制器", tags = {"对象存储管理"})
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/oss")
public class SysOssController extends BaseController {

    private final ISysOssService iSysOssService;

    /**
     * 查询OSS对象存储列表
     */
    @ApiOperation("查询OSS对象存储列表")
    @SaCheckPermission("backend:system:oss:list")
    @GetMapping("/list")
    public TableDataInfo<SysOssVo> list(@Validated(QueryGroup.class) SysOssBo bo, PageQuery pageQuery) {
        return iSysOssService.queryPageList(bo, pageQuery);
    }

    /**
     * 上传OSS对象存储
     */
    @ApiOperation("上传OSS对象存储")
    @ApiImplicitParams({
        @ApiImplicitParam(name = "file", value = "文件", paramType = "form", dataTypeClass = File.class, required = true)
    })
    @SaCheckPermission("backend:system:oss:upload")
    @Log(title = "OSS对象存储", businessType = BusinessType.INSERT)
    @PostMapping("/upload")
    public R<Map<String, String>> upload(@RequestPart("file") MultipartFile file) {
        if (ObjectUtil.isNull(file)) {
            throw new ServiceException("上传文件不能为空");
        }
        SysOss oss = iSysOssService.upload(file);
        Map<String, String> map = new HashMap<>(2);
        map.put("url", oss.getUrl());
        map.put("fileName", oss.getOriginalName());
        map.put("ossId", oss.getOssId().toString());
        return R.ok(map);
    }

    @ApiOperation("下载OSS对象存储")
    @SaCheckPermission("backend:system:oss:download")
    @GetMapping("/download/{ossId}")
    public void download(@ApiParam("OSS对象ID") @PathVariable Long ossId, HttpServletResponse response) throws IOException {
        SysOss sysOss = iSysOssService.getById(ossId);
        if (ObjectUtil.isNull(sysOss)) {
            throw new ServiceException("文件数据不存在!");
        }
        response.reset();
        FileUtils.setAttachmentResponseHeader(response, sysOss.getOriginalName());
        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8");
        long data;
        try {
            data = HttpUtil.download(sysOss.getUrl(), response.getOutputStream(), false);
        } catch (HttpException e) {
            if (e.getMessage().contains("403")) {
                throw new ServiceException("无读取权限, 请在对应的OSS开启'公有读'权限!");
            } else {
                throw new ServiceException(e.getMessage());
            }
        }
        response.setContentLength(Convert.toInt(data));
    }

    /**
     * 删除OSS对象存储
     */
    @ApiOperation("删除OSS对象存储")
    @SaCheckPermission("backend:system:oss:remove")
    @Log(title = "OSS对象存储", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ossIds}")
    public R<Void> remove(@ApiParam("OSS对象ID串")
                                   @NotEmpty(message = "主键不能为空")
                                   @PathVariable Long[] ossIds) {
        return toAjax(iSysOssService.deleteWithValidByIds(Arrays.asList(ossIds), true) ? 1 : 0);
    }

}

文件上传 服务层

package com.ruoyi.system.service;

import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.system.domain.SysOss;
import com.ruoyi.system.domain.bo.SysOssBo;
import com.ruoyi.system.domain.vo.SysOssVo;
import org.springframework.web.multipart.MultipartFile;

import java.util.Collection;

/**
 * 文件上传 服务层
 *
 * @author Lion Li
 */
public interface ISysOssService {

    TableDataInfo<SysOssVo> queryPageList(SysOssBo sysOss, PageQuery pageQuery);

    SysOss getById(Long ossId);

    SysOss upload(MultipartFile file);

    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);

}

文件上传 服务层实现

package com.ruoyi.system.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.oss.entity.UploadResult;
import com.ruoyi.oss.factory.OssFactory;
import com.ruoyi.oss.service.IOssStrategy;
import com.ruoyi.system.domain.SysOss;
import com.ruoyi.system.domain.bo.SysOssBo;
import com.ruoyi.system.domain.vo.SysOssVo;
import com.ruoyi.system.mapper.SysOssMapper;
import com.ruoyi.system.service.ISysOssService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.Collection;
import java.util.List;
import java.util.Map;

/**
 * 文件上传 服务层实现
 *
 * @author Lion Li
 */
@RequiredArgsConstructor
@Service
public class SysOssServiceImpl implements ISysOssService {

    private final SysOssMapper baseMapper;

    @Override
    public TableDataInfo<SysOssVo> queryPageList(SysOssBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<SysOss> lqw = buildQueryWrapper(bo);
        Page<SysOssVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        return TableDataInfo.build(result);
    }

    private LambdaQueryWrapper<SysOss> buildQueryWrapper(SysOssBo bo) {
        Map<String, Object> params = bo.getParams();
        LambdaQueryWrapper<SysOss> lqw = Wrappers.lambdaQuery();
        lqw.like(StringUtils.isNotBlank(bo.getFileName()), SysOss::getFileName, bo.getFileName());
        lqw.like(StringUtils.isNotBlank(bo.getOriginalName()), SysOss::getOriginalName, bo.getOriginalName());
        lqw.eq(StringUtils.isNotBlank(bo.getFileSuffix()), SysOss::getFileSuffix, bo.getFileSuffix());
        lqw.eq(StringUtils.isNotBlank(bo.getUrl()), SysOss::getUrl, bo.getUrl());
        lqw.between(params.get("beginCreateTime") != null && params.get("endCreateTime") != null,
            SysOss::getCreateTime, params.get("beginCreateTime"), params.get("endCreateTime"));
        lqw.eq(StringUtils.isNotBlank(bo.getCreateBy()), SysOss::getCreateBy, bo.getCreateBy());
        lqw.eq(StringUtils.isNotBlank(bo.getService()), SysOss::getService, bo.getService());
        return lqw;
    }

    @Override
    public SysOss getById(Long ossId) {
        return baseMapper.selectById(ossId);
    }

    @Override
    public SysOss upload(MultipartFile file) {
        String originalfileName = file.getOriginalFilename();
        String suffix = StringUtils.substring(originalfileName, originalfileName.lastIndexOf("."), originalfileName.length());
        IOssStrategy storage = OssFactory.instance();
        UploadResult uploadResult;
        try {
            uploadResult = storage.uploadSuffix(file.getBytes(), suffix, file.getContentType());
        } catch (IOException e) {
            throw new ServiceException(e.getMessage());
        }
        // 保存文件信息
        SysOss oss = new SysOss();
        oss.setUrl(uploadResult.getUrl());
        oss.setFileSuffix(suffix);
        oss.setFileName(uploadResult.getFilename());
        oss.setOriginalName(originalfileName);
        oss.setService(storage.getServiceType().getValue());
        baseMapper.insert(oss);
        return oss;
    }

    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if (isValid) {
            // 做一些业务上的校验,判断是否需要校验
        }
        List<SysOss> list = baseMapper.selectBatchIds(ids);
        for (SysOss sysOss : list) {
            IOssStrategy storage = OssFactory.instance(sysOss.getService());
            storage.delete(sysOss.getUrl());
        }
        return baseMapper.deleteBatchIds(ids) > 0;
    }

}

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.system.mapper.SysOssMapper">

    <resultMap type="com.ruoyi.system.domain.SysOss" id="SysOssResult">
        <result property="ossId" column="oss_id"/>
        <result property="fileName" column="file_name"/>
        <result property="fileSuffix" column="file_suffix"/>
        <result property="url" column="url"/>
        <result property="createTime" column="create_time"/>
        <result property="createBy" column="create_by"/>
        <result property="updateTime" column="update_time"/>
        <result property="updateBy" column="update_by"/>
        <result property="service" column="service"/>
    </resultMap>


</mapper>

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.system.mapper.SysOssConfigMapper">

    <resultMap type="com.ruoyi.system.domain.SysOssConfig" id="SysOssConfigResult">
        <result property="ossConfigId" column="oss_config_id"/>
        <result property="configKey" column="config_key"/>
        <result property="accessKey" column="access_key"/>
        <result property="secretKey" column="secret_key"/>
        <result property="bucketName" column="bucket_name"/>
        <result property="prefix" column="prefix"/>
        <result property="endpoint" column="endpoint"/>
        <result property="isHttps" column="is_https"/>
        <result property="region" column="region"/>
        <result property="status" column="status"/>
        <result property="ext1" column="ext1"/>
        <result property="createBy" column="create_by"/>
        <result property="createTime" column="create_time"/>
        <result property="updateBy" column="update_by"/>
        <result property="updateTime" column="update_time"/>
        <result property="remark" column="remark"/>
    </resultMap>


</mapper>

对象存储配置Controller

package com.ruoyi.web.controller.system;

import cn.dev33.satoken.annotation.SaCheckPermission;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.annotation.RepeatSubmit;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.validate.AddGroup;
import com.ruoyi.common.core.validate.EditGroup;
import com.ruoyi.common.core.validate.QueryGroup;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.system.domain.bo.SysOssConfigBo;
import com.ruoyi.system.domain.vo.SysOssConfigVo;
import com.ruoyi.system.service.ISysOssConfigService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.Arrays;

/**
 * 对象存储配置Controller
 *
 * @author Lion Li
 * @author 孤舟烟雨
 * @date 2021-08-13
 */
@Validated
@Api(value = "对象存储配置控制器", tags = {"对象存储配置管理"})
@RequiredArgsConstructor
@RestController
@RequestMapping("/system/oss/config")
public class SysOssConfigController extends BaseController {

    private final ISysOssConfigService iSysOssConfigService;

    /**
     * 查询对象存储配置列表
     */
    @ApiOperation("查询对象存储配置列表")
    @SaCheckPermission("backend:system:oss:list")
    @GetMapping("/list")
    public TableDataInfo<SysOssConfigVo> list(@Validated(QueryGroup.class) SysOssConfigBo bo, PageQuery pageQuery) {
        return iSysOssConfigService.queryPageList(bo, pageQuery);
    }

    /**
     * 获取对象存储配置详细信息
     */
    @ApiOperation("获取对象存储配置详细信息")
    @SaCheckPermission("backend:system:oss:query")
    @GetMapping("/{ossConfigId}")
    public R<SysOssConfigVo> getInfo(@ApiParam("OSS配置ID")
                                              @NotNull(message = "主键不能为空")
                                              @PathVariable("ossConfigId") Long ossConfigId) {
        return R.ok(iSysOssConfigService.queryById(ossConfigId));
    }

    /**
     * 新增对象存储配置
     */
    @ApiOperation("新增对象存储配置")
    @SaCheckPermission("backend:system:oss:add")
    @Log(title = "对象存储配置", businessType = BusinessType.INSERT)
    @RepeatSubmit()
    @PostMapping()
    public R<Void> add(@Validated(AddGroup.class) @RequestBody SysOssConfigBo bo) {
        return toAjax(iSysOssConfigService.insertByBo(bo) ? 1 : 0);
    }

    /**
     * 修改对象存储配置
     */
    @ApiOperation("修改对象存储配置")
    @SaCheckPermission("backend:system:oss:edit")
    @Log(title = "对象存储配置", businessType = BusinessType.UPDATE)
    @RepeatSubmit()
    @PutMapping()
    public R<Void> edit(@Validated(EditGroup.class) @RequestBody SysOssConfigBo bo) {
        return toAjax(iSysOssConfigService.updateByBo(bo) ? 1 : 0);
    }

    /**
     * 删除对象存储配置
     */
    @ApiOperation("删除对象存储配置")
    @SaCheckPermission("backend:system:oss:remove")
    @Log(title = "对象存储配置", businessType = BusinessType.DELETE)
    @DeleteMapping("/{ossConfigIds}")
    public R<Void> remove(@ApiParam("OSS配置ID串")
                                   @NotEmpty(message = "主键不能为空")
                                   @PathVariable Long[] ossConfigIds) {
        return toAjax(iSysOssConfigService.deleteWithValidByIds(Arrays.asList(ossConfigIds), true) ? 1 : 0);
    }

    /**
     * 状态修改
     */
    @ApiOperation("状态修改")
    @SaCheckPermission("backend:system:oss:edit")
    @Log(title = "对象存储状态修改", businessType = BusinessType.UPDATE)
    @PutMapping("/changeStatus")
    public R<Void> changeStatus(@RequestBody SysOssConfigBo bo) {
        return toAjax(iSysOssConfigService.updateOssConfigStatus(bo));
    }
}

BaseController

package com.ruoyi.common.core.controller;

import com.ruoyi.common.core.domain.R;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.helper.LoginHelper;
import com.ruoyi.common.utils.StringUtils;

/**
 * web层通用数据处理
 *
 * @author Lion Li
 */
public class BaseController {

    /**
     * 响应返回结果
     *
     * @param rows 影响行数
     * @return 操作结果
     */
    protected R<Void> toAjax(int rows) {
        return rows > 0 ? R.ok() : R.fail();
    }

    /**
     * 响应返回结果
     *
     * @param result 结果
     * @return 操作结果
     */
    protected R<Void> toAjax(boolean result) {
        return result ? R.ok() : R.fail();
    }

    /**
     * 页面跳转
     */
    public String redirect(String url) {
        return StringUtils.format("redirect:{}", url);
    }

    /**
     * 获取用户缓存信息
     */
    public LoginUser getLoginUser() {
        return LoginHelper.getLoginUser();
    }

    /**
     * 获取登录用户id
     */
    public Long getUserId() {
        return LoginHelper.getUserId();
    }

    /**
     * 获取登录部门id
     */
    public Long getDeptId() {
        return LoginHelper.getDeptId();
    }

    /**
     * 获取登录用户名
     */
    public String getUsername() {
        return LoginHelper.getUsername();
    }
}

对象存储配置Service业务层处理

package com.ruoyi.system.service;

import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.system.domain.bo.SysOssConfigBo;
import com.ruoyi.system.domain.vo.SysOssConfigVo;

import java.util.Collection;

/**
 * 对象存储配置Service接口
 *
 * @author Lion Li
 * @author 孤舟烟雨
 * @date 2021-08-13
 */
public interface ISysOssConfigService {

    /**
     * 初始化OSS配置
     */
    void init();

    /**
     * 查询单个
     */
    SysOssConfigVo queryById(Long ossConfigId);

    /**
     * 查询列表
     */
    TableDataInfo<SysOssConfigVo> queryPageList(SysOssConfigBo bo, PageQuery pageQuery);


    /**
     * 根据新增业务对象插入对象存储配置
     *
     * @param bo 对象存储配置新增业务对象
     * @return
     */
    Boolean insertByBo(SysOssConfigBo bo);

    /**
     * 根据编辑业务对象修改对象存储配置
     *
     * @param bo 对象存储配置编辑业务对象
     * @return
     */
    Boolean updateByBo(SysOssConfigBo bo);

    /**
     * 校验并删除数据
     *
     * @param ids     主键集合
     * @param isValid 是否校验,true-删除前校验,false-不校验
     * @return
     */
    Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid);

    /**
     * 启用停用状态
     */
    int updateOssConfigStatus(SysOssConfigBo bo);

}




package com.ruoyi.system.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.PageQuery;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.JsonUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.redis.RedisUtils;
import com.ruoyi.oss.constant.OssConstant;
import com.ruoyi.oss.factory.OssFactory;
import com.ruoyi.system.domain.SysOssConfig;
import com.ruoyi.system.domain.bo.SysOssConfigBo;
import com.ruoyi.system.domain.vo.SysOssConfigVo;
import com.ruoyi.system.mapper.SysOssConfigMapper;
import com.ruoyi.system.service.ISysOssConfigService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Collection;
import java.util.List;

/**
 * 对象存储配置Service业务层处理
 *
 * @author Lion Li
 * @author 孤舟烟雨
 * @date 2021-08-13
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class SysOssConfigServiceImpl implements ISysOssConfigService {

    private final SysOssConfigMapper baseMapper;

    /**
     * 项目启动时,初始化参数到缓存,加载配置类
     */
    @Override
    public void init() {
        List<SysOssConfig> list = baseMapper.selectList();
        // 加载OSS初始化配置
        for (SysOssConfig config : list) {
            String configKey = config.getConfigKey();
            if ("0".equals(config.getStatus())) {
                RedisUtils.setCacheObject(OssConstant.CACHE_CONFIG_KEY, configKey);
            }
            setConfigCache(true, config);
        }
        // 初始化OSS工厂
        OssFactory.init();
    }

    @Override
    public SysOssConfigVo queryById(Long ossConfigId) {
        return baseMapper.selectVoById(ossConfigId);
    }

    @Override
    public TableDataInfo<SysOssConfigVo> queryPageList(SysOssConfigBo bo, PageQuery pageQuery) {
        LambdaQueryWrapper<SysOssConfig> lqw = buildQueryWrapper(bo);
        Page<SysOssConfigVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
        return TableDataInfo.build(result);
    }


    private LambdaQueryWrapper<SysOssConfig> buildQueryWrapper(SysOssConfigBo bo) {
        LambdaQueryWrapper<SysOssConfig> lqw = Wrappers.lambdaQuery();
        lqw.eq(StringUtils.isNotBlank(bo.getConfigKey()), SysOssConfig::getConfigKey, bo.getConfigKey());
        lqw.like(StringUtils.isNotBlank(bo.getBucketName()), SysOssConfig::getBucketName, bo.getBucketName());
        lqw.eq(StringUtils.isNotBlank(bo.getStatus()), SysOssConfig::getStatus, bo.getStatus());
        return lqw;
    }

    @Override
    public Boolean insertByBo(SysOssConfigBo bo) {
        SysOssConfig config = BeanUtil.toBean(bo, SysOssConfig.class);
        validEntityBeforeSave(config);
        return setConfigCache(baseMapper.insert(config) > 0, config);
    }

    @Override
    public Boolean updateByBo(SysOssConfigBo bo) {
        SysOssConfig config = BeanUtil.toBean(bo, SysOssConfig.class);
        validEntityBeforeSave(config);
        LambdaUpdateWrapper<SysOssConfig> luw = new LambdaUpdateWrapper<>();
        luw.set(StringUtils.isBlank(config.getPrefix()), SysOssConfig::getPrefix, "");
        luw.set(StringUtils.isBlank(config.getRegion()), SysOssConfig::getRegion, "");
        luw.set(StringUtils.isBlank(config.getExt1()), SysOssConfig::getExt1, "");
        luw.eq(SysOssConfig::getOssConfigId, config.getOssConfigId());
        return setConfigCache(baseMapper.update(config, luw) > 0, config);
    }

    /**
     * 保存前的数据校验
     */
    private void validEntityBeforeSave(SysOssConfig entity) {
        if (StringUtils.isNotEmpty(entity.getConfigKey())
            && UserConstants.NOT_UNIQUE.equals(checkConfigKeyUnique(entity))) {
            throw new ServiceException("操作配置'" + entity.getConfigKey() + "'失败, 配置key已存在!");
        }
    }

    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if (isValid) {
            if (CollUtil.containsAny(ids, OssConstant.SYSTEM_DATA_IDS)) {
                throw new ServiceException("系统内置, 不可删除!");
            }
        }
        List<SysOssConfig> list = Lists.newArrayList();
        for (Long configId : ids) {
            SysOssConfig config = baseMapper.selectById(configId);
            list.add(config);
        }
        boolean flag = baseMapper.deleteBatchIds(ids) > 0;
        if (flag) {
            list.stream().forEach(sysOssConfig -> {
                RedisUtils.deleteObject(getCacheKey(sysOssConfig.getConfigKey()));
            });
        }
        return flag;
    }

    /**
     * 判断configKey是否唯一
     */
    private String checkConfigKeyUnique(SysOssConfig sysOssConfig) {
        long ossConfigId = ObjectUtil.isNull(sysOssConfig.getOssConfigId()) ? -1L : sysOssConfig.getOssConfigId();
        SysOssConfig info = baseMapper.selectOne(new LambdaQueryWrapper<SysOssConfig>()
            .select(SysOssConfig::getOssConfigId, SysOssConfig::getConfigKey)
            .eq(SysOssConfig::getConfigKey, sysOssConfig.getConfigKey()));
        if (ObjectUtil.isNotNull(info) && info.getOssConfigId() != ossConfigId) {
            return UserConstants.NOT_UNIQUE;
        }
        return UserConstants.UNIQUE;
    }

    /**
     * 启用禁用状态
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int updateOssConfigStatus(SysOssConfigBo bo) {
        SysOssConfig sysOssConfig = BeanUtil.toBean(bo, SysOssConfig.class);
        int row = baseMapper.update(null, new LambdaUpdateWrapper<SysOssConfig>()
            .set(SysOssConfig::getStatus, "1"));
        row += baseMapper.updateById(sysOssConfig);
        if (row > 0) {
            RedisUtils.setCacheObject(OssConstant.CACHE_CONFIG_KEY, sysOssConfig.getConfigKey());
        }
        return row;
    }

    /**
     * 设置cache key
     *
     * @param configKey 参数键
     * @return 缓存键key
     */
    private String getCacheKey(String configKey) {
        return OssConstant.SYS_OSS_KEY + configKey;
    }

    /**
     * 如果操作成功 则更新缓存
     *
     * @param flag   操作状态
     * @param config 配置
     * @return 返回操作状态
     */
    private boolean setConfigCache(boolean flag, SysOssConfig config) {
        if (flag) {
            RedisUtils.setCacheObject(
                getCacheKey(config.getConfigKey()),
                JsonUtils.toJsonString(config));
            RedisUtils.publish(OssConstant.CACHE_CONFIG_KEY, config.getConfigKey(), msg -> {
                log.info("发布刷新OSS配置 => " + msg);
            });
        }
        return flag;
    }
}


package com.ruoyi.system.mapper;

import com.ruoyi.common.core.mapper.BaseMapperPlus;
import com.ruoyi.system.domain.SysOssConfig;
import com.ruoyi.system.domain.vo.SysOssConfigVo;

/**
 * 对象存储配置Mapper接口
 *
 * @author Lion Li
 * @author 孤舟烟雨
 * @date 2021-08-13
 */
public interface SysOssConfigMapper extends BaseMapperPlus<SysOssConfigMapper, SysOssConfig, SysOssConfigVo> {

}

    package com.ruoyi.system.mapper;
    
    import com.ruoyi.common.core.mapper.BaseMapperPlus;
    import com.ruoyi.system.domain.SysOss;
    import com.ruoyi.system.domain.vo.SysOssVo;
    
    /**
     * 文件上传 数据层
     *
     * @author Lion Li
     */
    public interface SysOssMapper extends BaseMapperPlus<SysOssMapper, SysOss, SysOssVo> {
    }

表格分页数据对象

package com.ruoyi.common.core.page;

import cn.hutool.http.HttpStatus;
import com.baomidou.mybatisplus.core.metadata.IPage;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.io.Serializable;
import java.util.List;

/**
 * 表格分页数据对象
 *
 * @author Lion Li
 */

@Data
@NoArgsConstructor
@ApiModel("分页响应对象")
public class TableDataInfo<T> implements Serializable {
    private static final long serialVersionUID = 1L;

    /**
     * 总记录数
     */
    @ApiModelProperty("总记录数")
    private long total;

    /**
     * 列表数据
     */
    @ApiModelProperty("列表数据")
    private List<T> rows;

    /**
     * 消息状态码
     */
    @ApiModelProperty("消息状态码")
    private int code;

    /**
     * 消息内容
     */
    @ApiModelProperty("消息内容")
    private String msg;

    /**
     * 分页
     *
     * @param list  列表数据
     * @param total 总记录数
     */
    public TableDataInfo(List<T> list, long total) {
        this.rows = list;
        this.total = total;
    }

    public static <T> TableDataInfo<T> build(IPage<T> page) {
        TableDataInfo<T> rspData = new TableDataInfo<>();
        rspData.setCode(HttpStatus.HTTP_OK);
        rspData.setMsg("查询成功");
        rspData.setRows(page.getRecords());
        rspData.setTotal(page.getTotal());
        return rspData;
    }

    public static <T> TableDataInfo<T> build(List<T> list) {
        TableDataInfo<T> rspData = new TableDataInfo<>();
        rspData.setCode(HttpStatus.HTTP_OK);
        rspData.setMsg("查询成功");
        rspData.setRows(list);
        rspData.setTotal(list.size());
        return rspData;
    }

    public static <T> TableDataInfo<T> build() {
        TableDataInfo<T> rspData = new TableDataInfo<>();
        rspData.setCode(HttpStatus.HTTP_OK);
        rspData.setMsg("查询成功");
        return rspData;
    }

}

package com.ruoyi.system.domain.vo;

import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;


/**
 * 对象存储配置视图对象 sys_oss_config
 *
 * @author Lion Li
 * @author 孤舟烟雨
 * @date 2021-08-13
 */
@Data
@ApiModel("对象存储配置视图对象")
@ExcelIgnoreUnannotated
public class SysOssConfigVo {

    private static final long serialVersionUID = 1L;

    /**
     * 主建
     */
    @ApiModelProperty("主建")
    private Long ossConfigId;

    /**
     * 配置key
     */
    @ApiModelProperty("配置key")
    private String configKey;

    /**
     * accessKey
     */
    @ApiModelProperty("accessKey")
    private String accessKey;

    /**
     * 秘钥
     */
    @ApiModelProperty("secretKey")
    private String secretKey;

    /**
     * 桶名称
     */
    @ApiModelProperty("桶名称")
    private String bucketName;

    /**
     * 前缀
     */
    @ApiModelProperty("前缀")
    private String prefix;

    /**
     * 访问站点
     */
    @ApiModelProperty("访问站点")
    private String endpoint;

    /**
     * 是否https(Y=是,N=否)
     */
    @ApiModelProperty("是否https(Y=是,N=否)")
    private String isHttps;

    /**
     * 域
     */
    @ApiModelProperty("域")
    private String region;

    /**
     * 状态(0=正常,1=停用)
     */
    @ApiModelProperty("状态(0=正常,1=停用)")
    private String status;

    /**
     * 扩展字段
     */
    @ApiModelProperty("扩展字段")
    private String ext1;

    /**
     * 备注
     */
    @ApiModelProperty("备注")
    private String remark;

}

package com.ruoyi.system.domain.vo;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

import java.util.Date;

/**
 * OSS对象存储视图对象 sys_oss
 *
 * @author Lion Li
 */
@Data
@ApiModel("OSS对象存储视图对象")
public class SysOssVo {

    private static final long serialVersionUID = 1L;

    /**
     * 对象存储主键
     */
    @ApiModelProperty("对象存储主键")
    private Long ossId;

    /**
     * 文件名
     */
    @ApiModelProperty("文件名")
    private String fileName;

    /**
     * 原名
     */
    @ApiModelProperty("原名")
    private String originalName;

    /**
     * 文件后缀名
     */
    @ApiModelProperty("文件后缀名")
    private String fileSuffix;

    /**
     * URL地址
     */
    @ApiModelProperty("URL地址")
    private String url;

    /**
     * 创建时间
     */
    @ApiModelProperty("创建时间")
    private Date createTime;

    /**
     * 上传人
     */
    @ApiModelProperty("上传人")
    private String createBy;

    /**
     * 服务商
     */
    @ApiModelProperty("服务商")
    private String service;


}

package com.ruoyi.system.domain.bo;

import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.core.validate.AddGroup;
import com.ruoyi.common.core.validate.EditGroup;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

/**
 * 对象存储配置业务对象 sys_oss_config
 *
 * @author Lion Li
 * @author 孤舟烟雨
 * @date 2021-08-13
 */

@Data
@EqualsAndHashCode(callSuper = true)
@ApiModel("对象存储配置业务对象")
public class SysOssConfigBo extends BaseEntity {

    /**
     * 主建
     */
    @ApiModelProperty(value = "主建", required = true)
    @NotNull(message = "主建不能为空", groups = {EditGroup.class})
    private Long ossConfigId;

    /**
     * 配置key
     */
    @ApiModelProperty(value = "configKey", required = true)
    @NotBlank(message = "configKey不能为空", groups = {AddGroup.class, EditGroup.class})
    @Size(min = 2, max = 100, message = "configKey长度必须介于2和20 之间")
    private String configKey;

    /**
     * accessKey
     */
    @ApiModelProperty(value = "accessKey", required = true)
    @NotBlank(message = "accessKey不能为空", groups = {AddGroup.class, EditGroup.class})
    @Size(min = 2, max = 100, message = "accessKey长度必须介于2和100 之间")
    private String accessKey;

    /**
     * 秘钥
     */
    @ApiModelProperty(value = "secretKey", required = true)
    @NotBlank(message = "secretKey不能为空", groups = {AddGroup.class, EditGroup.class})
    @Size(min = 2, max = 100, message = "secretKey长度必须介于2和100 之间")
    private String secretKey;

    /**
     * 桶名称
     */
    @ApiModelProperty(value = "bucketName", required = true)
    @NotBlank(message = "bucketName不能为空", groups = {AddGroup.class, EditGroup.class})
    @Size(min = 2, max = 100, message = "bucketName长度必须介于2和100之间")
    private String bucketName;

    /**
     * 前缀
     */
    @ApiModelProperty(value = "前缀")
    private String prefix;

    /**
     * 访问站点
     */
    @ApiModelProperty(value = "endpoint", required = true)
    @NotBlank(message = "endpoint不能为空", groups = {AddGroup.class, EditGroup.class})
    @Size(min = 2, max = 100, message = "endpoint长度必须介于2和100之间")
    private String endpoint;

    /**
     * 是否https(Y=是,N=否)
     */
    @ApiModelProperty("是否https(Y=是,N=否)")
    private String isHttps;

    /**
     * 状态(0=正常,1=停用)
     */
    @ApiModelProperty("状态(0=正常,1=停用)")
    private String status;

    /**
     * 域
     */
    @ApiModelProperty(value = "region")
    private String region;

    /**
     * 扩展字段
     */
    @ApiModelProperty(value = "扩展字段")
    private String ext1;

}

package com.ruoyi.system.domain.bo;

import com.ruoyi.common.core.domain.BaseEntity;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;

/**
 * OSS对象存储分页查询对象 sys_oss
 *
 * @author Lion Li
 */
@Data
@EqualsAndHashCode(callSuper = true)
@ApiModel("OSS对象存储分页查询对象")
public class SysOssBo extends BaseEntity {

    /**
     * 文件名
     */
    @ApiModelProperty("文件名")
    private String fileName;
    /**
     * 原名
     */
    @ApiModelProperty("原名")
    private String originalName;
    /**
     * 文件后缀名
     */
    @ApiModelProperty("文件后缀名")
    private String fileSuffix;
    /**
     * URL地址
     */
    @ApiModelProperty("URL地址")
    private String url;
    /**
     * 服务商
     */
    @ApiModelProperty("服务商")
    private String service;

}

package com.ruoyi.system.listener;

import cn.dev33.satoken.secure.BCrypt;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.excel.ExcelListener;
import com.ruoyi.common.excel.ExcelResult;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.helper.LoginHelper;
import com.ruoyi.common.utils.ValidatorUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.system.domain.vo.SysUserImportVo;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.system.service.ISysUserService;
import lombok.extern.slf4j.Slf4j;

import java.util.List;

/**
 * 系统用户自定义导入
 *
 * @author Lion Li
 */
@Slf4j
public class SysUserImportListener extends AnalysisEventListener<SysUserImportVo> implements ExcelListener<SysUserImportVo> {

    private final ISysUserService userService;

    private final String password;

    private final Boolean isUpdateSupport;

    private final String operName;

    private int successNum = 0;
    private int failureNum = 0;
    private final StringBuilder successMsg = new StringBuilder();
    private final StringBuilder failureMsg = new StringBuilder();

    public SysUserImportListener(Boolean isUpdateSupport) {
        String initPassword = SpringUtils.getBean(ISysConfigService.class).selectConfigByKey("sys.user.initPassword");
        this.userService = SpringUtils.getBean(ISysUserService.class);
        this.password = BCrypt.hashpw(initPassword);
        this.isUpdateSupport = isUpdateSupport;
        this.operName = LoginHelper.getUsername();
    }

    @Override
    public void invoke(SysUserImportVo userVo, AnalysisContext context) {
        SysUser user = this.userService.selectUserByUserName(userVo.getUserName());
        try {
            // 验证是否存在这个用户
            if (ObjectUtil.isNull(user)) {
                user = BeanUtil.toBean(userVo, SysUser.class);
                ValidatorUtils.validate(user);
                user.setPassword(password);
                user.setCreateBy(operName);
                userService.insertUser(user);
                successNum++;
                successMsg.append("
"
).append(successNum).append("、账号 ").append(user.getUserName()).append(" 导入成功"); } else if (isUpdateSupport) { ValidatorUtils.validate(user); user.setUpdateBy(operName); userService.updateUser(user); successNum++; successMsg.append("
"
).append(successNum).append("、账号 ").append(user.getUserName()).append(" 更新成功"); } else { failureNum++; failureMsg.append("
"
).append(failureNum).append("、账号 ").append(user.getUserName()).append(" 已存在"); } } catch (Exception e) { failureNum++; String msg = "
"
+ failureNum + "、账号 " + user.getUserName() + " 导入失败:"; failureMsg.append(msg).append(e.getMessage()); log.error(msg, e); } } @Override public void doAfterAllAnalysed(AnalysisContext context) { } @Override public ExcelResult<SysUserImportVo> getExcelResult() { return new ExcelResult<SysUserImportVo>() { @Override public String getAnalysis() { if (failureNum > 0) { failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:"); throw new ServiceException(failureMsg.toString()); } else { successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:"); } return successMsg.toString(); } @Override public List<SysUserImportVo> getList() { return null; } @Override public List<String> getErrorList() { return null; } }; } }
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.ruoyi</groupId>
    <artifactId>ruoyi-vue-plus</artifactId>
    <version>4.1.0</version>

    <name>RuoYi-Vue-Plus</name>
    <url>https://gitee.com/JavaLionLi/RuoYi-Vue-Plus</url>
    <description>RuoYi-Vue-Plus后台管理系统</description>

    <properties>
        <ruoyi-vue-plus.version>4.1.0</ruoyi-vue-plus.version>
        <spring-boot.version>2.6.7</spring-boot.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>11</java.version>
        <maven-jar-plugin.version>3.2.2</maven-jar-plugin.version>
        <spring-boot.mybatis>2.2.0</spring-boot.mybatis>
        <druid.version>1.2.8</druid.version>
        <knife4j.version>3.0.3</knife4j.version>
        <swagger-annotations.version>1.5.22</swagger-annotations.version>
        <poi.version>4.1.2</poi.version>
        <commons-compress.version>1.21</commons-compress.version>
        <easyexcel.version>3.0.5</easyexcel.version>
        <velocity.version>2.3</velocity.version>
        <satoken.version>1.29.0</satoken.version>
        <mybatis-plus.version>3.5.1</mybatis-plus.version>
        <p6spy.version>3.9.1</p6spy.version>
        <hutool.version>5.7.22</hutool.version>
        <okhttp.version>4.9.3</okhttp.version>
        <spring-boot-admin.version>2.6.6</spring-boot-admin.version>
        <redisson.version>3.17.0</redisson.version>
        <lock4j.version>2.2.1</lock4j.version>
        <dynamic-ds.version>3.5.1</dynamic-ds.version>
        <tlog.version>1.3.6</tlog.version>
        <xxl-job.version>2.3.0</xxl-job.version>

        <!-- jdk11 缺失依赖 jaxb-->
        <jaxb.version>3.0.1</jaxb.version>
        <!-- 统一 guava 版本 解决隐式漏洞问题 -->
        <guava.version>30.0-jre</guava.version>

        <!-- OSS 配置 -->
        <qiniu.version>7.9.5</qiniu.version>
        <aliyun.oss.version>3.14.0</aliyun.oss.version>
        <qcloud.cos.version>5.6.72</qcloud.cos.version>
        <minio.version>8.3.8</minio.version>

        <!-- docker 配置 -->
        <docker.registry.url>localhost</docker.registry.url>
        <docker.registry.host>http://${docker.registry.url}:2375</docker.registry.host>
        <docker.namespace>ruoyi</docker.namespace>
        <docker.plugin.version>1.2.2</docker.plugin.version>

        <ncdg.platform>1.0-SNAPSHOT</ncdg.platform>

        <liquibase.version>3.8.8</liquibase.version>
        <persistence-api.version>1.0</persistence-api.version>
        <zhezhengding-api.version>1.2.0</zhezhengding-api.version>
    </properties>

    <!-- 依赖声明 -->
    <dependencyManagement>
        <dependencies>

            <!-- SpringBoot的依赖配置-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- hutool 的依赖配置-->
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-bom</artifactId>
                <version>${hutool.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>

            <!-- 阿里数据库连接池 -->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>${druid.version}</version>
            </dependency>

            <dependency>
                <groupId>com.github.xiaoymin</groupId>
                <artifactId>knife4j-spring-boot-starter</artifactId>
                <version>${knife4j.version}</version>
                <exclusions>
                    <exclusion>
                        <artifactId>swagger-annotations</artifactId>
                        <groupId>io.swagger</groupId>
                    </exclusion>
                </exclusions>
            </dependency>

            <dependency>
                <groupId>io.swagger</groupId>
                <artifactId>swagger-annotations</artifactId>
                <version>${swagger-annotations.version}</version>
            </dependency>

            <!-- excel工具 -->
            <dependency>
                <groupId>org.apache.poi</groupId>
                <artifactId>poi-ooxml</artifactId>
                <version>${poi.version}</version>
            </dependency>

            <!-- 修复poi漏洞 -->
            <dependency>
                <groupId>org.apache.commons</groupId>
                <artifactId>commons-compress</artifactId>
                <version>${commons-compress.version}</version>
            </dependency>

            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>easyexcel</artifactId>
                <version>${easyexcel.version}</version>
                <exclusions>
                    <exclusion>
                        <groupId>org.apache.poi</groupId>
                        <artifactId>poi</artifactId>
                    </exclusion>
                    <exclusion>
                        <groupId>org.apache.poi</groupId>
                        <artifactId>poi-ooxml-schemas</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>

            <!-- velocity代码生成使用模板 -->
            <dependency>
                <groupId>org.apache.velocity</groupId>
                <artifactId>velocity-engine-core</artifactId>
                <version>${velocity.version}</version>
            </dependency>

            <!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
            <dependency>
                <groupId>cn.dev33</groupId>
                <artifactId>sa-token-spring-boot-starter</artifactId>
                <version>${satoken.version}</version>
            </dependency>
            <!-- Sa-Token 整合 jwt -->
            <dependency>
                <groupId>cn.dev33</groupId>
                <artifactId>sa-token-jwt</artifactId>
                <version>${satoken.version}</version>
            </dependency>

            <!-- jdk11 缺失依赖 jaxb-->
            <dependency>
                <groupId>com.sun.xml.bind</groupId>
                <artifactId>jaxb-impl</artifactId>
                <version>${jaxb.version}</version>
            </dependency>

            <!-- dynamic-datasource 多数据源-->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
                <version>${dynamic-ds.version}</version>
            </dependency>

            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-extension</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>
            <!-- sql性能分析插件 -->
            <dependency>
                <groupId>p6spy</groupId>
                <artifactId>p6spy</artifactId>
                <version>${p6spy.version}</version>
            </dependency>

            <dependency>
                <groupId>com.squareup.okhttp3</groupId>
                <artifactId>okhttp</artifactId>
                <version>${okhttp.version}</version>
            </dependency>

            <dependency>
                <groupId>de.codecentric</groupId>
                <artifactId>spring-boot-admin-starter-server</artifactId>
                <version>${spring-boot-admin.version}</version>
            </dependency>
            <dependency>
                <groupId>de.codecentric</groupId>
                <artifactId>spring-boot-admin-starter-client</artifactId>
                <version>${spring-boot-admin.version}</version>
            </dependency>

            <!--redisson-->
            <dependency>
                <groupId>org.redisson</groupId>
                <artifactId>redisson-spring-boot-starter</artifactId>
                <version>${redisson.version}</version>
            </dependency>
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>lock4j-redisson-spring-boot-starter</artifactId>
                <version>${lock4j.version}</version>
            </dependency>

            <!-- xxl-job-core -->
            <dependency>
                <groupId>com.xuxueli</groupId>
                <artifactId>xxl-job-core</artifactId>
                <version>${xxl-job.version}</version>
            </dependency>

            <dependency>
                <groupId>com.yomahub</groupId>
                <artifactId>tlog-web-spring-boot-starter</artifactId>
                <version>${tlog.version}</version>
                <exclusions>
                    <exclusion>
                        <artifactId>log4j</artifactId>
                        <groupId>log4j</groupId>
                    </exclusion>
                    <exclusion>
                        <artifactId>dom4j</artifactId>
                        <groupId>dom4j</groupId>
                    </exclusion>
                    <exclusion>
                        <artifactId>commons-beanutils</artifactId>
                        <groupId>commons-beanutils</groupId>
                    </exclusion>
                </exclusions>
            </dependency>

            <dependency>
                <groupId>com.yomahub</groupId>
                <artifactId>tlog-xxljob-spring-boot-starter</artifactId>
                <version>${tlog.version}</version>
            </dependency>

            <!-- 统一 guava 版本 解决隐式漏洞问题 -->
            <dependency>
                <groupId>com.google.guava</groupId>
                <artifactId>guava</artifactId>
                <version>${guava.version}</version>
            </dependency>

            <!-- 定时任务 -->
            <dependency>
                <groupId>com.ruoyi</groupId>
                <artifactId>ruoyi-job</artifactId>
                <version>${ruoyi-vue-plus.version}</version>
            </dependency>

            <!-- 代码生成-->
            <dependency>
                <groupId>com.ruoyi</groupId>
                <artifactId>ruoyi-generator</artifactId>
                <version>${ruoyi-vue-plus.version}</version>
            </dependency>

            <!-- 核心模块-->
            <dependency>
                <groupId>com.ruoyi</groupId>
                <artifactId>ruoyi-framework</artifactId>
                <version>${ruoyi-vue-plus.version}</version>
            </dependency>

            <!-- 系统模块-->
            <dependency>
                <groupId>com.ruoyi</groupId>
                <artifactId>ruoyi-system</artifactId>
                <version>${ruoyi-vue-plus.version}</version>
            </dependency>

            <!-- 通用工具-->
            <dependency>
                <groupId>com.ruoyi</groupId>
                <artifactId>ruoyi-common</artifactId>
                <version>${ruoyi-vue-plus.version}</version>
            </dependency>

            <!-- OSS对象存储模块 -->
            <dependency>
                <groupId>com.ruoyi</groupId>
                <artifactId>ruoyi-oss</artifactId>
                <version>${ruoyi-vue-plus.version}</version>
            </dependency>

            <!-- demo模块 -->
            <dependency>
                <groupId>com.ruoyi</groupId>
                <artifactId>ruoyi-demo</artifactId>
                <version>${ruoyi-vue-plus.version}</version>
            </dependency>

            <!-- 智护幼苗 -->
            <dependency>
                <groupId>com.ruoyi</groupId>
                <artifactId>children-guard</artifactId>
                <version>${ruoyi-vue-plus.version}</version>
            </dependency>

            <!--爱心捐助模块-->
            <dependency>
                <groupId>com.ruoyi</groupId>
                <artifactId>ruoyi-donation</artifactId>
                <version>${ruoyi-vue-plus.version}</version>
            </dependency>

            <!--安全预警模块-->
            <dependency>
                <groupId>com.ruoyi</groupId>
                <artifactId>ruoyi-warn</artifactId>
                <version>${ruoyi-vue-plus.version}</version>
            </dependency>

            <dependency>
                <groupId>org.example</groupId>
                <artifactId>ncdg-platform-payment</artifactId>
                <version>${ncdg.platform}</version>
            </dependency>

            <dependency>
                <!-- @Column 字段用 -->
                <groupId>javax.persistence</groupId>
                <artifactId>persistence-api</artifactId>
                <version>${persistence-api.version}</version>
            </dependency>

            <!--浙政钉依赖-->
            <dependency>
                <groupId>com.zhezhengding</groupId>
                <artifactId>sdk-java-zhezhengding</artifactId>
                <version>${zhezhengding-api.version}</version>
            </dependency>

        </dependencies>
    </dependencyManagement>

    <modules>
        <module>ruoyi-admin</module>
        <module>ruoyi-framework</module>
        <module>ruoyi-system</module>
        <module>ruoyi-job</module>
        <module>ruoyi-generator</module>
        <module>ruoyi-common</module>
        <module>ruoyi-demo</module>
        <module>ruoyi-extend</module>
        <module>ruoyi-oss</module>
        <module>children-guard</module>
        <module>ruoyi-donation</module>
        <module>ruoyi-warn</module>
    </modules>
    <packaging>pom</packaging>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.9.0</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                    <encoding>${project.build.sourceEncoding}</encoding>
                </configuration>
            </plugin>
        </plugins>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <!-- 关闭过滤 -->
                <filtering>false</filtering>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <!-- 引入所有 匹配文件进行过滤 -->
                <includes>
                    <include>application*</include>
                    <include>bootstrap*</include>
                    <include>banner*</include>
                </includes>
                <!-- 启用过滤 即该资源中的变量将会被过滤器中的值替换 -->
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>

    <repositories>
        <repository>
            <id>public</id>
            <name>aliyun nexus</name>
            <url>https://maven.aliyun.com/repository/public/</url>
            <releases>
                <enabled>true</enabled>
            </releases>
        </repository>
        <repository>
            <id>ncdg-nexus-release</id>
            <name>Nexus Release Repository</name>
            <url>http://xxxxxxxxxxx:8186/repository/maven-releases/</url>
        </repository>

    </repositories>

    <pluginRepositories>
        <pluginRepository>
            <id>public</id>
            <name>aliyun nexus</name>
            <url>https://maven.aliyun.com/repository/public/</url>
            <releases>
                <enabled>true</enabled>
            </releases>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>

    </pluginRepositories>

    <profiles>
        <profile>
            <id>local</id>
            <properties>
                <!-- 环境标识,需要与配置文件的名称相对应 -->
                <profiles.active>local</profiles.active>
                <logging.level>debug</logging.level>
                <knife4j.production>false</knife4j.production>
                <endpoints.include>'*'</endpoints.include>
            </properties>
        </profile>
        <profile>
            <id>test</id>
            <properties>
                <!-- 环境标识,需要与配置文件的名称相对应 -->
                <profiles.active>test</profiles.active>
                <logging.level>debug</logging.level>
                <knife4j.production>false</knife4j.production>
                <endpoints.include>'*'</endpoints.include>
            </properties>
            <activation>
                <!-- 默认环境 -->
                <activeByDefault>true</activeByDefault>
            </activation>
        </profile>
        <profile>
            <id>dev</id>
            <properties>
                <!-- 环境标识,需要与配置文件的名称相对应 -->
                <profiles.active>dev</profiles.active>
                <logging.level>debug</logging.level>
                <knife4j.production>false</knife4j.production>
                <endpoints.include>'*'</endpoints.include>
            </properties>
        </profile>
        <profile>
            <id>prod</id>
            <properties>
                <profiles.active>prod</profiles.active>
                <logging.level>warn</logging.level>
                <knife4j.production>true</knife4j.production>
                <endpoints.include>health, info, logfile</endpoints.include>
            </properties>
        </profile>
        <profile>
            <id>release</id>
            <properties>
                <!-- 环境标识,需要与配置文件的名称相对应 -->
                <profiles.active>release</profiles.active>
                <logging.level>debug</logging.level>
                <knife4j.production>false</knife4j.production>
                <endpoints.include>'*'</endpoints.include>
            </properties>
        </profile>
    </profiles>

</project>

你可能感兴趣的:(Java开发中各种问题场景处理,服务器,docker,linux)