下载图片+水印(作者ID+作者名)
This commit is contained in:
16
src/main/java/com/bao/dating/config/AliyunOSSConfig.java
Normal file
16
src/main/java/com/bao/dating/config/AliyunOSSConfig.java
Normal 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;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -30,7 +30,8 @@ public class WebConfig implements WebMvcConfigurer {
|
|||||||
// 忽略的接口
|
// 忽略的接口
|
||||||
.excludePathPatterns(
|
.excludePathPatterns(
|
||||||
"/user/login",
|
"/user/login",
|
||||||
"/user/sendCode"
|
"/user/sendCode",
|
||||||
|
"/download/{postId}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ import org.springframework.beans.factory.annotation.Autowired;
|
|||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -87,4 +90,20 @@ public class PostController {
|
|||||||
PostEditVO result = postService.updatePost(postId, postRequestDTO);
|
PostEditVO result = postService.updatePost(postId, postRequestDTO);
|
||||||
return Result.success(ResultCode.SUCCESS, "动态更新成功", result);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -6,6 +6,7 @@ import com.bao.dating.common.ResultCode;
|
|||||||
import com.bao.dating.context.UserContext;
|
import com.bao.dating.context.UserContext;
|
||||||
import com.bao.dating.pojo.dto.UserInfoUpdateDTO;
|
import com.bao.dating.pojo.dto.UserInfoUpdateDTO;
|
||||||
import com.bao.dating.pojo.dto.UserLoginDTO;
|
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.UserInfoVO;
|
||||||
import com.bao.dating.pojo.vo.UserLoginVO;
|
import com.bao.dating.pojo.vo.UserLoginVO;
|
||||||
import com.bao.dating.service.UserService;
|
import com.bao.dating.service.UserService;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import org.apache.ibatis.annotations.Mapper;
|
|||||||
import org.apache.ibatis.annotations.Param;
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 动态Mapper
|
* 动态Mapper
|
||||||
@@ -95,4 +96,12 @@ public interface PostMapper {
|
|||||||
* @return 影响行数
|
* @return 影响行数
|
||||||
*/
|
*/
|
||||||
int decreaseFavoriteCount(Long postId);
|
int decreaseFavoriteCount(Long postId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据动态id查询用户名和媒体信息
|
||||||
|
*
|
||||||
|
* @param postId 动态id
|
||||||
|
* @return 用户名和媒体信息
|
||||||
|
*/
|
||||||
|
Map<String, Object> getUsernameByUserId(Long postId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ public class User implements Serializable {
|
|||||||
|
|
||||||
private String userPhone;
|
private String userPhone;
|
||||||
|
|
||||||
private Double latitude;
|
private Double latitude; // 纬度
|
||||||
|
|
||||||
private Double longitude;
|
private Double longitude; // 经度
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.bao.dating.pojo.entity.Post;
|
|||||||
import com.bao.dating.pojo.vo.PostEditVO;
|
import com.bao.dating.pojo.vo.PostEditVO;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -55,4 +56,11 @@ public interface PostService {
|
|||||||
* @return 用户id
|
* @return 用户id
|
||||||
*/
|
*/
|
||||||
Long selectUserIdByPostId(Long postId);
|
Long selectUserIdByPostId(Long postId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下载动态图片并添加水印
|
||||||
|
* @param postId 动态ID
|
||||||
|
* @return 带水印的图片
|
||||||
|
*/
|
||||||
|
BufferedImage downloadWithWatermark(Long postId) throws Exception;
|
||||||
}
|
}
|
||||||
@@ -10,11 +10,13 @@ import com.bao.dating.mapper.PostLikeMapper;
|
|||||||
import com.bao.dating.mapper.PostMapper;
|
import com.bao.dating.mapper.PostMapper;
|
||||||
import com.bao.dating.pojo.dto.PostRequestDTO;
|
import com.bao.dating.pojo.dto.PostRequestDTO;
|
||||||
import com.bao.dating.pojo.entity.Post;
|
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.pojo.vo.PostEditVO;
|
||||||
import com.bao.dating.service.PostService;
|
import com.bao.dating.service.PostService;
|
||||||
import com.bao.dating.common.aliyun.AliOssUtil;
|
import com.bao.dating.common.aliyun.AliOssUtil;
|
||||||
import com.bao.dating.service.UserService;
|
import com.bao.dating.service.UserService;
|
||||||
import com.bao.dating.util.FileUtil;
|
import com.bao.dating.util.FileUtil;
|
||||||
|
import com.bao.dating.util.WatermarkUtil;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
@@ -22,6 +24,7 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
@@ -57,6 +60,9 @@ public class PostServiceImpl implements PostService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private CommentsMapper commentsMapper;
|
private CommentsMapper commentsMapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private WatermarkUtil watermarkUtil;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 上传媒体文件
|
* 上传媒体文件
|
||||||
* @param files 媒体文件数组
|
* @param files 媒体文件数组
|
||||||
@@ -331,4 +337,61 @@ public class PostServiceImpl implements PostService {
|
|||||||
return postMapper.selectUserIdByPostId(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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
64
src/main/java/com/bao/dating/util/WatermarkUtil.java
Normal file
64
src/main/java/com/bao/dating/util/WatermarkUtil.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -122,5 +122,11 @@
|
|||||||
<select id="selectFavoriteCount" resultType="java.lang.Integer">
|
<select id="selectFavoriteCount" resultType="java.lang.Integer">
|
||||||
select dating.post.favorite_count from dating.post where post.post_id = #{postId}
|
select dating.post.favorite_count from dating.post where post.post_id = #{postId}
|
||||||
</select>
|
</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>
|
</mapper>
|
||||||
@@ -5,22 +5,6 @@
|
|||||||
<mapper namespace="com.bao.dating.mapper.UserMapper">
|
<mapper namespace="com.bao.dating.mapper.UserMapper">
|
||||||
|
|
||||||
<!--根据用户名查询用户-->
|
<!--根据用户名查询用户-->
|
||||||
<resultMap id="UserInfoVOResultMap" type="com.bao.dating.pojo.vo.UserInfoVO">
|
|
||||||
<id property="userId" column="user_id"/>
|
|
||||||
<result property="userName" column="user_name"/>
|
|
||||||
<result property="nickname" column="nickname"/>
|
|
||||||
<result property="avatarUrl" column="avatar_url"/>
|
|
||||||
<result property="backgroundUrl" column="background_url"/>
|
|
||||||
<result property="gender" column="gender"/>
|
|
||||||
<result property="birthday" column="birthday"/>
|
|
||||||
<result property="hobbies" column="hobbies" typeHandler="com.bao.dating.handler.ListToJsonTypeHandler"/>
|
|
||||||
<result property="signature" column="signature"/>
|
|
||||||
<result property="createdAt" column="created_at"/>
|
|
||||||
<result property="updatedAt" column="updated_at"/>
|
|
||||||
<result property="latitude" column="user_latitude"/>
|
|
||||||
<result property="longitude" column="user_longitude"/>
|
|
||||||
</resultMap>
|
|
||||||
|
|
||||||
<select id="getByUsername" resultType="com.bao.dating.pojo.entity.User">
|
<select id="getByUsername" resultType="com.bao.dating.pojo.entity.User">
|
||||||
SELECT
|
SELECT
|
||||||
user_id,
|
user_id,
|
||||||
@@ -44,6 +28,8 @@
|
|||||||
<result property="signature" column="signature"/>
|
<result property="signature" column="signature"/>
|
||||||
<result property="createdAt" column="created_at"/>
|
<result property="createdAt" column="created_at"/>
|
||||||
<result property="updatedAt" column="updated_at"/>
|
<result property="updatedAt" column="updated_at"/>
|
||||||
|
<result property="latitude" column="user_latitude"/>
|
||||||
|
<result property="longitude" column="user_longitude"/>
|
||||||
</resultMap>
|
</resultMap>
|
||||||
<select id="selectByUserId" resultMap="UserResultMap">
|
<select id="selectByUserId" resultMap="UserResultMap">
|
||||||
SELECT
|
SELECT
|
||||||
@@ -60,7 +46,7 @@
|
|||||||
updated_at,
|
updated_at,
|
||||||
user_latitude,
|
user_latitude,
|
||||||
user_longitude
|
user_longitude
|
||||||
FROM dating.user WHERE user_id = #{userId}
|
FROM dating.user WHERE user_id = #{userId}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<!--根据ID更新动态-->
|
<!--根据ID更新动态-->
|
||||||
@@ -96,6 +82,22 @@
|
|||||||
<select id="selectByPhone" resultType="com.bao.dating.pojo.entity.User">
|
<select id="selectByPhone" resultType="com.bao.dating.pojo.entity.User">
|
||||||
select * from dating.user where user_phone =#{phone}
|
select * from dating.user where user_phone =#{phone}
|
||||||
</select>
|
</select>
|
||||||
|
<resultMap id="UserInfoVOResultMap" type="com.bao.dating.pojo.vo.UserInfoVO">
|
||||||
|
<id property="userId" column="user_id"/>
|
||||||
|
<result property="userName" column="user_name"/>
|
||||||
|
<result property="nickname" column="nickname"/>
|
||||||
|
<result property="avatarUrl" column="avatar_url"/>
|
||||||
|
<result property="backgroundUrl" column="background_url"/>
|
||||||
|
<result property="gender" column="gender"/>
|
||||||
|
<result property="birthday" column="birthday"/>
|
||||||
|
<result property="hobbies" column="hobbies" typeHandler="com.bao.dating.handler.ListToJsonTypeHandler"/>
|
||||||
|
<result property="signature" column="signature"/>
|
||||||
|
<result property="createdAt" column="created_at"/>
|
||||||
|
<result property="updatedAt" column="updated_at"/>
|
||||||
|
<result property="latitude" column="user_latitude"/>
|
||||||
|
<result property="longitude" column="user_longitude"/>
|
||||||
|
</resultMap>
|
||||||
|
|
||||||
<select id="findByLatLngRange" resultMap="UserInfoVOResultMap">
|
<select id="findByLatLngRange" resultMap="UserInfoVOResultMap">
|
||||||
SELECT
|
SELECT
|
||||||
user_id,
|
user_id,
|
||||||
@@ -113,4 +115,6 @@
|
|||||||
user_longitude
|
user_longitude
|
||||||
FROM user WHERE user_latitude BETWEEN #{minLat} AND #{maxLat} AND user_longitude BETWEEN #{minLng} AND #{maxLng}
|
FROM user WHERE user_latitude BETWEEN #{minLat} AND #{maxLat} AND user_longitude BETWEEN #{minLng} AND #{maxLng}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
|
||||||
</mapper>
|
</mapper>
|
||||||
Reference in New Issue
Block a user