mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-27 01:23:55 +08:00
合并多个提交
合并多个提交
This commit is contained in:
+14
-4
@@ -21,7 +21,17 @@ conda install conda-forge::ffmpeg
|
|||||||
建议:如果 `EdgeTTS` 经常失败,请先检查是否使用了代理(梯子)。如果使用了,请尝试关闭代理后再试;
|
建议:如果 `EdgeTTS` 经常失败,请先检查是否使用了代理(梯子)。如果使用了,请尝试关闭代理后再试;
|
||||||
如果用的是火山引擎的豆包 TTS,经常失败时建议使用付费版本,因为测试版本仅支持 2 个并发。
|
如果用的是火山引擎的豆包 TTS,经常失败时建议使用付费版本,因为测试版本仅支持 2 个并发。
|
||||||
|
|
||||||
### 4、如何提高小智对话响应速度? ⚡
|
### 4、使用Wifi能连接自建服务器,但是4G模式却接不上 🔐
|
||||||
|
|
||||||
|
原因:虾哥的固件,4G模式需要使用安全连接。
|
||||||
|
|
||||||
|
解决方法:目前有两种方法可以解决。任选一种:
|
||||||
|
|
||||||
|
1、改代码。参考这个视频解决 https://www.bilibili.com/video/BV18MfTYoE85
|
||||||
|
|
||||||
|
2、使用nginx配置ssl证书。参考教程 https://icnt94i5ctj4.feishu.cn/docx/GnYOdMNJOoRCljx1ctecsj9cnRe
|
||||||
|
|
||||||
|
### 5、如何提高小智对话响应速度? ⚡
|
||||||
|
|
||||||
本项目默认配置为低成本方案,建议初学者先使用默认免费模型,解决"跑得动"的问题,再优化"跑得快"。
|
本项目默认配置为低成本方案,建议初学者先使用默认免费模型,解决"跑得动"的问题,再优化"跑得快"。
|
||||||
如需提升响应速度,可尝试更换各组件。以下为各组件的响应速度测试数据(仅供参考,不构成承诺):
|
如需提升响应速度,可尝试更换各组件。以下为各组件的响应速度测试数据(仅供参考,不构成承诺):
|
||||||
@@ -78,7 +88,7 @@ TTS 性能排行:
|
|||||||
- LLM:`AliLLM`
|
- LLM:`AliLLM`
|
||||||
- TTS:`DoubaoTTS`
|
- TTS:`DoubaoTTS`
|
||||||
|
|
||||||
### 5、我说话很慢,停顿时小智老是抢话 🗣️
|
### 6、我说话很慢,停顿时小智老是抢话 🗣️
|
||||||
|
|
||||||
建议:在配置文件中找到如下部分,将 `min_silence_duration_ms` 的值调大(例如改为 `1000`):
|
建议:在配置文件中找到如下部分,将 `min_silence_duration_ms` 的值调大(例如改为 `1000`):
|
||||||
|
|
||||||
@@ -90,7 +100,7 @@ VAD:
|
|||||||
min_silence_duration_ms: 700 # 如果说话停顿较长,可将此值调大
|
min_silence_duration_ms: 700 # 如果说话停顿较长,可将此值调大
|
||||||
```
|
```
|
||||||
|
|
||||||
### 6、我想通过小智控制电灯、空调、远程开关机等操作 💡
|
### 7、我想通过小智控制电灯、空调、远程开关机等操作 💡
|
||||||
|
|
||||||
本项目,支持以工具调用的方式控制HomeAssistant设备
|
本项目,支持以工具调用的方式控制HomeAssistant设备
|
||||||
|
|
||||||
@@ -128,7 +138,7 @@ Intent:
|
|||||||
- hass_play_music
|
- hass_play_music
|
||||||
```
|
```
|
||||||
|
|
||||||
### 7、更多问题,可联系我们反馈 💬
|
### 8、更多问题,可联系我们反馈 💬
|
||||||
|
|
||||||
我们的联系方式放在[百度网盘中,点击前往](https://pan.baidu.com/s/1x6USjvP1nTRsZ45XlJu65Q),提取码是`223y`。
|
我们的联系方式放在[百度网盘中,点击前往](https://pan.baidu.com/s/1x6USjvP1nTRsZ45XlJu65Q),提取码是`223y`。
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
本文档是开发类文档,如需部署小智服务端,[点击这里查看部署教程](.././FAQ.md#%E4%BD%BF%E7%94%A8%E6%96%B9%E5%BC%8F-)
|
本文档是开发类文档,如需部署小智服务端,[点击这里查看部署教程](../../../docs/Deployment.md)
|
||||||
# 项目介绍
|
# 项目介绍
|
||||||
|
|
||||||
manager-api 该项目基于SpringBoot框架开发。
|
manager-api 该项目基于SpringBoot框架开发。
|
||||||
|
|||||||
@@ -26,4 +26,13 @@ public class RedisKeys {
|
|||||||
public static String getDeviceCaptchaKey(String captcha) {
|
public static String getDeviceCaptchaKey(String captcha) {
|
||||||
return "sys:device:captcha:" + captcha;
|
return "sys:device:captcha:" + captcha;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户id的Key
|
||||||
|
*/
|
||||||
|
public static String getUserIdKey(Long userid) {
|
||||||
|
return "sys:username:id:" + userid;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ package xiaozhi.common.utils;
|
|||||||
|
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.ZoneId;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -60,4 +63,39 @@ public class DateUtils {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取简短的时间字符串:10秒前返回刚刚,多少秒前,几小时前,超过一周返回年月日时分秒
|
||||||
|
* @param date
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public static String getShortTime(Date date) {
|
||||||
|
if (date == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// 将 Date 转换为 Instant
|
||||||
|
LocalDateTime localDateTime = date.toInstant()
|
||||||
|
// 获取系统默认时区
|
||||||
|
.atZone(ZoneId.systemDefault())
|
||||||
|
// 转换为 LocalDateTime
|
||||||
|
.toLocalDateTime();
|
||||||
|
// 当前时间
|
||||||
|
LocalDateTime now = LocalDateTime.now();
|
||||||
|
// 时间差,单位为秒
|
||||||
|
long secondsBetween = ChronoUnit.SECONDS.between(localDateTime, now);
|
||||||
|
|
||||||
|
if (secondsBetween <= 10) {
|
||||||
|
return "刚刚";
|
||||||
|
} else if (secondsBetween < 60) {
|
||||||
|
return secondsBetween + "秒前";
|
||||||
|
} else if (secondsBetween < 60 * 60) {
|
||||||
|
return secondsBetween / 60 + "分钟前";
|
||||||
|
} else if (secondsBetween < 86400) {
|
||||||
|
return secondsBetween / 3600 + "小时前";
|
||||||
|
} else if (secondsBetween < 604800) {
|
||||||
|
return secondsBetween / 86400 + "天前";
|
||||||
|
} else {
|
||||||
|
// 超过一周,显示完整日期时间
|
||||||
|
return format(date,DATE_TIME_PATTERN);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,4 +22,10 @@ public interface AgentService extends BaseService<AgentEntity> {
|
|||||||
* 获取智能体详情
|
* 获取智能体详情
|
||||||
*/
|
*/
|
||||||
AgentEntity getAgentById(String id);
|
AgentEntity getAgentById(String id);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除这个用户的所有
|
||||||
|
* @param userId
|
||||||
|
*/
|
||||||
|
void deleteAgentByUserId(String userId);
|
||||||
}
|
}
|
||||||
+8
@@ -7,6 +7,7 @@ import java.util.UUID;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
|
||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
@@ -62,4 +63,11 @@ public class AgentServiceImpl extends BaseServiceImpl<AgentDao, AgentEntity> imp
|
|||||||
|
|
||||||
return super.insert(entity);
|
return super.insert(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteAgentByUserId(String userId) {
|
||||||
|
UpdateWrapper<AgentEntity> wrapper = new UpdateWrapper<>();
|
||||||
|
wrapper.eq("user_id", userId);
|
||||||
|
baseDao.delete(wrapper);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package xiaozhi.modules.device.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.Min;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询所有设备的DTO
|
||||||
|
*
|
||||||
|
* @author zjy
|
||||||
|
* @since 2025-3-21
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "查询所有设备的DTO")
|
||||||
|
public class DevicePageUserDTO {
|
||||||
|
|
||||||
|
@Schema(description = "设备关键词")
|
||||||
|
private String keywords;
|
||||||
|
|
||||||
|
@Schema(description = "页数")
|
||||||
|
@Min(value = 0, message = "{page.number}")
|
||||||
|
private String page;
|
||||||
|
|
||||||
|
@Schema(description = "显示列数")
|
||||||
|
@Min(value = 0, message = "{limit.number}")
|
||||||
|
private String limit;
|
||||||
|
}
|
||||||
@@ -2,10 +2,13 @@ package xiaozhi.modules.device.service;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.modules.device.dto.DeviceBindDTO;
|
import xiaozhi.modules.device.dto.DeviceBindDTO;
|
||||||
|
import xiaozhi.modules.device.dto.DevicePageUserDTO;
|
||||||
import xiaozhi.modules.device.dto.DeviceReportReqDTO;
|
import xiaozhi.modules.device.dto.DeviceReportReqDTO;
|
||||||
import xiaozhi.modules.device.dto.DeviceReportRespDTO;
|
import xiaozhi.modules.device.dto.DeviceReportRespDTO;
|
||||||
import xiaozhi.modules.device.entity.DeviceEntity;
|
import xiaozhi.modules.device.entity.DeviceEntity;
|
||||||
|
import xiaozhi.modules.device.vo.UserShowDeviceListVO;
|
||||||
|
|
||||||
public interface DeviceService {
|
public interface DeviceService {
|
||||||
|
|
||||||
@@ -26,7 +29,7 @@ public interface DeviceService {
|
|||||||
DeviceEntity bindDevice(DeviceBindDTO deviceHeader);
|
DeviceEntity bindDevice(DeviceBindDTO deviceHeader);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取用户设备列表
|
* 获取用户指定智能体的设备列表,
|
||||||
*/
|
*/
|
||||||
List<DeviceEntity> getUserDevices(Long userId, String agentId);
|
List<DeviceEntity> getUserDevices(Long userId, String agentId);
|
||||||
|
|
||||||
@@ -39,4 +42,27 @@ public interface DeviceService {
|
|||||||
* 设备激活
|
* 设备激活
|
||||||
*/
|
*/
|
||||||
Boolean deviceActivation(String activationCode);
|
Boolean deviceActivation(String activationCode);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除此用户的所有设备
|
||||||
|
*
|
||||||
|
* @param userId 用户id
|
||||||
|
*/
|
||||||
|
void deleteByUserId(Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取指定用户的设备数量
|
||||||
|
*
|
||||||
|
* @param userId 用户id
|
||||||
|
* @return 设备数量
|
||||||
|
*/
|
||||||
|
Long selectCountByUserId(Long userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 分页获取全部设备信息
|
||||||
|
*
|
||||||
|
* @param dto 分页查找参数
|
||||||
|
* @return 用户列表分页数据
|
||||||
|
*/
|
||||||
|
PageData<UserShowDeviceListVO> page(DevicePageUserDTO dto);
|
||||||
}
|
}
|
||||||
+50
-1
@@ -16,34 +16,44 @@ import org.springframework.stereotype.Service;
|
|||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||||
|
|
||||||
import cn.hutool.core.util.RandomUtil;
|
import cn.hutool.core.util.RandomUtil;
|
||||||
|
import xiaozhi.common.constant.Constant;
|
||||||
import xiaozhi.common.exception.RenException;
|
import xiaozhi.common.exception.RenException;
|
||||||
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.common.service.impl.BaseServiceImpl;
|
import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||||
import xiaozhi.common.user.UserDetail;
|
import xiaozhi.common.user.UserDetail;
|
||||||
import xiaozhi.common.utils.ConvertUtils;
|
import xiaozhi.common.utils.ConvertUtils;
|
||||||
|
import xiaozhi.common.utils.DateUtils;
|
||||||
import xiaozhi.modules.device.dao.DeviceDao;
|
import xiaozhi.modules.device.dao.DeviceDao;
|
||||||
import xiaozhi.modules.device.dto.DeviceBindDTO;
|
import xiaozhi.modules.device.dto.DeviceBindDTO;
|
||||||
|
import xiaozhi.modules.device.dto.DevicePageUserDTO;
|
||||||
import xiaozhi.modules.device.dto.DeviceReportReqDTO;
|
import xiaozhi.modules.device.dto.DeviceReportReqDTO;
|
||||||
import xiaozhi.modules.device.dto.DeviceReportRespDTO;
|
import xiaozhi.modules.device.dto.DeviceReportRespDTO;
|
||||||
import xiaozhi.modules.device.entity.DeviceEntity;
|
import xiaozhi.modules.device.entity.DeviceEntity;
|
||||||
import xiaozhi.modules.device.service.DeviceService;
|
import xiaozhi.modules.device.service.DeviceService;
|
||||||
|
import xiaozhi.modules.device.vo.UserShowDeviceListVO;
|
||||||
import xiaozhi.modules.security.user.SecurityUser;
|
import xiaozhi.modules.security.user.SecurityUser;
|
||||||
|
import xiaozhi.modules.sys.service.SysUserUtilService;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity> implements DeviceService {
|
public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity> implements DeviceService {
|
||||||
|
|
||||||
private final DeviceDao deviceDao;
|
private final DeviceDao deviceDao;
|
||||||
|
|
||||||
|
private final SysUserUtilService sysUserUtilService;
|
||||||
|
|
||||||
private final String frontedUrl;
|
private final String frontedUrl;
|
||||||
|
|
||||||
private final RedisTemplate<String, Object> redisTemplate;
|
private final RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
// 添加构造函数来初始化 deviceMapper
|
// 添加构造函数来初始化 deviceMapper
|
||||||
public DeviceServiceImpl(DeviceDao deviceDao,
|
public DeviceServiceImpl(DeviceDao deviceDao, SysUserUtilService sysUserUtilService,
|
||||||
@Value("${app.fronted-url:http://localhost:8001}") String frontedUrl,
|
@Value("${app.fronted-url:http://localhost:8001}") String frontedUrl,
|
||||||
RedisTemplate<String, Object> redisTemplate) {
|
RedisTemplate<String, Object> redisTemplate) {
|
||||||
this.deviceDao = deviceDao;
|
this.deviceDao = deviceDao;
|
||||||
|
this.sysUserUtilService = sysUserUtilService;
|
||||||
this.frontedUrl = frontedUrl;
|
this.frontedUrl = frontedUrl;
|
||||||
this.redisTemplate = redisTemplate;
|
this.redisTemplate = redisTemplate;
|
||||||
}
|
}
|
||||||
@@ -197,6 +207,45 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
|||||||
baseDao.delete(wrapper);
|
baseDao.delete(wrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteByUserId(Long userId) {
|
||||||
|
UpdateWrapper<DeviceEntity> wrapper = new UpdateWrapper<>();
|
||||||
|
wrapper.eq("user_id", userId);
|
||||||
|
baseDao.delete(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Long selectCountByUserId(Long userId) {
|
||||||
|
UpdateWrapper<DeviceEntity> wrapper = new UpdateWrapper<>();
|
||||||
|
wrapper.eq("user_id", userId);
|
||||||
|
return baseDao.selectCount(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PageData<UserShowDeviceListVO> page(DevicePageUserDTO dto) {
|
||||||
|
Map<String, Object> params = new HashMap<String, Object>();
|
||||||
|
params.put(Constant.PAGE, dto.getPage());
|
||||||
|
params.put(Constant.LIMIT, dto.getLimit());
|
||||||
|
IPage<DeviceEntity> page = baseDao.selectPage(
|
||||||
|
getPage(params, "sort", true),
|
||||||
|
// 定义查询条件
|
||||||
|
new QueryWrapper<DeviceEntity>()
|
||||||
|
// 必须设备关键词查找
|
||||||
|
.like(StringUtils.isNotBlank(dto.getKeywords()), "alias", dto.getKeywords()));
|
||||||
|
// 循环处理page获取回来的数据,返回需要的字段
|
||||||
|
List<UserShowDeviceListVO> list = page.getRecords().stream().map(device -> {
|
||||||
|
UserShowDeviceListVO vo = ConvertUtils.sourceToTarget(device, UserShowDeviceListVO.class);
|
||||||
|
// 把最后修改的时间,改为简短描述的时间
|
||||||
|
vo.setRecentChatTime(DateUtils.getShortTime(device.getUpdateDate()));
|
||||||
|
sysUserUtilService.assignUsername(device.getUserId(),
|
||||||
|
vo::setBindUserName);
|
||||||
|
vo.setDeviceType(device.getBoard());
|
||||||
|
return vo;
|
||||||
|
}).toList();
|
||||||
|
// 计算页数
|
||||||
|
return new PageData<>(list, page.getTotal());
|
||||||
|
}
|
||||||
|
|
||||||
private DeviceReportRespDTO.ServerTime buildServerTime() {
|
private DeviceReportRespDTO.ServerTime buildServerTime() {
|
||||||
DeviceReportRespDTO.ServerTime serverTime = new DeviceReportRespDTO.ServerTime();
|
DeviceReportRespDTO.ServerTime serverTime = new DeviceReportRespDTO.ServerTime();
|
||||||
TimeZone tz = TimeZone.getDefault();
|
TimeZone tz = TimeZone.getDefault();
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package xiaozhi.modules.device.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@Schema(description = "用户显示设备列表VO")
|
||||||
|
public class UserShowDeviceListVO {
|
||||||
|
|
||||||
|
@Schema(description = "app版本")
|
||||||
|
private String appVersion;
|
||||||
|
|
||||||
|
@Schema(description = "绑定用户名称")
|
||||||
|
private String bindUserName;
|
||||||
|
|
||||||
|
@Schema(description = "设备型号")
|
||||||
|
private String deviceType;
|
||||||
|
|
||||||
|
@Schema(description = "设备唯一标识符")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@Schema(description = "mac地址")
|
||||||
|
private String macAddress;
|
||||||
|
|
||||||
|
@Schema(description = "开启OTA")
|
||||||
|
private Integer otaUpgrade;
|
||||||
|
|
||||||
|
@Schema(description = "最近对话时间")
|
||||||
|
private String recentChatTime;
|
||||||
|
|
||||||
|
}
|
||||||
-4
@@ -5,8 +5,6 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
@@ -37,8 +35,6 @@ public class ModelConfigServiceImpl extends BaseServiceImpl<ModelConfigDao, Mode
|
|||||||
private final ModelProviderService modelProviderService;
|
private final ModelProviderService modelProviderService;
|
||||||
private final TimbreService timbreService;
|
private final TimbreService timbreService;
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(ModelConfigServiceImpl.class);
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> getModelCodeList(String modelType, String modelName) {
|
public List<String> getModelCodeList(String modelType, String modelName) {
|
||||||
return modelConfigDao.getModelCodeList(modelType, modelName);
|
return modelConfigDao.getModelCodeList(modelType, modelName);
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ import xiaozhi.common.constant.Constant;
|
|||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.common.utils.Result;
|
import xiaozhi.common.utils.Result;
|
||||||
import xiaozhi.common.validator.ValidatorUtils;
|
import xiaozhi.common.validator.ValidatorUtils;
|
||||||
|
import xiaozhi.modules.device.dto.DevicePageUserDTO;
|
||||||
|
import xiaozhi.modules.device.service.DeviceService;
|
||||||
|
import xiaozhi.modules.device.vo.UserShowDeviceListVO;
|
||||||
import xiaozhi.modules.sys.dto.AdminPageUserDTO;
|
import xiaozhi.modules.sys.dto.AdminPageUserDTO;
|
||||||
import xiaozhi.modules.sys.service.SysUserService;
|
import xiaozhi.modules.sys.service.SysUserService;
|
||||||
import xiaozhi.modules.sys.vo.AdminPageUserVO;
|
import xiaozhi.modules.sys.vo.AdminPageUserVO;
|
||||||
@@ -37,6 +40,8 @@ import xiaozhi.modules.sys.vo.AdminPageUserVO;
|
|||||||
public class AdminController {
|
public class AdminController {
|
||||||
private final SysUserService sysUserService;
|
private final SysUserService sysUserService;
|
||||||
|
|
||||||
|
private final DeviceService deviceService;
|
||||||
|
|
||||||
@GetMapping("/users")
|
@GetMapping("/users")
|
||||||
@Operation(summary = "分页查找用户")
|
@Operation(summary = "分页查找用户")
|
||||||
@RequiresPermissions("sys:role:superAdmin")
|
@RequiresPermissions("sys:role:superAdmin")
|
||||||
@@ -51,7 +56,7 @@ public class AdminController {
|
|||||||
dto.setMobile((String) params.get("mobile"));
|
dto.setMobile((String) params.get("mobile"));
|
||||||
dto.setLimit((String) params.get(Constant.LIMIT));
|
dto.setLimit((String) params.get(Constant.LIMIT));
|
||||||
dto.setPage((String) params.get(Constant.PAGE));
|
dto.setPage((String) params.get(Constant.PAGE));
|
||||||
|
ValidatorUtils.validateEntity(dto);
|
||||||
ValidatorUtils.validateEntity(dto);
|
ValidatorUtils.validateEntity(dto);
|
||||||
PageData<AdminPageUserVO> page = sysUserService.page(dto);
|
PageData<AdminPageUserVO> page = sysUserService.page(dto);
|
||||||
return new Result<PageData<AdminPageUserVO>>().ok(page);
|
return new Result<PageData<AdminPageUserVO>>().ok(page);
|
||||||
@@ -70,7 +75,7 @@ public class AdminController {
|
|||||||
@Operation(summary = "用户删除")
|
@Operation(summary = "用户删除")
|
||||||
@RequiresPermissions("sys:role:superAdmin")
|
@RequiresPermissions("sys:role:superAdmin")
|
||||||
public Result<Void> delete(@PathVariable Long id) {
|
public Result<Void> delete(@PathVariable Long id) {
|
||||||
sysUserService.delete(new Long[] { id });
|
sysUserService.deleteById(id);
|
||||||
return new Result<>();
|
return new Result<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,9 +87,14 @@ public class AdminController {
|
|||||||
@Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true),
|
@Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true),
|
||||||
@Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true),
|
@Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true),
|
||||||
})
|
})
|
||||||
public Result<Void> pageDevice(
|
public Result<PageData<UserShowDeviceListVO>> pageDevice(
|
||||||
@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
|
@Parameter(hidden = true) @RequestParam Map<String, Object> params) {
|
||||||
// TODO 等设备功能模块写好
|
DevicePageUserDTO dto = new DevicePageUserDTO();
|
||||||
return new Result<Void>().error(600, "等设备功能模块写好");
|
dto.setKeywords((String) params.get("keywords"));
|
||||||
|
dto.setLimit((String) params.get(Constant.LIMIT));
|
||||||
|
dto.setPage((String) params.get(Constant.PAGE));
|
||||||
|
ValidatorUtils.validateEntity(dto);
|
||||||
|
PageData<UserShowDeviceListVO> page = deviceService.page(dto);
|
||||||
|
return new Result<PageData<UserShowDeviceListVO>>().ok(page);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package xiaozhi.modules.sys.dto;
|
package xiaozhi.modules.sys.dto;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.Min;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -10,15 +11,17 @@ import lombok.Data;
|
|||||||
* @since 2025-3-21
|
* @since 2025-3-21
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@Schema(description = "音色分页参数")
|
@Schema(description = "管理员分页用户的参数DTO")
|
||||||
public class AdminPageUserDTO {
|
public class AdminPageUserDTO {
|
||||||
|
|
||||||
@Schema(description = "手机号码")
|
@Schema(description = "手机号码")
|
||||||
private String mobile;
|
private String mobile;
|
||||||
|
|
||||||
@Schema(description = "页数")
|
@Schema(description = "页数")
|
||||||
|
@Min(value = 0, message = "{sort.number}")
|
||||||
private String page;
|
private String page;
|
||||||
|
|
||||||
@Schema(description = "显示列数")
|
@Schema(description = "显示列数")
|
||||||
|
@Min(value = 0, message = "{sort.number}")
|
||||||
private String limit;
|
private String limit;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,12 @@ public interface SysUserService extends BaseService<SysUserEntity> {
|
|||||||
|
|
||||||
void save(SysUserDTO dto);
|
void save(SysUserDTO dto);
|
||||||
|
|
||||||
void delete(Long[] ids);
|
/**
|
||||||
|
* 删除指定用户,且有关联的数据设备和智能体
|
||||||
|
*
|
||||||
|
* @param ids
|
||||||
|
*/
|
||||||
|
void deleteById(Long ids);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 验证是否允许修改密码更改
|
* 验证是否允许修改密码更改
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package xiaozhi.modules.sys.service;
|
||||||
|
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定义一个系统用户工具类,避免和用户模块循环依赖
|
||||||
|
* 如用户和设备互相依赖,用户需要获取所有设备,设备又需要获取每个设备的用户名
|
||||||
|
* @author zjy
|
||||||
|
* @since 2025-4-2
|
||||||
|
*/
|
||||||
|
public interface SysUserUtilService {
|
||||||
|
/**
|
||||||
|
* 赋值用户名
|
||||||
|
* @param userId 用户id
|
||||||
|
* @param setter 赋值方法
|
||||||
|
*/
|
||||||
|
void assignUsername( Long userId, Consumer<String> setter);
|
||||||
|
}
|
||||||
+17
-10
@@ -1,6 +1,5 @@
|
|||||||
package xiaozhi.modules.sys.service.impl;
|
package xiaozhi.modules.sys.service.impl;
|
||||||
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -22,6 +21,8 @@ import xiaozhi.common.exception.RenException;
|
|||||||
import xiaozhi.common.page.PageData;
|
import xiaozhi.common.page.PageData;
|
||||||
import xiaozhi.common.service.impl.BaseServiceImpl;
|
import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||||
import xiaozhi.common.utils.ConvertUtils;
|
import xiaozhi.common.utils.ConvertUtils;
|
||||||
|
import xiaozhi.modules.agent.service.AgentService;
|
||||||
|
import xiaozhi.modules.device.service.DeviceService;
|
||||||
import xiaozhi.modules.security.password.PasswordUtils;
|
import xiaozhi.modules.security.password.PasswordUtils;
|
||||||
import xiaozhi.modules.sys.dao.SysUserDao;
|
import xiaozhi.modules.sys.dao.SysUserDao;
|
||||||
import xiaozhi.modules.sys.dto.AdminPageUserDTO;
|
import xiaozhi.modules.sys.dto.AdminPageUserDTO;
|
||||||
@@ -40,6 +41,10 @@ import xiaozhi.modules.sys.vo.AdminPageUserVO;
|
|||||||
public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntity> implements SysUserService {
|
public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntity> implements SysUserService {
|
||||||
private final SysUserDao sysUserDao;
|
private final SysUserDao sysUserDao;
|
||||||
|
|
||||||
|
private final DeviceService deviceService;
|
||||||
|
|
||||||
|
private final AgentService agentService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SysUserDTO getByUsername(String username) {
|
public SysUserDTO getByUsername(String username) {
|
||||||
QueryWrapper<SysUserEntity> queryWrapper = new QueryWrapper<>();
|
QueryWrapper<SysUserEntity> queryWrapper = new QueryWrapper<>();
|
||||||
@@ -87,10 +92,13 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntit
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void delete(Long[] ids) {
|
public void deleteById(Long id) {
|
||||||
// 删除用户
|
// 删除用户
|
||||||
baseDao.deleteBatchIds(Arrays.asList(ids));
|
baseDao.deleteById(id);
|
||||||
// TODO 除了要删除用户还要删除用户关联的设备,对话,智能体。等此3个功能完善在添加
|
// 删除设备
|
||||||
|
deviceService.deleteByUserId(id);
|
||||||
|
// 删除智能体
|
||||||
|
agentService.deleteById(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -148,17 +156,16 @@ public class SysUserServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntit
|
|||||||
params.put(Constant.LIMIT, dto.getLimit());
|
params.put(Constant.LIMIT, dto.getLimit());
|
||||||
IPage<SysUserEntity> page = baseDao.selectPage(
|
IPage<SysUserEntity> page = baseDao.selectPage(
|
||||||
getPage(params, "id", true),
|
getPage(params, "id", true),
|
||||||
// 定义查询条件
|
new QueryWrapper<SysUserEntity>().eq(StringUtils.isNotBlank(dto.getMobile()), "username",
|
||||||
new QueryWrapper<SysUserEntity>()
|
dto.getMobile()));
|
||||||
// 必须按照手机号码查找
|
// 循环处理page获取回来的数据,返回需要的字段
|
||||||
.eq(StringUtils.isNotBlank(dto.getMobile()), "username", dto.getMobile()));
|
|
||||||
List<AdminPageUserVO> list = page.getRecords().stream().map(user -> {
|
List<AdminPageUserVO> list = page.getRecords().stream().map(user -> {
|
||||||
AdminPageUserVO adminPageUserVO = new AdminPageUserVO();
|
AdminPageUserVO adminPageUserVO = new AdminPageUserVO();
|
||||||
adminPageUserVO.setUserid(user.getId().toString());
|
adminPageUserVO.setUserid(user.getId().toString());
|
||||||
adminPageUserVO.setMobile(user.getUsername());
|
adminPageUserVO.setMobile(user.getUsername());
|
||||||
|
String deviceCount = deviceService.selectCountByUserId(user.getId()).toString();
|
||||||
|
adminPageUserVO.setDeviceCount(deviceCount);
|
||||||
adminPageUserVO.setStatus(user.getStatus());
|
adminPageUserVO.setStatus(user.getStatus());
|
||||||
// TODO 2. 等设备功能写好,获取对应数据
|
|
||||||
adminPageUserVO.setDeviceCount("0");
|
|
||||||
return adminPageUserVO;
|
return adminPageUserVO;
|
||||||
}).toList();
|
}).toList();
|
||||||
return new PageData<>(list, page.getTotal());
|
return new PageData<>(list, page.getTotal());
|
||||||
|
|||||||
+35
@@ -0,0 +1,35 @@
|
|||||||
|
package xiaozhi.modules.sys.service.impl;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import xiaozhi.common.redis.RedisKeys;
|
||||||
|
import xiaozhi.common.redis.RedisUtils;
|
||||||
|
import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||||
|
import xiaozhi.modules.sys.dao.SysUserDao;
|
||||||
|
import xiaozhi.modules.sys.entity.SysUserEntity;
|
||||||
|
import xiaozhi.modules.sys.service.SysUserUtilService;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class SysUserUtilServiceImpl extends BaseServiceImpl<SysUserDao, SysUserEntity> implements SysUserUtilService {
|
||||||
|
|
||||||
|
private RedisUtils redisUtils;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void assignUsername(Long userId, Consumer<String> setter) {
|
||||||
|
String userIdKey = RedisKeys.getUserIdKey(userId);
|
||||||
|
String username = redisUtils.get(userIdKey).toString();
|
||||||
|
if(username != null){
|
||||||
|
setter.accept(username);
|
||||||
|
}else {
|
||||||
|
SysUserEntity entity = baseDao.selectById(userId);
|
||||||
|
if (entity != null) {
|
||||||
|
username = entity.getUsername();
|
||||||
|
redisUtils.set(userIdKey,username,10);
|
||||||
|
setter.accept(username);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,8 @@ id.require=ID\u4E0D\u80FD\u4E3A\u7A7A
|
|||||||
id.null=ID\u5FC5\u987B\u4E3A\u7A7A
|
id.null=ID\u5FC5\u987B\u4E3A\u7A7A
|
||||||
|
|
||||||
sort.number=\u6392\u5E8F\u503C\u4E0D\u80FD\u5C0F\u4E8E0
|
sort.number=\u6392\u5E8F\u503C\u4E0D\u80FD\u5C0F\u4E8E0
|
||||||
|
page.number=\u9875\u6570\u4E0D\u80FD\u5C0F\u4E8E0
|
||||||
|
limit.number=\u5217\u6570\u4E0D\u80FD\u5C0F\u4E8E0
|
||||||
|
|
||||||
sysdict.type.require=\u5B57\u5178\u7C7B\u578B\u4E0D\u80FD\u4E3A\u7A7A
|
sysdict.type.require=\u5B57\u5178\u7C7B\u578B\u4E0D\u80FD\u4E3A\u7A7A
|
||||||
sysdict.name.require=\u5B57\u5178\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
|
sysdict.name.require=\u5B57\u5178\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ id.require=ID can not be empty
|
|||||||
id.null=ID has to be empty
|
id.null=ID has to be empty
|
||||||
|
|
||||||
sort.number=The sort value cannot be less than 0
|
sort.number=The sort value cannot be less than 0
|
||||||
|
page.number=The page value cannot be less than 0
|
||||||
|
limit.number=The limit value cannot be less than 0
|
||||||
|
|
||||||
sysdict.type.require=The dictionary type cannot be empty
|
sysdict.type.require=The dictionary type cannot be empty
|
||||||
sysdict.name.require=The dictionary name cannot be empty
|
sysdict.name.require=The dictionary name cannot be empty
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ id.require=ID\u4E0D\u80FD\u4E3A\u7A7A
|
|||||||
id.null=ID\u5FC5\u987B\u4E3A\u7A7A
|
id.null=ID\u5FC5\u987B\u4E3A\u7A7A
|
||||||
|
|
||||||
sort.number=\u6392\u5E8F\u503C\u4E0D\u80FD\u5C0F\u4E8E0
|
sort.number=\u6392\u5E8F\u503C\u4E0D\u80FD\u5C0F\u4E8E0
|
||||||
|
page.number=\u9801\u6578\u4E0D\u80FD\u5C0F\u4E8E0
|
||||||
|
limit.number=\u5217\u6578\u4E0D\u80FD\u5C0F\u4E8E0
|
||||||
|
|
||||||
sysdict.type.require=\u5B57\u5178\u7C7B\u578B\u4E0D\u80FD\u4E3A\u7A7A
|
sysdict.type.require=\u5B57\u5178\u7C7B\u578B\u4E0D\u80FD\u4E3A\u7A7A
|
||||||
sysdict.name.require=\u5B57\u5178\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
|
sysdict.name.require=\u5B57\u5178\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ id.require=ID\u4E0D\u80FD\u4E3A\u7A7A
|
|||||||
id.null=ID\u5FC5\u987B\u4E3A\u7A7A
|
id.null=ID\u5FC5\u987B\u4E3A\u7A7A
|
||||||
|
|
||||||
sort.number=\u6392\u5E8F\u503C\u4E0D\u80FD\u5C0F\u4E8E0
|
sort.number=\u6392\u5E8F\u503C\u4E0D\u80FD\u5C0F\u4E8E0
|
||||||
|
page.number=\u9875\u6570\u4E0D\u80FD\u5C0F\u4E8E0
|
||||||
|
limit.number=\u5217\u6570\u4E0D\u80FD\u5C0F\u4E8E0
|
||||||
|
|
||||||
sysdict.type.require=\u5B57\u5178\u7C7B\u578B\u4E0D\u80FD\u4E3A\u7A7A
|
sysdict.type.require=\u5B57\u5178\u7C7B\u578B\u4E0D\u80FD\u4E3A\u7A7A
|
||||||
sysdict.name.require=\u5B57\u5178\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
|
sysdict.name.require=\u5B57\u5178\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
本文档是开发类文档,如需部署小智服务端,[点击这里查看部署教程](.././FAQ.md#%E4%BD%BF%E7%94%A8%E6%96%B9%E5%BC%8F-)
|
本文档是开发类文档,如需部署小智服务端,[点击这里查看部署教程](../../../docs/Deployment.md)
|
||||||
|
|
||||||
# xiaozhi
|
# xiaozhi
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
import RequestService from '../httpRequest'
|
||||||
|
import {getServiceUrl} from '../api'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
/**
|
||||||
|
* 设备激活接口
|
||||||
|
* @param {string} code 激活码
|
||||||
|
* @param {function} callback 回调函数
|
||||||
|
*/
|
||||||
|
activateDevice(code, callback) {
|
||||||
|
RequestService.sendRequest()
|
||||||
|
.url(`${getServiceUrl()}/api/v1/ota/activation`)
|
||||||
|
.method('GET')
|
||||||
|
.query({code})
|
||||||
|
.success((res) => {
|
||||||
|
RequestService.clearRequestTime()
|
||||||
|
callback(res)
|
||||||
|
})
|
||||||
|
.fail((err) => {
|
||||||
|
console.error('设备激活失败:', err)
|
||||||
|
RequestService.reAjaxFun(() => {
|
||||||
|
this.activateDevice(code, callback)
|
||||||
|
})
|
||||||
|
}).send()
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查OTA版本和设备激活状态
|
||||||
|
* @param {object} deviceInfo 设备信息对象
|
||||||
|
* @param {string} deviceId 设备唯一标识
|
||||||
|
* @param {string} clientId 客户端标识
|
||||||
|
* @param {function} callback 回调函数
|
||||||
|
*/
|
||||||
|
checkOtaVersion(deviceInfo, deviceId, clientId, callback) {
|
||||||
|
RequestService.sendRequest()
|
||||||
|
.url(`${getServiceUrl()}/api/v1/ota`)
|
||||||
|
.method('POST')
|
||||||
|
.header({
|
||||||
|
'Device-Id': deviceId,
|
||||||
|
'Client-Id': clientId
|
||||||
|
})
|
||||||
|
.data(deviceInfo)
|
||||||
|
.success((res) => {
|
||||||
|
RequestService.clearRequestTime()
|
||||||
|
callback(res)
|
||||||
|
})
|
||||||
|
.fail((err) => {
|
||||||
|
console.error('检查OTA版本失败:', err)
|
||||||
|
RequestService.reAjaxFun(() => {
|
||||||
|
this.checkOtaVersion(deviceInfo, deviceId, clientId, callback)
|
||||||
|
})
|
||||||
|
}).send()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@
|
|||||||
<span style="font-size: 11px"> 验证码:</span>
|
<span style="font-size: 11px"> 验证码:</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-46" style="margin-top: 12px;">
|
<div class="input-46" style="margin-top: 12px;">
|
||||||
<el-input placeholder="请输入设备播报的6位数验证码.." v-model="deviceCode" />
|
<el-input placeholder="请输入设备播报的6位数验证码.." v-model="deviceCode" @keyup.enter.native="confirm"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex;margin: 15px 15px;gap: 7px;">
|
<div style="display: flex;margin: 15px 15px;gap: 7px;">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-dialog :visible.sync="visible" width="400px" center>
|
<el-dialog :visible.sync="visible" width="400px" center @open="handleOpen">
|
||||||
<div style="margin: 0 10px 10px;display: flex;align-items: center;gap: 10px;font-weight: 700;font-size: 20px;text-align: left;color: #3d4566;">
|
<div style="margin: 0 10px 10px;display: flex;align-items: center;gap: 10px;font-weight: 700;font-size: 20px;text-align: left;color: #3d4566;">
|
||||||
<div style="width: 40px;height: 40px;border-radius: 50%;background: #5778ff;display: flex;align-items: center;justify-content: center;">
|
<div style="width: 40px;height: 40px;border-radius: 50%;background: #5778ff;display: flex;align-items: center;justify-content: center;">
|
||||||
<img loading="lazy" src="@/assets/home/equipment.png" alt="" style="width: 18px;height: 15px;" />
|
<img loading="lazy" src="@/assets/home/equipment.png" alt="" style="width: 18px;height: 15px;" />
|
||||||
@@ -12,7 +12,11 @@
|
|||||||
<div style="color: red;display: inline-block;">*</div> 智慧体名称:
|
<div style="color: red;display: inline-block;">*</div> 智慧体名称:
|
||||||
</div>
|
</div>
|
||||||
<div class="input-46" style="margin-top: 12px;">
|
<div class="input-46" style="margin-top: 12px;">
|
||||||
<el-input placeholder="请输入智能体名称.." v-model="wisdomBodyName" />
|
<el-input
|
||||||
|
ref="inputRef"
|
||||||
|
placeholder="请输入智能体名称.."
|
||||||
|
v-model="wisdomBodyName"
|
||||||
|
@keyup.enter.native="confirm" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex;margin: 15px 15px;gap: 7px;">
|
<div style="display: flex;margin: 15px 15px;gap: 7px;">
|
||||||
@@ -31,16 +35,23 @@
|
|||||||
<script>
|
<script>
|
||||||
import userApi from '@/apis/module/agent';
|
import userApi from '@/apis/module/agent';
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AddWisdomBodyDialog',
|
name: 'AddWisdomBodyDialog',
|
||||||
props: {
|
props: {
|
||||||
visible: { type: Boolean, required: true }
|
visible: { type: Boolean, required: true }
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return { wisdomBodyName: "" }
|
return {
|
||||||
|
wisdomBodyName: "",
|
||||||
|
inputRef: null
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
handleOpen() {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.$refs.inputRef.focus();
|
||||||
|
});
|
||||||
|
},
|
||||||
confirm() {
|
confirm() {
|
||||||
if (!this.wisdomBodyName.trim()) {
|
if (!this.wisdomBodyName.trim()) {
|
||||||
this.$message.error('请输入智能体名称');
|
this.$message.error('请输入智能体名称');
|
||||||
@@ -65,7 +76,6 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
.input-46 {
|
.input-46 {
|
||||||
border: 1px solid #e4e6ef;
|
border: 1px solid #e4e6ef;
|
||||||
background: #f6f8fb;
|
background: #f6f8fb;
|
||||||
|
|||||||
@@ -109,23 +109,26 @@ export default {
|
|||||||
// 处理搜索
|
// 处理搜索
|
||||||
handleSearch() {
|
handleSearch() {
|
||||||
const searchValue = this.search.trim();
|
const searchValue = this.search.trim();
|
||||||
let filteredDevices;
|
|
||||||
|
|
||||||
|
// 如果搜索内容为空,触发重置事件
|
||||||
if (!searchValue) {
|
if (!searchValue) {
|
||||||
// 当搜索内容为空时,显示原始完整列表
|
this.$emit('search-reset');
|
||||||
filteredDevices = this.$parent.originalDevices;
|
return;
|
||||||
} else {
|
|
||||||
// 过滤逻辑
|
|
||||||
filteredDevices = this.devices.filter(device => {
|
|
||||||
return device.agentName.includes(searchValue) ||
|
|
||||||
device.ttsModelName.includes(searchValue) ||
|
|
||||||
device.ttsVoiceName.includes(searchValue);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.$emit('search-result', filteredDevices);
|
try {
|
||||||
|
// 创建不区分大小写的正则表达式
|
||||||
|
const regex = new RegExp(searchValue, 'i');
|
||||||
|
// 触发搜索事件,将正则表达式传递给父组件
|
||||||
|
this.$emit('search', regex);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('正则表达式创建失败:', error);
|
||||||
|
this.$message.error({
|
||||||
|
message: '搜索关键词格式不正确',
|
||||||
|
showClose: true
|
||||||
|
});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// 显示修改密码弹窗
|
// 显示修改密码弹窗
|
||||||
showChangePasswordDialog() {
|
showChangePasswordDialog() {
|
||||||
this.isChangePasswordDialogVisible = true;
|
this.isChangePasswordDialogVisible = true;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="welcome">
|
<div class="welcome">
|
||||||
<!-- 公共头部 -->
|
<!-- 公共头部 -->
|
||||||
<HeaderBar :devices="devices" @search-result="handleSearchResult" />
|
<HeaderBar :devices="devices" @search="handleSearch" @search-reset="handleSearchReset" />
|
||||||
<el-main style="padding: 20px;display: flex;flex-direction: column;">
|
<el-main style="padding: 20px;display: flex;flex-direction: column;">
|
||||||
<div>
|
<div>
|
||||||
<!-- 首页内容 -->
|
<!-- 首页内容 -->
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex;flex-wrap: wrap;margin-top: 20px;gap: 20px;justify-content: flex-start;box-sizing: border-box;">
|
<div class="device-list-container">
|
||||||
<DeviceItem v-for="(item,index) in devices" :key="index" :device="item"
|
<DeviceItem v-for="(item,index) in devices" :key="index" :device="item"
|
||||||
@configure="goToRoleConfig"
|
@configure="goToRoleConfig"
|
||||||
@deviceManage="handleDeviceManage"
|
@deviceManage="handleDeviceManage"
|
||||||
@@ -60,6 +60,8 @@ export default {
|
|||||||
addDeviceDialogVisible: false,
|
addDeviceDialogVisible: false,
|
||||||
devices: [],
|
devices: [],
|
||||||
originalDevices: [],
|
originalDevices: [],
|
||||||
|
isSearching: false,
|
||||||
|
searchRegex: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -83,22 +85,42 @@ export default {
|
|||||||
handleDeviceManage() {
|
handleDeviceManage() {
|
||||||
this.$router.push('/device-management');
|
this.$router.push('/device-management');
|
||||||
},
|
},
|
||||||
// 获取智能体列表
|
handleSearch(regex) {
|
||||||
fetchAgentList() {
|
this.isSearching = true;
|
||||||
import('@/apis/module/agent').then(({ default: userApi }) => {
|
this.searchRegex = regex;
|
||||||
userApi.getAgentList(({data}) => {
|
this.applySearchFilter();
|
||||||
this.originalDevices = data.data.map(item => ({
|
},
|
||||||
...item,
|
handleSearchReset() {
|
||||||
agentId: item.id // 字段映射
|
this.isSearching = false;
|
||||||
}));
|
this.searchRegex = null;
|
||||||
this.devices = this.originalDevices;
|
this.devices = [...this.originalDevices];
|
||||||
});
|
},
|
||||||
|
applySearchFilter() {
|
||||||
|
if (!this.isSearching || !this.searchRegex) {
|
||||||
|
this.devices = [...this.originalDevices];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.devices = this.originalDevices.filter(device => {
|
||||||
|
return this.searchRegex.test(device.agentName);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
// 搜索更新智能体列表
|
// 搜索更新智能体列表
|
||||||
handleSearchResult(filteredList) {
|
handleSearchResult(filteredList) {
|
||||||
this.devices = filteredList; // 更新设备列表
|
this.devices = filteredList; // 更新设备列表
|
||||||
},
|
},
|
||||||
|
// 获取智能体列表
|
||||||
|
fetchAgentList() {
|
||||||
|
import('@/apis/module/agent').then(({ default: userApi }) => {
|
||||||
|
userApi.getAgentList(({data}) => {
|
||||||
|
this.originalDevices = data.data.map(item => ({
|
||||||
|
...item,
|
||||||
|
agentId: item.id // 字段映射
|
||||||
|
}));
|
||||||
|
this.handleSearchReset(); // 重置搜索状态
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
// 删除智能体
|
// 删除智能体
|
||||||
handleDeleteAgent(agentId) {
|
handleDeleteAgent(agentId) {
|
||||||
this.$confirm('确定要删除该智能体吗?', '提示', {
|
this.$confirm('确定要删除该智能体吗?', '提示', {
|
||||||
@@ -220,4 +242,27 @@ export default {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.device-list-container {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||||
|
gap: 30px;
|
||||||
|
padding: 30px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 在 DeviceItem.vue 的样式中 */
|
||||||
|
.device-item {
|
||||||
|
margin: 0 !important; /* 避免冲突 */
|
||||||
|
width: auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
margin-top: auto;
|
||||||
|
padding-top: 30px;
|
||||||
|
color: #979db1;
|
||||||
|
text-align: center; /* 居中显示 */
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
@@ -10,7 +10,10 @@ import requests
|
|||||||
|
|
||||||
def get_project_dir():
|
def get_project_dir():
|
||||||
"""获取项目根目录"""
|
"""获取项目根目录"""
|
||||||
return os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + '/'
|
return (
|
||||||
|
os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
+ "/"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def get_local_ip():
|
def get_local_ip():
|
||||||
@@ -24,6 +27,7 @@ def get_local_ip():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return "127.0.0.1"
|
return "127.0.0.1"
|
||||||
|
|
||||||
|
|
||||||
def is_private_ip(ip_addr):
|
def is_private_ip(ip_addr):
|
||||||
"""
|
"""
|
||||||
Check if an IP address is a private IP address (compatible with IPv4 and IPv6).
|
Check if an IP address is a private IP address (compatible with IPv4 and IPv6).
|
||||||
@@ -33,46 +37,48 @@ def is_private_ip(ip_addr):
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
# Validate IPv4 or IPv6 address format
|
# Validate IPv4 or IPv6 address format
|
||||||
if not re.match(r"^(\d{1,3}\.){3}\d{1,3}$|^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$", ip_addr):
|
if not re.match(
|
||||||
|
r"^(\d{1,3}\.){3}\d{1,3}$|^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$", ip_addr
|
||||||
|
):
|
||||||
return False # Invalid IP address format
|
return False # Invalid IP address format
|
||||||
|
|
||||||
# IPv4 private address ranges
|
# IPv4 private address ranges
|
||||||
if '.' in ip_addr: # IPv4 address
|
if "." in ip_addr: # IPv4 address
|
||||||
ip_parts = list(map(int, ip_addr.split('.')))
|
ip_parts = list(map(int, ip_addr.split(".")))
|
||||||
if ip_parts[0] == 10:
|
if ip_parts[0] == 10:
|
||||||
return True # 10.0.0.0/8 range
|
return True # 10.0.0.0/8 range
|
||||||
elif ip_parts[0] == 172 and 16 <= ip_parts[1] <= 31:
|
elif ip_parts[0] == 172 and 16 <= ip_parts[1] <= 31:
|
||||||
return True # 172.16.0.0/12 range
|
return True # 172.16.0.0/12 range
|
||||||
elif ip_parts[0] == 192 and ip_parts[1] == 168:
|
elif ip_parts[0] == 192 and ip_parts[1] == 168:
|
||||||
return True # 192.168.0.0/16 range
|
return True # 192.168.0.0/16 range
|
||||||
elif ip_addr == '127.0.0.1':
|
elif ip_addr == "127.0.0.1":
|
||||||
return True # Loopback address
|
return True # Loopback address
|
||||||
elif ip_parts[0] == 169 and ip_parts[1] == 254:
|
elif ip_parts[0] == 169 and ip_parts[1] == 254:
|
||||||
return True # Link-local address 169.254.0.0/16
|
return True # Link-local address 169.254.0.0/16
|
||||||
else:
|
else:
|
||||||
return False # Not a private IPv4 address
|
return False # Not a private IPv4 address
|
||||||
else: # IPv6 address
|
else: # IPv6 address
|
||||||
ip_addr = ip_addr.lower()
|
ip_addr = ip_addr.lower()
|
||||||
if ip_addr.startswith('fc00:') or ip_addr.startswith('fd00:'):
|
if ip_addr.startswith("fc00:") or ip_addr.startswith("fd00:"):
|
||||||
return True # Unique Local Addresses (FC00::/7)
|
return True # Unique Local Addresses (FC00::/7)
|
||||||
elif ip_addr == '::1':
|
elif ip_addr == "::1":
|
||||||
return True # Loopback address
|
return True # Loopback address
|
||||||
elif ip_addr.startswith('fe80:'):
|
elif ip_addr.startswith("fe80:"):
|
||||||
return True # Link-local unicast addresses (FE80::/10)
|
return True # Link-local unicast addresses (FE80::/10)
|
||||||
else:
|
else:
|
||||||
return False # Not a private IPv6 address
|
return False # Not a private IPv6 address
|
||||||
|
|
||||||
except (ValueError, IndexError):
|
except (ValueError, IndexError):
|
||||||
return False # IP address format error or insufficient segments
|
return False # IP address format error or insufficient segments
|
||||||
|
|
||||||
|
|
||||||
def get_ip_info(ip_addr):
|
def get_ip_info(ip_addr):
|
||||||
try:
|
try:
|
||||||
url = "https://whois.pconline.com.cn/ipJson.jsp?json=true"
|
if is_private_ip(ip_addr):
|
||||||
|
ip_addr = ""
|
||||||
|
url = f"https://whois.pconline.com.cn/ipJson.jsp?json=true&ip={ip_addr}"
|
||||||
resp = requests.get(url).json()
|
resp = requests.get(url).json()
|
||||||
|
ip_info = {"city": resp.get("city")}
|
||||||
ip_info = {
|
|
||||||
"city": resp.get("city")
|
|
||||||
}
|
|
||||||
return ip_info
|
return ip_info
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.error(f"Error getting client ip info: {e}")
|
logging.error(f"Error getting client ip info: {e}")
|
||||||
@@ -87,7 +93,7 @@ def read_config(config_path):
|
|||||||
|
|
||||||
def write_json_file(file_path, data):
|
def write_json_file(file_path, data):
|
||||||
"""将数据写入 JSON 文件"""
|
"""将数据写入 JSON 文件"""
|
||||||
with open(file_path, 'w', encoding='utf-8') as file:
|
with open(file_path, "w", encoding="utf-8") as file:
|
||||||
json.dump(data, file, ensure_ascii=False, indent=4)
|
json.dump(data, file, ensure_ascii=False, indent=4)
|
||||||
|
|
||||||
|
|
||||||
@@ -95,21 +101,28 @@ def is_punctuation_or_emoji(char):
|
|||||||
"""检查字符是否为空格、指定标点或表情符号"""
|
"""检查字符是否为空格、指定标点或表情符号"""
|
||||||
# 定义需要去除的中英文标点(包括全角/半角)
|
# 定义需要去除的中英文标点(包括全角/半角)
|
||||||
punctuation_set = {
|
punctuation_set = {
|
||||||
',', ',', # 中文逗号 + 英文逗号
|
",",
|
||||||
'。', '.', # 中文句号 + 英文句号
|
",", # 中文逗号 + 英文逗号
|
||||||
'!', '!', # 中文感叹号 + 英文感叹号
|
"。",
|
||||||
'-', '-', # 英文连字符 + 中文全角横线
|
".", # 中文句号 + 英文句号
|
||||||
'、' # 中文顿号
|
"!",
|
||||||
|
"!", # 中文感叹号 + 英文感叹号
|
||||||
|
"-",
|
||||||
|
"-", # 英文连字符 + 中文全角横线
|
||||||
|
"、", # 中文顿号
|
||||||
}
|
}
|
||||||
if char.isspace() or char in punctuation_set:
|
if char.isspace() or char in punctuation_set:
|
||||||
return True
|
return True
|
||||||
# 检查表情符号(保留原有逻辑)
|
# 检查表情符号(保留原有逻辑)
|
||||||
code_point = ord(char)
|
code_point = ord(char)
|
||||||
emoji_ranges = [
|
emoji_ranges = [
|
||||||
(0x1F600, 0x1F64F), (0x1F300, 0x1F5FF),
|
(0x1F600, 0x1F64F),
|
||||||
(0x1F680, 0x1F6FF), (0x1F900, 0x1F9FF),
|
(0x1F300, 0x1F5FF),
|
||||||
(0x1FA70, 0x1FAFF), (0x2600, 0x26FF),
|
(0x1F680, 0x1F6FF),
|
||||||
(0x2700, 0x27BF)
|
(0x1F900, 0x1F9FF),
|
||||||
|
(0x1FA70, 0x1FAFF),
|
||||||
|
(0x2600, 0x26FF),
|
||||||
|
(0x2700, 0x27BF),
|
||||||
]
|
]
|
||||||
return any(start <= code_point <= end for start, end in emoji_ranges)
|
return any(start <= code_point <= end for start, end in emoji_ranges)
|
||||||
|
|
||||||
@@ -125,27 +138,42 @@ def get_string_no_punctuation_or_emoji(s):
|
|||||||
end = len(chars) - 1
|
end = len(chars) - 1
|
||||||
while end >= start and is_punctuation_or_emoji(chars[end]):
|
while end >= start and is_punctuation_or_emoji(chars[end]):
|
||||||
end -= 1
|
end -= 1
|
||||||
return ''.join(chars[start:end + 1])
|
return "".join(chars[start : end + 1])
|
||||||
|
|
||||||
|
|
||||||
def remove_punctuation_and_length(text):
|
def remove_punctuation_and_length(text):
|
||||||
# 全角符号和半角符号的Unicode范围
|
# 全角符号和半角符号的Unicode范围
|
||||||
full_width_punctuations = '!"#$%&'()*+,-。/:;<=>?@[\]^_`{|}~'
|
full_width_punctuations = (
|
||||||
|
"!"#$%&'()*+,-。/:;<=>?@[\]^_`{|}~"
|
||||||
|
)
|
||||||
half_width_punctuations = r'!"#$%&\'()*+,-./:;<=>?@[\]^_`{|}~'
|
half_width_punctuations = r'!"#$%&\'()*+,-./:;<=>?@[\]^_`{|}~'
|
||||||
space = ' ' # 半角空格
|
space = " " # 半角空格
|
||||||
full_width_space = ' ' # 全角空格
|
full_width_space = " " # 全角空格
|
||||||
|
|
||||||
# 去除全角和半角符号以及空格
|
# 去除全角和半角符号以及空格
|
||||||
result = ''.join([char for char in text if
|
result = "".join(
|
||||||
char not in full_width_punctuations and char not in half_width_punctuations and char not in space and char not in full_width_space])
|
[
|
||||||
|
char
|
||||||
|
for char in text
|
||||||
|
if char not in full_width_punctuations
|
||||||
|
and char not in half_width_punctuations
|
||||||
|
and char not in space
|
||||||
|
and char not in full_width_space
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
if result == "Yeah":
|
if result == "Yeah":
|
||||||
return 0, ""
|
return 0, ""
|
||||||
return len(result), result
|
return len(result), result
|
||||||
|
|
||||||
|
|
||||||
def check_model_key(modelType, modelKey):
|
def check_model_key(modelType, modelKey):
|
||||||
if "你" in modelKey:
|
if "你" in modelKey:
|
||||||
logging.error("你还没配置" + modelType + "的密钥,请在配置文件中配置密钥,否则无法正常工作")
|
logging.error(
|
||||||
|
"你还没配置"
|
||||||
|
+ modelType
|
||||||
|
+ "的密钥,请在配置文件中配置密钥,否则无法正常工作"
|
||||||
|
)
|
||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -155,15 +183,15 @@ def check_ffmpeg_installed():
|
|||||||
try:
|
try:
|
||||||
# 执行ffmpeg -version命令,并捕获输出
|
# 执行ffmpeg -version命令,并捕获输出
|
||||||
result = subprocess.run(
|
result = subprocess.run(
|
||||||
['ffmpeg', '-version'],
|
["ffmpeg", "-version"],
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE,
|
stderr=subprocess.PIPE,
|
||||||
text=True,
|
text=True,
|
||||||
check=True # 如果返回码非零则抛出异常
|
check=True, # 如果返回码非零则抛出异常
|
||||||
)
|
)
|
||||||
# 检查输出中是否包含版本信息(可选)
|
# 检查输出中是否包含版本信息(可选)
|
||||||
output = result.stdout + result.stderr
|
output = result.stdout + result.stderr
|
||||||
if 'ffmpeg version' in output.lower():
|
if "ffmpeg version" in output.lower():
|
||||||
ffmpeg_installed = True
|
ffmpeg_installed = True
|
||||||
return False
|
return False
|
||||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||||
@@ -175,11 +203,12 @@ def check_ffmpeg_installed():
|
|||||||
error_msg += "1、按照项目的安装文档,正确进入conda环境\n"
|
error_msg += "1、按照项目的安装文档,正确进入conda环境\n"
|
||||||
error_msg += "2、查阅安装文档,如何在conda环境中安装ffmpeg\n"
|
error_msg += "2、查阅安装文档,如何在conda环境中安装ffmpeg\n"
|
||||||
raise ValueError(error_msg)
|
raise ValueError(error_msg)
|
||||||
|
|
||||||
|
|
||||||
def extract_json_from_string(input_string):
|
def extract_json_from_string(input_string):
|
||||||
"""提取字符串中的 JSON 部分"""
|
"""提取字符串中的 JSON 部分"""
|
||||||
pattern = r'(\{.*\})'
|
pattern = r"(\{.*\})"
|
||||||
match = re.search(pattern, input_string)
|
match = re.search(pattern, input_string)
|
||||||
if match:
|
if match:
|
||||||
return match.group(1) # 返回提取的 JSON 字符串
|
return match.group(1) # 返回提取的 JSON 字符串
|
||||||
return None
|
return None
|
||||||
|
|||||||
Reference in New Issue
Block a user