水印照片生成

This commit is contained in:
cwchen 2025-04-03 15:26:36 +08:00
parent e9048e44c5
commit cf7e692b1c
12 changed files with 161 additions and 110 deletions

View File

@ -3,6 +3,7 @@ package com.bonus.imgTool.backstage.controller;
import com.bonus.imgTool.annotation.DecryptAndVerify;
import com.bonus.imgTool.annotation.LogAnnotation;
import com.bonus.imgTool.backstage.entity.QueryParamDto;
import com.bonus.imgTool.backstage.entity.SynthesisQueryVo;
import com.bonus.imgTool.backstage.service.SynthesisQueryService;
import com.bonus.imgTool.model.SysUser;
import com.bonus.imgTool.system.vo.EncryptedReq;
@ -57,6 +58,13 @@ public class SynthesisQueryController {
return synthesisQueryService.collectData(data.getData());
}
@ApiOperation("生成水印")
@PostMapping(value = "generateWatermark")
@DecryptAndVerify(decryptedClass = SynthesisQueryVo.class)
public ServerResponse generateWatermark(EncryptedReq<SynthesisQueryVo> data) {
return synthesisQueryService.generateWatermark(data.getData());
}
/*@PostMapping(value = "getProClassifyStatisticsList")
@DecryptAndVerify(decryptedClass = QueryParamDto.class)//加解密统一管理
@LogAnnotation(operModul = "综合查询-照片综合查询", operation = "查询照片", operDesc = "系统级事件",operType="查询")

View File

@ -48,4 +48,22 @@ public interface SynthesisQueryDao {
void addComprehensiveQuery(ComprehensiveQueryVo comprehensiveQueryVo);
void updateComprehensiveQuery(ComprehensiveQueryVo comprehensiveQueryVo);
/**
* 生成水印照片
* @param vo
* @return void
* @author cwchen
* @date 2025/4/3 14:44
*/
void updateSyData(SynthesisQueryVo vo);
/**
* 获取水印照片地址
* @param vo
* @return String
* @author cwchen
* @date 2025/4/3 14:57
*/
String getSyData(SynthesisQueryVo vo);
}

View File

@ -2,6 +2,7 @@ package com.bonus.imgTool.backstage.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.util.Date;
@ -16,6 +17,9 @@ import java.util.Date;
public class SynthesisQueryVo {
private Long id;
/**工程名称*/
private String proName;
/**
* 原图图片路径
*/
@ -41,6 +45,7 @@ public class SynthesisQueryVo {
* 上传时间
*/
@JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8")
@DateTimeFormat(pattern = "yyyy-MM-dd")
private Date uploadTime;
/**
* 资源类型

View File

@ -2,6 +2,7 @@ package com.bonus.imgTool.backstage.service;
import com.bonus.imgTool.backstage.entity.ComprehensiveQueryVo;
import com.bonus.imgTool.backstage.entity.QueryParamDto;
import com.bonus.imgTool.backstage.entity.SynthesisQueryVo;
import com.bonus.imgTool.utils.ServerResponse;
/**
@ -53,4 +54,13 @@ public interface SynthesisQueryService {
* @return ServerResponse
*/
void updateComprehensiveQuery(ComprehensiveQueryVo comprehensiveQueryVo);
/**
* 生成水印
* @param data
* @return ServerResponse
* @author cwchen
* @date 2025/4/3 14:23
*/
ServerResponse generateWatermark(SynthesisQueryVo data);
}

View File

@ -7,7 +7,9 @@ import com.bonus.imgTool.backstage.entity.SynthesisNumVo;
import com.bonus.imgTool.backstage.entity.SynthesisQueryVo;
import com.bonus.imgTool.backstage.service.SynthesisQueryService;
import com.bonus.imgTool.system.vo.LoginUser;
import com.bonus.imgTool.utils.HighQualityWatermark;
import com.bonus.imgTool.utils.ServerResponse;
import com.bonus.imgTool.utils.SystemUtils;
import com.bonus.imgTool.utils.UserUtil;
import com.bonus.imgTool.webResult.Constants;
import com.github.pagehelper.PageHelper;
@ -20,6 +22,8 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import javax.annotation.Resource;
import java.io.File;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
@ -104,4 +108,43 @@ public class SynthesisQueryServiceImpl implements SynthesisQueryService {
public void updateComprehensiveQuery(ComprehensiveQueryVo comprehensiveQueryVo) {
synthesisQueryDao.updateComprehensiveQuery(comprehensiveQueryVo);
}
@Override
@Transactional(rollbackFor = Exception.class)
public ServerResponse generateWatermark(SynthesisQueryVo vo) {
try {
String path = SystemUtils.getUploadPath() + vo.getOriginalFilePath();
if (!new File(path).exists()) {
return ServerResponse.createErroe("原照片不存在");
}
String syPath = synthesisQueryDao.getSyData(vo);
if(StringUtils.isBlank(syPath)){
syPath = generateWatermarkData(vo);
}
vo.setWatermarkFilePath(syPath);
synthesisQueryDao.updateSyData(vo);
return ServerResponse.createSuccess("操作成功",syPath);
} catch (Exception e) {
log.error(e.toString(), e);
return ServerResponse.createErroe("操作失败");
}
}
public String generateWatermarkData(SynthesisQueryVo vo){
// 准备多行水印文本
List<String> watermarkLines = new ArrayList<>();
String uploadTime = new SimpleDateFormat("yyyy-MM-dd").format(vo.getUploadTime());
watermarkLines.add(uploadTime);
watermarkLines.add(vo.getProName().replaceAll("(.{18})", "$1@@"));
watermarkLines.add(vo.getUploadTypeName());
String sourceTypeName = null;
if (Objects.equals(vo.getSourceType(), "9")) {
sourceTypeName = vo.getTitle();
} else {
sourceTypeName = vo.getSourceTypeName().split("-")[1];
}
watermarkLines.add(sourceTypeName);
String localPath = SystemUtils.getUploadPath() +File.separator+ vo.getOriginalFilePath();
return HighQualityWatermark.generateWatermark(watermarkLines,localPath);
}
}

View File

@ -1,5 +1,6 @@
package com.bonus.imgTool.config;
import java.io.File;
import java.util.List;
import com.bonus.imgTool.utils.SystemUtils;
@ -60,9 +61,9 @@ public class WebMvcConfig implements WebMvcConfigurer {
String filePath=SystemUtils.getUploadPath();//获取文件上传路径
/** 本地文件上传路径 */
registry.addResourceHandler("/statics/**")
.addResourceLocations("file:" + filePath);
.addResourceLocations("file:" + filePath + File.separator);
registry.addResourceHandler("/files/**")
.addResourceLocations("file:"+filePath);
.addResourceLocations("file:"+filePath + File.separator);
}
}

View File

@ -1,4 +1,5 @@
package com.bonus.imgTool.utils;
import javax.imageio.*;
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
import javax.imageio.stream.FileImageOutputStream;
@ -8,6 +9,7 @@ import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @className:HighQualityWatermark
* @author:cwchen
@ -28,12 +30,13 @@ public class HighQualityWatermark {
/**
* 添加高质量多行水印
*
* @param sourceImagePath 源图片路径
* @param targetImagePath 目标图片路径
* @param textLines 多行水印文本
* @param position 水印位置
* @param opacity 透明度
* @param fontName 字体名称
* @param textLines 多行水印文本
* @param position 水印位置
* @param opacity 透明度
* @param fontName 字体名称
*/
public static void addHighQualityWatermark(String sourceImagePath, String targetImagePath,
List<String> textLines, String position,
@ -81,7 +84,7 @@ public class HighQualityWatermark {
// 设置水印颜色和透明度
Color watermarkColor = new Color(DEFAULT_COLOR.getRed(), DEFAULT_COLOR.getGreen(),
DEFAULT_COLOR.getBlue(), (int)(opacity * 255));
DEFAULT_COLOR.getBlue(), (int) (opacity * 255));
g2d.setColor(watermarkColor);
// 计算水印起始位置
@ -117,7 +120,7 @@ public class HighQualityWatermark {
int imgHeight, String fontName) {
// 基于图片对角线长度计算初始字体大小
double diagonal = Math.sqrt(imgWidth * imgWidth + imgHeight * imgHeight);
int initialSize = (int)(diagonal * 0.03);
int initialSize = (int) (diagonal * 0.03);
initialSize = Math.max(MIN_FONT_SIZE, Math.min(initialSize, MAX_FONT_SIZE));
Font testFont = new Font(fontName, DEFAULT_FONT_STYLE, initialSize);
@ -221,7 +224,7 @@ public class HighQualityWatermark {
private static Point calculateWatermarkPosition(String position, int imgWidth, int imgHeight,
int maxLineWidth, int totalHeight,
FontMetrics fm) {
int margin = (int)(Math.min(imgWidth, imgHeight) * 0.05); // 5%边距
int margin = (int) (Math.min(imgWidth, imgHeight) * 0.05); // 5%边距
int x = 0;
int y = 0;
@ -308,6 +311,29 @@ public class HighQualityWatermark {
}
}
/**
* 生成图片水印
* @return String
* @author cwchen
* @date 2025/4/3 14:28
*/
public static String generateWatermark(List<String> watermarkLines,String localPath) {
try {
String suffix = IDUtils.getSuffix(localPath);
String path = File.separator + "sy" + File.separator + IDUtils.createID() + suffix;
String outPath = SystemUtils.getUploadPath() + path;
// 添加高质量水印
addHighQualityWatermark(localPath, outPath,
watermarkLines, "bottom-left",
0.7f, "Microsoft YaHei");
System.out.println("高质量水印添加完成,图片已无损输出");
return path;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static void main(String[] args) {
try {
// 准备多行水印文本

View File

@ -103,7 +103,8 @@
source_type AS sourceType,
A.dict_name AS sourceTypeName,
IF(tpc.file_resource_id IS NULL,'0','1') AS collectStatus,
tcq.title
tcq.title,
tcq.pro_name AS proName
FROM tb_comprehensive_query tcq
LEFT JOIN sys_file_resource sfr ON tcq.id = sfr.source_id AND tcq.upload_type = sfr.upload_type AND sfr.is_active = '1'
LEFT JOIN tb_photo_collect tpc ON sfr.id = tpc.file_resource_id AND tpc.collect_user_id = #{userId}
@ -205,6 +206,10 @@
</where>
ORDER BY sfr.create_time DESC
</select>
<!--获取水印照片地址-->
<select id="getSyData" resultType="java.lang.String">
SELECT watermark_file_path FROM sys_file_resource WHERE id = #{id}
</select>
<!--收藏/取消收藏图片-->
<update id="collectData">
<if test="collectType == 1">
@ -217,6 +222,10 @@
DELETE FROM tb_photo_collect WHERE file_resource_id = #{id} AND collect_user_id = #{userId}
</if>
</update>
<!--生成水印照片-->
<update id="updateSyData">
UPDATE sys_file_resource SET watermark_file_path = #{watermarkFilePath} WHERE id = #{id}
</update>
<update id="updateComprehensiveQuery">
update tb_comprehensive_query
<trim prefix="SET" suffixOverrides=",">

View File

@ -28,7 +28,7 @@ if(url.indexOf("112.27.246.86")!=-1){
}
// console.log(dataUrl)
let imgUrl = dataUrl + /files/;
let imgUrl = dataUrl + '/files';
let tokens = localStorage.getItem("token");
function error(xhr) {

View File

@ -113,8 +113,8 @@ function initImgData(list) {
" </div>" +
" <div class='hidden-actions'><div class='hidden-btn layout'>" +
" <div class='layout' onclick='viewImg(" + JSON.stringify(item) + ")'><div class='img-view' title='放大'></div></div>" +
" <div class='layout' onclick='imgDownLoad(" + JSON.stringify(item) + ")'><div title='原图下载' class='img-download'></div></div>" +
" <div class='layout' onclick='waterImgDownLoad(" + JSON.stringify(item) + ")'><div title='水印下载' class='img-water'></div></div>" +
" <div class='layout' onclick='imgDownLoad(" + JSON.stringify(item) + ",1)'><div title='原图下载' class='img-download'></div></div>" +
" <div class='layout' onclick='generateWatermark(" + JSON.stringify(item) + ")'><div title='水印下载' class='img-water'></div></div>" +
setCollectImg(item) +
" </div></div>" +
" </div>");

View File

@ -42,4 +42,26 @@ function collectData(objParams) {
error(xhr)
});
return flag;
}
/**生成水印照片*/
function generateWatermark(objParams) {
let url = dataUrl + "/backstage/synthesisQuery/generateWatermark"
let params = {
encryptedData: encryptCBC(JSON.stringify(objParams))
}
let loadingMsg = layer.msg("水印照片生成中,请稍候...", {icon: 16, scrollbar: false, time: 0,});
ajaxRequest(url, "POST", params, true, function () {
}, function (result) {
layer.close(loadingMsg);
if (result.status === 200) {
objParams.watermarkFilePath = result.data;
imgDownLoad(objParams, 2);
}else{
parent.layer.msg(result.msg, {icon: 2});
}
}, function (xhr) {
error(xhr)
layer.close(loadingMsg);
});
}

View File

@ -17,12 +17,12 @@ function viewImg(item) {
}
/**原图下载*/
function imgDownLoad(item) {
let orginalPath = item.originalFilePath;
function imgDownLoad(item, type) {
let path = type === 1 ? item.originalFilePath : item.watermarkFilePath;
let obj = {
imgPath: orginalPath,
imgPath: path,
}
let loadingMsg = layer.msg("原图下载中,请稍候...", {icon: 16, scrollbar: false, time: 0,});
let loadingMsg = layer.msg("照片下载中,请稍候...", {icon: 16, scrollbar: false, time: 0,});
let url = dataUrl + "/common/download/downloadImage?token=" + tokens + "&encryptedData=" + encodeURIComponent(encryptCBC(JSON.stringify(obj)));
let xhr = new XMLHttpRequest();
xhr.open("get", url, true);
@ -35,103 +35,12 @@ function imgDownLoad(item) {
var a = document.createElement("a");
var url = window.URL.createObjectURL(blob);
a.href = url;
a.download = orginalPath.substring(orginalPath.lastIndexOf('/') + 1, orginalPath.length); // 文件名
a.download = path.substring(path.lastIndexOf('/') + 1, path.length); // 文件名
} else {
layer.msg("原图下载发生异常,请稍后重试", {icon: 16, scrollbar: false, time: 2000});
layer.msg("照片下载发生异常,请稍后重试", {icon: 16, scrollbar: false, time: 2000});
}
a.click();
window.URL.revokeObjectURL(url);
};
// xhr.send(params);
xhr.send();
}
/**水印下载*/
function waterImgDownLoad(item) {
/* let orginalPath = item.originalFilePath;
let obj = {
imgPath: orginalPath,
}
let loadingMsg = layer.msg("水印图片下载中,请稍候...", {icon: 16, scrollbar: false, time: 0,});
let url = dataUrl + "/common/download/downloadImage?token=" + tokens + "&encryptedData=" + encodeURIComponent(encryptCBC(JSON.stringify(obj)));
let xhr = new XMLHttpRequest();
xhr.open("get", url, true);
xhr.responseType = "blob"; // 转换流
xhr.setRequestHeader('Content-Type','application/json;charset=UTF-8')
xhr.onload = function () {
layer.close(loadingMsg);
if (this.status === 200) {
let blob = this.response;
var a = document.createElement("a");
var url = window.URL.createObjectURL(blob);
a.href = url;
a.download = orginalPath.substring(orginalPath.lastIndexOf('/') + 1,orginalPath.length); // 文件名
} else {
layer.msg("水印图片下载发生异常,请稍后重试", {icon: 16, scrollbar: false, time: 2000});
}
a.click();
window.URL.revokeObjectURL(url);
};
// xhr.send(params);
xhr.send();*/
function downloadImage(imageUrl) {
let orginalPath = item.originalFilePath;
let obj = {
imgPath: orginalPath,
}
$.ajax({
url: dataUrl + "/common/download/downloadImage?token=" + tokens,
type: 'GET',
data: {
encryptedData: encodeURIComponent(encryptCBC(JSON.stringify(obj)))
},
xhrFields: {
responseType: 'blob' // 重要指定响应类型为blob
},
success: function (data, status, xhr) {
// 检查是否是blob数据图片
if (data instanceof Blob) {
// 创建临时URL用于下载
var blobUrl = URL.createObjectURL(data);
// 创建下载链接
var a = document.createElement('a');
a.href = blobUrl;
a.download = orginalPath.substring(orginalPath.lastIndexOf('/') + 1,orginalPath.length); // 提取文件名
document.body.appendChild(a);
a.click();
// 清理
setTimeout(function () {
document.body.removeChild(a);
URL.revokeObjectURL(blobUrl);
}, 100);
} else {
// 如果返回的不是blob可能是错误信息
console.error('服务器返回意外响应:', data);
alert('下载失败: 服务器返回无效数据');
}
},
error: function (xhr, status, error) {
// 尝试解析错误信息
var errorMsg = '下载失败';
if (xhr.responseText) {
try {
var errorResponse = JSON.parse(xhr.responseText);
errorMsg = errorResponse.message || errorMsg;
} catch (e) {
errorMsg = xhr.responseText;
}
}
alert(errorMsg);
}
});
}
// 使用示例
$('#download-btn').click(function () {
var imageUrl = $('#image-url').val();
downloadImage(imageUrl);
});
}