Skip to content

存储到 腾讯 COS ​

添加依赖 ​

xml
    <dependency>
      <groupId>com.qcloud</groupId>
      <artifactId>cos_api</artifactId>
      <version>5.6.155</version>
    </dependency>

编写工具类 ​

java

package nexus.io.tio.boot.admin.utils.storage;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.Date;

import nexus.io.tio.utils.environment.EnvUtils;
import nexus.io.tio.utils.http.ContentTypeUtils;
import nexus.io.tio.utils.hutool.FilenameUtils;
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.HttpMethodName;
import com.qcloud.cos.http.HttpProtocol;
import com.qcloud.cos.model.GeneratePresignedUrlRequest;
import com.qcloud.cos.model.ObjectMetadata;
import com.qcloud.cos.model.PutObjectRequest;
import com.qcloud.cos.model.PutObjectResult;
import com.qcloud.cos.region.Region;

/**
 * Tencent COS 工具类
 */
public class TencentCOSUtils {

  /**
   * 公网访问 URL 模板:
   * https://<bucket>.cos.<region>.myqcloud.com/<objectKey>
   */
  public static final String urlFormat = "https://%s.cos.%s.myqcloud.com/%s";

  public static final String bucketName = EnvUtils.get("TENCENT_COS_BUCKET_NAME");
  public static final String regionName = EnvUtils.get("TENCENT_COS_REGION_NAME");
  public static final String secretId = EnvUtils.get("TENCENT_COS_SECRET_ID");
  public static final String secretKey = EnvUtils.get("TENCENT_COS_SECRET_KEY");

  /**
   * 可选:自定义域名(CDN/COS 绑定域名),例如 cdn.example.com
   */
  public static final String domain = EnvUtils.getStr("TENCENT_COS_BUCKET_DOMAIN");

  /** 默认预签名有效期 */
  public static final Duration DEFAULT_PRESIGN_EXPIRES = Duration.ofMinutes(30);

  // -------------------------
  // Upload
  // -------------------------

  public static PutObjectResult upload(COSClient cosClient, String targetName, byte[] fileContent, String suffix) {
    return upload(cosClient, bucketName, targetName, fileContent, suffix);
  }

  /**
   * 上传字节数组
   *
   * @param client    COSClient
   * @param bucket    bucket 名称(注意:腾讯 COS bucket 通常包含 appid 后缀,如 xxx-1250000000)
   * @param objectKey 对象 key
   * @param bytes     文件内容
   * @param suffix    后缀(用于推断 Content-Type)
   */
  public static PutObjectResult upload(COSClient client, String bucket, String objectKey, byte[] bytes, String suffix) {
    try {
      ObjectMetadata metadata = new ObjectMetadata();
      metadata.setContentLength(bytes.length);

      if (suffix != null && suffix.length() > 0) {
        metadata.setContentType(ContentTypeUtils.getContentType(suffix));
      }

      ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes);
      PutObjectRequest req = new PutObjectRequest(bucket, objectKey, inputStream, metadata);
      PutObjectResult putObject = client.putObject(req);
      return putObject;
    } catch (Exception e) {
      throw new RuntimeException("Tencent COS upload error", e);
    }
  }

  /**
   * 上传 File
   */
  public static PutObjectResult upload(COSClient client, String objectKey, File file) {
    return upload(client, bucketName, objectKey, file);
  }

  public static PutObjectResult upload(COSClient client, String bucket, String objectKey, File file) {
    String name = file.getName();
    String suffix = FilenameUtils.getSuffix(name);
    String contentType = ContentTypeUtils.getContentType(suffix);

    try {
      ObjectMetadata metadata = new ObjectMetadata();
      metadata.setContentLength(file.length());
      if (suffix != null && suffix.length() > 0) {
        metadata.setContentType(contentType);
      }

      PutObjectRequest req = new PutObjectRequest(bucket, objectKey, file);
      req.setMetadata(metadata);

      return client.putObject(req);
    } catch (Exception e) {
      throw new RuntimeException("Tencent COS upload error", e);
    }
  }

  // -------------------------
  // Public URL (仅当对象公开/域名放行时可用)
  // -------------------------

  public static String getUrl(String objectKey) {
    return getUrl(bucketName, objectKey);
  }

  public static String getUrl(String bucket, String objectKey) {
    if (domain != null && domain.length() > 0) {
      return "https://" + domain + "/" + objectKey;
    }
    return String.format(urlFormat, bucket, regionName, objectKey);
  }

  public static String getUrl(String regionName, String bucket, String objectKey) {
    if (domain != null && domain.length() > 0) {
      return "https://" + domain + "/" + objectKey;
    }
    return String.format(urlFormat, bucket, regionName, objectKey);
  }

  // -------------------------
  // Presigned Download URL (私有 bucket 推荐用这个)
  // -------------------------

  public static String getPresignedDownloadUrl(String objectKey) {
    return getPresignedDownloadUrl(bucketName, objectKey, DEFAULT_PRESIGN_EXPIRES, null, null);
  }

  public static String getPresignedDownloadUrl(String bucket, String objectKey) {
    return getPresignedDownloadUrl(bucket, objectKey, DEFAULT_PRESIGN_EXPIRES, null, null);
  }

  public static String getPresignedDownloadUrl(String region_name, String bucket, String targetName) {
    return getPresignedDownloadUrl(region_name, bucket, targetName, DEFAULT_PRESIGN_EXPIRES, null, null);
  }

  public static String getPresignedDownloadUrl(String bucket, String objectKey, Duration expires,
      String downloadFilename, String contentType) {
    return getPresignedDownloadUrl(regionName, bucket, objectKey, expires, downloadFilename, contentType);
  }

  public static String getPresignedDownloadUrl(String regionName, String bucket, String targetUri,
      String downloadFilename) {
    String suffix = FilenameUtils.getSuffix(downloadFilename);
    String contentType = ContentTypeUtils.getContentType(suffix);
    return getPresignedDownloadUrl(regionName, bucket, targetUri, DEFAULT_PRESIGN_EXPIRES, downloadFilename,
        contentType);
  }

  /**
   * 生成可下载的预签名 URL(GET)。
   *
   * @param bucket           bucket 名称
   * @param objectKey        对象 key
   * @param expires          过期时间
   * @param downloadFilename 下载保存时显示的文件名(可选)
   * @param contentType      响应 Content-Type(可选)
   */
  public static String getPresignedDownloadUrl(String regionName, String bucket, String objectKey, Duration expires,
      String downloadFilename, String contentType) {

    if (expires == null) {
      expires = DEFAULT_PRESIGN_EXPIRES;
    }

    COSClient client = null;
    try {
      client = buildClient(regionName);

      Date expiration = new Date(System.currentTimeMillis() + expires.toMillis());

      GeneratePresignedUrlRequest req = new GeneratePresignedUrlRequest(bucket, objectKey, HttpMethodName.GET);
      req.setExpiration(expiration);

      // 让浏览器下载,并兼容中文文件名
      if (downloadFilename != null && downloadFilename.length() > 0) {
        String safe = downloadFilename.replace("\"", "");
        String encoded = URLEncoder.encode(downloadFilename, StandardCharsets.UTF_8).replace("+", "%20");
        String disposition = "attachment; filename=\"" + safe + "\"; filename*=UTF-8''" + encoded;
        req.putCustomRequestHeader("response-content-disposition", disposition);
      } else {
        req.putCustomRequestHeader("response-content-disposition", "attachment");
      }

      if (contentType != null && contentType.length() > 0) {
        req.putCustomRequestHeader("response-content-type", contentType);
      }

      URL url = client.generatePresignedUrl(req);
      return url.toString();

    } catch (Exception e) {
      throw new RuntimeException(e);
    } finally {
      if (client != null) {
        client.shutdown();
      }
    }
  }

  // -------------------------
  // Client builder
  // -------------------------

  public static COSClient buildClient() {
    return buildClient();
  }

  public static COSClient buildClient(String regionName) {
    if (regionName == null || regionName.length() == 0) {
      throw new IllegalStateException("TENCENT_COS_REGION_NAME is empty");
    }
    if (bucketName == null || bucketName.length() == 0) {
      throw new IllegalStateException("TENCENT_COS_BUCKET_NAME is empty");
    }
    if (secretId == null || secretId.length() == 0 || secretKey == null || secretKey.length() == 0) {
      throw new IllegalStateException("TENCENT_COS_SECRET_ID / TENCENT_COS_SECRET_KEY is empty");
    }

    COSCredentials cred = new BasicCOSCredentials(secretId, secretKey);
    Region region = new Region(regionName);

    ClientConfig clientConfig = new ClientConfig(region);
    clientConfig.setHttpProtocol(HttpProtocol.https);

    return new COSClient(cred, clientConfig);
  }

  public static String getBucketName() {
    return bucketName;
  }

  public static String getRegionName() {
    return regionName;
  }

}

TencentStorageService 接收文件上传并入库 ​

java
package nexus.io.tio.boot.admin.services.storage;

import com.jfinal.kit.StrKit;
import nexus.io.db.TableInput;
import nexus.io.db.activerecord.Row;
import nexus.io.jfinal.aop.Aop;
import nexus.io.model.body.RespBodyVo;
import nexus.io.model.upload.UploadFile;
import nexus.io.model.upload.UploadResult;
import nexus.io.table.services.ApiTable;
import nexus.io.tio.boot.admin.consts.StoragePlatformConst;
import nexus.io.tio.boot.admin.costants.TioBootAdminTableNames;
import nexus.io.tio.boot.admin.dao.SystemUploadFileDao;
import nexus.io.tio.boot.admin.services.StorageService;
import nexus.io.tio.boot.admin.services.SysConfigConstantsService;
import nexus.io.tio.boot.admin.services.system.SystemUploadFileService;
import nexus.io.tio.boot.admin.utils.storage.TencentCOSUtils;
import nexus.io.tio.boot.admin.vo.SystemTxCosConfigVo;
import nexus.io.tio.utils.crypto.Md5Utils;
import nexus.io.tio.utils.hutool.FilenameUtils;
import nexus.io.tio.utils.snowflake.SnowflakeIdUtils;
import com.qcloud.cos.COSClient;

import lombok.extern.slf4j.Slf4j;

/**
 * Created by Tong Li <https://github.com/litongjava>
 */
@Slf4j
public class TencentStorageService implements StorageService {
  public RespBodyVo upload(UploadFile uploadFile) {

    String category = DEFAULT_CATEGORY;
    return upload(category, uploadFile);
  }

  public RespBodyVo upload(String category, UploadFile uploadFile) {
    if (StrKit.isBlank(category)) {
      category = DEFAULT_CATEGORY;
    }
    UploadResult vo = uploadFile(category, uploadFile);
    return RespBodyVo.ok(vo);
  }

  @Override
  public UploadResult uploadFile(String category, UploadFile uploadFile) {
    long id = SnowflakeIdUtils.id();
    UploadResult vo = uploadFile(category, uploadFile, id);
    return vo;
  }

  @Override
  public UploadResult uploadFile(String category, UploadFile uploadFile, Long id) {
    String filename = uploadFile.getName();

    String suffix = FilenameUtils.getSuffix(filename);
    String newFilename = id + "." + suffix;

    String targetName = category + newFilename;

    UploadResult vo = uploadFile(id, targetName, uploadFile, suffix);
    return vo;
  }

  public String getUrl(String bucketName, String targetName) {
    return TencentCOSUtils.getUrl(bucketName, targetName);
  }

  @Override
  public String getUrl(String targetName) {
    return TencentCOSUtils.getUrl(targetName);
  }

  public UploadResult getUrlById(String id) {
    return getUrlById(Long.parseLong(id));
  }

  public UploadResult getUrlById(long id) {
    Row record = Aop.get(SystemUploadFileDao.class).getFileBasicInfoById(id);
    if (record == null) {
      return null;
    }
    String url = this.getUrl(record.getStr("bucket_name"), record.getStr("target_name"));
    String originFilename = record.getStr("fielename");
    String md5 = record.getStr("md5");
    Long size = record.getLong("size");
    return new UploadResult(id, originFilename, size, url, md5);
  }

  public UploadResult getUrlByMd5(String md5) {
    return Aop.get(SystemUploadFileService.class).getUrlByMd5(md5);
  }

  @Override
  public UploadResult uploadFile(long id, String targetName, UploadFile uploadFile, String suffix) {
    byte[] fileContent = uploadFile.getData();
    String filename = uploadFile.getName();
    long size = uploadFile.getSize();
    SystemTxCosConfigVo systemTxCosConfig = Aop.get(SysConfigConstantsService.class).getSystemTxCosConfig();

    String bucketName = systemTxCosConfig.getBucketName();

    String etag = null;
    COSClient cosClient = null;
    try {
      cosClient = TencentCOSUtils.buildClient();
      etag = TencentCOSUtils.upload(cosClient, targetName, fileContent, suffix).getETag();
    } catch (Exception e) {
      log.error("Error uploading file", e);
      throw new RuntimeException(e);
    } finally {
      if (cosClient != null) {
        cosClient.shutdown();
      }
    }

    // Log and save to database
    log.info("Uploaded to COS with ETag: {}", etag);
    String md5 = Md5Utils.md5Hex(fileContent);
    TableInput kv = TableInput.create().set("md5", md5).set("name", filename).set("size", size)
        //
        .set("platform", StoragePlatformConst.aws_s3).set("region_name", systemTxCosConfig.getRegion())
        //
        .set("bucket_name", bucketName).set("target_name", targetName).set("file_id", etag);

    ApiTable.save(TioBootAdminTableNames.tio_boot_admin_system_upload_file, kv);
    String downloadUrl = getUrl(bucketName, targetName);

    UploadResult uploadResultVo = new UploadResult();
    uploadResultVo.setId(id).setUrl(downloadUrl).setSize(size);
    uploadResultVo.setMd5(md5).setTargetName(targetName);

    return uploadResultVo;
  }

  @Override
  public String getPresignedDownloadUrl(String targetName) {
    return TencentCOSUtils.getPresignedDownloadUrl(targetName);
  }

  @Override
  public String getPresignedDownloadUrl(String bucket, String targetName) {
    return TencentCOSUtils.getPresignedDownloadUrl(bucket, targetName);
  }

  @Override
  public String getPresignedDownloadUrl(String region, String bucket, String targetName) {
    return TencentCOSUtils.getPresignedDownloadUrl(region, bucket, targetName);
  }

  @Override
  public UploadResult getPresignedDownloadUrl(Long id) {
    return Aop.get(SystemUploadFileService.class).getPresignedDownloadUrl(id);
  }

}

SystemFileTencentCosHandler ​

java
package nexus.io.tio.boot.admin.handler.system;

import java.util.Map;

import com.jfinal.kit.Kv;
import com.jfinal.kit.StrKit;
import nexus.io.db.TableInput;
import nexus.io.db.TableResult;
import nexus.io.db.activerecord.Row;
import nexus.io.jfinal.aop.Aop;
import nexus.io.model.body.RespBodyVo;
import nexus.io.model.upload.UploadFile;
import nexus.io.model.upload.UploadResult;
import nexus.io.table.services.ApiTable;
import nexus.io.table.utils.TableInputUtils;
import nexus.io.table.utils.TableResultUtils;
import nexus.io.tio.boot.admin.costants.TioBootAdminTableNames;
import nexus.io.tio.boot.admin.services.storage.TencentStorageService;
import nexus.io.tio.boot.http.TioRequestContext;
import nexus.io.tio.boot.utils.TioRequestParamUtils;
import nexus.io.tio.http.common.HttpRequest;
import nexus.io.tio.http.common.HttpResponse;
import nexus.io.tio.http.server.model.HttpCors;
import nexus.io.tio.http.server.util.CORSUtils;
import nexus.io.tio.http.server.util.Resps;

/**
 * Created by Tong Li <https://github.com/litongjava>
 */
public class SystemFileTencentCosHandler {

  public HttpResponse upload(HttpRequest request) throws Exception {
    HttpResponse httpResponse = TioRequestContext.getResponse();
    CORSUtils.enableCORS(httpResponse, new HttpCors());

    UploadFile uploadFile = request.getUploadFile("file");
    TencentStorageService storageService = Aop.get(TencentStorageService.class);
    if (uploadFile != null) {
      RespBodyVo respVo = storageService.upload(uploadFile);
      return Resps.json(httpResponse, respVo);

    }
    return Resps.json(httpResponse, RespBodyVo.ok("Fail"));
  }
  
  public HttpResponse getUploadRecordByMd5(HttpRequest request) throws Exception {
    HttpResponse httpResponse = TioRequestContext.getResponse();
    CORSUtils.enableCORS(httpResponse, new HttpCors());

    Map<String, Object> map = TioRequestParamUtils.getRequestMap(request);
    TableInput kv = TableInputUtils.camelToUnderscore(map);
    // 调用ApiTable查询数据
    TableResult<Row> jsonBean = ApiTable.get(TioBootAdminTableNames.tio_boot_admin_system_upload_file, kv);
    TableResult<Kv> TableResult = TableResultUtils.recordToKv(jsonBean);

    return Resps.json(httpResponse, RespBodyVo.ok(TableResult.getData()).code(TableResult.getCode()).msg(TableResult.getMsg()));
  }

  
  public HttpResponse getUrl(HttpRequest request) throws Exception {
    HttpResponse httpResponse = TioRequestContext.getResponse();
    CORSUtils.enableCORS(httpResponse, new HttpCors());

    TencentStorageService storageService = Aop.get(TencentStorageService.class);
    RespBodyVo respBodyVo = null;
    String id = request.getParam("id");

    String md5 = request.getParam("md5");
    if (StrKit.notBlank(id)) {
      // id,md5,name,url,
      UploadResult uploadResultVo = storageService.getUrlById(id);
      if (uploadResultVo == null) {
        respBodyVo = RespBodyVo.fail();
      } else {
        respBodyVo = RespBodyVo.ok(uploadResultVo);
      }

    } else if (StrKit.notBlank(md5)) {
      UploadResult uploadResultVo = storageService.getUrlByMd5(md5);
      if (uploadResultVo == null) {
        respBodyVo = RespBodyVo.fail();
      } else {
        respBodyVo = RespBodyVo.ok(uploadResultVo);
      }
    } else {
      respBodyVo = RespBodyVo.fail("id or md5 can not be empty");
    }

    return Resps.json(httpResponse, respBodyVo);
  }
}

MIT Licensed