Merge origin/123321 into master

This commit is contained in:
bao
2026-01-19 08:25:02 +08:00
10 changed files with 190 additions and 4 deletions

View File

@@ -0,0 +1,16 @@
package com.bao.dating.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
@ConfigurationProperties(prefix = "aliyun.oss")
public class AliyunOSSConfig {
private String endpoint;
private String accessKeyId;
private String accessKeySecret;
private String bucketName;
}

View File

@@ -34,8 +34,8 @@ public class WebConfig implements WebMvcConfigurer {
"/user/emailLogin",
"/api/verification/send-email-code",
"/ip/location",
"/user/login",
"/user/sendCode"
"/user/sendCode",
"/download/{postId}"
);
}
}

View File

@@ -12,6 +12,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.imageio.ImageIO;
import javax.servlet.http.HttpServletResponse;
import java.awt.image.BufferedImage;
import java.util.List;
/**
@@ -83,4 +86,20 @@ public class PostController {
PostEditVO result = postService.updatePost(postId, postRequestDTO);
return Result.success(ResultCode.SUCCESS, "动态更新成功", result);
}
@GetMapping("/download/{postId}")
public void downloadPostImage(@PathVariable Long postId, HttpServletResponse response) throws Exception {
try {
//Service 返回已经加好水印的图片
BufferedImage image = postService.downloadWithWatermark(postId);
//设置响应头,触发浏览器下载
response.setContentType("image/jpeg");
response.setHeader("Content-Disposition", "attachment;filename=post_" + postId + ".jpg");
//输出到浏览器
ImageIO.write(image, "jpg", response.getOutputStream());
response.getOutputStream().flush();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -6,6 +6,7 @@ import com.bao.dating.common.ResultCode;
import com.bao.dating.context.UserContext;
import com.bao.dating.pojo.dto.UserInfoDTO;
import com.bao.dating.pojo.dto.UserLoginDTO;
import com.bao.dating.pojo.entity.User;
import com.bao.dating.pojo.vo.UserInfoVO;
import com.bao.dating.pojo.vo.UserLoginVO;
import com.bao.dating.service.UserService;

View File

@@ -5,6 +5,7 @@ import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
/**
* 动态Mapper
@@ -117,4 +118,12 @@ public interface PostMapper {
* @return 影响行数
*/
int decreaseFavoriteCount(Long postId);
/**
* 根据动态id查询用户名和媒体信息
*
* @param postId 动态id
* @return 用户名和媒体信息
*/
Map<String, Object> getUsernameByUserId(Long postId);
}

View File

@@ -5,6 +5,7 @@ import com.bao.dating.pojo.entity.Post;
import com.bao.dating.pojo.vo.PostEditVO;
import org.springframework.web.multipart.MultipartFile;
import java.awt.image.BufferedImage;
import java.util.List;
/**
@@ -55,4 +56,11 @@ public interface PostService {
* @return 用户id
*/
Long selectUserIdByPostId(Long postId);
/**
* 下载动态图片并添加水印
* @param postId 动态ID
* @return 带水印的图片
*/
BufferedImage downloadWithWatermark(Long postId) throws Exception;
}

View File

@@ -10,11 +10,13 @@ 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;
@@ -22,6 +24,7 @@ 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;
@@ -57,6 +60,9 @@ public class PostServiceImpl implements PostService {
@Autowired
private CommentsMapper commentsMapper;
@Autowired
private WatermarkUtil watermarkUtil;
/**
* 上传媒体文件
* @param files 媒体文件数组
@@ -344,4 +350,61 @@ public class PostServiceImpl implements PostService {
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);
}
}

View File

@@ -0,0 +1,64 @@
package com.bao.dating.util;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.bao.dating.config.AliyunOSSConfig;
import com.bao.dating.mapper.PostMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.InputStream;
@Component
public class WatermarkUtil {
@Autowired
private AliyunOSSConfig aliyunOSSConfig;
public BufferedImage downloadAndWatermark(String objectKey, String username, Long userId) throws Exception {
OSS ossClient = new OSSClientBuilder().build(
aliyunOSSConfig.getEndpoint(),
aliyunOSSConfig.getAccessKeyId(),
aliyunOSSConfig.getAccessKeySecret()
);
InputStream inputStream = ossClient.getObject(aliyunOSSConfig.getBucketName(), objectKey).getObjectContent();
BufferedImage image = ImageIO.read(inputStream);
Graphics2D g2d = image.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
// 字体小一点
Font font = new Font("微软雅黑", Font.BOLD, 24);
g2d.setFont(font);
String text = "作者:" + username + " (ID:" + userId + ")";
FontMetrics fm = g2d.getFontMetrics();
int textWidth = fm.stringWidth(text);
int textHeight = fm.getHeight();
// 右下角留边距
int x = image.getWidth() - textWidth - 20;
int y = image.getHeight() - 20;
// 黑色描边
g2d.setColor(Color.BLACK);
g2d.drawString(text, x - 1, y - 1);
g2d.drawString(text, x + 1, y - 1);
g2d.drawString(text, x - 1, y + 1);
g2d.drawString(text, x + 1, y + 1);
// 白色正文
g2d.setColor(Color.WHITE);
g2d.drawString(text, x, y);
g2d.dispose();
ossClient.shutdown();
return image;
}
}

View File

@@ -135,5 +135,11 @@
<select id="selectFavoriteCount" resultType="java.lang.Integer">
select dating.post.favorite_count from dating.post where post.post_id = #{postId}
</select>
<select id="getUsernameByUserId" resultType="map">
SELECT u.user_name, u.user_id, p.media_oss_keys
FROM post p
LEFT JOIN user u ON p.user_id = u.user_id
WHERE p.post_id = #{postId}
</select>
</mapper>