From f222b215d8b24c310ca4cff6fe1af7e6d6d94988 Mon Sep 17 00:00:00 2001 From: zhangsan <646228430@qq.com> Date: Fri, 7 Mar 2025 15:47:21 +0800 Subject: [PATCH] =?UTF-8?q?3.7=20=E8=85=BE=E8=AE=AF=E4=BA=91COS=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E4=B8=8A=E4=BC=A0=E6=8E=A5=E5=8F=A3=EF=BC=8Capplicati?= =?UTF-8?q?on-local=E6=96=87=E4=BB=B6=E4=BF=9D=E5=AD=98=E9=9A=90=E7=A7=81?= =?UTF-8?q?=E6=95=B0=E6=8D=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 + pom.xml | 6 + sql/create_table.sql | 26 ++++ .../aop/AuthInterceptor.java | 1 - .../config/CosClientConfig.java | 58 +++++++++ .../controller/FileController.java | 114 ++++++++++++++++++ .../manager/CosManager.java | 110 +++++++++++++++++ src/main/resources/application.yml | 17 ++- 8 files changed, 334 insertions(+), 2 deletions(-) create mode 100644 src/main/java/edu/whut/smilepicturebackend/config/CosClientConfig.java create mode 100644 src/main/java/edu/whut/smilepicturebackend/controller/FileController.java create mode 100644 src/main/java/edu/whut/smilepicturebackend/manager/CosManager.java diff --git a/.gitignore b/.gitignore index 141f938..0eb663d 100644 --- a/.gitignore +++ b/.gitignore @@ -67,6 +67,10 @@ rebel.xml # Docker 相关 docker-compose.override.yml +# 排除本地开发环境配置(如 Spring Boot 的 application-local.yml) +application-local.yml +application-local.* + # 如果你用到 IDEA 自带的 File-Based Storage(8+版本默认),可以添加: # .idea/.name # .idea/gradle.xml diff --git a/pom.xml b/pom.xml index e1ee83c..0ac29a8 100644 --- a/pom.xml +++ b/pom.xml @@ -51,6 +51,12 @@ com.baomidou mybatis-plus-jsqlparser-4.9 + + + com.qcloud + cos_api + 5.6.227 + com.mysql mysql-connector-j diff --git a/sql/create_table.sql b/sql/create_table.sql index 3718b59..643b133 100644 --- a/sql/create_table.sql +++ b/sql/create_table.sql @@ -21,3 +21,29 @@ create table if not exists user UNIQUE KEY uk_userAccount (user_account), INDEX idx_userName (user_name) ) comment '用户' collate = utf8mb4_unicode_ci; + +-- 图片表 +create table if not exists picture +( + id bigint auto_increment comment 'id' primary key, + url varchar(512) not null comment '图片 url', + name varchar(128) not null comment '图片名称', + introduction varchar(512) null comment '简介', + category varchar(64) null comment '分类', + tags varchar(512) null comment '标签(JSON 数组)', + pic_size bigint null comment '图片体积', + pic_width int null comment '图片宽度', + pic_height int null comment '图片高度', + pic_scale double null comment '图片宽高比例', + pic_format varchar(32) null comment '图片格式', + user_id bigint not null comment '创建用户 id', + create_time datetime default CURRENT_TIMESTAMP not null comment '创建时间', + edit_time datetime default CURRENT_TIMESTAMP not null comment '编辑时间', + update_time datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间', + is_delete tinyint default 0 not null comment '是否删除', + INDEX idx_name (name), -- 提升基于图片名称的查询性能 + INDEX idx_introduction (introduction), -- 用于模糊搜索图片简介 + INDEX idx_category (category), -- 提升基于分类的查询性能 + INDEX idx_tags (tags), -- 提升基于标签的查询性能 + INDEX idx_userId (user_id) -- 提升基于用户 ID 的查询性能 +) comment '图片' collate = utf8mb4_unicode_ci; \ No newline at end of file diff --git a/src/main/java/edu/whut/smilepicturebackend/aop/AuthInterceptor.java b/src/main/java/edu/whut/smilepicturebackend/aop/AuthInterceptor.java index 27010db..f254f53 100644 --- a/src/main/java/edu/whut/smilepicturebackend/aop/AuthInterceptor.java +++ b/src/main/java/edu/whut/smilepicturebackend/aop/AuthInterceptor.java @@ -15,7 +15,6 @@ import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; -import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; @Aspect diff --git a/src/main/java/edu/whut/smilepicturebackend/config/CosClientConfig.java b/src/main/java/edu/whut/smilepicturebackend/config/CosClientConfig.java new file mode 100644 index 0000000..e4a1248 --- /dev/null +++ b/src/main/java/edu/whut/smilepicturebackend/config/CosClientConfig.java @@ -0,0 +1,58 @@ +package edu.whut.smilepicturebackend.config; + +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.region.Region; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "cos.client") +@Data +public class CosClientConfig { + + /** + * 域名 + */ + private String host; + + /** + * secretId + */ + private String secretId; + + /** + * 密钥(注意不要泄露) + */ + private String secretKey; + + /** + * 区域 + */ + private String region; + + /** + * 桶名 + */ + private String bucket; + + @Bean + public COSClient cosClient() { + // 1 初始化用户身份信息(secretId, secretKey)。 + // SECRETID 和 SECRETKEY 请登录访问管理控制台 https://console.cloud.tencent.com/cam/capi 进行查看和管理 + COSCredentials cred = new BasicCOSCredentials(secretId, secretKey); + // 2 设置 bucket 的地域, COS 地域的简称请参见 https://cloud.tencent.com/document/product/436/6224 + // clientConfig 中包含了设置 region, https(默认 http), 超时, 代理等 set 方法, 使用可参见源码或者常见问题 Java SDK 部分。 + ClientConfig clientConfig = new ClientConfig(new Region(region)); + // 这里建议设置使用 https 协议 + // 从 5.6.54 版本开始,默认使用了 https + clientConfig.setHttpProtocol(HttpProtocol.https); + // 3 生成 cos 客户端。 + return new COSClient(cred, clientConfig); + } +} diff --git a/src/main/java/edu/whut/smilepicturebackend/controller/FileController.java b/src/main/java/edu/whut/smilepicturebackend/controller/FileController.java new file mode 100644 index 0000000..01f69ae --- /dev/null +++ b/src/main/java/edu/whut/smilepicturebackend/controller/FileController.java @@ -0,0 +1,114 @@ +package edu.whut.smilepicturebackend.controller; + +import com.qcloud.cos.model.COSObject; +import com.qcloud.cos.model.COSObjectInputStream; +import com.qcloud.cos.utils.IOUtils; +import edu.whut.smilepicturebackend.annotation.AuthCheck; +import edu.whut.smilepicturebackend.common.BaseResponse; +import edu.whut.smilepicturebackend.common.ResultUtils; +import edu.whut.smilepicturebackend.constant.UserConstant; +import edu.whut.smilepicturebackend.exception.BusinessException; +import edu.whut.smilepicturebackend.exception.ErrorCode; +import edu.whut.smilepicturebackend.manager.CosManager; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import javax.annotation.Resource; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; + +@Slf4j +@RestController +@RequestMapping("/file") +public class FileController { + + @Resource + private CosManager cosManager; + + /** + * 测试文件上传 + * + * @param multipartFile + * @return + */ + @AuthCheck(mustRole = UserConstant.ADMIN_ROLE) + @PostMapping("/test/upload") + public BaseResponse testUploadFile(@RequestPart("file") MultipartFile multipartFile) { + // 文件目录 + String filename = multipartFile.getOriginalFilename(); + String filepath = String.format("/test/%s", filename); + try (InputStream in = multipartFile.getInputStream()) { + cosManager.putObject( + filepath, + in, + multipartFile.getSize(), + multipartFile.getContentType() + ); + return ResultUtils.success(filepath); + } catch (Exception e) { + log.error("file upload error, filepath = {}", filepath, e); + throw new BusinessException(ErrorCode.SYSTEM_ERROR, "上传失败"); + } +// File file = null; +// try { +// // 上传文件 +// file = File.createTempFile(filepath, null); +// multipartFile.transferTo(file); //临时文件,适配这个接口 +// cosManager.putObject(filepath, file); +// // 返回可访问的地址 +// return ResultUtils.success(filepath); +// } catch (Exception e) { +// log.error("file upload error, filepath = " + filepath, e); +// throw new BusinessException(ErrorCode.SYSTEM_ERROR, "上传失败"); +// } finally { +// if (file != null) { +// // 删除临时文件 +// boolean delete = file.delete(); +// if (!delete) { +// log.error("file delete error, filepath = {}", filepath); +// } +// } +// } + } + + /** + * 测试文件下载 + * + * @param filepath 文件路径 + * @param response 响应对象 + */ + @AuthCheck(mustRole = UserConstant.ADMIN_ROLE) + @GetMapping("/test/download/") + public void testDownloadFile(String filepath, HttpServletResponse response) throws IOException { + COSObjectInputStream cosObjectInput = null; + try { + COSObject cosObject = cosManager.getObject(filepath); + cosObjectInput = cosObject.getObjectContent(); + byte[] bytes = IOUtils.toByteArray(cosObjectInput); + // 设置响应头 + response.setContentType("application/octet-stream;charset=UTF-8"); + response.setHeader("Content-Disposition", "attachment; filename=" + filepath); + // 写入响应 + response.getOutputStream().write(bytes); + response.getOutputStream().flush(); + } catch (Exception e) { + log.error("file download error, filepath = " + filepath, e); + throw new BusinessException(ErrorCode.SYSTEM_ERROR, "下载失败"); + } finally { + // 释放流 + if (cosObjectInput != null) { + cosObjectInput.close(); + } + } + + } +} + + + + + + + diff --git a/src/main/java/edu/whut/smilepicturebackend/manager/CosManager.java b/src/main/java/edu/whut/smilepicturebackend/manager/CosManager.java new file mode 100644 index 0000000..7295b69 --- /dev/null +++ b/src/main/java/edu/whut/smilepicturebackend/manager/CosManager.java @@ -0,0 +1,110 @@ +package edu.whut.smilepicturebackend.manager; + +import cn.hutool.core.io.FileUtil; +import com.qcloud.cos.COSClient; +import com.qcloud.cos.model.*; +import com.qcloud.cos.model.ciModel.persistence.PicOperations; +import edu.whut.smilepicturebackend.config.CosClientConfig; +import org.springframework.stereotype.Component; + +import javax.annotation.Resource; +import java.io.File; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +@Component +public class CosManager { + + @Resource + private CosClientConfig cosClientConfig; + + @Resource + private COSClient cosClient; + + /** + * 上传对象 + * + * @param key 唯一键 + * @param file 文件 + */ + public PutObjectResult putObject(String key, File file) { + PutObjectRequest putObjectRequest = new PutObjectRequest(cosClientConfig.getBucket(), key, + file); + return cosClient.putObject(putObjectRequest); + } + + /** + * 上传对象,接收InputStream文件流 + * @param key + * @param in + * @param contentLength + * @param contentType + */ + public void putObject(String key, InputStream in, long contentLength, String contentType) { + ObjectMetadata meta = new ObjectMetadata(); + meta.setContentLength(contentLength); + meta.setContentType(contentType); + // 其它 metadata 设置... + cosClient.putObject(cosClientConfig.getBucket(), key, in, meta); + } + + + /** + * 下载对象 + * + * @param key 唯一键 + */ + public COSObject getObject(String key) { + GetObjectRequest getObjectRequest = new GetObjectRequest(cosClientConfig.getBucket(), key); + return cosClient.getObject(getObjectRequest); + } + + /** + * 上传对象(附带图片信息) + * + * @param key 唯一键 + * @param file 文件 + */ + public PutObjectResult putPictureObject(String key, File file) { + PutObjectRequest putObjectRequest = new PutObjectRequest(cosClientConfig.getBucket(), key, + file); + // 对图片进行处理(获取基本信息也被视作为一种图片的处理) + PicOperations picOperations = new PicOperations(); + // 1 表示返回原图信息 + picOperations.setIsPicInfo(1); + // 图片处理规则列表 + List rules = new ArrayList<>(); + // 1. 图片压缩(转成 webp 格式) + String webpKey = FileUtil.mainName(key) + ".webp"; + PicOperations.Rule compressRule = new PicOperations.Rule(); + compressRule.setFileId(webpKey); + compressRule.setBucket(cosClientConfig.getBucket()); + compressRule.setRule("imageMogr2/format/webp"); + rules.add(compressRule); + // 2. 缩略图处理,仅对 > 20 KB 的图片生成缩略图 + if (file.length() > 2 * 1024) { + PicOperations.Rule thumbnailRule = new PicOperations.Rule(); + // 拼接缩略图的路径 + String thumbnailKey = FileUtil.mainName(key) + "_thumbnail." + FileUtil.getSuffix(key); + thumbnailRule.setFileId(thumbnailKey); + thumbnailRule.setBucket(cosClientConfig.getBucket()); + // 缩放规则 /thumbnail/x>(如果大于原图宽高,则不处理) + thumbnailRule.setRule(String.format("imageMogr2/thumbnail/%sx%s>", 256, 256)); + rules.add(thumbnailRule); + } + // 构造处理参数 + picOperations.setRules(rules); + putObjectRequest.setPicOperations(picOperations); + return cosClient.putObject(putObjectRequest); + } + + /** + * 删除对象 + * + * @param key 唯一键 + */ + public void deleteObject(String key) { + cosClient.deleteObject(cosClientConfig.getBucket(), key); + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index cd1ec9f..875a642 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -4,6 +4,8 @@ server: context-path: /api spring: + profiles: + active: local application: name: smile-picture-backend # 数据库配置 @@ -12,6 +14,10 @@ spring: url: jdbc:mysql://localhost:3306/smile-picture username: root password: 123456 + servlet: + multipart: + max-file-size: 10MB + max-request-size: 10MB mybatis-plus: type-aliases-package: edu.whut.smilepicturebackend.model.entity @@ -37,4 +43,13 @@ knife4j: default: api-rule: package api-rule-resources: - - edu.whut.smilepicturebackend.controller #接口地址 \ No newline at end of file + - edu.whut.smilepicturebackend.controller #接口地址 + +# 对象存储配置(需要从腾讯云获取) +cos: + client: + host: ${smile-picture.cos.client.host} + secretId: ${smile-picture.cos.client.secretId} + secretKey: ${smile-picture.cos.client.secretKey} + region: ${smile-picture.cos.client.region} + bucket: ${smile-picture.cos.client.bucket} \ No newline at end of file