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