2025-03-09 18:36:21 +08:00
|
|
|
|
package edu.whut.smilepicturebackend.service.impl;
|
|
|
|
|
|
2025-03-10 18:37:42 +08:00
|
|
|
|
import cn.hutool.core.bean.BeanUtil;
|
2025-03-09 18:36:21 +08:00
|
|
|
|
import cn.hutool.core.collection.CollUtil;
|
|
|
|
|
import cn.hutool.core.util.ObjUtil;
|
2025-03-14 16:55:32 +08:00
|
|
|
|
import cn.hutool.core.util.RandomUtil;
|
2025-03-09 18:36:21 +08:00
|
|
|
|
import cn.hutool.core.util.StrUtil;
|
2025-03-14 16:55:32 +08:00
|
|
|
|
import cn.hutool.json.JSONObject;
|
2025-03-10 12:15:54 +08:00
|
|
|
|
import cn.hutool.json.JSONUtil;
|
2025-03-09 18:36:21 +08:00
|
|
|
|
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;
|
2025-03-10 12:15:54 +08:00
|
|
|
|
import edu.whut.smilepicturebackend.exception.BusinessException;
|
2025-03-09 18:36:21 +08:00
|
|
|
|
import edu.whut.smilepicturebackend.exception.ErrorCode;
|
|
|
|
|
import edu.whut.smilepicturebackend.exception.ThrowUtils;
|
2025-03-17 14:28:02 +08:00
|
|
|
|
import edu.whut.smilepicturebackend.manager.CosManager;
|
2025-03-09 18:36:21 +08:00
|
|
|
|
import edu.whut.smilepicturebackend.manager.FileManager;
|
2025-03-14 16:55:32 +08:00
|
|
|
|
import edu.whut.smilepicturebackend.manager.cache.MyCacheManager;
|
2025-03-10 18:06:07 +08:00
|
|
|
|
import edu.whut.smilepicturebackend.manager.upload.FilePictureUpload;
|
|
|
|
|
import edu.whut.smilepicturebackend.manager.upload.PictureUploadTemplate;
|
|
|
|
|
import edu.whut.smilepicturebackend.manager.upload.UrlPictureUpload;
|
2025-03-09 18:36:21 +08:00
|
|
|
|
import edu.whut.smilepicturebackend.mapper.PictureMapper;
|
2025-03-12 15:07:12 +08:00
|
|
|
|
import edu.whut.smilepicturebackend.model.dto.picture.*;
|
2025-03-09 18:36:21 +08:00
|
|
|
|
import edu.whut.smilepicturebackend.model.entity.Picture;
|
2025-03-25 17:58:51 +08:00
|
|
|
|
import edu.whut.smilepicturebackend.model.entity.Space;
|
2025-03-09 18:36:21 +08:00
|
|
|
|
import edu.whut.smilepicturebackend.model.entity.User;
|
2025-03-10 18:37:42 +08:00
|
|
|
|
import edu.whut.smilepicturebackend.model.enums.PictureReviewStatusEnum;
|
2025-03-09 18:36:21 +08:00
|
|
|
|
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;
|
2025-03-25 17:58:51 +08:00
|
|
|
|
import edu.whut.smilepicturebackend.service.SpaceService;
|
2025-03-09 18:36:21 +08:00
|
|
|
|
import edu.whut.smilepicturebackend.service.UserService;
|
|
|
|
|
import lombok.RequiredArgsConstructor;
|
2025-03-12 15:07:12 +08:00
|
|
|
|
import lombok.extern.slf4j.Slf4j;
|
|
|
|
|
import org.jsoup.Jsoup;
|
|
|
|
|
import org.jsoup.nodes.Document;
|
|
|
|
|
import org.jsoup.nodes.Element;
|
|
|
|
|
import org.jsoup.select.Elements;
|
2025-03-09 18:36:21 +08:00
|
|
|
|
import org.springframework.beans.BeanUtils;
|
2025-03-17 14:28:02 +08:00
|
|
|
|
import org.springframework.scheduling.annotation.Async;
|
2025-03-09 18:36:21 +08:00
|
|
|
|
import org.springframework.stereotype.Service;
|
2025-03-14 16:55:32 +08:00
|
|
|
|
import org.springframework.util.DigestUtils;
|
2025-03-12 15:07:12 +08:00
|
|
|
|
|
2025-03-09 18:36:21 +08:00
|
|
|
|
|
|
|
|
|
import javax.servlet.http.HttpServletRequest;
|
2025-03-12 15:07:12 +08:00
|
|
|
|
import java.io.IOException;
|
2025-03-09 18:36:21 +08:00
|
|
|
|
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
|
|
|
|
|
*/
|
2025-03-12 15:07:12 +08:00
|
|
|
|
@Slf4j
|
2025-03-09 18:36:21 +08:00
|
|
|
|
@Service
|
|
|
|
|
@RequiredArgsConstructor
|
|
|
|
|
public class PictureServiceImpl extends ServiceImpl<PictureMapper, Picture>
|
|
|
|
|
implements PictureService {
|
|
|
|
|
private final FileManager fileManager;
|
|
|
|
|
private final UserService userService;
|
2025-03-10 18:06:07 +08:00
|
|
|
|
private final FilePictureUpload filePictureUpload;
|
|
|
|
|
private final UrlPictureUpload urlPictureUpload;
|
2025-03-14 16:55:32 +08:00
|
|
|
|
private final MyCacheManager cacheManager;
|
2025-03-17 14:28:02 +08:00
|
|
|
|
private final CosManager cosManager;
|
2025-03-25 17:58:51 +08:00
|
|
|
|
private final SpaceService spaceService;
|
2025-03-09 18:36:21 +08:00
|
|
|
|
@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, "简介过长");
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-03-17 19:42:59 +08:00
|
|
|
|
|
2025-03-09 18:36:21 +08:00
|
|
|
|
@Override
|
2025-03-10 18:06:07 +08:00
|
|
|
|
public PictureVO uploadPicture(Object inputSource, PictureUploadRequest pictureUploadRequest, User loginUser) {
|
2025-03-09 18:36:21 +08:00
|
|
|
|
// 校验参数
|
|
|
|
|
ThrowUtils.throwIf(loginUser == null, ErrorCode.NO_AUTH_ERROR);
|
2025-03-25 17:58:51 +08:00
|
|
|
|
// 校验空间是否存在
|
|
|
|
|
Long spaceId = pictureUploadRequest.getSpaceId();
|
|
|
|
|
if (spaceId != null) {
|
|
|
|
|
Space space = spaceService.getById(spaceId);
|
|
|
|
|
ThrowUtils.throwIf(space == null, ErrorCode.NOT_FOUND_ERROR, "空间不存在");
|
|
|
|
|
// 改为使用统一的权限校验
|
|
|
|
|
// 校验是否有空间的权限,仅空间管理员才能上传
|
|
|
|
|
if (!loginUser.getId().equals(space.getUserId())) {
|
|
|
|
|
throw new BusinessException(ErrorCode.NO_AUTH_ERROR, "没有空间权限");
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-03-17 19:42:59 +08:00
|
|
|
|
// 判断是创建还是替换
|
|
|
|
|
Long pictureId = pictureUploadRequest == null ? null : pictureUploadRequest.getId();
|
|
|
|
|
Picture oldPicture = null;
|
|
|
|
|
|
2025-03-09 18:36:21 +08:00
|
|
|
|
// 如果是更新,判断图片是否存在
|
|
|
|
|
if (pictureId != null) {
|
2025-03-17 19:42:59 +08:00
|
|
|
|
oldPicture = this.getById(pictureId);
|
2025-03-10 18:37:42 +08:00
|
|
|
|
ThrowUtils.throwIf(oldPicture == null, ErrorCode.NOT_FOUND_ERROR, "图片不存在");
|
|
|
|
|
// 仅本人或管理员可编辑图片
|
|
|
|
|
if (!oldPicture.getUserId().equals(loginUser.getId()) && !userService.isAdmin(loginUser)) {
|
|
|
|
|
throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
|
|
|
|
|
}
|
2025-03-25 17:58:51 +08:00
|
|
|
|
// 校验空间是否一致
|
|
|
|
|
// 没传 spaceId,则复用原有图片的 spaceId(这样也兼容了公共图库)
|
|
|
|
|
if (spaceId == null) {
|
|
|
|
|
if (oldPicture.getSpaceId() != null) {
|
|
|
|
|
spaceId = oldPicture.getSpaceId();
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 传了 spaceId,必须和原图片的空间 id 一致
|
|
|
|
|
if (ObjUtil.notEqual(spaceId, oldPicture.getSpaceId())) {
|
|
|
|
|
throw new BusinessException(ErrorCode.PARAMS_ERROR, "空间 id 不一致");
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-03-09 18:36:21 +08:00
|
|
|
|
}
|
|
|
|
|
// 上传图片,得到图片信息
|
2025-03-25 17:58:51 +08:00
|
|
|
|
// 按照用户 id 划分目录 => 按照空间划分目录
|
2025-03-09 18:36:21 +08:00
|
|
|
|
String uploadPathPrefix;
|
2025-03-25 17:58:51 +08:00
|
|
|
|
if (spaceId == null) {
|
|
|
|
|
// 公共图库+用户id
|
|
|
|
|
uploadPathPrefix = String.format("public/%s", loginUser.getId());
|
|
|
|
|
} else {
|
|
|
|
|
// 私人空间+空间id
|
|
|
|
|
uploadPathPrefix = String.format("space/%s", spaceId);
|
|
|
|
|
}
|
2025-03-14 16:55:32 +08:00
|
|
|
|
// 根据 inputSource 的类型区分上传方式!!
|
2025-03-10 18:06:07 +08:00
|
|
|
|
PictureUploadTemplate pictureUploadTemplate = filePictureUpload;
|
|
|
|
|
if (inputSource instanceof String) {
|
|
|
|
|
pictureUploadTemplate = urlPictureUpload;
|
|
|
|
|
}
|
2025-03-17 19:42:59 +08:00
|
|
|
|
//上传到腾讯云COS上
|
2025-03-10 18:06:07 +08:00
|
|
|
|
UploadPictureResult uploadPictureResult = pictureUploadTemplate.uploadPicture(inputSource, uploadPathPrefix);
|
2025-03-17 19:42:59 +08:00
|
|
|
|
// 构造要入库的图片信息,将图片信息存入数据库中。
|
2025-03-09 18:36:21 +08:00
|
|
|
|
Picture picture = new Picture();
|
|
|
|
|
// 复制同名属性(url、name、picSize、picWidth、picHeight、picScale、picFormat)
|
|
|
|
|
BeanUtils.copyProperties(uploadPictureResult, picture);
|
2025-03-12 15:07:12 +08:00
|
|
|
|
// 支持外层pictureUploadRequest传递图片名称
|
|
|
|
|
picture.setName(
|
|
|
|
|
StrUtil.blankToDefault(
|
|
|
|
|
pictureUploadRequest == null ? null : pictureUploadRequest.getPicName(),
|
|
|
|
|
uploadPictureResult.getName()
|
|
|
|
|
)
|
|
|
|
|
);
|
2025-03-09 18:36:21 +08:00
|
|
|
|
picture.setUserId(loginUser.getId());
|
2025-03-25 17:58:51 +08:00
|
|
|
|
picture.setSpaceId(spaceId);
|
2025-03-10 18:06:07 +08:00
|
|
|
|
// 补充审核参数
|
|
|
|
|
this.fillReviewParams(picture, loginUser);
|
2025-03-09 18:36:21 +08:00
|
|
|
|
// 操作数据库
|
|
|
|
|
// 如果 pictureId 不为空,表示更新,否则是新增
|
|
|
|
|
if (pictureId != null) {
|
|
|
|
|
// 如果是更新,需要补充 id 和编辑时间
|
|
|
|
|
picture.setId(pictureId);
|
|
|
|
|
picture.setEditTime(new Date());
|
|
|
|
|
}
|
|
|
|
|
boolean result = this.saveOrUpdate(picture);
|
|
|
|
|
ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR, "图片上传失败,数据库操作失败");
|
2025-03-17 19:42:59 +08:00
|
|
|
|
//如果是更新,清理旧的图片
|
|
|
|
|
if (oldPicture != null) {
|
|
|
|
|
this.clearPictureFile(oldPicture);
|
|
|
|
|
}
|
2025-03-09 18:36:21 +08:00
|
|
|
|
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())
|
2025-03-10 18:06:07 +08:00
|
|
|
|
.eq(ObjUtil.isNotEmpty(req.getReviewMessage()),Picture::getReviewMessage,req.getReviewMessage())
|
2025-03-09 18:36:21 +08:00
|
|
|
|
.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())
|
2025-03-10 18:06:07 +08:00
|
|
|
|
.eq(ObjUtil.isNotEmpty(req.getReviewStatus()),Picture::getReviewStatus,req.getReviewStatus())
|
|
|
|
|
.eq(ObjUtil.isNotEmpty(req.getReviewerId()),Picture::getReviewerId,req.getReviewerId())
|
2025-03-09 18:36:21 +08:00
|
|
|
|
.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;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-17 19:42:59 +08:00
|
|
|
|
@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);
|
|
|
|
|
}
|
2025-03-25 17:58:51 +08:00
|
|
|
|
// 校验权限
|
|
|
|
|
checkPictureAuth(loginUser, oldPicture);
|
2025-03-17 19:42:59 +08:00
|
|
|
|
// 操作数据库
|
|
|
|
|
boolean result = this.removeById(pictureId);
|
|
|
|
|
ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
|
|
|
|
|
//清理图片资源
|
|
|
|
|
this.clearPictureFile(oldPicture);
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-25 17:58:51 +08:00
|
|
|
|
@Override
|
|
|
|
|
public void editPicture(PictureEditRequest pictureEditRequest, User loginUser) {
|
|
|
|
|
// 在此处将实体类和 DTO 进行转换
|
|
|
|
|
Picture picture = new Picture();
|
|
|
|
|
BeanUtils.copyProperties(pictureEditRequest, picture);
|
|
|
|
|
// 注意将 list 转为 string
|
|
|
|
|
picture.setTags(JSONUtil.toJsonStr(pictureEditRequest.getTags()));
|
|
|
|
|
// 设置编辑时间
|
|
|
|
|
picture.setEditTime(new Date());
|
|
|
|
|
// 数据校验
|
|
|
|
|
this.validPicture(picture);
|
|
|
|
|
// 判断是否存在
|
|
|
|
|
long id = pictureEditRequest.getId();
|
|
|
|
|
Picture oldPicture = this.getById(id);
|
|
|
|
|
ThrowUtils.throwIf(oldPicture == null, ErrorCode.NOT_FOUND_ERROR);
|
|
|
|
|
// 校验权限
|
|
|
|
|
checkPictureAuth(loginUser, oldPicture);
|
|
|
|
|
// 补充审核参数,每次编辑图片都要重新过审
|
|
|
|
|
this.fillReviewParams(picture, loginUser);
|
|
|
|
|
// 操作数据库
|
|
|
|
|
boolean result = this.updateById(picture);
|
|
|
|
|
ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-09 18:36:21 +08:00
|
|
|
|
@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;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-10 12:15:54 +08:00
|
|
|
|
/**
|
|
|
|
|
* 分页获取图片封装
|
|
|
|
|
*/
|
2025-03-09 18:36:21 +08:00
|
|
|
|
@Override
|
|
|
|
|
public Page<PictureVO> getPictureVOPage(Page<Picture> picturePage, HttpServletRequest request) {
|
2025-03-10 12:15:54 +08:00
|
|
|
|
//从 Page<Picture> 中拿到原始记录和分页元数据
|
2025-03-09 18:36:21 +08:00
|
|
|
|
List<Picture> pictureList = picturePage.getRecords();
|
|
|
|
|
Page<PictureVO> pictureVOPage = new Page<>(picturePage.getCurrent(), picturePage.getSize(), picturePage.getTotal());
|
|
|
|
|
if (CollUtil.isEmpty(pictureList)) {
|
|
|
|
|
return pictureVOPage;
|
|
|
|
|
}
|
2025-03-10 12:15:54 +08:00
|
|
|
|
// 实体到 VO 的基本映射
|
2025-03-09 18:36:21 +08:00
|
|
|
|
List<PictureVO> pictureVOList = pictureList.stream()
|
|
|
|
|
.map(PictureVO::objToVo)
|
|
|
|
|
.collect(Collectors.toList());
|
2025-03-10 12:15:54 +08:00
|
|
|
|
//批量拉取关联的用户信息
|
|
|
|
|
//先从所有 Picture 里摘出不重复的 userId。
|
|
|
|
|
//调 userService.listByIds(...) 一次性把这些用户都查出来,避免 N+1 查询。
|
|
|
|
|
//再把结果按 userId 分组,方便快速根据 ID 拿到对应的 User 对象。
|
2025-03-09 18:36:21 +08:00
|
|
|
|
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));
|
2025-03-10 12:15:54 +08:00
|
|
|
|
// 把用户信息填充到 VO
|
2025-03-09 18:36:21 +08:00
|
|
|
|
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;
|
|
|
|
|
}
|
2025-03-10 12:15:54 +08:00
|
|
|
|
|
2025-03-10 18:37:42 +08:00
|
|
|
|
@Override
|
|
|
|
|
public void doPictureReview(PictureReviewRequest pictureReviewRequest, User loginUser) {
|
|
|
|
|
// 1. 校验参数
|
|
|
|
|
ThrowUtils.throwIf(pictureReviewRequest == null, ErrorCode.PARAMS_ERROR);
|
|
|
|
|
Long id = pictureReviewRequest.getId();
|
|
|
|
|
Integer reviewStatus = pictureReviewRequest.getReviewStatus();
|
|
|
|
|
PictureReviewStatusEnum reviewStatusEnum = PictureReviewStatusEnum.getEnumByValue(reviewStatus);
|
|
|
|
|
String reviewMessage = pictureReviewRequest.getReviewMessage();
|
|
|
|
|
if (id == null || reviewStatusEnum == null || PictureReviewStatusEnum.REVIEWING.equals(reviewStatusEnum)) {
|
|
|
|
|
throw new BusinessException(ErrorCode.PARAMS_ERROR);
|
|
|
|
|
}
|
|
|
|
|
// 2. 判断图片是否存在
|
|
|
|
|
Picture oldPicture = this.getById(id);
|
|
|
|
|
ThrowUtils.throwIf(oldPicture == null, ErrorCode.NOT_FOUND_ERROR);
|
|
|
|
|
// 3. 校验审核状态是否重复,已是改状态
|
|
|
|
|
if (oldPicture.getReviewStatus().equals(reviewStatus)) {
|
|
|
|
|
throw new BusinessException(ErrorCode.PARAMS_ERROR, "请勿重复审核");
|
|
|
|
|
}
|
|
|
|
|
// 4. 数据库操作
|
|
|
|
|
Picture updatePicture = new Picture();
|
|
|
|
|
BeanUtil.copyProperties(pictureReviewRequest, updatePicture);
|
|
|
|
|
updatePicture.setReviewerId(loginUser.getId()); //审核人
|
|
|
|
|
updatePicture.setReviewTime(new Date());
|
|
|
|
|
boolean result = this.updateById(updatePicture);
|
|
|
|
|
ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 填充审核参数
|
|
|
|
|
*
|
|
|
|
|
* @param picture
|
|
|
|
|
* @param loginUser
|
|
|
|
|
*/
|
|
|
|
|
@Override
|
|
|
|
|
public void fillReviewParams(Picture picture, User loginUser) {
|
|
|
|
|
if (userService.isAdmin(loginUser)) {
|
|
|
|
|
// 管理员自动过审
|
|
|
|
|
picture.setReviewStatus(PictureReviewStatusEnum.PASS.getValue());
|
|
|
|
|
picture.setReviewerId(loginUser.getId());
|
|
|
|
|
picture.setReviewMessage("管理员自动过审");
|
|
|
|
|
picture.setReviewTime(new Date());
|
|
|
|
|
} else {
|
|
|
|
|
// 非管理员,无论是编辑还是创建默认都是待审核
|
|
|
|
|
picture.setReviewStatus(PictureReviewStatusEnum.REVIEWING.getValue());
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-03-12 15:07:12 +08:00
|
|
|
|
|
|
|
|
|
//爬取网落图片,可以用ai分析标签
|
|
|
|
|
@Override
|
|
|
|
|
public Integer uploadPictureByBatch(PictureUploadByBatchRequest pictureUploadByBatchRequest, User loginUser) {
|
|
|
|
|
// 校验参数
|
|
|
|
|
String searchText = pictureUploadByBatchRequest.getSearchText();
|
|
|
|
|
Integer count = pictureUploadByBatchRequest.getCount();
|
|
|
|
|
ThrowUtils.throwIf(count > 30, ErrorCode.PARAMS_ERROR, "最多 30 条");
|
|
|
|
|
// 名称前缀默认等于搜索关键词
|
|
|
|
|
String namePrefix = pictureUploadByBatchRequest.getNamePrefix();
|
|
|
|
|
if (StrUtil.isBlank(namePrefix)) {
|
|
|
|
|
namePrefix = searchText;
|
|
|
|
|
}
|
|
|
|
|
// 抓取内容
|
|
|
|
|
String fetchUrl = String.format("https://cn.bing.com/images/async?q=%s&mmasync=1", searchText);
|
|
|
|
|
//最全的html文档
|
|
|
|
|
Document document;
|
|
|
|
|
try {
|
|
|
|
|
document = Jsoup.connect(fetchUrl).get();
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
log.error("获取页面失败", e);
|
|
|
|
|
throw new BusinessException(ErrorCode.OPERATION_ERROR, "获取页面失败");
|
|
|
|
|
}
|
|
|
|
|
// 解析内容
|
|
|
|
|
Element div = document.getElementsByClass("dgControl").first();
|
|
|
|
|
if (ObjUtil.isEmpty(div)) {
|
|
|
|
|
throw new BusinessException(ErrorCode.OPERATION_ERROR, "获取元素失败");
|
|
|
|
|
}
|
|
|
|
|
Elements imgElementList = div.select("img.mimg");
|
|
|
|
|
// 遍历元素,依次处理上传图片
|
|
|
|
|
int uploadCount = 0;
|
|
|
|
|
for (Element imgElement : imgElementList) {
|
2025-03-14 16:55:32 +08:00
|
|
|
|
// 找到最近的 <a class="iusc">,它的 m 属性里有完整的 JSON
|
|
|
|
|
Element a = imgElement.closest("a.iusc");
|
|
|
|
|
if (a == null || a.attr("m").isEmpty()) {
|
|
|
|
|
log.info("没找到 m 属性,跳过该图片");
|
2025-03-12 15:07:12 +08:00
|
|
|
|
continue;
|
|
|
|
|
}
|
2025-03-14 16:55:32 +08:00
|
|
|
|
// 用 Hutool 解析 JSON,取出 murl
|
|
|
|
|
JSONObject mObj = JSONUtil.parseObj(a.attr("m"));
|
|
|
|
|
String fileUrl = mObj.getStr("murl"); // murl是带 .jpg/.png 的原图 , src是缩略图,
|
|
|
|
|
|
|
|
|
|
// 可选:去掉 URL 后面的 ? 及参数
|
|
|
|
|
int qm = fileUrl.indexOf("?");
|
|
|
|
|
if (qm > 0) {
|
|
|
|
|
fileUrl = fileUrl.substring(0, qm);
|
2025-03-12 15:07:12 +08:00
|
|
|
|
}
|
|
|
|
|
// 上传图片
|
|
|
|
|
PictureUploadRequest pictureUploadRequest = new PictureUploadRequest();
|
|
|
|
|
pictureUploadRequest.setFileUrl(fileUrl);
|
|
|
|
|
pictureUploadRequest.setPicName(namePrefix + (uploadCount + 1));
|
|
|
|
|
try {
|
2025-03-14 16:55:32 +08:00
|
|
|
|
log.info("爬取图片url:"+fileUrl);
|
2025-03-12 15:07:12 +08:00
|
|
|
|
PictureVO pictureVO = this.uploadPicture(fileUrl, pictureUploadRequest, loginUser);
|
|
|
|
|
log.info("图片上传成功,id = {}", pictureVO.getId());
|
|
|
|
|
uploadCount++;
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
log.error("图片上传失败", e);
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (uploadCount >= count) {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return uploadCount;
|
|
|
|
|
}
|
2025-03-14 16:55:32 +08:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 查询图片带缓存
|
|
|
|
|
* @param queryRequest
|
|
|
|
|
* @param httpRequest
|
|
|
|
|
* @return
|
|
|
|
|
*/
|
|
|
|
|
@Override
|
|
|
|
|
public Page<PictureVO> listPictureVOByPageWithCache(PictureQueryRequest queryRequest, HttpServletRequest httpRequest) {
|
|
|
|
|
long current = queryRequest.getCurrent();
|
|
|
|
|
long size = queryRequest.getPageSize();
|
|
|
|
|
|
|
|
|
|
// 参数校验
|
|
|
|
|
ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR);
|
|
|
|
|
queryRequest.setReviewStatus(PictureReviewStatusEnum.PASS.getValue());
|
|
|
|
|
|
|
|
|
|
// 构造 cacheKey
|
|
|
|
|
String condJson = JSONUtil.toJsonStr(queryRequest);
|
|
|
|
|
String hash = DigestUtils.md5DigestAsHex(condJson.getBytes());
|
|
|
|
|
String cacheKey = "smilepicture:listPictureVOByPage:" + hash;
|
|
|
|
|
|
|
|
|
|
// 随机过期:300–600s
|
|
|
|
|
int expire = 300 + RandomUtil.randomInt(0, 300);
|
|
|
|
|
|
|
|
|
|
// 调用通用缓存方法
|
|
|
|
|
return cacheManager.getFromCacheOrDatabase(
|
|
|
|
|
cacheKey,
|
|
|
|
|
Page.class,
|
|
|
|
|
() -> {
|
|
|
|
|
Page<Picture> picturePage = this.page(new Page<>(current, size),
|
|
|
|
|
this.getQueryWrapper(queryRequest));
|
|
|
|
|
return this.getPictureVOPage(picturePage, httpRequest);
|
|
|
|
|
},
|
|
|
|
|
expire
|
|
|
|
|
);
|
|
|
|
|
}
|
2025-03-17 14:28:02 +08:00
|
|
|
|
|
|
|
|
|
@Async //异步执行
|
|
|
|
|
@Override
|
|
|
|
|
public void clearPictureFile(Picture oldPicture) {
|
2025-03-17 19:42:59 +08:00
|
|
|
|
// 判断该图片是否被多条记录使用(图片秒传的情况可能一个cos地址对应多个数据库url)
|
2025-03-17 14:28:02 +08:00
|
|
|
|
String pictureUrl = oldPicture.getUrl();
|
|
|
|
|
long count = this.lambdaQuery()
|
|
|
|
|
.eq(Picture::getUrl, pictureUrl)
|
|
|
|
|
.count();
|
|
|
|
|
// 有不止一条记录用到了该图片,不清理
|
|
|
|
|
if (count > 1) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// 删除图片
|
2025-03-17 19:42:59 +08:00
|
|
|
|
cosManager.deleteIfNotBlank(pictureUrl);
|
|
|
|
|
|
2025-03-17 14:28:02 +08:00
|
|
|
|
//删除原图
|
|
|
|
|
String originalUrl=oldPicture.getOriginalUrl();
|
2025-03-17 19:42:59 +08:00
|
|
|
|
cosManager.deleteIfNotBlank(originalUrl);
|
|
|
|
|
|
2025-03-17 14:28:02 +08:00
|
|
|
|
// 删除缩略图
|
|
|
|
|
String thumbnailUrl = oldPicture.getThumbnailUrl();
|
2025-03-17 19:42:59 +08:00
|
|
|
|
cosManager.deleteIfNotBlank(thumbnailUrl);
|
2025-03-17 14:28:02 +08:00
|
|
|
|
}
|
2025-03-25 17:58:51 +08:00
|
|
|
|
@Override
|
|
|
|
|
public void checkPictureAuth(User loginUser, Picture picture) {
|
|
|
|
|
Long spaceId = picture.getSpaceId();
|
|
|
|
|
Long loginUserId = loginUser.getId();
|
|
|
|
|
if (spaceId == null) {
|
|
|
|
|
// 公共图库,仅本人或管理员可操作
|
|
|
|
|
if (!picture.getUserId().equals(loginUserId) && !userService.isAdmin(loginUser)) {
|
|
|
|
|
throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// 私有空间,仅空间管理员可操作
|
|
|
|
|
if (!picture.getUserId().equals(loginUserId)) {
|
|
|
|
|
throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-03-09 18:36:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|