mirror of
https://gitee.com/dromara/hutool.git
synced 2025-11-24 08:33:22 +08:00
Compare commits
4 Commits
b0c375f2d2
...
0668337338
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0668337338 | ||
|
|
b4848a6d99 | ||
|
|
ec5c10e233 | ||
|
|
a4d1c963f6 |
@@ -49,11 +49,20 @@ public class JdkServiceLoaderUtil {
|
||||
*/
|
||||
public static <T> T loadFirstAvailable(final Class<T> clazz) {
|
||||
final Iterator<T> iterator = load(clazz).iterator();
|
||||
while (iterator.hasNext()) {
|
||||
while (true) {
|
||||
final T instance;
|
||||
try {
|
||||
return iterator.next();
|
||||
} catch (final ServiceConfigurationError ignore) {
|
||||
// ignore
|
||||
// 注意:JDK 24+ 下 hasNext() 和 next() 均可能触发 NoClassDefFoundError
|
||||
if (!iterator.hasNext()) {
|
||||
break;
|
||||
}
|
||||
instance = iterator.next();
|
||||
} catch (ServiceConfigurationError | NoClassDefFoundError e) {
|
||||
// 安全忽略当前实现,尝试下一个
|
||||
continue;
|
||||
}
|
||||
if (instance != null) {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -29,16 +29,20 @@ public class CronConfig {
|
||||
/**
|
||||
* 时区
|
||||
*/
|
||||
protected TimeZone timezone = TimeZone.getDefault();
|
||||
private TimeZone timezone = TimeZone.getDefault();
|
||||
/**
|
||||
* 是否支持秒匹配
|
||||
*/
|
||||
protected boolean matchSecond;
|
||||
private boolean matchSecond;
|
||||
/**
|
||||
* 是否为守护线程
|
||||
*/
|
||||
private boolean daemon;
|
||||
|
||||
/**
|
||||
* 构造
|
||||
*/
|
||||
public CronConfig(){
|
||||
public CronConfig() {
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -80,4 +84,24 @@ public class CronConfig {
|
||||
this.matchSecond = isMatchSecond;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为守护线程
|
||||
*
|
||||
* @return {@code true}守护线程,{@code false}非守护线程
|
||||
*/
|
||||
public boolean isDaemon() {
|
||||
return this.daemon;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否为守护线程
|
||||
*
|
||||
* @param daemon {@code true}守护线程,{@code false}非守护线程
|
||||
* @return this
|
||||
*/
|
||||
public CronConfig setDaemon(final boolean daemon) {
|
||||
this.daemon = daemon;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,33 +54,39 @@ public class CronTimer extends Thread implements Serializable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
final long timerUnit = this.scheduler.config.matchSecond ? TIMER_UNIT_SECOND : TIMER_UNIT_MINUTE;
|
||||
final long timerUnit = this.scheduler.config.isMatchSecond() ? TIMER_UNIT_SECOND : TIMER_UNIT_MINUTE;
|
||||
final long doubleTimeUnit = 2 * timerUnit;
|
||||
|
||||
long thisTime = System.currentTimeMillis();
|
||||
long nextTime;
|
||||
long sleep;
|
||||
while(!isStop){
|
||||
spawnLauncher(thisTime);
|
||||
|
||||
//下一时间计算是按照上一个执行点开始时间计算的
|
||||
//此处除以定时单位是为了清零单位以下部分,例如单位是分则秒和毫秒清零
|
||||
nextTime = ((thisTime / timerUnit) + 1) * timerUnit;
|
||||
sleep = nextTime - System.currentTimeMillis();
|
||||
if(isValidSleepMillis(sleep, timerUnit)){
|
||||
if (!ThreadUtil.safeSleep(sleep)) {
|
||||
//等待直到下一个时间点,如果被中断直接退出Timer
|
||||
break;
|
||||
}
|
||||
|
||||
//执行点,时间记录为执行开始的时间,而非结束时间
|
||||
spawnLauncher(nextTime);
|
||||
|
||||
// issue#3460 采用叠加方式,确保正好是1分钟或1秒,避免sleep晚醒问题
|
||||
// 此处无需校验,因为每次循环都是sleep与上触发点的时间差。
|
||||
// 当上一次晚醒后,本次会减少sleep时间,保证误差在一个unit内,并不断修正。
|
||||
thisTime = nextTime;
|
||||
} else{
|
||||
// 非正常时间重新计算(issue#1224@Github)
|
||||
long nextTime = ((thisTime / timerUnit) + 1) * timerUnit;
|
||||
final long sleep = nextTime - System.currentTimeMillis();
|
||||
if(sleep < 0){
|
||||
// 可能循环执行慢导致时间点跟不上系统时间,追赶系统时间并执行中间差异的时间点(issue#IB49EF@Gitee)
|
||||
thisTime = System.currentTimeMillis();
|
||||
while(nextTime <= thisTime){
|
||||
// 追赶系统时间并运行执行点
|
||||
spawnLauncher(nextTime);
|
||||
nextTime = ((thisTime / timerUnit) + 1) * timerUnit;
|
||||
}
|
||||
continue;
|
||||
} else if(sleep > doubleTimeUnit){
|
||||
// 时间回退,可能用户回拨了时间或自动校准了时间,重新计算(issue#1224@Github)
|
||||
thisTime = System.currentTimeMillis();
|
||||
continue;
|
||||
} else if (!ThreadUtil.safeSleep(sleep)) {
|
||||
//等待直到下一个时间点,如果被用户中断直接退出Timer
|
||||
break;
|
||||
}
|
||||
|
||||
// issue#3460 采用叠加方式,确保正好是1分钟或1秒,避免sleep晚醒问题
|
||||
// 此处无需校验,因为每次循环都是sleep与上触发点的时间差。
|
||||
// 当上一次晚醒后,本次会减少sleep时间,保证误差在一个unit内,并不断修正。
|
||||
thisTime = nextTime;
|
||||
}
|
||||
log.debug("Hutool-cron timer stopped.");
|
||||
}
|
||||
|
||||
@@ -16,12 +16,12 @@
|
||||
|
||||
package cn.hutool.v7.cron;
|
||||
|
||||
import cn.hutool.v7.core.data.id.IdUtil;
|
||||
import cn.hutool.v7.core.map.MapUtil;
|
||||
import cn.hutool.v7.core.text.CharUtil;
|
||||
import cn.hutool.v7.core.text.StrUtil;
|
||||
import cn.hutool.v7.core.thread.ExecutorBuilder;
|
||||
import cn.hutool.v7.core.thread.ThreadFactoryBuilder;
|
||||
import cn.hutool.v7.core.text.CharUtil;
|
||||
import cn.hutool.v7.core.data.id.IdUtil;
|
||||
import cn.hutool.v7.core.text.StrUtil;
|
||||
import cn.hutool.v7.cron.listener.TaskListener;
|
||||
import cn.hutool.v7.cron.listener.TaskListenerManager;
|
||||
import cn.hutool.v7.cron.pattern.CronPattern;
|
||||
@@ -35,30 +35,29 @@ import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
|
||||
/**
|
||||
* 任务调度器<br>
|
||||
*
|
||||
* <p>
|
||||
* 调度器启动流程:<br>
|
||||
*
|
||||
* <pre>
|
||||
* 启动Timer =》 启动TaskLauncher =》 启动TaskExecutor
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* 调度器关闭流程:<br>
|
||||
*
|
||||
* <pre>
|
||||
* 关闭Timer =》 关闭所有运行中的TaskLauncher =》 关闭所有运行中的TaskExecutor
|
||||
* </pre>
|
||||
*
|
||||
* <p>
|
||||
* 其中:
|
||||
*
|
||||
* <pre>
|
||||
* <strong>TaskLauncher</strong>:定时器每分钟调用一次(如果{@link Scheduler#isMatchSecond()}为{@code true}每秒调用一次),
|
||||
* <strong>TaskLauncher</strong>:定时器每分钟调用一次(如果{@link CronConfig#isMatchSecond()}为{@code true}每秒调用一次),
|
||||
* 负责检查<strong>TaskTable</strong>是否有匹配到此时间运行的Task
|
||||
* </pre>
|
||||
*
|
||||
@@ -73,65 +72,58 @@ public class Scheduler implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Lock lock = new ReentrantLock();
|
||||
/**
|
||||
* 定时任务锁,用于同步添加和删除操作
|
||||
*/
|
||||
private final Lock lock;
|
||||
/**
|
||||
* 定时任务配置
|
||||
*/
|
||||
protected final CronConfig config;
|
||||
|
||||
/** 定时任务配置 */
|
||||
protected CronConfig config = new CronConfig();
|
||||
/** 是否已经启动 */
|
||||
private boolean started = false;
|
||||
/** 是否为守护线程 */
|
||||
protected boolean daemon;
|
||||
|
||||
/** 定时器 */
|
||||
/**
|
||||
* 是否已经启动
|
||||
*/
|
||||
private boolean started;
|
||||
/**
|
||||
* 定时器
|
||||
*/
|
||||
private CronTimer timer;
|
||||
/** 定时任务表 */
|
||||
protected TaskTable taskTable = new TaskTable();
|
||||
/** 线程池,用于执行TaskLauncher和TaskExecutor */
|
||||
/**
|
||||
* 定时任务表
|
||||
*/
|
||||
protected TaskTable taskTable;
|
||||
/**
|
||||
* 线程池,用于执行TaskLauncher和TaskExecutor
|
||||
*/
|
||||
protected ExecutorService threadExecutor;
|
||||
/** 任务管理器 */
|
||||
/**
|
||||
* 任务管理器
|
||||
*/
|
||||
protected TaskManager taskManager;
|
||||
/** 监听管理器列表 */
|
||||
protected TaskListenerManager listenerManager = new TaskListenerManager();
|
||||
|
||||
// --------------------------------------------------------- Getters and Setters start
|
||||
/**
|
||||
* 设置时区
|
||||
*
|
||||
* @param timeZone 时区
|
||||
* @return this
|
||||
* 监听管理器列表
|
||||
*/
|
||||
public Scheduler setTimeZone(final TimeZone timeZone) {
|
||||
this.config.setTimeZone(timeZone);
|
||||
return this;
|
||||
protected TaskListenerManager listenerManager;
|
||||
|
||||
/**
|
||||
* 构造<br>
|
||||
* 使用默认配置
|
||||
*/
|
||||
public Scheduler() {
|
||||
this(new CronConfig());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得时区,默认为 {@link TimeZone#getDefault()}
|
||||
* 构造
|
||||
*
|
||||
* @return 时区
|
||||
* @param config 定时任务配置
|
||||
*/
|
||||
public TimeZone getTimeZone() {
|
||||
return this.config.getTimeZone();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否为守护线程<br>
|
||||
* 如果为true,则在调用{@link #stop()}方法后执行的定时任务立即结束,否则等待执行完毕才结束。默认非守护线程<br>
|
||||
* 如果用户调用{@link #setThreadExecutor(ExecutorService)}自定义线程池则此参数无效
|
||||
*
|
||||
* @param on {@code true}为守护线程,否则非守护线程
|
||||
* @return this
|
||||
* @throws CronException 定时任务已经启动抛出此异常
|
||||
*/
|
||||
public Scheduler setDaemon(final boolean on) throws CronException {
|
||||
lock.lock();
|
||||
try {
|
||||
checkStarted();
|
||||
this.daemon = on;
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
return this;
|
||||
public Scheduler(final CronConfig config) {
|
||||
this.config = config;
|
||||
lock = new ReentrantLock();
|
||||
this.taskTable = new TaskTable();
|
||||
this.listenerManager = new TaskListenerManager();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,24 +146,6 @@ public class Scheduler implements Serializable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为守护线程
|
||||
*
|
||||
* @return 是否为守护线程
|
||||
*/
|
||||
public boolean isDaemon() {
|
||||
return this.daemon;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否支持秒匹配
|
||||
*
|
||||
* @return {@code true}使用,{@code false}不使用
|
||||
*/
|
||||
public boolean isMatchSecond() {
|
||||
return this.config.isMatchSecond();
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置是否支持秒匹配,默认不使用
|
||||
*
|
||||
@@ -179,7 +153,13 @@ public class Scheduler implements Serializable {
|
||||
* @return this
|
||||
*/
|
||||
public Scheduler setMatchSecond(final boolean isMatchSecond) {
|
||||
this.config.setMatchSecond(isMatchSecond);
|
||||
lock.lock();
|
||||
try {
|
||||
checkStarted();
|
||||
this.config.setMatchSecond(isMatchSecond);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -190,7 +170,12 @@ public class Scheduler implements Serializable {
|
||||
* @return this
|
||||
*/
|
||||
public Scheduler addListener(final TaskListener listener) {
|
||||
this.listenerManager.addListener(listener);
|
||||
lock.lock();
|
||||
try {
|
||||
this.listenerManager.addListener(listener);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -201,12 +186,17 @@ public class Scheduler implements Serializable {
|
||||
* @return this
|
||||
*/
|
||||
public Scheduler removeListener(final TaskListener listener) {
|
||||
this.listenerManager.removeListener(listener);
|
||||
lock.lock();
|
||||
try {
|
||||
this.listenerManager.removeListener(listener);
|
||||
} finally {
|
||||
lock.unlock();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
// --------------------------------------------------------- Getters and Setters end
|
||||
|
||||
// region ----- schedule
|
||||
|
||||
/**
|
||||
* 批量加入配置文件中的定时任务<br>
|
||||
* 配置文件格式为: xxx.xxx.xxx.Class.method = * * * * *
|
||||
@@ -242,7 +232,7 @@ public class Scheduler implements Serializable {
|
||||
* 新增Task,使用随机UUID
|
||||
*
|
||||
* @param pattern {@link CronPattern}对应的String表达式
|
||||
* @param task {@link Task}
|
||||
* @param task {@link Task}
|
||||
* @return ID
|
||||
*/
|
||||
public String schedule(final String pattern, final Task task) {
|
||||
@@ -254,9 +244,9 @@ public class Scheduler implements Serializable {
|
||||
/**
|
||||
* 新增Task,如果任务ID已经存在,抛出异常
|
||||
*
|
||||
* @param id ID,为每一个Task定义一个ID
|
||||
* @param id ID,为每一个Task定义一个ID
|
||||
* @param pattern {@link CronPattern}对应的String表达式
|
||||
* @param task {@link Runnable}
|
||||
* @param task {@link Runnable}
|
||||
* @return this
|
||||
*/
|
||||
public Scheduler schedule(final String id, final String pattern, final Runnable task) {
|
||||
@@ -266,9 +256,9 @@ public class Scheduler implements Serializable {
|
||||
/**
|
||||
* 新增Task,如果任务ID已经存在,抛出异常
|
||||
*
|
||||
* @param id ID,为每一个Task定义一个ID
|
||||
* @param id ID,为每一个Task定义一个ID
|
||||
* @param pattern {@link CronPattern}对应的String表达式
|
||||
* @param task {@link Task}
|
||||
* @param task {@link Task}
|
||||
* @return this
|
||||
*/
|
||||
public Scheduler schedule(final String id, final String pattern, final Task task) {
|
||||
@@ -278,9 +268,9 @@ public class Scheduler implements Serializable {
|
||||
/**
|
||||
* 新增Task,如果任务ID已经存在,抛出异常
|
||||
*
|
||||
* @param id ID,为每一个Task定义一个ID
|
||||
* @param id ID,为每一个Task定义一个ID
|
||||
* @param pattern {@link CronPattern}
|
||||
* @param task {@link Task}
|
||||
* @param task {@link Task}
|
||||
* @return this
|
||||
*/
|
||||
public Scheduler schedule(final String id, final CronPattern pattern, final Task task) {
|
||||
@@ -314,7 +304,7 @@ public class Scheduler implements Serializable {
|
||||
/**
|
||||
* 更新Task执行的时间规则
|
||||
*
|
||||
* @param id Task的ID
|
||||
* @param id Task的ID
|
||||
* @param pattern {@link CronPattern}
|
||||
* @return this
|
||||
* @since 4.0.10
|
||||
@@ -378,6 +368,7 @@ public class Scheduler implements Serializable {
|
||||
|
||||
/**
|
||||
* 清空任务表
|
||||
*
|
||||
* @return this
|
||||
* @since 4.1.17
|
||||
*/
|
||||
@@ -400,7 +391,7 @@ public class Scheduler implements Serializable {
|
||||
* @return this
|
||||
*/
|
||||
public Scheduler start(final boolean isDaemon) {
|
||||
this.daemon = isDaemon;
|
||||
this.config.setDaemon(isDaemon);
|
||||
return start();
|
||||
}
|
||||
|
||||
@@ -410,21 +401,23 @@ public class Scheduler implements Serializable {
|
||||
* @return this
|
||||
*/
|
||||
public Scheduler start() {
|
||||
final boolean daemon = this.config.isDaemon();
|
||||
|
||||
lock.lock();
|
||||
try {
|
||||
checkStarted();
|
||||
|
||||
if(null == this.threadExecutor){
|
||||
if (null == this.threadExecutor) {
|
||||
// 无界线程池,确保每一个需要执行的线程都可以及时运行,同时复用已有线程避免线程重复创建
|
||||
this.threadExecutor = ExecutorBuilder.of().useSynchronousQueue().setThreadFactory(//
|
||||
ThreadFactoryBuilder.of().setNamePrefix("hutool-cron-").setDaemon(this.daemon).build()//
|
||||
ThreadFactoryBuilder.of().setNamePrefix("hutool-cron-").setDaemon(daemon).build()//
|
||||
).build();
|
||||
}
|
||||
this.taskManager = new TaskManager(this);
|
||||
|
||||
// Start CronTimer
|
||||
timer = new CronTimer(this);
|
||||
timer.setDaemon(this.daemon);
|
||||
timer.setDaemon(daemon);
|
||||
timer.start();
|
||||
this.started = true;
|
||||
} finally {
|
||||
@@ -468,7 +461,7 @@ public class Scheduler implements Serializable {
|
||||
this.threadExecutor = null;
|
||||
|
||||
//可选是否清空任务表
|
||||
if(clearTasks) {
|
||||
if (clearTasks) {
|
||||
clear();
|
||||
}
|
||||
|
||||
@@ -485,7 +478,7 @@ public class Scheduler implements Serializable {
|
||||
*
|
||||
* @throws CronException 已经启动则抛出此异常
|
||||
*/
|
||||
private void checkStarted() throws CronException{
|
||||
private void checkStarted() throws CronException {
|
||||
if (this.started) {
|
||||
throw new CronException("Scheduler already started!");
|
||||
}
|
||||
|
||||
@@ -45,8 +45,14 @@ public class TaskTable implements Serializable {
|
||||
*/
|
||||
public static final int DEFAULT_CAPACITY = 10;
|
||||
|
||||
/**
|
||||
* 读写锁,保证线程安全
|
||||
*/
|
||||
private final ReadWriteLock lock;
|
||||
|
||||
/**
|
||||
* 任务表,ID、表达式、任务一一对应。使用TripleTable存储,便于快速查找和更新<br>
|
||||
*/
|
||||
private final TripleTable<String, CronPattern, Task> table;
|
||||
|
||||
/**
|
||||
@@ -273,7 +279,7 @@ public class TaskTable implements Serializable {
|
||||
* 如果时间匹配则执行相应的Task,带读锁
|
||||
*
|
||||
* @param scheduler {@link Scheduler}
|
||||
* @param millis 时间毫秒
|
||||
* @param millis 时间毫秒
|
||||
*/
|
||||
public void executeTaskIfMatch(final Scheduler scheduler, final long millis) {
|
||||
final Lock readLock = lock.readLock();
|
||||
@@ -291,7 +297,7 @@ public class TaskTable implements Serializable {
|
||||
final StringBuilder builder = StrUtil.builder();
|
||||
for (int i = 0; i < size; i++) {
|
||||
builder.append(StrUtil.format("[{}] [{}] [{}]\n",
|
||||
this.table.getLeft(i), this.table.getMiddle(i), this.table.getRight(i)));
|
||||
this.table.getLeft(i), this.table.getMiddle(i), this.table.getRight(i)));
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
@@ -300,13 +306,13 @@ public class TaskTable implements Serializable {
|
||||
* 如果时间匹配则执行相应的Task,无锁
|
||||
*
|
||||
* @param scheduler {@link Scheduler}
|
||||
* @param millis 时间毫秒
|
||||
* @param millis 时间毫秒
|
||||
* @since 3.1.1
|
||||
*/
|
||||
private void executeTaskIfMatchInternal(final Scheduler scheduler, final long millis) {
|
||||
final int size = size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (this.table.getMiddle(i).match(scheduler.config.timezone, millis, scheduler.config.matchSecond)) {
|
||||
if (this.table.getMiddle(i).match(scheduler.config.getTimeZone(), millis, scheduler.config.isMatchSecond())) {
|
||||
scheduler.taskManager.spawnExecutor(
|
||||
new CronTask(this.table.getLeft(i), this.table.getMiddle(i), this.table.getRight(i)));
|
||||
}
|
||||
|
||||
@@ -18,8 +18,6 @@ package cn.hutool.v7.cron.pattern;
|
||||
|
||||
import cn.hutool.v7.core.comparator.CompareUtil;
|
||||
import cn.hutool.v7.core.date.CalendarUtil;
|
||||
import cn.hutool.v7.core.date.DateUtil;
|
||||
import cn.hutool.v7.core.lang.Console;
|
||||
import cn.hutool.v7.cron.pattern.matcher.PatternMatcher;
|
||||
import cn.hutool.v7.cron.pattern.parser.PatternParser;
|
||||
|
||||
|
||||
@@ -47,8 +47,7 @@ public class CronTest {
|
||||
public void cronTest() {
|
||||
// 支持秒级别定时任务
|
||||
CronUtil.setMatchSecond(true);
|
||||
CronUtil.getScheduler().setDaemon(false);
|
||||
CronUtil.start();
|
||||
CronUtil.start(false);
|
||||
|
||||
ThreadUtil.waitForDie();
|
||||
CronUtil.stop();
|
||||
|
||||
@@ -23,6 +23,7 @@ import cn.hutool.v7.core.lang.Assert;
|
||||
import cn.hutool.v7.core.text.CharUtil;
|
||||
import cn.hutool.v7.core.text.StrUtil;
|
||||
import cn.hutool.v7.core.util.RandomUtil;
|
||||
import cn.hutool.v7.core.xml.XmlUtil;
|
||||
import cn.hutool.v7.crypto.asymmetric.AsymmetricAlgorithm;
|
||||
import cn.hutool.v7.crypto.bc.ECKeyUtil;
|
||||
import cn.hutool.v7.crypto.bc.SM2Constant;
|
||||
@@ -234,6 +235,24 @@ public class KeyUtil {
|
||||
// endregion
|
||||
|
||||
// region ----- generatePrivateKey
|
||||
/**
|
||||
* 生成RSA私钥,仅用于非对称加密
|
||||
* 算法见:<a href="https://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#KeyFactory">...</a>
|
||||
*
|
||||
* @param key 密钥,支持XML和Base64两种格式,XML为C#生成格式,见{@link SpecUtil#xmlToRSAPrivateCrtKeySpec(String)}
|
||||
* @return RSA私钥 {@link PrivateKey}
|
||||
* @since 7.0.0
|
||||
*/
|
||||
public static PrivateKey generateRSAPrivateKey(String key) {
|
||||
Assert.notBlank(key, "Key is blank!");
|
||||
key = StrUtil.trim(key);
|
||||
if(StrUtil.startWith(key, XmlUtil.C_LT)){
|
||||
return generateRSAPrivateKey(SpecUtil.xmlToRSAPrivateCrtKeySpec(key));
|
||||
}
|
||||
|
||||
return generatePrivateKey(AsymmetricAlgorithm.RSA.getValue(), Base64.decode(key));
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成RSA私钥,仅用于非对称加密<br>
|
||||
* 采用PKCS#8规范,此规范定义了私钥信息语法和加密私钥语法<br>
|
||||
@@ -247,6 +266,18 @@ public class KeyUtil {
|
||||
return generatePrivateKey(AsymmetricAlgorithm.RSA.getValue(), key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成RSA私钥,仅用于非对称加密<br>
|
||||
* 算法见:<a href="https://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#KeyFactory">...</a>
|
||||
*
|
||||
* @param keySpec {@link KeySpec}
|
||||
* @return RSA私钥 {@link PrivateKey}
|
||||
* @since 5.8.41
|
||||
*/
|
||||
public static PrivateKey generateRSAPrivateKey(final KeySpec keySpec) {
|
||||
return generatePrivateKey(AsymmetricAlgorithm.RSA.getValue(), keySpec);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成私钥,仅用于非对称加密<br>
|
||||
* 采用PKCS#8规范,此规范定义了私钥信息语法和加密私钥语法<br>
|
||||
@@ -362,8 +393,7 @@ public class KeyUtil {
|
||||
* @since 5.3.6
|
||||
*/
|
||||
public static PublicKey getRSAPublicKey(final PrivateKey privateKey) {
|
||||
if (privateKey instanceof RSAPrivateCrtKey) {
|
||||
final RSAPrivateCrtKey privk = (RSAPrivateCrtKey) privateKey;
|
||||
if (privateKey instanceof final RSAPrivateCrtKey privk) {
|
||||
return getRSAPublicKey(privk.getModulus(), privk.getPublicExponent());
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -16,11 +16,16 @@
|
||||
|
||||
package cn.hutool.v7.crypto;
|
||||
|
||||
import cn.hutool.v7.core.codec.binary.Base64;
|
||||
import cn.hutool.v7.core.util.RandomUtil;
|
||||
import cn.hutool.v7.core.xml.XmlUtil;
|
||||
import org.w3c.dom.Element;
|
||||
|
||||
import javax.crypto.spec.*;
|
||||
import java.math.BigInteger;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.spec.KeySpec;
|
||||
import java.security.spec.RSAPrivateCrtKeySpec;
|
||||
|
||||
/**
|
||||
* 规范相关工具类,用于生成密钥规范、参数规范等快捷方法。
|
||||
@@ -91,4 +96,59 @@ public class SpecUtil {
|
||||
public static PBEParameterSpec createPBEParameterSpec(final byte[] salt, final int iterationCount) {
|
||||
return new PBEParameterSpec(salt, iterationCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将XML格式的密钥参数转化为{@link RSAPrivateCrtKeySpec},XML为C#生成格式,类似于:
|
||||
* <pre>{@code
|
||||
* <RSAKeyValue>
|
||||
* <Modulus>xx</Modulus>
|
||||
* <Exponent>xx</Exponent>
|
||||
* <P>xxxxxxxxx</P>
|
||||
* <Q>xxxxxxxxx</Q>
|
||||
* <DP>xxxxxxxx</DP>
|
||||
* <DQ>xxxxxxxx</DQ>
|
||||
* <InverseQ>xx</InverseQ>
|
||||
* <D>xxxxxxxxx</D>
|
||||
* </RSAKeyValue>
|
||||
* }</pre>
|
||||
*
|
||||
* @param xml xml格式密钥字符串
|
||||
* @return {@link RSAPrivateCrtKeySpec}
|
||||
*/
|
||||
public static RSAPrivateCrtKeySpec xmlToRSAPrivateCrtKeySpec(final String xml) {
|
||||
// 1. 解析XML
|
||||
final Element rootElement = XmlUtil.getRootElement(XmlUtil.parseXml(xml));
|
||||
|
||||
// 2. 提取各个字段
|
||||
final String modulusB64 = XmlUtil.elementText(rootElement, "Modulus");
|
||||
final String exponentB64 = XmlUtil.elementText(rootElement, "Exponent");
|
||||
final String pB64 = XmlUtil.elementText(rootElement, "P");
|
||||
final String qB64 = XmlUtil.elementText(rootElement, "Q");
|
||||
final String dpB64 = XmlUtil.elementText(rootElement, "DP");
|
||||
final String dqB64 = XmlUtil.elementText(rootElement, "DQ");
|
||||
final String inverseQB64 = XmlUtil.elementText(rootElement, "InverseQ");
|
||||
final String dB64 = XmlUtil.elementText(rootElement, "D");
|
||||
|
||||
// 3. Base64解码
|
||||
final byte[] modulus = Base64.decode(modulusB64);
|
||||
final byte[] publicExponent = Base64.decode(exponentB64);
|
||||
final byte[] privateExponent = Base64.decode(dB64);
|
||||
final byte[] primeP = Base64.decode(pB64);
|
||||
final byte[] primeQ = Base64.decode(qB64);
|
||||
final byte[] primeExponentP = Base64.decode(dpB64);
|
||||
final byte[] primeExponentQ = Base64.decode(dqB64);
|
||||
final byte[] crtCoefficient = Base64.decode(inverseQB64);
|
||||
|
||||
// 4. 创建RSAPrivateCrtKeySpec
|
||||
return new RSAPrivateCrtKeySpec(
|
||||
new BigInteger(1, modulus),
|
||||
new BigInteger(1, publicExponent),
|
||||
new BigInteger(1, privateExponent),
|
||||
new BigInteger(1, primeP),
|
||||
new BigInteger(1, primeQ),
|
||||
new BigInteger(1, primeExponentP),
|
||||
new BigInteger(1, primeExponentQ),
|
||||
new BigInteger(1, crtCoefficient)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package cn.hutool.v7.crypto.asymmetric;
|
||||
|
||||
import cn.hutool.v7.core.codec.binary.Base64;
|
||||
import cn.hutool.v7.crypto.KeyUtil;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.security.PrivateKey;
|
||||
|
||||
public class IssueID1EIKTest {
|
||||
@Test
|
||||
void rsaTest(){
|
||||
// 1. Base64解码
|
||||
final String keyXmlBase64Str = "PFJTQUtleVZhbHVlPjxNb2R1bHVzPnVscHlkSXJydHJUMzJBSnFDV0FFMHQxNXdHYjBKUTJqSnpBUW1FakpRRzhkcnUr" +
|
||||
"dDhyQUtzekVoNXRRL2x4eTdnMFVMR3dzWjNmekQrdm12d2lKWkx5d1dncmszMDdRbFpXSkU3dWIxM2ZtN2pUa0RLOXM0L294alNabm5JTHc" +
|
||||
"rc0lwVGFoLzdlL2hLNkxEN0VFbzNuTHZZK0VjTzdHa21IYXVCUW5CZmhPaz08L01vZHVsdXM+PEV4cG9uZW50PkFRQUI8L0V4cG9uZW50Pj" +
|
||||
"xQPjZlSFdVYUZNdWRTV0svODJPeWxxNHZ2Y0FDbmNHUHYvN1VKWVVETnY1elBZVGE5UFNXUTRzNUk3RHBDTWJYcExLK0VldE5mOUFCZ1ZwV" +
|
||||
"jZERTJlMTR3PT08L1A+PFE+eS9uMkc3d2FYZVlGUnZXWjNROW96NVkyVEpHdUdaSXIzeis3QlVGOWZIckp1Nk9SU2V0YUVkdW5tcjgzSFVN" +
|
||||
"N3E4TGIvWGxtdmVpS0p0OWh2NWx6d3c9PTwvUT48RFA+Sy9IdExTVmJuMGNjZUdQWnNzQVRmMWJIZlpoZjdLbmM2cDJlcm1NYjBadGlOeWF" +
|
||||
"MaFVTNWlyUWRPSjFjWlcybkZqV1VhWEp6N1VLWlBwdEZrYTNZOVE9PTwvRFA+PERRPkJSbm9QTU5VaVhxaU1TY2RSUGtJcndCYnRVaURhU0" +
|
||||
"pOdEpTY2NjSTBpRE50N2lKbUZNb3RBM3RSMHIzcmUvRGRnaXNxWTBsdzkxamtjNXBza0dVZkR3PT08L0RRPjxJbnZlcnNlUT5rVGpLTzBpc" +
|
||||
"XU4M3pTZGpqbWNoT2lYQ0k0bm5veTg5c0JiOFFqMk92TXpnRnhOazhVV1hoT29ZdGVnUDNiVUFhZEJBT3VGSnRCcE1RMmdCemo2ekRWZz09" +
|
||||
"PC9JbnZlcnNlUT48RD5COUhQeDdBa24vQU1EbFpibUxVY3ZyUm9iWGhrZWtHT1BSQzVRWXFjVjBYU1d3clhvNzFiVlpXVU5KbG5hYkhjOUc" +
|
||||
"4clBpRkRIcHVDcGI5Z2JxYitVdmdKRXFrd0t5cU5HSmdnSm9yS1Irb2doWFh3czRuZVVTV1lENnpqbGQvN2U0QlNRM05ScTJGbEFPSEZnRn" +
|
||||
"p3aElhazZwY1pOT2pwazlTUWdSY2ZaSGs9PC9EPjwvUlNBS2V5VmFsdWU+";
|
||||
final String keyXmlStr = Base64.decodeStr(keyXmlBase64Str);
|
||||
|
||||
final PrivateKey privateKey = KeyUtil.generateRSAPrivateKey(keyXmlStr);
|
||||
final RSA rsa = new RSA(privateKey, null);
|
||||
|
||||
final String encryptStr = "tqmp7hGri5WYcZT8bJXJK3SKVlkAx1i1JSpOlOIGB+EAA5OoWS0PtCcWdwLou/qVM28e" +
|
||||
"xXKGpmehYbx0Ez0Co8bLHMMnXU3bxp3PXstF2MvrODJoEz+nEzxQ92ngg2n/96Du1rCbwkletYFRO47HpkcEYSTKBsi6NtC98JhUsYSXG15" +
|
||||
"hCJu/I8vOWDF9sB4FCFF9qScpEOUndhctDvAH/UvxBqvSix8mJdL9pyz6Er3zhhQ//4LnI3dQQM0saTq4rZITliTxalT32DRfz0Vj5hNj/S" +
|
||||
"o54SspX6fbHjRu0jEaMAotebYZ1Tgpw4AHCYy1DIYoVeGSACd4kc+6ka67gI8jXD7H0tIhI2zyTU3MWQWm2tSOCj+WllELlmCn7ssDp37M6" +
|
||||
"hNO9Imzzj32hWQrsvYsCFufAh+KqRQ1zoF1CQVK8wHRf2ppSFjfR9cCcunpqHqeRrJIpzhJ11dvGZ3JokcjOfDrTNKyXXr7+NVkmc9jPvByEGJXcgkJuX1EHyMv";
|
||||
final String decrypt = rsa.decryptStr(encryptStr, KeyType.PrivateKey);
|
||||
|
||||
final String expectedDecodeStr = """
|
||||
cpu=178BFBFF00A50F00\r
|
||||
baseBoard=MP242ML1\r
|
||||
bios=MP242ML1\r
|
||||
mac=00:FF:CB:EF:28:18|00:FF:03:A2:FC:D7|C8:94:02:F8:8A:83\r
|
||||
cusname=123\r
|
||||
serviceno=12121\r
|
||||
kcliccount=1\r
|
||||
cjliccount=1\r
|
||||
venprintliccount=1\r
|
||||
beginTime=2025-10-11 14:05:10\r
|
||||
endTime=2026-10-11 14:05:10\r
|
||||
lictype=租赁\r
|
||||
serviceendtime=1\r
|
||||
validate=1\r
|
||||
validateunit=年\r
|
||||
""";
|
||||
|
||||
Assertions.assertEquals(expectedDecodeStr, decrypt);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user