<dependency>
<groupId>com.qcloud</groupId>
<artifactId>cos_api</artifactId>
<version>5.6.155</version>
</dependency>
package com.litongjava.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 com.litongjava.tio.utils.environment.EnvUtils;
import com.litongjava.tio.utils.http.ContentTypeUtils;
import com.litongjava.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);
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 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);
}
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();
}
}
}
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;
}
}
package com.litongjava.tio.boot.admin.services.storage;
import com.jfinal.kit.StrKit;
import com.litongjava.db.TableInput;
import com.litongjava.db.activerecord.Row;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.model.body.RespBodyVo;
import com.litongjava.model.upload.UploadFile;
import com.litongjava.model.upload.UploadResult;
import com.litongjava.table.services.ApiTable;
import com.litongjava.tio.boot.admin.consts.StoragePlatformConst;
import com.litongjava.tio.boot.admin.costants.TioBootAdminTableNames;
import com.litongjava.tio.boot.admin.dao.SystemUploadFileDao;
import com.litongjava.tio.boot.admin.services.StorageService;
import com.litongjava.tio.boot.admin.services.SysConfigConstantsService;
import com.litongjava.tio.boot.admin.services.system.SystemUploadFileService;
import com.litongjava.tio.boot.admin.utils.storage.TencentCOSUtils;
import com.litongjava.tio.boot.admin.vo.SystemTxCosConfigVo;
import com.litongjava.tio.utils.crypto.Md5Utils;
import com.litongjava.tio.utils.hutool.FilenameUtils;
import com.litongjava.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.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);
}
}
package com.litongjava.tio.boot.admin.handler.system;
import java.util.Map;
import com.jfinal.kit.Kv;
import com.jfinal.kit.StrKit;
import com.litongjava.db.TableInput;
import com.litongjava.db.TableResult;
import com.litongjava.db.activerecord.Row;
import com.litongjava.jfinal.aop.Aop;
import com.litongjava.model.body.RespBodyVo;
import com.litongjava.model.upload.UploadFile;
import com.litongjava.model.upload.UploadResult;
import com.litongjava.table.services.ApiTable;
import com.litongjava.table.utils.TableInputUtils;
import com.litongjava.table.utils.TableResultUtils;
import com.litongjava.tio.boot.admin.costants.TioBootAdminTableNames;
import com.litongjava.tio.boot.admin.services.storage.TencentStorageService;
import com.litongjava.tio.boot.http.TioRequestContext;
import com.litongjava.tio.boot.utils.TioRequestParamUtils;
import com.litongjava.tio.http.common.HttpRequest;
import com.litongjava.tio.http.common.HttpResponse;
import com.litongjava.tio.http.server.model.HttpCors;
import com.litongjava.tio.http.server.util.CORSUtils;
import com.litongjava.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);
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)) {
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);
}
}