搭建属于自己的对象储存服务(MinIO)与短链服务

springboot整合minio

文档地址

minio官方文档: https://docs.min.io/?ref=ob

minio 中文文档地址: http://docs.minio.org.cn/docs/master/java-client-api-reference

备注

不同版本的minio所实现的api和类是不同的,需要根据自己的需求版本进行实现,文档中的内容仅供参考,尤其是中文文档,版本过老,实际api调用方式已完全不同,建议以官方文档与实际版本源代码作为实现功能,本文使用 版本 8.2.2

本文版本

versionv 内容
1.0.0 初步实现,minio基础存储(获取)对象,通过redis缓存实现短链服务。

实现逻辑

实现minio储存

业务版本: version_1.0.0

业务说明: 仅需实现 minio基础的储存功能与简单的短链服务

minio储存没什么好说的,参照文档与当前版本源码即可实现,

主要在短链的服务怎么实现稍微要讲一下,

  1. 如何实现有效短链码?

我这里直接设置了两个策略接口,一个用来校验短链码是否合理,一个用来生成短链码。

  1. 如何储存短链码与实际储存对象的关系?

可以设置一个关系储存接口,使用例如mongo、redis等的实现,我下面的实现是没有写这个接口,直接使用redis作为储存关系的实现。

返回VO

/**

* @auth: black tea

* @date: 2021/6/18 8:45

* @description:

* 1.0.0 版本仅涉及到地址一部分属性;

* 以下大部分属性属于1.1.0的内容(例如锁定控制,无效控制,场景唯一id等)

*      1.1.0 可以精确控制是否可以访问该资源,可以设置该对象资源的私有功能,满足部分敏感文件不可以被公共访* 问的业务等,还可以将用户已经放弃的对象资源进行释放,减少服务资源空间的浪费!

*/

public class UploadBaseVo implements Serializable {

    private static final long serialVersionUID = 2763738636057411330L;

    /** 文件原名称 -前端用于文件名称替换 **/

    private String fileName;

    /** 长链原地址 **/

    private String url;

    /** 短链地址 **/

    private String shortUrl;

    /** 是否锁定,锁定后无法进行访问 **/

    private boolean isLock;

    /** 是否无效 **/

    private boolean isInvalid;

    /** 无效枚举 **/

    private UploadFileConstant.UploadFileInvalidEnum invalidEnum;

    /** 锁定或无效原因 **/

    private String lockOrInvalidCause;

    /** 最后有效时间 **/

    private Date lastEffectiveTime;

    /** 上传的客户端类型 **/

    private UploadFileConstant.uploadClientType clientType;

    /** 场景id - 唯一 - 非必有 **/

    private String sceneId;

    /** 销毁枚举 **/

    private UploadFileConstant.DestroyEnum destroyEnum;

    /**

    * return true 表示超时

    * @param date 比较的时间

    * @return boolean

    */

    public boolean isTimOut(Date date){

      return date.getTime() - lastEffectiveTime.getTime() > 0;

    }

}

上传对象文件

public MinioUploadVo upload(MultipartFile file, UploadBaseDto dto) throws IOException, InvalidKeyException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, ErrorResponseException {

        if (dto instanceof MinioUploadDto){

            MinioUploadDto uploadDto = (MinioUploadDto) dto;

            String bucketName = uploadDto.getBucketName();

            if (Validator.isEmpty(bucketName)){

                bucketName = MinioConstant.DEFAULT_STORAGE_BUCKET;

            }

            boolean isExist = minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());

            if (isExist) {

                log.info("存储桶已经存在!");

            } else {

                //创建存储桶并设置只读权限

                minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());

                SetBucketPolicyArgs setBucketPolicyArgs = SetBucketPolicyArgs.builder().bucket(bucketName).config(MinioConstant.PolicyType.READ_ONLY.toString()).build();

                minioClient.setBucketPolicy(setBucketPolicyArgs);

            }

            String filename = file.getOriginalFilename();

            // 设置存储对象名称

            String objectName = DateUtil.format(new Date(), "yyyyMMdd") + "/" + filename;

            // 使用putObject上传一个文件到存储桶中

            String fileContent = file.getContentType();

            if (file.getContentType().contains("ISO-8859-1")){

                fileContent = fileContent.replace("ISO-8859-1", "UTF-8");

            }

            PutObjectArgs putObjectArgs = PutObjectArgs.builder()

                    .bucket(bucketName)

                    .contentType(fileContent)

                    .object(objectName)

                    .stream(file.getInputStream(), file.getSize(), PutObjectArgs.MIN_MULTIPART_SIZE)

                    .build();

            ObjectWriteResponse objectWriteResponse = minioClient.putObject(putObjectArgs);

            log.info("文件上传成功,对象:{}", JSONUtil.toJsonStr(objectWriteResponse));

            MinioGetObjectDto getObjectDto = new MinioGetObjectDto();

            getObjectDto.setBucketName(bucketName);

            getObjectDto.setFileName(filename);

            getObjectDto.setObjectName(objectName);

            MinioUploadVo vo = this.getObject(getObjectDto);

            vo.setSceneId(dto.getSceneId());

            String url = vo.getShortUrl();

            String path = URLUtil.getPath(url);

            String path1 = path.substring(1, path.length());

            // 这里默认使用的是redis的储存短链策略

            redisUtils.set(UploadFileConstant.filePrefix + path1.charAt(0) + "::" + path1,vo);

            return vo;

        }else {

            log.error("上传失败,因为当前调用的服务(minioService),入参类型dto错误!");

        }

        return null;

    }

获取对象文件地址

public MinioUploadVo getObject(UploadBaseDto dto) throws IOException, InvalidResponseException, InsufficientDataException, NoSuchAlgorithmException, ServerException, InternalException, XmlParserException, ErrorResponseException, InternalException, InvalidKeyException {

        if (dto instanceof MinioGetObjectDto){

            MinioGetObjectDto getObjectDto = (MinioGetObjectDto) dto;

            String bucketName = getObjectDto.getBucketName();

            String objectName = getObjectDto.getObjectName();

            if (Validator.isEmpty(bucketName)){

                bucketName = MinioConstant.DEFAULT_STORAGE_BUCKET;

            }

            GetPresignedObjectUrlArgs getPresignedObjectUrlArgs = GetPresignedObjectUrlArgs.builder()

                    .method(Method.GET)

                    .bucket(bucketName)

                    .object(objectName)

                    .build();

            String url = minioClient.getPresignedObjectUrl(getPresignedObjectUrlArgs);

            getObjectDto.setUrl(url);

            return build(getObjectDto,new MD5Strategy(),7,null);

        }else {

            log.error("错误");

        }

      return null;

    }

短链服务实现

先增加两个策略接口!

1: 校验短链码是否重复接口?


@FunctionalInterface

public interface CheckShortUrlStrategy<T> {

    /**

    * 短链接地址是否重复, 重复 true

    * @param

    * @return

    */

    boolean isRepeat(T t);

}

2: 自定义生成短链码的接口?


@FunctionalInterface

public interface UrlToShortUrlStrategy {

    /**

    * 长链接转短连接

    * @param url 长链接

    * @return String

    */

    String toShortUrl(String url, int resultLength);

}

最终调用接口实现短链服务 ↓↓↓↓


/**

*  生成返回客户端的vo

*/

private MinioUploadVo build(MinioGetObjectDto dto, UrlToShortUrlStrategy strategy, int length, CheckShortUrlStrategy checkShortUrlStrategy){

        MinioUploadVo vo = new MinioUploadVo();

        vo.setSceneId(dto.getSceneId());

        vo.setBucketName(dto.getBucketName());

        vo.setObjectName(dto.getObjectName());

        vo.setFileName(dto.getFileName());

        String url = dto.getUrl();

        vo.setUrl(url);

        if (null == strategy){

            strategy = this.defaultBuildShortUrlStrategy();

        }

        String shortUrl = new UrlTpShortUrlContext(strategy).execute(url, length);

        while (this.shortUrlCheck(checkShortUrlStrategy,shortUrl)){

            shortUrl = new UrlTpShortUrlContext(strategy).execute(url, length);

        }

        vo.setShortUrl(shortUrl);

        vo.setClientType(UploadFileConstant.uploadClientType.MINIO_CLIENT);

        return vo;

    }

// 判断是否满足短链的策略

    private boolean shortUrlCheck(CheckShortUrlStrategy strategy,String shortUrl){

        if (null == strategy) {

          // 短链策略默认实现校验短链码是否重复!

            strategy = this.defaultCheckStrategy();

        }

        return strategy.isRepeat(shortUrl);

    }

    private CheckShortUrlStrategy<String> defaultCheckStrategy(){

        return url -> {

            String path = URLUtil.getPath(url);

            // 取url首位作为 :: 第二重

            String path1 = path.substring(1, path.length());

            UploadBaseVo uploadBaseVo = (UploadBaseVo) redisUtils.get(UploadFileConstant.filePrefix + path1.charAt(0) + "::" + path1);

            // 是否存在?

            return Validator.isNotNull(uploadBaseVo) && Validator.isNotEmpty(uploadBaseVo.getShortUrl());

        };

    }

    private UrlToShortUrlStrategy defaultBuildShortUrlStrategy(){

        // 默认采用随机值策略

        return new RandomStrategy();

    }

效果图

  • Minio 服务

    登录页面
    admin

其他的功能不做介绍,自己看下文档即可。

  • 项目图
服务上传图片
上传结果json

实际短链效果 -> 访问: http://localhost/iymQvia

image
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,009评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,808评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,891评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,283评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,285评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,409评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,809评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,487评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,680评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,499评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,548评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,268评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,815评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,872评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,102评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,683评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,253评论 2 341

推荐阅读更多精彩内容