diff --git a/minigame-demo-ddd/src/main/java/cc/magicjson/minigame/demo/ddd/infrastructure/config/EventConfig.java b/minigame-demo-ddd/src/main/java/cc/magicjson/minigame/demo/ddd/infrastructure/config/EventConfig.java new file mode 100644 index 0000000000000000000000000000000000000000..b995070a4167a028776d0b5c298f056788bf70c9 --- /dev/null +++ b/minigame-demo-ddd/src/main/java/cc/magicjson/minigame/demo/ddd/infrastructure/config/EventConfig.java @@ -0,0 +1,16 @@ +package cc.magicjson.minigame.demo.ddd.infrastructure.config; + +import cc.magicjson.minigame.demo.ddd.infrastructure.event.AntiCorruptionDomainEventPublisher; +import cc.magicjson.minigame.demo.ddd.infrastructure.event.GameEventListener; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class EventConfig { + private final AntiCorruptionDomainEventPublisher eventPublisher; + private final GameEventListener gameEventListener; + + public EventConfig(AntiCorruptionDomainEventPublisher eventPublisher, GameEventListener gameEventListener) { + this.eventPublisher = eventPublisher; + this.gameEventListener = gameEventListener; + } +} \ No newline at end of file diff --git a/quick-landing/dynamic-priority-executors/pom.xml b/quick-landing/dynamic-priority-executors/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..e8eae5a8d865b02f32f59ef0e86a9565ed9bdf5a --- /dev/null +++ b/quick-landing/dynamic-priority-executors/pom.xml @@ -0,0 +1,31 @@ + + 4.0.0 + + cc.magicjson + quick-landing + ${revision} + ../pom.xml + + + dynamic-priority-executors + jar + + dynamic-priority-executors + + + + + + org.springframework.boot + spring-boot-starter-test + + + + + org.projectlombok + lombok + true + + + diff --git a/quick-landing/dynamic-priority-executors/src/main/java/cc/magicjson/concurrent/priority/core/DynamicPriorityThreadPool.java b/quick-landing/dynamic-priority-executors/src/main/java/cc/magicjson/concurrent/priority/core/DynamicPriorityThreadPool.java new file mode 100644 index 0000000000000000000000000000000000000000..640e7aedaf7e07e5b0dbcf73463672fc242776dd --- /dev/null +++ b/quick-landing/dynamic-priority-executors/src/main/java/cc/magicjson/concurrent/priority/core/DynamicPriorityThreadPool.java @@ -0,0 +1,207 @@ +package cc.magicjson.concurrent.priority.core; + +import cc.magicjson.concurrent.priority.task.*; +import jakarta.validation.constraints.NotNull; + +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 动态优先级线程池,扩展了ThreadPoolExecutor,实现了自动关闭功能。 + * 这个线程池能够根据任务类型动态调整执行优先级,并保证最小数量的线程用于处理轻量级任务。 + */ +public class DynamicPriorityThreadPool extends ThreadPoolExecutor implements AutoCloseable { + + // 追踪当前正在执行轻量级任务的线程数 + private final AtomicInteger lightTaskThreads = new AtomicInteger(0); + // 保证处理轻量级任务的最小线程数 + private final int minLightTaskThreads; + // 用于分类提交的任务 + private final TaskClassifier taskClassifier; + + /** + * 构造函数 + * @param corePoolSize 核心线程数 + * @param maximumPoolSize 最大线程数 + * @param keepAliveTime 空闲线程存活时间 + * @param unit 时间单位 + * @param workQueue 工作队列 + * @param threadFactory 线程工厂 + * @param handler 拒绝策略 + * @param minLightTaskThreads 最小轻量级任务线程数 + * @param taskClassifier 任务分类器 + */ + public DynamicPriorityThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, @NotNull TimeUnit unit, + @NotNull BlockingQueue workQueue, @NotNull ThreadFactory threadFactory, + @NotNull RejectedExecutionHandler handler, int minLightTaskThreads, + @NotNull TaskClassifier taskClassifier) { + super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler); + this.minLightTaskThreads = minLightTaskThreads; + this.taskClassifier = taskClassifier; + } + + /** + * 在任务执行前调用,用于追踪轻量级任务的执行情况 + */ + @Override + protected void beforeExecute(@NotNull Thread t, @NotNull Runnable r) { + super.beforeExecute(t, r); + if (r instanceof PriorityTask task && task.getTaskType().isLightWeight()) { + lightTaskThreads.incrementAndGet(); + } + } + + /** + * 在任务执行后调用,用于更新轻量级任务的计数 + */ + @Override + protected void afterExecute(@NotNull Runnable r, Throwable t) { + super.afterExecute(r, t); + if (r instanceof PriorityTask task && task.getTaskType().isLightWeight()) { + lightTaskThreads.decrementAndGet(); + } + } + + /** + * 执行提交的任务,确保轻量级任务得到优先处理 + */ + @Override + public void execute(Runnable command) { + PriorityTask task; + if (command instanceof PriorityTask) { + task = (PriorityTask) command; + } else { + TaskType taskType = taskClassifier.classifyTask(command); + task = PriorityTask.createRunnableTask(command, taskType); + } + + if (task.getTaskType().isLightWeight() && lightTaskThreads.get() < minLightTaskThreads) { + super.execute(() -> { + lightTaskThreads.incrementAndGet(); + try { + task.run(); + } finally { + lightTaskThreads.decrementAndGet(); + } + }); + } else { + super.execute(task); + } + } + + /** + * 创建新的RunnableFuture任务 + */ + @NotNull + @Override + protected RunnableFuture newTaskFor(@NotNull Runnable runnable, T value) { + if (runnable instanceof PriorityTask) { + @SuppressWarnings("unchecked") + PriorityTask castedTask = (PriorityTask) runnable; + return new PriorityFutureTask<>(castedTask); + } + TaskType taskType = taskClassifier.classifyTask(runnable); + return new PriorityFutureTask<>(PriorityTask.createCallableTask(() -> { + runnable.run(); + return value; + }, taskType)); + } + + /** + * 创建新的RunnableFuture任务(针对Callable) + */ + @NotNull + @Override + protected RunnableFuture newTaskFor(@NotNull Callable callable) { + if (callable instanceof PriorityTask priorityTask) { + @SuppressWarnings("unchecked") + PriorityTask castedTask = (PriorityTask) priorityTask; + return new PriorityFutureTask<>(castedTask); + } + TaskType taskType = taskClassifier.classifyTask(() -> { + try { + callable.call(); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + return new PriorityFutureTask<>(new PriorityTask<>(callable, taskType)); + } + + /** + * 关闭线程池,实现AutoCloseable接口 + */ + @Override + public void close() { + shutdown(); + try { + if (!awaitTermination(60, TimeUnit.SECONDS)) { + shutdownNow(); + if (!awaitTermination(60, TimeUnit.SECONDS)) { + System.err.println("Pool did not terminate"); + } + } + } catch (InterruptedException ie) { + shutdownNow(); + Thread.currentThread().interrupt(); + } + } + + public static class Builder { + private int corePoolSize = Runtime.getRuntime().availableProcessors(); + private int maximumPoolSize = corePoolSize * 2; + private long keepAliveTime = 60L; + private TimeUnit unit = TimeUnit.SECONDS; + private BlockingQueue workQueue = new PriorityBlockingQueue<>(); + private ThreadFactory threadFactory = Executors.defaultThreadFactory(); + private RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy(); + private int minLightTaskThreads = 1; + private TaskClassifier taskClassifier = new DefaultTaskClassifier(); + + public Builder corePoolSize(int corePoolSize) { + this.corePoolSize = corePoolSize; + return this; + } + + public Builder maximumPoolSize(int maximumPoolSize) { + this.maximumPoolSize = maximumPoolSize; + return this; + } + + public Builder keepAliveTime(long keepAliveTime, TimeUnit unit) { + this.keepAliveTime = keepAliveTime; + this.unit = unit; + return this; + } + + public Builder workQueue(BlockingQueue workQueue) { + this.workQueue = workQueue; + return this; + } + + public Builder threadFactory(ThreadFactory threadFactory) { + this.threadFactory = threadFactory; + return this; + } + + public Builder rejectedExecutionHandler(RejectedExecutionHandler handler) { + this.handler = handler; + return this; + } + + public Builder minLightTaskThreads(int minLightTaskThreads) { + this.minLightTaskThreads = minLightTaskThreads; + return this; + } + + public Builder taskClassifier(TaskClassifier taskClassifier) { + this.taskClassifier = taskClassifier; + return this; + } + + public DynamicPriorityThreadPool build() { + return new DynamicPriorityThreadPool(corePoolSize, maximumPoolSize, keepAliveTime, unit, + workQueue, threadFactory, handler, minLightTaskThreads, taskClassifier); + } + } +} diff --git a/quick-landing/dynamic-priority-executors/src/main/java/cc/magicjson/concurrent/priority/task/DefaultTaskClassifier.java b/quick-landing/dynamic-priority-executors/src/main/java/cc/magicjson/concurrent/priority/task/DefaultTaskClassifier.java new file mode 100644 index 0000000000000000000000000000000000000000..5780760baeb293dd9873d4feaf0a88e95c405aa5 --- /dev/null +++ b/quick-landing/dynamic-priority-executors/src/main/java/cc/magicjson/concurrent/priority/task/DefaultTaskClassifier.java @@ -0,0 +1,34 @@ +package cc.magicjson.concurrent.priority.task; + +/** + * DefaultTaskClassifier 是 TaskClassifier 接口的默认实现。 + * 它通过检查任务类名中的关键字来确定任务类型。 + */ +public class DefaultTaskClassifier implements TaskClassifier { + + /** + * 根据任务的类名对任务进行分类。 + * + * @param task 需要分类的 Runnable 任务 + * @return 分类后的 TaskType + */ + @Override + public TaskType classifyTask(Runnable task) { + // 获取任务的简单类名 + String taskName = task.getClass().getSimpleName(); + + // 根据类名中的关键字判断任务类型 + if (taskName.contains("FileRead")) { + return TaskType.FILE_READ; + } else if (taskName.contains("DNS")) { + return TaskType.DNS_LOOKUP; + } else if (taskName.contains("Database")) { + return TaskType.DATABASE_QUERY; + } else if (taskName.contains("Network")) { + return TaskType.NETWORK_IO; + } + + // 如果没有匹配到特定类型,默认为计算任务 + return TaskType.COMPUTATION; + } +} diff --git a/quick-landing/dynamic-priority-executors/src/main/java/cc/magicjson/concurrent/priority/task/PriorityFutureTask.java b/quick-landing/dynamic-priority-executors/src/main/java/cc/magicjson/concurrent/priority/task/PriorityFutureTask.java new file mode 100644 index 0000000000000000000000000000000000000000..87b20945388ad0cca88530289251b4a307e78751 --- /dev/null +++ b/quick-landing/dynamic-priority-executors/src/main/java/cc/magicjson/concurrent/priority/task/PriorityFutureTask.java @@ -0,0 +1,39 @@ +package cc.magicjson.concurrent.priority.task; + +import java.util.concurrent.FutureTask; + +/** + * PriorityFutureTask 类扩展了 FutureTask,并实现了 Comparable 接口。 + * 这个类用于在线程池中包装 PriorityTask,使其可以根据优先级进行排序。 + * + * @param 任务结果的类型 + */ +public class PriorityFutureTask extends FutureTask implements Comparable> { + + /** + * 被包装的 PriorityTask 实例 + */ + private final PriorityTask priorityTask; + + /** + * 构造一个新的 PriorityFutureTask + * + * @param task 要包装的 PriorityTask + */ + public PriorityFutureTask(PriorityTask task) { + super(task); + this.priorityTask = task; + } + + /** + * 比较此 PriorityFutureTask 与另一个 PriorityFutureTask 的优先级 + * + * @param o 要比较的其他 PriorityFutureTask + * @return 负数表示此任务优先级更高,正数表示其他任务优先级更高,0 表示优先级相同 + */ + @Override + public int compareTo(PriorityFutureTask o) { + // 委托给内部 PriorityTask 的 compareTo 方法 + return this.priorityTask.compareTo(o.priorityTask); + } +} diff --git a/quick-landing/dynamic-priority-executors/src/main/java/cc/magicjson/concurrent/priority/task/PriorityTask.java b/quick-landing/dynamic-priority-executors/src/main/java/cc/magicjson/concurrent/priority/task/PriorityTask.java new file mode 100644 index 0000000000000000000000000000000000000000..01381a9f9a7afe489bc7ea5a3f6f48a1732a3b39 --- /dev/null +++ b/quick-landing/dynamic-priority-executors/src/main/java/cc/magicjson/concurrent/priority/task/PriorityTask.java @@ -0,0 +1,132 @@ +package cc.magicjson.concurrent.priority.task; + +import java.util.concurrent.Callable; + + +/** + * PriorityTask 类表示一个具有优先级的任务。 + * 它实现了 Callable 接口,同时也实现了 Comparable 接口以支持优先级比较。 + * + * @param 任务执行结果的类型 + */ +public class PriorityTask implements Runnable, Callable, Comparable> { + + // 存储 Callable 类型的任务 + private final Callable task; + + // 任务的类型,用于确定任务的特性(如是否为轻量级任务) + private final TaskType taskType; + + // 任务的优先级,数值越小优先级越高 + private final int priority; + + // 任务创建的时间戳,用于在优先级相同时进行比较 + private final long creationTime; + + public PriorityTask(Callable task, TaskType taskType) { + this.task = task; + this.taskType = taskType; + this.priority = taskType.getPriority(); + this.creationTime = System.nanoTime(); + } + + /** + * 实现 Callable 接口的 call 方法 + * + * @return 任务执行的结果 + * @throws Exception 如果任务执行过程中抛出异常 + */ + @Override + public V call() throws Exception { + return task.call(); + } + + /** + * 实现 Runnable 接口的 run 方法 + * + * @throws Exception 如果任务执行过程中抛出异常 + */ + @Override + public void run() { + try { + call(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * 获取任务类型 + * + * @return 任务类型 + */ + public TaskType getTaskType() { + return taskType; + } + + /** + * 获取任务优先级 + * + * @return 任务优先级 + */ + public int getPriority() { + return priority; + } + + /** + * 获取任务创建时间 + * + * @return 任务创建时间(纳秒) + */ + public long getCreationTime() { + return creationTime; + } + + /** + * 实现 Comparable 接口的 compareTo 方法 + * 首先比较优先级,优先级相同时比较创建时间 + * + * @param other 要比较的其他 PriorityTask + * @return 比较结果:负数表示此任务优先级更高,正数表示其他任务优先级更高,0表示优先级相同 + */ + @Override + public int compareTo(PriorityTask other) { + // 优先级数字越小,优先级越高 + int priorityCompare = Integer.compare(this.getTaskType().getPriority(), other.getTaskType().getPriority()); + if (priorityCompare != 0) { + return priorityCompare; + } + // 如果优先级相同,比较创建时间 + return Long.compare(this.getCreationTime(), other.getCreationTime()); + } + + /** + * 创建 Callable 版本的 PriorityTask 的工厂方法 + * + * @param callable Callable 任务 + * @param taskType 任务类型 + * @param Callable 的返回类型 + * @return 包装了 Callable 的 PriorityTask + */ + public static PriorityTask createCallableTask(Callable callable, TaskType taskType) { + return new PriorityTask<>(callable, taskType); + } + + /** + * 创建 Runnable 版本的 PriorityTask 的工厂方法 + * + * @param runnable Runnable 任务 + * @param taskType 任务类型 + * @return 包装了 Runnable 的 PriorityTask + */ + public static PriorityTask createRunnableTask(Runnable runnable, TaskType taskType) { + return new PriorityTask<>(() -> { + runnable.run(); + return null; + }, taskType); + } + + public Callable asCallable() { + return this; + } +} diff --git a/quick-landing/dynamic-priority-executors/src/main/java/cc/magicjson/concurrent/priority/task/TaskClassifier.java b/quick-landing/dynamic-priority-executors/src/main/java/cc/magicjson/concurrent/priority/task/TaskClassifier.java new file mode 100644 index 0000000000000000000000000000000000000000..9ecd96656c7293d27c821e224453de471c120a3e --- /dev/null +++ b/quick-landing/dynamic-priority-executors/src/main/java/cc/magicjson/concurrent/priority/task/TaskClassifier.java @@ -0,0 +1,16 @@ +package cc.magicjson.concurrent.priority.task; + +/** + * TaskClassifier 接口定义了任务分类器的行为。 + * 实现此接口的类负责将提交的 Runnable 任务分类为预定义的 TaskType。 + */ +public interface TaskClassifier { + + /** + * 对给定的任务进行分类。 + * + * @param task 需要分类的 Runnable 任务 + * @return 分类后的 TaskType + */ + TaskType classifyTask(Runnable task); +} diff --git a/quick-landing/dynamic-priority-executors/src/main/java/cc/magicjson/concurrent/priority/task/TaskType.java b/quick-landing/dynamic-priority-executors/src/main/java/cc/magicjson/concurrent/priority/task/TaskType.java new file mode 100644 index 0000000000000000000000000000000000000000..29025cf7189bddb096b208ca1fae1d51e638fbd2 --- /dev/null +++ b/quick-landing/dynamic-priority-executors/src/main/java/cc/magicjson/concurrent/priority/task/TaskType.java @@ -0,0 +1,80 @@ +package cc.magicjson.concurrent.priority.task; + +import lombok.Getter; + +/** + * TaskType 枚举定义了系统中不同类型的任务及其优先级和轻量级标志。 + * 这个枚举用于在动态优先级线程池中对任务进行分类和优先级排序。 + */ +public enum TaskType { + /** + * 文件读取任务 + * 优先级最高(1),被视为轻量级任务 + * 通常用于快速的文件I/O操作 + */ + FILE_READ(1, true), + + /** + * DNS查询任务 + * 优先级最低(5),不被视为轻量级任务 + * 用于网络相关的DNS解析操作 + */ + DNS_LOOKUP(5, false), + + /** + * 数据库查询任务 + * 优先级中等(3),不被视为轻量级任务 + * 用于数据库操作,可能涉及复杂查询或事务 + */ + DATABASE_QUERY(3, false), + + /** + * 计算任务 + * 优先级较低(4),不被视为轻量级任务 + * 用于CPU密集型的计算操作 + */ + COMPUTATION(4, false), + + /** + * 网络I/O任务 + * 优先级较高(2),不被视为轻量级任务 + * 用于一般的网络通信操作 + */ + NETWORK_IO(2, false); + + /** + * 任务的优先级 + * 数值越小,优先级越高 + * -- GETTER -- + * 获取任务的优先级 + * + */ + @Getter + private final int priority; + + /** + * 标志是否为轻量级任务 + * 轻量级任务可能会得到特殊处理,如优先执行 + */ + private final boolean isLightWeight; + + /** + * TaskType 的构造函数 + * + * @param priority 任务的优先级,数值越小优先级越高 + * @param isLightWeight 是否为轻量级任务的标志 + */ + TaskType(int priority, boolean isLightWeight) { + this.priority = priority; + this.isLightWeight = isLightWeight; + } + + /** + * 判断任务是否为轻量级任务 + * + * @return 如果是轻量级任务返回 true,否则返回 false + */ + public boolean isLightWeight() { + return isLightWeight; + } +} diff --git a/quick-landing/dynamic-priority-executors/src/test/java/cc/magicjson/concurrent/priority/test/DynamicPriorityThreadPoolTest.java b/quick-landing/dynamic-priority-executors/src/test/java/cc/magicjson/concurrent/priority/test/DynamicPriorityThreadPoolTest.java new file mode 100644 index 0000000000000000000000000000000000000000..b7e343f3c6e6380ee227ad3297c002f97cadeb58 --- /dev/null +++ b/quick-landing/dynamic-priority-executors/src/test/java/cc/magicjson/concurrent/priority/test/DynamicPriorityThreadPoolTest.java @@ -0,0 +1,131 @@ +package cc.magicjson.concurrent.priority.test; + +import cc.magicjson.concurrent.priority.core.DynamicPriorityThreadPool; +import cc.magicjson.concurrent.priority.task.PriorityTask; +import cc.magicjson.concurrent.priority.task.TaskType; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.Timeout; + +import java.time.Duration; +import java.time.Instant; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import static org.junit.jupiter.api.Assertions.*; + +public class DynamicPriorityThreadPoolTest { + + private record TaskExecution(TaskType type, Instant startTime, Instant endTime, int executionOrder) { + } + + @Test + @Timeout(60) + public void testTaskPriorities() { + try (var pool = new DynamicPriorityThreadPool.Builder() + .corePoolSize(2) + .maximumPoolSize(4) + .minLightTaskThreads(1) + .build()) { + + var executionOrder = new AtomicInteger(0); + var startSignal = new CompletableFuture(); + + System.out.println("Submitting tasks..."); + List> taskFutures = Arrays.stream(TaskType.values()) + .map(taskType -> { + System.out.println("Submitting task: " + taskType); + return createTask(taskType, executionOrder, startSignal, pool); + }) + .toList(); + + System.out.println("Starting all tasks"); + startSignal.complete(null); + + List results = CompletableFuture.allOf(taskFutures.toArray(CompletableFuture[]::new)) + .thenApply(v -> taskFutures.stream() + .map(CompletableFuture::join) + .toList()) + .orTimeout(30, TimeUnit.SECONDS) + .exceptionally(ex -> { + fail("Task execution failed or timed out: " + ex.getMessage()); + return List.of(); + }) + .join(); + + System.out.println("All tasks completed"); + verifyExecutionOrder(results); + verifyLightWeightTaskExecution(results); + printExecutionStatistics(results); + } + } + + private CompletableFuture createTask(TaskType taskType, AtomicInteger executionOrder, CompletableFuture startSignal, DynamicPriorityThreadPool pool) { + return startSignal.thenCompose(ignored -> { + PriorityTask task = PriorityTask.createCallableTask(() -> { + var start = Instant.now(); + try { + // 使用固定的睡眠时间,以避免执行时间影响优先级顺序 + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + var end = Instant.now(); + var order = executionOrder.incrementAndGet(); + System.out.printf("Task %s completed. Order: %d%n", taskType, order); + return new TaskExecution(taskType, start, end, order); + }, taskType); + + return CompletableFuture.supplyAsync(() -> { + try { + return pool.submit((Callable) task).get(); + } catch (Exception e) { + throw new CompletionException(e); + } + }); + }); + } + + private void verifyExecutionOrder(List results) { + for (int i = 0; i < results.size() - 1; i++) { + var current = results.get(i); + var next = results.get(i + 1); + assertTrue(current.type().getPriority() <= next.type().getPriority(), + () -> String.format("Tasks should be executed in priority order. %s (priority %d) " + + "should be executed before or at the same time as %s (priority %d)", + current.type(), current.type().getPriority(), + next.type(), next.type().getPriority())); + } + + var firstTask = results.get(0); + assertTrue(firstTask.type().isLightWeight(), + () -> "The first executed task should be a light-weight task. Actual: " + firstTask.type()); + } + + private void verifyLightWeightTaskExecution(List results) { + var lightWeightTasks = results.stream() + .filter(task -> task.type().isLightWeight()) + .toList(); + + assertFalse(lightWeightTasks.isEmpty(), "There should be at least one light-weight task"); + + var firstTaskStartTime = lightWeightTasks.get(0).startTime(); + for (var task : lightWeightTasks) { + var executionDelay = Duration.between(firstTaskStartTime, task.startTime()); + assertTrue(executionDelay.toMillis() < 50, + () -> String.format("Light-weight task %s should start within 50ms of the first task. " + + "Actual delay: %d ms", task.type(), executionDelay.toMillis())); + } + } + + private void printExecutionStatistics(List results) { + System.out.println("\nTask Execution Statistics:"); + results.forEach(task -> System.out.printf("%s: Order=%d, Duration=%d ms%n", + task.type(), task.executionOrder(), + Duration.between(task.startTime(), task.endTime()).toMillis())); + } +} diff --git a/quick-landing/pom.xml b/quick-landing/pom.xml index 7dfb4caf567730eef5152f37aa67153ca011b91c..ea18cf2a456940b21e533d1cf2e8e81f6cc1bd6f 100644 --- a/quick-landing/pom.xml +++ b/quick-landing/pom.xml @@ -15,6 +15,7 @@ https://gitee.com/MagicJson/learning-training.git dynamic-rest-caller + dynamic-priority-executors diff --git a/uml-adapter-architecture/LobeChat.yml b/uml-adapter-architecture/LobeChat.yml new file mode 100644 index 0000000000000000000000000000000000000000..d9750ebc2bffa48c899ff3cc7c1bda49ce495f97 --- /dev/null +++ b/uml-adapter-architecture/LobeChat.yml @@ -0,0 +1,13 @@ +version: '3.8' + +services: + lobe-chat: + image: lobehub/lobe-chat + container_name: lobe-chat + restart: always + ports: + - '3210:3210' + environment: + OPENAI_API_KEY: sess-Vzqgt1wlhovMf50SEjci3XAvCeMTqZAJXU4Yl0nE + OPENAI_PROXY_URL: https://api-proxy.com/v1 + ACCESS_CODE: lobe66 \ No newline at end of file