mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-24 16:13:54 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4a2c90ec87 | ||
|
|
27e57631a7 | ||
|
|
b1e39da9b8 | ||
|
|
e87fc766d2 |
@@ -21,14 +21,14 @@
|
||||
<junit.version>5.10.1</junit.version>
|
||||
<druid.version>1.2.20</druid.version>
|
||||
<mybatisplus.version>3.5.17</mybatisplus.version>
|
||||
<hutool.version>5.8.24</hutool.version>
|
||||
<jsoup.version>1.19.1</jsoup.version>
|
||||
<hutool.version>5.8.46</hutool.version>
|
||||
<jsoup.version>1.22.2</jsoup.version>
|
||||
<knife4j.version>4.6.0</knife4j.version>
|
||||
<springdoc.version>2.8.8</springdoc.version>
|
||||
<commons-lang3.version>3.18.0</commons-lang3.version>
|
||||
<commons-lang3.version>3.20.0</commons-lang3.version>
|
||||
<shiro.version>2.0.2</shiro.version>
|
||||
<captcha.version>1.6.2</captcha.version>
|
||||
<guava.version>33.0.0-jre</guava.version>
|
||||
<guava.version>33.6.0-jre</guava.version>
|
||||
<liquibase-core.version>4.20.0</liquibase-core.version>
|
||||
<aliyun-sms-version>4.1.0</aliyun-sms-version>
|
||||
<okio-version>3.4.0</okio-version>
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
package xiaozhi.common.utils;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
/**
|
||||
* 哈希加密算法的工具类
|
||||
* @author zjy
|
||||
*/
|
||||
@Slf4j
|
||||
public class HashEncryptionUtil {
|
||||
/**
|
||||
* 使用md5进行加密
|
||||
* @param context 被加密的内容
|
||||
* @return 哈希值
|
||||
*/
|
||||
public static String Md5hexDigest(String context){
|
||||
return hexDigest(context,"MD5");
|
||||
}
|
||||
|
||||
/**
|
||||
* 指定哈希算法进行加密
|
||||
* @param context 被加密的内容
|
||||
* @param algorithm 哈希算法
|
||||
* @return 哈希值
|
||||
*/
|
||||
public static String hexDigest(String context,String algorithm ){
|
||||
// 获取MD5算法实例
|
||||
MessageDigest md = null;
|
||||
try {
|
||||
md = MessageDigest.getInstance(algorithm);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
log.error("加密失败的算法:{}",algorithm);
|
||||
throw new RuntimeException("加密失败,"+ algorithm +"哈希算法系统不支持");
|
||||
}
|
||||
// 计算智能体id的MD5值
|
||||
byte[] messageDigest = md.digest(context.getBytes());
|
||||
// 将字节数组转换为十六进制字符串
|
||||
StringBuilder hexString = new StringBuilder();
|
||||
for (byte b : messageDigest) {
|
||||
String hex = Integer.toHexString(0xFF & b);
|
||||
if (hex.length() == 1) {
|
||||
hexString.append('0');
|
||||
}
|
||||
hexString.append(hex);
|
||||
}
|
||||
return hexString.toString();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
package xiaozhi.common.utils;
|
||||
|
||||
import cn.hutool.core.util.ReUtil;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* 通用工具类
|
||||
*/
|
||||
public class ToolUtil {
|
||||
private static final Logger logger = LoggerFactory.getLogger(ToolUtil.class);
|
||||
|
||||
/**
|
||||
* 对象是否不为空(新增)
|
||||
*/
|
||||
public static boolean isNotEmpty(Object o) {
|
||||
return !isEmpty(o);
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象是否为空
|
||||
*/
|
||||
public static boolean isEmpty(Object o) {
|
||||
if (o == null) {
|
||||
return true;
|
||||
}
|
||||
if (o instanceof String) {
|
||||
if (o.toString().trim().equals("")) {
|
||||
return true;
|
||||
}
|
||||
} else if (o instanceof List) {
|
||||
if (((List) o).size() == 0) {
|
||||
return true;
|
||||
}
|
||||
} else if (o instanceof Map) {
|
||||
if (((Map) o).size() == 0) {
|
||||
return true;
|
||||
}
|
||||
} else if (o instanceof Set) {
|
||||
if (((Set) o).size() == 0) {
|
||||
return true;
|
||||
}
|
||||
} else if (o instanceof Object[]) {
|
||||
if (((Object[]) o).length == 0) {
|
||||
return true;
|
||||
}
|
||||
} else if (o instanceof int[]) {
|
||||
if (((int[]) o).length == 0) {
|
||||
return true;
|
||||
}
|
||||
} else if (o instanceof long[]) {
|
||||
if (((long[]) o).length == 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象组中是否存在空对象
|
||||
*/
|
||||
public static boolean isOneEmpty(Object... os) {
|
||||
for (Object o : os) {
|
||||
if (isEmpty(o)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象组中是否全是空对象
|
||||
*/
|
||||
public static boolean isAllEmpty(Object... os) {
|
||||
for (Object o : os) {
|
||||
if (!isEmpty(o)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -5,6 +5,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.ListUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -20,7 +21,6 @@ import xiaozhi.common.constant.Constant;
|
||||
import xiaozhi.common.page.PageData;
|
||||
import xiaozhi.common.utils.ConvertUtils;
|
||||
import xiaozhi.common.utils.JsonUtils;
|
||||
import xiaozhi.common.utils.ToolUtil;
|
||||
import xiaozhi.modules.agent.Enums.AgentChatHistoryType;
|
||||
import xiaozhi.modules.agent.dao.AiAgentChatHistoryDao;
|
||||
import xiaozhi.modules.agent.dto.AgentChatHistoryDTO;
|
||||
@@ -107,7 +107,7 @@ public class AgentChatHistoryServiceImpl extends CrudRepository<AiAgentChatHisto
|
||||
if (deleteAudio) {
|
||||
// 分批删除音频,避免超时
|
||||
List<String> audioIds = baseMapper.getAudioIdsByAgentId(agentId);
|
||||
if (ToolUtil.isNotEmpty(audioIds)) {
|
||||
if (CollUtil.isNotEmpty(audioIds)) {
|
||||
// 每批删除1000条
|
||||
List<List<String>> batch = ListUtil.split(audioIds, 1000);
|
||||
batch.forEach(dataList -> {
|
||||
|
||||
+2
-2
@@ -12,11 +12,11 @@ import java.util.stream.Collectors;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import cn.hutool.crypto.digest.DigestUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import xiaozhi.common.constant.Constant;
|
||||
import xiaozhi.common.utils.AESUtils;
|
||||
import xiaozhi.common.utils.HashEncryptionUtil;
|
||||
import xiaozhi.common.utils.JsonUtils;
|
||||
import xiaozhi.modules.agent.Enums.XiaoZhiMcpJsonRpcJson;
|
||||
import xiaozhi.modules.agent.service.AgentMcpAccessPointService;
|
||||
@@ -226,7 +226,7 @@ public class AgentMcpAccessPointServiceImpl implements AgentMcpAccessPointServic
|
||||
*/
|
||||
private static String encryptToken(String agentId, String key) {
|
||||
// 使用md5对智能体id进行加密
|
||||
String md5 = HashEncryptionUtil.Md5hexDigest(agentId);
|
||||
String md5 = DigestUtil.md5Hex(agentId);
|
||||
// aes需要加密文本
|
||||
String json = "{\"agentId\": \"%s\"}".formatted(md5);
|
||||
// 加密后成token值
|
||||
|
||||
+4
-4
@@ -18,6 +18,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.repository.IRepository;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
import xiaozhi.common.constant.Constant;
|
||||
import xiaozhi.common.exception.ErrorCode;
|
||||
@@ -29,7 +30,6 @@ import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||
import xiaozhi.common.user.UserDetail;
|
||||
import xiaozhi.common.utils.ConvertUtils;
|
||||
import xiaozhi.common.utils.JsonUtils;
|
||||
import xiaozhi.common.utils.ToolUtil;
|
||||
import xiaozhi.modules.agent.dao.AgentDao;
|
||||
import xiaozhi.modules.agent.dao.AgentTagDao;
|
||||
import xiaozhi.modules.agent.dto.AgentCreateDTO;
|
||||
@@ -243,13 +243,13 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
||||
.map(DeviceEntity::getAgentId)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
if (ToolUtil.isNotEmpty(agentIds)) {
|
||||
if (CollUtil.isNotEmpty(agentIds)) {
|
||||
w.or().in("id", agentIds);
|
||||
}
|
||||
|
||||
// 按标签名搜索
|
||||
List<String> tagAgentIds = agentTagService.getAgentIdsByTagName(keyword);
|
||||
if (ToolUtil.isNotEmpty(tagAgentIds)) {
|
||||
if (CollUtil.isNotEmpty(tagAgentIds)) {
|
||||
w.or().in("id", tagAgentIds);
|
||||
}
|
||||
});
|
||||
@@ -291,7 +291,7 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
||||
|
||||
// 获取标签列表
|
||||
List<AgentTagEntity> tags = agentTagDao.selectByAgentId(agent.getId());
|
||||
if (ToolUtil.isNotEmpty(tags)) {
|
||||
if (CollUtil.isNotEmpty(tags)) {
|
||||
dto.setTags(tags.stream().map(this::convertTagToDTO).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
|
||||
+3
-1
@@ -125,7 +125,9 @@ public class DeviceController {
|
||||
return new Result<Void>().error("设备不存在");
|
||||
}
|
||||
BeanUtils.copyProperties(deviceUpdateDTO, entity);
|
||||
deviceService.updateById(entity);
|
||||
if (!deviceService.updateById(entity)) {
|
||||
return new Result<Void>().error(ErrorCode.UPDATE_DATA_FAILED);
|
||||
}
|
||||
return new Result<Void>();
|
||||
}
|
||||
|
||||
|
||||
+4
-11
@@ -5,8 +5,6 @@ import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
@@ -31,6 +29,7 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import cn.hutool.crypto.digest.DigestUtil;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.Parameters;
|
||||
@@ -288,7 +287,7 @@ public class OTAMagController {
|
||||
|
||||
// 返回文件路径
|
||||
return new Result<String>().ok(filePath.toString());
|
||||
} catch (IOException | NoSuchAlgorithmException e) {
|
||||
} catch (IOException e) {
|
||||
return new Result<String>().error("文件上传失败:" + e.getMessage());
|
||||
}
|
||||
}
|
||||
@@ -329,13 +328,7 @@ public class OTAMagController {
|
||||
return result;
|
||||
}
|
||||
|
||||
private String calculateMD5(MultipartFile file) throws IOException, NoSuchAlgorithmException {
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
byte[] digest = md.digest(file.getBytes());
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : digest) {
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
return sb.toString();
|
||||
private String calculateMD5(MultipartFile file) throws IOException {
|
||||
return DigestUtil.md5Hex(file.getBytes());
|
||||
}
|
||||
}
|
||||
|
||||
+9
-8
@@ -31,6 +31,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
@@ -51,7 +52,6 @@ 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;
|
||||
import xiaozhi.modules.device.dto.DevicePageUserDTO;
|
||||
@@ -103,15 +103,15 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
||||
throw new RenException(ErrorCode.ACTIVATION_CODE_EMPTY);
|
||||
}
|
||||
String deviceKey = RedisKeys.getOtaActivationCode(activationCode);
|
||||
Object cacheDeviceId = redisUtils.get(deviceKey);
|
||||
if (ToolUtil.isEmpty(cacheDeviceId)) {
|
||||
String cacheDeviceId = (String) redisUtils.get(deviceKey);
|
||||
if (StringUtils.isBlank(cacheDeviceId)) {
|
||||
throw new RenException(ErrorCode.ACTIVATION_CODE_ERROR);
|
||||
}
|
||||
String deviceId = (String) cacheDeviceId;
|
||||
String deviceId = cacheDeviceId;
|
||||
String safeDeviceId = deviceId.replace(":", "_").toLowerCase();
|
||||
String cacheDeviceKey = RedisKeys.getOtaDeviceActivationInfo(safeDeviceId);
|
||||
Map<String, Object> cacheMap = JsonUtils.toStringObjectMap(redisUtils.get(cacheDeviceKey));
|
||||
if (ToolUtil.isEmpty(cacheMap)) {
|
||||
if (MapUtil.isEmpty(cacheMap)) {
|
||||
throw new RenException(ErrorCode.ACTIVATION_CODE_ERROR);
|
||||
}
|
||||
String cachedCode = (String) cacheMap.get("activation_code");
|
||||
@@ -181,7 +181,7 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
||||
.builder(new HashMap<String, Set<String>>())
|
||||
.put("clientIds", deviceIds).build();
|
||||
|
||||
if (ToolUtil.isNotEmpty(deviceIds)) {
|
||||
if (CollUtil.isNotEmpty(deviceIds)) {
|
||||
return postToMqttGateway(url, params);
|
||||
}
|
||||
// 返回响应
|
||||
@@ -202,8 +202,8 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
||||
firmware.setUrl(Constant.INVALID_FIRMWARE_URL);
|
||||
response.setFirmware(firmware);
|
||||
} else {
|
||||
// 只有在设备已绑定且autoUpdate不为0的情况下才返回固件升级信息
|
||||
if (deviceById.getAutoUpdate() != 0) {
|
||||
// 只有在设备已绑定且明确开启自动升级时才返回固件升级信息
|
||||
if (Integer.valueOf(1).equals(deviceById.getAutoUpdate())) {
|
||||
String type = deviceReport.getBoard() == null ? null : deviceReport.getBoard().getType();
|
||||
DeviceReportRespDTO.Firmware firmware = buildFirmwareInfo(type,
|
||||
deviceReport.getApplication() == null ? null : deviceReport.getApplication().getVersion());
|
||||
@@ -299,6 +299,7 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
||||
UserShowDeviceListVO vo = ConvertUtils.sourceToTarget(device, UserShowDeviceListVO.class);
|
||||
vo.setDeviceType(device.getBoard());
|
||||
vo.setBoard(device.getBoard());
|
||||
vo.setAutoUpdate(device.getAutoUpdate());
|
||||
vo.setCreateDateTimestamp(toTimestamp(device.getCreateDate()));
|
||||
vo.setLastConnectedAtTimestamp(toTimestamp(device.getLastConnectedAt()));
|
||||
return vo;
|
||||
|
||||
@@ -32,8 +32,8 @@ public class UserShowDeviceListVO {
|
||||
@Schema(description = "设备别名")
|
||||
private String alias;
|
||||
|
||||
@Schema(description = "开启OTA")
|
||||
private Integer otaUpgrade;
|
||||
@Schema(description = "自动更新开关(0关闭/1开启)")
|
||||
private Integer autoUpdate;
|
||||
|
||||
@Schema(description = "最近对话时间")
|
||||
private String recentChatTime;
|
||||
|
||||
+2
-2
@@ -15,6 +15,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
@@ -23,7 +24,6 @@ import xiaozhi.common.exception.ErrorCode;
|
||||
import xiaozhi.common.exception.RenException;
|
||||
import xiaozhi.common.page.PageData;
|
||||
import xiaozhi.common.utils.Result;
|
||||
import xiaozhi.common.utils.ToolUtil;
|
||||
import xiaozhi.modules.knowledge.dto.KnowledgeBaseDTO;
|
||||
import xiaozhi.modules.knowledge.service.KnowledgeBaseService;
|
||||
import xiaozhi.modules.knowledge.service.KnowledgeManagerService;
|
||||
@@ -140,7 +140,7 @@ public class KnowledgeBaseController {
|
||||
List<String> idList = Arrays.asList(ids.split(","));
|
||||
List<KnowledgeBaseDTO> knowledgeBaseDTOs = Optional.ofNullable(knowledgeBaseService.getByDatasetIdList(idList))
|
||||
.orElseGet(ArrayList::new);
|
||||
if (ToolUtil.isNotEmpty(knowledgeBaseDTOs)) {
|
||||
if (CollUtil.isNotEmpty(knowledgeBaseDTOs)) {
|
||||
knowledgeBaseDTOs.forEach(item -> {
|
||||
// 检查权限:用户只能删除自己创建的知识库
|
||||
if (item.getCreator() == null || !item.getCreator().equals(currentUserId)) {
|
||||
|
||||
+4
-20
@@ -1,8 +1,7 @@
|
||||
package xiaozhi.modules.security.service.impl;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.time.Duration;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@@ -13,6 +12,7 @@ import com.google.common.cache.CacheBuilder;
|
||||
import com.wf.captcha.SpecCaptcha;
|
||||
import com.wf.captcha.base.Captcha;
|
||||
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import xiaozhi.common.constant.Constant;
|
||||
@@ -41,7 +41,7 @@ public class CaptchaServiceImpl implements CaptchaService {
|
||||
* Local Cache 5分钟过期
|
||||
*/
|
||||
Cache<String, String> localCache = CacheBuilder.newBuilder().maximumSize(1000)
|
||||
.expireAfterAccess(5, TimeUnit.MINUTES).build();
|
||||
.expireAfterAccess(Duration.ofMinutes(5)).build();
|
||||
|
||||
@Override
|
||||
public void create(HttpServletResponse response, String uuid) throws IOException {
|
||||
@@ -113,7 +113,7 @@ public class CaptchaServiceImpl implements CaptchaService {
|
||||
}
|
||||
|
||||
String key = RedisKeys.getSMSValidateCodeKey(phone);
|
||||
String validateCodes = generateValidateCode(6);
|
||||
String validateCodes = RandomUtil.randomNumbers(6);
|
||||
|
||||
// 设置验证码
|
||||
setCache(key, validateCodes);
|
||||
@@ -135,22 +135,6 @@ public class CaptchaServiceImpl implements CaptchaService {
|
||||
return validate(key, code, delete);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成指定数量的随机数验证码
|
||||
*
|
||||
* @param length 数量
|
||||
* @return 随机码
|
||||
*/
|
||||
private String generateValidateCode(Integer length) {
|
||||
String chars = "0123456789"; // 字符范围可以自定义:数字
|
||||
Random random = new Random();
|
||||
StringBuilder code = new StringBuilder();
|
||||
for (int i = 0; i < length; i++) {
|
||||
code.append(chars.charAt(random.nextInt(chars.length())));
|
||||
}
|
||||
return code.toString();
|
||||
}
|
||||
|
||||
private void setCache(String key, String value) {
|
||||
if (open) {
|
||||
key = RedisKeys.getCaptchaKey(key);
|
||||
|
||||
+3
-3
@@ -12,6 +12,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import lombok.AllArgsConstructor;
|
||||
import xiaozhi.common.exception.RenException;
|
||||
import xiaozhi.common.exception.ErrorCode;
|
||||
@@ -21,7 +22,6 @@ 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;
|
||||
import xiaozhi.modules.sys.dto.SysDictDataDTO;
|
||||
@@ -104,13 +104,13 @@ public class SysDictDataServiceImpl extends BaseServiceImpl<SysDictDataDao, SysD
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void delete(Long[] ids) {
|
||||
List<Long> idList = Arrays.asList(ids);
|
||||
if (ToolUtil.isNotEmpty(idList)) {
|
||||
if (CollUtil.isNotEmpty(idList)) {
|
||||
//批量删除redis字典
|
||||
List<String> redisKeyList = new ArrayList<>();
|
||||
//批量获取字典类型
|
||||
List<String> dictTypeList = Optional.ofNullable(baseDao.getDictTypesByIdList(idList)).orElseGet(ArrayList::new);
|
||||
dictTypeList.forEach(dictType -> redisKeyList.add(RedisKeys.getDictDataByTypeKey(dictType)));
|
||||
if (ToolUtil.isNotEmpty(redisKeyList)) {
|
||||
if (CollUtil.isNotEmpty(redisKeyList)) {
|
||||
//清除缓存
|
||||
redisUtils.delete(redisKeyList);
|
||||
}
|
||||
|
||||
+2
-2
@@ -17,6 +17,7 @@ import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import xiaozhi.common.constant.Constant;
|
||||
@@ -26,7 +27,6 @@ import xiaozhi.common.page.PageData;
|
||||
import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||
import xiaozhi.common.utils.ConvertUtils;
|
||||
import xiaozhi.common.utils.DateUtils;
|
||||
import xiaozhi.common.utils.ToolUtil;
|
||||
import xiaozhi.modules.model.entity.ModelConfigEntity;
|
||||
import xiaozhi.modules.model.service.ModelConfigService;
|
||||
import xiaozhi.modules.sys.dao.SysUserDao;
|
||||
@@ -120,7 +120,7 @@ public class VoiceCloneServiceImpl extends BaseServiceImpl<VoiceCloneDao, VoiceC
|
||||
entity.setTrainStatus(0); // 默认训练中
|
||||
batchInsertList.add(entity);
|
||||
}
|
||||
if (ToolUtil.isNotEmpty(batchInsertList)) {
|
||||
if (CollUtil.isNotEmpty(batchInsertList)) {
|
||||
insertBatch(batchInsertList);
|
||||
}
|
||||
}
|
||||
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
package xiaozhi.modules.device.controller;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
|
||||
import xiaozhi.common.exception.ErrorCode;
|
||||
import xiaozhi.common.redis.RedisUtils;
|
||||
import xiaozhi.common.user.UserDetail;
|
||||
import xiaozhi.common.utils.MessageUtils;
|
||||
import xiaozhi.common.utils.Result;
|
||||
import xiaozhi.modules.device.dto.DeviceUpdateDTO;
|
||||
import xiaozhi.modules.device.entity.DeviceEntity;
|
||||
import xiaozhi.modules.device.service.DeviceAddressBookService;
|
||||
import xiaozhi.modules.device.service.DeviceService;
|
||||
import xiaozhi.modules.security.user.SecurityUser;
|
||||
import xiaozhi.modules.sys.service.SysParamsService;
|
||||
|
||||
@DisplayName("设备更新接口回归测试")
|
||||
class DeviceControllerTest {
|
||||
|
||||
private static final String DEVICE_ID = "device-id";
|
||||
private static final long USER_ID = 1L;
|
||||
|
||||
@Test
|
||||
@DisplayName("数据库未更新时不误报自动升级状态修改成功")
|
||||
void updateFailureIsReturnedToCaller() {
|
||||
DeviceService deviceService = mock(DeviceService.class);
|
||||
DeviceEntity entity = ownedDevice();
|
||||
when(deviceService.selectById(DEVICE_ID)).thenReturn(entity);
|
||||
when(deviceService.updateById(entity)).thenReturn(false);
|
||||
DeviceController controller = controller(deviceService);
|
||||
|
||||
DeviceUpdateDTO update = new DeviceUpdateDTO();
|
||||
update.setAutoUpdate(0);
|
||||
|
||||
try (MockedStatic<SecurityUser> securityUser = mockStatic(SecurityUser.class);
|
||||
MockedStatic<MessageUtils> messageUtils = mockStatic(MessageUtils.class)) {
|
||||
securityUser.when(SecurityUser::getUser).thenReturn(currentUser());
|
||||
messageUtils.when(() -> MessageUtils.getMessage(ErrorCode.UPDATE_DATA_FAILED))
|
||||
.thenReturn("Failed to update data");
|
||||
|
||||
Result<Void> result = controller.updateDeviceInfo(DEVICE_ID, update);
|
||||
|
||||
assertEquals(ErrorCode.UPDATE_DATA_FAILED, result.getCode());
|
||||
assertEquals(0, entity.getAutoUpdate());
|
||||
verify(deviceService).updateById(entity);
|
||||
}
|
||||
}
|
||||
|
||||
private DeviceController controller(DeviceService deviceService) {
|
||||
return new DeviceController(
|
||||
deviceService,
|
||||
mock(DeviceAddressBookService.class),
|
||||
mock(RedisUtils.class),
|
||||
mock(SysParamsService.class));
|
||||
}
|
||||
|
||||
private DeviceEntity ownedDevice() {
|
||||
DeviceEntity entity = new DeviceEntity();
|
||||
entity.setId(DEVICE_ID);
|
||||
entity.setUserId(USER_ID);
|
||||
entity.setAutoUpdate(1);
|
||||
return entity;
|
||||
}
|
||||
|
||||
private UserDetail currentUser() {
|
||||
UserDetail user = new UserDetail();
|
||||
user.setId(USER_ID);
|
||||
return user;
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
package xiaozhi.modules.device.service.impl;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoInteractions;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.aop.framework.ProxyFactory;
|
||||
|
||||
import xiaozhi.common.constant.Constant;
|
||||
import xiaozhi.modules.device.dto.DeviceReportReqDTO;
|
||||
import xiaozhi.modules.device.dto.DeviceReportRespDTO;
|
||||
import xiaozhi.modules.device.entity.DeviceEntity;
|
||||
import xiaozhi.modules.device.service.OtaService;
|
||||
import xiaozhi.modules.sys.service.SysParamsService;
|
||||
|
||||
@DisplayName("设备自动升级回归测试")
|
||||
class DeviceAutoUpdateTest {
|
||||
|
||||
private static final String MAC_ADDRESS = "00:11:22:33:44:55";
|
||||
private static final String BOARD_TYPE = "test-board";
|
||||
private static final String CURRENT_VERSION = "1.0.0";
|
||||
|
||||
@Test
|
||||
@DisplayName("#3299 自动升级关闭时 OTA 响应不包含固件")
|
||||
void disabledAutoUpdateOmitsFirmware() {
|
||||
OtaService otaService = mock(OtaService.class);
|
||||
DeviceServiceImpl service = proxiedService(deviceWithAutoUpdate(0), otaService);
|
||||
|
||||
DeviceReportRespDTO response = service.checkDeviceActive(
|
||||
MAC_ADDRESS, MAC_ADDRESS, deviceReport());
|
||||
|
||||
assertNull(response.getFirmware());
|
||||
verifyNoInteractions(otaService);
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("自动升级开启时继续执行固件查询")
|
||||
void enabledAutoUpdateChecksFirmware() {
|
||||
OtaService otaService = mock(OtaService.class);
|
||||
when(otaService.getLatestOta(BOARD_TYPE)).thenReturn(null);
|
||||
DeviceServiceImpl service = proxiedService(deviceWithAutoUpdate(1), otaService);
|
||||
|
||||
DeviceReportRespDTO response = service.checkDeviceActive(
|
||||
MAC_ADDRESS, MAC_ADDRESS, deviceReport());
|
||||
|
||||
assertNotNull(response.getFirmware());
|
||||
assertEquals(CURRENT_VERSION, response.getFirmware().getVersion());
|
||||
assertEquals(Constant.INVALID_FIRMWARE_URL, response.getFirmware().getUrl());
|
||||
verify(otaService).getLatestOta(BOARD_TYPE);
|
||||
}
|
||||
|
||||
private DeviceServiceImpl proxiedService(DeviceEntity device, OtaService otaService) {
|
||||
SysParamsService sysParamsService = mock(SysParamsService.class);
|
||||
when(sysParamsService.getValue(Constant.SERVER_WEBSOCKET, true))
|
||||
.thenReturn("ws://127.0.0.1:8000/xiaozhi/v1/");
|
||||
when(sysParamsService.getValue(Constant.SERVER_AUTH_ENABLED, true)).thenReturn("false");
|
||||
when(sysParamsService.getValue(Constant.SERVER_MQTT_GATEWAY, true)).thenReturn(null);
|
||||
|
||||
DeviceServiceImpl target = new DeviceServiceImpl(
|
||||
null, null, sysParamsService, null, otaService, null) {
|
||||
@Override
|
||||
public DeviceEntity getDeviceByMacAddress(String macAddress) {
|
||||
return device;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateDeviceConnectionInfo(String agentId, String deviceId, String appVersion) {
|
||||
// No-op: connection timestamps are outside this OTA decision test.
|
||||
}
|
||||
};
|
||||
|
||||
ProxyFactory proxyFactory = new ProxyFactory(target);
|
||||
proxyFactory.setProxyTargetClass(true);
|
||||
proxyFactory.setExposeProxy(true);
|
||||
return (DeviceServiceImpl) proxyFactory.getProxy();
|
||||
}
|
||||
|
||||
private DeviceEntity deviceWithAutoUpdate(int autoUpdate) {
|
||||
DeviceEntity device = new DeviceEntity();
|
||||
device.setId(MAC_ADDRESS);
|
||||
device.setMacAddress(MAC_ADDRESS);
|
||||
device.setBoard(BOARD_TYPE);
|
||||
device.setAutoUpdate(autoUpdate);
|
||||
return device;
|
||||
}
|
||||
|
||||
private DeviceReportReqDTO deviceReport() {
|
||||
DeviceReportReqDTO.Application application = new DeviceReportReqDTO.Application();
|
||||
application.setVersion(CURRENT_VERSION);
|
||||
DeviceReportReqDTO.BoardInfo board = new DeviceReportReqDTO.BoardInfo();
|
||||
board.setType(BOARD_TYPE);
|
||||
|
||||
DeviceReportReqDTO report = new DeviceReportReqDTO();
|
||||
report.setApplication(application);
|
||||
report.setBoard(board);
|
||||
return report;
|
||||
}
|
||||
}
|
||||
+19
@@ -76,6 +76,25 @@ class DeviceTimeSerializationTest {
|
||||
() -> assertTrue(payload.path("createDate").isNull()));
|
||||
}
|
||||
|
||||
@ParameterizedTest(name = "自动升级状态 {0}")
|
||||
@ValueSource(ints = { 0, 1 })
|
||||
@DisplayName("#3299 设备列表按 autoUpdate 契约返回真实开关状态")
|
||||
void serializedDeviceContainsAutoUpdateState(int autoUpdate) {
|
||||
DeviceEntity entity = new DeviceEntity();
|
||||
entity.setAutoUpdate(autoUpdate);
|
||||
DeviceServiceImpl deviceService = serviceReturning(entity);
|
||||
|
||||
UserShowDeviceListVO device = deviceService.getUserDeviceList(1L, "agent-id").getFirst();
|
||||
ObjectMapper objectMapper = new WebMvcConfig().jackson2HttpMessageConverter().getObjectMapper();
|
||||
JsonNode payload = objectMapper.valueToTree(device);
|
||||
|
||||
assertAll(
|
||||
() -> assertEquals(autoUpdate, device.getAutoUpdate()),
|
||||
() -> assertEquals(autoUpdate, payload.path("autoUpdate").asInt()),
|
||||
() -> assertTrue(payload.path("otaUpgrade").isMissingNode(),
|
||||
"设备列表不应继续暴露未映射的旧字段 otaUpgrade"));
|
||||
}
|
||||
|
||||
private DeviceServiceImpl serviceReturning(DeviceEntity entity) {
|
||||
return new DeviceServiceImpl(null, null, null, null, null, null) {
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user