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 uploadMedia(MultipartFile[] files) { // 如果没有文件,则返回空列表 if (files == null || files.length == 0) { return Collections.emptyList(); } // 创建媒体文件列表 List 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 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 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); } }