3.9 图片模块 增删查改,Mybatis-plus lambdaquerywrapper写条件查询

This commit is contained in:
zhangsan 2025-03-09 18:36:21 +08:00
parent f222b215d8
commit d4464ca14e
17 changed files with 1254 additions and 80 deletions

View File

@ -40,7 +40,7 @@ public class FileController {
String filename = multipartFile.getOriginalFilename(); String filename = multipartFile.getOriginalFilename();
String filepath = String.format("/test/%s", filename); String filepath = String.format("/test/%s", filename);
try (InputStream in = multipartFile.getInputStream()) { try (InputStream in = multipartFile.getInputStream()) {
cosManager.putObject( cosManager.putObjectStream(
filepath, filepath,
in, in,
multipartFile.getSize(), multipartFile.getSize(),
@ -75,7 +75,7 @@ public class FileController {
/** /**
* 测试文件下载 * 测试文件下载
* * 注意此方法仅供测试后端处理流再返回给前端
* @param filepath 文件路径 * @param filepath 文件路径
* @param response 响应对象 * @param response 响应对象
*/ */
@ -102,7 +102,6 @@ public class FileController {
cosObjectInput.close(); cosObjectInput.close();
} }
} }
} }
} }

View File

@ -0,0 +1,137 @@
package edu.whut.smilepicturebackend.controller;
import cn.hutool.json.JSONUtil;
import edu.whut.smilepicturebackend.annotation.AuthCheck;
import edu.whut.smilepicturebackend.common.BaseResponse;
import edu.whut.smilepicturebackend.common.DeleteRequest;
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.exception.ThrowUtils;
import edu.whut.smilepicturebackend.model.dto.picture.PictureUpdateRequest;
import edu.whut.smilepicturebackend.model.dto.picture.PictureUploadRequest;
import edu.whut.smilepicturebackend.model.entity.Picture;
import edu.whut.smilepicturebackend.model.entity.User;
import edu.whut.smilepicturebackend.model.vo.PictureVO;
import edu.whut.smilepicturebackend.service.PictureService;
import edu.whut.smilepicturebackend.service.UserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/picture")
public class PictureController {
private final UserService userService;
private final PictureService pictureService;
/**
* 上传图片可重新上传
*/
@PostMapping("/upload")
@AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
public BaseResponse<PictureVO> uploadPicture(
@RequestPart("file") MultipartFile multipartFile,
PictureUploadRequest pictureUploadRequest,
HttpServletRequest request) {
User loginUser = userService.getLoginUser(request);
PictureVO pictureVO = pictureService.uploadPicture(multipartFile, pictureUploadRequest, loginUser);
return ResultUtils.success(pictureVO);
}
/**
* 删除图片
* @param deleteRequest
* @param request
* @return
*/
@PostMapping("/delete")
public BaseResponse<Boolean> deletePicture(@RequestBody DeleteRequest deleteRequest,
HttpServletRequest request) {
if (deleteRequest == null || deleteRequest.getId() <= 0) {
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);
return ResultUtils.success(true);
}
/**
* 更新图片仅管理员可用
*
* @param pictureUpdateRequest
* @param request
* @return
*/
@PostMapping("/update")
@AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
public BaseResponse<Boolean> updatePicture(@RequestBody PictureUpdateRequest pictureUpdateRequest,
HttpServletRequest request) {
if (pictureUpdateRequest == null || pictureUpdateRequest.getId() <= 0) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
// 将实体类和 DTO 进行转换
Picture picture = new Picture();
BeanUtils.copyProperties(pictureUpdateRequest, picture);
// 注意将 list 转为 string
//todo:其他地方是否有问题
picture.setTags(JSONUtil.toJsonStr(pictureUpdateRequest.getTags()));
// 数据校验
pictureService.validPicture(picture);
// 判断是否存在
long id = pictureUpdateRequest.getId();
Picture oldPicture = pictureService.getById(id);
ThrowUtils.throwIf(oldPicture == null, ErrorCode.NOT_FOUND_ERROR);
// 操作数据库
boolean result = pictureService.updateById(picture);
ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
return ResultUtils.success(true);
}
/**
* 根据 id 获取图片仅管理员可用
*/
@GetMapping("/get")
@AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
public BaseResponse<Picture> getPictureById(long id, HttpServletRequest request) {
ThrowUtils.throwIf(id <= 0, ErrorCode.PARAMS_ERROR);
// 查询数据库
Picture picture = pictureService.getById(id);
ThrowUtils.throwIf(picture == null, ErrorCode.NOT_FOUND_ERROR);
// 获取封装类
return ResultUtils.success(picture);
}
/**
* 根据 id 获取图片封装类
*/
@GetMapping("/get/vo")
public BaseResponse<PictureVO> getPictureVOById(long id, HttpServletRequest request) {
ThrowUtils.throwIf(id <= 0, ErrorCode.PARAMS_ERROR);
// 查询数据库
Picture picture = pictureService.getById(id);
ThrowUtils.throwIf(picture == null, ErrorCode.NOT_FOUND_ERROR);
// 获取封装类
return ResultUtils.success(pictureService.getPictureVO(picture, request));
}
}

View File

@ -1,6 +1,4 @@
package edu.whut.smilepicturebackend.manager; package edu.whut.smilepicturebackend.manager;
import cn.hutool.core.io.FileUtil;
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;
@ -10,9 +8,11 @@ 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.util.ArrayList;
import java.util.List;
/**
* 和业务没有关系通用的文件上传下载
*/
@Component @Component
public class CosManager { public class CosManager {
@ -25,7 +25,7 @@ public class CosManager {
/** /**
* 上传对象 * 上传对象
* *
* @param key 唯一键 * @param key 唯一键 相当于桶内的文件路径
* @param file 文件 * @param file 文件
*/ */
public PutObjectResult putObject(String key, File file) { public PutObjectResult putObject(String key, File file) {
@ -34,32 +34,6 @@ public class CosManager {
return cosClient.putObject(putObjectRequest); 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);
}
/** /**
* 上传对象附带图片信息 * 上传对象附带图片信息
* *
@ -73,32 +47,87 @@ public class CosManager {
PicOperations picOperations = new PicOperations(); PicOperations picOperations = new PicOperations();
// 1 表示返回原图信息 // 1 表示返回原图信息
picOperations.setIsPicInfo(1); picOperations.setIsPicInfo(1);
// 图片处理规则列表
List<PicOperations.Rule> 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/<Width>x<Height>>如果大于原图宽高则不处理
thumbnailRule.setRule(String.format("imageMogr2/thumbnail/%sx%s>", 256, 256));
rules.add(thumbnailRule);
}
// 构造处理参数 // 构造处理参数
picOperations.setRules(rules);
putObjectRequest.setPicOperations(picOperations); putObjectRequest.setPicOperations(picOperations);
return cosClient.putObject(putObjectRequest); return cosClient.putObject(putObjectRequest);
} }
/**
* 上传对象接收InputStream文件流
* @param key
* @param in
* @param contentLength
* @param contentType
*/
public PutObjectResult putObjectStream(String key,
InputStream in,
long contentLength,
String contentType) {
// 1. 构建 metadata
ObjectMetadata meta = new ObjectMetadata();
meta.setContentLength(contentLength);
meta.setContentType(contentType);
// 如果有额外的 metadata比如缓存控制Content-Disposition 也一并在这里设置
// 2. 构造 PutObjectRequest 支持 InputStream + metadata
PutObjectRequest request = new PutObjectRequest(
cosClientConfig.getBucket(),
key,
in,
meta
);
// 3. 调用同一个接口上传
return cosClient.putObject(request);
}
/**
* 上传对象接收InputStream文件流,附带图片信息
* @param key
* @param in
* @param contentLength
* @param contentType
*/
public PutObjectResult putPictureObjectStream(
String key,
InputStream in,
long contentLength,
String contentType) {
// 1. 构建基本的元数据
ObjectMetadata meta = new ObjectMetadata();
meta.setContentLength(contentLength);
meta.setContentType(contentType);
// 2. 构造带流的上传请求
// 这里使用了流式的 PutObjectRequest 构造器
PutObjectRequest request = new PutObjectRequest(
cosClientConfig.getBucket(),
key,
in,
meta
);
// 3. 设置图片处理操作返回原图信息
PicOperations picOps = new PicOperations();
picOps.setIsPicInfo(1);
request.setPicOperations(picOps);
// 4. 执行上传
return cosClient.putObject(request);
}
/**
* 下载对象
*
* @param key 唯一键
*/
public COSObject getObject(String key) {
GetObjectRequest getObjectRequest = new GetObjectRequest(cosClientConfig.getBucket(), key);
return cosClient.getObject(getObjectRequest);
}
/** /**
* 删除对象 * 删除对象
* *

View File

@ -0,0 +1,148 @@
package edu.whut.smilepicturebackend.manager;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.RandomUtil;
import com.qcloud.cos.model.PutObjectResult;
import com.qcloud.cos.model.ciModel.persistence.ImageInfo;
import edu.whut.smilepicturebackend.config.CosClientConfig;
import edu.whut.smilepicturebackend.exception.BusinessException;
import edu.whut.smilepicturebackend.exception.ErrorCode;
import edu.whut.smilepicturebackend.exception.ThrowUtils;
import edu.whut.smilepicturebackend.model.file.UploadPictureResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
/**
* 文件服务
* @deprecated 已废弃改为使用 upload 包的模板方法优化
*/
@Slf4j
@Service
@Deprecated
public class FileManager {
@Resource
private CosClientConfig cosClientConfig;
@Resource
private CosManager cosManager;
/**
* 上传图片
*
* @param multipartFile 文件
* @param uploadPathPrefix 上传路径前缀
* @return
*/
public UploadPictureResult uploadPicture(MultipartFile multipartFile, String uploadPathPrefix) {
// 1. 校验图片
validPicture(multipartFile);
// 2. 生成存储路径日期_随机串.后缀避免目录穿越防止用户上传的文件名中存在恶意字符造成上传失败或对本地文件产生影响
String originalFilename = multipartFile.getOriginalFilename();
String suffix = FileUtil.getSuffix(originalFilename);
String uuid = RandomUtil.randomString(16);
String date = DateUtil.formatDate(new Date()); // 默认 yyyy-MM-dd
String uploadFilename = String.format("%s_%s.%s", date, uuid, suffix);
//如果多个项目共享存储桶请在桶的根目录下以各项目名作为目录
String projectName="smile-picture";
String uploadPath = String.format("/%s/%s/%s", projectName,uploadPathPrefix, uploadFilename);
try (InputStream in = multipartFile.getInputStream()) {
// 3. 调用流式上传并触发 CI 获取原图信息
PutObjectResult putResult = cosManager.putPictureObjectStream(
uploadPath,
in,
multipartFile.getSize(),
multipartFile.getContentType()
);
// 4. 解析图片信息
ImageInfo imageInfo = putResult
.getCiUploadResult()
.getOriginalInfo()
.getImageInfo();
int picWidth = imageInfo.getWidth();
int picHeight = imageInfo.getHeight();
double picScale = NumberUtil.round((double)picWidth / picHeight, 2).doubleValue();
// 5. 封装返回对象
UploadPictureResult result = new UploadPictureResult();
result.setUrl(cosClientConfig.getHost() + "/" + uploadPath);
result.setName(FileUtil.mainName(originalFilename));
result.setPicSize(multipartFile.getSize());
result.setPicWidth(picWidth);
result.setPicHeight(picHeight);
result.setPicScale(picScale);
result.setPicFormat(imageInfo.getFormat());
return result;
} catch (IOException e) {
log.error("读取 MultipartFile 流失败uploadPath = {}", uploadPath, e);
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "上传失败");
} catch (Exception e) {
log.error("图片流式上传到对象存储失败uploadPath = {}", uploadPath, e);
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "上传失败");
}
}
/**
* 校验文件
*
* @param multipartFile
*/
private void validPicture(MultipartFile multipartFile) {
ThrowUtils.throwIf(multipartFile == null, ErrorCode.PARAMS_ERROR, "文件不能为空");
// 1. 校验文件大小
long fileSize = multipartFile.getSize();
final long ONE_M = 1024 * 1024;
ThrowUtils.throwIf(fileSize > 2 * ONE_M, ErrorCode.PARAMS_ERROR, "文件大小不能超过 2MB");
// 2. 校验文件后缀
String fileSuffix = FileUtil.getSuffix(multipartFile.getOriginalFilename());
// 允许上传的文件后缀列表或者集合
final List<String> ALLOW_FORMAT_LIST = Arrays.asList("jpeg", "png", "jpg", "webp");
ThrowUtils.throwIf(!ALLOW_FORMAT_LIST.contains(fileSuffix), ErrorCode.PARAMS_ERROR, "文件类型错误");
}
/**
* 清理临时文件
*
* @param file
*/
public void deleteTempFile(File file) {
if (file == null) {
return;
}
// 删除临时文件
boolean deleteResult = file.delete();
if (!deleteResult) {
log.error("file delete error, filepath = {}", file.getAbsolutePath());
}
}
}

View File

@ -0,0 +1,18 @@
package edu.whut.smilepicturebackend.mapper;
import edu.whut.smilepicturebackend.model.entity.Picture;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
/**
* @author 张三
* @description 针对表picture(图片)的数据库操作Mapper
* @createDate 2025-06-11 11:23:11
* @Entity edu.whut.smilepicturebackend.model.entity.Picture
*/
public interface PictureMapper extends BaseMapper<Picture> {
}

View File

@ -0,0 +1,40 @@
package edu.whut.smilepicturebackend.model.dto.picture;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 图片编辑请求
*/
@Data
public class PictureEditRequest implements Serializable {
/**
* id
*/
private Long id;
/**
* 图片名称
*/
private String name;
/**
* 简介
*/
private String introduction;
/**
* 分类
*/
private String category;
/**
* 标签
*/
private List<String> tags;
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,119 @@
package edu.whut.smilepicturebackend.model.dto.picture;
import edu.whut.smilepicturebackend.common.PageRequest;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
/**
* 图片查询请求
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class PictureQueryRequest extends PageRequest implements Serializable {
/**
* id
*/
private Long id;
/**
* 图片名称
*/
private String name;
/**
* 简介
*/
private String introduction;
/**
* 分类
*/
private String category;
/**
* 标签
*/
private List<String> tags;
/**
* 文件体积
*/
private Long picSize;
/**
* 图片宽度
*/
private Integer picWidth;
/**
* 图片高度
*/
private Integer picHeight;
/**
* 图片比例
*/
private Double picScale;
/**
* 图片格式
*/
private String picFormat;
/**
* 搜索词同时搜名称简介等
*/
private String searchText;
/**
* 用户 id
*/
private Long userId;
/**
* 审核状态0-待审核; 1-通过; 2-拒绝
*/
private Integer reviewStatus;
/**
* 审核信息
*/
private String reviewMessage;
/**
* 审核人 ID
*/
private Long reviewerId;
/**
* 审核时间
*/
private Date reviewTime;
/**
* 空间 id
*/
private Long spaceId;
/**
* 是否只查询 spaceId null 的数据
*/
private boolean nullSpaceId;
/*
* 开始编辑时间
*/
private Date startEditTime;
/*
* 结束编辑时间
*/
private Date endEditTime;
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,40 @@
package edu.whut.smilepicturebackend.model.dto.picture;
import lombok.Data;
import java.io.Serializable;
import java.util.List;
/**
* 图片更新请求
*/
@Data
public class PictureUpdateRequest implements Serializable {
/**
* id
*/
private Long id;
/**
* 图片名称
*/
private String name;
/**
* 简介
*/
private String introduction;
/**
* 分类
*/
private String category;
/**
* 标签
*/
private List<String> tags;
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,21 @@
package edu.whut.smilepicturebackend.model.dto.picture;
import lombok.Data;
import java.io.Serializable;
/**
* 图片上传请求
*
* @author 程序员鱼皮 <a href="https://www.codefather.cn">编程导航原创项目</a>
*/
@Data
public class PictureUploadRequest implements Serializable {
/**
* 图片 id用于修改
*/
private Long id;
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,100 @@
package edu.whut.smilepicturebackend.model.entity;
import com.baomidou.mybatisplus.annotation.*;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
/**
* 图片
* @TableName picture
*/
@TableName(value ="picture")
@Data
public class Picture implements Serializable {
/**
* id
*/
@TableId(type = IdType.ASSIGN_ID)
private Long id;
/**
* 图片 url
*/
private String url;
/**
* 图片名称
*/
private String name;
/**
* 简介
*/
private String introduction;
/**
* 分类
*/
private String category;
/**
* 标签JSON 数组
*/
private String tags;
/**
* 图片体积
*/
private Long picSize;
/**
* 图片宽度
*/
private Integer picWidth;
/**
* 图片高度
*/
private Integer picHeight;
/**
* 图片宽高比例
*/
private Double picScale;
/**
* 图片格式
*/
private String picFormat;
/**
* 创建用户 id
*/
private Long userId;
/**
* 创建时间
*/
private Date createTime;
/**
* 编辑时间
*/
private Date editTime;
/**
* 更新时间
*/
private Date updateTime;
/**
* 是否删除
*/
@TableLogic
private Integer isDelete;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,55 @@
package edu.whut.smilepicturebackend.model.file;
import lombok.Data;
/**
* 上传图片的结果
*/
@Data
public class UploadPictureResult {
/**
* 图片地址
*/
private String url;
/**
* 缩略图 url
*/
private String thumbnailUrl;
/**
* 图片名称
*/
private String name;
/**
* 文件体积
*/
private Long picSize;
/**
* 图片宽度
*/
private int picWidth;
/**
* 图片高度
*/
private int picHeight;
/**
* 图片宽高比
*/
private Double picScale;
/**
* 图片格式
*/
private String picFormat;
/**
* 图片主色调
*/
private String picColor;
}

View File

@ -0,0 +1,133 @@
package edu.whut.smilepicturebackend.model.vo;
import cn.hutool.json.JSONUtil;
import edu.whut.smilepicturebackend.model.entity.Picture;
import lombok.Data;
import org.springframework.beans.BeanUtils;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@Data
public class PictureVO implements Serializable {
/**
* id
*/
private Long id;
/**
* 图片 url
*/
private String url;
/**
* 图片名称
*/
private String name;
/**
* 简介
*/
private String introduction;
/**
* 标签
*/
private List<String> tags;
/**
* 分类
*/
private String category;
/**
* 文件体积
*/
private Long picSize;
/**
* 图片宽度
*/
private Integer picWidth;
/**
* 图片高度
*/
private Integer picHeight;
/**
* 图片比例
*/
private Double picScale;
/**
* 图片格式
*/
private String picFormat;
/**
* 用户 id
*/
private Long userId;
/**
* 创建时间
*/
private Date createTime;
/**
* 编辑时间
*/
private Date editTime;
/**
* 更新时间
*/
private Date updateTime;
/**
* 创建用户信息
*/
private UserVO user;
/**
* 权限列表
*/
private List<String> permissionList = new ArrayList<>();
private static final long serialVersionUID = 1L;
/**
* 封装类转对象
*/
public static Picture voToObj(PictureVO pictureVO) {
if (pictureVO == null) {
return null;
}
Picture picture = new Picture();
BeanUtils.copyProperties(pictureVO, picture);
// 类型不同需要转换
picture.setTags(JSONUtil.toJsonStr(pictureVO.getTags()));
return picture;
}
/**
* 对象转封装类
*/
public static PictureVO objToVo(Picture picture) {
if (picture == null) {
return null;
}
PictureVO pictureVO = new PictureVO();
BeanUtils.copyProperties(picture, pictureVO);
// 类型不同需要转换
pictureVO.setTags(JSONUtil.toList(picture.getTags(), String.class));
return pictureVO;
}
}

View File

@ -0,0 +1,64 @@
package edu.whut.smilepicturebackend.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import edu.whut.smilepicturebackend.model.dto.picture.PictureQueryRequest;
import edu.whut.smilepicturebackend.model.dto.picture.PictureUploadRequest;
import edu.whut.smilepicturebackend.model.entity.Picture;
import edu.whut.smilepicturebackend.model.entity.User;
import edu.whut.smilepicturebackend.model.vo.PictureVO;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
/**
* @author 张三
* @description 针对表picture(图片)的数据库操作Service
* @createDate 2025-06-11 11:23:11
*/
public interface PictureService extends IService<Picture> {
/**
* 校验图片
*
* @param picture
*/
void validPicture(Picture picture);
/**
* 上传图片
*
* @param multipartFile 文件输入源
* @param pictureUploadRequest
* @param loginUser
* @return
*/
PictureVO uploadPicture(MultipartFile multipartFile, PictureUploadRequest pictureUploadRequest, User loginUser);
/**
* 获取查询对象
*
* @param pictureQueryRequest
* @return
*/
LambdaQueryWrapper<Picture> getQueryWrapper(PictureQueryRequest pictureQueryRequest);
/**
* 获取图片包装类单条
*
* @param picture
* @param request
* @return
*/
PictureVO getPictureVO(Picture picture, HttpServletRequest request);
/**
* 获取图片包装类分页
*
* @param picturePage
* @param request
* @return
*/
Page<PictureVO> getPictureVOPage(Page<Picture> picturePage, HttpServletRequest request);
}

View File

@ -1,5 +1,6 @@
package edu.whut.smilepicturebackend.service; package edu.whut.smilepicturebackend.service;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import edu.whut.smilepicturebackend.model.dto.user.UserAddRequest; import edu.whut.smilepicturebackend.model.dto.user.UserAddRequest;
@ -74,11 +75,36 @@ public interface UserService extends IService<User> {
*/ */
boolean userLogout(HttpServletRequest request); boolean userLogout(HttpServletRequest request);
/**
* 获得脱敏后的用户信息
*
* @param user
* @return
*/
UserVO getUserVO(User user); UserVO getUserVO(User user);
/**
* 获得脱敏后的用户信息列表
*
* @param userList
* @return 脱敏后的用户列表
*/
List<UserVO> getUserVOList(List<User> userList); List<UserVO> getUserVOList(List<User> userList);
QueryWrapper<User> getQueryWrapper(UserQueryRequest userQueryRequest); /**
* 获取查询条件
* @param userQueryRequest
* @return
*/
LambdaQueryWrapper<User> getQueryWrapper(UserQueryRequest userQueryRequest);
/**
* 是否为管理员
*
* @param user
* @return
*/
boolean isAdmin(User user);
long createUser(UserAddRequest userAddRequest); long createUser(UserAddRequest userAddRequest);
} }

View File

@ -0,0 +1,198 @@
package edu.whut.smilepicturebackend.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import edu.whut.smilepicturebackend.exception.ErrorCode;
import edu.whut.smilepicturebackend.exception.ThrowUtils;
import edu.whut.smilepicturebackend.manager.FileManager;
import edu.whut.smilepicturebackend.mapper.PictureMapper;
import edu.whut.smilepicturebackend.model.dto.picture.PictureQueryRequest;
import edu.whut.smilepicturebackend.model.dto.picture.PictureUploadRequest;
import edu.whut.smilepicturebackend.model.entity.Picture;
import edu.whut.smilepicturebackend.model.entity.User;
import edu.whut.smilepicturebackend.model.file.UploadPictureResult;
import edu.whut.smilepicturebackend.model.vo.PictureVO;
import edu.whut.smilepicturebackend.model.vo.UserVO;
import edu.whut.smilepicturebackend.service.PictureService;
import edu.whut.smilepicturebackend.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @author 张三
* @description 针对表picture(图片)的数据库操作Service实现
* @createDate 2025-06-11 11:23:11
*/
@Service
@RequiredArgsConstructor
public class PictureServiceImpl extends ServiceImpl<PictureMapper, Picture>
implements PictureService {
private final FileManager fileManager;
private final UserService userService;
@Override
public void validPicture(Picture picture) {
ThrowUtils.throwIf(picture == null, ErrorCode.PARAMS_ERROR);
// 从对象中取值
Long id = picture.getId();
String url = picture.getUrl();
String introduction = picture.getIntroduction();
// 修改数据时id 不能为空有参数则校验
ThrowUtils.throwIf(ObjUtil.isNull(id), ErrorCode.PARAMS_ERROR, "id 不能为空");
// 如果传递了 url才校验
if (StrUtil.isNotBlank(url)) {
ThrowUtils.throwIf(url.length() > 1024, ErrorCode.PARAMS_ERROR, "url 过长");
}
if (StrUtil.isNotBlank(introduction)) {
ThrowUtils.throwIf(introduction.length() > 800, ErrorCode.PARAMS_ERROR, "简介过长");
}
}
@Override
public PictureVO uploadPicture(MultipartFile multipartFile, PictureUploadRequest pictureUploadRequest, User loginUser) {
// 校验参数
ThrowUtils.throwIf(loginUser == null, ErrorCode.NO_AUTH_ERROR);
// 判断是新增还是删除
Long pictureId = null;
if (pictureUploadRequest != null) {
pictureId = pictureUploadRequest.getId();
}
// 如果是更新判断图片是否存在
if (pictureId != null) {
boolean exists=this.lambdaQuery()
.eq(Picture::getId,pictureId)
.exists();
ThrowUtils.throwIf(!exists, ErrorCode.NOT_FOUND_ERROR, "图片不存在");
}
// 上传图片得到图片信息
String uploadPathPrefix;
//公共图库下每个用户有自己的userid管理的文件夹
uploadPathPrefix = String.format("public/%s", loginUser.getId());
UploadPictureResult uploadPictureResult = fileManager.uploadPicture(multipartFile, uploadPathPrefix);
// 构造要入库的图片信息
Picture picture = new Picture();
// 复制同名属性urlnamepicSizepicWidthpicHeightpicScalepicFormat
BeanUtils.copyProperties(uploadPictureResult, picture);
picture.setUserId(loginUser.getId());
// 操作数据库
// 如果 pictureId 不为空表示更新否则是新增
if (pictureId != null) {
// 如果是更新需要补充 id 和编辑时间
picture.setId(pictureId);
picture.setEditTime(new Date());
}
boolean result = this.saveOrUpdate(picture);
ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR, "图片上传失败,数据库操作失败");
return PictureVO.objToVo(picture);
}
@Override
public LambdaQueryWrapper<Picture> getQueryWrapper(PictureQueryRequest req) {
LambdaQueryWrapper<Picture> qw = Wrappers.lambdaQuery(Picture.class);
if (req == null) {
return qw;
}
// 精简版条件构造
qw.eq(ObjUtil.isNotEmpty(req.getId()), Picture::getId, req.getId())
.eq(ObjUtil.isNotEmpty(req.getUserId()), Picture::getUserId, req.getUserId())
.like(StrUtil.isNotBlank(req.getName()), Picture::getName, req.getName())
.like(StrUtil.isNotBlank(req.getIntroduction()), Picture::getIntroduction, req.getIntroduction())
.like(StrUtil.isNotBlank(req.getPicFormat()), Picture::getPicFormat, req.getPicFormat())
.eq(StrUtil.isNotBlank(req.getCategory()), Picture::getCategory, req.getCategory())
.eq(ObjUtil.isNotEmpty(req.getPicWidth()), Picture::getPicWidth, req.getPicWidth())
.eq(ObjUtil.isNotEmpty(req.getPicHeight()), Picture::getPicHeight, req.getPicHeight())
.eq(ObjUtil.isNotEmpty(req.getPicSize()), Picture::getPicSize, req.getPicSize())
.eq(ObjUtil.isNotEmpty(req.getPicScale()), Picture::getPicScale, req.getPicScale())
.ge(ObjUtil.isNotEmpty(req.getStartEditTime()), Picture::getEditTime, req.getStartEditTime())
.lt(ObjUtil.isNotEmpty(req.getEndEditTime()), Picture::getEditTime, req.getEndEditTime());
// 全字段模糊搜索
if (StrUtil.isNotBlank(req.getSearchText())) {
qw.and(w -> w
.like(Picture::getName, req.getSearchText())
.or()
.like(Picture::getIntroduction, req.getSearchText())
);
}
// JSON 数组 tags 查询
if (CollUtil.isNotEmpty(req.getTags())) {
req.getTags().forEach(tag ->
qw.like(Picture::getTags, "\"" + tag + "\"")
);
}
// 动态排序转下划线字段名并手工拼接
if (StrUtil.isNotBlank(req.getSortField())) {
String column = StrUtil.toUnderlineCase(req.getSortField());
String direction = "ascend".equalsIgnoreCase(req.getSortOrder()) ? "ASC" : "DESC";
qw.last("ORDER BY " + column + " " + direction);
}
return qw;
}
@Override
public PictureVO getPictureVO(Picture picture, HttpServletRequest request) {
// 对象转封装类
PictureVO pictureVO = PictureVO.objToVo(picture);
// 关联查询用户信息
Long userId = picture.getUserId();
if (userId != null && userId > 0) {
User user = userService.getById(userId);
UserVO userVO = userService.getUserVO(user);
pictureVO.setUser(userVO);
}
return pictureVO;
}
@Override
public Page<PictureVO> getPictureVOPage(Page<Picture> picturePage, HttpServletRequest request) {
List<Picture> pictureList = picturePage.getRecords();
Page<PictureVO> pictureVOPage = new Page<>(picturePage.getCurrent(), picturePage.getSize(), picturePage.getTotal());
if (CollUtil.isEmpty(pictureList)) {
return pictureVOPage;
}
// 对象列表 => 封装对象列表
List<PictureVO> pictureVOList = pictureList.stream()
.map(PictureVO::objToVo)
.collect(Collectors.toList());
// 1. 关联查询用户信息
// 1,2,3,4
Set<Long> userIdSet = pictureList.stream().map(Picture::getUserId).collect(Collectors.toSet());
// 1 => user1, 2 => user2
Map<Long, List<User>> userIdUserListMap = userService.listByIds(userIdSet).stream()
.collect(Collectors.groupingBy(User::getId));
// 2. 填充信息
pictureVOList.forEach(pictureVO -> {
Long userId = pictureVO.getUserId();
User user = null;
if (userIdUserListMap.containsKey(userId)) {
user = userIdUserListMap.get(userId).get(0);
}
pictureVO.setUser(userService.getUserVO(user));
});
pictureVOPage.setRecords(pictureVOList);
return pictureVOPage;
}
}

View File

@ -179,6 +179,11 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User>
.collect(Collectors.toList()); .collect(Collectors.toList());
} }
/**
* 获取当前登录用户
* @param request
* @return
*/
@Override @Override
public User getLoginUser(HttpServletRequest request) { public User getLoginUser(HttpServletRequest request) {
// 判断是否已经登录 // 判断是否已经登录
@ -187,12 +192,12 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User>
if (currentUser == null || currentUser.getId() == null) { if (currentUser == null || currentUser.getId() == null) {
throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR); throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
} }
// 从数据库中查询追求性能的话可以注释直接返回上述结果 // // 从数据库中查询追求性能的话可以注释直接返回上述结果
Long userId = currentUser.getId(); // Long userId = currentUser.getId();
currentUser = this.getById(userId); // currentUser = this.getById(userId);
if (currentUser == null) { // if (currentUser == null) {
throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR); // throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
} // }
return currentUser; return currentUser;
} }
@ -216,29 +221,37 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User>
/** /**
* 查询条件 * 查询条件
* @param userQueryRequest * @param req
* @return * @return
*/ */
@Override @Override
public QueryWrapper<User> getQueryWrapper(UserQueryRequest userQueryRequest) { public LambdaQueryWrapper<User> getQueryWrapper(UserQueryRequest req) {
if (userQueryRequest == null) { if (req == null) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数为空"); throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数为空");
} }
Long id = userQueryRequest.getId();
String userName = userQueryRequest.getUserName(); LambdaQueryWrapper<User> qw = Wrappers.lambdaQuery(User.class);
String userAccount = userQueryRequest.getUserAccount();
String userProfile = userQueryRequest.getUserProfile(); // 基本等值和模糊匹配
String userRole = userQueryRequest.getUserRole(); qw.eq(ObjUtil.isNotNull(req.getId()), User::getId, req.getId())
String sortField = userQueryRequest.getSortField(); .eq(StrUtil.isNotBlank(req.getUserRole()), User::getUserRole, req.getUserRole())
String sortOrder = userQueryRequest.getSortOrder(); .like(StrUtil.isNotBlank(req.getUserAccount()), User::getUserAccount, req.getUserAccount())
QueryWrapper<User> queryWrapper = new QueryWrapper<>(); .like(StrUtil.isNotBlank(req.getUserName()), User::getUserName, req.getUserName())
queryWrapper.eq(ObjUtil.isNotNull(id), "id", id); .like(StrUtil.isNotBlank(req.getUserProfile()), User::getUserProfile, req.getUserProfile());
queryWrapper.eq(StrUtil.isNotBlank(userRole), "userRole", userRole);
queryWrapper.like(StrUtil.isNotBlank(userAccount), "userAccount", userAccount); // 动态排序
queryWrapper.like(StrUtil.isNotBlank(userName), "userName", userName); if (StrUtil.isNotBlank(req.getSortField())) {
queryWrapper.like(StrUtil.isNotBlank(userProfile), "userProfile", userProfile); String column = StrUtil.toUnderlineCase(req.getSortField());
queryWrapper.orderBy(StrUtil.isNotEmpty(sortField), sortOrder.equals("ascend"), sortField); boolean asc = "ascend".equalsIgnoreCase(req.getSortOrder());
return queryWrapper; qw.last("ORDER BY " + column + (asc ? " ASC" : " DESC"));
}
return qw;
}
@Override
public boolean isAdmin(User user) {
return user != null && UserRoleEnum.ADMIN.getValue().equals(user.getUserRole());
} }
/** /**

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="edu.whut.smilepicturebackend.mapper.PictureMapper">
<resultMap id="BaseResultMap" type="edu.whut.smilepicturebackend.model.entity.Picture">
<id property="id" column="id" jdbcType="BIGINT"/>
<result property="url" column="url" jdbcType="VARCHAR"/>
<result property="name" column="name" jdbcType="VARCHAR"/>
<result property="introduction" column="introduction" jdbcType="VARCHAR"/>
<result property="category" column="category" jdbcType="VARCHAR"/>
<result property="tags" column="tags" jdbcType="VARCHAR"/>
<result property="picSize" column="pic_size" jdbcType="BIGINT"/>
<result property="picWidth" column="pic_width" jdbcType="INTEGER"/>
<result property="picHeight" column="pic_height" jdbcType="INTEGER"/>
<result property="picScale" column="pic_scale" jdbcType="DOUBLE"/>
<result property="picFormat" column="pic_format" jdbcType="VARCHAR"/>
<result property="userId" column="user_id" jdbcType="BIGINT"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
<result property="editTime" column="edit_time" jdbcType="TIMESTAMP"/>
<result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
<result property="isDelete" column="is_delete" jdbcType="TINYINT"/>
</resultMap>
<sql id="Base_Column_List">
id,url,name,
introduction,category,tags,
pic_size,pic_width,pic_height,
pic_scale,pic_format,user_id,
create_time,edit_time,update_time,
is_delete
</sql>
</mapper>