• 026-从零搭建微服务-文件服务(二)


    写在最前

    如果这个项目让你有所收获,记得 Star 关注哦,这对我是非常不错的鼓励与支持。

    源码地址(后端):https://gitee.com/csps/mingyue-springcloud-learning

    源码地址(前端):https://gitee.com/csps/mingyue-springcloud-ui

    文档地址:https://gitee.com/csps/mingyue-springcloud-learning/wikis

    OSS 基础表设计

    1. OSS对象存储表

    DROP TABLE IF EXISTS sys_oss;
    CREATE TABLE sys_oss (
        oss_id           BIGINT(20)        NOT NULL                   COMMENT 'OSS对象ID',
        file_name        VARCHAR(255)      NOT NULL DEFAULT ''        COMMENT '文件名',
        original_name    VARCHAR(255)      NOT NULL DEFAULT ''        COMMENT '原名',
        file_suffix      VARCHAR(10)       NOT NULL DEFAULT ''        COMMENT '文件后缀名',
        file_url         VARCHAR(500)      NOT NULL                   COMMENT '文件URL',
        create_time      DATETIME          DEFAULT NULL               COMMENT '创建时间',
        create_by        VARCHAR(64)       DEFAULT ''                 COMMENT '上传人',
        update_time      DATETIME          DEFAULT NULl               COMMENT '更新时间',
        update_by        VARCHAR(64)       DEFAULT ''                 COMMENT '更新人',
        service          VARCHAR(20)       NOT NULL DEFAULT 'minio'   COMMENT '服务商',
        primary key (oss_id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC COMMENT='OSS对象存储表';
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    2. OSS对象存储动态配置

    DROP TABLE IF EXISTS sys_oss_config;
    CREATE TABLE sys_oss_config (
        oss_config_id     BIGINT(20)         NOT NULL                 COMMENT 'OSS动态配置ID',
        config_key        VARCHAR(20)        NOT NULL DEFAULT ''      COMMENT '配置key',
        access_key        VARCHAR(255)       DEFAULT ''               COMMENT 'accessKey',
        secret_key        VARCHAR(255)       DEFAULT ''               COMMENT '秘钥',
        bucket_name       VARCHAR(255)       DEFAULT ''               COMMENT '桶名称',
        prefix            VARCHAR(255)       DEFAULT ''               COMMENT '前缀',
        endpoint          VARCHAR(255)       DEFAULT ''               COMMENT '访问站点',
        domain            VARCHAR(255)       DEFAULT ''               COMMENT '自定义域名',
        is_https          CHAR(1)            DEFAULT 'N'              COMMENT '是否https(Y是 N否)',
        region            VARCHAR(255)       DEFAULT ''               COMMENT '域',
        access_policy     CHAR(1)            NOT NULL DEFAULT '1'     COMMENT '桶权限类型(0-private 1-public 2-custom)',
        status            CHAR(1)            DEFAULT '1'              COMMENT '是否默认(0是 1否)',
        extend            VARCHAR(255)       DEFAULT ''               COMMENT '扩展字段',
        create_by         VARCHAR(64)        DEFAULT ''               COMMENT '创建者',
        create_time       DATETIME           DEFAULT NULL             COMMENT '创建时间',
        update_by         VARCHAR(64)        DEFAULT ''               COMMENT '更新者',
        update_time       DATETIME           DEFAULT NULL             COMMENT '更新时间',
        PRIMARY KEY (oss_config_id)
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC COMMENT='OSS对象存储动态配置表';
    
    INSERT INTO `sys_oss_config` VALUES (1, 'minio', 'd6zVm5AP07uGCqSmsTxe', 'Vsm6qQDHgGchukEpyEoeX3dTe7fic60nTi8D9a0I', 'mingyue', '', 'mingyue-minio:5000', '', 'N', '', '1', '0', '', 'admin', '2023-09-11 17:50:40', 'admin', '2023-09-11 17:50:40');
    COMMIT;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    OSS 配置加载

    初始化OSS配置

    @Override
    public void init() {
        List<SysOssConfig> list = this.list();
        // 加载 OSS 初始化配置
        for (SysOssConfig config : list) {
            String configKey = config.getConfigKey();
            if ("0".equals(config.getStatus())) {
                RedisUtils.setCacheObject(OssConstant.DEFAULT_CONFIG_KEY, configKey);
            }
    
            RedisUtils.setCacheMapValue(OssConstant.SYS_OSS_CONFIG, config.getConfigKey(), JSONUtil.toJsonStr(config));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    OssApplicationRunner

    @Slf4j
    @Component
    @RequiredArgsConstructor
    public class OssApplicationRunner implements ApplicationRunner {
    
        private final SysOssConfigService sysOssConfigService;
    
        @Override
        public void run(ApplicationArguments args) {
            sysOssConfigService.init();
            log.info("初始化 OSS 配置成功");
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    改进 OssFactory

    @Slf4j
    public class OssFactory {
    
    	private static final Map<String, OssClient> CLIENT_CACHE = new ConcurrentHashMap<>();
    
    	/**
    	 * 获取默认实例
    	 */
    	public static OssClient instance() {
    		// 获取redis 默认类型
    		String configKey = RedisUtils.getCacheObject(OssConstant.DEFAULT_CONFIG_KEY);
    		if (StrUtil.isEmpty(configKey)) {
    			throw new OssException("文件存储服务类型无法找到!");
    		}
    		return instance(configKey);
    	}
    
    	/**
    	 * 根据类型获取实例
    	 */
    	public static OssClient instance(String configKey) {
    		String json = RedisUtils.getCacheMapValue(OssConstant.SYS_OSS_CONFIG, configKey);
    		if (json == null) {
    			throw new OssException("系统异常, '" + configKey + "'配置信息不存在!");
    		}
    		OssProperties properties = JSONUtil.toBean(json, OssProperties.class);
    		OssClient client = CLIENT_CACHE.get(configKey);
    		if (client == null) {
    			CLIENT_CACHE.put(configKey, new OssClient(configKey, properties));
    			log.info("创建OSS实例 key => {}", configKey);
    			return CLIENT_CACHE.get(configKey);
    		}
    		// 配置不相同则重新构建
    		if (!client.checkPropertiesSame(properties)) {
    			CLIENT_CACHE.put(configKey, new OssClient(configKey, properties));
    			log.info("重载OSS实例 key => {}", configKey);
    			return CLIENT_CACHE.get(configKey);
    		}
    		return client;
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    移除 Nacos OSS 配置

    因为从数据库加载配置,所以不在需要 Nacos 配置了

    oss:
      configKey: minio
      endpoint: mingyue-minio:5000
      domain:
      prefix:
      accessKey: d6zVm5AP07uGCqSmsTxe
      secretKey: Vsm6qQDHgGchukEpyEoeX3dTe7fic60nTi8D9a0I
      bucketName: mingyue
      region: 
      isHttps: N
      accessPolicy: 1
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    上传测试

    {
      "code": 200,
      "msg": "操作成功",
      "data": {
        "ossId": "1701490497677180930",
        "fileName": "2023-09-12/d1b5389a465f4bf7985844916d785c06.png",
        "originalName": "head_1.png",
        "fileSuffix": ".png",
        "fileUrl": "http://mingyue-minio:5000/mingyue/2023-09-12/d1b5389a465f4bf7985844916d785c06.png",
        "createTime": "2023-09-12 14:58:41",
        "createBy": "mingyue",
        "service": "minio"
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    OSS 上传信息保存

    /**
     * 构建上传文件返回信息
     * @param originalFilename 原始文件名
     * @param suffix 文件后缀
     * @param configKey 配置key
     * @param uploadResult OSS服务返回结果
     * @return
     */
    private SysOssVo buildResult(String originalFilename, String suffix, String configKey, UploadResult uploadResult) {
      SysOss oss = new SysOss();
      oss.setFileUrl(uploadResult.getFileUrl());
      oss.setFileSuffix(suffix);
      oss.setFileName(uploadResult.getFileName());
      oss.setOriginalName(originalFilename);
      oss.setService(configKey);
      this.save(oss);
    
      SysOssVo sysOssVo = BeanUtil.toBean(oss, SysOssVo.class);
      return this.matchingUrl(sysOssVo);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    删除文件

    逻辑实现

    删除数据库记录的同时需要删除OSS服务对应的文件

    @Override
    public Boolean deleteByOssIds(List<Long> ossIds) {
      List<SysOss> list = this.listByIds(ossIds);
      if (CollUtil.isEmpty(list)) {
        return Boolean.FALSE;
      }
    
      for (SysOss sysOss : list) {
        OssClient storage = OssFactory.instance(sysOss.getService());
        storage.delete(sysOss.getFileUrl());
      }
    
      return this.removeBatchByIds(ossIds);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    删除接口

    @DeleteMapping("/{ossIds}")
    @Operation(summary = "删除OSS对象存储",
        parameters = { @Parameter(name = "ossIds", description = "oss对象Ids", required = true) })
    public R<Boolean> remove(@NotEmpty(message = "主键不能为空") @PathVariable List<Long> ossIds) {
      return R.ok(sysOssService.deleteByOssIds(ossIds));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    删除测试

    删除前打开文件查看:http://mingyue-minio:5000/mingyue/2023-09-12/d1b5389a465f4bf7985844916d785c06.png

    curl -X 'DELETE' \
      'http://mingyue-gateway:9100/oss/sysOss/1701490497677180930' \
      -H 'accept: */*' \
      -H 'Authorization: 6H1mlA91zFRa5yEpIl2b2mnCjbG5B44f'
    
    • 1
    • 2
    • 3
    • 4

    删除后再打开

    <Error>
        <Code>NoSuchKeyCode>
        <Message>The specified key does not exist.Message>
        <Key>2023-09-12/d1b5389a465f4bf7985844916d785c06.pngKey>
        <BucketName>mingyueBucketName>
        <Resource>/mingyue/2023-09-12/d1b5389a465f4bf7985844916d785c06.pngResource>
        <RequestId>17841B7B6B41C214RequestId>
        <HostId>dd9025bab4ad464b049177c95eb6ebf374d3b3fd1af9251148b658df7ac2e3e8HostId>
    Error>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    下载文件

    逻辑实现

    @Override
    public void download(Long ossId, HttpServletResponse response) throws IOException {
      SysOss sysOss = this.getById(ossId);
      if (ObjectUtil.isNull(sysOss)) {
        throw new ServiceException("文件数据不存在!");
      }
      FileUtils.setAttachmentResponseHeader(response, sysOss.getOriginalName());
      response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE + "; charset=UTF-8");
      OssClient storage = OssFactory.instance(sysOss.getService());
    
      try (InputStream inputStream = storage.getObjectContent(sysOss.getFileUrl())) {
        int available = inputStream.available();
        IoUtil.copy(inputStream, response.getOutputStream(), available);
        response.setContentLength(available);
      }
      catch (Exception e) {
        throw new ServiceException(e.getMessage());
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    下载接口

    @GetMapping("/download/{ossId}")
    @Operation(summary = "下载OSS对象存储",
        parameters = { @Parameter(in = ParameterIn.PATH, name = "ossIds", description = "oss对象Ids", required = true) })
    public void download(@PathVariable Long ossId, HttpServletResponse response) throws IOException {
      sysOssService.download(ossId, response);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    下载测试

    curl -X 'GET' \
      'http://mingyue-gateway:9100/oss/sysOss/download/1701492631160229889' \
      -H 'accept: */*'
    
    • 1
    • 2
    • 3

    image-20230912190918686

    小结

    文件服务基础已经完成啦,接下来可以自己尝试集成其他厂商的 OSS 服务。

    文件服务更新暂告一段落,接下来弄一弄搜索服务,打算用 ES(Elasticsearch)作为搜索服务基础工具,期待一下吧~~

  • 相关阅读:
    都讨论大厂面试,当我小厂面试请喝茶的?
    redis
    代码随想录算法训练营第二十五天|216.组合总和III 17.电话号码的字母组合
    JS力扣刷题经典100题——两数相加
    selenium+python自动化安装驱动 碰到的问题
    面试突击59:一个表中可以有多个自增列吗?
    C++定时器和时间轮
    Go语言实现原理——Map实现原理
    Linux下git安装及使用
    代码随想录二刷 Day23
  • 原文地址:https://blog.csdn.net/csp732171109/article/details/132839020