SpringBoot实现图片添加水印

提示:今日完成图片添加水印功能

后续可能还会继续完善这个功能

文章目录

目录

文章目录

 前端部分

后端

Xml

Controller层

Sercive层

Service实现层

 Config配置层

application.properties

文件后缀名获取

常量定义



 前端部分

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>upload</title>
</head>
<body>
<h1>上传图片</h1>
<form method="post" enctype="multipart/form-data" action="http://localhost:8080/api/upload"><input type="file" name="file" /><button type="submit">上传</button>
</form>
</body>
</html>

后端

Xml

        <dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.11.0</version></dependency><dependency><groupId>net.coobird</groupId><artifactId>thumbnailator</artifactId><version>0.4.8</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId><version>3.12.0</version></dependency>

Controller层

@RequestMapping("/upload")public ResponseVO upload(@RequestParam("file")MultipartFile file){if(null == file){throw new BusinessException("上传文件不能为空");}uploadService.upload(file);return getSuccessResponseVO("上传成功");}

Sercive层

public interface UploadService {void upload(MultipartFile file);
}

Service实现层

@Service("UploadService")
public class UploadServiceImpl implements UploadService {private static final Logger logger = LoggerFactory.getLogger(UploadServiceImpl.class);private static final List<String> ALLOWED_EXTENSIONS = Arrays.asList("jpg");@Resourceprivate AppConfig appConfig;/*** 上传文件。** @param file 上传的文件对象。* @throws BusinessException 如果文件格式不被允许或上传过程中出现错误。*/@Overridepublic void upload(MultipartFile file) {// 构建文件存储路径String projectFolder = appConfig.getProjectFolder()+ Constants.FILE_PATH;// 获取文件原始名称String fileName = file.getOriginalFilename();// 检查文件格式是否被允许Boolean allowed = isAllowed(fileName);if(!allowed){throw new BusinessException("文件格式错误,请上传jpg,png,webp,jpeg,gif等图片格式");}// 获取当前日期,用于构建日期目录LocalDate now = LocalDate.now();// 根据日期格式化字符串String datePath = now.format(DateTimeFormatter.ofPattern(DateTimePatternEnum.YYYY_MM.getPattern()));// 构建上传文件的项目文件夹路径File uploadFileProjectFolder = new File(projectFolder + "/"+datePath);// 如果文件夹不存在,则创建文件夹if(!uploadFileProjectFolder.exists()){uploadFileProjectFolder.mkdirs();}// 移除文件扩展名,用于构建文件夹名称String fileNameWithout = fileName.substring(0,fileName.lastIndexOf("."));// 构建文件夹路径File fileFolder = new File(uploadFileProjectFolder.getPath() + "/" + fileNameWithout);// 如果文件夹不存在,则创建文件夹if(!fileFolder.exists()){fileFolder.mkdirs();}// 构建新文件路径File newFile = new File(fileFolder.getPath() + "/" + fileName);// 获取文件扩展名String suffix = StringUtil.getSuffix(fileName);// 构建带水印的文件名//带水印版本的名字String watermarkName = fileNameWithout+"_"+suffix;// 创建带水印的文件对象//创建带水印且压缩画质的版本File newWatermarkName = new File(fileFolder.getPath() + "/" + watermarkName);try {// 将上传的文件保存到服务器file.transferTo(newFile);} catch (IOException e) {logger.error("上传文件失败:{}",e);throw new BusinessException("上传文件失败");}try {// 为文件添加水印并压缩addWatermarkAndCompress(newFile, newWatermarkName,"贺浩浩");} catch (IOException e) {logger.error("保存水印版本失败:{}",e);throw new BusinessException("保存水印版本失败");}logger.info("projectFolder:{}",projectFolder);}/*** 检查文件名是否允许。** 此方法通过检查文件名的扩展名来确定文件名是否被允许。文件名必须包含至少一个点(.)* 以标识扩展名,并且扩展名必须在预定义的允许扩展名列表中。** @param fileName 要检查的文件名。* @return 如果文件名的扩展名被允许,则返回true;否则返回false。*/private Boolean isAllowed(String fileName) {// 检查文件名是否为空或不包含点(.)if(fileName == null || fileName.lastIndexOf(".") == -1){return false;}// 提取文件名的扩展名,并转换为小写String extension = fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase();// 检查提取的扩展名是否在允许的扩展名列表中return ALLOWED_EXTENSIONS.contains(extension);}/*** 给图片添加水印并压缩保存。** 此方法接收原始图片文件、目标输出文件和水印文本作为参数,它将原始图片读入,* 添加水印后,按照原始尺寸进行压缩,并保存到目标文件中。** @param originalFile 原始图片文件,添加水印和压缩的基础文件。* @param outputFile 添加水印并压缩后的图片保存位置。* @param watermarkText 要添加的水印文本。* @throws IOException 如果读取或写入文件发生错误。*/private void addWatermarkAndCompress(File originalFile, File outputFile,String watermarkText) throws IOException {// 读取原始图片BufferedImage originalImage = ImageIO.read(originalFile);// 添加水印BufferedImage watermarkedImage = addWatermark(originalImage, watermarkText);// 获取原图尺寸,以保持压缩后的图片尺寸与原图相同// 获取原图尺寸int originalWidth = originalImage.getWidth();int originalHeight = originalImage.getHeight();// 使用Thumbnails.of方法对添加水印后的图片进行缩放和压缩// 按比例缩放并压缩Thumbnails.of(watermarkedImage).size(originalWidth, originalHeight).outputQuality(0.4) // 调整画质压缩.toFile(outputFile);}/*** 给图片添加水印。** @param image 原始图片。* @param watermarkText 水印文本。* @return 添加了水印的图片。*/private BufferedImage addWatermark(BufferedImage image, String watermarkText) {// 获取原始图片的宽度和高度int imageWidth = image.getWidth();int imageHeight = image.getHeight();// 创建Graphics2D对象,用于在图片上绘制水印// 创建用于绘制水印的Graphics2D对象Graphics2D g2d = (Graphics2D) image.getGraphics();// 设置透明度,使水印呈现半透明效果// 设置水印的属性AlphaComposite alphaChannel = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f);g2d.setComposite(alphaChannel);// 设置水印文字颜色为灰色g2d.setColor(Color.GRAY);// 设置水印文字的字体、大小和样式// 使用支持中文的字体,例如SimHei(黑体)Font font = new Font("SimHei", Font.BOLD, 36);g2d.setFont(font);// 获取水印文字的尺寸信息FontMetrics fontMetrics = g2d.getFontMetrics();Rectangle2D rect = fontMetrics.getStringBounds(watermarkText, g2d);int textWidth = (int) rect.getWidth();int textHeight = (int) rect.getHeight();// 用于随机生成水印位置偏移量Random random = new Random();// 平铺方式添加水印,通过控制行间距和文字在行内的偏移,实现错落有致的布局效果// 平铺方式添加水印,单双行错开并随机偏移for (int y = 0; y < imageHeight; y += textHeight + 100) {// 判断当前行为偶数行还是奇数行,奇数行文字向右偏移boolean oddRow = (y / (textHeight + 100)) % 2 == 0;for (int x = oddRow ? 0 : textWidth / 2; x < imageWidth; x += textWidth + 300) {// 随机生成水平和垂直偏移量,使水印位置略有变化,避免整齐排列int xOffset = random.nextInt(100) - 50; // 随机偏移 -50 到 50 像素int yOffset = random.nextInt(50) - 25;  // 随机偏移 -25 到 25 像素// 在图片上绘制水印文字,位置略有偏移g2d.drawString(watermarkText, x + xOffset, y + yOffset);}}// 释放Graphics2D资源g2d.dispose();// 返回添加了水印的图片return image;}
}

由于只是一个简单的demo练习,所以并未使用到数据库,等待后续有时间出一个完整版本

 Config配置层

@Component
public class AppConfig {@Value("${project.folder:}")private String projectFolder;public String getProjectFolder() {return projectFolder;}
}

application.properties

# 应用服务 WEB 访问端口
server.port=8080
server.servlet.context-path=/api#文件大小配置
spring.servlet.multipart.max-file-size=15MB
spring.servlet.multipart.max-request-size=15MB#项目目录
project.folder=F:/a-xxxxxxxxxxxxxxxxx/java/SpringBoot_practice/mys

文件后缀名获取

public static String getSuffix(String fileName){String suffix = fileName.substring(fileName.lastIndexOf('.')).toLowerCase();return suffix;}

常量定义

/*** Constants类用于定义应用程序中使用的常量。* 该类中的常量应该是整个应用程序范围内不变的值。*/
public class Constants {/*** 文件路径常量。* 该常量定义了访问文件系统的根路径。* 使用此常量可以确保应用程序中对文件路径的引用具有一致性和可维护性。*/public static final String FILE_PATH = "/file";
}

上传结果

名字里面带下划线的是水印版本(原图)

不带的是无水印版本

 

 添加水印之后的版本

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/37852.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

WIN11,如何同时连接有线网络与WLAN无线网络

之前写了两篇文章&#xff0c;一篇是双网卡多网卡时win11如何设置网卡优先级_多网卡设置网卡优先级-CSDN博客 另一篇是win11 以太网和WLAN冲突 连接网线时导致WiFi掉线 解决_win11 以太网和wifi不能同时生效-CSDN博客 这篇是对上面两篇的补充&#xff1a;主要解决电脑重启后&…

语音芯片TD5580,USB小音响芯片—拓达半导体

有时候电脑的声卡会出现损坏的问题&#xff0c;给我们的生活带来了很多麻烦。这时候&#xff0c;我们就需要一款方便易用的产品来解决声卡问题。USB声卡小音响就是为了解决这个问题而设计的一款便捷的产品。它不仅可以作为一个小音响&#xff0c;让您在工作和娱乐的时候享受高品…

docker-compose搭建minio对象存储服务器

docker-compose搭建minio对象存储服务器 最近想使用oss对象存储进行用户图片上传的管理&#xff0c;了解了一下例如aliyun或者腾讯云的oss对象存储服务&#xff0c;但是呢涉及到对象存储以及经费有限的缘故&#xff0c;决定自己手动搭建一个oss对象存储服务器&#xff1b; 首先…

烧结银到底有多牛?欢迎咨询SHAREX善仁新材研究院

烧结银到底有多牛&#xff1f;欢迎咨询SHAREX善仁新材研究院 在当今日新月异的科技浪潮中&#xff0c;材料科学以其独特的魅力引领着人类探索未知领域的步伐。在众多前沿材料中&#xff0c;烧结银凭借其卓越的性能和广泛的应用前景&#xff0c;逐渐崭露头角&#xff0c;成为科…

创建XCOM窗体和跳转连接

Xcom 窗体&#xff1a; (groupBox组合框&#xff0c;comboBox下拉框) xcom代码&#xff1a; namespace _01_作业 {// 1kb 1024B 1200B// 1MB public partial class Form1 : Form{public List<string> botelv new List<string> { "600","1200&…

Unix Network Programming Episode 96

‘socketpair’ Function The socketpair function creates two sockets that are then connected together. This function applies only to Unix domain sockets. #include <sys/socket.h> int socketpair(int family, int type, int protocol, int sockfd[2]);POSIX…

(十七)如何学习统计学基础知识(学习路线)

统计学是数据科学的基本支柱。统计学的目的是帮助你理解数据并从中得出有意义的结论。在数据科学中&#xff0c;统计学在理解数据模式和趋势、做出预测和检验假设方面起着至关重要的作用。 (一) 数据科学统计学习路线图 本文为学习统计学并将其应用于数据科学提供了清晰、结构化…

如何使用 SPM 插件从 Pkl 配置文件生成 Swift 接口

文章目录 前言示例展示 Pkl 配置生成 Swift 绑定手动安装和使用 pkl-gen-swift创建 SPM 命令插件加载 Pkl 配置总结前言 Pkl(全称为 Pickle)是苹果推出的一种全新的专用于配置的编程语言。它允许开发人员通过类型和内置验证安全、直观地设计数据模型。 作为苹果语言,Pkl 有…

Python容器 之 列表--下标和切片

列表的切片 得到是 新的列表字符串的切片 得到是 新的字符串 如果下标 不存在会报错 list1 [1, 3.14, "hello", False] print(list1)# 获取 列表中 第一个数据 print(list1[0]) # 1# 获取列表中的最后一个数据 print(list1[-1]) # [False]# 获取中间两个数 即 3.1…

3.2ui功能讲解之graph页面

本节重点介绍 : graph页面target页面flags页面status页面tsdb-status页面 访问地址 $ip:9090 graph页面 autocomplete 可以补全metrics tag信息或者 内置的关键字 &#xff0c;如sum聚合函数table查询 instante查询&#xff0c; 一个点的查询graph查询调整分辨率 resolutio…

记录:有趣的C#多元运算符 ? : 表达式写法

有时候用 if //...Whatre you she wanna go else if //...do do do else //...and i know something just like this... 感觉代码太多了怎么优雅的、高端的替换&#xff1f; 看个高端的栗子菊&#xff1a; LedCOM["parity"] ledData[4] "N" ? …

Study--Oracle-05-Oracler体系结构

一、oracle 体系概览 Oracle数据库的体系结构通常包括以下主要组件&#xff1a; 1、实例&#xff08;Instance&#xff09;&#xff1a;运行数据库的软件环境&#xff0c;包括内存结构&#xff08;SGA&#xff09;和进程结构&#xff08;Background Processes and User Proces…

Django 一对多关系

1&#xff0c;创建 Django 应用 Test/app9 django-admin startapp app9 2&#xff0c;注册应用 Test/Test/settings.py 3&#xff0c;添加应用路由 Test/Test/urls.py from django.contrib import admin from django.urls import path, includeurlpatterns [path(admin/,…

《每天5分钟用Flask搭建一个管理系统》 第10章:前端集成

第10章&#xff1a;前端集成 10.1 前端技术概述 前端技术指的是构建Web应用用户界面所使用的技术&#xff0c;包括HTML、CSS和JavaScript。现代Web开发中&#xff0c;前端框架如React、Vue.js和Angular等被广泛使用。 10.2 AJAX与Flask的集成 AJAX&#xff08;Asynchronous…

数据资产安全策略的定制化之道:深入了解各企业独特需求,量身打造个性化的数据资产保护方案,确保数据安全无虞,助力企业稳健发展

目录 一、引言 二、企业数据资产安全现状分析 &#xff08;一&#xff09;数据安全风险多样化 &#xff08;二&#xff09;传统安全措施难以满足需求 &#xff08;三&#xff09;企业数据资产安全意识亟待提高 三、定制化数据资产安全策略的重要性 &#xff08;一&#…

natvicat为什么连不上linux上的mysql?

老规矩&#xff0c;废话不多说&#xff0c;直接上教程。 号外&#xff0c;数据库管理工具领域的知名品牌Navicat&#xff0c;推出其免费版本——Navicat Premium Lite&#xff0c;用户可从Navicat官网下载体验这款软件。 https://www.navicat.com.cn/download/navicat-premium-…

【HALCON】如何实现hw窗口自适应相机拍照成像的大小

前言 在开发一个喷码检测软件的时候碰到相机成像和hw窗体的大小不一致&#xff0c;hw太小显示不完全成像的图片&#xff0c;这使得成像不均匀&#xff0c;现场辨别起来比较不直观&#xff0c;因此需要对其进行一个调整。 解决 省略掉读取图片的环节&#xff0c;我们只需要将…

别再用this.$forceUpdate()了!—性能优化篇

文章目录 别再用this.$forceUpdate()了&#xff01;—性能优化篇&#x1f388;介绍&#x1f9e8;弊端注意事项 &#x1f386;解决实例 别再用this.$forceUpdate()了&#xff01;—性能优化篇 起因是接手公司之前外包的项目做项目优化&#xff0c;代码看着一言难尽&#xff0c;…

CGI面试题及参考答案

什么是CGI?它在Web服务器与应用程序之间扮演什么角色? CGI(Common Gateway Interface) 是一种标准协议,它定义了Web服务器与运行在服务器上的外部程序(通常是脚本或应用程序)之间的通信方式。简单来说,CGI充当了一个桥梁,使得Web服务器能够将用户的请求传递给后端程序…

ruoyi—cloud 新建模块+生成代码

1.复制一个模块——修改名字 2.打开模块下的yml文件&#xff0c;修改端口号和名字 &#xff08;1&#xff09;修改一个名字 &#xff08;2&#xff09;打开yml文件 &#xff08;3&#xff09;修改端口号&#xff0c;不要重复 &#xff08;4&#xff09;改名字和模块一致 3.…