mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-22 23:23:55 +08:00
fix(manager-api): make JSON collections type-safe
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
package xiaozhi.common.utils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -16,6 +18,10 @@ import cn.hutool.core.util.StrUtil;
|
||||
*/
|
||||
public class JsonUtils {
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
private static final TypeReference<Map<String, Object>> STRING_OBJECT_MAP = new TypeReference<>() {
|
||||
};
|
||||
private static final TypeReference<List<Map<String, Object>>> STRING_OBJECT_MAP_LIST = new TypeReference<>() {
|
||||
};
|
||||
|
||||
public static String toJsonString(Object object) {
|
||||
try {
|
||||
@@ -67,4 +73,59 @@ public class JsonUtils {
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<String, Object> parseMap(String text) {
|
||||
if (StrUtil.isEmpty(text)) {
|
||||
return null;
|
||||
}
|
||||
return parseObject(text, STRING_OBJECT_MAP);
|
||||
}
|
||||
|
||||
public static List<Map<String, Object>> parseMapList(String text) {
|
||||
if (StrUtil.isEmpty(text)) {
|
||||
return null;
|
||||
}
|
||||
return parseObject(text, STRING_OBJECT_MAP_LIST);
|
||||
}
|
||||
|
||||
public static Map<String, Object> toStringObjectMap(Object value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
if (!(value instanceof Map<?, ?> map)) {
|
||||
throw new ClassCastException("Expected Map but got " + value.getClass().getName());
|
||||
}
|
||||
|
||||
Map<String, Object> result = new LinkedHashMap<>(map.size());
|
||||
for (Map.Entry<?, ?> entry : map.entrySet()) {
|
||||
result.put(String.class.cast(entry.getKey()), entry.getValue());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static List<Map<String, Object>> toStringObjectMapList(Object value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<?> list = List.class.cast(value);
|
||||
List<Map<String, Object>> result = new ArrayList<>(list.size());
|
||||
for (Object item : list) {
|
||||
result.add(toStringObjectMap(item));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <T> List<T> toList(Object value, Class<T> elementType) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<?> list = List.class.cast(value);
|
||||
List<T> result = new ArrayList<>(list.size());
|
||||
for (Object item : list) {
|
||||
result.add(elementType.cast(item));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+3
-2
@@ -34,6 +34,7 @@ import xiaozhi.common.page.PageData;
|
||||
import xiaozhi.common.redis.RedisKeys;
|
||||
import xiaozhi.common.redis.RedisUtils;
|
||||
import xiaozhi.common.user.UserDetail;
|
||||
import xiaozhi.common.utils.JsonUtils;
|
||||
import xiaozhi.common.utils.Result;
|
||||
import xiaozhi.common.utils.ResultUtils;
|
||||
import xiaozhi.modules.agent.dto.AgentChatHistoryDTO;
|
||||
@@ -340,8 +341,8 @@ public class AgentController {
|
||||
@RequiresPermissions("sys:role:normal")
|
||||
public Result<Void> saveAgentTags(@PathVariable String id, @RequestBody Map<String, Object> params) {
|
||||
requireAgentPermission(id);
|
||||
List<String> tagIds = (List<String>) params.get("tagIds");
|
||||
List<String> tagNames = (List<String>) params.get("tagNames");
|
||||
List<String> tagIds = JsonUtils.toList(params.get("tagIds"), String.class);
|
||||
List<String> tagNames = JsonUtils.toList(params.get("tagNames"), String.class);
|
||||
AgentUpdateDTO dto = new AgentUpdateDTO();
|
||||
dto.setTagIds(tagIds);
|
||||
dto.setTagNames(tagNames);
|
||||
|
||||
+1
-1
@@ -167,7 +167,7 @@ public class AgentChatHistoryServiceImpl extends CrudRepository<AiAgentChatHisto
|
||||
|
||||
// 尝试解析为 JSON
|
||||
try {
|
||||
Map<String, Object> jsonMap = JsonUtils.parseObject(content, Map.class);
|
||||
Map<String, Object> jsonMap = JsonUtils.parseMap(content);
|
||||
if (jsonMap != null && jsonMap.containsKey("content")) {
|
||||
Object contentObj = jsonMap.get("content");
|
||||
return contentObj != null ? contentObj.toString() : content;
|
||||
|
||||
+10
-10
@@ -76,7 +76,7 @@ public class AgentMcpAccessPointServiceImpl implements AgentMcpAccessPointServic
|
||||
// 等待初始化响应 (id=1) - 移除固定延迟,改为响应驱动
|
||||
List<String> initResponses = client.listenerWithoutClose(response -> {
|
||||
try {
|
||||
Map<String, Object> jsonMap = JsonUtils.parseObject(response, Map.class);
|
||||
Map<String, Object> jsonMap = JsonUtils.parseMap(response);
|
||||
if (jsonMap != null && Integer.valueOf(1).equals(jsonMap.get("id"))) {
|
||||
// 检查是否有result字段,表示初始化成功
|
||||
return jsonMap.containsKey("result") && !jsonMap.containsKey("error");
|
||||
@@ -92,7 +92,7 @@ public class AgentMcpAccessPointServiceImpl implements AgentMcpAccessPointServic
|
||||
boolean initSucceeded = false;
|
||||
for (String response : initResponses) {
|
||||
try {
|
||||
Map<String, Object> jsonMap = JsonUtils.parseObject(response, Map.class);
|
||||
Map<String, Object> jsonMap = JsonUtils.parseMap(response);
|
||||
if (jsonMap != null && Integer.valueOf(1).equals(jsonMap.get("id"))) {
|
||||
if (jsonMap.containsKey("result")) {
|
||||
log.info("MCP初始化成功,智能体ID: {}", id);
|
||||
@@ -123,7 +123,7 @@ public class AgentMcpAccessPointServiceImpl implements AgentMcpAccessPointServic
|
||||
// 等待工具列表响应 (id=2)
|
||||
List<String> toolsResponses = client.listener(response -> {
|
||||
try {
|
||||
Map<String, Object> jsonMap = JsonUtils.parseObject(response, Map.class);
|
||||
Map<String, Object> jsonMap = JsonUtils.parseMap(response);
|
||||
return jsonMap != null && Integer.valueOf(2).equals(jsonMap.get("id"));
|
||||
} catch (Exception e) {
|
||||
log.warn("解析工具列表响应失败: {}", response, e);
|
||||
@@ -134,18 +134,18 @@ public class AgentMcpAccessPointServiceImpl implements AgentMcpAccessPointServic
|
||||
// 处理工具列表响应
|
||||
for (String response : toolsResponses) {
|
||||
try {
|
||||
Map<String, Object> jsonMap = JsonUtils.parseObject(response, Map.class);
|
||||
Map<String, Object> jsonMap = JsonUtils.parseMap(response);
|
||||
if (jsonMap != null && Integer.valueOf(2).equals(jsonMap.get("id"))) {
|
||||
// 检查是否有result字段
|
||||
Object resultObj = jsonMap.get("result");
|
||||
if (resultObj instanceof Map) {
|
||||
Map<String, Object> resultMap = (Map<String, Object>) resultObj;
|
||||
if (resultObj instanceof Map<?, ?>) {
|
||||
Map<String, Object> resultMap = JsonUtils.toStringObjectMap(resultObj);
|
||||
Object toolsObj = resultMap.get("tools");
|
||||
if (toolsObj instanceof List) {
|
||||
List<Map<String, Object>> toolsList = (List<Map<String, Object>>) toolsObj;
|
||||
if (toolsObj instanceof List<?>) {
|
||||
List<Map<String, Object>> toolsList = JsonUtils.toStringObjectMapList(toolsObj);
|
||||
// 提取工具名称列表
|
||||
List<String> result = toolsList.stream()
|
||||
.map(tool -> (String) tool.get("name"))
|
||||
.map(tool -> String.class.cast(tool.get("name")))
|
||||
.filter(name -> name != null)
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
@@ -232,4 +232,4 @@ public class AgentMcpAccessPointServiceImpl implements AgentMcpAccessPointServic
|
||||
// 加密后成token值
|
||||
return AESUtils.encrypt(key, json);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -677,7 +677,7 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
||||
mapping.setPluginId(pluginId);
|
||||
|
||||
Map<String, Object> paramInfo = new HashMap<>();
|
||||
List<Map<String, Object>> fields = JsonUtils.parseObject(provider.getFields(), List.class);
|
||||
List<Map<String, Object>> fields = JsonUtils.parseMapList(provider.getFields());
|
||||
if (fields != null) {
|
||||
for (Map<String, Object> field : fields) {
|
||||
paramInfo.put((String) field.get("key"), field.get("default"));
|
||||
|
||||
@@ -10,7 +10,7 @@ public interface ConfigService {
|
||||
* @param isCache 是否缓存
|
||||
* @return 配置信息
|
||||
*/
|
||||
Object getConfig(Boolean isCache);
|
||||
Map<String, Object> getConfig(Boolean isCache);
|
||||
|
||||
/**
|
||||
* 获取智能体模型配置
|
||||
@@ -28,4 +28,4 @@ public interface ConfigService {
|
||||
* @return 替换词列表,格式如 ["模板1|模板01", "模板2|模板02"]
|
||||
*/
|
||||
List<String> getCorrectWords(String macAddress);
|
||||
}
|
||||
}
|
||||
|
||||
+7
-7
@@ -65,12 +65,12 @@ public class ConfigServiceImpl implements ConfigService {
|
||||
private final CorrectWordFileService correctWordFileService;
|
||||
|
||||
@Override
|
||||
public Object getConfig(Boolean isCache) {
|
||||
public Map<String, Object> getConfig(Boolean isCache) {
|
||||
if (isCache) {
|
||||
// 先从Redis获取配置
|
||||
Object cachedConfig = redisUtils.get(RedisKeys.getServerConfigKey());
|
||||
if (cachedConfig != null) {
|
||||
return cachedConfig;
|
||||
return JsonUtils.toStringObjectMap(cachedConfig);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ public class ConfigServiceImpl implements ConfigService {
|
||||
if (isAdminRequest != null && "true".equals(isAdminRequest)) {
|
||||
// 管理控制台请求,返回getConfig的结果
|
||||
redisUtils.delete(redisKey); // 使用后清理
|
||||
return (Map<String, Object>) getConfig(true);
|
||||
return getConfig(true);
|
||||
}
|
||||
// 根据MAC地址查找设备
|
||||
DeviceEntity device = deviceService.getDeviceByMacAddress(macAddress);
|
||||
@@ -277,10 +277,10 @@ public class ConfigServiceImpl implements ConfigService {
|
||||
// 遍历除最后一个key之外的所有key
|
||||
for (int i = 0; i < keys.length - 1; i++) {
|
||||
String key = keys[i];
|
||||
if (!current.containsKey(key)) {
|
||||
current.put(key, new HashMap<String, Object>());
|
||||
}
|
||||
current = (Map<String, Object>) current.get(key);
|
||||
Object nestedConfig = current.computeIfAbsent(key, ignored -> new HashMap<String, Object>());
|
||||
Map<String, Object> nestedMap = JsonUtils.toStringObjectMap(nestedConfig);
|
||||
current.put(key, nestedMap);
|
||||
current = nestedMap;
|
||||
}
|
||||
|
||||
// 处理最后一个key
|
||||
|
||||
+3
-2
@@ -50,6 +50,7 @@ import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||
import xiaozhi.common.user.UserDetail;
|
||||
import xiaozhi.common.utils.ConvertUtils;
|
||||
import xiaozhi.common.utils.DateUtils;
|
||||
import xiaozhi.common.utils.JsonUtils;
|
||||
import xiaozhi.common.utils.ToolUtil;
|
||||
import xiaozhi.modules.device.dao.DeviceDao;
|
||||
import xiaozhi.modules.device.dto.DeviceManualAddDTO;
|
||||
@@ -109,7 +110,7 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
||||
String deviceId = (String) cacheDeviceId;
|
||||
String safeDeviceId = deviceId.replace(":", "_").toLowerCase();
|
||||
String cacheDeviceKey = RedisKeys.getOtaDeviceActivationInfo(safeDeviceId);
|
||||
Map<String, Object> cacheMap = (Map<String, Object>) redisUtils.get(cacheDeviceKey);
|
||||
Map<String, Object> cacheMap = JsonUtils.toStringObjectMap(redisUtils.get(cacheDeviceKey));
|
||||
if (ToolUtil.isEmpty(cacheMap)) {
|
||||
throw new RenException(ErrorCode.ACTIVATION_CODE_ERROR);
|
||||
}
|
||||
@@ -411,7 +412,7 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
||||
public String geCodeByDeviceId(String deviceId) {
|
||||
String dataKey = getDeviceCacheKey(deviceId);
|
||||
|
||||
Map<String, Object> cacheMap = (Map<String, Object>) redisUtils.get(dataKey);
|
||||
Map<String, Object> cacheMap = JsonUtils.toStringObjectMap(redisUtils.get(dataKey));
|
||||
if (cacheMap != null && cacheMap.containsKey("activation_code")) {
|
||||
String cachedCode = (String) cacheMap.get("activation_code");
|
||||
return cachedCode;
|
||||
|
||||
+11
-10
@@ -149,8 +149,8 @@ public class RAGFlowAdapter extends KnowledgeBaseAdapter {
|
||||
log.info("=== [RAGFlow] 获取文档列表: datasetId={} ===", datasetId);
|
||||
|
||||
// 使用 Jackson 将 DTO 转为 Map 作为查询参数
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> params = objectMapper.convertValue(req, Map.class);
|
||||
Map<String, Object> params = objectMapper.convertValue(req, new TypeReference<Map<String, Object>>() {
|
||||
});
|
||||
|
||||
Map<String, Object> response = getClient().get("/api/v1/datasets/" + datasetId + "/documents", params);
|
||||
|
||||
@@ -174,13 +174,12 @@ public class RAGFlowAdapter extends KnowledgeBaseAdapter {
|
||||
.pageSize(1)
|
||||
.build();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> params = objectMapper.convertValue(req, Map.class);
|
||||
Map<String, Object> params = objectMapper.convertValue(req, new TypeReference<Map<String, Object>>() {
|
||||
});
|
||||
Map<String, Object> response = getClient().get("/api/v1/datasets/" + datasetId + "/documents", params);
|
||||
|
||||
Object dataObj = response.get("data");
|
||||
if (dataObj instanceof Map) {
|
||||
Map<String, Object> dataMap = (Map<String, Object>) dataObj;
|
||||
if (dataObj instanceof Map<?, ?> dataMap) {
|
||||
List<?> documents = (List<?>) dataMap.get("docs");
|
||||
if (documents != null && !documents.isEmpty()) {
|
||||
return objectMapper.convertValue(documents.get(0), DocumentDTO.InfoVO.class);
|
||||
@@ -567,8 +566,8 @@ public class RAGFlowAdapter extends KnowledgeBaseAdapter {
|
||||
return new PageData<>(new ArrayList<>(), 0);
|
||||
}
|
||||
|
||||
Map<String, Object> dataMap = (Map<String, Object>) dataObj;
|
||||
List<Map<String, Object>> documents = (List<Map<String, Object>>) dataMap.get("docs");
|
||||
Map<?, ?> dataMap = Map.class.cast(dataObj);
|
||||
List<?> documents = List.class.cast(dataMap.get("docs"));
|
||||
if (documents == null || documents.isEmpty()) {
|
||||
// RAGFlow 明确返回了空文档列表,这是合法的"真空"
|
||||
return new PageData<>(new ArrayList<>(), 0);
|
||||
@@ -679,7 +678,9 @@ public class RAGFlowAdapter extends KnowledgeBaseAdapter {
|
||||
dto.setChunkMethod(info.getChunkMethod().name().toLowerCase());
|
||||
}
|
||||
if (info.getParserConfig() != null) {
|
||||
dto.setParserConfig(objectMapper.convertValue(info.getParserConfig(), Map.class));
|
||||
dto.setParserConfig(objectMapper.convertValue(info.getParserConfig(),
|
||||
new TypeReference<Map<String, Object>>() {
|
||||
}));
|
||||
}
|
||||
|
||||
return dto;
|
||||
@@ -717,4 +718,4 @@ public class RAGFlowAdapter extends KnowledgeBaseAdapter {
|
||||
return !multipartFile.isEmpty();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -20,6 +20,7 @@ import xiaozhi.common.redis.RedisKeys;
|
||||
import xiaozhi.common.redis.RedisUtils;
|
||||
import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||
import xiaozhi.common.utils.ConvertUtils;
|
||||
import xiaozhi.common.utils.JsonUtils;
|
||||
import xiaozhi.common.utils.ToolUtil;
|
||||
import xiaozhi.modules.sys.dao.SysDictDataDao;
|
||||
import xiaozhi.modules.sys.dao.SysUserDao;
|
||||
@@ -170,7 +171,7 @@ public class SysDictDataServiceImpl extends BaseServiceImpl<SysDictDataDao, SysD
|
||||
|
||||
// 先从Redis获取缓存
|
||||
String key = RedisKeys.getDictDataByTypeKey(dictType);
|
||||
List<SysDictDataItem> cachedData = (List<SysDictDataItem>) redisUtils.get(key);
|
||||
List<SysDictDataItem> cachedData = JsonUtils.toList(redisUtils.get(key), SysDictDataItem.class);
|
||||
if (cachedData != null) {
|
||||
return cachedData;
|
||||
}
|
||||
|
||||
+10
-12
@@ -287,10 +287,10 @@ public class SysParamsServiceImpl extends BaseServiceImpl<SysParamsDao, SysParam
|
||||
|
||||
try {
|
||||
if (StringUtils.isNotBlank(currentConfig)) {
|
||||
currentMap = JsonUtils.parseObject(currentConfig, Map.class);
|
||||
currentMap = JsonUtils.parseMap(currentConfig);
|
||||
}
|
||||
if (StringUtils.isNotBlank(configJson)) {
|
||||
newMap = JsonUtils.parseObject(configJson, Map.class);
|
||||
newMap = JsonUtils.parseMap(configJson);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RenException(ErrorCode.PARAM_JSON_INVALID);
|
||||
@@ -298,8 +298,8 @@ public class SysParamsServiceImpl extends BaseServiceImpl<SysParamsDao, SysParam
|
||||
|
||||
// 检查addressBook功能是否被关闭
|
||||
if (currentMap != null && newMap != null) {
|
||||
Map<String, Object> currentFeatures = (Map<String, Object>) currentMap.get("features");
|
||||
Map<String, Object> newFeatures = (Map<String, Object>) newMap.get("features");
|
||||
Map<?, ?> currentFeatures = Map.class.cast(currentMap.get("features"));
|
||||
Map<?, ?> newFeatures = Map.class.cast(newMap.get("features"));
|
||||
|
||||
if (currentFeatures != null && newFeatures != null) {
|
||||
Object currentAddressBookObj = currentFeatures.get("addressBook");
|
||||
@@ -308,16 +308,14 @@ public class SysParamsServiceImpl extends BaseServiceImpl<SysParamsDao, SysParam
|
||||
Boolean currentEnabled = false;
|
||||
Boolean newEnabled = false;
|
||||
|
||||
if (currentAddressBookObj instanceof Map) {
|
||||
Map<String, Object> currentAddressBook = (Map<String, Object>) currentAddressBookObj;
|
||||
currentEnabled = currentAddressBook.get("enabled") != null
|
||||
? (Boolean) currentAddressBook.get("enabled") : false;
|
||||
if (currentAddressBookObj instanceof Map<?, ?> currentAddressBook) {
|
||||
Object enabled = currentAddressBook.get("enabled");
|
||||
currentEnabled = enabled != null ? Boolean.class.cast(enabled) : false;
|
||||
}
|
||||
|
||||
if (newAddressBookObj instanceof Map) {
|
||||
Map<String, Object> newAddressBook = (Map<String, Object>) newAddressBookObj;
|
||||
newEnabled = newAddressBook.get("enabled") != null
|
||||
? (Boolean) newAddressBook.get("enabled") : false;
|
||||
if (newAddressBookObj instanceof Map<?, ?> newAddressBook) {
|
||||
Object enabled = newAddressBook.get("enabled");
|
||||
newEnabled = enabled != null ? Boolean.class.cast(enabled) : false;
|
||||
}
|
||||
|
||||
// 如果之前是启用状态,现在被禁用,删除所有call_device插件
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package xiaozhi.common.redis;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
|
||||
import xiaozhi.common.utils.JsonUtils;
|
||||
import xiaozhi.modules.sys.vo.SysDictDataItem;
|
||||
|
||||
class RedisSerializationTest {
|
||||
private final RedisSerializer<Object> serializer = RedisSerializer.json();
|
||||
|
||||
@Test
|
||||
void serverConfigRoundTripRestoresStringKeyedMaps() {
|
||||
Map<String, Object> features = new HashMap<>();
|
||||
features.put("addressBook", Map.of("enabled", true));
|
||||
Map<String, Object> config = new HashMap<>();
|
||||
config.put("features", features);
|
||||
|
||||
Object restored = roundTrip(config);
|
||||
Map<String, Object> restoredConfig = JsonUtils.toStringObjectMap(restored);
|
||||
Map<String, Object> restoredFeatures = JsonUtils.toStringObjectMap(restoredConfig.get("features"));
|
||||
Map<String, Object> addressBook = JsonUtils.toStringObjectMap(restoredFeatures.get("addressBook"));
|
||||
|
||||
assertEquals(true, addressBook.get("enabled"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void dictionaryListRoundTripRestoresDtoElements() {
|
||||
SysDictDataItem item = new SysDictDataItem();
|
||||
item.setName("enabled");
|
||||
item.setKey("1");
|
||||
|
||||
Object restored = roundTrip(new ArrayList<>(List.of(item)));
|
||||
List<SysDictDataItem> restoredItems = JsonUtils.toList(restored, SysDictDataItem.class);
|
||||
|
||||
assertInstanceOf(SysDictDataItem.class, restoredItems.get(0));
|
||||
assertEquals("enabled", restoredItems.get(0).getName());
|
||||
assertEquals("1", restoredItems.get(0).getKey());
|
||||
}
|
||||
|
||||
private Object roundTrip(Object value) {
|
||||
byte[] bytes = serializer.serialize(value);
|
||||
assertNotNull(bytes);
|
||||
Object restored = serializer.deserialize(bytes);
|
||||
assertNotNull(restored);
|
||||
return restored;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package xiaozhi.common.utils;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class JsonUtilsTest {
|
||||
|
||||
@Test
|
||||
void parsesTypedMapsAndPreservesNestedCollectionShapes() {
|
||||
Map<String, Object> map = JsonUtils.parseMap(
|
||||
"{\"enabled\":true,\"nested\":{\"items\":[{\"name\":\"tool\"}]}}");
|
||||
Map<?, ?> nested = assertInstanceOf(Map.class, map.get("nested"));
|
||||
List<?> items = assertInstanceOf(List.class, nested.get("items"));
|
||||
Map<?, ?> item = assertInstanceOf(Map.class, items.get(0));
|
||||
|
||||
assertEquals(true, map.get("enabled"));
|
||||
assertEquals("tool", item.get("name"));
|
||||
|
||||
List<Map<String, Object>> maps = JsonUtils.parseMapList(
|
||||
"[{\"name\":\"first\",\"values\":[1,2]},{\"name\":\"second\"}]");
|
||||
assertEquals("first", maps.get(0).get("name"));
|
||||
assertEquals(List.of(1, 2), maps.get(0).get("values"));
|
||||
assertEquals("second", maps.get(1).get("name"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void checkedConvertersAreShallowMutableCopies() {
|
||||
List<Object> nested = new ArrayList<>(List.of(Map.of("name", "tool")));
|
||||
Map<String, Object> source = new LinkedHashMap<>();
|
||||
source.put("nested", nested);
|
||||
|
||||
Map<String, Object> map = JsonUtils.toStringObjectMap(source);
|
||||
List<Map<String, Object>> maps = JsonUtils.toStringObjectMapList(List.of(source));
|
||||
List<String> strings = JsonUtils.toList(Arrays.asList("a", null), String.class);
|
||||
|
||||
assertSame(nested, map.get("nested"));
|
||||
assertSame(nested, maps.get(0).get("nested"));
|
||||
map.put("enabled", true);
|
||||
maps.add(Map.of("name", "second"));
|
||||
strings.add("b");
|
||||
assertEquals(true, map.get("enabled"));
|
||||
assertEquals(2, maps.size());
|
||||
assertEquals(Arrays.asList("a", null, "b"), strings);
|
||||
}
|
||||
|
||||
@Test
|
||||
void preservesNullInputs() {
|
||||
assertNull(JsonUtils.parseMap(""));
|
||||
assertNull(JsonUtils.parseMapList(null));
|
||||
assertNull(JsonUtils.toStringObjectMap(null));
|
||||
assertNull(JsonUtils.toStringObjectMapList(null));
|
||||
assertNull(JsonUtils.toList(null, String.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsUnexpectedJsonShapesAndRuntimeTypes() {
|
||||
assertThrows(RuntimeException.class, () -> JsonUtils.parseMap("[]"));
|
||||
assertThrows(RuntimeException.class, () -> JsonUtils.parseMapList("{}"));
|
||||
assertThrows(ClassCastException.class, () -> JsonUtils.toStringObjectMap(List.of()));
|
||||
assertThrows(ClassCastException.class, () -> JsonUtils.toStringObjectMap(Map.of(1, "value")));
|
||||
assertThrows(ClassCastException.class, () -> JsonUtils.toStringObjectMapList(List.of("value")));
|
||||
assertThrows(ClassCastException.class, () -> JsonUtils.toStringObjectMapList(Map.of()));
|
||||
assertThrows(ClassCastException.class, () -> JsonUtils.toList(List.of(1), String.class));
|
||||
assertThrows(ClassCastException.class, () -> JsonUtils.toList(Map.of(), String.class));
|
||||
}
|
||||
}
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
package xiaozhi.modules.config.service.impl;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.mockito.ArgumentMatchers.anyMap;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import xiaozhi.common.redis.RedisKeys;
|
||||
import xiaozhi.common.redis.RedisUtils;
|
||||
import xiaozhi.modules.agent.dao.AgentVoicePrintDao;
|
||||
import xiaozhi.modules.agent.service.AgentContextProviderService;
|
||||
import xiaozhi.modules.agent.service.AgentMcpAccessPointService;
|
||||
import xiaozhi.modules.agent.service.AgentPluginMappingService;
|
||||
import xiaozhi.modules.agent.service.AgentService;
|
||||
import xiaozhi.modules.agent.service.AgentTemplateService;
|
||||
import xiaozhi.modules.correctword.service.CorrectWordFileService;
|
||||
import xiaozhi.modules.device.service.DeviceService;
|
||||
import xiaozhi.modules.model.service.ModelConfigService;
|
||||
import xiaozhi.modules.sys.dto.SysParamsDTO;
|
||||
import xiaozhi.modules.sys.service.SysParamsService;
|
||||
import xiaozhi.modules.timbre.service.TimbreService;
|
||||
import xiaozhi.modules.voiceclone.service.VoiceCloneService;
|
||||
|
||||
class ConfigServiceImplTest {
|
||||
|
||||
@Test
|
||||
void cachedServerConfigIsCheckedAsAStringKeyedMapWithoutChangingNestedValues() {
|
||||
RedisUtils redisUtils = mock(RedisUtils.class);
|
||||
Map<String, Object> nested = new HashMap<>();
|
||||
nested.put("enabled", true);
|
||||
Map<String, Object> cached = new HashMap<>();
|
||||
cached.put("features", nested);
|
||||
when(redisUtils.get(RedisKeys.getServerConfigKey())).thenReturn(cached);
|
||||
|
||||
ConfigServiceImpl service = newService(mock(SysParamsService.class), redisUtils);
|
||||
|
||||
Map<String, Object> result = service.getConfig(true);
|
||||
|
||||
assertNotSame(cached, result);
|
||||
assertSame(nested, result.get("features"));
|
||||
assertEquals(true, ((Map<?, ?>) result.get("features")).get("enabled"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void nestedSystemParametersStillShareAndPopulateTheSameConfigBranch() {
|
||||
SysParamsService sysParamsService = mock(SysParamsService.class);
|
||||
SysParamsDTO enabled = parameter("server.features.enabled", "true", "boolean");
|
||||
SysParamsDTO labels = parameter("server.features.labels", "first;second", "array");
|
||||
when(sysParamsService.list(anyMap())).thenReturn(List.of(enabled, labels));
|
||||
ConfigServiceImpl service = newService(sysParamsService, mock(RedisUtils.class));
|
||||
Map<String, Object> config = new HashMap<>();
|
||||
|
||||
Object returned = ReflectionTestUtils.invokeMethod(service, "buildConfig", config);
|
||||
|
||||
assertSame(config, returned);
|
||||
Map<?, ?> server = assertInstanceOf(Map.class, config.get("server"));
|
||||
Map<?, ?> features = assertInstanceOf(Map.class, server.get("features"));
|
||||
assertEquals(true, features.get("enabled"));
|
||||
assertEquals(List.of("first", "second"), features.get("labels"));
|
||||
}
|
||||
|
||||
private static SysParamsDTO parameter(String code, String value, String type) {
|
||||
SysParamsDTO parameter = new SysParamsDTO();
|
||||
parameter.setParamCode(code);
|
||||
parameter.setParamValue(value);
|
||||
parameter.setValueType(type);
|
||||
return parameter;
|
||||
}
|
||||
|
||||
private static ConfigServiceImpl newService(SysParamsService sysParamsService, RedisUtils redisUtils) {
|
||||
return new ConfigServiceImpl(
|
||||
sysParamsService,
|
||||
mock(DeviceService.class),
|
||||
mock(ModelConfigService.class),
|
||||
mock(AgentService.class),
|
||||
mock(AgentTemplateService.class),
|
||||
redisUtils,
|
||||
mock(TimbreService.class),
|
||||
mock(AgentPluginMappingService.class),
|
||||
mock(AgentMcpAccessPointService.class),
|
||||
mock(AgentContextProviderService.class),
|
||||
mock(VoiceCloneService.class),
|
||||
mock(AgentVoicePrintDao.class),
|
||||
mock(CorrectWordFileService.class));
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
package xiaozhi.modules.sys.service.impl;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotSame;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import xiaozhi.common.redis.RedisKeys;
|
||||
import xiaozhi.common.redis.RedisUtils;
|
||||
import xiaozhi.modules.sys.dao.SysUserDao;
|
||||
import xiaozhi.modules.sys.vo.SysDictDataItem;
|
||||
|
||||
class SysDictDataServiceImplTest {
|
||||
|
||||
@Test
|
||||
void cachedDictionaryItemsAreCheckedAndReturnedAsDtos() {
|
||||
RedisUtils redisUtils = mock(RedisUtils.class);
|
||||
SysDictDataItem item = new SysDictDataItem();
|
||||
item.setName("enabled");
|
||||
item.setKey("1");
|
||||
List<SysDictDataItem> cached = new ArrayList<>(List.of(item));
|
||||
when(redisUtils.get(RedisKeys.getDictDataByTypeKey("status"))).thenReturn(cached);
|
||||
SysDictDataServiceImpl service = new SysDictDataServiceImpl(mock(SysUserDao.class), redisUtils);
|
||||
|
||||
List<SysDictDataItem> result = service.getDictDataByType("status");
|
||||
|
||||
assertNotSame(cached, result);
|
||||
assertEquals(List.of(item), result);
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package xiaozhi.modules.sys.service.impl;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import xiaozhi.common.constant.Constant;
|
||||
import xiaozhi.modules.agent.service.AgentPluginMappingService;
|
||||
import xiaozhi.modules.sys.dao.SysParamsDao;
|
||||
import xiaozhi.modules.sys.redis.SysParamsRedis;
|
||||
|
||||
class SysParamsServiceImplTest {
|
||||
|
||||
@Test
|
||||
void disablingAddressBookStillDeletesItsSystemPlugin() {
|
||||
SysParamsRedis sysParamsRedis = mock(SysParamsRedis.class);
|
||||
AgentPluginMappingService pluginMappingService = mock(AgentPluginMappingService.class);
|
||||
SysParamsDao sysParamsDao = mock(SysParamsDao.class);
|
||||
SysParamsServiceImpl service = new SysParamsServiceImpl(sysParamsRedis, pluginMappingService);
|
||||
ReflectionTestUtils.setField(service, "baseDao", sysParamsDao);
|
||||
|
||||
String currentConfig = "{\"features\":{\"addressBook\":{\"enabled\":true}}}";
|
||||
String newConfig = "{\"features\":{\"addressBook\":{\"enabled\":false}}}";
|
||||
when(sysParamsDao.getValueByCode(Constant.SYSTEM_WEB_MENU)).thenReturn(currentConfig);
|
||||
when(sysParamsDao.updateValueByCode(Constant.SYSTEM_WEB_MENU, newConfig)).thenReturn(1);
|
||||
|
||||
service.updateSystemWebMenu(newConfig);
|
||||
|
||||
verify(pluginMappingService).deleteByPluginId("SYSTEM_PLUGIN_CALL_DEVICE");
|
||||
verify(sysParamsDao).updateValueByCode(Constant.SYSTEM_WEB_MENU, newConfig);
|
||||
verify(sysParamsRedis).set(Constant.SYSTEM_WEB_MENU, newConfig);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user