Compare commits

...

13 Commits

Author SHA1 Message Date
maxf
e9c65f8866 [tag] 1.5.3-jre11
Some checks failed
yexuejc-base package jre11 / package_job (push) Has been cancelled
2025-04-27 16:38:06 +08:00
maxf
f2c904869f [gitea] runner test
Some checks failed
yexuejc-base package jre11 / package_job (push) Failing after 40s
2025-01-23 16:10:16 +08:00
maxf
b0fd28d208 [gitea] runner test
Some checks failed
yexuejc-base package jre11 / package_job (push) Failing after 2m31s
2025-01-23 13:24:01 +08:00
maxf
982f3aa418 [update] 更新jwt工具 2024-09-01 16:47:43 +08:00
maxf
8b43d99e11 [update] 完善AES ALGORITHM 2024-07-04 17:39:45 +08:00
yexuejc
31040ad0e9 [update] 测试代码更新 2024-06-20 09:47:22 +08:00
yexuejc
9f5ecefc43 [update] StrUtil 增加国家代码二进制相互转换 2024-06-13 16:38:41 +08:00
yexuejc
beb72c8009 [update] StrUtil 增加国家的二进制转换方法和不为空执行方法 2024-06-05 16:23:51 +08:00
its
0403c7c693 [test] AES单元测试 2024-04-26 13:14:17 +08:00
its
cf8231017d [update] AES类兼容ECB(虽然不再建议利用) 2024-04-26 11:41:08 +08:00
its
1415200b1a [update] JsonUtil 增加objToMap;优化obj2Json
DateUtil 标准化日期时间的转换函数
2024-04-19 20:04:10 +08:00
its
0954eb64b5 [update] FileUtil增加读取大文件自定义方法和单纯读取方法 2024-04-15 18:19:04 +08:00
its
70aad08b97 [update] FileUtil增加读取大文件自定义方法和单纯读取方法 2024-04-15 17:21:39 +08:00
17 changed files with 834 additions and 125 deletions

View File

@ -0,0 +1,24 @@
name: yexuejc-base package jre11
run-name: Deploy to ${{ inputs.deploy_target }} by @${{ github.actor }} 🚀
on:
push:
branches:
- jre11
jobs:
package_job:
runs-on: ubuntu-latest
steps:
- run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event."
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!"
- run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
- name: Check out repository code
uses: advanced-security/maven-dependency-submission-action@v4
- run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
- name: List files in the repository
run: |
ls ${{ gitea.workspace }}
mvn -v
mvn clean package
- run: echo "🍏 This job's status is ${{ job.status }}."

View File

@ -1,6 +1,17 @@
yexuejc-base 更新记录
------------------
#### version 1.5.3-jre11
**time 2025-4-27 16:37:29** <br/>
**branch** jre11 <br/>
**update** <br/>
1. [FileUtil](src/main/java/com/yexuejc/base/util/FileUtil.java) 增加读取大文件自定义方法和单纯读取方法
2. [JsonUtil](src/main/java/com/yexuejc/base/util/JsonUtil.java) 增加objToMap优化obj2Json
3. [DateUtil](src/main/java/com/yexuejc/base/util/DateUtil.java) 标准化日期时间的转换函数
4. [AES](src/main/java/com/yexuejc/base/encrypt/AES.java) 兼容ECB(虽然不再建议利用)
5. [StrUtil](src/main/java/com/yexuejc/base/util/StrUtil.java) 增加国家代码二进制相互转换
---
#### version 1.5.2-jre11
**time2024-4-7 14:34:33** <br/>
**branch** jre11 <br/>

View File

@ -6,7 +6,7 @@
<groupId>top.yexuejc</groupId>
<artifactId>yexuejc-base</artifactId>
<version>1.5.2-jre11</version>
<version>1.5.3-jre11</version>
<name>yexuejc-base</name>
<url>https://github.com/yexuejc/yexuejc-base</url>

View File

@ -0,0 +1,38 @@
package com.yexuejc.base.converter;
import java.io.IOException;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer;
/**
* 反序列化中内容为空时返回Integer为空
* <p>使用方式@JsonDeserialize(using = IntegerNullValueDeserializer.class)</p>
* @author: yexuejc
* @date: 2024/4/15 18:08
*/
public class IntegerNullValueDeserializer extends StdScalarDeserializer<Integer> {
public IntegerNullValueDeserializer() {
super(Integer.class);
}
@Override
public Integer deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
String value = p.getValueAsString();
if (isInteger(value)) {
return super._parseInteger(p, ctxt, Integer.class);
} else {
return null;
}
}
private static boolean isInteger(String s) {
try {
Integer.parseInt(s);
return true;
} catch (NumberFormatException e) {
return false;
}
}
}

View File

@ -1,11 +1,11 @@
package com.yexuejc.base.encrypt;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
/**
* AES加解密
@ -35,32 +35,50 @@ public class AES {
AES_CBC_NoPadding("AES/CBC/NoPadding"),
//AES/CBC/PKCS5Padding 32 16
AES_CBC_PKCS5Padding("AES/CBC/PKCS5Padding"),
//AES/CBC/PKCS7Padding 32 16
AES_CBC_PKCS7Padding("AES/CBC/PKCS7Padding"),
//AES/CBC/ISO10126Padding 32 16
AES_CBC_ISO10126Padding("AES/CBC/ISO10126Padding"),
//AES/CBC/ZeroPadding 16 16
AES_CBC_ZeroPadding("AES/CBC/ZeroPadding"),
//AES/CFB/NoPadding 16 原始数据长度
AES_CFB_NoPadding("AES/CFB/NoPadding"),
//AES/CFB/PKCS5Padding 32 16
//AES/CFB/PKCS5Padding 16 不支持
AES_CFB_PKCS5Padding("AES/CFB/PKCS5Padding"),
//AES/CFB/ISO10126Padding 32 16
//AES/CFB/PKCS7Padding 16 不支持
AES_CFB_PKCS7Padding("AES/CFB/PKCS7Padding"),
//AES/CFB/ISO10126Padding 16 不支持
AES_CFB_ISO10126Padding("AES/CFB/ISO10126Padding"),
//AES/CFB/ZeroPadding 16 不支持
AES_CFB_ZeroPadding("AES/CFB/ZeroPadding"),
//AES/ECB/NoPadding 16 不支持
AES_ECB_NoPadding("AES/ECB/NoPadding"),
//AES/ECB/PKCS5Padding 32 16
//AES/ECB/PKCS5Padding 32 不支持
AES_ECB_PKCS5Padding("AES/ECB/PKCS5Padding"),
//AES/ECB/PKCS7Padding 32 16
AES_ECB_PKCS7Padding("AES/ECB/PKCS7Padding"),
//AES/ECB/ISO10126Padding 32 16
AES_ECB_ISO10126Padding("AES/ECB/ISO10126Padding"),
//AES/ECB/ZeroPadding 16 16
AES_ECB_ZeroPadding("AES/ECB/ZeroPadding"),
//AES/OFB/NoPadding 16 原始数据长度
AES_OFB_NoPadding("AES/OFB/NoPadding"),
//AES/OFB/PKCS5Padding 32 16
AES_OFB_PKCS5Padding("AES/OFB/PKCS5Padding"),
//AES/OFB/PKCS7Padding 32 16
AES_OFB_PKCS7Padding("AES/OFB/PKCS7Padding"),
//AES/OFB/ISO10126Padding 32 16
AES_OFB_ISO10126Padding("AES/OFB/ISO10126Padding"),
//AES/OFB/ZeroPadding 16 16
AES_OFB_ZeroPadding("AES/OFB/ZeroPadding"),
//AES/PCBC/NoPadding 16 不支持
AES_PCBC_NoPadding("AES/PCBC/NoPadding"),
//AES/PCBC/PKCS5Padding 32 16
AES_PCBC_PKCS5Padding("AES/PCBC/PKCS5Padding"),
//AES/PCBC/ISO10126Padding 32 16
AES_PCBC_ISO10126Padding("AES/PCBC/ISO10126Padding");
AES_PCBC_ISO10126Padding("AES/PCBC/ISO10126Padding"),
//AES/CTR/NoPadding 16 不支持
AES_CTR_NoPadding("AES/CTR/NoPadding");
public String name;
ALGORITHM(String name) {
@ -93,7 +111,10 @@ public class AES {
byte[] plaintext = new byte[plaintextLength];
System.arraycopy(dataBytes, 0, plaintext, 0, dataBytes.length);
SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(charset), AES_ALGORITHM);
IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes(charset));
IvParameterSpec ivspec = null;
if(!algorithm.name.contains("ECB")){
ivspec = new IvParameterSpec(iv.getBytes(charset));
}
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
byte[] encrypted = cipher.doFinal(plaintext);
return Base64.getEncoder().encodeToString(encrypted);
@ -115,7 +136,10 @@ public class AES {
byte[] encrypted = Base64.getDecoder().decode(data);
Cipher cipher = Cipher.getInstance(algorithm.name);
SecretKeySpec keyspec = new SecretKeySpec(key.getBytes(charset), AES_ALGORITHM);
IvParameterSpec ivspec = new IvParameterSpec(iv.getBytes(charset));
IvParameterSpec ivspec = null;
if(!algorithm.name.contains("ECB")){
ivspec = new IvParameterSpec(iv.getBytes(charset));
}
cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
byte[] original = cipher.doFinal(encrypted);
return new String(original, charset).trim();

View File

@ -80,57 +80,122 @@ public class DateUtil {
/**
* 日期字符串转date
*
* @param dateStr
* @return Date
* @param dateStr 格式yyyy-MM-dd
* @return Date 日期
* @throws ParseException
* @deprecated 替代参考 {@link #parseDate(String, String)}
* @see 1.5.2
*/
@Deprecated
public static Date str2date(String dateStr) throws ParseException {
Date date = DATE_FORMAT.parse(dateStr);
return date;
}
/**
* date转字符串
*
* @param date
* @return Date
* @throws ParseException
* @param date 日期
* @return String 格式 yyyy-MM-dd
* @deprecated 替代参考 {@link #formatDate(Date, String)} {@link #formatDate(Date, String, Locale)}
* @see 1.5.2
*/
public static String date2str(Date date) throws ParseException {
@Deprecated
public static String date2str(Date date) {
if (date != null) {
return DATE_FORMAT.format(date);
} else {
return "null";
return "";
}
}
/**
* 日期字符串转dateTime
*
* @param dateStr
* @return
* @param dateStr 格式yyyy-MM-dd HH:mm:ss.SSS
* @return Date 时间
* @throws ParseException
* @deprecated 替代参考 {@link #parseDate(String, String)}
* @see 1.5.2
*/
@Deprecated
public static Date str2dateTime(String dateStr) throws ParseException {
Date date = DATE_TIME_FORMAT.parse(dateStr);
return date;
return DATE_TIME_FORMAT.parse(dateStr);
}
/**
* dateTime转字符串
*
* @param date
* @return Date
* @throws ParseException
* @param date 时间
* @return String 格式yyyy-MM-dd HH:mm:ss.SSS
* @deprecated 替代参考 {@link #formatDate(Date, String)} {@link #formatDate(Date, String, Locale)}
* @see 1.5.2
*/
public static String dateTime2str(Date date) throws ParseException {
@Deprecated
public static String dateTime2str(Date date) {
if (date != null) {
return DATE_TIME_FORMAT.format(date);
} else {
return "null";
return "";
}
}
/**
* 格式化当前日期为指定格式的字符串
*
* @param dateFormat 日期格式字符串用于指定日期的输出格式例如"yyyy-MM-dd HH:mm:ss"
* @return 格式化后的当前日期字符串
*/
public static String formatDateNow(String dateFormat) {
return formatDate(new Date(), dateFormat); // 使用系统当前时间生成日期对象并按指定格式进行格式化
}
/**
* 解析字符串形式的日期到Date对象
*
* @param dateStr 待解析的日期字符串
* @param dateFormat 日期字符串的格式
* @return 返回解析后的Date对象如果解析失败则返回null
* @throws ParseException
*/
public static Date parseDate(String dateStr, String dateFormat) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
// 尝试根据给定的日期格式解析日期字符串
return sdf.parse(dateStr);
}
/**
* 根据指定的日期格式和地域格式化日期对象为字符串
* 如果未指定地域则默认使用中文地域格式
*
* @param date 需要格式化的日期对象
* @param dateFormat 日期格式字符串例如"yyyy-MM-dd"
* @return 格式化后的日期字符串
*/
public static String formatDate(Date date, String dateFormat) {
return formatDate(date, dateFormat, null);
}
/**
* 根据指定的日期格式地域格式化日期对象为字符串
*
* @param date 需要格式化的日期对象
* @param dateFormat 日期格式字符串例如"yyyy-MM-dd"
* @param locale 地域设置如果为null则默认使用中文地域
* @return 格式化后的日期字符串
*/
public static String formatDate(Date date, String dateFormat, Locale locale) {
SimpleDateFormat sdf;
// 根据是否提供了地域参数来创建SimpleDateFormat实例
if (StrUtil.isEmpty(locale)) {
sdf = new SimpleDateFormat(dateFormat, Locale.CHINA);
} else {
sdf = new SimpleDateFormat(dateFormat, locale);
}
return sdf.format(date);
}
/**
* 获取本周的日期

View File

@ -18,6 +18,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.List;
import java.util.function.Function;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.CRC32;
@ -38,11 +39,14 @@ import io.jsonwebtoken.lang.Assert;
* @time 2017年11月3日 下午3:12:49
*/
public class FileUtil {
static Logger logger = Logger.getLogger(FileUtil.class.getName());
private static final Logger logger = Logger.getLogger(FileUtil.class.getName());
private FileUtil() {
}
private static final String NEW_LINE = "\n";
private static final String CONSTANT_DOT = ".";
private static final String TYPE_TAR_GZ = ".tar.gz";
private static final String TYPE_CSV = ".csv";
private static final String TAR_GZ = "tar.gz";
@ -61,7 +65,7 @@ public class FileUtil {
if (fileName.lastIndexOf(TYPE_TAR_GZ) > 0) {
return TAR_GZ;
}
return fileName.substring(fileName.lastIndexOf(".") + 1);
return fileName.substring(fileName.lastIndexOf(CONSTANT_DOT) + 1);
} catch (Exception e) {
logger.severe("file doesn't exist or is not a file");
}
@ -332,22 +336,19 @@ public class FileUtil {
}
/**
* 分段读取大文件
* 分段读取大文件不限格式
*
* @param csvFilePath 文件路径
* @param filePath 文件路径
* @param readFileBean 分段每次读取的bean 初始值需要设置每次读取的行数
* @param <T> 读取结果类型bean
* @return
* @return 文件分页读取内容自定义处理后及读取信息
*/
public static <T> ReadFileBean<T> readBigFile(String csvFilePath, ReadFileBean<T> readFileBean, Class<T> readCls) throws IOException {
if (!isFileExist(csvFilePath)) {
throw new FileNotFoundException(String.format("解析用的csv [%s] 文件不存在。", csvFilePath));
}
if (!csvFilePath.endsWith(TYPE_CSV)) {
throw new IOException(String.format("解析用的csv [%s] 文件不是CSV文件格式。", csvFilePath));
public static <T> ReadFileBean<T> readBigFile(String filePath, ReadFileBean<T> readFileBean, Function<List<String>, List<T>> readAfter) throws IOException {
if (!isFileExist(filePath)) {
throw new FileNotFoundException(String.format("[%s]文件不存在。", filePath));
}
List<String> datas = new ArrayList<>();
try (RandomAccessFile randomAccessFile = new RandomAccessFile(new File(csvFilePath), "r")) {
try (RandomAccessFile randomAccessFile = new RandomAccessFile(new File(filePath), "r")) {
if (readFileBean.getPointer() < 0) {
readFileBean.setPointer(0);
}
@ -365,29 +366,60 @@ public class FileUtil {
//无数据
return readFileBean.setDatas(new ArrayList<>());
}
//csv文件处理
com.yexuejc.base.pojo.CsvToBean csvToBean = getCsvToBean(readCls);
readFileBean.setHeader(csvToBean.getHeader());
if (csvToBean.hasHeader()) {
//文件存在header,设置header优先,没设置使用文件的
if (StrUtil.isNotEmpty(csvToBean.getHeader())) {
//替换header
datas.remove(0);
datas.add(0, csvToBean.getHeader());
} else {
readFileBean.setHeader(datas.get(0));
}
} else {
//文件不存在header使用设置的
datas.add(0, csvToBean.getHeader());
}
List<T> dataList = readCsv(String.join("\n", datas), readCls, csvToBean.getDelimiter());
List<T> dataList = readAfter.apply(datas);
readFileBean.setDatas(dataList);
return readFileBean;
}
/**
* 分段读取大文件(不解析)
*
* @param csvFilePath 文件路径
* @param readFileBean 分段每次读取的bean 初始值需要设置每次读取的行数
* @return 文件分页读取内容每行为一个String对象及读取信息
*/
public static ReadFileBean<String> readBigFile(String csvFilePath, ReadFileBean<String> readFileBean) throws IOException {
return readBigFile(csvFilePath, readFileBean, (datas) -> datas);
}
/**
* 分段读取大文件(CSV格式)
*
* @param csvFilePath 文件路径
* @param readFileBean 分段每次读取的bean 初始值需要设置每次读取的行数
* @param <T> 读取结果类型bean
* @return 文件分页读取内容转bean后及读取信息
*/
public static <T> ReadFileBean<T> readBigFile(String csvFilePath, ReadFileBean<T> readFileBean, Class<T> readCls) throws IOException {
if (!csvFilePath.endsWith(TYPE_CSV)) {
throw new IOException(String.format("[%s]文件不是CSV文件格式。", csvFilePath));
}
return readBigFile(csvFilePath, readFileBean, (datas) -> {
//csv文件处理
com.yexuejc.base.pojo.CsvToBean csvToBean = getCsvToBean(readCls);
readFileBean.setHeader(csvToBean.getHeader());
if (csvToBean.hasHeader()) {
//文件存在header,设置header优先,没设置使用文件的
if (StrUtil.isNotEmpty(csvToBean.getHeader())) {
//替换header
datas.remove(0);
datas.add(0, csvToBean.getHeader());
} else {
readFileBean.setHeader(datas.get(0));
}
} else {
//文件不存在header使用设置的
datas.add(0, csvToBean.getHeader());
}
try {
return readCsv(String.join(NEW_LINE, datas), readCls, csvToBean.getDelimiter());
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
/**
* 获取csv的header,使用注解{@link CsvToBean}
*

View File

@ -249,6 +249,29 @@ public class JsonUtil {
return pojo;
}
/**
* Json字符串转换为Java对象
*
* @param in 输入流
* @param parametrized 容器类
* @param parameterClasses 实际类
* @return
*/
public static <T> T json2Obj(InputStream in, Class<T> parametrized, Class<?>... parameterClasses) {
T pojo = null;
JavaType javaType = jsonMapper.getTypeFactory().constructParametricType(parametrized, parameterClasses);
try {
pojo = jsonMapper.readValue(in, javaType);
} catch (JsonParseException e) {
log.warning("json to Object JsonParseException.\n" + StrUtil.printStackTrace(e));
} catch (JsonMappingException e) {
log.warning("json to Object JsonMappingException.\n" + StrUtil.printStackTrace(e));
} catch (IOException e) {
log.warning("json to Object IOException.\n" + StrUtil.printStackTrace(e));
}
return pojo;
}
/**
* Json字符串转换为Java-Map对象
*
@ -291,29 +314,6 @@ public class JsonUtil {
return null;
}
/**
* Json字符串转换为Java对象
*
* @param json 字符串
* @param parametrized 容器类
* @param parameterClasses 实际类
* @return
*/
public static <T> T json2Obj(InputStream json, Class<T> parametrized, Class<?>... parameterClasses) {
T pojo = null;
JavaType javaType = jsonMapper.getTypeFactory().constructParametrizedType(parametrized, parametrized,
parameterClasses);
try {
pojo = jsonMapper.readValue(json, javaType);
} catch (JsonParseException e) {
log.warning("json to Object JsonParseException.\n" + StrUtil.printStackTrace(e));
} catch (JsonMappingException e) {
log.warning("json to Object JsonMappingException.\n" + StrUtil.printStackTrace(e));
} catch (IOException e) {
log.warning("json to Object IOException.\n" + StrUtil.printStackTrace(e));
}
return pojo;
}
/**
* 将任何对象转换为json
@ -322,15 +322,55 @@ public class JsonUtil {
* @return 返回json
*/
public static String obj2Json(Object pojo) {
String json = null;
if (StrUtil.isEmpty(pojo)) {
return "";
}
String json = "";
try {
json = jsonMapper.writeValueAsString(pojo);
return jsonMapper.writeValueAsString(pojo);
} catch (JsonProcessingException e) {
log.warning("json to Object JsonProcessingException.\n" + StrUtil.printStackTrace(e));
}
return json;
}
/**
* 从bean到Map的转换
* <p>
* Map map = JsonUtil.obj2Map(data);
* </p>
*
* @param pojo bean
* @return Map
*/
public static Map<?, ?> objToMap(Object pojo) {
return json2Obj(obj2Json(pojo), Map.class);
}
/**
* json -> Map<String, String> 转换
*
* @param json JSON
* @return Map
*/
public static Map<String, String> jsonToMapString(String json) {
return objToMap(json, String.class, String.class);
}
/**
* Bean 对象转换为 Map
* <p>
* 示例Map<String, RequestBean> map = JacksonMapperConfig.objToMap(data, String.class, RequestBean.class);
* </p>
*
* @param pojo Bean 数据
* @return Map 数据
*/
public static <K, V> Map<K, V> objToMap(Object pojo, Class<K> kCls, Class<V> vCls) {
return json2Obj(obj2Json(pojo), Map.class, kCls, vCls);
}
/**
* 格式化输出
*
@ -338,7 +378,7 @@ public class JsonUtil {
* @return 格式化后的字符串
*/
public static String formatPrinter(Object obj) {
String json = null;
String json = "";
try {
json = jsonMapper.writerWithDefaultPrettyPrinter().writeValueAsString(obj);
} catch (JsonProcessingException e) {

View File

@ -1,12 +1,14 @@
package com.yexuejc.base.util;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Map;
import javax.crypto.SecretKey;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SecureDigestAlgorithm;
/**
* jwt工具类
@ -34,35 +36,46 @@ public class JwtUtil {
/**
* 参数配置设置一次即可多次设置会覆盖之前的
*
* @param key 加密key 默认h%OG8Y3WgA5AN7&6Ke7I#C1XvneW0N8a
* @param type 加密类型默认JWT
* @param iss token发行商: 默认yexuejc.top
* @param type 加密类型默认JWT
* @param iss token发行商: 默认yexuejc.top
* @param key 加密key 默认h%OG8Y3WgA5AN7&6Ke7I#C1XvneW0N8a
* @param algorithm 加密方式
* @return
*/
public static JwtUtil config(String key, String type, String iss) {
public static JwtUtil config(String type, String iss, String key, SecureDigestAlgorithm<SecretKey, SecretKey> algorithm) {
JwtUtil jwtUtil = instace();
jwtUtil.JWT_SIGNATURE_KEY = key;
jwtUtil.JWT_HEADER_TYP = type;
jwtUtil.JWT_CLAIMS_ISS = iss;
if (null != key) {
jwtUtil.jwtSignatureKey = key;
}
if (null != type) {
jwtUtil.jwtHeaderTyp = type;
}
if (null != iss) {
jwtUtil.jwtClaimsIss = iss;
}
if (null != algorithm) {
jwtUtil.algorithm = algorithm;
}
return jwtUtil;
}
public static class Instace {
private static class Instace {
private static JwtUtil jwtUtil = new JwtUtil();
}
/**
* 加密用KEY
*/
private String JWT_SIGNATURE_KEY = "h%OG8Y3WgA5AN7&6Ke7I#C1XvneW0N8a";
private String jwtSignatureKey = "h%OG8Y3WgA5AN7&6Ke7I#C1XvneW0N8a";
/**
* token类型
*/
private String JWT_HEADER_TYP = "JWT";
private String jwtHeaderTyp = "JWT";
/**
* token发行商
*/
private String JWT_CLAIMS_ISS = "yexuejc.top";
private String jwtClaimsIss = "yexuejc.top";
private SecureDigestAlgorithm<SecretKey, SecretKey> algorithm = Jwts.SIG.HS512;
/**
* 加密内容生成token
@ -71,27 +84,36 @@ public class JwtUtil {
* @return
*/
public String compact(Object subjectObj) {
String subject = null;
if (subjectObj instanceof String) {
subject = (String) subjectObj;
} else {
subject = JsonUtil.obj2Json(subjectObj);
return compact(null, subjectObj);
}
public String compact(String subId, Object subjectObj) {
String subject = subId;
Map<String, Object> subMap = JsonUtil.objToMap(subjectObj, String.class, Object.class);
if (StrUtil.isEmpty(subject)) {
if (subjectObj instanceof String) {
subject = (String) subjectObj;
} else if (StrUtil.isNotEmpty(subMap)) {
Object sid = subMap.get("subId");
Object s = subMap.get("sub");
subject = StrUtil.isNotEmpty(sid) ? String.valueOf(sid) : String.valueOf(s);
}
}
Date now = new Date();
return Jwts.builder()
// 设置token的唯一标识IDclaims.jti
.id(StrUtil.genUUID())
// 设置token类型header.typ
.header().add("typ", JWT_HEADER_TYP)
.and()
.header().add("typ", jwtHeaderTyp).and()
// 设置token发行时间为当前时间claims.iat
.issuedAt(now)
.issuedAt(now).claims(JsonUtil.objToMap(subjectObj, String.class, Object.class))
// 设置token发行商/发行者claims.iss
.issuer(JWT_CLAIMS_ISS)
.issuer(jwtClaimsIss)
// 设置token用户定义主体claims.sub
.subject(subject)
// 设置算法签名,(密钥加密算法)
.signWith(getSecretKey(), Jwts.SIG.HS512)
.signWith(getSecretKey(), algorithm)
// 生成token
.compact();
}
@ -114,20 +136,27 @@ public class JwtUtil {
* @return
*/
public <T> T parse(String token, Class<T> cls) {
return JsonUtil.json2Obj(parseStr(token), cls);
return JsonUtil.json2Obj(JsonUtil.obj2Json(parseStr(token)), cls);
}
/**
* 解密token为字符串
*
* @param token
* @return
*/
public String parseStr(String token) {
return Jwts.parser().verifyWith(getSecretKey()).build().parseSignedClaims(token).getPayload().getSubject();
public Claims parseStr(String token) {
return Jwts.parser().verifyWith(getSecretKey()).build().parseSignedClaims(token).getPayload();
}
private SecretKey getSecretKey() {
byte[] bytes = Decoders.BASE64.decode(JWT_SIGNATURE_KEY);
int minLen = 32;
if (jwtSignatureKey.length() % minLen != 0) {
// 补齐32的倍数,左补0
String format = "%" + ((jwtSignatureKey.length() / minLen + 1) * minLen) + "s";
jwtSignatureKey = String.format(format, jwtSignatureKey).replace(' ', '0');
}
byte[] bytes = jwtSignatureKey.getBytes(StandardCharsets.UTF_8);
return Keys.hmacShaKeyFor(bytes);
}
}

View File

@ -4,15 +4,7 @@ import java.lang.reflect.Array;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Random;
import java.util.UUID;
import java.util.*;
import java.util.function.Consumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -473,4 +465,110 @@ public final class StrUtil {
}
return "";
}
/**
* 不是空对象时执行
*
* @param checkObj 判定对象
* @param consumer 执行器
* @param <T> 判定对象
*/
public static <T> void notNullExecute(T checkObj, Consumer<T> consumer) {
if (null != checkObj) {
consumer.accept(checkObj);
}
}
/**
* 不是空内容时执行
*
* @param checkObj 判定对象
* @param consumer 执行器
* @param <T> 判定对象
*/
public static <T> void notEmptyExecute(T checkObj, Consumer<T> consumer) {
if (isNotEmpty(checkObj)) {
consumer.accept(checkObj);
}
}
private static final List<String> COUNTRY_CODE = Arrays.asList("JPN", "KOR", "THA", "SGP", "CHN", "TWN", "HKG", "MAC", "999");
/**
* 国名を国コードに変換
*
* @param country JPN日本KOR韓国THAタイSGPシンガポールCHN中国内陸TWN中国台湾HKG中国香港MACマカオ999その他不明
* @return code byte <pre>
* 1 0 1  0      1 1 1 1 1 <br>
* 日本 韓国  タイ シンガポール 中国内陸 台湾  香港 マカオ その他
* <br>
* 0位その他1位マカオ2位香港3位台湾4位中国内陸5位シンガポール6位タイ7位韓国8位日本
* <br> 1当該国で表示0当該国表示しない
* </pre>
*/
public static byte countryToCodeByte(String country) {
String code = countryToCode(country);
return (byte) Integer.parseInt(code, 2);
}
/**
* 国家代码转換成二进制
*
* @param country JPN日本KOR韓国THA泰国CHN中国内陸SGP新加坡TWN中国台湾HKG中国香港MAC中国澳门999其他不明
* @return <pre>
* 1 0 1 0    1 1 1 1 1 <br>
* 日本 韓国  泰国 新加坡 中国内陸 台湾  香港 澳门 其他
* <br>
* 0位其他1位中国澳门2位中国香港3位中国台湾4位中国内陸5位新加坡6位泰国7位韓国8位日本
* <br> 1在该国表示0不表示该国
* </pre>
*/
public static String countryToCode(String country) {
int index = COUNTRY_CODE.indexOf(country);
if (index == -1) {
return "000000000";
}
int bn = 1 << (COUNTRY_CODE.size() - 1 - index);
// 转成二进制
String bs = Integer.toBinaryString(bn);
// 为了保证长度一致前面补0
return String.format("%09d", Integer.parseInt(bs));
}
/**
* 国家代码二进制转国家代码
*
* @param countryCode 国家代码二进制转:010000000
* <pre>
* 1 0 1 0    1 1 1 1 1 <br>
* 日本 韓国  泰国 新加坡 中国内陸 台湾  香港 澳门 其他
* <br>
* 0位其他1位中国澳门2位中国香港3位中国台湾4位中国内陸5位新加坡6位泰国7位韓国8位日本
* <br> 1在该国表示0不表示该国
* </pre>
* @return JPN日本KOR韓国THA泰国CHN中国内陸SGP新加坡TWN中国台湾HKG中国香港MAC中国澳门999其他不明
*/
public static String getCountryByCode(String countryCode) {
int i = Integer.parseInt(countryCode, 2);
int index = Integer.numberOfTrailingZeros(i);
if (index > COUNTRY_CODE.size()) {
return "O";
}
return COUNTRY_CODE.get(COUNTRY_CODE.size() - 1 - index);
}
/**
* nationalCodeは想定国エリア範囲内存在するかどうか
* 想定国エリア範囲"JPN", "KOR", "THA", "SGP", "CHN", "TWN", "HKG", "MAC", "999"
*
* @param nationCode
* @return 存在true存在しないfalse
*/
public static boolean containsCountry(String nationCode) {
if (isNotEmpty(nationCode)) {
return COUNTRY_CODE.contains(nationCode);
}
return false;
}
}

View File

@ -0,0 +1,55 @@
package com.yexuejc.base.encrypt;
import java.nio.charset.StandardCharsets;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
public class AESTest {
@Test
public void testEncrypt() throws Exception {
String data = "Hello World!";
AES aes = AES.builder()
.setAlgorithm(AES.ALGORITHM.AES_CBC_PKCS5Padding)
.setKey("hj7x89H$yuBI0456")
.setIv("NIfb&95GUY86Gfgh")
.setCharset(StandardCharsets.UTF_8);
String encrypted = aes.encrypt(data);
assertNotNull(encrypted);
assertFalse(encrypted.isEmpty());
}
@Test
public void testDecrypt() throws Exception {
String data = "p0x0vK5T6OOy69+p9cgI/9xfeoi/f0t6NO7HbLsUON4=";
AES aes = AES.builder()
.setAlgorithm(AES.ALGORITHM.AES_CBC_PKCS5Padding)
.setKey("hj7x89H$yuBI0456")
.setIv("NIfb&95GUY86Gfgh")
.setCharset(StandardCharsets.UTF_8);
String decrypted = aes.decrypt(data);
assertNotNull(decrypted);
assertFalse(decrypted.isEmpty());
assertEquals("Hello World!", decrypted);
}
@Test
public void testEncryptAndDecrypt() throws Exception {
String data = "张三";
AES aes = AES.builder()
.setAlgorithm(AES.ALGORITHM.AES_OFB_ISO10126Padding)
.setKey("hj7x89H$yuBI0456")
.setIv("NIfb&95GUY86Gfgh")
.setCharset(StandardCharsets.UTF_8);
String encrypt = aes.encrypt(data);
System.out.println("加密:" + encrypt);
String decrypt = aes.decrypt(encrypt);
System.out.println("解密:" + decrypt);
}
}

View File

@ -0,0 +1,70 @@
package com.yexuejc.base.util;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.util.Date;
import java.util.Locale;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class DateUtilTest {
@Test
public void testParseDate() throws ParseException {
String dateStr = "2022-03-20";
String dateFormat = "yyyy-MM-dd";
Date date = DateUtil.parseDate(dateStr, dateFormat);
SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
assertEquals(sdf.format(date), dateStr);
}
@Test
public void testParseLocalDate() throws ParseException {
String dateStr = "2022-03-20";
String dateFormat = "yyyy-MM-dd";
LocalDate date = DateTimeUtil.parseLocalDate(DateUtil.parseDate(dateStr, dateFormat));
assertEquals(date.toString(), dateStr);
}
@Test
public void testFormatDateNow() {
String dateFormat = "yyyy-MM-dd";
String nowDateStr = DateUtil.formatDateNow(dateFormat);
SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
String nowDate = sdf.format(new Date());
assertEquals(nowDate, nowDateStr);
}
@Test
public void testFormatDate() {
Date date = new Date();
String dateFormat = "yyyy-MM-dd";
String formattedDate = DateUtil.formatDate(date, dateFormat);
SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
assertEquals(sdf.format(date), formattedDate);
}
@Test
public void testFormatDateWithLocale() {
Date date = new Date();
String dateFormat = "yyyy-MM-dd";
Locale locale = Locale.JAPAN;
String formattedDate = DateUtil.formatDate(date, dateFormat, locale);
SimpleDateFormat sdf = new SimpleDateFormat(dateFormat, locale);
assertEquals(sdf.format(date), formattedDate);
}
@Test
public void testGetDateByPlusDay() {
int days = 2;
String dateFormat = "yyyy-MM-dd";
String plusDateStr = DateUtil.formatDate(DateUtil.datePlus(new Date(), days), dateFormat);
LocalDate nowDate = LocalDate.now();
LocalDate plusDate = nowDate.plusDays(days);
assertEquals(plusDate.toString(), plusDateStr);
}
}

View File

@ -4,6 +4,7 @@ import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;
import com.yexuejc.base.pojo.ReadFileBean;
import com.yexuejc.base.util.bean.AppnodeCertCsvBean;
@ -54,19 +55,63 @@ public class FileUtilTest {
}
private static void readCsvFile() throws IOException {
String path = "F:\\coding\\yexuejc-base2\\src\\test\\java\\com\\yexuejc\\base\\util\\test.csv";
String path = "F:\\coding\\yexuejc-base\\src\\test\\java\\com\\yexuejc\\base\\util\\test.csv";
List<AppnodeCertCsvBean> list = FileUtil.readCsv(path, AppnodeCertCsvBean.class, true, "enable,domain,protocol,deployHost,deployPath,uname,pwd,appnodeId", ',');
System.out.println("***********************************************");
System.out.println(JsonUtil.formatPrinter(list));
System.out.println("条数:" + list.size());
// List<AppnodeCertCsvBean> list = FileUtil.readCsv(path, AppnodeCertCsvBean.class, true, "enable,domain,protocol,deployHost,deployPath,uname,pwd,appnodeId", ',');
// System.out.println("***********************************************");
// System.out.println(JsonUtil.formatPrinter(list));
// System.out.println("条数:" + list.size());
//直接把每行读取成字符串
ReadFileBean<String> readFileBean2 = new ReadFileBean<>(2);
ReadFileBean<String> bean2 = FileUtil.readBigFile(path, readFileBean2);
System.out.println("直接把每行读取成字符串============================================");
System.out.println(JsonUtil.formatPrinter(bean2));
System.out.println("直接把每行读取成字符串============================================");
//自定义每行数据的处理
ReadFileBean<AppnodeCertCsvBean> readFileBean1 = new ReadFileBean<>(2);
ReadFileBean<AppnodeCertCsvBean> bean1 = FileUtil.readBigFile(path, readFileBean1, datas -> {
if (readFileBean1.getStartRowNum() == 1) {
datas.remove(0);//跳过第一行
}
return datas.stream().map(str -> {
//自定义处理每一条数据
String[] split = str.split(",");
AppnodeCertCsvBean app = new AppnodeCertCsvBean();
app.setEnable(getValue(split, 0));
app.setDomain(getValue(split, 1));
app.setProtocol(getValue(split, 2));
app.setDeployHost(getValue(split, 3));
app.setDeployPath(getValue(split, 4));
app.setUname(getValue(split, 5));
app.setPwd(getValue(split, 6));
if (StrUtil.isNotEmpty(getValue(split, 7))) {
app.setAppnodeId(Integer.valueOf(getValue(split, 7)));
}
return app;
}).collect(Collectors.toList());
});
System.out.println("自定义每行数据的处理============================================");
System.out.println(JsonUtil.formatPrinter(bean1));
System.out.println("自定义每行数据的处理============================================");
//直接使用提供的csv文件读取
ReadFileBean<AppnodeCertCsvBean> readFileBean = new ReadFileBean<>(2);
do {
ReadFileBean<AppnodeCertCsvBean> bean = FileUtil.readBigFile(path, readFileBean, AppnodeCertCsvBean.class);
System.out.println("============================================");
System.out.println("直接使用提供的csv文件读取============================================");
System.out.println(JsonUtil.formatPrinter(bean));
System.out.println("直接使用提供的csv文件读取============================================");
} while (readFileBean.hasNext());
}
private static String getValue(String[] value, int index) {
try {
return value[index];
} catch (Exception e) {
return "";
}
}
}

View File

@ -0,0 +1,26 @@
package com.yexuejc.base.util;
import java.util.Map;
import io.jsonwebtoken.Jwts;
import org.junit.jupiter.api.Test;
/**
* @author maxiaofeng
* @date 2024/9/1 15:23
*/
class JwtUtilTest {
@Test
void compact() {
Map map = JsonUtil.json2Obj("{\n" + " \"payload\": {\n" + " \"sub\": \"0fqgx9h1d9eijd07umlyn1ez52jvkkkb883j\",\n" + " " +
"\"iss\": \"www" + ".donki.com\",\n" + " \"aud\": \"D27A80C9AF01488F9A1152B8C20D93BA\",\n" + " \"exp\": 1725260806," +
"\n" + " " + "\"iat\": 1725174406,\n" + " \"address\": {\n" + " \"country\": \"南朝鮮\"\n" + " },\n" + " " + "\"email_verified\": true,\n" + " \"birthdate\": \"1993/11/16\",\n" + " \"news_letter\": \"1\",\n" + " " + "\"gender\": \"other\",\n" + " \"member_code\": \"3511623793483553889787\",\n" + " \"preferred_username\": " + "\"3923490434\",\n" + " \"nonce\": \"mjhjSTmtVRNWPBtOQXZx\",\n" + " \"country_code\": \"CHN\",\n" + " " + "\"language_code\": \"zh-TW\",\n" + " \"updated_at\": 1722404351,\n" + " \"nickname\": \"3923490434\",\n" + " " + "\"customer_flag\": \"0\",\n" + " \"service_term_version\": \"2\",\n" + " \"email\": \"1828844769@qq.com\",\n" + " " + " \"auth_time\": 1725173983\n" + " }\n" + "}", Map.class);
JwtUtil jwtUtil = JwtUtil.config("JWT", "www.donki.com", "5326D79EDEF344C3B3431F268DD2564C", Jwts.SIG.HS256);
String jwtStr = jwtUtil.compact(map.get("payload"));
System.out.println(jwtStr);
Map<?, ?> parse = jwtUtil.parse(jwtStr);
System.out.println(JsonUtil.obj2Json(parse));
}
}

View File

@ -0,0 +1,139 @@
package com.yexuejc.base.util;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
class StrUtilTest {
@Test
void isEmpty() {
}
@Test
void isNotEmpty() {
}
@Test
void genUUID() {
}
@Test
void testGenUUID() {
}
@Test
void genNum() {
}
@Test
void toHex() {
}
@Test
void toMD5() {
}
@Test
void toSHA256() {
}
@Test
void toSHA() {
}
@Test
void iso2utf() {
}
@Test
void isNumeric() {
}
@Test
void codeId() {
}
@Test
void decodeId() {
}
@Test
void parseUrlencoded() {
}
@Test
void getSignContent() {
}
@Test
void replaceMobile() {
}
@Test
void mapSort() {
}
@Test
void setStr() {
}
@Test
void underlineToCamel() {
}
@Test
void camelToUnderline() {
}
@Test
void printStackTraceAndMessage() {
}
@Test
void printStackTrace() {
}
@Test
void notNullExecute() {
}
@Test
void notEmptyExecute() {
}
@Test
void countryToCode() {
Assertions.assertEquals(StrUtil.countryToCode("JPN"), "100000000");
Assertions.assertEquals(StrUtil.countryToCode("KOR"), "010000000");
Assertions.assertEquals(StrUtil.countryToCode("THA"), "001000000");
Assertions.assertEquals(StrUtil.countryToCode("SGP"), "000100000");
Assertions.assertEquals(StrUtil.countryToCode("CHN"), "000010000");
Assertions.assertEquals(StrUtil.countryToCode("TWN"), "000001000");
Assertions.assertEquals(StrUtil.countryToCode("HKG"), "000000100");
Assertions.assertEquals(StrUtil.countryToCode("MAC"), "000000010");
Assertions.assertEquals(StrUtil.countryToCode("999"), "000000001");
Assertions.assertEquals(StrUtil.countryToCode("O"), "000000000");
}
@Test
void getCountryByCode() {
Assertions.assertEquals(StrUtil.getCountryByCode("100000000"), "JPN");
Assertions.assertEquals(StrUtil.getCountryByCode("010000000"), "KOR");
Assertions.assertEquals(StrUtil.getCountryByCode("001000000"), "THA");
Assertions.assertEquals(StrUtil.getCountryByCode("000100000"), "SGP");
Assertions.assertEquals(StrUtil.getCountryByCode("000010000"), "CHN");
Assertions.assertEquals(StrUtil.getCountryByCode("000001000"), "TWN");
Assertions.assertEquals(StrUtil.getCountryByCode("000000100"), "HKG");
Assertions.assertEquals(StrUtil.getCountryByCode("000000010"), "MAC");
Assertions.assertEquals(StrUtil.getCountryByCode("000000001"), "999");
Assertions.assertEquals(StrUtil.getCountryByCode("000000000"), "O");
Assertions.assertEquals(StrUtil.getCountryByCode("100000000000"), "O");
Assertions.assertEquals(StrUtil.getCountryByCode("-100000000000"), "O");
}
@Test
void countryToCodeByte() {
System.out.println(String.format("%9s", Integer.toBinaryString(StrUtil.countryToCodeByte("CHN") & 0xFF)).replace(" ", "0"));
}
}

View File

@ -2,14 +2,16 @@ package com.yexuejc.base.util.bean;
import java.io.Serializable;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.yexuejc.base.annotation.CsvToBean;
import com.yexuejc.base.converter.IntegerNullValueDeserializer;
/**
*
* @author: yexuejc
* @date: 2024/2/27 10:40
*/
@CsvToBean(header = "enable,domain,protocol,deployHost,deployPath,uname,pwd,appnodeId", hasHeader = true)
@CsvToBean(header = "enable,domain,protocol,deployHost,deployPath,uname,pwd,appnodeId")
public class AppnodeCertCsvBean implements Serializable {
/**是否生效Y/N*/
private String enable;
@ -37,6 +39,7 @@ public class AppnodeCertCsvBean implements Serializable {
/**
* appnode协议时且远程部署时对应的远程appnode的ApiNodeId
*/
@JsonDeserialize(using = IntegerNullValueDeserializer.class)
private Integer appnodeId;
public String getEnable() {

View File

@ -0,0 +1,10 @@
是否生效,域名,部署协议,部署服务器,部署证书位置,服务器账号,服务器密码,appnodeId
N,kasm.mx.yexuejc.top,appnode,http://192.168.56.101:8888,,admin,admin,1
N,kasm.mx.yexuejc.top,appnode,local,,,,
N,shop.mx.yexuejc.top,appnode,local,,,,
Y,cloud.yexuejc.top,ssh,118.126.109.109:10371,/home/frpuser/http/cert/,frpuser,yexuejc1,
Y,blog.yexuejc.top,ssh,118.126.109.109:10371,/home/frpuser/http/cert/,frpuser,yexuejc1,
Y,yexuejc.top,ssh,118.126.109.109:10371,/home/frpuser/http/cert/,frpuser,yexuejc1,
Y,jenkins.yexuejc.top,ssh,118.126.109.109:10371,/home/frpuser/http/cert/,frpuser,yexuejc1,
Y,git.yexuejc.top,ssh,118.126.109.109:10371,/home/frpuser/http/cert/,frpuser,yexuejc1,
Y,nexus.yexuejc.top,ssh,118.126.109.109:10371,/home/frpuser/http/cert/,frpuser,yexuejc1,
1 是否生效 域名 部署协议 部署服务器 部署证书位置 服务器账号 服务器密码 appnodeId
2 N kasm.mx.yexuejc.top appnode http://192.168.56.101:8888 admin admin 1
3 N kasm.mx.yexuejc.top appnode local
4 N shop.mx.yexuejc.top appnode local
5 Y cloud.yexuejc.top ssh 118.126.109.109:10371 /home/frpuser/http/cert/ frpuser yexuejc1
6 Y blog.yexuejc.top ssh 118.126.109.109:10371 /home/frpuser/http/cert/ frpuser yexuejc1
7 Y yexuejc.top ssh 118.126.109.109:10371 /home/frpuser/http/cert/ frpuser yexuejc1
8 Y jenkins.yexuejc.top ssh 118.126.109.109:10371 /home/frpuser/http/cert/ frpuser yexuejc1
9 Y git.yexuejc.top ssh 118.126.109.109:10371 /home/frpuser/http/cert/ frpuser yexuejc1
10 Y nexus.yexuejc.top ssh 118.126.109.109:10371 /home/frpuser/http/cert/ frpuser yexuejc1