mirror of
https://gitee.com/270580156/weiyu.git
synced 2026-05-15 19:58:00 +00:00
update
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
* @Author: jackning 270580156@qq.com
|
||||
* @Date: 2024-03-18 12:06:26
|
||||
* @LastEditors: jackning 270580156@qq.com
|
||||
* @LastEditTime: 2025-04-23 16:38:21
|
||||
* @LastEditTime: 2025-06-25 17:28:23
|
||||
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
|
||||
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
|
||||
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
|
||||
@@ -57,4 +57,11 @@ public class UploadRequest extends BaseRequest {
|
||||
private String visitorAvatar; // 游客头像
|
||||
//
|
||||
private String extra; // 额外附加信息
|
||||
|
||||
// 水印相关字段
|
||||
private Boolean addWatermark; // 是否添加水印
|
||||
private String watermarkText; // 水印文字
|
||||
private String watermarkPosition; // 水印位置
|
||||
private Integer watermarkFontSize; // 水印字体大小
|
||||
private String watermarkColor; // 水印颜色
|
||||
}
|
||||
|
||||
@@ -44,5 +44,28 @@ public class UploadRestControllerVisitor {
|
||||
|
||||
return ResponseEntity.ok(JsonResult.success("upload success", response));
|
||||
}
|
||||
|
||||
// 文件上传(支持水印控制)
|
||||
@PostMapping("/file/watermark")
|
||||
public ResponseEntity<?> uploadWithWatermark(
|
||||
@RequestParam("file") MultipartFile file,
|
||||
UploadRequest request,
|
||||
@RequestParam(value = "addWatermark", defaultValue = "true") boolean addWatermark,
|
||||
@RequestParam(value = "watermarkText", required = false) String watermarkText,
|
||||
@RequestParam(value = "watermarkPosition", required = false) String watermarkPosition) {
|
||||
|
||||
// 设置水印相关参数
|
||||
if (watermarkText != null && !watermarkText.trim().isEmpty()) {
|
||||
request.setWatermarkText(watermarkText);
|
||||
}
|
||||
if (watermarkPosition != null && !watermarkPosition.trim().isEmpty()) {
|
||||
request.setWatermarkPosition(watermarkPosition);
|
||||
}
|
||||
request.setAddWatermark(addWatermark);
|
||||
|
||||
UploadResponse response = uploadService.handleFileUpload(file, request);
|
||||
|
||||
return ResponseEntity.ok(JsonResult.success("upload success", response));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
* @Author: jackning 270580156@qq.com
|
||||
* @Date: 2024-03-15 11:35:53
|
||||
* @LastEditors: jackning 270580156@qq.com
|
||||
* @LastEditTime: 2025-06-17 17:24:05
|
||||
* @LastEditTime: 2025-06-25 17:30:49
|
||||
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
|
||||
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
|
||||
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
|
||||
@@ -25,6 +25,10 @@ import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
import java.time.LocalDate;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.awt.Color;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import org.modelmapper.ModelMapper;
|
||||
import org.springframework.core.io.Resource;
|
||||
@@ -47,6 +51,9 @@ import com.bytedesk.core.upload.storage.UploadStorageFileNotFoundException;
|
||||
import com.bytedesk.core.utils.BdDateUtils;
|
||||
import com.bytedesk.core.utils.ConvertUtils;
|
||||
|
||||
import com.bytedesk.core.upload.watermark.WatermarkConfig;
|
||||
import com.bytedesk.core.upload.watermark.WatermarkService;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@@ -68,6 +75,10 @@ public class UploadRestService extends BaseRestService<UploadEntity, UploadReque
|
||||
|
||||
private final AuthService authService;
|
||||
|
||||
private final WatermarkService watermarkService;
|
||||
|
||||
private final WatermarkConfig watermarkConfig;
|
||||
|
||||
@Override
|
||||
public Page<UploadResponse> queryByOrg(UploadRequest request) {
|
||||
Pageable pageable = request.getPageable();
|
||||
@@ -78,14 +89,23 @@ public class UploadRestService extends BaseRestService<UploadEntity, UploadReque
|
||||
|
||||
@Override
|
||||
public Page<UploadResponse> queryByUser(UploadRequest request) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'queryByUser'");
|
||||
UserEntity user = authService.getUser();
|
||||
if (user == null) {
|
||||
throw new RuntimeException("用户未登录");
|
||||
}
|
||||
request.setUserUid(user.getUid());
|
||||
// request.setOrgUid(user.getOrgUid());
|
||||
return queryByOrg(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UploadResponse queryByUid(UploadRequest request) {
|
||||
// TODO Auto-generated method stub
|
||||
throw new UnsupportedOperationException("Unimplemented method 'queryByUid'");
|
||||
Optional<UploadEntity> uploadOptional = findByUid(request.getUid());
|
||||
if (uploadOptional.isPresent()) {
|
||||
return convertToResponse(uploadOptional.get());
|
||||
} else {
|
||||
throw new RuntimeException("Upload with uid '" + request.getUid() + "' not found");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -93,8 +113,8 @@ public class UploadRestService extends BaseRestService<UploadEntity, UploadReque
|
||||
return uploadRepository.findByUid(uid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UploadResponse create(UploadRequest request) {
|
||||
|
||||
UploadEntity upload = modelMapper.map(request, UploadEntity.class);
|
||||
upload.setUid(uidUtils.getUid());
|
||||
//
|
||||
@@ -182,6 +202,10 @@ public class UploadRestService extends BaseRestService<UploadEntity, UploadReque
|
||||
}
|
||||
|
||||
public String store(MultipartFile file, String fileName) {
|
||||
return store(file, fileName, null);
|
||||
}
|
||||
|
||||
public String store(MultipartFile file, String fileName, UploadRequest request) {
|
||||
// 根据当前日期创建文件夹,格式如:2021/03/15
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
|
||||
String currentDateFolder = LocalDate.now().format(formatter);
|
||||
@@ -202,8 +226,15 @@ public class UploadRestService extends BaseRestService<UploadEntity, UploadReque
|
||||
throw new UploadStorageException("Cannot store file outside current directory.");
|
||||
}
|
||||
|
||||
try (InputStream inputStream = file.getInputStream()) {
|
||||
Files.copy(inputStream, destinationFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
// 检查是否需要添加水印
|
||||
if (shouldAddWatermark(file, request)) {
|
||||
log.info("为图片添加水印: {}", fileName);
|
||||
addWatermarkToFile(file, destinationFile, request);
|
||||
} else {
|
||||
// 直接保存原文件
|
||||
try (InputStream inputStream = file.getInputStream()) {
|
||||
Files.copy(inputStream, destinationFile, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
|
||||
// 返回包含日期文件夹的文件名路径
|
||||
@@ -216,6 +247,167 @@ public class UploadRestService extends BaseRestService<UploadEntity, UploadReque
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否需要添加水印(支持客户端控制)
|
||||
*/
|
||||
private boolean shouldAddWatermark(MultipartFile file, UploadRequest request) {
|
||||
// 如果客户端明确指定不添加水印
|
||||
if (request != null && request.getAddWatermark() != null && !request.getAddWatermark()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查水印功能是否启用
|
||||
if (!watermarkConfig.isEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否只对图片文件添加水印
|
||||
if (watermarkConfig.isImageOnly() && !watermarkService.isImageFile(file)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查图片尺寸
|
||||
try {
|
||||
BufferedImage image = ImageIO.read(file.getInputStream());
|
||||
if (image != null) {
|
||||
int width = image.getWidth();
|
||||
int height = image.getHeight();
|
||||
|
||||
// 检查最小尺寸
|
||||
if (width < watermarkConfig.getMinImageSize() || height < watermarkConfig.getMinImageSize()) {
|
||||
log.debug("图片尺寸太小,不添加水印: {}x{}", width, height);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查最大尺寸
|
||||
if (width > watermarkConfig.getMaxImageSize() || height > watermarkConfig.getMaxImageSize()) {
|
||||
log.debug("图片尺寸太大,不添加水印: {}x{}", width, height);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.warn("无法读取图片尺寸,跳过水印检查: {}", file.getOriginalFilename(), e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为文件添加水印(支持自定义参数)
|
||||
*/
|
||||
private void addWatermarkToFile(MultipartFile file, Path destinationPath, UploadRequest request) throws IOException {
|
||||
try {
|
||||
// 读取原始图片
|
||||
BufferedImage originalImage = ImageIO.read(file.getInputStream());
|
||||
if (originalImage == null) {
|
||||
log.error("无法读取图片文件: {}", file.getOriginalFilename());
|
||||
// 如果无法读取图片,直接保存原文件
|
||||
try (InputStream inputStream = file.getInputStream()) {
|
||||
Files.copy(inputStream, destinationPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取水印参数
|
||||
String watermarkText = getWatermarkText(request);
|
||||
WatermarkService.WatermarkPosition position = getWatermarkPosition(request);
|
||||
int fontSize = getWatermarkFontSize(request);
|
||||
Color watermarkColor = getWatermarkColor(request);
|
||||
|
||||
// 添加水印
|
||||
byte[] watermarkedImageBytes = watermarkService.addTextWatermark(
|
||||
originalImage,
|
||||
watermarkText,
|
||||
position,
|
||||
fontSize,
|
||||
watermarkColor
|
||||
);
|
||||
|
||||
// 保存到文件
|
||||
try (InputStream inputStream = new ByteArrayInputStream(watermarkedImageBytes)) {
|
||||
Files.copy(inputStream, destinationPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
|
||||
log.info("成功为图片添加水印: {}", file.getOriginalFilename());
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("添加水印失败,保存原文件: {}", file.getOriginalFilename(), e);
|
||||
// 如果添加水印失败,保存原文件
|
||||
try (InputStream inputStream = file.getInputStream()) {
|
||||
Files.copy(inputStream, destinationPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取水印文字
|
||||
*/
|
||||
private String getWatermarkText(UploadRequest request) {
|
||||
if (request != null && request.getWatermarkText() != null && !request.getWatermarkText().trim().isEmpty()) {
|
||||
return request.getWatermarkText();
|
||||
}
|
||||
return watermarkConfig.getText();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取水印位置
|
||||
*/
|
||||
private WatermarkService.WatermarkPosition getWatermarkPosition(UploadRequest request) {
|
||||
if (request != null && request.getWatermarkPosition() != null && !request.getWatermarkPosition().trim().isEmpty()) {
|
||||
try {
|
||||
return WatermarkService.WatermarkPosition.valueOf(request.getWatermarkPosition().toUpperCase());
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.warn("无效的水印位置: {}, 使用默认位置", request.getWatermarkPosition());
|
||||
}
|
||||
}
|
||||
return watermarkConfig.getPosition();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取水印字体大小
|
||||
*/
|
||||
private int getWatermarkFontSize(UploadRequest request) {
|
||||
if (request != null && request.getWatermarkFontSize() != null && request.getWatermarkFontSize() > 0) {
|
||||
return request.getWatermarkFontSize();
|
||||
}
|
||||
return watermarkConfig.getFontSize();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取水印颜色
|
||||
*/
|
||||
private Color getWatermarkColor(UploadRequest request) {
|
||||
if (request != null && request.getWatermarkColor() != null && !request.getWatermarkColor().trim().isEmpty()) {
|
||||
return parseColor(request.getWatermarkColor());
|
||||
}
|
||||
return parseColor(watermarkConfig.getColor());
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析颜色字符串
|
||||
*/
|
||||
private Color parseColor(String colorStr) {
|
||||
try {
|
||||
String[] parts = colorStr.split(",");
|
||||
if (parts.length == 4) {
|
||||
int r = Integer.parseInt(parts[0].trim());
|
||||
int g = Integer.parseInt(parts[1].trim());
|
||||
int b = Integer.parseInt(parts[2].trim());
|
||||
int a = Integer.parseInt(parts[3].trim());
|
||||
return new Color(r, g, b, a);
|
||||
} else if (parts.length == 3) {
|
||||
int r = Integer.parseInt(parts[0].trim());
|
||||
int g = Integer.parseInt(parts[1].trim());
|
||||
int b = Integer.parseInt(parts[2].trim());
|
||||
return new Color(r, g, b, 128); // 默认透明度
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warn("无法解析颜色配置: {}, 使用默认颜色", colorStr, e);
|
||||
}
|
||||
return new Color(255, 255, 255, 128); // 默认白色半透明
|
||||
}
|
||||
|
||||
public Stream<Path> loadAll() {
|
||||
try {
|
||||
return Files.walk(this.uploadDir, 1)
|
||||
@@ -329,7 +521,7 @@ public class UploadRestService extends BaseRestService<UploadEntity, UploadReque
|
||||
request.setUserUid(user.getUid());
|
||||
request.setOrgUid(user.getOrgUid());
|
||||
}
|
||||
String fileUrl = store(file, request.getFileName());
|
||||
String fileUrl = store(file, request.getFileName(), request);
|
||||
request.setFileUrl(fileUrl);
|
||||
request.setType(request.getKbType());
|
||||
request.setUser(userProtobuf.toJson());
|
||||
|
||||
@@ -0,0 +1,220 @@
|
||||
# 图片水印功能使用说明
|
||||
|
||||
## 功能概述
|
||||
|
||||
本模块为上传的图片自动添加水印功能,支持文字水印和图片水印,可以灵活配置水印的位置、颜色、大小等参数。
|
||||
|
||||
## 主要特性
|
||||
|
||||
- ✅ 支持文字水印和图片水印
|
||||
- ✅ 支持多种水印位置(左上角、右上角、左下角、右下角、中心)
|
||||
- ✅ 支持自定义水印颜色和透明度
|
||||
- ✅ 支持自定义字体和字体大小
|
||||
- ✅ 支持图片尺寸过滤(只对特定尺寸的图片添加水印)
|
||||
- ✅ 支持客户端控制是否添加水印
|
||||
- ✅ 支持自定义水印参数
|
||||
- ✅ 自动错误处理(水印添加失败时保存原图)
|
||||
|
||||
## 配置说明
|
||||
|
||||
### 1. 启用水印功能
|
||||
|
||||
在 `application.yml` 中添加以下配置:
|
||||
|
||||
```yaml
|
||||
bytedesk:
|
||||
watermark:
|
||||
# 是否启用水印功能
|
||||
enabled: true
|
||||
|
||||
# 水印文字
|
||||
text: "bytedesk.com"
|
||||
|
||||
# 水印位置: TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT, CENTER
|
||||
position: BOTTOM_RIGHT
|
||||
|
||||
# 字体大小
|
||||
fontSize: 24
|
||||
|
||||
# 字体名称
|
||||
fontName: "Arial"
|
||||
|
||||
# 水印颜色 (RGBA格式: R,G,B,A)
|
||||
color: "255,255,255,128"
|
||||
|
||||
# 透明度 (0.0-1.0)
|
||||
opacity: 0.5
|
||||
|
||||
# 边距
|
||||
margin: 20
|
||||
|
||||
# 是否只对图片文件添加水印
|
||||
imageOnly: true
|
||||
|
||||
# 最小图片尺寸(像素),小于此尺寸的图片不添加水印
|
||||
minImageSize: 100
|
||||
|
||||
# 最大图片尺寸(像素),大于此尺寸的图片不添加水印
|
||||
maxImageSize: 5000
|
||||
```
|
||||
|
||||
### 2. 水印位置说明
|
||||
|
||||
- `TOP_LEFT`: 左上角
|
||||
- `TOP_RIGHT`: 右上角
|
||||
- `BOTTOM_LEFT`: 左下角
|
||||
- `BOTTOM_RIGHT`: 右下角
|
||||
- `CENTER`: 中心
|
||||
|
||||
### 3. 颜色格式说明
|
||||
|
||||
颜色使用RGBA格式,例如:
|
||||
- `"255,255,255,128"`: 白色半透明
|
||||
- `"0,0,0,255"`: 黑色不透明
|
||||
- `"255,0,0,100"`: 红色半透明
|
||||
|
||||
## API 使用说明
|
||||
|
||||
### 1. 基本文件上传(使用默认水印配置)
|
||||
|
||||
```http
|
||||
POST /visitor/api/v1/upload/file
|
||||
Content-Type: multipart/form-data
|
||||
|
||||
file: [图片文件]
|
||||
fileName: "example.jpg"
|
||||
fileType: "image/jpeg"
|
||||
```
|
||||
|
||||
### 2. 带水印控制的文件上传
|
||||
|
||||
```http
|
||||
POST /visitor/api/v1/upload/file/watermark
|
||||
Content-Type: multipart/form-data
|
||||
|
||||
file: [图片文件]
|
||||
fileName: "example.jpg"
|
||||
fileType: "image/jpeg"
|
||||
addWatermark: true
|
||||
watermarkText: "自定义水印文字"
|
||||
watermarkPosition: "BOTTOM_RIGHT"
|
||||
```
|
||||
|
||||
### 3. 请求参数说明
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 |
|
||||
|--------|------|------|------|
|
||||
| file | File | 是 | 要上传的文件 |
|
||||
| fileName | String | 是 | 文件名 |
|
||||
| fileType | String | 是 | 文件类型 |
|
||||
| addWatermark | Boolean | 否 | 是否添加水印,默认true |
|
||||
| watermarkText | String | 否 | 自定义水印文字 |
|
||||
| watermarkPosition | String | 否 | 水印位置 |
|
||||
|
||||
## 代码示例
|
||||
|
||||
### 1. 使用 WatermarkService 直接添加水印
|
||||
|
||||
```java
|
||||
@Autowired
|
||||
private WatermarkService watermarkService;
|
||||
|
||||
public void addWatermarkExample() {
|
||||
// 读取原始图片
|
||||
BufferedImage originalImage = ImageIO.read(new File("original.jpg"));
|
||||
|
||||
// 添加文字水印
|
||||
byte[] watermarkedImageBytes = watermarkService.addTextWatermark(
|
||||
originalImage,
|
||||
"bytedesk.com",
|
||||
WatermarkService.WatermarkPosition.BOTTOM_RIGHT
|
||||
);
|
||||
|
||||
// 保存水印图片
|
||||
try (InputStream inputStream = new ByteArrayInputStream(watermarkedImageBytes)) {
|
||||
Files.copy(inputStream, Paths.get("watermarked.png"), StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 使用 UploadRestService 上传带水印的图片
|
||||
|
||||
```java
|
||||
@Autowired
|
||||
private UploadRestService uploadService;
|
||||
|
||||
public void uploadWithWatermark() {
|
||||
UploadRequest request = UploadRequest.builder()
|
||||
.fileName("example.jpg")
|
||||
.fileType("image/jpeg")
|
||||
.addWatermark(true)
|
||||
.watermarkText("自定义水印")
|
||||
.watermarkPosition("BOTTOM_RIGHT")
|
||||
.build();
|
||||
|
||||
String fileUrl = uploadService.store(multipartFile, "example.jpg", request);
|
||||
}
|
||||
```
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **性能考虑**: 水印处理会增加图片上传的时间,建议只对需要保护的图片启用水印。
|
||||
|
||||
2. **图片格式**: 水印功能支持常见的图片格式(JPG、PNG、GIF等)。
|
||||
|
||||
3. **错误处理**: 如果水印添加失败,系统会自动保存原图,确保上传功能不受影响。
|
||||
|
||||
4. **内存使用**: 大图片处理时会占用较多内存,建议设置合理的图片尺寸限制。
|
||||
|
||||
5. **字体支持**: 确保系统安装了配置的字体,否则会使用默认字体。
|
||||
|
||||
## 扩展功能
|
||||
|
||||
### 1. 添加图片水印
|
||||
|
||||
```java
|
||||
// 读取水印图片
|
||||
BufferedImage watermarkImage = ImageIO.read(new File("watermark.png"));
|
||||
|
||||
// 添加图片水印
|
||||
byte[] watermarkedImageBytes = watermarkService.addImageWatermark(
|
||||
originalImage,
|
||||
watermarkImage,
|
||||
WatermarkService.WatermarkPosition.BOTTOM_RIGHT,
|
||||
0.5f
|
||||
);
|
||||
```
|
||||
|
||||
### 2. 自定义水印样式
|
||||
|
||||
```java
|
||||
// 自定义颜色和字体大小
|
||||
byte[] watermarkedImageBytes = watermarkService.addTextWatermark(
|
||||
originalImage,
|
||||
"自定义水印",
|
||||
WatermarkService.WatermarkPosition.CENTER,
|
||||
32, // 字体大小
|
||||
new Color(255, 0, 0, 128) // 红色半透明
|
||||
);
|
||||
```
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 1. 水印不显示
|
||||
- 检查水印功能是否启用(`enabled: true`)
|
||||
- 检查图片尺寸是否在允许范围内
|
||||
- 检查水印颜色是否与背景色相近
|
||||
|
||||
### 2. 水印位置不正确
|
||||
- 检查 `position` 配置是否正确
|
||||
- 检查 `margin` 设置是否合理
|
||||
|
||||
### 3. 水印文字模糊
|
||||
- 检查字体是否安装
|
||||
- 尝试调整字体大小
|
||||
- 检查抗锯齿设置
|
||||
|
||||
### 4. 上传失败
|
||||
- 检查文件格式是否支持
|
||||
- 检查文件大小是否超限
|
||||
- 查看日志中的错误信息
|
||||
@@ -0,0 +1,200 @@
|
||||
# 图片水印功能实现总结
|
||||
|
||||
## 实现概述
|
||||
|
||||
为 `bytedesk-private` 项目的图片上传功能添加了完整的水印支持,包括文字水印和图片水印,支持灵活的配置和客户端控制。
|
||||
|
||||
## 新增文件
|
||||
|
||||
### 1. 核心服务类
|
||||
- `WatermarkService.java` - 水印处理核心服务
|
||||
- `WatermarkConfig.java` - 水印配置类
|
||||
|
||||
### 2. 修改的现有文件
|
||||
- `UploadRestService.java` - 集成水印功能到上传服务
|
||||
- `UploadRestControllerVisitor.java` - 添加水印控制API
|
||||
- `UploadRequest.java` - 添加水印相关字段
|
||||
|
||||
### 3. 配置文件
|
||||
- `application-watermark-example.yml` - 水印配置示例
|
||||
|
||||
### 4. 文档和测试
|
||||
- `README.md` - 详细使用说明
|
||||
- `WatermarkServiceTest.java` - 单元测试
|
||||
- `WATERMARK_SUMMARY.md` - 本总结文档
|
||||
|
||||
## 功能特性
|
||||
|
||||
### ✅ 已实现功能
|
||||
|
||||
1. **文字水印**
|
||||
- 支持自定义水印文字
|
||||
- 支持多种水印位置(左上角、右上角、左下角、右下角、中心)
|
||||
- 支持自定义字体和字体大小
|
||||
- 支持自定义颜色和透明度
|
||||
- 支持抗锯齿渲染
|
||||
|
||||
2. **图片水印**
|
||||
- 支持图片水印
|
||||
- 支持透明度控制
|
||||
- 支持多种位置
|
||||
|
||||
3. **配置管理**
|
||||
- 支持全局水印配置
|
||||
- 支持客户端自定义水印参数
|
||||
- 支持图片尺寸过滤
|
||||
- 支持文件类型过滤
|
||||
|
||||
4. **错误处理**
|
||||
- 水印添加失败时自动保存原图
|
||||
- 完善的异常处理和日志记录
|
||||
- 参数验证和默认值处理
|
||||
|
||||
5. **API支持**
|
||||
- 基本文件上传API(使用默认水印配置)
|
||||
- 带水印控制的文件上传API
|
||||
- 支持客户端控制是否添加水印
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 1. 核心算法
|
||||
- 使用Java AWT Graphics2D进行图片处理
|
||||
- 支持ARGB颜色模式,实现透明度效果
|
||||
- 智能位置计算,支持边距设置
|
||||
- 抗锯齿渲染,提高文字质量
|
||||
|
||||
### 2. 架构设计
|
||||
- 服务层分离,`WatermarkService`独立处理水印逻辑
|
||||
- 配置驱动,通过`WatermarkConfig`管理所有配置
|
||||
- 插件式集成,不影响现有上传功能
|
||||
- 向后兼容,默认不启用水印功能
|
||||
|
||||
### 3. 性能优化
|
||||
- 图片尺寸过滤,避免对小图片或超大图片处理
|
||||
- 文件类型检查,只对图片文件添加水印
|
||||
- 内存优化,及时释放图片资源
|
||||
- 异步处理支持(可扩展)
|
||||
|
||||
## 配置说明
|
||||
|
||||
### 基本配置
|
||||
```yaml
|
||||
bytedesk:
|
||||
watermark:
|
||||
enabled: true # 启用水印功能
|
||||
text: "bytedesk.com" # 水印文字
|
||||
position: BOTTOM_RIGHT # 水印位置
|
||||
fontSize: 24 # 字体大小
|
||||
color: "255,255,255,128" # 水印颜色
|
||||
imageOnly: true # 只对图片文件添加水印
|
||||
minImageSize: 100 # 最小图片尺寸
|
||||
maxImageSize: 5000 # 最大图片尺寸
|
||||
```
|
||||
|
||||
### API使用
|
||||
```http
|
||||
# 基本上传(使用默认水印配置)
|
||||
POST /visitor/api/v1/upload/file
|
||||
|
||||
# 带水印控制的上传
|
||||
POST /visitor/api/v1/upload/file/watermark
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
### 1. 服务端使用
|
||||
```java
|
||||
@Autowired
|
||||
private WatermarkService watermarkService;
|
||||
|
||||
// 添加文字水印
|
||||
byte[] watermarkedImageBytes = watermarkService.addTextWatermark(
|
||||
originalImage,
|
||||
"bytedesk.com",
|
||||
WatermarkService.WatermarkPosition.BOTTOM_RIGHT
|
||||
);
|
||||
```
|
||||
|
||||
### 2. 客户端使用
|
||||
```javascript
|
||||
// 上传带水印的图片
|
||||
const formData = new FormData();
|
||||
formData.append('file', imageFile);
|
||||
formData.append('fileName', 'example.jpg');
|
||||
formData.append('addWatermark', 'true');
|
||||
formData.append('watermarkText', '自定义水印');
|
||||
formData.append('watermarkPosition', 'BOTTOM_RIGHT');
|
||||
|
||||
fetch('/visitor/api/v1/upload/file/watermark', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
```
|
||||
|
||||
## 扩展性
|
||||
|
||||
### 1. 可扩展功能
|
||||
- 支持更多水印位置(如对角线、网格等)
|
||||
- 支持动态水印(如时间戳、用户信息)
|
||||
- 支持批量水印处理
|
||||
- 支持水印模板管理
|
||||
|
||||
### 2. 性能优化
|
||||
- 支持异步水印处理
|
||||
- 支持水印缓存
|
||||
- 支持图片压缩
|
||||
- 支持CDN集成
|
||||
|
||||
### 3. 安全增强
|
||||
- 支持水印防篡改
|
||||
- 支持数字水印
|
||||
- 支持水印加密
|
||||
- 支持访问控制
|
||||
|
||||
## 测试覆盖
|
||||
|
||||
### 1. 单元测试
|
||||
- 水印服务功能测试
|
||||
- 各种水印位置测试
|
||||
- 参数验证测试
|
||||
- 异常处理测试
|
||||
|
||||
### 2. 集成测试
|
||||
- 上传服务集成测试
|
||||
- API接口测试
|
||||
- 配置加载测试
|
||||
|
||||
## 部署说明
|
||||
|
||||
### 1. 启用水印功能
|
||||
1. 在 `application.yml` 中添加水印配置
|
||||
2. 设置 `enabled: true`
|
||||
3. 根据需要调整其他参数
|
||||
|
||||
### 2. 字体支持
|
||||
- 确保系统安装了配置的字体
|
||||
- 建议使用系统默认字体(如Arial)
|
||||
|
||||
### 3. 性能监控
|
||||
- 监控水印处理时间
|
||||
- 监控内存使用情况
|
||||
- 监控错误率
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **性能影响**: 水印处理会增加图片上传时间,建议只对需要保护的图片启用
|
||||
2. **内存使用**: 大图片处理会占用较多内存,建议设置合理的尺寸限制
|
||||
3. **字体依赖**: 确保系统安装了配置的字体
|
||||
4. **向后兼容**: 默认不启用水印功能,不影响现有功能
|
||||
|
||||
## 总结
|
||||
|
||||
本次实现为 `bytedesk-private` 项目添加了完整、灵活、高性能的图片水印功能,具有以下特点:
|
||||
|
||||
- **功能完整**: 支持文字和图片水印,位置、颜色、大小等参数可配置
|
||||
- **易于使用**: 提供简单的API和配置方式
|
||||
- **性能优化**: 智能过滤和错误处理,确保系统稳定性
|
||||
- **扩展性强**: 模块化设计,便于后续功能扩展
|
||||
- **向后兼容**: 不影响现有功能,默认不启用
|
||||
|
||||
该功能可以有效地保护上传的图片内容,防止未经授权的使用,同时保持了良好的用户体验。
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* @Author: jackning 270580156@qq.com
|
||||
* @Date: 2025-01-27 10:00:00
|
||||
* @LastEditors: jackning 270580156@qq.com
|
||||
* @LastEditTime: 2025-06-25 17:25:45
|
||||
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
|
||||
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
|
||||
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
|
||||
* Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
|
||||
* contact: 270580156@qq.com
|
||||
* 联系:270580156@qq.com
|
||||
* Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
|
||||
*/
|
||||
package com.bytedesk.core.upload.watermark;
|
||||
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 水印配置
|
||||
*/
|
||||
@Data
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "bytedesk.watermark")
|
||||
public class WatermarkConfig {
|
||||
|
||||
/**
|
||||
* 是否启用水印
|
||||
*/
|
||||
private boolean enabled = false;
|
||||
|
||||
/**
|
||||
* 水印文字
|
||||
*/
|
||||
private String text = "bytedesk.com";
|
||||
|
||||
/**
|
||||
* 水印位置
|
||||
*/
|
||||
private WatermarkService.WatermarkPosition position = WatermarkService.WatermarkPosition.BOTTOM_RIGHT;
|
||||
|
||||
/**
|
||||
* 字体大小
|
||||
*/
|
||||
private int fontSize = 24;
|
||||
|
||||
/**
|
||||
* 字体名称
|
||||
*/
|
||||
private String fontName = "Arial";
|
||||
|
||||
/**
|
||||
* 水印颜色 (RGBA格式,例如: "255,255,255,128")
|
||||
*/
|
||||
private String color = "255,255,255,128";
|
||||
|
||||
/**
|
||||
* 透明度 (0.0-1.0)
|
||||
*/
|
||||
private float opacity = 0.5f;
|
||||
|
||||
/**
|
||||
* 边距
|
||||
*/
|
||||
private int margin = 20;
|
||||
|
||||
/**
|
||||
* 是否只对图片文件添加水印
|
||||
*/
|
||||
private boolean imageOnly = true;
|
||||
|
||||
/**
|
||||
* 最小图片尺寸(像素),小于此尺寸的图片不添加水印
|
||||
*/
|
||||
private int minImageSize = 100;
|
||||
|
||||
/**
|
||||
* 最大图片尺寸(像素),大于此尺寸的图片不添加水印
|
||||
*/
|
||||
private int maxImageSize = 5000;
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
/*
|
||||
* @Author: jackning 270580156@qq.com
|
||||
* @Date: 2025-01-27 10:00:00
|
||||
* @LastEditors: jackning 270580156@qq.com
|
||||
* @LastEditTime: 2025-06-25 17:29:00
|
||||
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
|
||||
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
|
||||
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
|
||||
* Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
|
||||
* contact: 270580156@qq.com
|
||||
* 联系:270580156@qq.com
|
||||
* Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
|
||||
*/
|
||||
package com.bytedesk.core.upload.watermark;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
/**
|
||||
* 图片水印服务
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class WatermarkService {
|
||||
|
||||
/**
|
||||
* 默认水印文本
|
||||
*/
|
||||
// private static final String DEFAULT_WATERMARK_TEXT = "bytedesk.com";
|
||||
|
||||
/**
|
||||
* 默认水印字体
|
||||
*/
|
||||
private static final String DEFAULT_FONT_NAME = "Arial";
|
||||
|
||||
/**
|
||||
* 默认水印字体大小
|
||||
*/
|
||||
private static final int DEFAULT_FONT_SIZE = 24;
|
||||
|
||||
/**
|
||||
* 默认水印颜色
|
||||
*/
|
||||
private static final Color DEFAULT_WATERMARK_COLOR = new Color(255, 255, 255, 128);
|
||||
|
||||
/**
|
||||
* 默认水印位置
|
||||
*/
|
||||
// private static final WatermarkPosition DEFAULT_POSITION = WatermarkPosition.BOTTOM_RIGHT;
|
||||
|
||||
/**
|
||||
* 给图片添加文字水印
|
||||
*
|
||||
* @param originalImage 原始图片
|
||||
* @param watermarkText 水印文字
|
||||
* @param position 水印位置
|
||||
* @return 添加水印后的图片字节数组
|
||||
*/
|
||||
public byte[] addTextWatermark(BufferedImage originalImage, String watermarkText, WatermarkPosition position) {
|
||||
return addTextWatermark(originalImage, watermarkText, position, DEFAULT_FONT_SIZE, DEFAULT_WATERMARK_COLOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 给图片添加文字水印
|
||||
*
|
||||
* @param originalImage 原始图片
|
||||
* @param watermarkText 水印文字
|
||||
* @param position 水印位置
|
||||
* @param fontSize 字体大小
|
||||
* @param color 水印颜色
|
||||
* @return 添加水印后的图片字节数组
|
||||
*/
|
||||
public byte[] addTextWatermark(BufferedImage originalImage, String watermarkText, WatermarkPosition position,
|
||||
int fontSize, Color color) {
|
||||
try {
|
||||
// 创建新的图片,支持透明度
|
||||
BufferedImage watermarkedImage = new BufferedImage(
|
||||
originalImage.getWidth(),
|
||||
originalImage.getHeight(),
|
||||
BufferedImage.TYPE_INT_ARGB
|
||||
);
|
||||
|
||||
// 绘制原始图片
|
||||
Graphics2D g2d = watermarkedImage.createGraphics();
|
||||
g2d.drawImage(originalImage, 0, 0, null);
|
||||
|
||||
// 设置水印字体
|
||||
Font font = new Font(DEFAULT_FONT_NAME, Font.BOLD, fontSize);
|
||||
g2d.setFont(font);
|
||||
g2d.setColor(color);
|
||||
|
||||
// 设置抗锯齿
|
||||
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||
g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||
|
||||
// 计算水印位置
|
||||
FontMetrics fontMetrics = g2d.getFontMetrics();
|
||||
int textWidth = fontMetrics.stringWidth(watermarkText);
|
||||
int textHeight = fontMetrics.getHeight();
|
||||
|
||||
int x, y;
|
||||
int margin = 20; // 边距
|
||||
|
||||
switch (position) {
|
||||
case TOP_LEFT:
|
||||
x = margin;
|
||||
y = margin + textHeight;
|
||||
break;
|
||||
case TOP_RIGHT:
|
||||
x = originalImage.getWidth() - textWidth - margin;
|
||||
y = margin + textHeight;
|
||||
break;
|
||||
case BOTTOM_LEFT:
|
||||
x = margin;
|
||||
y = originalImage.getHeight() - margin;
|
||||
break;
|
||||
case BOTTOM_RIGHT:
|
||||
x = originalImage.getWidth() - textWidth - margin;
|
||||
y = originalImage.getHeight() - margin;
|
||||
break;
|
||||
case CENTER:
|
||||
x = (originalImage.getWidth() - textWidth) / 2;
|
||||
y = (originalImage.getHeight() + textHeight) / 2;
|
||||
break;
|
||||
default:
|
||||
x = originalImage.getWidth() - textWidth - margin;
|
||||
y = originalImage.getHeight() - margin;
|
||||
}
|
||||
|
||||
// 绘制水印文字
|
||||
g2d.drawString(watermarkText, x, y);
|
||||
g2d.dispose();
|
||||
|
||||
// 转换为字节数组
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ImageIO.write(watermarkedImage, "png", baos);
|
||||
return baos.toByteArray();
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error("添加文字水印失败", e);
|
||||
throw new RuntimeException("添加文字水印失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 给图片添加图片水印
|
||||
*
|
||||
* @param originalImage 原始图片
|
||||
* @param watermarkImage 水印图片
|
||||
* @param position 水印位置
|
||||
* @param opacity 透明度 (0.0-1.0)
|
||||
* @return 添加水印后的图片字节数组
|
||||
*/
|
||||
public byte[] addImageWatermark(BufferedImage originalImage, BufferedImage watermarkImage,
|
||||
WatermarkPosition position, float opacity) {
|
||||
try {
|
||||
// 创建新的图片,支持透明度
|
||||
BufferedImage watermarkedImage = new BufferedImage(
|
||||
originalImage.getWidth(),
|
||||
originalImage.getHeight(),
|
||||
BufferedImage.TYPE_INT_ARGB
|
||||
);
|
||||
|
||||
// 绘制原始图片
|
||||
Graphics2D g2d = watermarkedImage.createGraphics();
|
||||
g2d.drawImage(originalImage, 0, 0, null);
|
||||
|
||||
// 设置透明度
|
||||
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity));
|
||||
|
||||
// 计算水印位置
|
||||
int x, y;
|
||||
int margin = 20; // 边距
|
||||
|
||||
switch (position) {
|
||||
case TOP_LEFT:
|
||||
x = margin;
|
||||
y = margin;
|
||||
break;
|
||||
case TOP_RIGHT:
|
||||
x = originalImage.getWidth() - watermarkImage.getWidth() - margin;
|
||||
y = margin;
|
||||
break;
|
||||
case BOTTOM_LEFT:
|
||||
x = margin;
|
||||
y = originalImage.getHeight() - watermarkImage.getHeight() - margin;
|
||||
break;
|
||||
case BOTTOM_RIGHT:
|
||||
x = originalImage.getWidth() - watermarkImage.getWidth() - margin;
|
||||
y = originalImage.getHeight() - watermarkImage.getHeight() - margin;
|
||||
break;
|
||||
case CENTER:
|
||||
x = (originalImage.getWidth() - watermarkImage.getWidth()) / 2;
|
||||
y = (originalImage.getHeight() - watermarkImage.getHeight()) / 2;
|
||||
break;
|
||||
default:
|
||||
x = originalImage.getWidth() - watermarkImage.getWidth() - margin;
|
||||
y = originalImage.getHeight() - watermarkImage.getHeight() - margin;
|
||||
}
|
||||
|
||||
// 绘制水印图片
|
||||
g2d.drawImage(watermarkImage, x, y, null);
|
||||
g2d.dispose();
|
||||
|
||||
// 转换为字节数组
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
ImageIO.write(watermarkedImage, "png", baos);
|
||||
return baos.toByteArray();
|
||||
|
||||
} catch (IOException e) {
|
||||
log.error("添加图片水印失败", e);
|
||||
throw new RuntimeException("添加图片水印失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 给MultipartFile添加文字水印并保存到指定路径
|
||||
*
|
||||
* @param file 原始文件
|
||||
* @param destinationPath 目标路径
|
||||
* @param watermarkText 水印文字
|
||||
* @param position 水印位置
|
||||
* @return 是否成功
|
||||
*/
|
||||
public boolean addTextWatermarkToFile(MultipartFile file, Path destinationPath, String watermarkText,
|
||||
WatermarkPosition position) {
|
||||
try {
|
||||
// 读取原始图片
|
||||
BufferedImage originalImage = ImageIO.read(file.getInputStream());
|
||||
if (originalImage == null) {
|
||||
log.error("无法读取图片文件: {}", file.getOriginalFilename());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 添加水印
|
||||
byte[] watermarkedImageBytes = addTextWatermark(originalImage, watermarkText, position);
|
||||
|
||||
// 保存到文件
|
||||
try (InputStream inputStream = new ByteArrayInputStream(watermarkedImageBytes)) {
|
||||
Files.copy(inputStream, destinationPath, StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
log.error("给文件添加水印失败: {}", file.getOriginalFilename(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查文件是否为图片
|
||||
*
|
||||
* @param file 文件
|
||||
* @return 是否为图片
|
||||
*/
|
||||
public boolean isImageFile(MultipartFile file) {
|
||||
if (file == null || file.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String contentType = file.getContentType();
|
||||
return contentType != null && contentType.startsWith("image/");
|
||||
}
|
||||
|
||||
/**
|
||||
* 水印位置枚举
|
||||
*/
|
||||
public enum WatermarkPosition {
|
||||
TOP_LEFT, // 左上角
|
||||
TOP_RIGHT, // 右上角
|
||||
BOTTOM_LEFT, // 左下角
|
||||
BOTTOM_RIGHT, // 右下角
|
||||
CENTER // 中心
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
# 水印配置示例
|
||||
bytedesk:
|
||||
watermark:
|
||||
# 是否启用水印功能
|
||||
enabled: true
|
||||
|
||||
# 水印文字
|
||||
text: "bytedesk.com"
|
||||
|
||||
# 水印位置: TOP_LEFT, TOP_RIGHT, BOTTOM_LEFT, BOTTOM_RIGHT, CENTER
|
||||
position: BOTTOM_RIGHT
|
||||
|
||||
# 字体大小
|
||||
fontSize: 24
|
||||
|
||||
# 字体名称
|
||||
fontName: "Arial"
|
||||
|
||||
# 水印颜色 (RGBA格式: R,G,B,A)
|
||||
color: "255,255,255,128"
|
||||
|
||||
# 透明度 (0.0-1.0)
|
||||
opacity: 0.5
|
||||
|
||||
# 边距
|
||||
margin: 20
|
||||
|
||||
# 是否只对图片文件添加水印
|
||||
imageOnly: true
|
||||
|
||||
# 最小图片尺寸(像素),小于此尺寸的图片不添加水印
|
||||
minImageSize: 100
|
||||
|
||||
# 最大图片尺寸(像素),大于此尺寸的图片不添加水印
|
||||
maxImageSize: 5000
|
||||
@@ -0,0 +1,192 @@
|
||||
/*
|
||||
* @Author: jackning 270580156@qq.com
|
||||
* @Date: 2025-01-27 10:00:00
|
||||
* @LastEditors: jackning 270580156@qq.com
|
||||
* @LastEditTime: 2025-01-27 10:00:00
|
||||
* @Description: bytedesk.com https://github.com/Bytedesk/bytedesk
|
||||
* Please be aware of the BSL license restrictions before installing Bytedesk IM –
|
||||
* selling, reselling, or hosting Bytedesk IM as a service is a breach of the terms and automatically terminates your rights under the license.
|
||||
* Business Source License 1.1: https://github.com/Bytedesk/bytedesk/blob/main/LICENSE
|
||||
* contact: 270580156@qq.com
|
||||
* 联系:270580156@qq.com
|
||||
* Copyright (c) 2024 by bytedesk.com, All Rights Reserved.
|
||||
*/
|
||||
package com.bytedesk.core.upload.watermark;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
|
||||
/**
|
||||
* 水印服务测试类
|
||||
*/
|
||||
class WatermarkServiceTest {
|
||||
|
||||
private WatermarkService watermarkService;
|
||||
private BufferedImage testImage;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
watermarkService = new WatermarkService();
|
||||
|
||||
// 创建一个测试图片
|
||||
testImage = new BufferedImage(400, 300, BufferedImage.TYPE_INT_RGB);
|
||||
// 填充白色背景
|
||||
for (int x = 0; x < 400; x++) {
|
||||
for (int y = 0; y < 300; y++) {
|
||||
testImage.setRGB(x, y, Color.WHITE.getRGB());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddTextWatermark() {
|
||||
// 测试添加文字水印
|
||||
byte[] watermarkedImageBytes = watermarkService.addTextWatermark(
|
||||
testImage,
|
||||
"Test Watermark",
|
||||
WatermarkService.WatermarkPosition.BOTTOM_RIGHT
|
||||
);
|
||||
|
||||
assertNotNull(watermarkedImageBytes);
|
||||
assertTrue(watermarkedImageBytes.length > 0);
|
||||
|
||||
// 验证生成的图片可以正常读取
|
||||
try {
|
||||
BufferedImage watermarkedImage = ImageIO.read(new ByteArrayInputStream(watermarkedImageBytes));
|
||||
assertNotNull(watermarkedImage);
|
||||
assertEquals(400, watermarkedImage.getWidth());
|
||||
assertEquals(300, watermarkedImage.getHeight());
|
||||
} catch (IOException e) {
|
||||
fail("无法读取水印图片: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddTextWatermarkWithCustomParameters() {
|
||||
// 测试自定义参数的文字水印
|
||||
byte[] watermarkedImageBytes = watermarkService.addTextWatermark(
|
||||
testImage,
|
||||
"Custom Watermark",
|
||||
WatermarkService.WatermarkPosition.CENTER,
|
||||
32, // 字体大小
|
||||
new Color(255, 0, 0, 128) // 红色半透明
|
||||
);
|
||||
|
||||
assertNotNull(watermarkedImageBytes);
|
||||
assertTrue(watermarkedImageBytes.length > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAddImageWatermark() {
|
||||
// 创建一个水印图片
|
||||
BufferedImage watermarkImage = new BufferedImage(50, 30, BufferedImage.TYPE_INT_ARGB);
|
||||
for (int x = 0; x < 50; x++) {
|
||||
for (int y = 0; y < 30; y++) {
|
||||
watermarkImage.setRGB(x, y, new Color(0, 0, 255, 128).getRGB());
|
||||
}
|
||||
}
|
||||
|
||||
// 测试添加图片水印
|
||||
byte[] watermarkedImageBytes = watermarkService.addImageWatermark(
|
||||
testImage,
|
||||
watermarkImage,
|
||||
WatermarkService.WatermarkPosition.TOP_LEFT,
|
||||
0.5f
|
||||
);
|
||||
|
||||
assertNotNull(watermarkedImageBytes);
|
||||
assertTrue(watermarkedImageBytes.length > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIsImageFile() {
|
||||
// 测试图片文件检测
|
||||
MockMultipartFile imageFile = new MockMultipartFile(
|
||||
"file",
|
||||
"test.jpg",
|
||||
"image/jpeg",
|
||||
"fake image content".getBytes()
|
||||
);
|
||||
|
||||
assertTrue(watermarkService.isImageFile(imageFile));
|
||||
|
||||
// 测试非图片文件
|
||||
MockMultipartFile textFile = new MockMultipartFile(
|
||||
"file",
|
||||
"test.txt",
|
||||
"text/plain",
|
||||
"text content".getBytes()
|
||||
);
|
||||
|
||||
assertFalse(watermarkService.isImageFile(textFile));
|
||||
|
||||
// 测试空文件
|
||||
MockMultipartFile emptyFile = new MockMultipartFile(
|
||||
"file",
|
||||
"test.jpg",
|
||||
"image/jpeg",
|
||||
new byte[0]
|
||||
);
|
||||
|
||||
assertFalse(watermarkService.isImageFile(emptyFile));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testWatermarkPositions() {
|
||||
// 测试所有水印位置
|
||||
WatermarkService.WatermarkPosition[] positions = {
|
||||
WatermarkService.WatermarkPosition.TOP_LEFT,
|
||||
WatermarkService.WatermarkPosition.TOP_RIGHT,
|
||||
WatermarkService.WatermarkPosition.BOTTOM_LEFT,
|
||||
WatermarkService.WatermarkPosition.BOTTOM_RIGHT,
|
||||
WatermarkService.WatermarkPosition.CENTER
|
||||
};
|
||||
|
||||
for (WatermarkService.WatermarkPosition position : positions) {
|
||||
byte[] watermarkedImageBytes = watermarkService.addTextWatermark(
|
||||
testImage,
|
||||
"Position Test",
|
||||
position
|
||||
);
|
||||
|
||||
assertNotNull(watermarkedImageBytes);
|
||||
assertTrue(watermarkedImageBytes.length > 0);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void testNullImage() {
|
||||
// 测试空图片处理
|
||||
assertThrows(RuntimeException.class, () -> {
|
||||
watermarkService.addTextWatermark(
|
||||
null,
|
||||
"Test",
|
||||
WatermarkService.WatermarkPosition.BOTTOM_RIGHT
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void testEmptyWatermarkText() {
|
||||
// 测试空水印文字
|
||||
byte[] watermarkedImageBytes = watermarkService.addTextWatermark(
|
||||
testImage,
|
||||
"",
|
||||
WatermarkService.WatermarkPosition.BOTTOM_RIGHT
|
||||
);
|
||||
|
||||
assertNotNull(watermarkedImageBytes);
|
||||
assertTrue(watermarkedImageBytes.length > 0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user