package com.htsc.project.common;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.text.DecimalFormat;
import java.util.Map;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
import net.coobird.thumbnailator.Thumbnails;
/**
* @author 020152
* @date 2023/5/9
*/
@Service
@Slf4j
public class ImageCompress {
private static final Integer COMPRESSED = 1;
/**
* 支持的文件的头,key:文件头,value:支持的文件后缀,以逗号分割
* {"FFD8FF":"jpg,jpeg","89504E47":"png"}
*/
@Value(value = "#{${image.compress.fileHeader:{\"FFD8FF\":\"jpg,jpeg\",\"89504E47\":\"png\"}}}")
private Map<String, String> compressFileHeaders;
/**
* 是否压缩图片,0不压缩,1压缩
*/
@Value(value = "${image.compressed:0}")
private Integer compressed;
/**
* 对png图片开始压缩的大小,对较小的png不压缩,防止无用功
*/
@Value(value = "${image.compress.minSize:100000}")
private Long minCompressedSize;
/**
* 太大的png压缩必要不大,可能是无用功
*/
@Value(value = "${image.compress.maxSize:10000000}")
private Long maxCompressedSize;
/**
* 压缩率,默认0.9
*/
@Value(value = "${image.compressFactor:0.9}")
private Double compressFactor;
@Autowired
private Config config;
@PostConstruct
public void init() {
log.info("compressHeaders:{}, compressed:{},compressFactor:{}", compressFileHeaders, compressed, compressFactor);
log.info(Constant.LOG_DEVMODESTR + config.getDevMode());
}
/**
* 压缩图片
*
* @param oriFile 原始文件
* @param fileName 文件名
* @param random uuid随机
* @return 压缩后的文件
*/
public File compress(File oriFile, String fileName, String random) {
if (!COMPRESSED.equals(compressed)) {
return oriFile;
}
try (InputStream inputStream = Files.newInputStream(oriFile.toPath())) {
byte[] bytes = new byte[10];
inputStream.read(bytes, 0, bytes.length);
// 校验文件头信息,防止txt、html等文件
String fileHeader = bytesToHex(bytes);
String matchExt = compressFileHeaders.keySet().stream().filter(fileHeader::startsWith).findAny().orElse(null);
if (matchExt == null) {
// 非图片文件,直接返回
return oriFile;
}
String suffix, prefix;
if (fileName.lastIndexOf(".") == -1) {
suffix = "." + compressFileHeaders.get(matchExt);
if (suffix.contains(",")) {
suffix = ".jpg";
}
prefix = fileName;
} else {
suffix = fileName.substring(fileName.lastIndexOf("."));
prefix = fileName.substring(0, fileName.lastIndexOf("."));
}
String finalFileName = prefix + suffix;
// 后缀名和真实文件类型不匹配,如本身是png文件但是后缀为jpg,或反之
if (!compressFileHeaders.get(matchExt).contains(suffix.substring(1))) {
// 实际是jpg文件,但是用了png后缀,在toFile使用jpg压缩,保证压缩率,但是不修改目标文件后缀名
if (!suffix.contains("jpg")) {
finalFileName = prefix + ".jpg";
}
// 实际是png文件,但是用jpg后缀,在toFile使用jpg压缩可能出现黑底
if ("png".equals(compressFileHeaders.get(matchExt))) {
finalFileName = prefix + ".png";
}
}
finalFileName = random + "_" + finalFileName;
File result = compressFile(matchExt, oriFile, finalFileName);
long oriLength = oriFile.length();
long length = result.length();
Double cut = (oriLength - length) / (double) oriLength;
DecimalFormat df = new DecimalFormat("#.##%");
String cutStr = df.format(cut);
log.info("fileName:{}, finalFileName:{} , 压缩前:{}, 压缩后:{}, 节省:{}", fileName, finalFileName, oriLength, length, cutStr);
// 压缩失败=0;压缩负优化;png原图大小不在压缩范围
if (length == 0 || length > oriLength || result.equals(oriFile)) {
return oriFile;
}
oriFile.delete();
return result;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 字节数组转Hex
*
* @param bytes 字节数组
* @return Hex
*/
public String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
if (bytes != null) {
for (byte aByte : bytes) {
String hex = byteToHex(aByte);
sb.append(hex);
}
}
return sb.toString();
}
/**
* Byte字节转Hex
*
* @param b 字节
* @return Hex
*/
public String byteToHex(byte b) {
String hexString = Integer.toHexString(b & 0xFF);
//由于十六进制是由0~9、A~F来表示1~16,所以如果Byte转换成Hex后如果是<16,就会是一个字符(比如A=10),通常是使用两个字符来表示16进制位的,
//假如一个字符的话,遇到字符串11,这到底是1个字节,还是1和1两个字节,容易混淆,如果是补0,那么1和1补充后就是0101,11就表示纯粹的11
if (hexString.length() < 2) {
hexString = 0 + hexString;
}
return hexString.toUpperCase();
}
private File compressFile(String matchExt, File oriFile, String finalFileName) throws IOException {
if (compressFileHeaders.get(matchExt).contains("jpg")) {
return compressJpg(oriFile, finalFileName);
} else {
return compressPng(oriFile, finalFileName);
}
}
/**
* 实际可以和compressJpg合并为一个方法,考虑后期png可能单独处理,暂时保留
*/
private File compressPng(File oriFile, String finalFileName) throws IOException {
if (oriFile.length() < minCompressedSize || oriFile.length() > maxCompressedSize) {
return oriFile;
}
Thumbnails.of(oriFile).scale(compressFactor).toFile(finalFileName);
return new File(finalFileName);
}
private File compressJpg(File oriFile, String finalFileName) throws IOException {
Thumbnails.of(oriFile).scale(compressFactor).toFile(finalFileName);
return new File(finalFileName);
}
}