mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-23 23:53:55 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
942d55118b | ||
|
|
f47f3b050b | ||
|
|
e3453b1a87 | ||
|
|
d760465408 | ||
|
|
bdb2538239 | ||
|
|
ea6c144e43 | ||
|
|
a79aa455d6 | ||
|
|
6e92d169ec | ||
|
|
ed89c05245 | ||
|
|
b500d1c6bd | ||
|
|
0598b09629 | ||
|
|
e4907121f4 | ||
|
|
2a618d2f8f |
@@ -33,6 +33,7 @@
|
||||
<aliyun-sms-version>4.1.0</aliyun-sms-version>
|
||||
<okio-version>3.4.0</okio-version>
|
||||
<skipTests>true</skipTests>
|
||||
<argLine></argLine>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -272,11 +273,22 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<configuration>
|
||||
<proc>full</proc>
|
||||
<compilerArgs>
|
||||
<arg>-Xlint:deprecation,unchecked</arg>
|
||||
</compilerArgs>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-surefire-plugin</artifactId>
|
||||
<configuration>
|
||||
<skipTests>${skipTests}</skipTests>
|
||||
<argLine>@{argLine} -Xshare:off -javaagent:"${settings.localRepository}/org/mockito/mockito-core/${mockito.version}/mockito-core-${mockito.version}.jar"</argLine>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
|
||||
+1
-1
@@ -67,7 +67,7 @@ public class DataFilterInterceptor implements InnerInterceptor {
|
||||
private String getSelect(String buildSql, DataScope scope) {
|
||||
try {
|
||||
Select select = (Select) CCJSqlParserUtil.parse(buildSql);
|
||||
PlainSelect plainSelect = (PlainSelect) select.getSelectBody();
|
||||
PlainSelect plainSelect = select.getPlainSelect();
|
||||
|
||||
Expression expression = plainSelect.getWhere();
|
||||
if (expression == null) {
|
||||
|
||||
@@ -81,8 +81,8 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
|
||||
// 处理排序字段
|
||||
if (orderField instanceof String) {
|
||||
orderFields.add((String) orderField);
|
||||
} else if (orderField instanceof List) {
|
||||
orderFields.addAll((List<String>) orderField);
|
||||
} else if (orderField instanceof List<?> fields) {
|
||||
fields.forEach(field -> orderFields.add(String.class.cast(field)));
|
||||
}
|
||||
|
||||
// 有排序字段则排序
|
||||
@@ -142,11 +142,12 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
|
||||
return SqlHelper.retBool(result);
|
||||
}
|
||||
|
||||
protected Class<M> currentMapperClass() {
|
||||
return (Class<M>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseServiceImpl.class, 0);
|
||||
protected Class<?> currentMapperClass() {
|
||||
return ReflectionKit.getSuperClassGenericType(this.getClass(), BaseServiceImpl.class, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Class<T> currentModelClass() {
|
||||
return (Class<T>) ReflectionKit.getSuperClassGenericType(this.getClass(), BaseServiceImpl.class, 1);
|
||||
}
|
||||
@@ -226,6 +227,6 @@ public abstract class BaseServiceImpl<M extends BaseMapper<T>, T> implements Bas
|
||||
|
||||
@Override
|
||||
public boolean deleteBatchIds(Collection<? extends Serializable> idList) {
|
||||
return SqlHelper.retBool(baseDao.deleteBatchIds(idList));
|
||||
return SqlHelper.retBool(baseDao.deleteByIds(idList));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import xiaozhi.common.utils.ConvertUtils;
|
||||
public abstract class CrudServiceImpl<M extends BaseMapper<T>, T, D> extends BaseServiceImpl<M, T>
|
||||
implements CrudService<T, D> {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Class<D> currentDtoClass() {
|
||||
return (Class<D>) ReflectionKit.getSuperClassGenericType(getClass(), CrudServiceImpl.class, 2);
|
||||
}
|
||||
@@ -70,6 +71,6 @@ public abstract class CrudServiceImpl<M extends BaseMapper<T>, T, D> extends Bas
|
||||
|
||||
@Override
|
||||
public void delete(Serializable[] ids) {
|
||||
baseDao.deleteBatchIds(Arrays.asList(ids));
|
||||
baseDao.deleteByIds(Arrays.asList(ids));
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -75,11 +75,11 @@ public class SensitiveDataUtils {
|
||||
Object value = jsonObject.get(key);
|
||||
|
||||
if (SENSITIVE_FIELDS.contains(key.toLowerCase()) && value instanceof String) {
|
||||
result.put(key, maskMiddle((String) value));
|
||||
result.set(key, maskMiddle((String) value));
|
||||
} else if (value instanceof JSONObject) {
|
||||
result.put(key, maskSensitiveFields((JSONObject) value));
|
||||
result.set(key, maskSensitiveFields((JSONObject) value));
|
||||
} else {
|
||||
result.put(key, value);
|
||||
result.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,10 +22,10 @@ public class SqlFilter {
|
||||
return null;
|
||||
}
|
||||
// 去掉'|"|;|\字符
|
||||
str = StringUtils.replace(str, "'", "");
|
||||
str = StringUtils.replace(str, "\"", "");
|
||||
str = StringUtils.replace(str, ";", "");
|
||||
str = StringUtils.replace(str, "\\", "");
|
||||
str = str.replace("'", "");
|
||||
str = str.replace("\"", "");
|
||||
str = str.replace(";", "");
|
||||
str = str.replace("\\", "");
|
||||
|
||||
// 转换成小写
|
||||
str = str.toLowerCase();
|
||||
|
||||
+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);
|
||||
|
||||
@@ -39,7 +39,7 @@ public class AgentDTO {
|
||||
private String systemPrompt;
|
||||
|
||||
@Schema(description = "总结记忆", example = "构建可生长的动态记忆网络,在有限空间内保留关键信息的同时,智能维护信息演变轨迹\n" +
|
||||
"根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", required = false)
|
||||
"根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private String summaryMemory;
|
||||
|
||||
@Schema(description = "最后连接时间", example = "2024-03-20 10:00:00")
|
||||
|
||||
@@ -14,6 +14,6 @@ public class AgentMemoryDTO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Schema(description = "总结记忆", example = "构建可生长的动态记忆网络,在有限空间内保留关键信息的同时,智能维护信息演变轨迹\n" +
|
||||
"根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", required = false)
|
||||
"根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private String summaryMemory;
|
||||
}
|
||||
@@ -39,10 +39,10 @@ public class AgentUpdateDTO implements Serializable {
|
||||
@Schema(description = "小模型标识", example = "slm_model_02", nullable = true)
|
||||
private String slmModelId;
|
||||
|
||||
@Schema(description = "VLLM模型标识", example = "vllm_model_02", required = false)
|
||||
@Schema(description = "VLLM模型标识", example = "vllm_model_02", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private String vllmModelId;
|
||||
|
||||
@Schema(description = "语音合成模型标识", example = "tts_model_02", required = false)
|
||||
@Schema(description = "语音合成模型标识", example = "tts_model_02", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private String ttsModelId;
|
||||
|
||||
@Schema(description = "音色标识", example = "voice_02", nullable = true)
|
||||
|
||||
@@ -74,7 +74,7 @@ public class AgentEntity {
|
||||
private String systemPrompt;
|
||||
|
||||
@Schema(description = "总结记忆", example = "构建可生长的动态记忆网络,在有限空间内保留关键信息的同时,智能维护信息演变轨迹\n" +
|
||||
"根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", required = false)
|
||||
"根据对话记录,总结user的重要信息,以便在未来的对话中提供更个性化的服务", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
|
||||
private String summaryMemory;
|
||||
|
||||
@Schema(description = "语言编码")
|
||||
|
||||
+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;
|
||||
|
||||
+9
-9
@@ -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());
|
||||
|
||||
+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"));
|
||||
|
||||
+1
-1
@@ -133,7 +133,7 @@ public class AgentTagServiceImpl extends BaseServiceImpl<AgentTagDao, AgentTagEn
|
||||
}
|
||||
|
||||
if (tagIds != null && !tagIds.isEmpty()) {
|
||||
List<AgentTagEntity> tagIdEntities = baseDao.selectBatchIds(tagIds);
|
||||
List<AgentTagEntity> tagIdEntities = baseDao.selectByIds(tagIds);
|
||||
for (AgentTagEntity tag : tagIdEntities) {
|
||||
if (tag != null && (currentTagNames.contains(tag.getTagName()) ||
|
||||
newTagNames.contains(tag.getTagName()))) {
|
||||
|
||||
@@ -10,7 +10,7 @@ public interface ConfigService {
|
||||
* @param isCache 是否缓存
|
||||
* @return 配置信息
|
||||
*/
|
||||
Object getConfig(Boolean isCache);
|
||||
Map<String, Object> getConfig(Boolean isCache);
|
||||
|
||||
/**
|
||||
* 获取智能体模型配置
|
||||
|
||||
+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
|
||||
|
||||
@@ -12,6 +12,11 @@ import xiaozhi.modules.device.entity.DeviceAddressBookEntity;
|
||||
@Mapper
|
||||
public interface DeviceAddressBookDao extends BaseMapper<DeviceAddressBookEntity> {
|
||||
|
||||
/**
|
||||
* 新增设备通讯录记录
|
||||
*/
|
||||
int insertAddressBook(DeviceAddressBookEntity entity);
|
||||
|
||||
/**
|
||||
* 获取设备通讯录列表
|
||||
*/
|
||||
|
||||
+1
-1
@@ -167,7 +167,7 @@ public class DeviceAddressBookServiceImpl implements DeviceAddressBookService {
|
||||
alias = generateUniqueAlias(macAddress, targetMac, alias);
|
||||
entity.setAlias(alias);
|
||||
entity.setHasPermission(hasPermission);
|
||||
deviceAddressBookDao.insert(entity);
|
||||
deviceAddressBookDao.insertAddressBook(entity);
|
||||
} else {
|
||||
if (alias != null) {
|
||||
updateAlias(macAddress, targetMac, alias);
|
||||
|
||||
+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;
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ public class OtaServiceImpl extends BaseServiceImpl<OtaDao, OtaEntity> implement
|
||||
|
||||
@Override
|
||||
public void delete(String[] ids) {
|
||||
baseDao.deleteBatchIds(Arrays.asList(ids));
|
||||
baseDao.deleteByIds(Arrays.asList(ids));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+10
-9
@@ -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;
|
||||
|
||||
+3
-9
@@ -116,13 +116,11 @@ public class OpenAIStyleLLMServiceImpl implements LLMService {
|
||||
Map<String, Object> requestBody = new HashMap<>();
|
||||
requestBody.put("model", model != null ? model : "gpt-3.5-turbo");
|
||||
|
||||
Map<String, Object>[] messages = new Map[1];
|
||||
Map<String, Object> message = new HashMap<>();
|
||||
message.put("role", "user");
|
||||
message.put("content", prompt);
|
||||
messages[0] = message;
|
||||
|
||||
requestBody.put("messages", messages);
|
||||
requestBody.put("messages", List.of(message));
|
||||
requestBody.put("temperature", temperature != null ? temperature : 0.7);
|
||||
requestBody.put("max_tokens", maxTokens != null ? maxTokens : 2000);
|
||||
|
||||
@@ -212,13 +210,11 @@ public class OpenAIStyleLLMServiceImpl implements LLMService {
|
||||
Map<String, Object> requestBody = new HashMap<>();
|
||||
requestBody.put("model", model != null ? model : "gpt-3.5-turbo");
|
||||
|
||||
Map<String, Object>[] messages = new Map[1];
|
||||
Map<String, Object> message = new HashMap<>();
|
||||
message.put("role", "user");
|
||||
message.put("content", prompt);
|
||||
messages[0] = message;
|
||||
|
||||
requestBody.put("messages", messages);
|
||||
requestBody.put("messages", List.of(message));
|
||||
requestBody.put("temperature", 0.2);
|
||||
requestBody.put("max_tokens", 2000);
|
||||
|
||||
@@ -368,13 +364,11 @@ public class OpenAIStyleLLMServiceImpl implements LLMService {
|
||||
Map<String, Object> requestBody = new HashMap<>();
|
||||
requestBody.put("model", model != null ? model : "gpt-3.5-turbo");
|
||||
|
||||
Map<String, Object>[] messages = new Map[1];
|
||||
Map<String, Object> message = new HashMap<>();
|
||||
message.put("role", "user");
|
||||
message.put("content", prompt);
|
||||
messages[0] = message;
|
||||
|
||||
requestBody.put("messages", messages);
|
||||
requestBody.put("messages", List.of(message));
|
||||
requestBody.put("temperature", 0.3);
|
||||
requestBody.put("max_tokens", 50);
|
||||
|
||||
|
||||
+5
-5
@@ -362,14 +362,14 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
|
||||
if (SensitiveDataUtils.isSensitiveField(key)) {
|
||||
|
||||
if (value instanceof String && !SensitiveDataUtils.isMaskedValue((String) value)) {
|
||||
updatedJson.put(key, value);
|
||||
updatedJson.set(key, value);
|
||||
}
|
||||
} else if (value instanceof JSONObject) {
|
||||
// 递归处理嵌套JSON
|
||||
mergeJson(updatedJson, key, (JSONObject) value);
|
||||
} else {
|
||||
// 非敏感字段直接更新
|
||||
updatedJson.put(key, value);
|
||||
updatedJson.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,7 +405,7 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
|
||||
|
||||
// 如果 original 中不存在 key,创建一个新的 JSON 对象
|
||||
if (!original.containsKey(key)) {
|
||||
original.put(key, new JSONObject());
|
||||
original.set(key, new JSONObject());
|
||||
}
|
||||
|
||||
// 获取 original 中的子对象
|
||||
@@ -420,7 +420,7 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
|
||||
log.warn("mergeJson: key '{}' 的值不是 JSONObject 类型 (实际类型:{}),将创建新对象",
|
||||
key, originalValue != null ? originalValue.getClass().getSimpleName() : "null");
|
||||
originalChild = new JSONObject();
|
||||
original.put(key, originalChild);
|
||||
original.set(key, originalChild);
|
||||
}
|
||||
|
||||
for (String childKey : updated.keySet()) {
|
||||
@@ -430,7 +430,7 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
|
||||
} else {
|
||||
if (!SensitiveDataUtils.isSensitiveField(childKey) ||
|
||||
(childValue instanceof String && !isMaskedValue((String) childValue))) {
|
||||
originalChild.put(childKey, childValue);
|
||||
originalChild.set(childKey, childValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -164,7 +164,7 @@ public class ModelProviderServiceImpl extends BaseServiceImpl<ModelProviderDao,
|
||||
|
||||
@Override
|
||||
public void delete(List<String> ids) {
|
||||
if (modelProviderDao.deleteBatchIds(ids) == 0) {
|
||||
if (modelProviderDao.deleteByIds(ids) == 0) {
|
||||
throw new RenException(ErrorCode.DELETE_DATA_FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,16 +4,19 @@ import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.shiro.mgt.SecurityManager;
|
||||
import org.apache.shiro.session.mgt.SessionManager;
|
||||
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
|
||||
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
|
||||
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
|
||||
import org.apache.shiro.web.config.ShiroFilterConfiguration;
|
||||
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
|
||||
import org.apache.shiro.web.mgt.WebSecurityManager;
|
||||
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Role;
|
||||
|
||||
import jakarta.servlet.Filter;
|
||||
import xiaozhi.modules.security.oauth2.Oauth2Filter;
|
||||
@@ -39,7 +42,7 @@ public class ShiroConfig {
|
||||
}
|
||||
|
||||
@Bean("securityManager")
|
||||
public SecurityManager securityManager(Oauth2Realm oAuth2Realm, SessionManager sessionManager) {
|
||||
public WebSecurityManager securityManager(Oauth2Realm oAuth2Realm, SessionManager sessionManager) {
|
||||
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
|
||||
securityManager.setRealm(oAuth2Realm);
|
||||
securityManager.setSessionManager(sessionManager);
|
||||
@@ -48,7 +51,8 @@ public class ShiroConfig {
|
||||
}
|
||||
|
||||
@Bean("shiroFilter")
|
||||
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager, SysParamsService sysParamsService) {
|
||||
public static ShiroFilterFactoryBean shirFilter(@Lazy WebSecurityManager securityManager,
|
||||
@Lazy SysParamsService sysParamsService) {
|
||||
ShiroFilterConfiguration config = new ShiroFilterConfiguration();
|
||||
config.setFilterOncePerRequest(true);
|
||||
|
||||
@@ -101,12 +105,14 @@ public class ShiroConfig {
|
||||
}
|
||||
|
||||
@Bean("lifecycleBeanPostProcessor")
|
||||
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
|
||||
public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
|
||||
return new LifecycleBeanPostProcessor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
|
||||
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
|
||||
public static AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(
|
||||
@Lazy WebSecurityManager securityManager) {
|
||||
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
|
||||
advisor.setSecurityManager(securityManager);
|
||||
return advisor;
|
||||
|
||||
@@ -31,7 +31,7 @@ public class SysUserDTO implements Serializable {
|
||||
@NotNull(message = "{id.require}", groups = UpdateGroup.class)
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "用户名", required = true)
|
||||
@Schema(description = "用户名", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank(message = "{sysuser.username.require}", groups = DefaultGroup.class)
|
||||
private String username;
|
||||
|
||||
@@ -40,14 +40,14 @@ public class SysUserDTO implements Serializable {
|
||||
@NotBlank(message = "{sysuser.password.require}", groups = AddGroup.class)
|
||||
private String password;
|
||||
|
||||
@Schema(description = "姓名", required = true)
|
||||
@Schema(description = "姓名", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotBlank(message = "{sysuser.realname.require}", groups = DefaultGroup.class)
|
||||
private String realName;
|
||||
|
||||
@Schema(description = "头像")
|
||||
private String headUrl;
|
||||
|
||||
@Schema(description = "性别 0:男 1:女 2:保密", required = true)
|
||||
@Schema(description = "性别 0:男 1:女 2:保密", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@Range(min = 0, max = 2, message = "{sysuser.gender.range}", groups = DefaultGroup.class)
|
||||
private Integer gender;
|
||||
|
||||
@@ -58,11 +58,11 @@ public class SysUserDTO implements Serializable {
|
||||
@Schema(description = "手机号")
|
||||
private String mobile;
|
||||
|
||||
@Schema(description = "部门ID", required = true)
|
||||
@Schema(description = "部门ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@NotNull(message = "{sysuser.deptId.require}", groups = DefaultGroup.class)
|
||||
private Long deptId;
|
||||
|
||||
@Schema(description = "状态 0:停用 1:正常", required = true)
|
||||
@Schema(description = "状态 0:停用 1:正常", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
@Range(min = 0, max = 1, message = "{sysuser.status.range}", groups = DefaultGroup.class)
|
||||
private Integer status;
|
||||
|
||||
|
||||
+3
-2
@@ -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;
|
||||
@@ -138,7 +139,7 @@ public class SysDictDataServiceImpl extends BaseServiceImpl<SysDictDataDao, SysD
|
||||
|
||||
// 设置更新者和创建者名称
|
||||
if (!userIds.isEmpty()) {
|
||||
List<SysUserEntity> sysUserEntities = sysUserDao.selectBatchIds(userIds);
|
||||
List<SysUserEntity> sysUserEntities = sysUserDao.selectByIds(userIds);
|
||||
// 把List转成Map,Map<Long, String>
|
||||
Map<Long, String> userNameMap = sysUserEntities.stream().collect(Collectors.toMap(SysUserEntity::getId,
|
||||
SysUserEntity::getUsername, (existing, replacement) -> existing));
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
+1
-1
@@ -128,7 +128,7 @@ public class SysDictTypeServiceImpl extends BaseServiceImpl<SysDictTypeDao, SysD
|
||||
|
||||
// 设置更新者和创建者名称
|
||||
if (!userIds.isEmpty()) {
|
||||
List<SysUserEntity> sysUserEntities = sysUserDao.selectBatchIds(userIds);
|
||||
List<SysUserEntity> sysUserEntities = sysUserDao.selectByIds(userIds);
|
||||
// 把List转成Map,Map<Long, String>
|
||||
Map<Long, String> userNameMap = sysUserEntities.stream().collect(Collectors.toMap(SysUserEntity::getId,
|
||||
SysUserEntity::getUsername, (existing, replacement) -> existing));
|
||||
|
||||
+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插件
|
||||
|
||||
+1
-1
@@ -117,7 +117,7 @@ public class TimbreServiceImpl extends BaseServiceImpl<TimbreDao, TimbreEntity>
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(String[] ids) {
|
||||
baseDao.deleteBatchIds(Arrays.asList(ids));
|
||||
baseDao.deleteByIds(Arrays.asList(ids));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+1
-1
@@ -127,7 +127,7 @@ public class VoiceCloneServiceImpl extends BaseServiceImpl<VoiceCloneDao, VoiceC
|
||||
|
||||
@Override
|
||||
public void delete(String[] ids) {
|
||||
baseDao.deleteBatchIds(Arrays.asList(ids));
|
||||
baseDao.deleteByIds(Arrays.asList(ids));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,20 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<!-- 启用JansiConsoleAppender以确保控制台输出有颜色 -->
|
||||
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
|
||||
|
||||
<!-- 确保日志目录存在 -->
|
||||
<timestamp key="bySecond" datePattern="yyyyMMdd'T'HHmmss"/>
|
||||
|
||||
<!-- 定义日志文件存储位置 -->
|
||||
<property name="LOG_HOME" value="./logs" />
|
||||
|
||||
<!-- 使用自定义的初始化监听器确保日志目录存在 -->
|
||||
<define name="LOGBACK_DIR_CHECK" class="ch.qos.logback.core.property.FileExistsPropertyDefiner">
|
||||
<path>${LOG_HOME}</path>
|
||||
<createIfMissing>true</createIfMissing>
|
||||
</define>
|
||||
|
||||
<!-- 引入Spring Boot默认配置 -->
|
||||
<include resource="org/springframework/boot/logging/logback/defaults.xml" />
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
WHERE mac_address = #{macAddress} AND target_mac = #{targetMac}
|
||||
</update>
|
||||
|
||||
<insert id="insert">
|
||||
<insert id="insertAddressBook">
|
||||
INSERT INTO ai_device_address_book (mac_address, target_mac, alias, has_permission, creator, create_date, updater, update_date)
|
||||
VALUES (#{macAddress}, #{targetMac}, #{alias}, #{hasPermission}, #{creator}, NOW(), #{updater}, NOW())
|
||||
</insert>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
+20
-1
@@ -22,6 +22,7 @@ import java.util.function.BiFunction;
|
||||
|
||||
import org.apache.ibatis.binding.MapperMethod;
|
||||
import org.apache.ibatis.executor.BatchResult;
|
||||
import org.apache.ibatis.logging.nologging.NoLoggingImpl;
|
||||
import org.apache.ibatis.mapping.MappedStatement;
|
||||
import org.apache.ibatis.session.ExecutorType;
|
||||
import org.apache.ibatis.session.SqlSession;
|
||||
@@ -57,7 +58,8 @@ class BaseServiceImplTest {
|
||||
when(sqlSessionFactory.openSession(ExecutorType.BATCH)).thenReturn(sqlSession);
|
||||
when(sqlSession.insert(eq(INSERT_STATEMENT), any(TestEntity.class))).thenReturn(1);
|
||||
when(sqlSession.flushStatements())
|
||||
.thenReturn(List.of(batchResult(1, 1)), List.of(batchResult(1)));
|
||||
.thenReturn(List.of(batchResult(1, 1)))
|
||||
.thenReturn(List.of(batchResult(1)));
|
||||
|
||||
TestEntity first = new TestEntity(1L);
|
||||
TestEntity second = new TestEntity(2L);
|
||||
@@ -126,6 +128,19 @@ class BaseServiceImplTest {
|
||||
verifyNoInteractions(sqlSessionFactory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deleteBatchIdsDelegatesToCompatibleApiAndPreservesAffectedRowResult() {
|
||||
TestMapper mapper = mock(TestMapper.class);
|
||||
List<Long> ids = List.of(1L, 2L);
|
||||
service.baseDao = mapper;
|
||||
when(mapper.deleteByIds(ids)).thenReturn(2).thenReturn(0);
|
||||
|
||||
assertTrue(service.deleteBatchIds(ids));
|
||||
assertFalse(service.deleteBatchIds(ids));
|
||||
|
||||
verify(mapper, times(2)).deleteByIds(ids);
|
||||
}
|
||||
|
||||
@Test
|
||||
void activeTransactionSynchronizationUsesTransactionAwareCommitAndLifecycle() {
|
||||
SqlSessionFactory sqlSessionFactory = mock(SqlSessionFactory.class);
|
||||
@@ -170,6 +185,10 @@ class BaseServiceImplTest {
|
||||
}
|
||||
|
||||
private static class TestService extends BaseServiceImpl<TestMapper, TestEntity> {
|
||||
private TestService() {
|
||||
log = new NoLoggingImpl(TestService.class.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSqlStatement(SqlMethod sqlMethod) {
|
||||
return switch (sqlMethod) {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package xiaozhi.modules.device;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
@@ -11,6 +10,8 @@ import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import xiaozhi.common.exception.ErrorCode;
|
||||
import xiaozhi.common.exception.RenException;
|
||||
import xiaozhi.common.redis.RedisUtils;
|
||||
import xiaozhi.modules.sys.dto.SysUserDTO;
|
||||
import xiaozhi.modules.sys.service.SysUserService;
|
||||
@@ -27,11 +28,13 @@ public class DeviceTest {
|
||||
private SysUserService sysUserService;
|
||||
|
||||
@Test
|
||||
public void testSaveUser() {
|
||||
public void testRejectWeakPassword() {
|
||||
SysUserDTO userDTO = new SysUserDTO();
|
||||
userDTO.setUsername("test");
|
||||
userDTO.setPassword(UUID.randomUUID().toString());
|
||||
sysUserService.save(userDTO);
|
||||
userDTO.setPassword("weak-password-123");
|
||||
|
||||
RenException exception = Assertions.assertThrows(RenException.class, () -> sysUserService.save(userDTO));
|
||||
Assertions.assertEquals(ErrorCode.PASSWORD_WEAK_ERROR, exception.getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
package xiaozhi.modules.device.service.impl;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import xiaozhi.common.redis.RedisUtils;
|
||||
import xiaozhi.modules.device.dao.DeviceAddressBookDao;
|
||||
import xiaozhi.modules.device.entity.DeviceAddressBookEntity;
|
||||
import xiaozhi.modules.device.service.DeviceService;
|
||||
import xiaozhi.modules.sys.service.SysParamsService;
|
||||
|
||||
class DeviceAddressBookServiceImplTest {
|
||||
|
||||
@Test
|
||||
void newEntryUsesTheDedicatedAddressBookInsertMapping() {
|
||||
DeviceAddressBookDao addressBookDao = mock(DeviceAddressBookDao.class);
|
||||
RedisUtils redisUtils = mock(RedisUtils.class);
|
||||
DeviceService deviceService = mock(DeviceService.class);
|
||||
SysParamsService sysParamsService = mock(SysParamsService.class);
|
||||
DeviceAddressBookServiceImpl service = new DeviceAddressBookServiceImpl(
|
||||
addressBookDao, redisUtils, deviceService, sysParamsService);
|
||||
when(addressBookDao.selectOne(any())).thenReturn(null);
|
||||
when(addressBookDao.selectList(null)).thenReturn(List.of());
|
||||
|
||||
service.saveOrUpdate("00:11:22:33:44:55", "00:11:22:33:44:66", "living-room", true);
|
||||
|
||||
ArgumentCaptor<DeviceAddressBookEntity> entityCaptor = ArgumentCaptor.forClass(DeviceAddressBookEntity.class);
|
||||
verify(addressBookDao).insertAddressBook(entityCaptor.capture());
|
||||
DeviceAddressBookEntity entity = entityCaptor.getValue();
|
||||
assertEquals("00:11:22:33:44:55", entity.getMacAddress());
|
||||
assertEquals("00:11:22:33:44:66", entity.getTargetMac());
|
||||
assertEquals("living-room", entity.getAlias());
|
||||
assertTrue(entity.getHasPermission());
|
||||
verify(addressBookDao, never()).insert(any(DeviceAddressBookEntity.class));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package xiaozhi.modules.security.config;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import org.apache.shiro.session.mgt.SessionManager;
|
||||
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
|
||||
import org.apache.shiro.web.mgt.WebSecurityManager;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.config.BeanPostProcessor;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Role;
|
||||
|
||||
import xiaozhi.modules.security.oauth2.Oauth2Realm;
|
||||
import xiaozhi.modules.sys.service.SysParamsService;
|
||||
|
||||
class ShiroConfigTest {
|
||||
|
||||
@Test
|
||||
void beanPostProcessorFactoriesDoNotInstantiateShiroConfigOrBusinessDependenciesEarly() throws Exception {
|
||||
Method lifecycleFactory = ShiroConfig.class.getDeclaredMethod("lifecycleBeanPostProcessor");
|
||||
Method filterFactory = ShiroConfig.class.getDeclaredMethod(
|
||||
"shirFilter", WebSecurityManager.class, SysParamsService.class);
|
||||
|
||||
assertTrue(Modifier.isStatic(lifecycleFactory.getModifiers()));
|
||||
assertTrue(BeanPostProcessor.class.isAssignableFrom(ShiroFilterFactoryBean.class));
|
||||
assertTrue(Modifier.isStatic(filterFactory.getModifiers()));
|
||||
Lazy securityManagerLazy = filterFactory.getParameters()[0].getAnnotation(Lazy.class);
|
||||
Lazy sysParamsServiceLazy = filterFactory.getParameters()[1].getAnnotation(Lazy.class);
|
||||
assertNotNull(securityManagerLazy);
|
||||
assertNotNull(sysParamsServiceLazy);
|
||||
assertTrue(securityManagerLazy.value());
|
||||
assertTrue(sysParamsServiceLazy.value());
|
||||
}
|
||||
|
||||
@Test
|
||||
void authorizationAdvisorIsInfrastructureAndDefersItsSecurityManager() throws Exception {
|
||||
Method advisorFactory = ShiroConfig.class.getDeclaredMethod(
|
||||
"authorizationAttributeSourceAdvisor", WebSecurityManager.class);
|
||||
|
||||
assertTrue(Modifier.isStatic(advisorFactory.getModifiers()));
|
||||
Lazy securityManagerLazy = advisorFactory.getParameters()[0].getAnnotation(Lazy.class);
|
||||
assertNotNull(securityManagerLazy);
|
||||
assertTrue(securityManagerLazy.value());
|
||||
assertEquals(BeanDefinition.ROLE_INFRASTRUCTURE, advisorFactory.getAnnotation(Role.class).value());
|
||||
}
|
||||
|
||||
@Test
|
||||
void securityManagerRetainsTheWebSecurityContractRequiredByShiroFilter() throws Exception {
|
||||
Method securityManagerFactory = ShiroConfig.class.getDeclaredMethod(
|
||||
"securityManager", Oauth2Realm.class, SessionManager.class);
|
||||
|
||||
assertEquals(WebSecurityManager.class, securityManagerFactory.getReturnType());
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,23 @@
|
||||
package xiaozhi.modules.sys;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.bean.override.mockito.MockitoBean;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import xiaozhi.common.exception.ErrorCode;
|
||||
import xiaozhi.common.exception.RenException;
|
||||
import xiaozhi.modules.security.controller.LoginController;
|
||||
import xiaozhi.modules.security.dto.LoginDTO;
|
||||
import xiaozhi.modules.security.dto.SmsVerificationDTO;
|
||||
import xiaozhi.modules.sys.dto.RetrievePasswordDTO;
|
||||
import xiaozhi.modules.sys.service.SysUserService;
|
||||
|
||||
@Slf4j
|
||||
@SpringBootTest
|
||||
@@ -19,12 +27,19 @@ class loginControllerTest {
|
||||
@Autowired
|
||||
LoginController loginController;
|
||||
|
||||
@MockitoBean
|
||||
SysUserService sysUserService;
|
||||
|
||||
@Test
|
||||
public void testRegister() {
|
||||
when(sysUserService.getAllowUserRegister()).thenReturn(false);
|
||||
|
||||
LoginDTO loginDTO = new LoginDTO();
|
||||
loginDTO.setUsername("手机号码");
|
||||
loginDTO.setPassword("密码");
|
||||
loginController.register(loginDTO);
|
||||
|
||||
RenException exception = assertThrows(RenException.class, () -> loginController.register(loginDTO));
|
||||
assertEquals(ErrorCode.USER_REGISTER_DISABLED, exception.getCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+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);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
spring:
|
||||
messages:
|
||||
encoding: UTF-8
|
||||
basename: i18n/messages
|
||||
data:
|
||||
redis:
|
||||
host: localhost
|
||||
|
||||
@@ -118,7 +118,16 @@ async def process_intent_result(
|
||||
|
||||
请根据以上信息回答用户的问题:{original_text}"""
|
||||
|
||||
response = conn.intent.replyResult(context_prompt, original_text)
|
||||
# 使用异步调用避免阻塞事件循环,影响其他设备的音频播放
|
||||
try:
|
||||
response = asyncio.run_coroutine_threadsafe(
|
||||
conn.intent.replyResult(context_prompt, original_text),
|
||||
conn.loop,
|
||||
).result()
|
||||
except Exception as e:
|
||||
conn.logger.bind(tag=TAG).error(f"LLM生成回复失败: {e}")
|
||||
response = None
|
||||
if response:
|
||||
speak_txt(conn, response)
|
||||
|
||||
conn.executor.submit(process_context_result)
|
||||
@@ -184,7 +193,15 @@ async def process_intent_result(
|
||||
elif result.action == Action.REQLLM: # 调用函数后再请求llm生成回复
|
||||
text = result.result
|
||||
conn.dialogue.put(Message(role="tool", content=text))
|
||||
llm_result = conn.intent.replyResult(text, original_text)
|
||||
# 使用异步调用避免阻塞事件循环,影响其他设备的音频播放
|
||||
try:
|
||||
llm_result = asyncio.run_coroutine_threadsafe(
|
||||
conn.intent.replyResult(text, original_text),
|
||||
conn.loop,
|
||||
).result()
|
||||
except Exception as e:
|
||||
conn.logger.bind(tag=TAG).error(f"LLM生成回复失败: {e}")
|
||||
llm_result = text
|
||||
if llm_result is None:
|
||||
llm_result = text
|
||||
speak_txt(conn, llm_result)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import asyncio
|
||||
from typing import List, Dict, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -120,12 +121,18 @@ class IntentProvider(IntentProviderBase):
|
||||
)
|
||||
return prompt
|
||||
|
||||
def replyResult(self, text: str, original_text: str):
|
||||
async def replyResult(self, text: str, original_text: str):
|
||||
"""使用 asyncio.to_thread 避免阻塞事件循环"""
|
||||
try:
|
||||
llm_result = self.llm.response_no_stream(
|
||||
user_prompt = (
|
||||
"请根据以上内容,像人类一样说话的口吻回复用户,要求简洁,请直接返回结果。用户现在说:"
|
||||
+ original_text
|
||||
)
|
||||
# 使用 to_thread 将同步阻塞调用放到线程池中执行,不阻塞事件循环
|
||||
llm_result = await asyncio.to_thread(
|
||||
self.llm.response_no_stream,
|
||||
system_prompt=text,
|
||||
user_prompt="请根据以上内容,像人类一样说话的口吻回复用户,要求简洁,请直接返回结果。用户现在说:"
|
||||
+ original_text,
|
||||
user_prompt=user_prompt,
|
||||
)
|
||||
return llm_result
|
||||
except Exception as e:
|
||||
@@ -207,8 +214,11 @@ class IntentProvider(IntentProviderBase):
|
||||
logger.bind(tag=TAG).debug(f"开始LLM意图识别调用, 模型: {model_info}")
|
||||
|
||||
try:
|
||||
intent = self.llm.response_no_stream(
|
||||
system_prompt=prompt_music, user_prompt=user_prompt
|
||||
# 使用 to_thread 将同步阻塞调用放到线程池中,避免阻塞事件循环
|
||||
intent = await asyncio.to_thread(
|
||||
self.llm.response_no_stream,
|
||||
system_prompt=prompt_music,
|
||||
user_prompt=user_prompt,
|
||||
)
|
||||
except Exception as e:
|
||||
logger.bind(tag=TAG).error(f"Error in intent detection LLM call: {e}")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import random
|
||||
import httpx
|
||||
from markitdown import MarkItDown
|
||||
from io import BytesIO
|
||||
from markitdown import MarkItDown, StreamInfo
|
||||
from config.logger import setup_logging
|
||||
from plugins_func.register import register_function, ToolType, ActionResponse, Action
|
||||
from typing import TYPE_CHECKING
|
||||
@@ -144,7 +145,14 @@ async def fetch_news_detail(url):
|
||||
|
||||
# 使用MarkItDown清理HTML内容
|
||||
md = MarkItDown(enable_plugins=False)
|
||||
result = md.convert(response)
|
||||
result = md.convert_stream(
|
||||
BytesIO(response.content),
|
||||
stream_info=StreamInfo(
|
||||
mimetype="text/html",
|
||||
extension=".html",
|
||||
charset=response.encoding or "utf-8",
|
||||
),
|
||||
)
|
||||
|
||||
# 获取清理后的文本内容
|
||||
clean_text = result.text_content
|
||||
|
||||
Reference in New Issue
Block a user