package com.multictrl.common.utils; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.json.JSONArray; import cn.hutool.json.JSONObject; import cn.hutool.json.JSONUtil; import com.alibaba.fastjson2.JSON; import com.alibaba.fastjson2.filter.NameFilter; import com.drew.imaging.jpeg.JpegMetadataReader; import com.drew.imaging.jpeg.JpegProcessingException; import com.drew.imaging.mp4.Mp4MetadataReader; import com.drew.metadata.Directory; import com.drew.metadata.Metadata; import com.drew.metadata.Tag; import com.multictrl.common.exception.ExceptionUtils; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.math.BigDecimal; import java.math.RoundingMode; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.List; import java.util.Locale; /** * 通用工具 * * @author Sdy * @since 1.0.0 2026/4/24 */ @Slf4j public class Utils { /** * 线程休息n毫秒 */ public static void sleep(long millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { log.error("线程睡眠异常:{}", ExceptionUtils.getErrorStackTrace(e)); } } /* * 驼峰转下划线 */ public static JSONObject beanToSnakeJson(Object obj) { JSONObject camelJson = JSONUtil.parseObj(obj); JSONObject snakeJson = new JSONObject(); for (String key : camelJson.keySet()) { String snakeKey = StrUtil.toUnderlineCase(key); // 驼峰转下划线 Object value = camelJson.get(key); // 递归处理嵌套 JSONObject 或 JSONArray if (value instanceof JSONObject) { value = beanToSnakeJson(value); // 递归转换 } else if (value instanceof JSONArray) { JSONArray arr = new JSONArray(); for (Object item : (JSONArray) value) { if (item instanceof JSONObject) { arr.add(beanToSnakeJson(item)); } else { arr.add(item); } } value = arr; } snakeJson.set(snakeKey, value); } return snakeJson; } /* * 阿里FastJson2驼峰转下划线 */ public static com.alibaba.fastjson2.JSONObject beanToSnakeJsonFastJson(Object o) { NameFilter snakeCaseFilter = (object, name, value) -> name.replaceAll("([a-z])([A-Z])", "$1_$2").toLowerCase(); return com.alibaba.fastjson2.JSONObject.parseObject(JSON.toJSONString(o, snakeCaseFilter)); } /** * 获取最后两个字符 * "a/b/c/d" "c/d" */ public static String getLastTwoSegments(String str, String separator) { if (StrUtil.isBlank(str)) return str; // 按分隔符切割 List parts = StrUtil.split(str, separator); if (parts.size() <= 2) { return str; // 不足两段则原样返回 } else { // 取最后两段 List lastTwo = parts.subList(parts.size() - 2, parts.size()); return CollUtil.join(lastTwo, String.valueOf(separator)); } } /** * 文件转md5 */ public static String md5File(String path) { StringBuilder sb = new StringBuilder(); try { MessageDigest md = MessageDigest.getInstance("MD5"); md.update(FileUtils.readFileToByteArray(new File(path))); byte[] b = md.digest(); int d; for (byte value : b) { d = value; if (d < 0) { d = value & 0xff; // 与上一行效果等同 // i += 256; } if (d < 16) { sb.append("0"); } sb.append(Integer.toHexString(d)); } } catch (NoSuchAlgorithmException | IOException e) { log.error("md5 fail", e); } return sb.toString(); } /** * 获取文本的md5 */ public static String md5Txt(String context) { try { MessageDigest md = MessageDigest.getInstance("MD5"); byte[] buffer = context.getBytes(); md.update(buffer, 0, buffer.length); byte[] digest = md.digest(); StringBuilder sb = new StringBuilder(); for (byte b : digest) { sb.append(String.format("%02x", b)); } return sb.toString(); } catch (NoSuchAlgorithmException exception) { log.error("md5 fail", exception); } return null; } /** * 获取图片的Windows XP Keywords */ public static String getImageWindowsXpKeywords(String mediaPath) { Metadata metadata; try { metadata = JpegMetadataReader.readMetadata(new File(mediaPath)); for (Directory exif : metadata.getDirectories()) { for (Tag tag : exif.getTags()) { if ("Windows XP Keywords".equals(tag.getTagName())) { return tag.getDescription(); } } } } catch (JpegProcessingException | IOException e) { log.error("error", e); } return null; } public static long getImageTime(String mediaPath) { SimpleDateFormat sdf = new SimpleDateFormat("yyyy:MM:dd HH:mm:ss"); Metadata metadata; try { metadata = JpegMetadataReader.readMetadata(new File(mediaPath)); for (Directory exif : metadata.getDirectories()) { for (Tag tag : exif.getTags()) { if ("Date/Time".equals(tag.getTagName())) { return sdf.parse(tag.getDescription()).getTime(); } } } } catch (JpegProcessingException | IOException e) { log.error("error", e); } catch (ParseException e) { throw new RuntimeException(e); } return 0; } public static long getVideoTime(String videoPath) { Metadata metadata; try { metadata = Mp4MetadataReader.readMetadata(new File(videoPath)); for (Directory exif : metadata.getDirectories()) { for (Tag tag : exif.getTags()) { if ("Creation Time".equals(tag.getTagName())) { try { SimpleDateFormat sdf = new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", Locale.US); Date d = sdf.parse(tag.getDescription()); return d.getTime(); } catch (ParseException ignored) { } } } } } catch (IOException e) { log.error("error", e); } return 0; } /** * 格式化double,保留指定位小数(四舍五入) * * @param value 原始数值 * @param scale 小数位数 * @return 格式化后的字符串 */ private static String format(double value, int scale) { if (Double.isNaN(value) || Double.isInfinite(value)) { return "0"; } BigDecimal bd = BigDecimal.valueOf(value); bd = bd.setScale(scale, RoundingMode.HALF_UP); return bd.stripTrailingZeros().toPlainString(); } /** * 字节数格式化为人性化字符串(自动选择B, KB, MB, GB, TB) * 1024进制 * * @param bytes 字节数 * @return 如 "1.45 GB" */ public static String formatBytes(long bytes) { if (bytes < 0) { return "0 B"; } if (bytes < 1024) { return bytes + " B"; } int exp = (int) (Math.log(bytes) / Math.log(1024)); char pre = "KMGTPE".charAt(exp - 1); double result = bytes / Math.pow(1024, exp); return format(result, 2) + " " + pre + "B"; } }