task-62: 本地任务实体缓存共享组件(容量 LRU/TTL 即时回收/定时清理)接入 8 模块
This commit is contained in:
+3
-1
@@ -5,6 +5,7 @@ import com.nanri.aiimage.config.TaskPressureProperties;
|
||||
import com.nanri.aiimage.modules.shopdatacrawl.model.dto.ShopDataCrawlShopPayloadDto;
|
||||
import com.nanri.aiimage.modules.task.model.entity.FileTaskEntity;
|
||||
import com.nanri.aiimage.modules.task.service.TaskScopePayloadStorageService;
|
||||
import com.nanri.aiimage.modules.task.util.TaskEntityLocalCache;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mock;
|
||||
@@ -54,7 +55,8 @@ class ShopDataCrawlTaskCacheServiceTest {
|
||||
stringRedisTemplate,
|
||||
objectMapper,
|
||||
properties,
|
||||
taskScopePayloadStorageService);
|
||||
taskScopePayloadStorageService,
|
||||
new TaskEntityLocalCache(properties, objectMapper));
|
||||
|
||||
lenient().when(stringRedisTemplate.opsForValue()).thenReturn(valueOperations);
|
||||
lenient().when(valueOperations.multiGet(any())).thenReturn(List.of());
|
||||
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
package com.nanri.aiimage.modules.task.util;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.nanri.aiimage.config.TaskPressureProperties;
|
||||
import com.nanri.aiimage.modules.task.model.entity.FileTaskEntity;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
/**
|
||||
* Task 62:为本地任务实体缓存增加最大条目数、TTL 和定时清理。
|
||||
* TaskEntityLocalCache 为共享任务链路提供有界本地实体缓存:容量超限按
|
||||
* cachedAtMillis LRU 淘汰最旧条目(内存有界,不发生无界增长);TTL 内
|
||||
* 返回实体副本、过期条目读取时即时回收;定时清理 removeExpired 批量
|
||||
* 释放过期条目;同一任务重复保存只占一条(幂等)、删除即时释放;
|
||||
* 空/非法输入安全跳过不创建无效条目。
|
||||
*/
|
||||
class TaskEntityLocalCacheTest {
|
||||
|
||||
private TaskPressureProperties properties;
|
||||
private TaskEntityLocalCache cache;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
properties = new TaskPressureProperties();
|
||||
properties.setLocalTaskEntityCacheMillis(3000);
|
||||
properties.setLocalTaskEntityCacheCapacity(4);
|
||||
cache = new TaskEntityLocalCache(properties, new ObjectMapper());
|
||||
}
|
||||
|
||||
private static FileTaskEntity task(long id) {
|
||||
FileTaskEntity entity = new FileTaskEntity();
|
||||
entity.setId(id);
|
||||
entity.setModuleType("TEST");
|
||||
return entity;
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_task_062_cache_cleanup_normal_default_path() {
|
||||
// 正常路径:保存后 TTL 内立即可读回同值实体,缓存条目数精确。
|
||||
cache.put(1L, task(1L));
|
||||
|
||||
TaskEntityLocalCache.Entry entry = cache.get(1L);
|
||||
|
||||
assertNotNull(entry, "保存后立即可读");
|
||||
assertEquals(1L, entry.task().getId(), "读回实体一致");
|
||||
assertEquals(1, cache.size(), "恰好一条");
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_task_062_cache_cleanup_normal_multiple_items() {
|
||||
// 批量场景:容量内多个任务实体全部可读回,数量精确,互不覆盖。
|
||||
properties.setLocalTaskEntityCacheCapacity(10);
|
||||
for (long id = 1; id <= 5; id++) {
|
||||
cache.put(id, task(id));
|
||||
}
|
||||
|
||||
for (long id = 1; id <= 5; id++) {
|
||||
TaskEntityLocalCache.Entry entry = cache.get(id);
|
||||
assertNotNull(entry, "任务 " + id + " 可读回");
|
||||
assertEquals(id, entry.task().getId(), "任务 " + id + " 值正确");
|
||||
}
|
||||
assertEquals(5, cache.size(), "5 条全部保留");
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_task_062_cache_cleanup_normal_repeated_operation_is_idempotent() {
|
||||
// 幂等:同一任务重复保存只占一条,值稳定,不产生重复条目。
|
||||
cache.put(1L, task(1L));
|
||||
cache.put(1L, task(1L));
|
||||
cache.put(1L, task(1L));
|
||||
|
||||
assertEquals(1, cache.size(), "重复保存只占一条");
|
||||
assertEquals(1L, cache.get(1L).task().getId(), "值稳定");
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_task_062_cache_cleanup_boundary_empty_input() {
|
||||
// 空输入:null 任务 / 空 id 任务 / null 查询安全跳过,不创建无效条目。
|
||||
cache.put(null, task(1L));
|
||||
cache.put(2L, null);
|
||||
FileTaskEntity noId = new FileTaskEntity();
|
||||
cache.put(noId.getId(), noId);
|
||||
|
||||
assertNull(cache.get(null), "null 查询返回 null");
|
||||
assertNull(cache.get(-1L), "非法 id 查询返回 null");
|
||||
assertEquals(0, cache.size(), "空输入零条目");
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_task_062_cache_cleanup_boundary_single_item() {
|
||||
// 单元素:单条保存读回正确,删除后立即释放。
|
||||
cache.put(9L, task(9L));
|
||||
assertEquals(9L, cache.get(9L).task().getId(), "单条读回");
|
||||
|
||||
cache.evict(9L);
|
||||
|
||||
assertNull(cache.get(9L), "删除后释放");
|
||||
assertEquals(0, cache.size(), "删除后零条目");
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_task_062_cache_cleanup_boundary_limit_and_overflow() {
|
||||
// 上限/超限:容量 4 保存 6 条 → 最旧 2 条被 LRU 淘汰,
|
||||
// 条目数不超过容量上限,最新 4 条仍可读回,无无界增长。
|
||||
for (long id = 1; id <= 6; id++) {
|
||||
cache.put(id, task(id));
|
||||
}
|
||||
|
||||
assertEquals(4, cache.size(), "条目数不超过容量上限");
|
||||
assertNull(cache.get(1L), "最旧条目被淘汰");
|
||||
assertNull(cache.get(2L), "次旧条目被淘汰");
|
||||
for (long id = 3; id <= 6; id++) {
|
||||
assertNotNull(cache.get(id), "任务 " + id + " 仍可读回");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_task_062_cache_cleanup_invalid_input_rejected() {
|
||||
// 非法参数:非正任务 id 保存被拒绝,缓存状态不受污染。
|
||||
FileTaskEntity negative = task(-1L);
|
||||
FileTaskEntity zero = task(0L);
|
||||
cache.put(-1L, negative);
|
||||
cache.put(0L, zero);
|
||||
|
||||
assertEquals(0, cache.size(), "非法 id 零条目");
|
||||
}
|
||||
|
||||
@Test
|
||||
void test_task_062_cache_cleanup_dependency_failure_releases_resources() {
|
||||
// 依赖失败(时间流逝使缓存条目过期):过期条目读取时即时回收,
|
||||
// 定时清理批量释放全部过期条目,未过期条目保留,无资源残留。
|
||||
cache.put(1L, task(1L));
|
||||
cache.put(2L, task(2L));
|
||||
long now = System.currentTimeMillis();
|
||||
|
||||
assertNull(cache.get(1L, now + 4000), "超过 TTL 读取返回 null 并即时回收");
|
||||
assertEquals(1, cache.size(), "过期条目读取时已回收");
|
||||
|
||||
int removed = cache.removeExpired(now + 4000);
|
||||
assertEquals(1, removed, "定时清理释放剩余过期条目");
|
||||
assertEquals(0, cache.size(), "过期条目全部释放");
|
||||
|
||||
cache.put(3L, task(3L));
|
||||
assertEquals(0, cache.removeExpired(now), "未过期条目保留");
|
||||
assertEquals(1, cache.size(), "未过期条目不受清理影响");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user