From 1026b764ddcb5b8ff26c0877ddcb5f1b281c8c0c Mon Sep 17 00:00:00 2001 From: zhangsan <646228430@qq.com> Date: Mon, 17 Mar 2025 19:42:59 +0800 Subject: [PATCH] =?UTF-8?q?3.17=20=E5=9B=BE=E7=89=87=E5=AD=98=E5=82=A8?= =?UTF-8?q?=E4=BC=98=E5=8C=96=EF=BC=8Credis=E4=BF=9D=E5=AD=98session?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=EF=BC=8C=E5=AE=9E=E7=8E=B0=E6=8C=81=E4=B9=85?= =?UTF-8?q?=E5=8C=96=E7=99=BB=E5=BD=95=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 5 ++ sql/create_table.sql | 2 +- .../controller/PictureController.java | 20 ++----- .../manager/CosManager.java | 49 +++++++++++++++-- .../manager/upload/PictureUploadTemplate.java | 1 + .../service/PictureService.java | 8 +++ .../service/impl/PictureServiceImpl.java | 53 +++++++++++++------ src/main/resources/application.yml | 9 ++++ 8 files changed, 109 insertions(+), 38 deletions(-) diff --git a/pom.xml b/pom.xml index 14b2e0d..ad99baa 100644 --- a/pom.xml +++ b/pom.xml @@ -74,6 +74,11 @@ caffeine 3.1.8 + + + org.springframework.session + spring-session-data-redis + org.springframework.boot spring-boot-configuration-processor diff --git a/sql/create_table.sql b/sql/create_table.sql index b1e5280..46aba32 100644 --- a/sql/create_table.sql +++ b/sql/create_table.sql @@ -61,4 +61,4 @@ CREATE INDEX idx_reviewStatus ON picture (review_status); ALTER TABLE picture -- 添加新列 ADD COLUMN original_url varchar(512) NULL COMMENT '原图 url', - ADD COLUMN thumbnail_url varchar(512) NULL COMMENT '缩略图 url'; \ No newline at end of file + ADD COLUMN thumbnail_url varchar(512) NULL COMMENT '缩略图 url'; diff --git a/src/main/java/edu/whut/smilepicturebackend/controller/PictureController.java b/src/main/java/edu/whut/smilepicturebackend/controller/PictureController.java index 8403ebd..4d22916 100644 --- a/src/main/java/edu/whut/smilepicturebackend/controller/PictureController.java +++ b/src/main/java/edu/whut/smilepicturebackend/controller/PictureController.java @@ -40,7 +40,8 @@ public class PictureController { /** - * 上传图片(可重新上传) + * 上传图片(可重新上传),前端选中图片就会调用该接口,无需前端点'创建'按钮! + * */ @PostMapping("/upload") // @AuthCheck(mustRole = UserConstant.ADMIN_ROLE) @@ -79,20 +80,7 @@ public class PictureController { throw new BusinessException(ErrorCode.PARAMS_ERROR); } User loginUser = userService.getLoginUser(request); - Long id = deleteRequest.getId(); - //判断用户是否存在 - Picture oldPicture = pictureService.getById(id); - ThrowUtils.throwIf(oldPicture == null, ErrorCode.NOT_FOUND_ERROR); - // 仅本人或者管理员可删除 - if (!oldPicture.getUserId().equals(loginUser.getId()) - && !userService.isAdmin(loginUser)) { - throw new BusinessException(ErrorCode.NO_AUTH_ERROR); - } - // 操作数据库 - boolean result = pictureService.removeById(id); - ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR); - //清理图片资源 - pictureService.clearPictureFile(oldPicture); + pictureService.deletePicture(deleteRequest.getId(), loginUser); return ResultUtils.success(true); } @@ -209,7 +197,7 @@ public class PictureController { } /** - * 编辑图片(给用户使用) + * 编辑图片(给用户使用)或创建图片时,编辑标签、分类的时候 */ @PostMapping("/edit") public BaseResponse editPicture(@RequestBody PictureEditRequest pictureEditRequest, HttpServletRequest request) { diff --git a/src/main/java/edu/whut/smilepicturebackend/manager/CosManager.java b/src/main/java/edu/whut/smilepicturebackend/manager/CosManager.java index 27e4db3..b875ac8 100644 --- a/src/main/java/edu/whut/smilepicturebackend/manager/CosManager.java +++ b/src/main/java/edu/whut/smilepicturebackend/manager/CosManager.java @@ -1,15 +1,19 @@ package edu.whut.smilepicturebackend.manager; import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.StrUtil; 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 lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import javax.annotation.Resource; import java.io.File; import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; @@ -19,6 +23,7 @@ import java.util.List; */ @Component @RequiredArgsConstructor +@Slf4j public class CosManager { private final CosClientConfig cosClientConfig; @@ -155,11 +160,45 @@ public class CosManager { /** - * 删除对象 - * - * @param key 唯一键 + * 删除对象 https://cloud.tencent.com/document/product/436/65939 + * @param url */ - public void deleteObject(String key) { - cosClient.deleteObject(cosClientConfig.getBucket(), key); + public void deleteIfNotBlank(String url) { + if (StrUtil.isBlank(url)) { + return; + } + try { + // 1. 解析 URL → 得到对象 key + String key = extractUploadPath(url); + if (StrUtil.isBlank(key)) { + log.warn("无法从 URL 解析出 COS 对象 key,跳过删除: {}", url); + return; + } + // 2. 调用 COS SDK 删除 + log.info("清理图片中... {}", key); + cosClient.deleteObject(cosClientConfig.getBucket(), key); + } catch (IllegalArgumentException e) { + // URL 格式非法或解析失败时给出警告,不中断业务 + log.warn("删除图片失败:URL 解析异常,url={}", url, e); + } } + + /** + * 从完整 COS URL 中解析出对象 key(即 uploadPath) + * + * @param originalUrl 形如 https://bucket.cos.xxx/my/prefix/file.png + * @return my/prefix/file.png + */ + public static String extractUploadPath(String originalUrl) { + try { + // 1. 直接拿 URI 的 path 部分,例如 "//smile-picture/public/.../xxx.png" + String path = new URI(originalUrl).getPath(); + + // 2. 去掉所有前导斜杠,得到真正的 COS 对象 key + return path.replaceFirst("^/+", ""); // => smile-picture/public/.../xxx.png + } catch (URISyntaxException e) { + throw new IllegalArgumentException("非法的 COS URL: " + originalUrl, e); + } + } + } diff --git a/src/main/java/edu/whut/smilepicturebackend/manager/upload/PictureUploadTemplate.java b/src/main/java/edu/whut/smilepicturebackend/manager/upload/PictureUploadTemplate.java index d373d1b..a508002 100644 --- a/src/main/java/edu/whut/smilepicturebackend/manager/upload/PictureUploadTemplate.java +++ b/src/main/java/edu/whut/smilepicturebackend/manager/upload/PictureUploadTemplate.java @@ -135,6 +135,7 @@ public abstract class PictureUploadTemplate { uploadPictureResult.setPicColor(imageInfo.getAve()); // 设置缩略图地址 uploadPictureResult.setThumbnailUrl(cosClientConfig.getHost() + "/" + thumbnailCiObject.getKey()); + //设置原图地址 uploadPictureResult.setOriginalUrl(cosClientConfig.getHost() + "/" + uploadPath); // 返回可访问的地址 return uploadPictureResult; diff --git a/src/main/java/edu/whut/smilepicturebackend/service/PictureService.java b/src/main/java/edu/whut/smilepicturebackend/service/PictureService.java index fba9e72..75b427e 100644 --- a/src/main/java/edu/whut/smilepicturebackend/service/PictureService.java +++ b/src/main/java/edu/whut/smilepicturebackend/service/PictureService.java @@ -43,6 +43,14 @@ public interface PictureService extends IService { */ LambdaQueryWrapper getQueryWrapper(PictureQueryRequest pictureQueryRequest); + /** + * 删除图片 + * + * @param pictureId + * @param loginUser + */ + void deletePicture(long pictureId, User loginUser); + /** * 获取图片包装类(单条) * diff --git a/src/main/java/edu/whut/smilepicturebackend/service/impl/PictureServiceImpl.java b/src/main/java/edu/whut/smilepicturebackend/service/impl/PictureServiceImpl.java index fdd1bb4..fe4393f 100644 --- a/src/main/java/edu/whut/smilepicturebackend/service/impl/PictureServiceImpl.java +++ b/src/main/java/edu/whut/smilepicturebackend/service/impl/PictureServiceImpl.java @@ -40,6 +40,7 @@ import org.jsoup.select.Elements; import org.springframework.beans.BeanUtils; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.util.DigestUtils; @@ -84,19 +85,19 @@ public class PictureServiceImpl extends ServiceImpl ThrowUtils.throwIf(introduction.length() > 800, ErrorCode.PARAMS_ERROR, "简介过长"); } } + @Override public PictureVO uploadPicture(Object inputSource, PictureUploadRequest pictureUploadRequest, User loginUser) { // 校验参数 ThrowUtils.throwIf(loginUser == null, ErrorCode.NO_AUTH_ERROR); - // 判断是新增还是删除 - Long pictureId = null; - if (pictureUploadRequest != null) { - pictureId = pictureUploadRequest.getId(); - } + // 判断是创建还是替换 + Long pictureId = pictureUploadRequest == null ? null : pictureUploadRequest.getId(); + Picture oldPicture = null; + // 如果是更新,判断图片是否存在 if (pictureId != null) { - Picture oldPicture = this.getById(pictureId); + oldPicture = this.getById(pictureId); ThrowUtils.throwIf(oldPicture == null, ErrorCode.NOT_FOUND_ERROR, "图片不存在"); // 仅本人或管理员可编辑图片 if (!oldPicture.getUserId().equals(loginUser.getId()) && !userService.isAdmin(loginUser)) { @@ -112,8 +113,9 @@ public class PictureServiceImpl extends ServiceImpl if (inputSource instanceof String) { pictureUploadTemplate = urlPictureUpload; } + //上传到腾讯云COS上 UploadPictureResult uploadPictureResult = pictureUploadTemplate.uploadPicture(inputSource, uploadPathPrefix); - // 构造要入库的图片信息 + // 构造要入库的图片信息,将图片信息存入数据库中。 Picture picture = new Picture(); // 复制同名属性(url、name、picSize、picWidth、picHeight、picScale、picFormat) BeanUtils.copyProperties(uploadPictureResult, picture); @@ -135,8 +137,11 @@ public class PictureServiceImpl extends ServiceImpl picture.setEditTime(new Date()); } boolean result = this.saveOrUpdate(picture); - //todo:如果是更新,清理旧的图片 ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR, "图片上传失败,数据库操作失败"); + //如果是更新,清理旧的图片 + if (oldPicture != null) { + this.clearPictureFile(oldPicture); + } return PictureVO.objToVo(picture); } @@ -190,6 +195,22 @@ public class PictureServiceImpl extends ServiceImpl return qw; } + @Override + public void deletePicture(long pictureId, User loginUser) { + Picture oldPicture = this.getById(pictureId); + ThrowUtils.throwIf(oldPicture == null, ErrorCode.NOT_FOUND_ERROR); + // 仅本人或者管理员可删除 + if (!oldPicture.getUserId().equals(loginUser.getId()) + && !userService.isAdmin(loginUser)) { + throw new BusinessException(ErrorCode.NO_AUTH_ERROR); + } + // 操作数据库 + boolean result = this.removeById(pictureId); + ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR); + //清理图片资源 + this.clearPictureFile(oldPicture); + } + @Override public PictureVO getPictureVO(Picture picture, HttpServletRequest request) { // 对象转封装类 @@ -418,7 +439,7 @@ public class PictureServiceImpl extends ServiceImpl @Async //异步执行 @Override public void clearPictureFile(Picture oldPicture) { - // 判断改图片是否被多条记录使用(图片秒传的情况可能一个cos地址对应多个数据库url) + // 判断该图片是否被多条记录使用(图片秒传的情况可能一个cos地址对应多个数据库url) String pictureUrl = oldPicture.getUrl(); long count = this.lambdaQuery() .eq(Picture::getUrl, pictureUrl) @@ -428,18 +449,18 @@ public class PictureServiceImpl extends ServiceImpl return; } // 删除图片 - cosManager.deleteObject(pictureUrl); + cosManager.deleteIfNotBlank(pictureUrl); + //删除原图 String originalUrl=oldPicture.getOriginalUrl(); - if (StrUtil.isNotBlank(originalUrl)) { - cosManager.deleteObject(originalUrl); - } + cosManager.deleteIfNotBlank(originalUrl); + // 删除缩略图 String thumbnailUrl = oldPicture.getThumbnailUrl(); - if (StrUtil.isNotBlank(thumbnailUrl)) { - cosManager.deleteObject(thumbnailUrl); - } + cosManager.deleteIfNotBlank(thumbnailUrl); + } + } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index cc67e40..2998472 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -2,6 +2,10 @@ server: port: 8123 servlet: context-path: /api + # cookie 30 天过期 + session: + cookie: + max-age: 2592000 spring: profiles: @@ -21,6 +25,11 @@ spring: port: 6379 password: 123456 timeout: 5000 + # Session 配置 + session: + store-type: redis + # session 30 天后过期,单位是秒 + timeout: 2592000 servlet: multipart: max-file-size: 10MB