397 lines
14 KiB
Java
397 lines
14 KiB
Java
package com.bao.dating.service.impl;
|
||
|
||
import com.bao.dating.common.aliyun.GreenImageScan;
|
||
import com.bao.dating.common.aliyun.GreenTextScan;
|
||
import com.bao.dating.common.result.GreenAuditResult;
|
||
import com.bao.dating.context.UserContext;
|
||
import com.bao.dating.mapper.CommentsMapper;
|
||
import com.bao.dating.mapper.PostFavoriteMapper;
|
||
import com.bao.dating.mapper.PostLikeMapper;
|
||
import com.bao.dating.mapper.PostMapper;
|
||
import com.bao.dating.pojo.dto.PostRequestDTO;
|
||
import com.bao.dating.pojo.entity.Post;
|
||
import com.bao.dating.pojo.entity.User;
|
||
import com.bao.dating.pojo.vo.PostEditVO;
|
||
import com.bao.dating.service.PostService;
|
||
import com.bao.dating.common.aliyun.AliOssUtil;
|
||
import com.bao.dating.service.UserService;
|
||
import com.bao.dating.util.FileUtil;
|
||
import com.bao.dating.util.WatermarkUtil;
|
||
import org.springframework.beans.BeanUtils;
|
||
import org.springframework.beans.factory.annotation.Autowired;
|
||
import org.springframework.stereotype.Service;
|
||
import org.springframework.transaction.annotation.Transactional;
|
||
import org.springframework.util.CollectionUtils;
|
||
import org.springframework.web.multipart.MultipartFile;
|
||
|
||
import java.awt.image.BufferedImage;
|
||
import java.io.IOException;
|
||
import java.time.LocalDate;
|
||
import java.time.LocalDateTime;
|
||
import java.time.format.DateTimeFormatter;
|
||
import java.util.*;
|
||
|
||
/**
|
||
* 动态服务实现类
|
||
*
|
||
* @author KilLze yang
|
||
*/
|
||
@Service
|
||
public class PostServiceImpl implements PostService {
|
||
|
||
@Autowired
|
||
private AliOssUtil ossUtil;
|
||
|
||
@Autowired
|
||
private GreenTextScan greenTextScan;
|
||
|
||
@Autowired
|
||
private GreenImageScan greenImageScan;
|
||
|
||
@Autowired
|
||
private PostMapper postMapper;
|
||
|
||
@Autowired
|
||
private PostLikeMapper postLikeMapper;
|
||
|
||
@Autowired
|
||
private PostFavoriteMapper postFavoriteMapper;
|
||
|
||
@Autowired
|
||
private CommentsMapper commentsMapper;
|
||
|
||
@Autowired
|
||
private WatermarkUtil watermarkUtil;
|
||
|
||
/**
|
||
* 上传媒体文件
|
||
* @param files 媒体文件数组
|
||
* @return 上传后的文件URL列表
|
||
*/
|
||
@Override
|
||
public List<String> uploadMedia(MultipartFile[] files) {
|
||
// 如果没有文件,则返回空列表
|
||
if (files == null || files.length == 0) {
|
||
return Collections.emptyList();
|
||
}
|
||
// 创建媒体文件列表
|
||
List<String> mediaUrls = new ArrayList<>();
|
||
|
||
for (MultipartFile file : files) {
|
||
// 跳过空文件
|
||
if (file == null || file.isEmpty()) {
|
||
continue;
|
||
}
|
||
// 获取文件名并跳过空文件
|
||
String originalFilename = file.getOriginalFilename();
|
||
if (originalFilename == null) {
|
||
continue;
|
||
}
|
||
// 校验文件类型
|
||
String fileType = FileUtil.getFileType(originalFilename);
|
||
if (!"image".equals(fileType) && !"video".equals(fileType)) {
|
||
throw new RuntimeException("不支持的文件类型:" + originalFilename);
|
||
}
|
||
|
||
// 创建目录
|
||
String dir = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM"));
|
||
// 获取文件扩展名
|
||
String extension = FileUtil.getFileExtension(originalFilename);
|
||
// 生成唯一文件名
|
||
String newFileName = UUID.randomUUID().toString().replace("-", "") + "." + extension;
|
||
// 获取用户ID
|
||
Long userId = UserContext.getUserId();
|
||
// 创建文件名
|
||
String fileName = "post/" + userId + "/" + dir + "/" + newFileName;
|
||
|
||
try {
|
||
// 获取文件字节数据
|
||
byte[] fileBytes = file.getBytes();
|
||
// 上传图片或视频
|
||
String ossUrl = ossUtil.upload(fileBytes, fileName);
|
||
if (ossUrl == null || ossUrl.isEmpty()) {
|
||
throw new RuntimeException("文件上传失败:" + originalFilename);
|
||
}
|
||
// 添加上传后的 URL
|
||
mediaUrls.add(ossUrl);
|
||
} catch (IOException e) {
|
||
// 统一异常处理
|
||
throw new RuntimeException("上传媒体文件失败:" + originalFilename, e);
|
||
}
|
||
}
|
||
return mediaUrls;
|
||
}
|
||
|
||
/**
|
||
* 创建动态
|
||
*
|
||
* @param postRequestDTO 动态数据传输对象
|
||
* @return 创建的动态对象
|
||
*/
|
||
@Override
|
||
public Post createPost(PostRequestDTO postRequestDTO) {
|
||
|
||
// 创建动态对象
|
||
Post post = new Post();
|
||
Long userId = UserContext.getUserId();
|
||
post.setUserId(userId);
|
||
post.setContent(postRequestDTO.getContent());
|
||
post.setTags(postRequestDTO.getTags());
|
||
post.setMediaOssKeys(new ArrayList<>());
|
||
// 如果有传入的媒体链接,则使用它们
|
||
if (postRequestDTO.getMediaOssKeys() != null && !postRequestDTO.getMediaOssKeys().isEmpty()) {
|
||
post.setMediaOssKeys(postRequestDTO.getMediaOssKeys());
|
||
}
|
||
|
||
// 1. 文本内容审核
|
||
Map textResult;
|
||
try {
|
||
textResult = greenTextScan.greeTextScan(postRequestDTO.getContent());
|
||
} catch (Exception e) {
|
||
throw new RuntimeException("文本审核失败");
|
||
}
|
||
|
||
String textSuggestion = (String) textResult.get("suggestion");
|
||
|
||
// 2. 图片审核(如果有)
|
||
if (postRequestDTO.getMediaOssKeys() != null && !postRequestDTO.getMediaOssKeys().isEmpty()) {
|
||
Map imageResult;
|
||
try {
|
||
imageResult = greenImageScan.imageScan(postRequestDTO.getMediaOssKeys());
|
||
} catch (Exception e) {
|
||
throw new RuntimeException(e);
|
||
}
|
||
// 图片审核结果
|
||
String imageSuggestion = (String) imageResult.get("suggestion");
|
||
|
||
// 根据审核结果设置状态
|
||
if (GreenAuditResult.BLOCK.equals(textSuggestion) || GreenAuditResult.BLOCK.equals(imageSuggestion)) {
|
||
// 审核未通过,允许用户修改
|
||
post.setIsPublic(2);
|
||
} else if (GreenAuditResult.REVIEW.equals(textSuggestion) || GreenAuditResult.REVIEW.equals(imageSuggestion)) {
|
||
// 待审核,需人工审核
|
||
post.setIsPublic(1);
|
||
} else {
|
||
// 审核通过
|
||
post.setIsPublic(0);
|
||
}
|
||
} else {
|
||
// 只有文本内容的情况
|
||
if (GreenAuditResult.BLOCK.equals(textSuggestion)) {
|
||
// 审核未通过,允许用户修改
|
||
post.setIsPublic(2);
|
||
} else if (GreenAuditResult.REVIEW.equals(textSuggestion)) {
|
||
// 待审核,需人工审核
|
||
post.setIsPublic(1);
|
||
} else {
|
||
// 审核通过
|
||
post.setIsPublic(0);
|
||
}
|
||
}
|
||
post.setUpdatedAt(LocalDateTime.now());
|
||
post.setCreatedAt(LocalDateTime.now());
|
||
|
||
// 保存动态到数据库
|
||
postMapper.insert(post);
|
||
return post;
|
||
}
|
||
|
||
/**
|
||
* 批量删除动态(将动态状态改为已删除)
|
||
*
|
||
* @param postIds 动态ID
|
||
* @return 删除的动态对象
|
||
*/
|
||
@Override
|
||
@Transactional(rollbackFor = Exception.class)
|
||
public int deletePostById(List<Long> postIds) {
|
||
// 判断用户权限
|
||
Long userId = UserContext.getUserId();
|
||
|
||
if (CollectionUtils.isEmpty(postIds)) {
|
||
return 0;
|
||
}
|
||
int affected = postMapper.updatePublicById(postIds, userId);
|
||
|
||
if (affected == 0) {
|
||
throw new RuntimeException("未删除任何动态,可能无权限或动态不存在");
|
||
}
|
||
|
||
// 删除动态下的评论、点赞、收藏
|
||
commentsMapper.deleteCommentsByPostIds(postIds);
|
||
postLikeMapper.deleteLikesByPostIds(postIds);
|
||
postFavoriteMapper.deleteFavoritesByPostIds(postIds);
|
||
|
||
return affected;
|
||
}
|
||
|
||
/**
|
||
* 查询动态详情(用于编辑)
|
||
*
|
||
* @param postId 动态ID
|
||
* @return 动态详情
|
||
*/
|
||
@Override
|
||
public PostEditVO getPostForEdit(Long postId) {
|
||
Post post = postMapper.selectById(postId);
|
||
if (post == null) {
|
||
throw new RuntimeException("动态不存在");
|
||
}
|
||
// 判断用户权限
|
||
Long userId = UserContext.getUserId();
|
||
if (post.getUserId() == null || !post.getUserId().equals(userId)){
|
||
throw new RuntimeException("无权限查看此动态");
|
||
}
|
||
PostEditVO postEditVO = new PostEditVO();
|
||
BeanUtils.copyProperties(post, postEditVO);
|
||
return postEditVO;
|
||
}
|
||
|
||
/**
|
||
* 修改动态
|
||
* @param postId 动态ID
|
||
* @param postRequestDTO 修改的动态数据传输对象
|
||
* @return 修改的动态对象
|
||
*/
|
||
@Override
|
||
public PostEditVO updatePost(Long postId, PostRequestDTO postRequestDTO) {
|
||
// 查询动态
|
||
Post post = postMapper.selectById(postId);
|
||
if (post == null) {
|
||
throw new RuntimeException("动态不存在");
|
||
}
|
||
// 判断用户权限
|
||
Long userId = UserContext.getUserId();
|
||
if (post.getUserId() == null || !post.getUserId().equals(userId)){
|
||
throw new RuntimeException("无权限修改此动态");
|
||
}
|
||
post.setContent(postRequestDTO.getContent());
|
||
// 如果请求中的mediaOssKeys不为null(即使是空列表),则更新为新值
|
||
post.setMediaOssKeys(postRequestDTO.getMediaOssKeys());
|
||
|
||
// 1. 文本内容审核
|
||
Map textResult;
|
||
try {
|
||
textResult = greenTextScan.greeTextScan(postRequestDTO.getContent());
|
||
} catch (Exception e) {
|
||
throw new RuntimeException("文本审核失败");
|
||
}
|
||
// 文本审核结果
|
||
String textSuggestion = (String) textResult.get("suggestion");
|
||
|
||
// 2. 图片审核(如果有媒体文件)
|
||
if (post.getMediaOssKeys() != null && !post.getMediaOssKeys().isEmpty()) {
|
||
Map imageResult;
|
||
try {
|
||
imageResult = greenImageScan.imageScan(post.getMediaOssKeys());
|
||
} catch (Exception e) {
|
||
throw new RuntimeException(e);
|
||
}
|
||
// 图片审核结果
|
||
String imageSuggestion = (String) imageResult.get("suggestion");
|
||
|
||
// 根据审核结果设置状态
|
||
if (GreenAuditResult.BLOCK.equals(textSuggestion) || GreenAuditResult.BLOCK.equals(imageSuggestion)) {
|
||
// 审核未通过,允许用户修改
|
||
post.setIsPublic(2);
|
||
} else if (GreenAuditResult.REVIEW.equals(textSuggestion) || GreenAuditResult.REVIEW.equals(imageSuggestion)) {
|
||
// 待审核,需人工审核
|
||
post.setIsPublic(1);
|
||
} else {
|
||
// 审核通过
|
||
post.setIsPublic(0);
|
||
}
|
||
} else {
|
||
// 只有文本内容的情况
|
||
if (GreenAuditResult.BLOCK.equals(textSuggestion)) {
|
||
// 审核未通过,允许用户修改
|
||
post.setIsPublic(2);
|
||
} else if (GreenAuditResult.REVIEW.equals(textSuggestion)) {
|
||
// 待审核,需人工审核
|
||
post.setIsPublic(1);
|
||
} else {
|
||
// 审核通过
|
||
post.setIsPublic(0);
|
||
}
|
||
}
|
||
post.setTags(postRequestDTO.getTags());
|
||
post.setUpdatedAt(LocalDateTime.now());
|
||
|
||
// 更新动态
|
||
postMapper.updateById(post);
|
||
|
||
// 返回动态详情
|
||
PostEditVO postEditVO = new PostEditVO();
|
||
BeanUtils.copyProperties(post, postEditVO);
|
||
return postEditVO;
|
||
}
|
||
|
||
/**
|
||
* 根据动态ID查询用户ID
|
||
*
|
||
* @param postId 动态ID
|
||
* @return 用户ID
|
||
*/
|
||
@Override
|
||
public Long selectUserIdByPostId(Long postId) {
|
||
return postMapper.selectUserIdByPostId(postId);
|
||
}
|
||
|
||
/**
|
||
* 下载动态图片并添加水印
|
||
*
|
||
* @param postId 动态ID
|
||
* @return 带水印的图片
|
||
* @throws Exception
|
||
*/
|
||
@Override
|
||
public BufferedImage downloadWithWatermark(Long postId) throws Exception {
|
||
// 一次性查出 动态图片 + 作者信息
|
||
Map<String, Object> map = postMapper.getUsernameByUserId(postId);
|
||
|
||
if (map == null || map.isEmpty()) {
|
||
Post post = postMapper.selectById(postId);
|
||
if (post == null) {
|
||
throw new RuntimeException("未找到指定postId的帖子: " + postId);
|
||
}
|
||
throw new RuntimeException("未找到与postId相关的用户和媒体信息: " + postId);
|
||
}
|
||
|
||
String mediaUrl = (String) map.get("media_oss_keys");
|
||
String username = (String) map.get("user_name");
|
||
Object userIdObj = map.get("user_id");
|
||
|
||
if (mediaUrl == null || username == null || userIdObj == null) {
|
||
throw new RuntimeException("用户或媒体信息不完整: " + map);
|
||
}
|
||
|
||
mediaUrl = mediaUrl.trim();
|
||
if (mediaUrl.isEmpty()) {
|
||
throw new RuntimeException("媒体URL为空,postId: " + postId);
|
||
}
|
||
|
||
Long userId = userIdObj instanceof Number
|
||
? ((Number) userIdObj).longValue()
|
||
: Long.valueOf(userIdObj.toString());
|
||
|
||
// 解析 OSS ObjectKey(支持完整URL和直接存key两种)
|
||
String cleanUrl = mediaUrl.split("\\?")[0]; // 去掉 ? 后面的参数
|
||
String objectKey;
|
||
|
||
if (cleanUrl.startsWith("http")) {
|
||
// https://xxx.oss-cn-xxx.aliyuncs.com/post/xxx.jpg → post/xxx.jpg
|
||
objectKey = cleanUrl.substring(cleanUrl.indexOf(".com/") + 5);
|
||
} else {
|
||
objectKey = cleanUrl;
|
||
}
|
||
|
||
if (objectKey.trim().isEmpty()) {
|
||
throw new RuntimeException("解析后的ObjectKey为空,url: " + mediaUrl);
|
||
}
|
||
|
||
// 下载并动态加水印(只给下载的人看,OSS原图不改,数据库不动)
|
||
return watermarkUtil.downloadAndWatermark(objectKey, username, userId);
|
||
}
|
||
|
||
|
||
} |