3.17 图片存储优化,redis保存session信息,实现持久化登录状态
This commit is contained in:
parent
0fd9ef2e83
commit
1026b764dd
5
pom.xml
5
pom.xml
@ -74,6 +74,11 @@
|
|||||||
<artifactId>caffeine</artifactId>
|
<artifactId>caffeine</artifactId>
|
||||||
<version>3.1.8</version>
|
<version>3.1.8</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- Spring Session + Redis -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.session</groupId>
|
||||||
|
<artifactId>spring-session-data-redis</artifactId>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||||
|
@ -40,7 +40,8 @@ public class PictureController {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上传图片(可重新上传)
|
* 上传图片(可重新上传),前端选中图片就会调用该接口,无需前端点'创建'按钮!
|
||||||
|
*
|
||||||
*/
|
*/
|
||||||
@PostMapping("/upload")
|
@PostMapping("/upload")
|
||||||
// @AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
|
// @AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
|
||||||
@ -79,20 +80,7 @@ public class PictureController {
|
|||||||
throw new BusinessException(ErrorCode.PARAMS_ERROR);
|
throw new BusinessException(ErrorCode.PARAMS_ERROR);
|
||||||
}
|
}
|
||||||
User loginUser = userService.getLoginUser(request);
|
User loginUser = userService.getLoginUser(request);
|
||||||
Long id = deleteRequest.getId();
|
pictureService.deletePicture(deleteRequest.getId(), loginUser);
|
||||||
//判断用户是否存在
|
|
||||||
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);
|
|
||||||
return ResultUtils.success(true);
|
return ResultUtils.success(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -209,7 +197,7 @@ public class PictureController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 编辑图片(给用户使用)
|
* 编辑图片(给用户使用)或创建图片时,编辑标签、分类的时候
|
||||||
*/
|
*/
|
||||||
@PostMapping("/edit")
|
@PostMapping("/edit")
|
||||||
public BaseResponse<Boolean> editPicture(@RequestBody PictureEditRequest pictureEditRequest, HttpServletRequest request) {
|
public BaseResponse<Boolean> editPicture(@RequestBody PictureEditRequest pictureEditRequest, HttpServletRequest request) {
|
||||||
|
@ -1,15 +1,19 @@
|
|||||||
package edu.whut.smilepicturebackend.manager;
|
package edu.whut.smilepicturebackend.manager;
|
||||||
import cn.hutool.core.io.FileUtil;
|
import cn.hutool.core.io.FileUtil;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
import com.qcloud.cos.COSClient;
|
import com.qcloud.cos.COSClient;
|
||||||
import com.qcloud.cos.model.*;
|
import com.qcloud.cos.model.*;
|
||||||
import com.qcloud.cos.model.ciModel.persistence.PicOperations;
|
import com.qcloud.cos.model.ciModel.persistence.PicOperations;
|
||||||
import edu.whut.smilepicturebackend.config.CosClientConfig;
|
import edu.whut.smilepicturebackend.config.CosClientConfig;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -19,6 +23,7 @@ import java.util.List;
|
|||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
public class CosManager {
|
public class CosManager {
|
||||||
|
|
||||||
private final CosClientConfig cosClientConfig;
|
private final CosClientConfig cosClientConfig;
|
||||||
@ -155,11 +160,45 @@ public class CosManager {
|
|||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除对象
|
* 删除对象 https://cloud.tencent.com/document/product/436/65939
|
||||||
*
|
* @param url
|
||||||
* @param key 唯一键
|
|
||||||
*/
|
*/
|
||||||
public void deleteObject(String key) {
|
public void deleteIfNotBlank(String url) {
|
||||||
cosClient.deleteObject(cosClientConfig.getBucket(), key);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -135,6 +135,7 @@ public abstract class PictureUploadTemplate {
|
|||||||
uploadPictureResult.setPicColor(imageInfo.getAve());
|
uploadPictureResult.setPicColor(imageInfo.getAve());
|
||||||
// 设置缩略图地址
|
// 设置缩略图地址
|
||||||
uploadPictureResult.setThumbnailUrl(cosClientConfig.getHost() + "/" + thumbnailCiObject.getKey());
|
uploadPictureResult.setThumbnailUrl(cosClientConfig.getHost() + "/" + thumbnailCiObject.getKey());
|
||||||
|
//设置原图地址
|
||||||
uploadPictureResult.setOriginalUrl(cosClientConfig.getHost() + "/" + uploadPath);
|
uploadPictureResult.setOriginalUrl(cosClientConfig.getHost() + "/" + uploadPath);
|
||||||
// 返回可访问的地址
|
// 返回可访问的地址
|
||||||
return uploadPictureResult;
|
return uploadPictureResult;
|
||||||
|
@ -43,6 +43,14 @@ public interface PictureService extends IService<Picture> {
|
|||||||
*/
|
*/
|
||||||
LambdaQueryWrapper<Picture> getQueryWrapper(PictureQueryRequest pictureQueryRequest);
|
LambdaQueryWrapper<Picture> getQueryWrapper(PictureQueryRequest pictureQueryRequest);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除图片
|
||||||
|
*
|
||||||
|
* @param pictureId
|
||||||
|
* @param loginUser
|
||||||
|
*/
|
||||||
|
void deletePicture(long pictureId, User loginUser);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取图片包装类(单条)
|
* 获取图片包装类(单条)
|
||||||
*
|
*
|
||||||
|
@ -40,6 +40,7 @@ import org.jsoup.select.Elements;
|
|||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.scheduling.annotation.Async;
|
import org.springframework.scheduling.annotation.Async;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.util.DigestUtils;
|
import org.springframework.util.DigestUtils;
|
||||||
|
|
||||||
|
|
||||||
@ -84,19 +85,19 @@ public class PictureServiceImpl extends ServiceImpl<PictureMapper, Picture>
|
|||||||
ThrowUtils.throwIf(introduction.length() > 800, ErrorCode.PARAMS_ERROR, "简介过长");
|
ThrowUtils.throwIf(introduction.length() > 800, ErrorCode.PARAMS_ERROR, "简介过长");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public PictureVO uploadPicture(Object inputSource, PictureUploadRequest pictureUploadRequest, User loginUser) {
|
public PictureVO uploadPicture(Object inputSource, PictureUploadRequest pictureUploadRequest, User loginUser) {
|
||||||
// 校验参数
|
// 校验参数
|
||||||
ThrowUtils.throwIf(loginUser == null, ErrorCode.NO_AUTH_ERROR);
|
ThrowUtils.throwIf(loginUser == null, ErrorCode.NO_AUTH_ERROR);
|
||||||
|
|
||||||
// 判断是新增还是删除
|
// 判断是创建还是替换
|
||||||
Long pictureId = null;
|
Long pictureId = pictureUploadRequest == null ? null : pictureUploadRequest.getId();
|
||||||
if (pictureUploadRequest != null) {
|
Picture oldPicture = null;
|
||||||
pictureId = pictureUploadRequest.getId();
|
|
||||||
}
|
|
||||||
// 如果是更新,判断图片是否存在
|
// 如果是更新,判断图片是否存在
|
||||||
if (pictureId != null) {
|
if (pictureId != null) {
|
||||||
Picture oldPicture = this.getById(pictureId);
|
oldPicture = this.getById(pictureId);
|
||||||
ThrowUtils.throwIf(oldPicture == null, ErrorCode.NOT_FOUND_ERROR, "图片不存在");
|
ThrowUtils.throwIf(oldPicture == null, ErrorCode.NOT_FOUND_ERROR, "图片不存在");
|
||||||
// 仅本人或管理员可编辑图片
|
// 仅本人或管理员可编辑图片
|
||||||
if (!oldPicture.getUserId().equals(loginUser.getId()) && !userService.isAdmin(loginUser)) {
|
if (!oldPicture.getUserId().equals(loginUser.getId()) && !userService.isAdmin(loginUser)) {
|
||||||
@ -112,8 +113,9 @@ public class PictureServiceImpl extends ServiceImpl<PictureMapper, Picture>
|
|||||||
if (inputSource instanceof String) {
|
if (inputSource instanceof String) {
|
||||||
pictureUploadTemplate = urlPictureUpload;
|
pictureUploadTemplate = urlPictureUpload;
|
||||||
}
|
}
|
||||||
|
//上传到腾讯云COS上
|
||||||
UploadPictureResult uploadPictureResult = pictureUploadTemplate.uploadPicture(inputSource, uploadPathPrefix);
|
UploadPictureResult uploadPictureResult = pictureUploadTemplate.uploadPicture(inputSource, uploadPathPrefix);
|
||||||
// 构造要入库的图片信息
|
// 构造要入库的图片信息,将图片信息存入数据库中。
|
||||||
Picture picture = new Picture();
|
Picture picture = new Picture();
|
||||||
// 复制同名属性(url、name、picSize、picWidth、picHeight、picScale、picFormat)
|
// 复制同名属性(url、name、picSize、picWidth、picHeight、picScale、picFormat)
|
||||||
BeanUtils.copyProperties(uploadPictureResult, picture);
|
BeanUtils.copyProperties(uploadPictureResult, picture);
|
||||||
@ -135,8 +137,11 @@ public class PictureServiceImpl extends ServiceImpl<PictureMapper, Picture>
|
|||||||
picture.setEditTime(new Date());
|
picture.setEditTime(new Date());
|
||||||
}
|
}
|
||||||
boolean result = this.saveOrUpdate(picture);
|
boolean result = this.saveOrUpdate(picture);
|
||||||
//todo:如果是更新,清理旧的图片
|
|
||||||
ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR, "图片上传失败,数据库操作失败");
|
ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR, "图片上传失败,数据库操作失败");
|
||||||
|
//如果是更新,清理旧的图片
|
||||||
|
if (oldPicture != null) {
|
||||||
|
this.clearPictureFile(oldPicture);
|
||||||
|
}
|
||||||
return PictureVO.objToVo(picture);
|
return PictureVO.objToVo(picture);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,6 +195,22 @@ public class PictureServiceImpl extends ServiceImpl<PictureMapper, Picture>
|
|||||||
return qw;
|
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
|
@Override
|
||||||
public PictureVO getPictureVO(Picture picture, HttpServletRequest request) {
|
public PictureVO getPictureVO(Picture picture, HttpServletRequest request) {
|
||||||
// 对象转封装类
|
// 对象转封装类
|
||||||
@ -418,7 +439,7 @@ public class PictureServiceImpl extends ServiceImpl<PictureMapper, Picture>
|
|||||||
@Async //异步执行
|
@Async //异步执行
|
||||||
@Override
|
@Override
|
||||||
public void clearPictureFile(Picture oldPicture) {
|
public void clearPictureFile(Picture oldPicture) {
|
||||||
// 判断改图片是否被多条记录使用(图片秒传的情况可能一个cos地址对应多个数据库url)
|
// 判断该图片是否被多条记录使用(图片秒传的情况可能一个cos地址对应多个数据库url)
|
||||||
String pictureUrl = oldPicture.getUrl();
|
String pictureUrl = oldPicture.getUrl();
|
||||||
long count = this.lambdaQuery()
|
long count = this.lambdaQuery()
|
||||||
.eq(Picture::getUrl, pictureUrl)
|
.eq(Picture::getUrl, pictureUrl)
|
||||||
@ -428,18 +449,18 @@ public class PictureServiceImpl extends ServiceImpl<PictureMapper, Picture>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// 删除图片
|
// 删除图片
|
||||||
cosManager.deleteObject(pictureUrl);
|
cosManager.deleteIfNotBlank(pictureUrl);
|
||||||
|
|
||||||
//删除原图
|
//删除原图
|
||||||
String originalUrl=oldPicture.getOriginalUrl();
|
String originalUrl=oldPicture.getOriginalUrl();
|
||||||
if (StrUtil.isNotBlank(originalUrl)) {
|
cosManager.deleteIfNotBlank(originalUrl);
|
||||||
cosManager.deleteObject(originalUrl);
|
|
||||||
}
|
|
||||||
// 删除缩略图
|
// 删除缩略图
|
||||||
String thumbnailUrl = oldPicture.getThumbnailUrl();
|
String thumbnailUrl = oldPicture.getThumbnailUrl();
|
||||||
if (StrUtil.isNotBlank(thumbnailUrl)) {
|
cosManager.deleteIfNotBlank(thumbnailUrl);
|
||||||
cosManager.deleteObject(thumbnailUrl);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,6 +2,10 @@ server:
|
|||||||
port: 8123
|
port: 8123
|
||||||
servlet:
|
servlet:
|
||||||
context-path: /api
|
context-path: /api
|
||||||
|
# cookie 30 天过期
|
||||||
|
session:
|
||||||
|
cookie:
|
||||||
|
max-age: 2592000
|
||||||
|
|
||||||
spring:
|
spring:
|
||||||
profiles:
|
profiles:
|
||||||
@ -21,6 +25,11 @@ spring:
|
|||||||
port: 6379
|
port: 6379
|
||||||
password: 123456
|
password: 123456
|
||||||
timeout: 5000
|
timeout: 5000
|
||||||
|
# Session 配置
|
||||||
|
session:
|
||||||
|
store-type: redis
|
||||||
|
# session 30 天后过期,单位是秒
|
||||||
|
timeout: 2592000
|
||||||
servlet:
|
servlet:
|
||||||
multipart:
|
multipart:
|
||||||
max-file-size: 10MB
|
max-file-size: 10MB
|
||||||
|
Loading…
x
Reference in New Issue
Block a user