前言
总结记录一下在开发中经常使用到的一些工具类。
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;
}
}