当前位置: 首页 > news >正文

【Java】常用工具类方法:树形结构、获取IP、对象拷贝、File相关、雪花算法等

1、生成子孙树

    /*** 生成子孙树** @param dataArray 遍历所有数据, 每个数据加到其父节点下* @return 子孙树json*/public static JSONArray makeTree(JSONArray dataArray) {List<Map<String, Object>> data = new ArrayList<>();for (int i = 0; i < dataArray.size(); i++) {JSONObject jsonObject = dataArray.getJSONObject(i);Map<String, Object> map = new HashMap<>(jsonObject);data.add(map);}List<Map<String, Object>> res = new ArrayList<>();Map<Integer, Map<String, Object>> map = new HashMap<>();// 整理数组for (Map<String, Object> vo : data) {map.put((Integer) vo.get("id"), vo);}// 查询子孙for (Map<String, Object> vo : data) {Integer pid = (Integer) vo.get("pid");if (pid != 0) {Map<String, Object> parent = map.get(pid);if (parent != null) {List<Map<String, Object>> children = (List<Map<String, Object>>) parent.get("children");if (children == null) {children = new ArrayList<>();parent.put("children", children);}children.add(vo);}}}// 去除杂质for (Map<String, Object> vo : data) {int pid = (Integer) vo.get("pid");if (pid == 0) {res.add(vo);}}return new JSONArray(Collections.singletonList(res));}

2、对象序列化成JSON

/*** 对象序列化成JSON*/public static JSONObject parseToUnderlineJson(Object object) {if (object != null) {JSONObject json = JSONObject.parseObject(JSON.toJSONString(object));JSONObject jsonObject = new JSONObject();for (String key : json.keySet()) {String camelName = underscoreName(key);jsonObject.put(camelName, json.get(key));}return jsonObject;}return null;}public static String underscoreName(String name) {StringBuilder result = new StringBuilder();if (name != null && name.length() > 0) {result.append(name.substring(0, 1).toLowerCase());for (int i = 1; i < name.length(); i++) {String s = name.substring(i, i + 1);// 在大写字母前添加下划线if (!StringUtils.isAllLowerCase(s) && Character.isUpperCase(s.charAt(0))) {result.append("_").append(s.toLowerCase());} else {result.append(s);}}}return result.toString();}

3、获取请求的IP地址

    /*** 未知IP*/private static final String UNKNOWN = "unknown";/*** 获取请求的IP地址** @param request request* @return ip地址*/public static String getIpAddr(HttpServletRequest request) {String ip = request.getHeader("x-forwarded-for");if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeader("Proxy-Client-IP");}if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getHeader("WL-Proxy-Client-IP");}if (ip == null || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;}

4、文件相关


import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;import java.io.*;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.security.MessageDigest;
import java.util.Arrays;@Slf4j
public class FileUtil {/*** 返回文件后缀名** @return 文件后缀名*/public static String getSuffixName(String fileName) {if (StringUtils.isNotBlank(fileName)) {return fileName.substring(fileName.lastIndexOf(".") + 1);}return "";}/*** 返回文件后缀名前面带.** @return 文件后缀名*/public static String getSuffixNameWithPoint(String fileName) {if (StringUtils.isNotBlank(fileName)) {return fileName.substring(fileName.lastIndexOf("."));}return "";}/*** 获取文件大小 返回 KB 保留3位小数 没有文件时返回0** @param path*            文件绝对路径* @return Double*/public static Double getFileSize(String path) {File file = new File(path);return (double)file.length() / 1000.000;}/*** 创建目录** @param path*            文件夹路径* @return boolean*/public static boolean createDir(String path) {File dir = new File(path);return dir.getParentFile().exists() && dir.mkdir();}/*** 读取到字节数组--方式1** @param filePath*            文件路径* @return byte[]*/public static byte[] toByteArray(String filePath) {File file = new File(filePath);long fileSize = file.length();if (fileSize > Integer.MAX_VALUE) {log.info("file too big...");return null;}FileInputStream fi = null;byte[] buffer = new byte[0];try {fi = new FileInputStream(file);buffer = new byte[(int)fileSize];int offset = 0;int numRead = 0;while (offset < buffer.length && (numRead = fi.read(buffer, offset, buffer.length - offset)) >= 0) {offset += numRead;}// 确保所有数据均被读取if (offset != buffer.length) {throw new IOException("Could not completely read file " + file.getName());}fi.close();} catch (IOException e) {log.error("error={}", "文件读取到字节数组错误", e);} finally {try {if (fi != null) {fi.close();}} catch (IOException e) {log.error("关闭fi错误", e);}}return buffer;}/*** 读取到字节数组--方式2** @param filePath*            文件路径* @return byte[]* @throws FileNotFoundException*/public static byte[] toByteArrayTwo(String filePath) throws FileNotFoundException {File f = new File(filePath);if (!f.exists()) {throw new FileNotFoundException(filePath);}BufferedInputStream in = null;try (ByteArrayOutputStream bos = new ByteArrayOutputStream((int)f.length())) {in = new BufferedInputStream(new FileInputStream(f));int bufSize = 1024;byte[] buffer = new byte[bufSize];int len = 0;while (-1 != (len = in.read(buffer, 0, bufSize))) {bos.write(buffer, 0, len);}return bos.toByteArray();} catch (IOException e) {log.error("error={}", "读取到字节数组错误", e);} finally {closeBufferedInputStream(in);}return null;}/*** 关闭输入流* * @param in*            输入流*/private static void closeBufferedInputStream(BufferedInputStream in) {try {if (in != null) {in.close();}} catch (IOException e) {log.error("closeBufferedInputStream", e);}}/*** 读取到字节数组--方式3** @param filePath*            文件路径* @return byte[]* @throws IOException*             IOException*/public static byte[] toByteArrayThree(String filePath) throws IOException {FileChannel fc = null;RandomAccessFile rf = null;try {rf = new RandomAccessFile(filePath, "r");fc = rf.getChannel();MappedByteBuffer byteBuffer = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()).load();byte[] result = new byte[(int)fc.size()];if (byteBuffer.remaining() > 0) {byteBuffer.get(result, 0, byteBuffer.remaining());}return result;} catch (IOException e) {log.error("toByteArrayThree",e);throw e;} finally {try {assert rf != null;rf.close();assert fc != null;fc.close();} catch (IOException e) {log.error("close_rf_fc",e);}}}/*** 获取该输入流的MD5值** @param is*            输入流* @return md5*/public static String getMd5Content(InputStream is) {StringBuilder md5 = new StringBuilder();MessageDigest md = null;try {md = MessageDigest.getInstance("MD5");byte[] dataBytes = new byte[1024];int nread = 0;while ((nread = is.read(dataBytes)) != -1) {md.update(dataBytes, 0, nread);} ;byte[] mdbytes = md.digest();// convert the byte to hex formatfor (byte mdByte : mdbytes) {md5.append(Integer.toString((mdByte & 0xff) + 0x100, 16).substring(1));}} catch (Exception e) {log.error("error={}", "获取文件md5值失败",e);}return md5.toString();}/*** 获取该文件的MD5值** @param file*            文件* @return MD5*/public static String getFileMd5Content(File file) {String md5 = "";try (FileInputStream fis = new FileInputStream(file)) {md5 = getMd5Content(fis);} catch (IOException e) {log.error("error={}", "获取文件md5值失败",e);}return md5;}/*** 得到文件md5值** @param file*            文件* @return md5String*/public static String getFileMd5(File file) {String md5File = "";try (FileInputStream fileInputStream = new FileInputStream(file);){md5File = MessageDigestUtil.md5Encode(Arrays.toString(IOUtils.toByteArray(fileInputStream)));} catch (IOException e) {log.error("error={}", "获取文件md5值失败",e);}return md5File;}/*** 判断文件是否存在** @param filePath*            文件路径* @return 文件是否存在*/public static boolean judeFileExists(String filePath) {File file = new File(filePath);return file.exists();}
}
import lombok.extern.slf4j.Slf4j;import java.io.BufferedReader;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;@Slf4j
public class MessageDigestUtil {/*** md5加密** @param md5Str 加密字符* @return md5加密之后的字符*/public static String md5Encode(String md5Str) {MessageDigest md5;try {md5 = MessageDigest.getInstance("MD5");} catch (NoSuchAlgorithmException e) {log.error("md5Encode_md5",e);return "";}byte[] bytes;try {bytes = md5Str.getBytes("UTF-8");} catch (UnsupportedEncodingException e) {log.error("md5Encode_bytes",e);return "";}byte[] md5Byte = md5.digest(bytes);StringBuffer stringBuffer = new StringBuffer();messageDigest(md5Byte, stringBuffer);return stringBuffer.toString();}private static void messageDigest(byte[] md5Byte, StringBuffer buffer) {for (byte aMd5Byte : md5Byte) {int val = ((int) aMd5Byte) & 0xff;if (val < 16) {buffer.append("0");}buffer.append(Integer.toHexString(val));}}public static String read(BufferedReader bufferedReader) {StringBuilder sb = new StringBuilder("");String temp;try {while ((temp = bufferedReader.readLine()) != null) {sb.append(temp);}} catch (IOException e) {log.error("read",e);}return sb.toString();}/*** 利用java原生的摘要实现SHA256加密** @param str 加密后的报文* @return 加密字符串*/public static String sha256Encode(String str) {MessageDigest messageDigest;String encodeStr = "";try {messageDigest = MessageDigest.getInstance("SHA-256");messageDigest.update(str.getBytes(StandardCharsets.UTF_8));encodeStr = byte2Hex(messageDigest.digest());} catch (NoSuchAlgorithmException e) {log.error("sha256Encode",e);}return encodeStr;}/*** 将byte转为16进制** @param bytes byte数组* @return 16进制字符串*/private static String byte2Hex(byte[] bytes) {StringBuilder stringBuffer = new StringBuilder();String temp = null;for (byte aByte : bytes) {temp = Integer.toHexString(aByte & 0xFF);if (temp.length() == 1) {//1得到一位的进行补0操作stringBuffer.append("0");}stringBuffer.append(temp);}return stringBuffer.toString();}}

5、对象拷贝


import org.springframework.cglib.beans.BeanCopier;import java.util.HashMap;
import java.util.Map;/*** BeanCopier工具类*/
public class BeanCopierUtils {public static Map<String, BeanCopier> beanCopierCacheMap = new HashMap<>();/**** 将soruce对象的属性转换给target对象* @date 2022/3/9 9:11* @param source 需要转换的对象* @param target 目标对象*/public static void copyProperties(Object source, Object target) {BeanCopier beanCopier;String cacheKey = source.getClass().toString() + target.getClass().toString();if (!beanCopierCacheMap.containsKey(cacheKey)) {synchronized (BeanCopierUtils.class) {if (!beanCopierCacheMap.containsKey(cacheKey)) {beanCopier = BeanCopier.create(source.getClass(), target.getClass(), false);beanCopierCacheMap.put(cacheKey, beanCopier);} else {beanCopier = beanCopierCacheMap.get(cacheKey);}}} else {beanCopier = beanCopierCacheMap.get(cacheKey);}beanCopier.copy(source, target, null);}
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;/*** 数据格式转换工具类*/
public class ObjectConvertUtils {private static final Logger LOGGER = LoggerFactory.getLogger(ObjectConvertUtils.class);/*** 浅克隆** @param source 原对象* @param clazz  目标对象* @return T* @date 2022/4/6 13:45*/public static <T> T clone(Object source, Class<T> clazz) {return clone(source, clazz, true);}/**** @date 2022/7/18 16:36* @param source 原对象* @param clazz 目标对象* @param whetherAssignNull 是否赋值空值:true是 false否* @return T*/public static <T> T clone(Object source, Class<T> clazz, Boolean whetherAssignNull) {T target;if (source == null) {return null;}try {target = clazz.newInstance();if (whetherAssignNull) {BeanUtils.copyProperties(source, target);} else {BeanUtils.copyProperties(source, target, getNullPropertyNames(source));}return target;} catch (Exception e) {LOGGER.error("数据转换异常", e);return null;}}/*** 对象与对象之间的数据转换** @param source 转换的数据对象* @param target 需要转换数据的对象* @date 2022/7/14 17:24*/public static void clone(Object source, Object target) {clone(source, target, true);}/**** @date 2022/7/18 16:39* @param source 转换的数据对象* @param target 需要转换数据的对象* @param whetherAssignNull 是否赋值空值:true是 false否*/public static void clone(Object source, Object target, Boolean whetherAssignNull) {if (source == null) {return;}try {if (whetherAssignNull) {BeanUtils.copyProperties(source, target, getNullPropertyNames(source));} else {BeanUtils.copyProperties(source, target);}} catch (Exception e) {LOGGER.error("数据转换异常", e);}}/*** 对象与对象之间的数据转换** @param source 转换的数据对象* @param target 需要转换数据的对象* @date 2022/7/15 9:11*/public static void cglibBeanCopierCloneObject(Object source, Object target) {BeanCopierUtils.copyProperties(source, target);}/*** 对象与对象之间的数据转换** @param source 转换的数据对象* @param target 需要转换数据的对象* @date 2022/7/15 9:11*/public static <T> T cglibBeanCopierCloneObject(Object source, Class<T> target) {T t;try {t = target.newInstance();BeanCopierUtils.copyProperties(source, t);} catch (InstantiationException | IllegalAccessException e) {throw new RuntimeException(e);}return t;}/*** 将list集合转换为传入的对象的数据集合** @param sourceList 原数据集合* @param clazz      需要转换的集合数据对象* @return java.util.List<T>* @date 2022/4/6 13:49*/public static <T> List<T> cloneList(List<?> sourceList, Class<T> clazz) {return cloneList(sourceList, clazz, true);}/**** @date 2022/7/18 16:41* @param sourceList 原数据集合* @param clazz 需要转换的集合数据对象* @param whetherAssignNull 是否赋值空值:true是 false否* @return java.util.List<T>*/public static <T> List<T> cloneList(List<?> sourceList, Class<T> clazz, Boolean whetherAssignNull) {try {List<T> targetList = new ArrayList<>(sourceList.size());for (Object source : sourceList) {if (whetherAssignNull) {targetList.add(clone(source, clazz));} else {targetList.add(clone(source, clazz, false));}}return targetList;} catch (Exception e) {LOGGER.error("数据转换异常", e);return null;}}/*** 获取需要忽略的属性*/public static String[] getNullPropertyNames(Object source) {final BeanWrapper src = new BeanWrapperImpl(source);PropertyDescriptor[] pds = src.getPropertyDescriptors();Set<String> emptyNames = new HashSet<>();for (PropertyDescriptor pd : pds) {Object srcValue = src.getPropertyValue(pd.getName());// 此处判断可根据需求修改if (srcValue == null) {emptyNames.add(pd.getName());}}String[] result = new String[emptyNames.size()];return emptyNames.toArray(result);}
}

6、雪花算法生成ID


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;import java.util.Date;/*** @Description 雪花算法-ID*/
@Component
public class SnowFlakeUtil {private Logger log = LoggerFactory.getLogger(SnowFlakeUtil.class);/*** 记录上一毫秒数*/private static long lastTimestamp = -1L;/*** 记录毫秒内的序列,0-4095*/private static long sequence = 0L;private static Long machineId = 1L;private static Long datacenterId =1L;public static synchronized String getId() {long timestamp = System.currentTimeMillis();// 如果当前时间小于上一次ID生成的时间戳,说明系统时钟被修改过,回退在上一次ID生成时间之前应当抛出异常!!!if (timestamp < lastTimestamp) {throw new IllegalStateException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));}//如果是同一时间生成的,则进行毫秒内序列if (lastTimestamp == timestamp) {sequence = (sequence + 1) & UniqueIdMetaData.SEQUENCE_MASK;//毫秒内序列溢出if (sequence == 0) {//阻塞到下一个毫秒,获得新的时间戳timestamp = System.currentTimeMillis();while (timestamp <= lastTimestamp) {timestamp = System.currentTimeMillis();}return String.valueOf(timestamp);}}//时间戳改变,毫秒内序列重置else {sequence = 0L;}// 上次生成ID的时间截lastTimestamp = timestamp;// 移位并通过或运算组成64位IDreturn  String.valueOf(((timestamp - UniqueIdMetaData.START_TIME) << UniqueIdMetaData.TIMESTAMP_LEFT_SHIFT_BITS)| (datacenterId << UniqueIdMetaData.DATACENTER_SHIFT_BITS)| (machineId<< UniqueIdMetaData.MACHINE_SHIFT_BITS)| sequence);}public static synchronized Long getLongId() {return Long.parseLong(getId());}public UniqueId explainId(long id) {UniqueId uniqueId = SnowFlakeUtil.convert(id);if (uniqueId == null) {log.error("==>  解析ID失败, ID不合法");return null;}return uniqueId;}public Date transTime(long time) {return new Date(time + UniqueIdMetaData.START_TIME);}/*** 唯一ID对象解析返回ID** @param uniqueId* @return*/public static long convert(UniqueId uniqueId) {long result = 0;try {result = 0L;result |= uniqueId.getSequence();result |= uniqueId.getMachineId() << UniqueIdMetaData.MACHINE_SHIFT_BITS;result |= uniqueId.getDatacenterId() << UniqueIdMetaData.DATACENTER_SHIFT_BITS;result |= uniqueId.getTimestamp() << UniqueIdMetaData.TIMESTAMP_LEFT_SHIFT_BITS;} catch (Exception e) {e.printStackTrace();return result;}return result;}public static UniqueId convert(long id) {UniqueId uniqueId = null;try {uniqueId = new UniqueId();uniqueId.setSequence(id & UniqueIdMetaData.SEQUENCE_MASK);uniqueId.setMachineId((id >>> UniqueIdMetaData.MACHINE_SHIFT_BITS) & UniqueIdMetaData.MACHINE_MASK);uniqueId.setDatacenterId((id >>> UniqueIdMetaData.DATACENTER_SHIFT_BITS) & UniqueIdMetaData.DATACENTER_MASK);uniqueId.setTimestamp((id >>> UniqueIdMetaData.TIMESTAMP_LEFT_SHIFT_BITS) & UniqueIdMetaData.TIMESTAMP_MASK);} catch (Exception e) {e.printStackTrace();return uniqueId;}return uniqueId;}
}
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;import java.io.Serializable;/*** @Description*/
@NoArgsConstructor
@AllArgsConstructor
@Data
public class UniqueId implements Serializable {/*** 0     +   41       +   5             +       5           +       12* 固定  +  时间戳    +   工作机器ID     +     数据中心ID    +      序列号*/private static final long serialVersionUID = 8632670752020316524L;/*** 工作机器ID、数据中心ID、序列号、上次生成ID的时间戳*/@ApiModelProperty(value = "机器ID")private long machineId;@ApiModelProperty(value = "数据中心ID")private long datacenterId;@ApiModelProperty(value = "毫秒内序列")private long sequence;@ApiModelProperty(value = "时间戳")private long timestamp;@Overridepublic String toString() {return "UniqueIdRespVo{" +"服务机器ID=" + machineId +", 数据中心ID=" + datacenterId +", 毫秒内的序列=" + sequence +", 生成时间与预设时间戳间隔=" + timestamp +'}';}
}
import io.swagger.annotations.ApiModelProperty;public class UniqueIdMetaData {/*** 取当前系统启动时间为参考起始时间,* 取1995-04-01为参考日*/public static final long START_TIME = 796665600000L;/*** 机器ID所占位数*/@ApiModelProperty(value = "机器位数")public static final long MACHINE_ID_BITS = 5L;/*** 机器ID最大值31,0-31*/@ApiModelProperty(value = "机器ID最大")public static final long MAX_MACHINE_ID = ~(-1L << MACHINE_ID_BITS);/*** 数据中心ID所占位数*/@ApiModelProperty(value = "数据中心ID所占位数")public static final long DATACENTER_ID_BITS = 5L;/*** 数据中心ID最大值31,0-31*/@ApiModelProperty(value = "数据中心ID最大值")public static final long MAX_DATACENTER_ID = ~(-1L << MACHINE_ID_BITS);/*** Sequence所占位数*/@ApiModelProperty(value = "序列所占位数")public static final long SEQUENCE_BITS = 12L;/*** 机器ID偏移量12*/@ApiModelProperty(value = "机器ID偏移量")public static final long MACHINE_SHIFT_BITS = SEQUENCE_BITS;/*** 数据中心ID偏移量12+5=17*/@ApiModelProperty(value = "数据中心ID偏移量")public static final long DATACENTER_SHIFT_BITS = SEQUENCE_BITS + MACHINE_ID_BITS;/*** 时间戳的偏移量12+5+5=22*/@ApiModelProperty(value = "时间戳偏移量")public static final long TIMESTAMP_LEFT_SHIFT_BITS = SEQUENCE_BITS + MACHINE_ID_BITS + DATACENTER_ID_BITS;/*** Sequence掩码4095*/@ApiModelProperty(value = "序列掩码")public static final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);/*** 机器ID掩码1023*/@ApiModelProperty(value = "机器ID掩码")public static final long MACHINE_MASK = ~(-1L << MACHINE_ID_BITS);/*** 数据中心掩码1023*/@ApiModelProperty(value = "数据中心掩码")public static final long DATACENTER_MASK = ~(-1L << MACHINE_ID_BITS);/*** 时间戳掩码2的41次方减1*/@ApiModelProperty(value = "时间戳掩码")public static final long TIMESTAMP_MASK = ~(-1L << 41L);}

7、生成UUID

import java.util.UUID;/*** <p>DESC: UUID工具</p>* <p>VERSION:1.0.0</p>*/
public class UUIDUtil {public static String get32Uuid() {return UUID.randomUUID().toString().trim().replaceAll("-", "").toUpperCase();}}

8、去掉图片的base64的头部

    /*** 去掉图片的base64的头部** @param base64* @return*/public static String baseurlPhotos(String base64) {return base64.substring(base64.indexOf(",") + 1);}

9、日期相关


import lombok.SneakyThrows;import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.temporal.TemporalAdjusters;
import java.util.*;/*** @Author: songmingsong* @CreateTime: 2024-12-13* @Description: 日期工具* @Version: 1.0*/
public class DateUtils {/*** 时间格式(yyyy-MM-dd)*/public final static String DATE_PATTERN = "yyyy-MM-dd";/*** 时间格式(yyyy-MM-dd HH:mm:ss)*/public final static String DATE_TIME_PATTERN = "yyyy-MM-dd HH:mm:ss";/*** 获取当前日期 before 天的时间点** @param before 往前推多少天* @param hour   时* @param minute 分* @param second 秒* @return 当前日期前推before天的 时分秒时间*/public static Date getTimePoint(int before, int hour, int minute, int second) {Calendar calendar = Calendar.getInstance();calendar.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DAY_OF_MONTH) - before, hour, minute, second);return calendar.getTime();}/*** 日期格式化 日期格式为:yyyy-MM-dd* @param date 日期* @return 返回yyyy-MM-dd格式日期*/public static String formatDate(Date date) {return formatDate(date, DATE_PATTERN);}/*** 日期格式化 自定义格式* @param date    日期* @param pattern 格式,如:DateUtils.DATE_TIME_PATTERN* @return 返回yyyy-MM-dd格式日期*/public static String formatDate(Date date, String pattern) {if (date != null) {SimpleDateFormat df = new SimpleDateFormat(pattern);return df.format(date);}return null;}/*** 日期解析 日期格式为:yyyy-MM-dd HH:mm:ss* @param dataStr 日期字符串* @return 返回yyyy-MM-dd HH:mm:ss格式日期*/public static Date parse(String dataStr) throws ParseException {SimpleDateFormat sdf = new SimpleDateFormat(DATE_TIME_PATTERN);return sdf.parse(dataStr);}/*** 日期解析 自定义格式* @param date    日期字符串* @param pattern 格式,如:DateUtils.DATE_TIME_PATTERN* @return 返回Date*/public static Date parse(String date, String pattern) {try {return new SimpleDateFormat(pattern).parse(date);} catch (ParseException e) {e.printStackTrace();}return null;}/*** 返回0时0分0秒的date** @param date 日期* @return*/@SneakyThrowspublic static Date startOfDay(Date date) {Calendar calendar = Calendar.getInstance();calendar.setTime(date);// 将时间部分设置为00:00:00.000calendar.set(Calendar.HOUR_OF_DAY, 0);calendar.set(Calendar.MINUTE, 0);calendar.set(Calendar.SECOND, 0);calendar.set(Calendar.MILLISECOND, 0);return calendar.getTime();}/*** 这个月的第一天* @param date 日期* @return*/public static Date startOfMonth(Date date) {Date date1 = startOfDay(date);Calendar cal = Calendar.getInstance();cal.setTime(date1);cal.set(Calendar.DAY_OF_MONTH,1);return cal.getTime();}/*** 获取某个月有多少天** @param yearMonth* @return*/public static String getLastDayOfMonth(String yearMonth) {int year = Integer.parseInt(yearMonth.split("-")[0]);  //年int month = Integer.parseInt(yearMonth.split("-")[1]); //月Calendar cal = Calendar.getInstance();// 设置年份cal.set(Calendar.YEAR, year);// 设置月份// cal.set(Calendar.MONTH, month - 1);cal.set(Calendar.MONTH, month); //设置当前月的上一个月// 获取某月最大天数//int lastDay = cal.getActualMaximum(Calendar.DATE);int lastDay = cal.getMinimum(Calendar.DATE); //获取月份中的最小值,即第一天// 设置日历中月份的最大天数//cal.set(Calendar.DAY_OF_MONTH, lastDay);cal.set(Calendar.DAY_OF_MONTH, lastDay - 1); //上月的第一天减去1就是当月的最后一天// 格式化日期SimpleDateFormat sdf = new SimpleDateFormat("dd");return sdf.format(cal.getTime());}/*** 获得某月最大时间* @param date* @return*/public static Date getEndMonthOfDay(Date date) {if (date == null) {return null;}LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), ZoneId.systemDefault());LocalDateTime endOfDay = localDateTime.with(TemporalAdjusters.lastDayOfMonth());return getEndOfDay(localDateTimeToDate(endOfDay));}/*** 获得某天最大时间* @param date* @return*/public static Date getStartMonthOfDay(Date date) {if (date == null) {return null;}LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), ZoneId.systemDefault());LocalDateTime endOfDay = localDateTime.with(TemporalAdjusters.firstDayOfMonth());return getStartOfDay(localDateTimeToDate(endOfDay));}public static Date getMonthLastDay(Date date) {Calendar calendar = Calendar.getInstance();calendar.setTime(date);calendar.set(Calendar.DAY_OF_MONTH, 0);calendar.add(Calendar.MONTH, 1);return calendar.getTime();}/*** 判断当天和传入的时间是否是同一天** @param thatDay 另一个日期* @return*/public static boolean isSameDay(Date thatDay) {return isSameDay(thatDay, new Date());}/*** 判断两个日期是否为同一天** @param date1 一个日期* @param date2 另一个日期* @return*/public static boolean isSameDay(Date date1, Date date2) {if (date1 == null || date2 == null) {return false;}Calendar thisDat = Calendar.getInstance();thisDat.setTime(date1);Calendar thatDay = Calendar.getInstance();thatDay.setTime(date2);return (thatDay.get(Calendar.YEAR) == thisDat.get(Calendar.YEAR) &&thatDay.get(Calendar.MONTH) == thisDat.get(Calendar.MONTH) &&thatDay.get(Calendar.DAY_OF_MONTH) == thisDat.get(Calendar.DAY_OF_MONTH));}/*** 判断两个日期相差多少天** @param endTime* @param startTime* @return*/public static int dateMinus(Date endTime, Date startTime) {return (int) ((endTime.getTime() - startTime.getTime()) / (1000 * 60 * 60 * 24));}/*** 时间戳转字符串** @param timeStamp* @return*///传入时间戳即可public static String conversionTime(String timeStamp) {//yyyy-MM-dd HH:mm:ss 转换的时间格式  可以自定义SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");//转换String time = sdf.format(new Date(Long.parseLong(timeStamp)));return time;}/*** 判断两个日期相差多少秒** @param endTime* @param startTime* @return*/public static int dateSeconds(Date endTime, Date startTime) {return (int) ((endTime.getTime() - startTime.getTime()) / (1000));}/*** 获得某天最小时间* @param date* @return*/public static Date getStartOfDay(Date date) {if (date == null) {return null;}LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), ZoneId.systemDefault());LocalDateTime startOfDay = localDateTime.with(LocalTime.MIN);return localDateTimeToDate(startOfDay);}/*** 获得某天最大时间* @param date* @return*/public static Date getEndOfDay(Date date) {if (date == null) {return null;}LocalDateTime localDateTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(date.getTime()), ZoneId.systemDefault());LocalDateTime endOfDay = localDateTime.with(LocalTime.MAX);return localDateTimeToDate(endOfDay);}/*** localDateTime 转date* @param localDateTime* @return*/public static Date localDateTimeToDate(LocalDateTime localDateTime) {return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());}/*** 将秒转为时分秒格式【01:01:01】* @param second 需要转化的秒数* @return*/public static String secondConvertHourMinSecond(Long second) {String str = "00:00:00";if (second == null || second < 0) {return str;}// 得到小时long h = second / 3600;str = h > 0 ? ((h < 10 ? ("0" + h) : h) + ":") : "00:";// 得到分钟long m = (second % 3600) / 60;str += (m < 10 ? ("0" + m) : m) + ":";//得到剩余秒long s = second % 60;str += (s < 10 ? ("0" + s) : s);return str;}/**** @param date* @return*/public static String getWeekOfDate(Date date) {String[] weekDays = {"7", "1", "2", "3", "4", "5", "6"}; // "星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"Calendar cal = Calendar.getInstance();cal.setTime(date);int w = cal.get(Calendar.DAY_OF_WEEK) - 1; // 将星期日转换为7return weekDays[w]; // 直接返回对应的星期数}/*** 获取两个日期之间的所有年* @param startDate* @param endDate* @return*/public static List<String> getAllYearsBetweenDates(Date startDate, Date endDate)  {List<String> years = new ArrayList<>();Calendar startCal = Calendar.getInstance();startCal.setTime(startDate);int startYear = startCal.get(Calendar.YEAR);Calendar endCal = Calendar.getInstance();endCal.setTime(endDate);int endYear = endCal.get(Calendar.YEAR);for (int year = startYear; year <= endYear; year++) {years.add(String.valueOf(year));}return years;}/*** 获取两个日期之间的所有月份* @param startDate* @param endDate* @return*/public static List<String> getAllMonthsBetweenDates(Date startDate, Date endDate)  {List<String> months = new ArrayList<>();//具体逻辑Calendar startCal = Calendar.getInstance();startCal.setTime(startDate);startCal.set(Calendar.DAY_OF_MONTH, 1); // 设置为每个月的第一天Calendar endCal = Calendar.getInstance();endCal.setTime(endDate);endCal.set(Calendar.DAY_OF_MONTH, 1); // 设置为每个月的第一天while (startCal.before(endCal) || startCal.equals(endCal)) {int year = startCal.get(Calendar.YEAR);int month = startCal.get(Calendar.MONTH) + 1; // Calendar中月份从0开始,所以要加1months.add(String.format("%d-%02d", year, month)); // 格式化年份和月份为 yyyy-MMstartCal.add(Calendar.MONTH, 1); // 增加一个月}return months;}/*** 获取两个日期间所有的日期* @param startDate* @param endDate* @return*/public static List<String> getAllDaysBetweenDates(Date startDate, Date endDate){List<String> days = new ArrayList<>();//具体实现Calendar startCal = Calendar.getInstance();startCal.setTime(startDate);Calendar endCal = Calendar.getInstance();endCal.setTime(endDate);SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");while (startCal.before(endCal) || startCal.equals(endCal)) {days.add(sdf.format(startCal.getTime()));startCal.add(Calendar.DATE, 1);}return days;}/*** 获取两个时间点之间的所有的整点时间* @param startDate* @param endDate* @return*/public static List<String> getAllTimesBetweenDates(Date startDate, Date endDate,String format){// 创建一个字符串列表用来存放整点时间List<String> timesList = new ArrayList<>();// 使用日历类来处理时间Calendar calendar = Calendar.getInstance();calendar.setTime(startDate);// 格式化时间为 "yyyy-MM-dd HH:mm"SimpleDateFormat sdf = new SimpleDateFormat(format);// 逐小时遍历
//        while (calendar.getTime().before(endDate) || calendar.getTime().equals(endDate)) {while (calendar.getTime().before(endDate)) {// 将当前时间格式化为字符串并加入列表timesList.add(sdf.format(calendar.getTime()));// 增加一个小时calendar.add(Calendar.HOUR_OF_DAY, 1);}return timesList;}/*** 获取24小时制的时间* @return*/public static List<String> getAllTimesBetweenDates(){// 创建一个字符串数组,容量为24String[] times24 = new String[24];// 设置格式化器为24小时制java.time.format.DateTimeFormatter formatter = java.time.format.DateTimeFormatter.ofPattern("HH");// 获取0点到23点的每一个小时for (int i = 0; i < 24; i++) {LocalTime time = LocalTime.of(i, 0);times24[i] = time.format(formatter);}return new ArrayList<>(Arrays.asList(times24));}/*** 在指定日期上增加i年,负数为减少i年* @param date 指定的日期* @param i 增加/减少的年数* @return*/public static Date addDateYears(Date date, int i) {Calendar calendar = Calendar.getInstance();calendar.setTime(date);calendar.add(Calendar.YEAR, i);return calendar.getTime();}/*** 在指定日期上增加i月,负数为减少i月* @param date* @param i* @return*/public static Date addDateMonths(Date date, int i) {Calendar calendar = Calendar.getInstance();calendar.setTime(date);calendar.add(Calendar.MONTH, i);return calendar.getTime();}/*** 在指定日期上增加i周,负数为减少i周* @param date* @param i* @return*/public static Date addDateWeeks(Date date, int i) {Calendar calendar = Calendar.getInstance();calendar.setTime(date);calendar.add(Calendar.WEEK_OF_YEAR, i);return calendar.getTime();}/*** 在指定日期上增加i天,负数为减少i天* @param date* @param i* @return*/public static Date addDateDays(Date date, int i) {Calendar calendar = Calendar.getInstance();calendar.setTime(date);calendar.add(Calendar.DAY_OF_YEAR, i);return calendar.getTime();}/*** 在指定日期上增加i分钟,负数为减少i分钟* @param date* @param i* @return*/public static Date addDateMinute(Date date, int i) {Calendar calendar = Calendar.getInstance();calendar.setTime(date);calendar.add(Calendar.MINUTE, i);return calendar.getTime();}/*** 在指定日期上增加i单位的时间,负数为减少i单位的时间* @param date 指定日期* @param i 增加/减少的时间数量* @param unit 时间单位,如Calendar.DAY_OF_YEAR、Calendar.HOUR_OF_DAY等* @return*/public static Date addDateUnits(Date date, int i,int unit) {Calendar calendar = Calendar.getInstance();calendar.setTime(date);calendar.add(unit, i);return calendar.getTime();}}

相关文章:

【Java】常用工具类方法:树形结构、获取IP、对象拷贝、File相关、雪花算法等

1、生成子孙树 /*** 生成子孙树** param dataArray 遍历所有数据, 每个数据加到其父节点下* return 子孙树json*/public static JSONArray makeTree(JSONArray dataArray) {List<Map<String, Object>> data new ArrayList<>();for (int i 0; i < dataAr…...

豆瓣电影Top250的数据采集与可视化分析(scrapy+mysql+matplotlib)

文章目录 豆瓣电影Top250的数据采集与可视化分析(scrapy+mysql+matplotlib)写在前面数据采集(Visual Studio Code+Navicat)1.观察网页信息2.编写Scrapy代码(Visual Studio Code)2.1 创建Scrapy项目`doubanProject`2.2 创建爬虫脚本`douban.py`2.3 修改`douban.py`的代码2…...

2024微短剧行业生态洞察报告汇总PDF洞察(附原数据表)

原文链接&#xff1a; https://tecdat.cn/?p39072 本报告合集洞察从多个维度全面解读微短剧行业。在行业发展层面&#xff0c;市场规模与用户规模双增长&#xff0c;创造大量高收入就业岗位并带动产业链升级。内容创作上&#xff0c;精品化、品牌化趋势凸显&#xff0c;题材走…...

PHP语言的数据库交互

PHP语言的数据库交互 引言 在现代Web开发中&#xff0c;数据库是存储和管理应用数据的重要组成部分。随着互联网的快速发展&#xff0c;网站和应用程序对数据存储和操作的需求变得越来越复杂。PHP作为一种广泛使用的服务器端脚本语言&#xff0c;提供了多种数据库交互的方法&…...

flutter跨端UI框架简介

flutter跨端UI框架简介 简介 Flutter是由Google开发的开源应用开发框架&#xff0c;主要用于构建高性能、跨平台的移动、Web和桌面应用程序。Flutter使用Dart语言&#xff0c;提供了一套丰富的Widgets&#xff0c;使开发者能够快速创建美观的用户界面。其最大特点是热重载功能…...

自动化标注平台开源,基于 yolov8标注平台可本地部署

yolov8标注平台本地部署&#xff08;docker部署&#xff09;&#xff0c;已调通yolov8模型自动预标注功能。 下面开始背景知识…… 1&#xff09;数据标注为什么在人工智能时代如此重要&#xff1f; 数据标注在人工智能时代如此重要&#xff0c;原因如下&#xff1a; 为机器…...

Walrus Learn to Earn计划正式启动!探索去中心化存储的无限可能

本期 Learn to Earn 活动将带领开发者和区块链爱好者深入探索 Walrus 的技术核心与实际应用&#xff0c;解锁分布式存储的无限可能。参与者不仅能提升技能&#xff0c;还能通过完成任务赢取丰厚奖励&#xff01;&#x1f30a; 什么是 Walrus&#xff1f; 数据主权如今正成为越…...

第35天:安全开发-JavaEE应用原生反序列化重写方法链条分析触发类类加载

时间轴&#xff1a; 序列化与反序列化图解&#xff1a; 演示案例&#xff1a; Java-原生使用-序列化&反序列化 Java-安全问题-重写方法&触发方法 Java-安全问题-可控其他类重写方法 Java-原生使用-序列化&反序列化 1.为什么进行序列化和反序列化&#xff1…...

【mptcp】ubuntu18.04和MT7981搭建mptcp测试环境操作说明

目录 安装ubuntu18.04,可以使用虚拟机安装... 2 点击安装VMware Tool 2 更新ubuntu18.04源... 4 安装ifconfig指令工具包... 5 安装vim工具包... 5...

【数据分析(二)】初探 Pandas

目录 引言1. 基本数据结构1.1. Series 的初始化和简单操作1.2. DataFrame 的初始化和简单操作1.2.1. 初始化与持久化1.2.2. 读取查看1.2.3. 行操作1.2.4. 列操作1.2.5. 选中筛查 2. 数据预处理2.0. 生成样例表2.1. 缺失值处理2.2. 类型转换和排序2.3. 统计分析 3. 数据透视3.0.…...

第9章:Python TDD解决货币对象相等性比较难题

写在前面 这本书是我们老板推荐过的&#xff0c;我在《价值心法》的推荐书单里也看到了它。用了一段时间 Cursor 软件后&#xff0c;我突然思考&#xff0c;对于测试开发工程师来说&#xff0c;什么才更有价值呢&#xff1f;如何让 AI 工具更好地辅助自己写代码&#xff0c;或许…...

更新布局元素的属性

每个布局元素都有一组可以通过编程来更新的属性.布局元素有很多种不同的类型,如图例,图形,文本,地图整饰等等. 操作方法: 1.打开目标活动地图文档 2.打开python窗口 3.导入arcpy模块 import arcpy.mapping as mapping 4.引用当前活动地图文档,把该引用赋值给变量 mxd map…...

UDP協議與代理IP介紹

UDP&#xff0c;全稱是用戶數據報協議&#xff08;User Datagram Protocol&#xff09;&#xff0c;是Internet協議套組的一部分&#xff0c;與TCP協議一道工作。與TCP相比&#xff0c;UDP可以理解為一個更“羽量級”的協議。它不需要像TCP那樣在數據傳輸開始之前建立連接&…...

QT 中 UDP 的使用

目录 一、UDP 简介 二、QT 中 UDP 编程的基本步骤 &#xff08;一&#xff09;包含头文件 &#xff08;二&#xff09;创建 UDP 套接字对象 &#xff08;三&#xff09;绑定端口 &#xff08;四&#xff09;发送数据 &#xff08;五&#xff09;接收数据 三、完整示例代…...

leetcode刷题记录(七十二)——146. LRU 缓存

&#xff08;一&#xff09;问题描述 146. LRU 缓存 - 力扣&#xff08;LeetCode&#xff09;146. LRU 缓存 - 请你设计并实现一个满足 LRU (最近最少使用) 缓存 [https://baike.baidu.com/item/LRU] 约束的数据结构。实现 LRUCache 类&#xff1a; * LRUCache(int capacity)…...

深圳大学-计算机系统(3)-实验一MIPS指令集实验

实验目标 a) 了解WinMIPS64的基本功能和作用&#xff1b; b) 熟悉MIPS指令、初步建立指令流水执行的感性认识&#xff1b; c) 掌握该工具的基本命令和操作&#xff0c;为流水线实验作准备。 实验内容 按照下面的实验步骤及说明&#xff0c;完成相关操作记录实验过程的截图&a…...

Java面试专题——面向对象

面向过程和面向对象的区别 面向过程&#xff1a;当事件比较简单的时候&#xff0c;利用面向过程&#xff0c;注重的是事件的具体的步骤/过程&#xff0c;注重的是过程中的具体的行为&#xff0c;以函数为最小单位&#xff0c;考虑怎么做。 面向对象&#xff1a;注重找“参与者…...

知行合一:解决有心无力的问题,解决知易行难的问题,知行合一并不意味着事事都要合一,而是....

问题是什么&#xff1f; 想学习的时候&#xff0c;有手机阻碍我们。想戒掉手机短视频&#xff0c;卸载后&#xff0c;几天的时间&#xff0c;又下载了回来。制定了减肥计划&#xff0c;但就是不执行。明知道这样做是不对的&#xff0c;但依然行动不起来。 沉溺于各种各样的享…...

Qt中自定义信号与槽

在学习信号和槽的时候&#xff0c;我们知道信号一般对应的就是用户的行为&#xff0c;槽指的是接受到信号后的响应&#xff0c;在类内有许多的内置信号和槽函数&#xff0c;能够去实现一些常见的行为&#xff0c;但实际业务开发中&#xff0c;尤其是接受到信号的响应会根据具体…...

.NET 8 项目 Docker 方式部署到 Linux 系统详细操作步骤

本文将详细介绍如何将一个 .NET 8 项目通过 Docker 部署到 Linux 系统中。以下步骤包括从项目的创建、Dockerfile 的编写、镜像构建、到最后在 Linux 上的容器运行。 1. 环境准备 在开始之前&#xff0c;请确保你已经具备以下环境&#xff1a; Linux 系统&#xff08;如 Ubu…...

react实现markdown文件预览

文章目录 react实现markdown文件预览1、实现md文件预览2、解决图片不显示3、实现效果 react实现markdown文件预览 1、实现md文件预览 1️⃣第一步&#xff1a;安装依赖&#xff1a; npm install react-markdown remark-gfmreact-markdown&#xff1a;将 Markdown 渲染为 Rea…...

Docker 与容器技术的未来:从 OCI 标准到 eBPF 的演进

Docker 的出现无疑是云计算发展史上的一个里程碑。它以其直观的打包、分发和运行方式,极大地简化了应用程序的部署和管理,从而推动了微服务架构和 DevOps 文化的普及。然而,容器技术的未来并非仅仅局限于 Docker,它正朝着更深层次的标准化和更底层的操作系统内核创新方向演…...

高雄市12岁以下身心障碍儿童口腔保健合作院所名单数据集

描述&#xff1a; 关键字&#xff1a;儿童、口腔、保健、院所、名单 字段特征&#xff1a;序号、院所分级、合作医疗院所、市话、地址 语言&#xff1a;繁体 行数/数量&#xff1a;129行&#xff0c;5列 数据量 &#xff1a;7.27KB 格式&#xff1a;CSV、JSON、XML 目录…...

Java高效批量读取Redis数据:原理、方案与实战案例

Java高效批量读取Redis数据&#xff1a;原理、方案与实战案例 在电商大促场景中&#xff0c;某平台需要实时展示用户购物车数据&#xff0c;面对每秒10万的请求&#xff0c;传统单次读取Redis的方式导致响应延迟高达500ms。通过批量读取优化&#xff0c;最终将延迟降至20ms以内…...

nvidia系列教程-agx-orin安装ros

目录 前言 一、安装前的准备工作 二、ROS安装 三、ROS验证 总结 前言 在机器人开发、自动驾驶等领域,NVIDIA Jetson AGX Orin 凭借其强大的算力成为开发者的得力工具。而 ROS(Robot Operating System)作为机器人领域广泛使用的开源框架,为开发者提供了丰富的功能和工具。…...

CMake入门:3、变量操作 set 和 list

在 CMake 中&#xff0c;set 和 list 是两个核心命令&#xff0c;用于变量管理和列表操作。理解它们的用法对于编写高效的 CMakeLists.txt 文件至关重要。下面详细介绍这两个命令的功能和常见用法&#xff1a; 一、set 命令&#xff1a;变量定义与赋值 set 命令用于创建、修改…...

云服务器自带的防御可靠吗

最近有不少朋友问我&#xff0c;云服务器自带的防御靠不靠谱。就拿大厂的云服务器来说&#xff0c;很多都自带5G防御。但这5G防御能力&#xff0c;在如今的网络攻击环境下&#xff0c;真的有些不够看。 如今&#xff0c;网络攻击手段层出不穷&#xff0c;攻击流量更是越来越大。…...

Duix.HeyGem:以“离线+开源”重构数字人创作生态

在AI技术快速演进的今天,虚拟数字人正从高成本、高门槛的专业领域走向大众化应用。Duix.HeyGem 数字人项目正是这一趋势下的杰出代表。该项目由一支拥有七年AI研发经验的团队打造,通过放弃传统3D建模路径,转向真人视频驱动的AI训练模型,成功实现了低成本、高质量、本地化的…...

MPLAB X IDE ​软件安装与卸载

1、下载MPLAB X IDE V6.25 MPLAB X IDE | Microchip Technology 正常选Windows&#xff0c;点击Download&#xff0c;等待自动下载完成&#xff1b; MPLAB X IDE 一台电脑上可以安装多个版本&#xff1b; 2、安装MPLAB X IDE V6.25 右键以管理员运行&#xff1b;next; 勾选 I a…...

Ubuntu 25.10 将默认使用 sudo-rs

非盈利组织 Trifecta Tech Foundation 报告&#xff0c;Ubuntu 25.10 将默认使用它开发的 sudo-rs——用内存安全语言 Rust 开发的 sudo 实现。 Ubuntu 25.10 代号 Questing Quokka&#xff0c;预计将于 2025 年 10 月释出&#xff0c;是一个短期支持版本。Sudo-rs 是 Trifect…...