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.rhkb.cn/news/365912.html

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

相关文章

在postman中调试supabase的API接口

文章目录 在supabase中获取API地址和key知道它的restfull风格在postman中进行的设置1、get请求调试2、post新增用户调试3、使用patch更新数据&#xff0c;不用put&#xff01;4、delete删除数据 总结 在supabase中获取API地址和key 首先登录dashboard后台&#xff0c;首页- 右…

deepin基于apt-mirror同步软件源及构建本地内网源

1.安装apt-mirror sudo apt install -y apt-mirror2.配置apt-mirror(/etc/apt/mirror.list) sudo cp /etc/apt/mirror.list /etc/apt/mirror.list.deepin.bak #备份配置文件 sudo gedit /etc/apt/mirror.list修改如下&#xff1a; deb [trustedyes] https://mirrors.bfsu.ed…

1976 ssm 营地管理系统开发mysql数据库web结构java编程计算机网页源码Myeclipse项目

一、源码特点 ssm 营地管理系统是一套完善的信息系统&#xff0c;结合springMVC框架完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用SSM框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开…

单例模式(下)

文章目录 文章介绍步骤安排及单例讲解step1&#xff1a;注册单例类型&#xff08;main.cpp&#xff09;step2&#xff1a;定义类和私有构造函数&#xff08;keyboardinputmanager.h&#xff09;step3:&#xff08;keyboardinputmanager.cpp&#xff09;step4&#xff1a;在qml中…

【Qt】认识Qt界面Hello world小程序

一.认识Qt界面 1.左边栏 在编辑模式下&#xff0c;左边竖排的两个窗⼝叫做 "边栏" 。 ① 是项⽬⽂件管理窗⼝ ② 是打开⽂件列表窗⼝。 边栏⾥的窗⼝数⽬可以增加&#xff0c;边栏⼦窗⼝标题栏有⼀排⼩按钮&#xff0c;最右边的是关闭按钮&#xff0c;倒数第⼆个是 …

HTML5+CSS3+JS小实例:图片九宫格

实例:图片九宫格 技术栈:HTML+CSS+JS 效果: 源码: 【HTML】 <!DOCTYPE html> <html lang="zh-CN"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1…

【吊打面试官系列-MyBatis面试题】模糊查询 like 语句该怎么写?

大家好&#xff0c;我是锋哥。今天分享关于 【模糊查询 like 语句该怎么写?】面试题&#xff0c;希望对大家有帮助&#xff1b; 模糊查询 like 语句该怎么写? 第 1 种&#xff1a;在 Java 代码中添加 sql 通配符。 string wildcardname “%smi%”; list<name> names …

无法访问指向的web服务器(或虚拟主机)的目录,请检查网络设置

微信公众平台,进行业务域名、JS接口安全域名、网页授权域名配置时&#xff0c;遇到的问题中有&#xff1a;无法访问指向的web服务器&#xff08;或虚拟主机&#xff09;的目录&#xff0c;请检查网络设置&#xff0c;这里简单记录一下处理过程。 关于这个问题首先保证下载…

Win11找不到组策略编辑器(gpedit.msc)解决

由于需要同时连接有线网络和无线网络&#xff0c;且重启后双网络都自动连接&#xff0c;因此需要配置组策略。 但是win11找不到组策略编辑器。 灵感来源&#xff1a;Win11找不到组策略编辑器&#xff08;gpedit.msc&#xff09;解决教程 - 知乎 (zhihu.com) 在Win11中&#…

API-Window对象

学习目标&#xff1a; 掌握Window对象 学习内容&#xff1a; BOM&#xff08;浏览器对象模型&#xff09;定时器-延时函数JS执行机制location对象navigation对象history对象 BOM&#xff08;浏览器对象模型&#xff09;&#xff1a; BOM是浏览器对象模型。 window对象是一个全…

Taro +vue3 中的微信小程序中的分享

微信小程序 右上角分享 的触发 以及配 useShareAppMessage(() > {return {title: "电影属全国通兑券",page: /pages/home/index,imageUrl: "http:///chuanshuo.jpg",};}); 置 就是Taro框架中提供的一个分享Api 封装好的

笔记本电脑部署VMware ESXi 6.0系统

正文共&#xff1a;888 字 18 图&#xff0c;预估阅读时间&#xff1a;1 分钟 前面我们介绍了在笔记本上安装Windows 11操作系统&#xff08;Windows 11升级不了&#xff1f;但Win10就要停服了啊&#xff01;来&#xff0c;我教你&#xff01;&#xff09;&#xff0c;也介绍了…

ADS基础教程23 - 有限元电磁仿真(FEM)可视化操作

EM介绍 一、引言二、FEM可视化操作流程1.打开可视化界面2.查看介质的网格3.设置网格颜色4.选择网格5.传感器选择6.编辑传感器7.选择频率8.动画 三、总结 一、引言 在ADS基础教程22中介绍了如何在ADS进行有限元电磁仿真&#xff08;FEM&#xff09;&#xff0c;本文将继续介绍F…

tkinter实现进度条

tkinter实现进度条 效果代码解析导入需要的模块定义进度条 代码 效果 代码解析 导入需要的模块 import tkinter as tk from tkinter import ttk定义进度条 def start_progress():progress[value] 0max_value 100step 10for i in range(0, max_value, step):progress[valu…

ARCGIS python 裁剪栅格函数 arcpy.management.Clip

ARCGIS python 裁剪栅格函数 arcpy.management.Clip 1 功能 裁剪掉栅格数据集、镶嵌数据集或图像服务图层的一部分。 2 使用情况 基于模板范围提取部分栅格数据集&#xff0c;输出与模板范围相交的所有像素使用以 x 和 y 坐标的最小值和最大值确定的包络矩形或使用输出范围文…

计算机毕业设计Python深度学习美食推荐系统 美食可视化 美食数据分析大屏 美食爬虫 美团爬虫 机器学习 大数据毕业设计 Django Vue.js

Python美食推荐系统开题报告 一、项目背景与意义 随着互联网和移动技术的飞速发展&#xff0c;人们的生活方式发生了巨大变化&#xff0c;尤其是餐饮行业。在线美食平台如雨后春笋般涌现&#xff0c;为用户提供了丰富的美食选择。然而&#xff0c;如何在海量的餐饮信息中快速…

GPT-4o文科成绩超一本线,理科为何表现不佳?

目录 01 评测榜单 02 实际效果 什么&#xff1f;许多大模型的文科成绩竟然超过了一本线&#xff0c;还是在竞争激烈的河南省&#xff1f; 没错&#xff0c;最近有一项大模型“高考大摸底”评测引起了广泛关注。 河南高考文科今年的一本线是521分&#xff0c;根据这项评测&…

《塔瑞斯世界》国服震撼登场!AOC助力玩家开启游戏新征途!

一款真正高画质、重机制、轻数值的MMORPG大作&#xff01; 你是否厌倦了在MMORPG游戏中被“氪金大佬”碾压&#xff1f;你是否渴望一个纯粹依靠技术和策略就能获得成就感的游戏世界&#xff1f;如果你对这两个问题的答案都是肯定的&#xff0c;那么《塔瑞斯世界》或许值得你一…

C++ initializer_list类型推导

目录 initializer_list C自动类型推断 auto typeid decltype initializer_list<T> C支持统一初始化{ }&#xff0c;出现了一个新的类型initializer_list<T>&#xff0c;一切类型都可以用列表初始化。提供了一种更加灵活、安全和明确的方式来初始化对象。 class…

2024年道路运输安全员(企业管理人员)备考题库资料。

46.危险货物道路运输随车携带的单据&#xff0c;下列选项不属于的是&#xff08;&#xff09;。 A.道路运输危险货物安全卡 B.运单或者电子运单 C.道路危险货物运输从业资格证 D.车辆检测报告 答案&#xff1a;D 47.危险货物运输驾驶人员在24小时内实际驾驶车辆时间累计不…