自定义注解记录接口切面log日志入库优化

文章目录

1.前言

书接上回:自定义注解使用AspectJ切面和SpringBoot的Even事件优雅记录业务接口及第三方接口调用日志实现思路

复制代码
https://mp.weixin.qq.com/s/6KojKiWgJSX6P2JBumT9_Q
https://blog.csdn.net/qq_34905631/article/details/145148496?spm=1001.2014.3001.5501

由于之前这篇文章实践之后,发现几个问题:

1.记录的日志信息不完整(过长被截断存储)

2.记录信息占用的存储过多

3.切面记录log日志入库是同步的,针对以上的问题做如下的优化。

2.优化

2.1记录的日志信息不完整

2.1.1字段类型选择

由于之前mysql表的字段设计是text的所以存储长度max是65535字节,验证mysql的text字段长度最大存储长度是65535字节:

sql 复制代码
-- 创建验证表
CREATE TABLE base64_byte_test (
    id INT AUTO_INCREMENT PRIMARY KEY,
    pure_base64 TEXT CHARACTER SET utf8mb4,
    impure_base64 TEXT CHARACTER SET utf8mb4
) ENGINE=InnoDB ROW_FORMAT=DYNAMIC;


-- 插入纯净和非纯净Base64
INSERT INTO base64_byte_test (pure_base64, impure_base64) VALUES (
    REPEAT('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', 1024),
    CONCAT(REPEAT('ABC', 21844), '😊')  -- 混入emoji
);

INSERT INTO base64_byte_test (pure_base64, impure_base64) VALUES (
    REPEAT('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', 2048),
    CONCAT(REPEAT('ABC', 41844), '😊')  -- 混入emoji
);

INSERT INTO base64_byte_test (pure_base64, impure_base64) VALUES (
    REPEAT('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', 512),
    CONCAT(REPEAT('ABC', 512), '😊')  -- 混入emoji
);


INSERT INTO base64_byte_test (pure_base64, impure_base64) VALUES (
    REPEAT('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', 2048),
    CONCAT('😊 ',REPEAT('ABC', 41844))  -- 混入emoji
);

-- 验证字节与字符关系
SELECT 
    id,
    -- 纯净Base64
    CHAR_LENGTH(pure_base64) AS pure_char_count,
    LENGTH(pure_base64) AS pure_byte_count,
    (CHAR_LENGTH(pure_base64) = LENGTH(pure_base64)) AS pure_equal,
    
    -- 非纯净Base64
    CHAR_LENGTH(impure_base64) AS impure_char_count,
    LENGTH(impure_base64) AS impure_byte_count,
    (CHAR_LENGTH(impure_base64) = LENGTH(impure_base64)) AS impure_equal
FROM base64_byte_test;


-- 验证字节与字符关系
SELECT * FROM base64_byte_test;

mysql的REPEAT函数是重复多少次的意思,如果超过65535个字节则会被截断存储,需要注意的是mysql的text的存储还跟字符串的编码有关系,例如uft-8编码的纯净的base64字符串则刚好可以存下65535个字节,又因为纯净uft-8编码的base64的字符串的字节数据字节数跟字符数是相等的,所以该纯净uft-8编码的base64的字符串的length就是字节数的大小。

修改表字段存储类型为:MEDIUMTEXT 、 LONGTEXT、mysql8支持JSON类型或者使用二进制类型存储,但实际项目中推荐使用JSON直接存储切面解析的json字段数据即可或者是使用text类型也足够了,核心原则:没有必要、没有意义的字段数据就不用存,使用更少的存储存储更多的信息,text更大类型或者二进制的类型使用的也很少,保持存储的每条数据的总体大小16kb以内,不要存太多太大的数据进去,浪费存储和查询一个不小心查挂了,存的多存的大,插入就慢消耗CPU、带宽和内存,查询也是一样的道理,直接爆炸BigBang。在计算机的世界中所有一切的数据都是以二进制的形式存在和存储(0/1----就像中国古代的《易经》里面的阴阳,有点玄幻不可思议且包罗万象,不得不佩服古人的智慧)而二进制都是以字节(一个字节Byte占8个bit位)形式来网络传输最终转换为0/1,而操作系统有64、32位、多少位就是:2的多少次方,二进制的表示形式则是使用:原码、补码、反码来表示,然鹅在程序代码中更喜欢使用十六进制更可读的方式来表示计算二进制的这三种码的各种运算操作(位的与、或、非、异或、取反、左移、右移、无符号右移等运算),大端存储:高位字节存在低地址(符合人类阅读数字从左到右的习惯),小端存储:低位字节存在低地址(符合计算机低位开始处理的习惯),这些就涉及到计算机组成原理的知识了,在这里只是简单的带过一下,有的时候确实是忽略了底层,导致会存在很大的困惑,像每种高级语言都会有类型,而且每种类型都会有一个一个长度(占多少字节)像每种编码存储英文和中文使用的字节数是不同的,在计算机的世界所有一且的信息都是二进制0/1,都是符号和数字的映射与转换,所以就有字符码、编码、字体概念如下:

2.1.2字符码、编码、字体

概念 本质 关键说明
字符码(Code Point) 字符的唯一数字ID 如 Unicode 中"中" = U+4E2D(十进制 20013)。⚠️ 中文语境中"字符码"常被误用,此处特指"码点",非存储字节。
编码(Character Encoding) 码点 ↔ 字节的转换规则 规定如何将码点转为存储/传输的字节序列。例: • UTF-8:U+4E2DE4 B8 AD(3字节) • GBK:U+4E2DD6 D0(2字节) • ASCII:仅支持0-127(英文基础)
字体(Font) 字形(Glyph)的视觉数据库 包含字符的矢量/位图轮廓(如"中"在宋体、黑体中的形状差异)。格式:TTF、OTF 等。

📌 关键区分

  • 字符集(如 Unicode) = 全球字符的"户口本"(定义有哪些字符)
  • 字符码(码点) = 户口本中每个字符的"身份证号"
  • 编码 = 身份证号的"加密存储方式"(便于计算机处理)
  • 字体 = 根据身份证号调取的"人物画像"

字符码、编码、字体 的 总结

  • 字符码 = 字符的"数字身份证"(唯一标识)
  • 编码 = 身份证的"存储压缩协议"(字节表示)
  • 字体 = 身份证对应的"人物肖像库"(视觉呈现)

三者环环相扣:无正确编码 → 信息失真;无匹配字体 → 无法看见。理解它们,是解决乱码、多语言支持、跨平台显示问题的基石。

2.1.3 Unicode与不同字符集的关系是什么?

Unicode 与历史上各类字符集的关系,可概括为:Unicode 是旨在终结"字符集碎片化"的全球统一字符集标准,其他字符集是其历史背景、映射对象或局部子集。以下是关键解析:


🌍 Unicode核心定位:从"割裂"到"统一"

问题背景 Unicode 的解决方案
碎片化:ASCII(英文)、GB2312/GBK(简中)、Big5(繁中)、Shift-JIS(日文)、ISO-8859 系列(欧洲)等互不兼容,同一字节在不同系统中含义不同 单一字符集 :为全球所有书写系统字符分配唯一码点 (如 U+4E2D 专指"中"),彻底消除歧义
冲突与重复:同一汉字在不同字符集中编码不同(如"高"在 GBK 与 Big5 中字节不同) 字符统一原则 :CJK 汉字经"汉字等同"处理,简/繁/日/韩变体若字源相同则共享码点(如"骨"在中日韩统一为 U+9AA8
扩展困难:旧字符集容量固定(如 GBK 仅约 2 万字),难以新增字符 可扩展架构:17 个平面(0x0000--0x10FFFF),支持超 140 万个码点,持续纳入新文字(如 Emoji、古籍字符)

🔗 Unicode与具体字符集的关系

字符集类型 与 Unicode 的关系 关键说明
ASCII 完全子集 Unicode 前 128 码点(U+0000--U+007F)与 ASCII 严格一致,UTF-8 编码下 ASCII 字符保持单字节,实现无缝兼容
区域性字符集 (GBK, Big5, Shift-JIS 等) 映射子集 Unicode 通过官方映射表(如 Unicode Han Database)收录其全部字符: • GBK → Unicode:D6 D0(GBK 字节)↔ U+4E2D • Big5 → Unicode:A4 A4U+4E2D ⚠️ 注意:部分私有区字符需特殊处理
ISO/IEC 10646 技术同盟 与 Unicode 字符集码点完全同步(UCS 与 Unicode 合作维护),但 Unicode 标准包含更多实现规范(排序、正则等)
代码页(Code Page) (如 Windows-1252) 历史过渡 旧系统通过代码页管理字符集;现代系统以 Unicode 为内核,代码页仅用于兼容遗留数据(如 Windows 内部转换)

🔄 Unicode 如何"整合"旧字符集?

  1. 收录:将旧字符集所有有效字符纳入 Unicode 字符库(经考证去重)
  2. 映射 :建立标准双向映射表(如 GB18030-2022 ↔ Unicode)
  3. 转换
    • 旧数据 → Unicode:通过映射表将 GBK 字节转为码点(需指定源编码)
    • Unicode → 旧系统:反向映射(若目标字符集不支持该字符,则替换为 ? 或触发错误)
  4. 编码实现 :Unicode 字符集需配合编码方案使用(如 UTF-8 存储"中"为 E4 B8 AD),而旧字符集常"字符集+编码"捆绑(如 GBK 本身定义字节规则)

⚠️ 重要澄清(避免常见误解)

误区 正解
"Unicode 是一种编码" ❌ Unicode 是字符集标准 ;UTF-8/UTF-16/UTF-32 是其实现编码方案
"Unicode 包含所有字体" ❌ Unicode 定义字符标识,字体负责字形渲染(同一码点在不同字体中显示不同)
"旧字符集已淘汰" ⚠️ 遗留系统/文件仍广泛使用(如日文 Shift-JIS 文档),但新开发强制推荐 UTF-8
"Unicode 解决所有乱码" ❌ 乱码仍可能发生:① 编码识别错误(如 UTF-8 文件误用 GBK 打开)② 字体缺失字形

💡现实意义

  • 开发者:坚持"存储/传输用 UTF-8,内部处理用 Unicode 码点",避免编码转换陷阱
  • 用户:遇到乱码时,优先检查文件编码设置(而非字体)
  • 历史视角:Unicode 是信息全球化基石------没有它,跨语言网页、多语言 App、Emoji 互通均不可能实现

🌐 一句话总结

Unicode 不是"另一个字符集",而是终结字符集战争的终极框架------它将所有历史字符集纳入统一坐标系,使计算机真正理解"人类书写的所有符号"。旧字符集是地图上的局部区域,Unicode 是覆盖全球的经纬网格。

了解了以上的信息有啥用:本质上还是一种符号到数字的一种映射转换,Unicode是一种字符标准不是一种编码,它是所有字符的统一身份证的字典,字符在Unicode身份证字典下的那个身份证的数字是全球唯一的,但是Unicode的其它编码子集实现(ASCII、UTF-8/16/32、GBK、GBK2312、IOS8859-1)[字节存储实现],字体则是身份证对应的符号长啥样子,所以就相当于你身份证号对应的是一个独一无二的人,全人类相当于Unicode标准、中国人(黄种人)、美国人(白人、黑人,,,),,,,,亚洲人、欧洲人、美洲人、非洲人,,,,,这些不同按照不同国家、名族、地域划分的人种则是对应不同人的实现,本质上是人类这个物种的基因23条染色体(基因密码),不同人种则是不同的基因表达的实现,字体则相当于你每天穿的衣服或者说是去不同照相馆照相一样(呈现出来的样子不一样而已,但都是你这个人),乱码好比是你跟一个不懂中文的老外讲中文,他根本听不懂。还有之前遇到一个itextpdf字体的问题,明明我都加载了对应的字体了,但是输入参数之后生成的pdf文件里面的字体显示是乱码的,后面我看了下itext的源码打了断点调试之后才发现,从参数pdf文档上复制到postman执行的参数里面有一个汉字的编码乱码了是一个ASCII的编码,这就导致了根据这个码去对应的front字体库里面的编码查找不到对应的字体就显示乱码了,这个问题也是一个奇葩的问题,就相当于说这个字的码不是一个Unicode标准的码,而是一个ASCII的编码,然后去front字体的编码文件里面查找就找不到,itextpdf的字体匹配是一个字符一个字符的去字体文件里面匹配的,这种奇葩问题也是很奇葩,搞了好久才搞清楚是啥问题导致字体没有正确加载么。

2.2.记录信息占用的存储过多

使用zstd无字典压缩压缩转utf-8的纯base64编码,然后切面判断接口字段的json参数使用zstd压缩转utf-8的纯base64编码的长度小于等于65535才入库,超过65535则丢弃,该方案可以降低一半多的存储保存记录更多跟完整的信息,如果mysql表字段使用的是json可以直接存,或者使用二进制的格式,直接保存一个zstd压缩之后的byte[]数组,这种可以减少70%-80%的存储空间的,而本文的方案是在转了一次base64编码,所以就没有直接存二进制更节省空间,但是也不影响。

ZstdUtils 轻量级Zstd压缩工具类(无字典方式)

依赖:

xml 复制代码
        <dependency>
            <groupId>com.github.luben</groupId>
            <artifactId>zstd-jni</artifactId>
            <version>1.5.5-10</version>
        </dependency>

源码:

java 复制代码
package xxx.xxx.util;

import com.github.luben.zstd.Zstd;
import com.github.luben.zstd.ZstdInputStream;
import com.github.luben.zstd.ZstdOutputStream;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Base64;
import java.util.Objects;

/**
 * 轻量级Zstd压缩工具类(无字典方式)
 */
@Slf4j
public class ZstdUtils {

    // 默认压缩级别 - 平衡速度和压缩率
    private static final int DEFAULT_COMPRESSION_LEVEL = 5;

    // 流式处理缓冲区大小 (8KB)
    private static final int BUFFER_SIZE = 8192;

    /**
     * 压缩JSON字符串
     *
     * @param str 字符串
     * @return 压缩后的字节数组
     * @throws RuntimeException 压缩失败时抛出
     */
    public static byte[] compress(String str) {
        if (str == null || str.isEmpty()) {
            return new byte[0];
        }

        try {
            byte[] input = str.getBytes(StandardCharsets.UTF_8);
            return compress(input);
        } catch (Exception e) {
            throw new RuntimeException("Failed to compress str: " + e.getMessage(), e);
        }
    }

    /**
     * 压缩字节数组
     *
     * @param input 输入字节数组
     * @return 压缩后的字节数组
     * @throws RuntimeException 压缩失败时抛出
     */
    public static byte[] compress(byte[] input) {
        if (input == null || input.length == 0) {
            return new byte[0];
        }
        try {
            // 获取最大压缩后大小
            long maxCompressedSize = Zstd.compressBound(input.length);
            byte[] compressed = new byte[(int) maxCompressedSize];
            // 执行压缩
            long compressedSize = Zstd.compress(compressed, input, DEFAULT_COMPRESSION_LEVEL);
            // 检查错误
            if (Zstd.isError(compressedSize)) {
                throw new RuntimeException("Compression failed: " + Zstd.getErrorName(compressedSize));
            }
            // 创建精确大小的结果数组
            byte[] result = new byte[(int) compressedSize];
            System.arraycopy(compressed, 0, result, 0, (int) compressedSize);
            return result;
        } catch (Exception e) {
            throw new RuntimeException("Compression failed: " + e.getMessage(), e);
        }
    }

    /**
     * 解压字节数组
     *
     * @param compressed 压缩数据
     * @return 原始字符串
     * @throws RuntimeException 解压失败时抛出
     */
    public static String decompress(byte[] compressed) {
        if (compressed == null || compressed.length == 0) {
            return "";
        }
        try {
            byte[] decompressed = decompressToBytes(compressed);
            return new String(decompressed, StandardCharsets.UTF_8);
        } catch (Exception e) {
            throw new RuntimeException("Failed to decompress data: " + e.getMessage(), e);
        }
    }

    /**
     * 解压为字节数组
     *
     * @param compressed 压缩数据
     * @return 解压后的字节数组
     * @throws RuntimeException 解压失败时抛出
     */
    public static byte[] decompressToBytes(byte[] compressed) {
        if (compressed == null || compressed.length == 0) {
            return new byte[0];
        }
        try {
            // 获取解压后大小
            long decompressedSize = Zstd.decompressedSize(compressed);
            if (decompressedSize == 0) {
                // 无法确定大小时,使用保守估计 (压缩数据的4倍)
                decompressedSize = compressed.length * 4L;
            }
            // 确保不会分配过大内存
            if (decompressedSize > Integer.MAX_VALUE) {
                throw new OutOfMemoryError("Decompressed data too large: " + decompressedSize + " bytes");
            }
            byte[] decompressed = new byte[(int) decompressedSize];
            long actualSize = Zstd.decompress(decompressed, compressed);
            // 检查错误
            if (Zstd.isError(actualSize)) {
                throw new RuntimeException("Decompression failed: " + Zstd.getErrorName(actualSize));
            }
            // 如果实际大小与预估不同,创建精确大小的数组
            if (actualSize != decompressedSize) {
                byte[] result = new byte[(int) actualSize];
                System.arraycopy(decompressed, 0, result, 0, (int) actualSize);
                return result;
            }
            return decompressed;
        } catch (Exception e) {
            throw new RuntimeException("Decompression failed: " + e.getMessage(), e);
        }
    }

    /**
     * 压缩文件 (流式处理,适合大文件)
     *
     * @param inputPath  输入文件路径
     * @param outputPath 输出文件路径
     * @throws IOException IO异常
     */
    public static void compressFile(Path inputPath, Path outputPath) throws IOException {
        try (InputStream in = new BufferedInputStream(Files.newInputStream(inputPath));
             OutputStream out = new BufferedOutputStream(Files.newOutputStream(outputPath))) {
            compressStream(in, out);
        }
    }

    /**
     * 解压文件 (流式处理,适合大文件)
     *
     * @param inputPath  压缩文件路径
     * @param outputPath 输出文件路径
     * @throws IOException IO异常
     */
    public static void decompressFile(Path inputPath, Path outputPath) throws IOException {
        try (InputStream in = new BufferedInputStream(Files.newInputStream(inputPath));
             OutputStream out = new BufferedOutputStream(Files.newOutputStream(outputPath))) {
            decompressStream(in, out);
        }
    }

    /**
     * 压缩数据流 (内存高效)
     *
     * @param inputStream  输入流
     * @param outputStream 输出流
     * @throws IOException IO异常
     */
    /**
     * 内存高效的 Zstd 流式压缩 - 正确实现
     */
    public static void compressStream(InputStream inputStream, OutputStream outputStream) throws IOException {
        final int BUFFER_SIZE = 8192; // 8KB 缓冲区
        try (
                // ✅ 专业流式压缩 - 自动处理帧格式
                ZstdOutputStream zstdOut = new ZstdOutputStream(outputStream)
                        .setLevel(DEFAULT_COMPRESSION_LEVEL)  // 设置压缩级别
                        .setCloseFrameOnFlush(true);          // 确保每个flush都完成帧
                BufferedInputStream bufferedIn = new BufferedInputStream(inputStream, BUFFER_SIZE);
                BufferedOutputStream bufferedOut = new BufferedOutputStream(zstdOut, BUFFER_SIZE)
        ) {
            byte[] buffer = new byte[BUFFER_SIZE];
            int bytesRead;

            // 标准流式复制 - 自动处理所有 Zstd 帧细节
            while ((bytesRead = bufferedIn.read(buffer)) != -1) {
                if (bytesRead > 0) {
                    bufferedOut.write(buffer, 0, bytesRead);
                    // 可选:定期flush以控制内存使用
                    if (bytesRead < BUFFER_SIZE) {
                        bufferedOut.flush();
                    }
                }
            }
            // 确保所有数据都被压缩和写出
            bufferedOut.flush();
        }
        // 自动关闭所有资源
    }

    /**
     * 内存高效的流式解压缩 - 正确实现
     */
    public static void decompressStream(InputStream inputStream, OutputStream outputStream) throws IOException {
        // ✅ 使用专门设计的 ZstdInputStream
        try (ZstdInputStream zstdIn = new ZstdInputStream(inputStream);
             BufferedOutputStream bufferedOut = new BufferedOutputStream(outputStream)) {
            byte[] buffer = new byte[BUFFER_SIZE];
            int bytesRead;
            // 标准流式复制
            while ((bytesRead = zstdIn.read(buffer)) != -1) {
                bufferedOut.write(buffer, 0, bytesRead);
            }
            bufferedOut.flush();
        }
        // 自动关闭资源
    }

    /**
     * 估算压缩后的大小 (不执行实际压缩)
     *
     * @param str 字符串
     * @return 估算的压缩后大小
     */
    public long estimateCompressedSize(String str) {
        if (str == null || str.isEmpty()) {
            return 0;
        }
        byte[] input = str.getBytes(StandardCharsets.UTF_8);
        return Zstd.compressBound(input.length);
    }

    /**
     * 压缩效果
     *
     * @param originalSize
     * @param compressedSize
     * @return
     */
    public static String compressResult(int originalSize, int compressedSize) {
        // 3. 计算正确指标
        //压缩比
        double compressionRatio = (double) originalSize / compressedSize;
        //空间节省率
        double spaceSavings = (1 - (double) compressedSize / originalSize) * 100;
        //记录压缩后占比(不推荐作为主要指标)
        double compressedRatio = (double) compressedSize / originalSize * 100; // 压缩后占比
        StringBuffer sb = new StringBuffer();
        sb.append("原始大小:【").append(originalSize).append("字节】, 压缩后大小:【").append(compressedSize).append("字节】, 压缩比:【").append(String.format("%.2f:1", compressionRatio)).append("】").append(String.format(", 空间节省率: 【%.2f%%", spaceSavings)).append("】").append(String.format(", 压缩后占比: 【%.2f%%】", compressedRatio));
        return sb.toString();
    }

    /**
     * zstd压缩
     *
     * @param str
     * @return
     */
    public static byte[] zstdCompress(String str) {
        log.info("zstdCompress.str:{}", str);
        // 1. 压缩
        byte[] compressed = ZstdUtils.compress(str);
        log.info("zstdCompress压缩效果: {}", ZstdUtils.compressResult(str.getBytes(StandardCharsets.UTF_8).length, compressed.length));
        return compressed;
    }

    /**
     * base64编码(默认小写,不用转大写,否则配合使用zstd解码会失败)
     *
     * @param bytes
     * @param originalStr
     * @return
     */
    public static String bytesToBase64Str(byte[] bytes, String originalStr) {
        // 一行代码终极解决方案(Java 8+)
        String base64Str = new String(Base64.getEncoder().encode(bytes), StandardCharsets.UTF_8);
        log.info("bytesToBase64Str.base64Str(默认小写):{}", base64Str);
        log.info("base64Str压缩效果: {}", ZstdUtils.compressResult(originalStr.getBytes(StandardCharsets.UTF_8).length, base64Str.getBytes(StandardCharsets.UTF_8).length));
        return base64Str;
    }

    /**
     * base64解码
     *
     * @param base64Str
     * @return
     */
    public static byte[] base64ToBytes(String base64Str) {
        log.info("base64ToBytes.base64Str:{}", base64Str);
        return Base64.getDecoder().decode(base64Str.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 校验纯base64(utf-8)字符串在mysql8+数据库utf8mb4字符集下,TEXT字段最大容量为65,535字节,可以刚好存下
     * Base64字符串在UTF-8编码下的字符数严格等于字节数
     * TEXT: 65,535(字节)
     * MEDIUMTEXT: 16,777,215(字节)
     * LONGTEXT: 4,294,967,295(字节)
     * 二进制:无上限
     * 还跟mysql配置可接受最大数据包有关系:
     *
     * @param base64Str
     * @return
     */
    public static Boolean checkBase64ToDbTextTypeMaxStoreLimit(String base64Str) {
        if (StringUtils.isNotEmpty(base64Str)) {
            log.info("checkBase64ToDbTextTypeMaxStoreLimit.base64Str.length:{}", base64Str.length());
            //TEXT
            if (base64Str.length() > 65535) {
                return Boolean.TRUE;
            } else if (base64Str.length() > 16777215) {
                //MEDIUMTEXT
                return Boolean.TRUE;
            } /*else if (base64Str.length() > 4294967295L) {
                //LONGTEXT 太大了,没有必要存
            }*/
        }
        return Boolean.FALSE;
    }

    /**
     * 使用 URL 安全的 Base64 编码器,不添加填充
     *
     * @param str
     * @param charset
     * @return
     */
    public static String encodeUrlSafe1(String str, Charset charset) {
        if (StringUtils.isEmpty(str)) {
            return "";
        }
        if (Objects.nonNull(charset)) {
            Base64.getUrlEncoder().withoutPadding().encodeToString(str.getBytes(charset));
        }
        // 使用 URL 安全的 Base64 编码器,不添加填充
        return Base64.getUrlEncoder().withoutPadding().encodeToString(str.getBytes(StandardCharsets.UTF_8));
    }

    /**
     * 使用 URL 安全的 Base64 编码器,不添加填充
     *
     * @param data
     * @return
     */
    public static String encodeUrlSafe2(byte[] data) {
        // 使用 URL 安全的 Base64 编码器,不添加填充
        return Base64.getUrlEncoder().withoutPadding().encodeToString(data);
    }

    /**
     * URL 安全的解码器能自动处理带或不带填充的数据
     *
     * @param encoded
     * @return
     */
    public static byte[] decodeUrlSafe(String encoded) {
        // URL 安全的解码器能自动处理带或不带填充的数据
        return Base64.getUrlDecoder().decode(encoded);
    }

    /**
     * 将解码后的字节数组转换为字符串(如果原始数据是文本)
     */
    public static String decodeUrlSafeToString(String encoded, Charset charset) {
        byte[] decodedBytes = decodeUrlSafe(encoded);
        if (Objects.nonNull(charset)) {
            return new String(decodedBytes, charset);
        }
        return new String(decodedBytes, StandardCharsets.UTF_8);
    }


    /**
     * 字符串转Hex
     */
    public static String strToHex(String str, Charset charset) {
        byte[] originalBytes = null;
        if (Objects.nonNull(charset)) {
            originalBytes = str.getBytes(charset);
        } else {
            originalBytes = str.getBytes(StandardCharsets.UTF_8);
        }
        // 2. 转换为十六进制字符串
        StringBuilder hexString = new StringBuilder();
        for (byte b : originalBytes) {
            hexString.append(String.format("%02x", b));
        }
        return hexString.toString();
    }

    /**
     * hex转bytes数组
     *
     * @param hexStr
     * @return
     */
    public static byte[] hexToBytes(String hexStr) {
        // 2. 十六进制转字节数组
        byte[] bytes = new byte[hexStr.length() / 2];
        for (int i = 0; i < bytes.length; i++) {
            bytes[i] = (byte) ((Character.digit(hexStr.charAt(i * 2), 16) << 4)
                    + Character.digit(hexStr.charAt(i * 2 + 1), 16));
        }
        return bytes;
    }

    /**
     * byte数组转String
     *
     * @param bytes
     * @param charset
     * @return
     */
    public static String bytesToString(byte[] bytes, Charset charset) {
        if (Objects.nonNull(charset)) {
            return new String(bytes, charset);
        }
        return new String(bytes, StandardCharsets.UTF_8);
    }

}

2.3切面记录log日志入库是同步的

改为异步(提升系统响应性能),省略

3.总结

本次分享到此结束,希望我的分享对你有所启发和帮助,请一键三连,么么么哒!

相关推荐
人道领域2 小时前
javaWeb从入门到进阶(maven高级进阶)
java·spring·maven
一路向北⁢2 小时前
Spring Boot 3 整合 SSE (Server-Sent Events) 企业级最佳实践(一)
java·spring boot·后端·sse·通信
风象南2 小时前
JFR:Spring Boot 应用的性能诊断利器
java·spring boot·后端
爱吃山竹的大肚肚2 小时前
微服务间通过Feign传输文件,处理MultipartFile类型
java·spring boot·后端·spring cloud·微服务
_周游2 小时前
Java8 API文档搜索引擎_使用内存缓冲区优化
java·搜索引擎·intellij-idea
twj_one2 小时前
java中23种设计模式
java·开发语言·设计模式
tsyjjOvO3 小时前
JDBC(Java Database Connectivity)
java·数据库
qq_12498707533 小时前
基于springboot的尿毒症健康管理系统的设计与实现(源码+论文+部署+安装)
java·spring boot·spring·毕业设计·计算机毕业设计
黎子越3 小时前
python相关练习
java·前端·python