提示:今日完成图片添加水印功能
后续可能还会继续完善这个功能
文章目录
目录
文章目录
前端部分
后端
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";
}
上传结果
名字里面带下划线的是水印版本(原图)
不带的是无水印版本
添加水印之后的版本