常用的工具类
我的技术学习物语果然有问题最后更新

常用的工具类

阅读0访客0字数503

前言

总结记录一下在开发中经常使用到的一些工具类。

MD5加密

用户的密码一般都会进行加密后在存储到数据库当中,所以一般用到MD5这种加密算法。

这种加密算法最大的特点就是不可逆,只能加密不能解密。

这是挺简单的一种加密算法了,之后还可以考虑加盐和哈希散列,进行更复杂的加密手段。

public class Md5Utils {
    public static String encrypt(String strSrc) {
        try {
            char hexChars[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8',
                    '9', 'a', 'b', 'c', 'd', 'e', 'f' };
            byte[] bytes = strSrc.getBytes();
            MessageDigest md = MessageDigest.getInstance("MD5");
            md.update(bytes);
            bytes = md.digest();
            int j = bytes.length;
            char[] chars = new char[j * 2];
            int k = 0;
            for (int i = 0; i < bytes.length; i++) {
                byte b = bytes[i];
                chars[k++] = hexChars[b >>> 4 & 0xf];
                chars[k++] = hexChars[b & 0xf];
            }
            return new String(chars);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            throw new RuntimeException("MD5加密出错+" + e);
        }
    }
}

雪花算法

对于MySQL的主键问题,一般都需要使用自增的主键。但是只是从1自增就会因为分库分表产生主键相同的情况。

所以这次使用雪花算法生成一个分布式全局唯一ID

首先要知道雪花算法生成的ID是怎么样的。

分布式ID一般都是Long类型,所以会有64位。

  • 第一部分是一个bit位,是一个标识部分,在java中由于long的最高位是符号位,正数是0,负数是1,一般生成的ID为正数,所以固定为0
  • 第二部分是时间戳,该部分占41bit,这个是毫秒级的时间
  • 第三部分是工作机器ID,占10bit
  • 第四部分是序列号,占12bit,支持同一毫秒内同一个节点可以生成4096个ID
/**
 * <p>
 *     雪花算法,分布式全局唯一ID
 * </p>
 * @ClassName SnowFlake
 * @Author yww
 * @date 2021/4/24 18:39
 **/
public class SnowFlake {

    /**
     *  起始的时间戳
     */
    private final static long START_STMP = 1480166465631L;
    /**
     *  序列号占用的位数
     */
    private final static long SEQUENCE_BIT = 12;
    /**
     *  机器标识占用的位数
     */
    private final static long MACHINE_BIT = 5;
    /**
     *  数据中心占用的位数
     */
    private final static long DATACENTER_BIT = 5;
    /**
     *  每一部分的最大值
     */
    private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);
    private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);
    private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);
    /**
     *  每一部分向左的位移
     */
    private final static long MACHINE_LEFT = SEQUENCE_BIT;
    private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
    private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;
    /**
     *  数据中心
     */
    private long datacenterId;
    /**
     *  机器标识
     */
    private long machineId;
    /**
     *  序列号
     */
    private long sequence = 0L;
    /**
     *  上一次时间戳
     */
    private long lastStmp = -1L;

    public SnowFlake(long datacenterId, long machineId) {
        if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {
            throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");
        }
        if (machineId > MAX_MACHINE_NUM || machineId < 0) {
            throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");
        }
        this.datacenterId = datacenterId;
        this.machineId = machineId;
    }

    /**
     *  产生下一个ID
     */
    public synchronized long nextId() {
        long currStmp = getNewstmp();
        if (currStmp < lastStmp) {
            throw new RuntimeException("Clock moved backwards.  Refusing to generate id");
        }

        if (currStmp == lastStmp) {
            //相同毫秒内,序列号自增
            sequence = (sequence + 1) & MAX_SEQUENCE;
            //同一毫秒的序列数已经达到最大
            if (sequence == 0L) {
                currStmp = getNextMill();
            }
        } else {
            //不同毫秒内,序列号置为0
            sequence = 0L;
        }

        lastStmp = currStmp;

        return          // 时间戳部分
                (currStmp - START_STMP) << TIMESTMP_LEFT
                        // 数据中心部分
                | datacenterId << DATACENTER_LEFT
                        // 机器标识部分
                | machineId << MACHINE_LEFT
                        // 序列号部分
                | sequence;
    }

    private long getNextMill() {
        long mill = getNewstmp();
        while (mill <= lastStmp) {
            mill = getNewstmp();
        }
        return mill;
    }

    private long getNewstmp() {
        return System.currentTimeMillis();
    }

    /**
     *  测试
     */
    public static void main(String[] args) {

        SnowFlake snowFlake = new SnowFlake(2, 3);

        for (int i = 0; i < (1 << 12); i++) {
            System.out.println(snowFlake.nextId());
        }

    }


}

参考于:https://github.com/beyondfengyu/SnowFlake

JWT工具类

引入依赖

    <!--  jwt  -->
    <dependency>
        <groupId>com.auth0</groupId>
        <artifactId>java-jwt</artifactId>
        <version>${jwt.version}</version>
    </dependency>

工具类

package com.yww.management.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.*;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * <p>
 *      Token工具类
 * 1. Header,记录令牌类型和签名算法
 * 2. payload,携带用户信息
 *      (1) iss(issuer), 签发者
 *      (2) sub(subject), 面向的主体
 *      (3) aud(audience), 接收方
 *      (4) nbf(notBefore), 开始生效生效时间戳
 *      (5) exp(expiresAt), 过期时间戳
 *      (6) iat(issuedAt ), 签发时间
 *      (7) jti(jwtId), 唯一标识
 * 3. signature, 签名,防止Token被篡改
 * </p>
 *
 * @ClassName TokenUtil
 * @Author yww
 * @Date 2022/10/15 14:31
 */
public class TokenUtil {

    /**
     * 生成Token
     *
     * @return  Token
     */
    public static String genToken(String secret) {
        // 设置Token头部(不设置也会默认有这两个值)
        Map<String, Object> header = new HashMap<String, Object>(2) {
            private static final long serialVersionUID = 1L;
            {
                put("alg", "HMAC512");
                put("typ", "JWT");
            }
        };
        // 设置需要放在Token的有效载荷
        Map<String, Object> payload = new HashMap<String, Object>(3) {
            private static final long serialVersionUID = 1L;
            {
                put("name", "yww");
                put("num", 1141950370);
            }
        };

        Date now = new Date();
        Date exp = new Date(now.getTime() + 10);

        return JWT.create()
                // 设置header
                .withHeader(header)
                // 设置payload
                .withIssuer("yww")
                .withSubject("management")
                .withAudience("vue-management")
                .withNotBefore(now)
                .withExpiresAt(exp)
                .withIssuedAt(now)
                .withJWTId("123456")
                .withPayload(payload)
                // 签名
                .sign(Algorithm.HMAC512(secret));
    }

    /**
     * 解析Token
     *
     * @param token Token
     */
    public static void parse(String token) {
        try {
            JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC512("1")).build();
            DecodedJWT decode = jwtVerifier.verify(token);
            System.out.println(decode.getHeader());
            System.out.println(decode.getPayload());
            System.out.println(decode.getSignature());
        } catch (AlgorithmMismatchException e) {
            System.out.println("签名算法不匹配");
        } catch (SignatureVerificationException e) {
            System.out.println("签名无效");
        } catch (TokenExpiredException e) {
            System.out.println("令牌已过期");
        } catch (MissingClaimException e) {
            System.out.println("缺少要验证的声明");
        } catch (IncorrectClaimException e) {
            System.out.println("声明包含的值和预期不符合");
        } catch (JWTVerificationException e) {
            System.out.println("验证中的某一个步骤失败");
        }
    }

}

响应工具类

import cn.hutool.json.JSONUtil;
import com.yww.management.common.Result;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * <p>
 *      用于接口外响应JSON的响应工具类
 * </p>
 *
 * @ClassName ResponseUtil
 * @Author yww
 * @Date 2022/10/12 21:30
 */
public class ResponseUtil {

    /**
     * 用于返回JSON数据
     * @param response 请求响应
     * @param result   响应封装
     */
    public static void response(HttpServletResponse response, Result<Object> result) throws IOException {
        // 设置响应的Header
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Cache-Control","no-cache");
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        // 设置响应码
        response.setStatus(result.getCode());
        // 设置响应内容
        response.getWriter().write(JSONUtil.toJsonStr(result));
        // 将缓存信息刷新到页面
        response.getWriter().flush();
    }

}

IP工具类

这个工具类主要是用于获取请求的IP地址。

import cn.hutool.core.util.StrUtil;

import javax.servlet.http.HttpServletRequest;

/**
 * <p>
 *      IP相关工具类
 * </p>
 *
 * @ClassName IpUtil
 * @Author yww
 * @Date 2022/10/16 14:27
 */
public class IpUtil {

    /**
     * 获取客户端IP
     *
     * @param request   请求
     * @return          IP
     */
    public static String getIpAddr(HttpServletRequest request) {
        if (request == null) {
            return "unknown";
        }
        // 判断x-forwarded-for是否有IP
        String ip = request.getHeader("x-forwarded-for");
        if (isNotUnknown(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        // 判断Proxy-Client-IP是否有IP
        if (isNotUnknown(ip)) {
            ip = request.getHeader("X-Forwarded-For");
        }
        // 判断X-Forwarded-For是否有IP
        if (isNotUnknown(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        // 判断WL-Proxy-Client-IP是否有IP
        if (isNotUnknown(ip)) {
            ip = request.getHeader("X-Real-IP");
        }
        // 判断X-Real-IP是否有IP
        if (isNotUnknown(ip)) {
            ip = request.getRemoteAddr();
        }
        // 若是IP最后为0:0:0:0:0:0:0:1,就是本地回环地址,否则第一个非unknown的IP地址就是真实地址
        return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : getMultistageReverseProxyIp(ip);
    }

    /**
     * 从多级反向代理中获得第一个非unknown IP地址
     *
     * @param ip 获得的IP地址
     * @return 第一个非unknown IP地址
     */
    @SuppressWarnings("all")
    public static String getMultistageReverseProxyIp(String ip) {
        // 多级反向代理检测
        if (ip != null && ip.indexOf(",") > 0) {
            final String[] ips = ip.trim().split(",");
            for (String subIp : ips) {
                if (isNotUnknown(subIp)) {
                    ip = subIp;
                    break;
                }
            }
        }
        return ip;
    }

    /**
     * 检测给定字符串是否为未知,多用于检测HTTP请求相关
     *
     * @param checkString 被检测的字符串
     * @return 是否未知
     */
    public static boolean isNotUnknown(String checkString) {
        return StrUtil.isNotBlank(checkString) && StrUtil.isNotEmpty(checkString) && !"unknown".equalsIgnoreCase(checkString);
    }

}

BigDecimal工具类


import java.math.BigDecimal;
import java.math.RoundingMode;

/**
 * <p>
 *      BigDecimal工具类
 *  用来提供浮点数的精确运算的工具类
 * </p>
 *
 * @ClassName BigDecimalUtil
 * @Author yww
 * @Date 2022/10/25 16:47
 */
public class BigDecimalUtil {

    /**
     *  除法运算默认精度
     */
    private static final int DEFAULT_PRECISION = 10;

    /**
     *  私有化无参构造
     */
    private BigDecimalUtil() {}

    /**
     *  用于浮点数的精确加法
     *
     * @param value1    浮点数1
     * @param value2    浮点数2
     * @return          浮点数1 + 浮点数2
     */
    public static double addUp(double value1, double value2) {
        BigDecimal b1 = BigDecimal.valueOf(value1);
        BigDecimal b2 = BigDecimal.valueOf(value2);
        return b1.add(b2).doubleValue();
    }

    /**
     *  精确减法
     *
     * @param value1    浮点数1
     * @param value2    浮点数2
     * @return          浮点数1 - 浮点数2
     */
    public static double subtract(double value1, double value2) {
        BigDecimal b1 = BigDecimal.valueOf(value1);
        BigDecimal b2 = BigDecimal.valueOf(value2);
        return b1.subtract(b2).doubleValue();
    }

    /**
     *  精确乘法
     *
     * @param value1    浮点数1
     * @param value2    浮点数2
     * @return          浮点数1 * 浮点数2
     */
    public static double multiply(double value1, double value2) {
        BigDecimal b1 = BigDecimal.valueOf(value1);
        BigDecimal b2 = BigDecimal.valueOf(value2);
        return b1.multiply(b2).doubleValue();
    }

    /**
     *  精确除法(使用默认精度)
     *
     * @param value1    浮点数1
     * @param value2    浮点数2
     * @return          浮点数1 / 浮点数2
     */
    public static double div(double value1, double value2) throws IllegalAccessException {
        return div(value1, value2, DEFAULT_PRECISION);
    }

    /**
     *  精确除法(指定精度)
     *  四舍五入
     *
     * @param value1        浮点数1
     * @param value2        浮点数2
     * @param precision     精度
     * @return              浮点数1 / 浮点数2
     */
    public static double div(double value1, double value2, int precision) throws IllegalAccessException {
        if (precision < 0) {
            throw new IllegalAccessException("除法的精度不能小于0");
        }
        return div(value1, value2, precision, RoundingMode.HALF_UP);
    }

    /**
     *  精确除法(指定精度)
     *  指定舍入的策略
     *
     * @param value1        浮点数1
     * @param value2        浮点数2
     * @param precision     精度
     * @param mode          舍入策略
     * @return              浮点数1 / 浮点数2
     */
    public static double div(double value1, double value2, int precision, RoundingMode mode) throws IllegalAccessException {
        if (precision < 0) {
            throw new IllegalAccessException("除法的精度不能小于0");
        }
        BigDecimal b1 = BigDecimal.valueOf(value1);
        BigDecimal b2 = BigDecimal.valueOf(value2);
        return b1.divide(b2, precision, mode).doubleValue();
    }
    
}

断言工具类

hutool里也有很多断言的工具类,但是抛出的异常不是自己定义的,所以为了指定抛出的异常,需要自己在定义一个断言工具类。


import cn.hutool.core.util.StrUtil;
import com.yww.management.common.exception.GlobalException;

/**
 * <p>
 *      断言工具类
 * </p>
 *
 * @ClassName AssertUtils
 * @Author yww
 * @Date 2022/10/20 11:27
 */
public class AssertUtils {

    /**
     * 私有化无参构造器
     */
    private AssertUtils() {}

    /**
     * 如果对象为{@code null}, 则抛出异常
     *
     * @param object 要判断的对象
     * @throws GlobalException  全局异常类
     */
    public static void notNull(Object object) throws GlobalException {
        notNull(object, "不能处理空对象");
    }

    /**
     * 如果对象为{@code null}, 则抛出异常
     *
     * @param object  要判断的对象
     * @param message 断言失败时的错误信息
     * @throws GlobalException  全局异常类
     */
    public static void notNull(Object object, String message) throws GlobalException {
        if (object == null) {
            throw new GlobalException(message);
        }
    }

    /**
     * 如果字符串为{@code null}、空字符串或仅包含空白字符, 则抛出异常
     *
     * @param text 要进行检查的字符串
     * @throws GlobalException  全局异常类
     */
    public static void hasText(String text) throws GlobalException {
        hasText(text, "此参数不能为空字符串");
    }

    /**
     * 如果字符串为{@code null}、空字符串或仅包含空白字符, 则抛出异常
     *
     * @param text    要进行检查的字符串
     * @param message 断言失败时的错误信息
     * @throws GlobalException  全局异常类
     */
    public static void hasText(String text, String message) throws GlobalException {
        if (StrUtil.isBlank(text)) {
            throw new GlobalException(message);
        }
    }
    
}

ThreadLocal工具类


import java.util.HashMap;
import java.util.Map;

/**
 * <p>
 *      ThreadLocal工具类
 * </p>
 *
 * @ClassName ThreadLocalUtil
 * @Author yww
 * @Date 2022/10/21 14:27
 */
@SuppressWarnings("unchecked")
public class ThreadLocalUtil {

    /**
     *  ThreadLocalMap
     */
    private static final ThreadLocal<Map<String, Object>> THREAD_LOCAL_MAP = ThreadLocal.withInitial(HashMap::new);

    /**
     * 私有化无参构造器
     */
    private ThreadLocalUtil() {}

    /**
     * 根据指定的key获取当前线程相关的变量值
     *
     * @param key 变量的key
     * @param <T> 变量值的具体类型
     * @return 若无此key对应的变量值, 则返回{@code null}
     * @throws ClassCastException 若接收此返回值的变量类型与上下文保存的值的实际类型不匹配, 则抛出异常
     */
    public static <T> T get(String key) {
        return (T) THREAD_LOCAL_MAP.get().get(key);
    }

    /**
     * 根据指定的key获取当前线程相关的变量值, 若为{@code null}则返回指定的默认值
     *
     * @param key          变量的key
     * @param defaultValue 默认值
     * @param <T>          变量值的具体类型
     * @return 若无此key对应的变量值, 则返回defaultValue
     * @throws ClassCastException 若接收此返回值的变量类型与上下文保存的值的实际类型不匹配, 则抛出异常
     */
    public static <T> T get(String key, T defaultValue) {
        T value = get(key);
        return value == null ? defaultValue : value;
    }

    /**
     * 设置线程相关上下文的变量值
     *
     * @param key   变量的key
     * @param value 变量值
     */
    public static void set(String key, Object value) {
        THREAD_LOCAL_MAP.get().put(key, value);
    }

    /**
     * 删除指定key的变量
     *
     * @param key 变量的key
     */
    public static void remove(String key) {
        THREAD_LOCAL_MAP.get().remove(key);
    }

    /**
     * 清除当前线程相关的上下文
     */
    public static void close() {
        THREAD_LOCAL_MAP.remove();
    }

}

分页工具类

封装分页请求对象

如果需要添加其他的属性筛选,只需要继承该封装对象即可。


import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

/**
 * <p>
 *      分页查询请求对象
 * </p>
 *
 * @ClassName PageVo
 * @Author yww
 * @Date 2022/10/24 21:18
 */
@SuppressWarnings("all")
@JsonIgnoreProperties(ignoreUnknown = true)
public class PageVo {

    /**
     * 每页显示行数默认为10
     */
    private static final int DEFAULT_SIZE = 10;

    /**
     *  当前页码, 首页为1
     */
    private int page = 1;

    /**
     *  每页记录条数
     */
    private int size = DEFAULT_SIZE;

    /**
     *  排序字段名,asc,desc
     */
    private String sort;

    /**
     *  排序方向
     */
    private String dir;

    public static PageVo of(int page, int size) {
        PageVo pageReqVo = new PageVo();
        pageReqVo.setPage(page);
        pageReqVo.setSize(size);
        return pageReqVo;
    }

    public PageVo() {}

    @JsonIgnore
    public int getOffset() {
        return (getPage() - 1) * getSize();
    }

    public int getPage() {
        return page > 0 ? page : 1;
    }

    public void setPage(int page) {
        this.page = page;
    }

    public int getSize() {
        return size > 0 ? size : DEFAULT_SIZE;
    }

    public void setSize(int size) {
        this.size = size;
    }

    public String getSort() {
        return sort;
    }

    public void setSort(String sort) {
        this.sort = sort;
    }

    public String getDir() {
        return dir;
    }

    public void setDir(String dir) {
        this.dir = dir;
    }

}

分页结果类

import com.fasterxml.jackson.annotation.JsonPropertyOrder;

import java.util.List;

/**
 * <p>
 *      分页查询结果
 * </p>
 *
 * @ClassName PageResultVo
 * @Author yww
 * @Date 2022/10/24 21:15
 */
@JsonPropertyOrder({"start", "size", "total", "rows"})
public class PageResultVo<T> {

    /**
     *  本页记录在所有记录中的起始位置
     */
    private int start;

    /**
     *  每页记录条数
     */
    private int size;

    /**
     *  总记录条数
     */
    private int total;

    /**
     *  当前页数据
     */
    private List<T> rows;

    public static <E> PageResultVo<E> ofReqVo(PageVo reqVo, List<E> rows, int total) {
        PageResultVo<E> pageResultVo = new PageResultVo<>();
        pageResultVo.setSize(reqVo.getSize());
        pageResultVo.setStart(reqVo.getOffset());
        pageResultVo.setTotal(total);
        pageResultVo.setRows(rows);
        return pageResultVo;
    }

    public PageResultVo() {
    }

    public int getStart() {
        return start;
    }

    public void setStart(int start) {
        this.start = start;
    }

    public int getSize() {
        return size;
    }

    public void setSize(int size) {
        this.size = size;
    }

    public int getTotal() {
        return total;
    }

    public void setTotal(int total) {
        this.total = total;
    }

    public List<T> getRows() {
        return rows;
    }

    public void setRows(List<T> rows) {
        this.rows = rows;
    }

}

分页工具类

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.ISqlSegment;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.segments.MergeSegments;
import com.baomidou.mybatisplus.core.conditions.segments.NormalSegmentList;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.yww.management.common.exception.GlobalException;

import java.util.ArrayList;
import java.util.List;

/**
 * <p>
 *      分页工具类
 * </p>
 *
 * @ClassName PageUtil
 * @Author yww
 * @Date 2022/10/24 21:21
 */
@SuppressWarnings("all")
public class PageUtil {

    /**
     * mybatis-plus自带分页插件
     *
     * @param pageVo
     * @param mapper
     * @param clazz
     * @param <R>
     * @param <Q>
     * @return
     */
    public static <R, Q extends PageVo> PageResultVo<R> paging(Q pageVo, BaseMapper<R> mapper, Class<R> clazz) {
        R entity;
        try {
            entity = clazz.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new GlobalException("分页类型转换错误");
        }
        BeanUtil.copyProperties(pageVo, entity);
        IPage<R> page = new Page<>(pageVo.getPage(), pageVo.getSize());
        page = mapper.selectPage(page, Wrappers.lambdaQuery(entity));
        return PageResultVo.ofReqVo(pageVo, page.getRecords(), Long.valueOf(page.getTotal()).intValue());
    }

    /**
     * mybatis-plus自带分页插件
     *
     * @param pageVo
     * @param mapper
     * @param <R>
     * @param <Q>
     * @return
     */
    public static <R, Q extends PageVo> PageResultVo<R> paging(Q pageVo, BaseMapper<R> mapper, LambdaQueryWrapper<R> lambdaQuery, Class<R> clazz) {
        R entity;
        try {
            entity = clazz.newInstance();
        } catch (InstantiationException | IllegalAccessException e) {
            throw new GlobalException("分页类型转换错误");
        }
        // 已设置在lambdaQuery的字段实体类中不应该重复设置
        String[] whereFields = getWhereFields(lambdaQuery);
        BeanUtil.copyProperties(pageVo, entity, whereFields);
        lambdaQuery.setEntity(entity);
        IPage<R> page = new Page<>(pageVo.getPage(), pageVo.getSize());
        page = mapper.selectPage(page, lambdaQuery);
        return PageResultVo.ofReqVo(pageVo, page.getRecords(), Long.valueOf(page.getTotal()).intValue());
    }

    /**
     * 获取设置的条件字段名
     *
     * @param lambdaQuery
     * @param <R>
     * @return
     */
    public static <R> String[] getWhereFields(LambdaQueryWrapper<R> lambdaQuery) {
        List<String> fields = new ArrayList<>();
        MergeSegments expression = lambdaQuery.getExpression();
        NormalSegmentList normal = expression.getNormal();
        int index = 0;
        for (ISqlSegment iSqlSegment : normal) {
            if (index % 4 == 0) {
                String field = StrUtil.toCamelCase(iSqlSegment.getSqlSegment());
                fields.add(field);
            }
            index++;
        }
        return fields.toArray(new String[]{});
    }

}

文件工具类

package com.yww.filebackend.utils;

import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.yww.filebackend.entity.SysFile;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * <p>
 *      文件工具类
 * </p>
 *
 * @author yww
 */
@Slf4j
public class FileUtil {

    /**
     * 文件保存路径(需要注意)
     */
    private final static String FILE_SAVE_PATH = "D:\\file";

    /**
     * 文件日期部分路径格式
     */
    public final static String DATE_FORMAT = "/yyyy/MM/dd/";
    /**
     * windows下文件日期部分路径格式
     */
    private final static String DATE_FORMAT_WIN = "\\yyyy\\MM\\dd\\";

    /**
     * 保存文件
     *
     * @param file  文件
     * @return      文件信息
     */
    public static SysFile saveFile(MultipartFile file) {
        String originalFilename = file.getOriginalFilename();
        // 获取文件扩展名
        String ext = getExtName(originalFilename);
        // 生成新的文件名称
        String newName = IdUtil.fastSimpleUUID() + "." + ext;
        String dateFormat = new SimpleDateFormat(getDateFormat()).format(new Date());
        String path = FILE_SAVE_PATH + dateFormat + newName;
        File dest = new File(path);
        // 若是路径不存在就先创建父级目录
        if (!dest.exists()) {
            cn.hutool.core.io.FileUtil.touch(dest);
        }
        // 保存文件
        try {
            file.transferTo(dest);
        } catch (IOException e) {
            log.warn("保存文件出错,保存的文件名称为:" + originalFilename);
            log.warn("保存文件出错,保存的路径为:" + path);
            throw new RuntimeException(e);
        }
        return SysFile.builder()
                .fileName(originalFilename)
                .path(path)
                .size(file.getSize())
                .build();
    }

    /**
     * 获取文件后缀
     * 参考自hutool的FileUtil.extName()方法
     *
     * @param fileName  文件名称
     * @return          文件后缀
     */
    public static String getExtName(String fileName) {
        if (fileName == null) {
            return null;
        }
        int index = fileName.lastIndexOf(".");
        if (index == -1) {
            return "";
        }
        // 可能会有两个.符号
        int secondToLastIndex = fileName.substring(0, index).lastIndexOf(".");
        return fileName.substring(secondToLastIndex == -1 ? index + 1 : secondToLastIndex + 1);
    }

    /**
     * 获取日期格式
     * 由于操作系统不一样,所以可能会有不同文件路径格式
     *
     * @return  日期格式
     */
    public static String getDateFormat() {
        String osName = System.getProperty("os.name").toLowerCase();
        if (StrUtil.isNotBlank(osName) && osName.contains("win")) {
            return DATE_FORMAT_WIN;
        }
        return DATE_FORMAT;
    }

}