水印照片生成
This commit is contained in:
parent
e9048e44c5
commit
cf7e692b1c
|
|
@ -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="查询")
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
/**
|
||||
* 资源类型
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
// 准备多行水印文本
|
||||
|
|
|
|||
|
|
@ -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=",">
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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>");
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
|
@ -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);
|
||||
});
|
||||
}
|
||||
Loading…
Reference in New Issue