fix(cache): 解决缓存中JSON null值导致的空指针问题
- 添加对历史缓存中JSON "null" 字符串的兼容处理 - 当缓存解析出null值时清理缓存并回源数据库 - 在CmsWebsiteServiceImpl中增加缓存清理逻辑 - 在ShopWebsiteServiceImpl中统一缓存异常处理机制 - 添加单元测试验证JSON null值场景的正确回退行为
This commit is contained in:
@@ -337,9 +337,16 @@ public class CmsWebsiteServiceImpl extends ServiceImpl<CmsWebsiteMapper, CmsWebs
|
||||
if (StrUtil.isNotBlank(siteInfo)) {
|
||||
log.info("从缓存获取网站信息,租户ID: {}", tenantId);
|
||||
try {
|
||||
return JSONUtil.parseObject(siteInfo, ShopVo.class);
|
||||
// 兼容历史缓存:JSON "null" 会被解析为 null;此时应视为未命中并回源数据库。
|
||||
ShopVo cacheVo = JSONUtil.parseObject(siteInfo, ShopVo.class);
|
||||
if (cacheVo != null) {
|
||||
return cacheVo;
|
||||
}
|
||||
log.warn("网站信息缓存命中但内容为空(null),清理缓存后回源数据库,租户ID: {}", tenantId);
|
||||
redisUtil.delete(cacheKey);
|
||||
} catch (Exception e) {
|
||||
log.warn("缓存解析失败,从数据库重新获取: {}", e.getMessage());
|
||||
log.warn("缓存解析失败,清理缓存后从数据库重新获取: {}", e.getMessage());
|
||||
redisUtil.delete(cacheKey);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -51,6 +51,9 @@ public class ShopWebsiteServiceImpl implements ShopWebsiteService {
|
||||
log.info("从缓存获取商城信息,租户ID: {}", tenantId);
|
||||
return cacheVo;
|
||||
}
|
||||
// 兼容历史缓存:JSON "null" 会被解析为 null;此时清理缓存并回源。
|
||||
log.warn("商城信息缓存命中但内容为空(null),清理缓存后回源数据库,租户ID: {}", tenantId);
|
||||
redisUtil.delete(cacheKey);
|
||||
} catch (Exception e) {
|
||||
log.warn("商城缓存解析失败,清理缓存后重新获取: {}", e.getMessage());
|
||||
redisUtil.delete(cacheKey);
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.gxwebsoft.cms.service.impl;
|
||||
|
||||
import com.gxwebsoft.cms.entity.CmsWebsite;
|
||||
import com.gxwebsoft.cms.mapper.CmsWebsiteMapper;
|
||||
import com.gxwebsoft.cms.service.CmsNavigationService;
|
||||
import com.gxwebsoft.common.core.utils.RedisUtil;
|
||||
import com.gxwebsoft.shop.vo.ShopVo;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
class CmsWebsiteServiceImplCacheTest {
|
||||
|
||||
@Test
|
||||
void getSiteInfo_cacheValueIsJsonNull_shouldFallbackToDb() throws Exception {
|
||||
Integer tenantId = 1;
|
||||
|
||||
RedisUtil redisUtil = mock(RedisUtil.class);
|
||||
CmsNavigationService cmsNavigationService = mock(CmsNavigationService.class);
|
||||
CmsWebsiteMapper cmsWebsiteMapper = mock(CmsWebsiteMapper.class);
|
||||
|
||||
// 历史缓存可能存在字符串 "null";Jackson 解析后会得到 null,需回源 DB。
|
||||
when(redisUtil.get("SiteInfo:" + tenantId)).thenReturn("null");
|
||||
when(cmsNavigationService.listRel(any())).thenReturn(Collections.emptyList());
|
||||
|
||||
CmsWebsite website = new CmsWebsite();
|
||||
website.setTenantId(tenantId);
|
||||
website.setTenantName("tenant");
|
||||
website.setWebsiteName("site");
|
||||
when(cmsWebsiteMapper.getByTenantId(tenantId)).thenReturn(website);
|
||||
|
||||
CmsWebsiteServiceImpl service = new CmsWebsiteServiceImpl();
|
||||
setField(service, "redisUtil", redisUtil);
|
||||
setField(service, "cmsNavigationService", cmsNavigationService);
|
||||
setField(service, "baseMapper", cmsWebsiteMapper);
|
||||
|
||||
ShopVo vo = service.getSiteInfo(tenantId);
|
||||
|
||||
assertNotNull(vo);
|
||||
verify(redisUtil).delete("SiteInfo:" + tenantId);
|
||||
verify(cmsWebsiteMapper).getByTenantId(tenantId);
|
||||
}
|
||||
|
||||
private static void setField(Object target, String fieldName, Object value) throws Exception {
|
||||
Class<?> c = target.getClass();
|
||||
while (c != null) {
|
||||
try {
|
||||
Field f = c.getDeclaredField(fieldName);
|
||||
f.setAccessible(true);
|
||||
f.set(target, value);
|
||||
return;
|
||||
} catch (NoSuchFieldException ignored) {
|
||||
c = c.getSuperclass();
|
||||
}
|
||||
}
|
||||
throw new NoSuchFieldException(fieldName);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user