diff --git a/src/main/java/edu/whut/smilepicturebackend/controller/FileController.java b/src/main/java/edu/whut/smilepicturebackend/controller/FileController.java index 01f69ae..c094fd5 100644 --- a/src/main/java/edu/whut/smilepicturebackend/controller/FileController.java +++ b/src/main/java/edu/whut/smilepicturebackend/controller/FileController.java @@ -40,7 +40,7 @@ public class FileController { String filename = multipartFile.getOriginalFilename(); String filepath = String.format("/test/%s", filename); try (InputStream in = multipartFile.getInputStream()) { - cosManager.putObject( + cosManager.putObjectStream( filepath, in, multipartFile.getSize(), @@ -75,7 +75,7 @@ public class FileController { /** * 测试文件下载 - * + * 注意,此方法仅供测试,后端处理流再返回给前端 * @param filepath 文件路径 * @param response 响应对象 */ @@ -102,7 +102,6 @@ public class FileController { cosObjectInput.close(); } } - } } diff --git a/src/main/java/edu/whut/smilepicturebackend/controller/PictureController.java b/src/main/java/edu/whut/smilepicturebackend/controller/PictureController.java new file mode 100644 index 0000000..1288434 --- /dev/null +++ b/src/main/java/edu/whut/smilepicturebackend/controller/PictureController.java @@ -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 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 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 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 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 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)); + } +} diff --git a/src/main/java/edu/whut/smilepicturebackend/manager/CosManager.java b/src/main/java/edu/whut/smilepicturebackend/manager/CosManager.java index 7295b69..0a04183 100644 --- a/src/main/java/edu/whut/smilepicturebackend/manager/CosManager.java +++ b/src/main/java/edu/whut/smilepicturebackend/manager/CosManager.java @@ -1,6 +1,4 @@ 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; @@ -10,9 +8,11 @@ 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 { @@ -25,7 +25,7 @@ public class CosManager { /** * 上传对象 * - * @param key 唯一键 + * @param key 唯一键 ,相当于桶内的「文件路径」! * @param file 文件 */ public PutObjectResult putObject(String key, File file) { @@ -34,32 +34,6 @@ public class CosManager { 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(); // 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); } + + /** + * 上传对象,接收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); + } + + /** * 删除对象 * diff --git a/src/main/java/edu/whut/smilepicturebackend/manager/FileManager.java b/src/main/java/edu/whut/smilepicturebackend/manager/FileManager.java new file mode 100644 index 0000000..02c1645 --- /dev/null +++ b/src/main/java/edu/whut/smilepicturebackend/manager/FileManager.java @@ -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 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()); + } + } +} + + + + + + + + + + + + + diff --git a/src/main/java/edu/whut/smilepicturebackend/mapper/PictureMapper.java b/src/main/java/edu/whut/smilepicturebackend/mapper/PictureMapper.java new file mode 100644 index 0000000..7a54d05 --- /dev/null +++ b/src/main/java/edu/whut/smilepicturebackend/mapper/PictureMapper.java @@ -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 { + +} + + + + diff --git a/src/main/java/edu/whut/smilepicturebackend/model/dto/picture/PictureEditRequest.java b/src/main/java/edu/whut/smilepicturebackend/model/dto/picture/PictureEditRequest.java new file mode 100644 index 0000000..cff8264 --- /dev/null +++ b/src/main/java/edu/whut/smilepicturebackend/model/dto/picture/PictureEditRequest.java @@ -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 tags; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/edu/whut/smilepicturebackend/model/dto/picture/PictureQueryRequest.java b/src/main/java/edu/whut/smilepicturebackend/model/dto/picture/PictureQueryRequest.java new file mode 100644 index 0000000..57c62b9 --- /dev/null +++ b/src/main/java/edu/whut/smilepicturebackend/model/dto/picture/PictureQueryRequest.java @@ -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 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; +} \ No newline at end of file diff --git a/src/main/java/edu/whut/smilepicturebackend/model/dto/picture/PictureUpdateRequest.java b/src/main/java/edu/whut/smilepicturebackend/model/dto/picture/PictureUpdateRequest.java new file mode 100644 index 0000000..90f253f --- /dev/null +++ b/src/main/java/edu/whut/smilepicturebackend/model/dto/picture/PictureUpdateRequest.java @@ -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 tags; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/edu/whut/smilepicturebackend/model/dto/picture/PictureUploadRequest.java b/src/main/java/edu/whut/smilepicturebackend/model/dto/picture/PictureUploadRequest.java new file mode 100644 index 0000000..8ea657f --- /dev/null +++ b/src/main/java/edu/whut/smilepicturebackend/model/dto/picture/PictureUploadRequest.java @@ -0,0 +1,21 @@ +package edu.whut.smilepicturebackend.model.dto.picture; + +import lombok.Data; + +import java.io.Serializable; + +/** + * 图片上传请求 + * + * @author 程序员鱼皮 编程导航原创项目 + */ +@Data +public class PictureUploadRequest implements Serializable { + + /** + * 图片 id(用于修改) + */ + private Long id; + + private static final long serialVersionUID = 1L; +} \ No newline at end of file diff --git a/src/main/java/edu/whut/smilepicturebackend/model/entity/Picture.java b/src/main/java/edu/whut/smilepicturebackend/model/entity/Picture.java new file mode 100644 index 0000000..c2a1df0 --- /dev/null +++ b/src/main/java/edu/whut/smilepicturebackend/model/entity/Picture.java @@ -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; +} \ No newline at end of file diff --git a/src/main/java/edu/whut/smilepicturebackend/model/file/UploadPictureResult.java b/src/main/java/edu/whut/smilepicturebackend/model/file/UploadPictureResult.java new file mode 100644 index 0000000..ceff098 --- /dev/null +++ b/src/main/java/edu/whut/smilepicturebackend/model/file/UploadPictureResult.java @@ -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; +} \ No newline at end of file diff --git a/src/main/java/edu/whut/smilepicturebackend/model/vo/PictureVO.java b/src/main/java/edu/whut/smilepicturebackend/model/vo/PictureVO.java new file mode 100644 index 0000000..a125072 --- /dev/null +++ b/src/main/java/edu/whut/smilepicturebackend/model/vo/PictureVO.java @@ -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 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 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; + } +} \ No newline at end of file diff --git a/src/main/java/edu/whut/smilepicturebackend/service/PictureService.java b/src/main/java/edu/whut/smilepicturebackend/service/PictureService.java new file mode 100644 index 0000000..ee785eb --- /dev/null +++ b/src/main/java/edu/whut/smilepicturebackend/service/PictureService.java @@ -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 { + /** + * 校验图片 + * + * @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 getQueryWrapper(PictureQueryRequest pictureQueryRequest); + + /** + * 获取图片包装类(单条) + * + * @param picture + * @param request + * @return + */ + PictureVO getPictureVO(Picture picture, HttpServletRequest request); + + /** + * 获取图片包装类(分页) + * + * @param picturePage + * @param request + * @return + */ + Page getPictureVOPage(Page picturePage, HttpServletRequest request); +} diff --git a/src/main/java/edu/whut/smilepicturebackend/service/UserService.java b/src/main/java/edu/whut/smilepicturebackend/service/UserService.java index 2c4b8d1..be974bb 100644 --- a/src/main/java/edu/whut/smilepicturebackend/service/UserService.java +++ b/src/main/java/edu/whut/smilepicturebackend/service/UserService.java @@ -1,5 +1,6 @@ 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.service.IService; import edu.whut.smilepicturebackend.model.dto.user.UserAddRequest; @@ -74,11 +75,36 @@ public interface UserService extends IService { */ boolean userLogout(HttpServletRequest request); + /** + * 获得脱敏后的用户信息 + * + * @param user + * @return + */ UserVO getUserVO(User user); + /** + * 获得脱敏后的用户信息列表 + * + * @param userList + * @return 脱敏后的用户列表 + */ List getUserVOList(List userList); - QueryWrapper getQueryWrapper(UserQueryRequest userQueryRequest); + /** + * 获取查询条件 + * @param userQueryRequest + * @return + */ + LambdaQueryWrapper getQueryWrapper(UserQueryRequest userQueryRequest); + + /** + * 是否为管理员 + * + * @param user + * @return + */ + boolean isAdmin(User user); long createUser(UserAddRequest userAddRequest); } diff --git a/src/main/java/edu/whut/smilepicturebackend/service/impl/PictureServiceImpl.java b/src/main/java/edu/whut/smilepicturebackend/service/impl/PictureServiceImpl.java new file mode 100644 index 0000000..2b8c082 --- /dev/null +++ b/src/main/java/edu/whut/smilepicturebackend/service/impl/PictureServiceImpl.java @@ -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 + 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(); + // 复制同名属性(url、name、picSize、picWidth、picHeight、picScale、picFormat) + 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 getQueryWrapper(PictureQueryRequest req) { + LambdaQueryWrapper 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 getPictureVOPage(Page picturePage, HttpServletRequest request) { + List pictureList = picturePage.getRecords(); + Page pictureVOPage = new Page<>(picturePage.getCurrent(), picturePage.getSize(), picturePage.getTotal()); + if (CollUtil.isEmpty(pictureList)) { + return pictureVOPage; + } + // 对象列表 => 封装对象列表 + List pictureVOList = pictureList.stream() + .map(PictureVO::objToVo) + .collect(Collectors.toList()); + // 1. 关联查询用户信息 + // 1,2,3,4 + Set userIdSet = pictureList.stream().map(Picture::getUserId).collect(Collectors.toSet()); + // 1 => user1, 2 => user2 + Map> 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; + } +} + + + + diff --git a/src/main/java/edu/whut/smilepicturebackend/service/impl/UserServiceImpl.java b/src/main/java/edu/whut/smilepicturebackend/service/impl/UserServiceImpl.java index 9b28d03..e43be50 100644 --- a/src/main/java/edu/whut/smilepicturebackend/service/impl/UserServiceImpl.java +++ b/src/main/java/edu/whut/smilepicturebackend/service/impl/UserServiceImpl.java @@ -179,6 +179,11 @@ public class UserServiceImpl extends ServiceImpl .collect(Collectors.toList()); } + /** + * 获取当前登录用户 + * @param request + * @return + */ @Override public User getLoginUser(HttpServletRequest request) { // 判断是否已经登录 @@ -187,12 +192,12 @@ public class UserServiceImpl extends ServiceImpl if (currentUser == null || currentUser.getId() == null) { throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR); } - // 从数据库中查询(追求性能的话可以注释,直接返回上述结果) - Long userId = currentUser.getId(); - currentUser = this.getById(userId); - if (currentUser == null) { - throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR); - } +// // 从数据库中查询(追求性能的话可以注释,直接返回上述结果) +// Long userId = currentUser.getId(); +// currentUser = this.getById(userId); +// if (currentUser == null) { +// throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR); +// } return currentUser; } @@ -216,29 +221,37 @@ public class UserServiceImpl extends ServiceImpl /** * 查询条件 - * @param userQueryRequest + * @param req * @return */ @Override - public QueryWrapper getQueryWrapper(UserQueryRequest userQueryRequest) { - if (userQueryRequest == null) { + public LambdaQueryWrapper getQueryWrapper(UserQueryRequest req) { + if (req == null) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数为空"); } - Long id = userQueryRequest.getId(); - String userName = userQueryRequest.getUserName(); - String userAccount = userQueryRequest.getUserAccount(); - String userProfile = userQueryRequest.getUserProfile(); - String userRole = userQueryRequest.getUserRole(); - String sortField = userQueryRequest.getSortField(); - String sortOrder = userQueryRequest.getSortOrder(); - QueryWrapper queryWrapper = new QueryWrapper<>(); - queryWrapper.eq(ObjUtil.isNotNull(id), "id", id); - queryWrapper.eq(StrUtil.isNotBlank(userRole), "userRole", userRole); - queryWrapper.like(StrUtil.isNotBlank(userAccount), "userAccount", userAccount); - queryWrapper.like(StrUtil.isNotBlank(userName), "userName", userName); - queryWrapper.like(StrUtil.isNotBlank(userProfile), "userProfile", userProfile); - queryWrapper.orderBy(StrUtil.isNotEmpty(sortField), sortOrder.equals("ascend"), sortField); - return queryWrapper; + + LambdaQueryWrapper qw = Wrappers.lambdaQuery(User.class); + + // 基本等值和模糊匹配 + qw.eq(ObjUtil.isNotNull(req.getId()), User::getId, req.getId()) + .eq(StrUtil.isNotBlank(req.getUserRole()), User::getUserRole, req.getUserRole()) + .like(StrUtil.isNotBlank(req.getUserAccount()), User::getUserAccount, req.getUserAccount()) + .like(StrUtil.isNotBlank(req.getUserName()), User::getUserName, req.getUserName()) + .like(StrUtil.isNotBlank(req.getUserProfile()), User::getUserProfile, req.getUserProfile()); + + // 动态排序 + if (StrUtil.isNotBlank(req.getSortField())) { + String column = StrUtil.toUnderlineCase(req.getSortField()); + boolean asc = "ascend".equalsIgnoreCase(req.getSortOrder()); + 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()); } /** diff --git a/src/main/resources/mapper/PictureMapper.xml b/src/main/resources/mapper/PictureMapper.xml new file mode 100644 index 0000000..070a1cb --- /dev/null +++ b/src/main/resources/mapper/PictureMapper.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + 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 + +