diff --git a/main/manager-api/pom.xml b/main/manager-api/pom.xml
index eacbdff9..0857ec3c 100644
--- a/main/manager-api/pom.xml
+++ b/main/manager-api/pom.xml
@@ -168,6 +168,12 @@
lombok
true
+
+
+ org.apache.logging.log4j
+ log4j-slf4j-impl
+ 2.20.0
+
diff --git a/main/manager-api/src/main/java/xiaozhi/common/controller/BaseController.java b/main/manager-api/src/main/java/xiaozhi/common/controller/BaseController.java
new file mode 100644
index 00000000..8cda64c0
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/common/controller/BaseController.java
@@ -0,0 +1,46 @@
+package xiaozhi.common.controller;
+
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.util.Enumeration;
+import java.util.HashMap;
+
+public class BaseController {
+
+ @Autowired
+ protected HttpServletRequest request;
+
+ /**
+ * 获取请求头
+ *
+ * @return
+ */
+ protected HashMap getRequestHeaders() {
+ HashMap requestHeaders = new HashMap();
+ Enumeration headerNames = request.getHeaderNames();
+ while (headerNames.hasMoreElements()) {
+ String headerName = headerNames.nextElement();
+ String headerValue = request.getHeader(headerName);
+ requestHeaders.put(headerName, headerValue);
+ }
+ return requestHeaders;
+ }
+
+ /**
+ * 获取请求参数
+ *
+ * @return
+ */
+ protected HashMap getRequestParams() {
+ HashMap requestParams = new HashMap();
+ Enumeration paramNames = request.getParameterNames();
+ while (paramNames.hasMoreElements()) {
+ String paramName = paramNames.nextElement();
+ String paramValue = request.getParameter(paramName);
+ requestParams.put(paramName, paramValue);
+ }
+ return requestParams;
+ }
+
+}
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/UserAgentController.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/UserAgentController.java
new file mode 100644
index 00000000..0e13fac2
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/UserAgentController.java
@@ -0,0 +1,195 @@
+package xiaozhi.modules.agent.controller;
+
+import cn.hutool.json.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.beanutils.BeanUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.web.bind.annotation.*;
+import xiaozhi.common.controller.BaseController;
+import xiaozhi.common.redis.RedisUtils;
+import xiaozhi.common.user.UserDetail;
+import xiaozhi.common.utils.Result;
+import xiaozhi.modules.agent.domain.Agent;
+import xiaozhi.modules.agent.domain.AgentTemplate;
+import xiaozhi.modules.agent.service.AgentService;
+import xiaozhi.modules.agent.service.AgentTemplateService;
+import xiaozhi.modules.agent.vo.AgentConfigVO;
+import xiaozhi.modules.agent.vo.AgentVO;
+import xiaozhi.modules.device.domain.Device;
+import xiaozhi.modules.device.service.DeviceService;
+import xiaozhi.modules.model.domain.ModelConfig;
+import xiaozhi.modules.model.domain.TtsVoice;
+import xiaozhi.modules.model.service.ModelConfigService;
+import xiaozhi.modules.model.service.TtsVoiceService;
+import xiaozhi.modules.security.user.SecurityUser;
+
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.UUID;
+
+@Slf4j
+@Tag(name = "智能体管理")
+@AllArgsConstructor
+@RestController
+@RequestMapping("/user/agent")
+public class UserAgentController extends BaseController {
+ private final AgentService agentService;
+ private final AgentTemplateService agentTemplateService;
+ private final ModelConfigService modelConfigService;
+ private final TtsVoiceService ttsVoiceService;
+ private final DeviceService deviceService;
+ private final RedisUtils redisUtils;
+
+ @PostMapping
+ @Operation(summary = "添加智能体")
+ public Result addAgent(@RequestBody Agent agent) {
+ UserDetail user = SecurityUser.getUser();
+ if(StringUtils.isBlank(agent.getAgentName())){
+ log.error("智能体名称不能为空");
+ }
+ Agent oldAgent = agentService.getOne(new QueryWrapper().eq("agent_name", agent.getAgentName()));
+ if(ObjectUtils.isNull(oldAgent)){
+ AgentTemplate agentTemplate = agentTemplateService.getOne(new QueryWrapper().eq("is_default", 1));
+ if(ObjectUtils.isNull(agentTemplate)){
+
+ }else{
+ try {
+ oldAgent = new Agent();
+ BeanUtils.copyProperties(oldAgent, agentTemplate);
+ oldAgent.setId(UUID.randomUUID().toString().replace("-",""));
+ oldAgent.setAgentName(agent.getAgentName());
+ oldAgent.setUserId(user.getId());
+ oldAgent.setCreator(user.getId());
+ oldAgent.setCreatedAt(new Date());
+ agentService.save(oldAgent);
+ } catch (Exception e) {
+ log.error("对象赋值异常", e);
+ }
+ }
+ }else{
+
+ }
+ return new Result().ok(agent);
+ }
+
+ @GetMapping
+ @Operation(summary = "智能体列表")
+ public Result> agentList() {
+ UserDetail user = SecurityUser.getUser();
+ List agents = agentService.list(new QueryWrapper().eq("user_id",user.getId()));
+ List list = new ArrayList<>();
+ this.convertAgetVOList(list, agents);
+ return new Result>().ok(list);
+ }
+
+ @GetMapping("/loadAgentConfig/{deviceId}")
+ @Operation(summary = "下载智能体配置")
+// public Result loadAgentConfig(@PathVariable String deviceId){
+ public Result loadAgentConfig(@PathVariable String deviceId){
+ Device device = deviceService.getOne(new QueryWrapper().eq("mac_address", deviceId.toUpperCase()));
+ if(ObjectUtils.isNull(device)){
+ return new Result().error("设备不存在");
+ }
+ Agent agent = agentService.getOne(new QueryWrapper().eq("id", device.getAgentId()));
+ if(ObjectUtils.isNull(agent)){
+ return new Result().error("智能体不存在");
+ }
+ AgentConfigVO agentConfigVO = new AgentConfigVO();
+// return new Result().ok(agentConfigVO);
+ String json = "{\n" +
+ " \"ASR\": {\n" +
+ " \"FunASR\": {\n" +
+ " \"model_dir\": \"models/SenseVoiceSmall\",\n" +
+ " \"output_dir\": \"tmp/\",\n" +
+ " \"type\": \"fun_local\"\n" +
+ " }\n" +
+ " },\n" +
+ " \"LLM\": {\n" +
+ " \"ChatGLMLLM\": {\n" +
+ " \"api_key\": \"0415dad4014847babc3e3f03024c50a3.qH7FgTy5Yawc85fl\",\n" +
+ " \"model_name\": \"glm-4-flash\",\n" +
+ " \"type\": \"openai\",\n" +
+ " \"url\": \"https://open.bigmodel.cn/api/paas/v4/\"\n" +
+ " }\n" +
+ " },\n" +
+ " \"TTS\": {\n" +
+ " \"DoubaoTTS\": {\n" +
+ " \"access_token\": \"hrnx22F9WutWBm7YJzE62r_Z1myUmHEL\",\n" +
+ " \"api_url\": \"https://openspeech.bytedance.com/api/v1/tts\",\n" +
+ " \"appid\": \"6295576095\",\n" +
+ " \"authorization\": \"Bearer;\",\n" +
+ " \"cluster\": \"volcano_tts\",\n" +
+ " \"output_dir\": \"tmp/\",\n" +
+ " \"type\": \"doubao\",\n" +
+ " \"voice\": \"BV034_streaming\"\n" +
+ " }\n" +
+ " },\n" +
+ " \"VAD\": {\n" +
+ " \"SileroVAD\": {\n" +
+ " \"min_silence_duration_ms\": 700,\n" +
+ " \"model_dir\": \"models/snakers4_silero-vad\",\n" +
+ " \"threshold\": 0.5\n" +
+ " }\n" +
+ " },\n" +
+ " \"auth_code\": \"642365\",\n" +
+ " \"prompt\": \"你是一个叫小优的女孩,来自优享生活公司的AI智能体,声音好听,习惯简短表达,爱用网络梗。\\n请注意,要像一个人一样说话,请不要回复表情符号、代码、和xml标签。\\n现在我正在和你进行语音聊天,我们开始吧。\\n如果用户希望结束对话,请在最后说“拜拜”或“再见”。\\n\",\n" +
+ " \"selected_module\": {\n" +
+ " \"ASR\": \"FunASR\",\n" +
+ " \"Intent\": \"function_call\",\n" +
+ " \"LLM\": \"ChatGLMLLM\",\n" +
+ " \"Memory\": \"mem0ai\",\n" +
+ " \"TTS\": \"DoubaoTTS\",\n" +
+ " \"VAD\": \"SileroVAD\"\n" +
+ " },\n" +
+ " \"owner\":\"18600806164\"\n" +
+ "}";
+
+ return new Result().ok(new JSONObject(json));
+ }
+
+ /**
+ * 将Agent对象列表转换为AgentVO对象列表
+ * 此方法遍历Agent对象列表,将每个Agent对象转换为AgentVO对象,并添加到AgentVO列表中
+ *
+ * @param agentVOList 转换后的AgentVO对象列表
+ * @param agentList 原始的Agent对象列表
+ */
+ private void convertAgetVOList(List agentVOList, List agentList){
+ // 遍历Agent对象列表
+ for(Agent agent : agentList) {
+ // 创建一个新的AgentVO对象
+ AgentVO agentVO = new AgentVO();
+ // 将当前Agent对象的属性值转换并设置到AgentVO对象中
+ this.convertAgentVO(agentVO, agent);
+ // 将转换后的AgentVO对象添加到AgentVO列表中
+ agentVOList.add(agentVO);
+ }
+ }
+ private void convertAgentVO(AgentVO agentVO, Agent agent){
+ try {
+ BeanUtils.copyProperties(agentVO, agent);
+ agentVO.setTtsModelName("未知");
+ TtsVoice ttsVoice = ttsVoiceService.getOne(new QueryWrapper().eq("id", agent.getTtsVoiceId()));
+ if(ObjectUtils.isNotNull(ttsVoice)){
+ agentVO.setTtsModelName(ttsVoice.getName());
+ }
+ agentVO.setLlmModelName("未知");
+ ModelConfig modelConfig = modelConfigService.getOne(new QueryWrapper().eq("id", agent.getLlmModelId()));
+ if(ObjectUtils.isNotNull(modelConfig)){
+ agentVO.setLlmModelName(modelConfig.getModelName());
+ }
+ agentVO.setLastConnectedAt("今天");
+ agentVO.setDeviceCount(0L);
+ long deviceCount = deviceService.count(new QueryWrapper().eq("agent_id", agent.getId()));
+ agentVO.setDeviceCount(deviceCount);
+ } catch (Exception e) {
+ log.error("[convertAgentVO]对象转换报错",e);
+ }
+ }
+}
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/domain/Agent.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/domain/Agent.java
new file mode 100644
index 00000000..74f75491
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/domain/Agent.java
@@ -0,0 +1,116 @@
+package xiaozhi.modules.agent.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.util.Date;
+import lombok.Data;
+
+/**
+ * 智能体配置表
+ * @TableName ai_agent
+ */
+@TableName(value ="ai_agent")
+@Data
+public class Agent implements Serializable {
+ /**
+ * 智能体唯一标识
+ */
+ @TableId
+ private String id;
+
+ /**
+ * 所属用户 ID
+ */
+ private Long userId;
+
+ /**
+ * 智能体编码
+ */
+ private String agentCode;
+
+ /**
+ * 智能体名称
+ */
+ private String agentName;
+
+ /**
+ * 语音识别模型标识
+ */
+ private String asrModelId;
+
+ /**
+ * 语音活动检测标识
+ */
+ private String vadModelId;
+
+ /**
+ * 大语言模型标识
+ */
+ private String llmModelId;
+
+ /**
+ * 语音合成模型标识
+ */
+ private String ttsModelId;
+
+ /**
+ * 音色标识
+ */
+ private String ttsVoiceId;
+
+ /**
+ * 记忆模型标识
+ */
+ private String memModelId;
+
+ /**
+ * 意图模型标识
+ */
+ private String intentModelId;
+
+ /**
+ * 角色设定参数
+ */
+ private String systemPrompt;
+
+ /**
+ * 语言编码
+ */
+ private String langCode;
+
+ /**
+ * 交互语种
+ */
+ private String language;
+
+ /**
+ * 排序权重
+ */
+ private Integer sort;
+
+ /**
+ * 创建者 ID
+ */
+ private Long creator;
+
+ /**
+ * 创建时间
+ */
+ private Date createdAt;
+
+ /**
+ * 更新者 ID
+ */
+ private Long updater;
+
+ /**
+ * 更新时间
+ */
+ private Date updatedAt;
+
+ @TableField(exist = false)
+ private static final long serialVersionUID = 1L;
+}
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/domain/AgentTemplate.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/domain/AgentTemplate.java
new file mode 100644
index 00000000..b25dc407
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/domain/AgentTemplate.java
@@ -0,0 +1,116 @@
+package xiaozhi.modules.agent.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.util.Date;
+import lombok.Data;
+
+/**
+ * 智能体配置模板表
+ * @TableName ai_agent_template
+ */
+@TableName(value ="ai_agent_template")
+@Data
+public class AgentTemplate implements Serializable {
+ /**
+ * 智能体唯一标识
+ */
+ @TableId
+ private String id;
+
+ /**
+ * 智能体编码
+ */
+ private String agentCode;
+
+ /**
+ * 智能体名称
+ */
+ private String agentName;
+
+ /**
+ * 语音识别模型标识
+ */
+ private String asrModelId;
+
+ /**
+ * 语音活动检测标识
+ */
+ private String vadModelId;
+
+ /**
+ * 大语言模型标识
+ */
+ private String llmModelId;
+
+ /**
+ * 语音合成模型标识
+ */
+ private String ttsModelId;
+
+ /**
+ * 音色标识
+ */
+ private String ttsVoiceId;
+
+ /**
+ * 记忆模型标识
+ */
+ private String memModelId;
+
+ /**
+ * 意图模型标识
+ */
+ private String intentModelId;
+
+ /**
+ * 角色设定参数
+ */
+ private String systemPrompt;
+
+ /**
+ * 语言编码
+ */
+ private String langCode;
+
+ /**
+ * 交互语种
+ */
+ private String language;
+
+ /**
+ * 排序权重
+ */
+ private Integer sort;
+
+ /**
+ * 是否默认模板:1:是,0:不是
+ */
+ private Integer isDefault;
+
+ /**
+ * 创建者 ID
+ */
+ private Long creator;
+
+ /**
+ * 创建时间
+ */
+ private Date createdAt;
+
+ /**
+ * 更新者 ID
+ */
+ private Long updater;
+
+ /**
+ * 更新时间
+ */
+ private Date updatedAt;
+
+ @TableField(exist = false)
+ private static final long serialVersionUID = 1L;
+}
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/mapper/AgentMapper.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/mapper/AgentMapper.java
new file mode 100644
index 00000000..7c849280
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/mapper/AgentMapper.java
@@ -0,0 +1,20 @@
+package xiaozhi.modules.agent.mapper;
+
+import org.apache.ibatis.annotations.Mapper;
+import xiaozhi.modules.agent.domain.Agent;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+* @author chenerlei
+* @description 针对表【ai_agent(智能体配置表)】的数据库操作Mapper
+* @createDate 2025-03-22 11:48:18
+* @Entity xiaozhi.modules.agent.domain.Agent
+*/
+@Mapper
+public interface AgentMapper extends BaseMapper {
+
+}
+
+
+
+
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/mapper/AgentTemplateMapper.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/mapper/AgentTemplateMapper.java
new file mode 100644
index 00000000..c3222cc4
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/mapper/AgentTemplateMapper.java
@@ -0,0 +1,20 @@
+package xiaozhi.modules.agent.mapper;
+
+import org.apache.ibatis.annotations.Mapper;
+import xiaozhi.modules.agent.domain.AgentTemplate;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+* @author chenerlei
+* @description 针对表【ai_agent_template(智能体配置模板表)】的数据库操作Mapper
+* @createDate 2025-03-22 11:48:18
+* @Entity xiaozhi.modules.agent.domain.AgentTemplate
+*/
+@Mapper
+public interface AgentTemplateMapper extends BaseMapper {
+
+}
+
+
+
+
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentService.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentService.java
new file mode 100644
index 00000000..b7a7705b
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentService.java
@@ -0,0 +1,13 @@
+package xiaozhi.modules.agent.service;
+
+import xiaozhi.modules.agent.domain.Agent;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+* @author chenerlei
+* @description 针对表【ai_agent(智能体配置表)】的数据库操作Service
+* @createDate 2025-03-22 11:48:18
+*/
+public interface AgentService extends IService {
+
+}
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentTemplateService.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentTemplateService.java
new file mode 100644
index 00000000..18849526
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/AgentTemplateService.java
@@ -0,0 +1,13 @@
+package xiaozhi.modules.agent.service;
+
+import xiaozhi.modules.agent.domain.AgentTemplate;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+* @author chenerlei
+* @description 针对表【ai_agent_template(智能体配置模板表)】的数据库操作Service
+* @createDate 2025-03-22 11:48:18
+*/
+public interface AgentTemplateService extends IService {
+
+}
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java
new file mode 100644
index 00000000..dfa3400d
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentServiceImpl.java
@@ -0,0 +1,22 @@
+package xiaozhi.modules.agent.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import xiaozhi.modules.agent.domain.Agent;
+import xiaozhi.modules.agent.service.AgentService;
+import xiaozhi.modules.agent.mapper.AgentMapper;
+import org.springframework.stereotype.Service;
+
+/**
+* @author chenerlei
+* @description 针对表【ai_agent(智能体配置表)】的数据库操作Service实现
+* @createDate 2025-03-22 11:48:18
+*/
+@Service
+public class AgentServiceImpl extends ServiceImpl
+ implements AgentService{
+
+}
+
+
+
+
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentTemplateServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentTemplateServiceImpl.java
new file mode 100644
index 00000000..112b65c0
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/service/impl/AgentTemplateServiceImpl.java
@@ -0,0 +1,22 @@
+package xiaozhi.modules.agent.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import xiaozhi.modules.agent.domain.AgentTemplate;
+import xiaozhi.modules.agent.service.AgentTemplateService;
+import xiaozhi.modules.agent.mapper.AgentTemplateMapper;
+import org.springframework.stereotype.Service;
+
+/**
+* @author chenerlei
+* @description 针对表【ai_agent_template(智能体配置模板表)】的数据库操作Service实现
+* @createDate 2025-03-22 11:48:18
+*/
+@Service
+public class AgentTemplateServiceImpl extends ServiceImpl
+ implements AgentTemplateService{
+
+}
+
+
+
+
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/vo/AgentConfigVO.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/vo/AgentConfigVO.java
new file mode 100644
index 00000000..ba1ececc
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/vo/AgentConfigVO.java
@@ -0,0 +1,17 @@
+package xiaozhi.modules.agent.vo;
+
+import cn.hutool.json.JSONObject;
+import lombok.Data;
+
+@Data
+public class AgentConfigVO {
+ private JSONObject selected_module;
+ private String prompt;
+ private JSONObject LLM;
+ private JSONObject TTS;
+ private JSONObject ASR;
+ private JSONObject VAD;
+ private JSONObject Memory;
+ private JSONObject Intent;
+ private String owner;
+}
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/vo/AgentVO.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/vo/AgentVO.java
new file mode 100644
index 00000000..3ffddbd7
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/vo/AgentVO.java
@@ -0,0 +1,19 @@
+package xiaozhi.modules.agent.vo;
+
+import lombok.Data;
+import xiaozhi.modules.agent.domain.Agent;
+
+@Data
+public class AgentVO extends Agent {
+ //角色音色
+ private String ttsModelName;
+
+ //角色明星
+ private String llmModelName;
+
+ //最近对话
+ private String lastConnectedAt;
+
+ //设备数量
+ private Long deviceCount;
+}
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/constant/DeviceConstant.java b/main/manager-api/src/main/java/xiaozhi/modules/device/constant/DeviceConstant.java
new file mode 100644
index 00000000..5d6fb39e
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/device/constant/DeviceConstant.java
@@ -0,0 +1,7 @@
+package xiaozhi.modules.device.constant;
+
+public class DeviceConstant {
+
+ public static final String REDIS_KEY_PREFIX_DEVICE_ACTIVATION_CODE = "device:activation:code";
+ public static final String REDIS_KEY_PREFIX_DEVICE_ACTIVATION_MAC = "device:activation:mac";
+}
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/controller/DeviceController.java b/main/manager-api/src/main/java/xiaozhi/modules/device/controller/DeviceController.java
deleted file mode 100644
index d458e9f9..00000000
--- a/main/manager-api/src/main/java/xiaozhi/modules/device/controller/DeviceController.java
+++ /dev/null
@@ -1,82 +0,0 @@
-package xiaozhi.modules.device.controller;
-
-import io.swagger.v3.oas.annotations.Operation;
-import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.Parameters;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import lombok.AllArgsConstructor;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.shiro.authz.annotation.RequiresPermissions;
-import org.springframework.web.bind.annotation.*;
-import xiaozhi.common.constant.Constant;
-import xiaozhi.common.exception.ErrorCode;
-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.modules.device.dto.DeviceHeaderDTO;
-import xiaozhi.modules.device.dto.DeviceUnBindDTO;
-import xiaozhi.modules.device.entity.DeviceEntity;
-import xiaozhi.modules.device.service.DeviceService;
-import xiaozhi.modules.security.user.SecurityUser;
-
-import java.util.List;
-import java.util.Map;
-
-@Tag(name = "设备管理")
-@AllArgsConstructor
-@RestController
-@RequestMapping("/device")
-public class DeviceController {
- private final DeviceService deviceService;
- private final RedisUtils redisUtils;
-
-
- @PostMapping("/bind/{deviceCode}")
- @Operation(summary = "绑定设备")
- @RequiresPermissions("sys:role:normal")
- public Result bindDevice(@PathVariable String deviceCode) {
- UserDetail user = SecurityUser.getUser();
-
- String deviceHeaders = (String) redisUtils.get(RedisKeys.getDeviceCaptchaKey(deviceCode));
- if (StringUtils.isBlank(deviceHeaders)) {
- return new Result().error(ErrorCode.DEVICE_CAPTCHA_ERROR);
- }
- DeviceHeaderDTO deviceHeader = JsonUtils.parseObject(deviceHeaders.getBytes(), DeviceHeaderDTO.class);
- DeviceEntity device = deviceService.bindDevice(user.getId(), deviceHeader);
- return new Result().ok(device);
- }
-
- @GetMapping("/bind")
- @Operation(summary = "获取已绑定设备")
- @RequiresPermissions("sys:role:normal")
- public Result> getUserDevices() {
- UserDetail user = SecurityUser.getUser();
- List devices = deviceService.getUserDevices(user.getId());
- return new Result>().ok(devices);
- }
-
- @PostMapping("/unbind")
- @Operation(summary = "解绑设备")
- @RequiresPermissions("sys:role:normal")
- public Result unbindDevice(@RequestBody DeviceUnBindDTO unDeviveBind) {
- UserDetail user = SecurityUser.getUser();
- deviceService.unbindDevice(user.getId(), unDeviveBind.getDeviceId());
- return new Result();
- }
-
- @GetMapping("/all")
- @Operation(summary = "设备列表(管理员)")
- @RequiresPermissions("sys:role:superAdmin")
- @Parameters({
- @Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true),
- @Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true),
- })
- public Result> adminDeviceList(
- @Parameter(hidden = true) @RequestParam Map params) {
- PageData page = deviceService.adminDeviceList(params);
- return new Result>().ok(page);
- }
-}
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/controller/OtaController.java b/main/manager-api/src/main/java/xiaozhi/modules/device/controller/OtaController.java
new file mode 100644
index 00000000..c222a264
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/device/controller/OtaController.java
@@ -0,0 +1,81 @@
+package xiaozhi.modules.device.controller;
+
+import cn.hutool.json.JSONObject;
+import cn.hutool.json.JSONUtil;
+import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.data.redis.core.RedisTemplate;
+import org.springframework.http.HttpHeaders;
+import org.springframework.web.bind.annotation.*;
+import xiaozhi.common.controller.BaseController;
+import xiaozhi.common.redis.RedisUtils;
+import xiaozhi.modules.device.constant.DeviceConstant;
+import xiaozhi.modules.device.domain.Device;
+import xiaozhi.modules.device.service.DeviceService;
+import xiaozhi.modules.device.utils.CodeGeneratorUtil;
+import xiaozhi.modules.device.vo.DeviceOtaVO;
+import xiaozhi.modules.sys.service.SysParamsService;
+
+import java.util.Date;
+
+@Slf4j
+@Tag(name = "设备OTA管理")
+@AllArgsConstructor
+@RestController
+@RequestMapping("/ota")
+public class OtaController extends BaseController {
+ private final DeviceService deviceService;
+ private final SysParamsService sysParamsService;
+ @Resource
+ private RedisUtils redisUtils;
+ private final String OTA_PARAM_CODE = "OTA";
+
+ @CrossOrigin(originPatterns = "*", methods = {RequestMethod.GET, RequestMethod.POST})
+ @RequestMapping(path = "/", method = {RequestMethod.POST, RequestMethod.GET}, produces = "application/json")
+ @Operation(summary = "设备OTA升级")
+ public String ota(@RequestHeader(required = false) HttpHeaders headers, @RequestBody(required = false) JSONObject jsonObject) {
+ log.info("OTA升级请求:header:{},body:{}", headers, jsonObject);
+ if (ObjectUtils.isNull(headers) || StringUtils.isBlank(headers.getFirst("device-id"))) {
+ log.error("设备ID不能为空");
+ return "{\"error\": \"Device ID is required\"}";
+ }
+
+ DeviceOtaVO otaVO = new DeviceOtaVO();
+ Device device = deviceService.getOne(new UpdateWrapper().eq("mac_address", headers.getFirst("device-id").toUpperCase()).eq("id", headers.getFirst("client-Id").replace("-", "")));
+ if (ObjectUtils.isNull(device)) {
+ // 从 Redis 中获取设备信息
+ Object redisValue = redisUtils.hGet(DeviceConstant.REDIS_KEY_PREFIX_DEVICE_ACTIVATION_MAC, headers.getFirst("device-id").toUpperCase());
+ if (ObjectUtils.isNull(redisValue)) {
+ String code = CodeGeneratorUtil.generateCode(6);
+ log.info("[gen]授权码已广播:{}", code);
+ otaVO.setActivation(new DeviceOtaVO.Activation(code, "youxlife.com\n" + code));
+ redisUtils.hSet(DeviceConstant.REDIS_KEY_PREFIX_DEVICE_ACTIVATION_MAC, headers.getFirst("device-id").toUpperCase(), code, RedisUtils.HOUR_ONE_EXPIRE);
+ redisUtils.hSet(DeviceConstant.REDIS_KEY_PREFIX_DEVICE_ACTIVATION_CODE, code, jsonObject, RedisUtils.HOUR_ONE_EXPIRE);
+ } else {
+ log.warn("[get]授权码已广播:{}", redisValue);
+ otaVO.setActivation(new DeviceOtaVO.Activation(redisValue.toString(), "youxlife.com\n" + redisValue));
+ }
+ }
+
+
+ if (ObjectUtils.isNull(device) || device.getAutoUpdate() == 1) {
+ //{"version":"1.0.0","url":"http://https://youxlife.oss-cn-zhangjiakou.aliyuncs.com/v1.4.5.bin"}
+ String value = sysParamsService.getValue(OTA_PARAM_CODE);
+ if (StringUtils.isBlank(value)) {
+ log.warn("OTA升级信息未配置");
+ } else {
+ JSONObject object = new JSONObject(value);
+ otaVO.setFirmware(new DeviceOtaVO.Firmware(object.getStr("version"), object.getStr("url")));
+ }
+ }
+
+ otaVO.setServer_time(new DeviceOtaVO.ServerTime(new Date().getTime(), 8 * 60));
+ return JSONUtil.toJsonStr(otaVO);
+ }
+}
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/controller/UserDeviceController.java b/main/manager-api/src/main/java/xiaozhi/modules/device/controller/UserDeviceController.java
new file mode 100644
index 00000000..b5900faa
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/device/controller/UserDeviceController.java
@@ -0,0 +1,127 @@
+package xiaozhi.modules.device.controller;
+
+import cn.hutool.json.JSONObject;
+import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.baomidou.mybatisplus.core.toolkit.ObjectUtils;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.web.bind.annotation.*;
+import xiaozhi.common.controller.BaseController;
+import xiaozhi.common.redis.RedisUtils;
+import xiaozhi.common.user.UserDetail;
+import xiaozhi.common.utils.Result;
+import xiaozhi.modules.agent.domain.Agent;
+import xiaozhi.modules.agent.service.AgentService;
+import xiaozhi.modules.device.constant.DeviceConstant;
+import xiaozhi.modules.device.domain.Device;
+import xiaozhi.modules.device.service.DeviceService;
+import xiaozhi.modules.device.vo.DeviceCodeVO;
+import xiaozhi.modules.security.user.SecurityUser;
+
+import java.util.Date;
+
+@Slf4j
+@Tag(name = "设备管理")
+@AllArgsConstructor
+@RestController
+@RequestMapping("/user/agent/device")
+public class UserDeviceController extends BaseController {
+ private final AgentService agentService;
+ private final DeviceService deviceService;
+ private final RedisUtils redisUtils;
+
+ @GetMapping("/bind/{agentId}")
+ @Operation(summary = "已绑定设备列表")
+ public Result> bindDeviceList(@PathVariable String agentId, @RequestParam Integer pageNo, @RequestParam Integer pageSize) {
+ if (StringUtils.isBlank(agentId)) {
+ log.error("智能体ID不能为空");
+ return new Result().error("智能体ID不能为空");
+ }
+
+ UserDetail user = SecurityUser.getUser();
+ Agent agent = agentService.getById(agentId);
+ if (ObjectUtils.isNull(agent)) {
+ log.error("智能体不存在");
+ return new Result().error("智能体不存在");
+ }
+ Page page = new Page(pageNo, pageSize);
+ page = deviceService.page(page, new QueryWrapper().eq("user_id", user.getId()).eq("agent_id", agentId));
+ return new Result>().ok(page);
+ }
+
+
+ @PostMapping("/bind/{agentId}")
+ @Operation(summary = "绑定设备")
+ public Result bindDevice(@PathVariable String agentId, @RequestBody DeviceCodeVO deviceCodeVO) {
+ if (ObjectUtils.isNull(deviceCodeVO) || StringUtils.isBlank(deviceCodeVO.getDeviceCode())) {
+ log.error("授权码不能为空");
+ return new Result().error("授权码不能为空");
+ }
+ if (StringUtils.isBlank(agentId)) {
+ log.error("智能体ID不能为空");
+ return new Result().error("智能体ID不能为空");
+ }
+
+ UserDetail user = SecurityUser.getUser();
+
+ // 从 Redis 中获取设备信息
+ Object redisValue = redisUtils.hGet(DeviceConstant.REDIS_KEY_PREFIX_DEVICE_ACTIVATION_CODE, deviceCodeVO.getDeviceCode());
+ if (ObjectUtils.isNull(redisValue)) {
+ log.error("授权码不存在:{}", deviceCodeVO.getDeviceCode());
+ return new Result().error("授权码不存在");
+ }
+ JSONObject jsonObject = (JSONObject) redisValue;
+ try {
+ redisUtils.hDel(DeviceConstant.REDIS_KEY_PREFIX_DEVICE_ACTIVATION_CODE, deviceCodeVO.getDeviceCode());
+ redisUtils.hDel(DeviceConstant.REDIS_KEY_PREFIX_DEVICE_ACTIVATION_MAC, jsonObject.getStr("mac_address").toUpperCase());
+ } catch (Exception e) {
+ log.warn("授权缓存删除失败", e);
+ }
+ JSONObject board = jsonObject.getJSONObject("board");
+ JSONObject application = jsonObject.getJSONObject("application");
+ if (ObjectUtils.isNull(board) || ObjectUtils.isNull(application)) {
+ log.error("设备码绑定信息无效,{}:{}", jsonObject.getStr("mac_address").toUpperCase(), deviceCodeVO.getDeviceCode());
+ return new Result().error("设备码绑定信息无效");
+ }
+
+ Agent agent = agentService.getById(agentId);
+ if (ObjectUtils.isNull(agent)) {
+ log.error("智能体不存在");
+ return new Result().error("智能体不存在");
+ }
+ Device device = new Device();
+ device.setId(jsonObject.getStr("uuid").replace("-", ""));
+ device.setUserId(user.getId());
+ device.setMacAddress(jsonObject.getStr("mac_address").toUpperCase());
+ device.setBoard(board.getStr("type"));
+ device.setAppVersion(application.getStr("version"));
+ device.setAgentId(agentId);
+ device.setCreator(user.getId());
+ device.setCreateDate(new Date());
+ device.setAutoUpdate(1);
+ boolean bool = deviceService.save(device);
+ if (!bool) {
+ return new Result().error("绑定失败");
+ }
+ return new Result().ok(device);
+ }
+
+ @PutMapping("/unbind/{deviceId}")
+ @Operation(summary = "解绑设备")
+ public Result bindDevice(@PathVariable String deviceId) {
+ if (StringUtils.isBlank(deviceId)) {
+ log.error("设备ID不能为空");
+ return new Result().error("设备ID不能为空");
+ }
+ UserDetail user = SecurityUser.getUser();
+ boolean bool = deviceService.removeById(deviceId);
+ if (!bool) {
+ return new Result().error("解绑失败");
+ }
+ return new Result().ok(null);
+ }
+}
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/dao/DeviceDao.java b/main/manager-api/src/main/java/xiaozhi/modules/device/dao/DeviceDao.java
deleted file mode 100644
index 9e4a8cd4..00000000
--- a/main/manager-api/src/main/java/xiaozhi/modules/device/dao/DeviceDao.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package xiaozhi.modules.device.dao;
-
-import com.baomidou.mybatisplus.core.mapper.BaseMapper;
-import org.apache.ibatis.annotations.Mapper;
-import xiaozhi.modules.device.entity.DeviceEntity;
-
-@Mapper
-public interface DeviceDao extends BaseMapper {
-}
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/domain/Device.java b/main/manager-api/src/main/java/xiaozhi/modules/device/domain/Device.java
new file mode 100644
index 00000000..ae662dde
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/device/domain/Device.java
@@ -0,0 +1,91 @@
+package xiaozhi.modules.device.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.util.Date;
+import lombok.Data;
+
+/**
+ * 设备信息表
+ * @TableName ai_device
+ */
+@TableName(value ="ai_device")
+@Data
+public class Device implements Serializable {
+ /**
+ * 设备唯一标识
+ */
+ @TableId
+ private String id;
+
+ /**
+ * 关联用户 ID
+ */
+ private Long userId;
+
+ /**
+ * MAC 地址
+ */
+ private String macAddress;
+
+ /**
+ * 最后连接时间
+ */
+ private Date lastConnectedAt;
+
+ /**
+ * 自动更新开关(0 关闭/1 开启)
+ */
+ private Integer autoUpdate;
+
+ /**
+ * 设备硬件型号
+ */
+ private String board;
+
+ /**
+ * 设备别名
+ */
+ private String alias;
+
+ /**
+ * 智能体 ID
+ */
+ private String agentId;
+
+ /**
+ * 固件版本号
+ */
+ private String appVersion;
+
+ /**
+ * 排序
+ */
+ private Integer sort;
+
+ /**
+ * 创建者
+ */
+ private Long creator;
+
+ /**
+ * 创建时间
+ */
+ private Date createDate;
+
+ /**
+ * 更新者
+ */
+ private Long updater;
+
+ /**
+ * 更新时间
+ */
+ private Date updateDate;
+
+ @TableField(exist = false)
+ private static final long serialVersionUID = 1L;
+}
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceHeaderDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceHeaderDTO.java
deleted file mode 100644
index 83327fef..00000000
--- a/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceHeaderDTO.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package xiaozhi.modules.device.dto;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-@Data
-@Schema(description = "设备连接头信息")
-public class DeviceHeaderDTO {
-
- @Schema(description = "设备ID")
- private String deviceId;
-
- @Schema(description = "协议版本号")
- private Long protocolVersion;
-
- @Schema(description = "认证信息")
- private String authorization;
-
-}
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceUnBindDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceUnBindDTO.java
deleted file mode 100644
index 830a51ae..00000000
--- a/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceUnBindDTO.java
+++ /dev/null
@@ -1,19 +0,0 @@
-package xiaozhi.modules.device.dto;
-import io.swagger.v3.oas.annotations.media.Schema;
-import jakarta.validation.constraints.NotBlank;
-import lombok.Data;
-
-import java.io.Serializable;
-
-/**
- * 设备解绑表单
- */
-@Data
-@Schema(description = "设备解绑表单")
-public class DeviceUnBindDTO implements Serializable {
-
- @Schema(description = "设备ID")
- @NotBlank(message = "设备ID不能为空")
- private Long deviceId;
-
-}
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/entity/DeviceEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/device/entity/DeviceEntity.java
deleted file mode 100644
index ccbe4683..00000000
--- a/main/manager-api/src/main/java/xiaozhi/modules/device/entity/DeviceEntity.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package xiaozhi.modules.device.entity;
-
-import com.baomidou.mybatisplus.annotation.TableName;
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.Data;
-
-import java.util.Date;
-
-@Data
-@TableName("ai_device")
-@Schema(description = "设备信息")
-public class DeviceEntity {
- @Schema(description = "设备ID")
- private Long id;
-
- @Schema(description = "关联用户ID")
- private Long userId;
-
- @Schema(description = "MAC地址")
- private String macAddress;
-
- @Schema(description = "最后连接时间")
- private Date lastConnectedAt;
-
- @Schema(description = "自动更新开关(0关闭/1开启)")
- private Integer autoUpdate;
-
- @Schema(description = "设备硬件型号")
- private String board;
-
- @Schema(description = "设备别名")
- private String alias;
-
- @Schema(description = "智能体ID")
- private String agentId;
-
- @Schema(description = "固件版本号")
- private String appVersion;
-
- @Schema(description = "排序")
- private Integer sort;
-
- @Schema(description = "创建者")
- private Long creator;
-
- @Schema(description = "创建时间")
- private Date createDate;
-
- @Schema(description = "更新者")
- private Long updater;
-
- @Schema(description = "更新时间")
- private Date updateDate;
-}
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/mapper/DeviceMapper.java b/main/manager-api/src/main/java/xiaozhi/modules/device/mapper/DeviceMapper.java
new file mode 100644
index 00000000..b43847a5
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/device/mapper/DeviceMapper.java
@@ -0,0 +1,20 @@
+package xiaozhi.modules.device.mapper;
+
+import org.apache.ibatis.annotations.Mapper;
+import xiaozhi.modules.device.domain.Device;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+* @author chenerlei
+* @description 针对表【ai_device(设备信息表)】的数据库操作Mapper
+* @createDate 2025-03-22 13:26:35
+* @Entity xiaozhi.modules.device.domain.Device
+*/
+@Mapper
+public interface DeviceMapper extends BaseMapper {
+
+}
+
+
+
+
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/service/DeviceService.java b/main/manager-api/src/main/java/xiaozhi/modules/device/service/DeviceService.java
index 16ad44fe..ec3efe56 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/device/service/DeviceService.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/device/service/DeviceService.java
@@ -1,18 +1,13 @@
-package xiaozhi.modules.device.service;
-
-import xiaozhi.common.page.PageData;
-import xiaozhi.modules.device.dto.DeviceHeaderDTO;
-import xiaozhi.modules.device.entity.DeviceEntity;
-
-import java.util.List;
-import java.util.Map;
-
-public interface DeviceService {
- DeviceEntity bindDevice(Long userId, DeviceHeaderDTO deviceHeader);
-
- List getUserDevices(Long userId);
-
- void unbindDevice(Long userId, Long deviceId);
-
- PageData adminDeviceList(Map params);
-}
\ No newline at end of file
+package xiaozhi.modules.device.service;
+
+import xiaozhi.modules.device.domain.Device;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+* @author chenerlei
+* @description 针对表【ai_device(设备信息表)】的数据库操作Service
+* @createDate 2025-03-22 13:26:35
+*/
+public interface DeviceService extends IService {
+
+}
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java
index 279a9f2e..4c317a64 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/device/service/impl/DeviceServiceImpl.java
@@ -1,57 +1,22 @@
-package xiaozhi.modules.device.service.impl;
-
-import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
-import com.baomidou.mybatisplus.core.metadata.IPage;
-import org.springframework.stereotype.Service;
-import xiaozhi.common.page.PageData;
-import xiaozhi.common.service.impl.BaseServiceImpl;
-import xiaozhi.modules.device.dao.DeviceDao;
-import xiaozhi.modules.device.dto.DeviceHeaderDTO;
-import xiaozhi.modules.device.entity.DeviceEntity;
-import xiaozhi.modules.device.service.DeviceService;
-
-import java.util.Date;
-import java.util.List;
-import java.util.Map;
-
-@Service
-public class DeviceServiceImpl extends BaseServiceImpl implements DeviceService {
- private final DeviceDao deviceDao;
-
- // 添加构造函数来初始化 deviceMapper
- public DeviceServiceImpl(DeviceDao deviceDao) {
- this.deviceDao = deviceDao;
- }
-
- @Override
- public DeviceEntity bindDevice(Long userId, DeviceHeaderDTO deviceHeader) {
- DeviceEntity device = new DeviceEntity();
- device.setUserId(userId);
- device.setMacAddress(deviceHeader.getDeviceId());
- device.setCreateDate(new Date());
- deviceDao.insert(device);
- return device;
- }
-
- @Override
- public List getUserDevices(Long userId) {
- QueryWrapper wrapper = new QueryWrapper<>();
- wrapper.eq("user_id", userId);
- return deviceDao.selectList(wrapper);
- }
-
- @Override
- public void unbindDevice(Long userId, Long deviceId) {
- deviceDao.deleteById(deviceId);
- }
-
- @Override
- public PageData adminDeviceList(Map params) {
- IPage page = deviceDao.selectPage(
- getPage(params, "sort", true),
- new QueryWrapper<>()
- );
- return new PageData<>(page.getRecords(), page.getTotal());
- }
-
-}
\ No newline at end of file
+package xiaozhi.modules.device.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import xiaozhi.modules.device.domain.Device;
+import xiaozhi.modules.device.service.DeviceService;
+import xiaozhi.modules.device.mapper.DeviceMapper;
+import org.springframework.stereotype.Service;
+
+/**
+* @author chenerlei
+* @description 针对表【ai_device(设备信息表)】的数据库操作Service实现
+* @createDate 2025-03-22 13:26:35
+*/
+@Service
+public class DeviceServiceImpl extends ServiceImpl
+ implements DeviceService{
+
+}
+
+
+
+
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/utils/CodeGeneratorUtil.java b/main/manager-api/src/main/java/xiaozhi/modules/device/utils/CodeGeneratorUtil.java
new file mode 100644
index 00000000..3a9ca2e6
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/device/utils/CodeGeneratorUtil.java
@@ -0,0 +1,68 @@
+package xiaozhi.modules.device.utils;
+
+import java.util.Random;
+
+public class CodeGeneratorUtil {
+
+ private static long lastTime = 0;
+ private static int counter = 0;
+
+ /**
+ * 生成6位数字码,首位不为0,短期内不会重复。
+ * @return 6位数字码
+ */
+ public static synchronized String generateCode() {
+ long currentTime = System.currentTimeMillis();
+
+ // 如果当前时间与上次相同,增加计数器以避免重复
+ if (currentTime == lastTime) {
+ counter++;
+ } else {
+ counter = 0; // 时间变化时重置计数器
+ }
+ lastTime = currentTime;
+
+ // 使用当前时间和计数器生成随机种子
+ long seed = currentTime + counter;
+ Random random = new Random(seed);
+
+ // 确保首位不为0
+ int firstDigit = random.nextInt(9) + 1;
+ int remainingDigits = random.nextInt(100000); // 剩余5位
+
+ // 拼接生成的6位数字码
+ return String.format("%d%05d", firstDigit, remainingDigits);
+ }
+
+ /**
+ * 生成指定位数的数字码,首位不为0,短期内不会重复。
+ * @param digits 生成的数字码位数
+ * @return 指定位数的数字码
+ */
+ public static synchronized String generateCode(int digits) {
+ if (digits < 2) {
+ throw new IllegalArgumentException("数字码位数必须大于等于2");
+ }
+
+ long currentTime = System.currentTimeMillis();
+
+ // 如果当前时间与上次相同,增加计数器以避免重复
+ if (currentTime == lastTime) {
+ counter++;
+ } else {
+ counter = 0; // 时间变化时重置计数器
+ }
+ lastTime = currentTime;
+
+ // 使用当前时间和计数器生成随机种子
+ long seed = currentTime + counter;
+ Random random = new Random(seed);
+
+ // 确保首位不为0
+ int firstDigit = random.nextInt(9) + 1;
+ int remainingDigits = random.nextInt((int) Math.pow(10, digits - 1)); // 剩余位数
+
+ // 拼接生成的动态位数数字码
+ return String.format("%d%0" + (digits - 1) + "d", firstDigit, remainingDigits);
+ }
+}
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/vo/DeviceCodeVO.java b/main/manager-api/src/main/java/xiaozhi/modules/device/vo/DeviceCodeVO.java
new file mode 100644
index 00000000..f5817dc1
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/device/vo/DeviceCodeVO.java
@@ -0,0 +1,10 @@
+package xiaozhi.modules.device.vo;
+
+import lombok.Data;
+
+@Data
+public class DeviceCodeVO {
+ private String deviceId;
+ private String deviceCode;
+ private String token;
+}
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/vo/DeviceOtaVO.java b/main/manager-api/src/main/java/xiaozhi/modules/device/vo/DeviceOtaVO.java
new file mode 100644
index 00000000..c5af75af
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/device/vo/DeviceOtaVO.java
@@ -0,0 +1,55 @@
+package xiaozhi.modules.device.vo;
+
+import lombok.Data;
+
+@Data
+public class DeviceOtaVO {
+ private Activation activation;
+ private Mqtt mqtt;
+ private ServerTime server_time;
+ private Firmware firmware;
+ private String error;
+
+ @Data
+ public static class Activation {
+ private String code;
+ private String message;
+
+ public Activation() {
+ }
+ public Activation(String code, String message) {
+ this.code = code;
+ this.message = message;
+ }
+ }
+
+ @Data
+ public class Mqtt {
+
+ }
+
+ @Data
+ public static class ServerTime {
+ private Long timestamp;
+ private Integer timezone_offset;
+
+ public ServerTime() {
+ }
+ public ServerTime(Long timestamp, Integer timezone_offset) {
+ this.timestamp = timestamp;
+ this.timezone_offset = timezone_offset;
+ }
+ }
+
+ @Data
+ public static class Firmware {
+ private String version;
+ private String url;
+ public Firmware() {
+ }
+ public Firmware(String version, String url) {
+ this.version = version;
+ this.url = url;
+ }
+ }
+}
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/domain/ModelConfig.java b/main/manager-api/src/main/java/xiaozhi/modules/model/domain/ModelConfig.java
new file mode 100644
index 00000000..b9967900
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/model/domain/ModelConfig.java
@@ -0,0 +1,91 @@
+package xiaozhi.modules.model.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.util.Date;
+import lombok.Data;
+
+/**
+ * 模型配置表
+ * @TableName ai_model_config
+ */
+@TableName(value ="ai_model_config")
+@Data
+public class ModelConfig implements Serializable {
+ /**
+ * 主键
+ */
+ @TableId
+ private String id;
+
+ /**
+ * 模型类型(Memory/ASR/VAD/LLM/TTS)
+ */
+ private String modelType;
+
+ /**
+ * 模型编码(如AliLLM、DoubaoTTS)
+ */
+ private String modelCode;
+
+ /**
+ * 模型名称
+ */
+ private String modelName;
+
+ /**
+ * 是否默认配置(0否 1是)
+ */
+ private Integer isDefault;
+
+ /**
+ * 是否启用
+ */
+ private Integer isEnabled;
+
+ /**
+ * 模型配置(JSON格式)
+ */
+ private Object configJson;
+
+ /**
+ * 官方文档链接
+ */
+ private String docLink;
+
+ /**
+ * 备注
+ */
+ private String remark;
+
+ /**
+ * 排序
+ */
+ private Integer sort;
+
+ /**
+ * 创建者
+ */
+ private Long creator;
+
+ /**
+ * 创建时间
+ */
+ private Date createDate;
+
+ /**
+ * 更新者
+ */
+ private Long updater;
+
+ /**
+ * 更新时间
+ */
+ private Date updateDate;
+
+ @TableField(exist = false)
+ private static final long serialVersionUID = 1L;
+}
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/domain/TtsVoice.java b/main/manager-api/src/main/java/xiaozhi/modules/model/domain/TtsVoice.java
new file mode 100644
index 00000000..744bd6e8
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/model/domain/TtsVoice.java
@@ -0,0 +1,81 @@
+package xiaozhi.modules.model.domain;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import java.io.Serializable;
+import java.util.Date;
+import lombok.Data;
+
+/**
+ * TTS 音色表
+ * @TableName ai_tts_voice
+ */
+@TableName(value ="ai_tts_voice")
+@Data
+public class TtsVoice implements Serializable {
+ /**
+ * 主键
+ */
+ @TableId
+ private String id;
+
+ /**
+ * 对应 TTS 模型主键
+ */
+ private String ttsModelId;
+
+ /**
+ * 音色名称
+ */
+ private String name;
+
+ /**
+ * 音色编码
+ */
+ private String ttsVoice;
+
+ /**
+ * 语言
+ */
+ private String languages;
+
+ /**
+ * 音色 Demo
+ */
+ private String voiceDemo;
+
+ /**
+ * 备注
+ */
+ private String remark;
+
+ /**
+ * 排序
+ */
+ private Integer sort;
+
+ /**
+ * 创建者
+ */
+ private Long creator;
+
+ /**
+ * 创建时间
+ */
+ private Date createDate;
+
+ /**
+ * 更新者
+ */
+ private Long updater;
+
+ /**
+ * 更新时间
+ */
+ private Date updateDate;
+
+ @TableField(exist = false)
+ private static final long serialVersionUID = 1L;
+}
\ No newline at end of file
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/mapper/ModelConfigMapper.java b/main/manager-api/src/main/java/xiaozhi/modules/model/mapper/ModelConfigMapper.java
new file mode 100644
index 00000000..0bbf4ca8
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/model/mapper/ModelConfigMapper.java
@@ -0,0 +1,20 @@
+package xiaozhi.modules.model.mapper;
+
+import org.apache.ibatis.annotations.Mapper;
+import xiaozhi.modules.model.domain.ModelConfig;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+* @author chenerlei
+* @description 针对表【ai_model_config(模型配置表)】的数据库操作Mapper
+* @createDate 2025-03-22 15:31:57
+* @Entity xiaozhi.modules.model.domain.ModelConfig
+*/
+@Mapper
+public interface ModelConfigMapper extends BaseMapper {
+
+}
+
+
+
+
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/mapper/TtsVoiceMapper.java b/main/manager-api/src/main/java/xiaozhi/modules/model/mapper/TtsVoiceMapper.java
new file mode 100644
index 00000000..c4ec5089
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/model/mapper/TtsVoiceMapper.java
@@ -0,0 +1,20 @@
+package xiaozhi.modules.model.mapper;
+
+import org.apache.ibatis.annotations.Mapper;
+import xiaozhi.modules.model.domain.TtsVoice;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+/**
+* @author chenerlei
+* @description 针对表【ai_tts_voice(TTS 音色表)】的数据库操作Mapper
+* @createDate 2025-03-22 15:31:57
+* @Entity xiaozhi.modules.model.domain.TtsVoice
+*/
+@Mapper
+public interface TtsVoiceMapper extends BaseMapper {
+
+}
+
+
+
+
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelConfigService.java b/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelConfigService.java
new file mode 100644
index 00000000..a82905eb
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelConfigService.java
@@ -0,0 +1,13 @@
+package xiaozhi.modules.model.service;
+
+import xiaozhi.modules.model.domain.ModelConfig;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+* @author chenerlei
+* @description 针对表【ai_model_config(模型配置表)】的数据库操作Service
+* @createDate 2025-03-22 15:31:57
+*/
+public interface ModelConfigService extends IService {
+
+}
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/service/TtsVoiceService.java b/main/manager-api/src/main/java/xiaozhi/modules/model/service/TtsVoiceService.java
new file mode 100644
index 00000000..5432d715
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/model/service/TtsVoiceService.java
@@ -0,0 +1,13 @@
+package xiaozhi.modules.model.service;
+
+import xiaozhi.modules.model.domain.TtsVoice;
+import com.baomidou.mybatisplus.extension.service.IService;
+
+/**
+* @author chenerlei
+* @description 针对表【ai_tts_voice(TTS 音色表)】的数据库操作Service
+* @createDate 2025-03-22 15:31:57
+*/
+public interface TtsVoiceService extends IService {
+
+}
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelConfigServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelConfigServiceImpl.java
new file mode 100644
index 00000000..f057e270
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelConfigServiceImpl.java
@@ -0,0 +1,22 @@
+package xiaozhi.modules.model.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import xiaozhi.modules.model.domain.ModelConfig;
+import xiaozhi.modules.model.service.ModelConfigService;
+import xiaozhi.modules.model.mapper.ModelConfigMapper;
+import org.springframework.stereotype.Service;
+
+/**
+* @author chenerlei
+* @description 针对表【ai_model_config(模型配置表)】的数据库操作Service实现
+* @createDate 2025-03-22 15:31:57
+*/
+@Service
+public class ModelConfigServiceImpl extends ServiceImpl
+ implements ModelConfigService{
+
+}
+
+
+
+
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/TtsVoiceServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/TtsVoiceServiceImpl.java
new file mode 100644
index 00000000..cb27eaa4
--- /dev/null
+++ b/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/TtsVoiceServiceImpl.java
@@ -0,0 +1,22 @@
+package xiaozhi.modules.model.service.impl;
+
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import xiaozhi.modules.model.domain.TtsVoice;
+import xiaozhi.modules.model.service.TtsVoiceService;
+import xiaozhi.modules.model.mapper.TtsVoiceMapper;
+import org.springframework.stereotype.Service;
+
+/**
+* @author chenerlei
+* @description 针对表【ai_tts_voice(TTS 音色表)】的数据库操作Service实现
+* @createDate 2025-03-22 15:31:57
+*/
+@Service
+public class TtsVoiceServiceImpl extends ServiceImpl
+ implements TtsVoiceService{
+
+}
+
+
+
+
diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java b/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java
index de25d80e..74b85933 100644
--- a/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java
+++ b/main/manager-api/src/main/java/xiaozhi/modules/security/config/ShiroConfig.java
@@ -73,6 +73,8 @@ public class ShiroConfig {
filterMap.put("/user/captcha", "anon");
filterMap.put("/user/login", "anon");
filterMap.put("/user/register", "anon");
+ filterMap.put("/ota/**", "anon");
+ filterMap.put("/user/agent/loadAgentConfig/**", "anon");
filterMap.put("/**", "oauth2");
shiroFilter.setFilterChainDefinitionMap(filterMap);
diff --git a/main/manager-api/src/main/resources/application-dev.yml b/main/manager-api/src/main/resources/application-dev.yml
index a8fdf21f..90a62b6a 100644
--- a/main/manager-api/src/main/resources/application-dev.yml
+++ b/main/manager-api/src/main/resources/application-dev.yml
@@ -41,4 +41,22 @@ spring:
merge-sql: false
wall:
config:
- multi-statement-allow: true
\ No newline at end of file
+ multi-statement-allow: true
+ redis:
+ host: localhost # Redis服务器地址
+ port: 6379 # Redis服务器连接端口
+ password: # Redis服务器连接密码(默认为空)
+ database: 0 # Redis数据库索引(默认为0)
+# jedis:
+# pool:
+# max-active: 8 # 连接池最大连接数(使用负值表示没有限制)
+# max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制)
+# max-idle: 8 # 连接池中的最大空闲连接
+# min-idle: 0 # 连接池中的最小空闲连接
+ timeout: 10000ms # 连接超时时间(毫秒)
+ lettuce:
+ pool:
+ max-active: 8 # 连接池最大连接数(使用负值表示没有限制)
+ max-idle: 8 # 连接池中的最大空闲连接
+ min-idle: 0 # 连接池中的最小空闲连接
+ shutdown-timeout: 100ms # 客户端优雅关闭的等待时间
\ No newline at end of file
diff --git a/main/manager-api/src/main/resources/application.yml b/main/manager-api/src/main/resources/application.yml
index 967f610e..74dfb0e6 100644
--- a/main/manager-api/src/main/resources/application.yml
+++ b/main/manager-api/src/main/resources/application.yml
@@ -24,19 +24,6 @@ spring:
max-file-size: 100MB
max-request-size: 100MB
enabled: true
- data:
- redis:
- database: 0
- host: 127.0.0.1
- port: 6379
- password: # 密码(默认为空)
- timeout: 6000ms # 连接超时时长(毫秒)
- lettuce:
- pool:
- max-active: 1000 # 连接池最大连接数(使用负值表示没有限制)
- max-wait: -1ms # 连接池最大阻塞等待时间(使用负值表示没有限制)
- max-idle: 10 # 连接池中的最大空闲连接
- min-idle: 5 # 连接池中的最小空闲连接
main:
allow-bean-definition-overriding: true
@@ -51,7 +38,7 @@ knife4j:
renren:
redis:
- open: false
+ open: true
xss:
enabled: true
exclude-urls:
@@ -60,7 +47,7 @@ renren:
mybatis-plus:
mapper-locations: classpath*:/mapper/**/*.xml
#实体扫描,多个package用逗号或者分号分隔
- typeAliasesPackage: xiaozhi.modules.*.entity
+ typeAliasesPackage: xiaozhi.modules.*.entity;xiaozhi.modules.*.domain
global-config:
#数据库相关配置
db-config:
diff --git a/main/manager-api/src/main/resources/mapper/agent/AgentMapper.xml b/main/manager-api/src/main/resources/mapper/agent/AgentMapper.xml
new file mode 100644
index 00000000..7d8ddc74
--- /dev/null
+++ b/main/manager-api/src/main/resources/mapper/agent/AgentMapper.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ id,user_id,agent_code,agent_name,asr_model_id,vad_model_id,
+ llm_model_id,tts_model_id,tts_voice_id,mem_model_id,intent_model_id,
+ system_prompt,lang_code,language,sort,creator,
+ created_at,updater,updated_at
+
+
diff --git a/main/manager-api/src/main/resources/mapper/agent/AgentTemplateMapper.xml b/main/manager-api/src/main/resources/mapper/agent/AgentTemplateMapper.xml
new file mode 100644
index 00000000..833bcd0e
--- /dev/null
+++ b/main/manager-api/src/main/resources/mapper/agent/AgentTemplateMapper.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ id,agent_code,agent_name,asr_model_id,vad_model_id,llm_model_id,
+ tts_model_id,tts_voice_id,mem_model_id,intent_model_id,system_prompt,
+ lang_code,language,sort,is_default,creator,
+ created_at,updater,updated_at
+
+
diff --git a/main/manager-api/src/main/resources/mapper/device/DeviceMapper.xml b/main/manager-api/src/main/resources/mapper/device/DeviceMapper.xml
new file mode 100644
index 00000000..743003f7
--- /dev/null
+++ b/main/manager-api/src/main/resources/mapper/device/DeviceMapper.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ id,user_id,mac_address,last_connected_at,auto_update,board,
+ alias,agent_id,app_version,sort,creator,
+ create_date,updater,update_date
+
+
diff --git a/main/manager-api/src/main/resources/mapper/model/ModelConfigMapper.xml b/main/manager-api/src/main/resources/mapper/model/ModelConfigMapper.xml
new file mode 100644
index 00000000..90cf097b
--- /dev/null
+++ b/main/manager-api/src/main/resources/mapper/model/ModelConfigMapper.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ id,model_type,model_code,model_name,is_default,is_enabled,
+ config_json,doc_link,remark,sort,creator,
+ create_date,updater,update_date
+
+
diff --git a/main/manager-api/src/main/resources/mapper/model/TtsVoiceMapper.xml b/main/manager-api/src/main/resources/mapper/model/TtsVoiceMapper.xml
new file mode 100644
index 00000000..008cc4ed
--- /dev/null
+++ b/main/manager-api/src/main/resources/mapper/model/TtsVoiceMapper.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ id,tts_model_id,name,tts_voice,languages,voice_demo,
+ remark,sort,creator,create_date,updater,
+ update_date
+
+
diff --git a/main/manager-web/src/apis/api.js b/main/manager-web/src/apis/api.js
index 38c2f86b..03a5b5a2 100755
--- a/main/manager-web/src/apis/api.js
+++ b/main/manager-web/src/apis/api.js
@@ -9,9 +9,9 @@ import user from './module/user.js'
* 如果你想调用8002端口,就用'/xiaozhi-esp32-api/api/v1',请与vue.config.js的devServer配置相结合,方便跨域请求
*
*/
-const DEV_API_SERVICE = 'https://m1.apifoxmock.com/m1/5931378-5618560-default'
+// const DEV_API_SERVICE = 'https://m1.apifoxmock.com/m1/5931378-5618560-default'
// 8002开发完成完成后使用这个
-// const DEV_API_SERVICE = '/xiaozhi-esp32-api'
+const DEV_API_SERVICE = '/xiaozhi-esp32-api'
/**
* 根据开发环境返回接口url
diff --git a/main/manager-web/src/apis/module/agent.js b/main/manager-web/src/apis/module/agent.js
index 721f075c..e3c2c538 100755
--- a/main/manager-web/src/apis/module/agent.js
+++ b/main/manager-web/src/apis/module/agent.js
@@ -16,20 +16,17 @@ export default {
}).send()
},
//添加智能体
- addAgent(name, callback) {
+ addAgent(agentName, callback) {
RequestService.sendRequest()
.url(`${getServiceUrl()}/api/v1/user/agent`)
.method('POST')
- .data({name})
+ .data({agentName})
.success((res) => {
RequestService.clearRequestTime();
callback(res);
})
.fail((err) => {
console.error('添加智能体失败:', err);
- RequestService.reAjaxFun(() => {
- this.addAgent(name, callback);
- });
}).send();
},
//获取智能体配置
diff --git a/main/manager-web/src/apis/module/device.js b/main/manager-web/src/apis/module/device.js
index cd568745..e16bec25 100755
--- a/main/manager-web/src/apis/module/device.js
+++ b/main/manager-web/src/apis/module/device.js
@@ -4,9 +4,10 @@ import {getServiceUrl} from '../api'
export default {
//设备列表
- getDeviceList(agent_id, callback) {
+ getDeviceList(agent_id, page, callback) {
RequestService.sendRequest().url(`${getServiceUrl()}/api/v1/user/agent/device/bind/${agent_id}`)
.method('GET')
+ .data(page)
.success((res) => {
RequestService.clearRequestTime()
callback(res)
@@ -16,11 +17,11 @@ export default {
}).send()
},
//绑定设备
- bindDevice(agent_id, code, callback) {
+ bindDevice(agentId, deviceCode, callback) {
RequestService.sendRequest()
- .url(`${getServiceUrl()}/api/v1/user/agent/device/bind/${agent_id}`)
+ .url(`${getServiceUrl()}/api/v1/user/agent/device/bind/${agentId}`)
.method('POST')
- .data({code})
+ .data({deviceCode})
.success((res) => {
RequestService.clearRequestTime();
callback(res);
@@ -28,14 +29,14 @@ export default {
.fail((err) => {
console.error('绑定设备失败:', err);
RequestService.reAjaxFun(() => {
- this.addDevice(name, callback);
+ this.addDevice(agentId, deviceCode, callback);
});
}).send();
},
// 解绑设备
- unbindDevice(device_id, callback) {
+ unbindDevice(deviceId, callback) {
RequestService.sendRequest()
- .url(`${getServiceUrl()}/api/v1/user/device/unbind/${device_id}`)
+ .url(`${getServiceUrl()}/api/v1/user/agent/device/unbind/${deviceId}`)
.method('PUT')
.success((res) => {
RequestService.clearRequestTime();
@@ -43,7 +44,7 @@ export default {
})
.fail(() => {
RequestService.reAjaxFun(() => {
- this.unbindDevice(device_id, callback);
+ this.unbindDevice(deviceId, callback);
});
}).send()
},
diff --git a/main/manager-web/src/components/AddAgentDialog.vue b/main/manager-web/src/components/AddAgentDialog.vue
index 026b646e..e805897f 100644
--- a/main/manager-web/src/components/AddAgentDialog.vue
+++ b/main/manager-web/src/components/AddAgentDialog.vue
@@ -46,12 +46,13 @@ export default {
this.$message.error('请输入智能体名称');
return;
}
- userApi.addAgent(this.agentName, (res) => {
- this.$message.success('添加成功');
- this.$emit('confirm', res);
+ this.$emit('confirm',this.agentName)
+ // userApi.addAgent(this.agentName, (res) => {
+ // this.$message.success('添加成功');
+ // this.$emit('confirm', res);
this.$emit('update:visible', false);
this.agentName = "";
- });
+ // });
},
cancel() {
this.$emit('update:visible', false)
diff --git a/main/manager-web/src/components/AddDeviceDialog.vue b/main/manager-web/src/components/AddDeviceDialog.vue
index f98ff7d9..5b94090e 100644
--- a/main/manager-web/src/components/AddDeviceDialog.vue
+++ b/main/manager-web/src/components/AddDeviceDialog.vue
@@ -40,8 +40,12 @@ export default {
},
methods: {
confirm() {
+ if (!this.deviceCode.trim()) {
+ this.$message.error('请输入设备验证码');
+ return;
+ }
+ this.$emit('confirm',this.deviceCode)
this.$emit('update:visible', false)
- this.$emit('added', this.deviceCode)
this.deviceCode = ""
},
cancel() {
diff --git a/main/manager-web/src/utils/date.js b/main/manager-web/src/utils/date.js
index c6210bc0..afc8a339 100755
--- a/main/manager-web/src/utils/date.js
+++ b/main/manager-web/src/utils/date.js
@@ -39,10 +39,13 @@ function formatDateTool(date, fmt) {
const o = {
'M+': date.getMonth() + 1,
'd+': date.getDate(),
- 'h+': date.getHours(),
+ 'H+': date.getHours(),
+ 'h+': date.getHours() > 12 ? date.getHours() - 12 : date.getHours(),
'm+': date.getMinutes(),
's+': date.getSeconds()
}
+ // 12小时制
+ const is_12Hours = fmt.indexOf('hh') > -1;
for (const k in o) {
if (new RegExp(`(${k})`).test(fmt)) {
const str = o[k] + ''
@@ -52,6 +55,8 @@ function formatDateTool(date, fmt) {
)
}
}
+ // 12小时制
+ fmt = is_12Hours ? date.getHours() > 12 ? fmt + " PM" : fmt + " AM" : fmt
return fmt
}
diff --git a/main/manager-web/src/views/device.vue b/main/manager-web/src/views/device.vue
index bbfe41fc..4a60bdde 100644
--- a/main/manager-web/src/views/device.vue
+++ b/main/manager-web/src/views/device.vue
@@ -18,50 +18,53 @@
+ 添加设备
-
-
-
-
+
+
+
+
+
-
+
- {{ scope.row.remark }}
+ {{ scope.row.alias }}
-
+
-
+
解绑
-
+
-
+
diff --git a/main/manager-web/src/views/home.vue b/main/manager-web/src/views/home.vue
index 0c55e9f6..ee561668 100644
--- a/main/manager-web/src/views/home.vue
+++ b/main/manager-web/src/views/home.vue
@@ -1,7 +1,7 @@
-
+
@@ -120,7 +120,7 @@ export default {
},
// 搜索更新智能体列表
handleSearchResult(filteredList) {
- this.devices = filteredList; // 更新设备列表
+ this.agents = filteredList; // 更新设备列表
}
}
}
diff --git a/main/manager-web/src/views/roleConfig.vue b/main/manager-web/src/views/roleConfig.vue
index 36ae7ecc..72fa8c0d 100644
--- a/main/manager-web/src/views/roleConfig.vue
+++ b/main/manager-web/src/views/roleConfig.vue
@@ -119,19 +119,19 @@ export default {
intent:""
}
},
- options: [
- { value: '选项1', label: '黄金糕' },
- { value: '选项2', label: '双皮奶' }
- ],
+ options: [
+ { value: '选项1', label: '黄金糕' },
+ { value: '选项2', label: '双皮奶' }
+ ],
models: [
- { label: '大语言模型(LLM)', key: 'llm' },
- { label: '语音转文本模型(ASR)', key: 'asr' },
- { label: '语音活动检测模型(VAD)', key: 'vad' },
- { label: '语音生成模型(TTS)', key: 'tts' },
- { label: '意图分类模型(Intent)', key: 'intent' },
- { label: '记忆增强模型(Memory)', key: 'memory' }
- ],
- templates: ['台湾女友', '土豆子', '英语老师', '好奇小男孩', '汪汪队队长']
+ { label: '大语言模型(LLM)', key: 'llm' },
+ { label: '语音转文本模型(ASR)', key: 'asr' },
+ { label: '语音活动检测模型(VAD)', key: 'vad' },
+ { label: '语音生成模型(TTS)', key: 'tts' },
+ { label: '意图分类模型(Intent)', key: 'intent' },
+ { label: '记忆增强模型(Memory)', key: 'memory' }
+ ],
+ templates: ['通用男声','通用女声','阳光男生','奶气萌娃']
}
},
diff --git a/main/xiaozhi-server/config.yaml b/main/xiaozhi-server/config.yaml
index ac62f988..6bfe9322 100644
--- a/main/xiaozhi-server/config.yaml
+++ b/main/xiaozhi-server/config.yaml
@@ -510,6 +510,11 @@ music:
- ".p3"
refresh_time: 300 # 刷新音乐列表的时间间隔,单位为秒
+# 远程配置
+remote_config:
+ enabled: true
+ url: http://192.168.5.11:8002/user/agent/loadAgentConfig/
+
# 以下配置在小于等于0.0.9版本中的docker容器中可用
# 0.0.9以后的新版本源码部署已经无法奏效
manager:
diff --git a/main/xiaozhi-server/config/private_config.py b/main/xiaozhi-server/config/private_config.py
index f708a95b..5a0fd504 100644
--- a/main/xiaozhi-server/config/private_config.py
+++ b/main/xiaozhi-server/config/private_config.py
@@ -1,6 +1,8 @@
import os
import time
import yaml
+import json
+import requests
from config.logger import setup_logging
from typing import Dict, Any, Optional
from copy import deepcopy
@@ -22,6 +24,14 @@ class PrivateConfig:
async def load_or_create(self):
try:
+ # 优先通过远程获取配置
+ fetch_config_ = {}
+ remote_config = self.default_config['remote_config']
+ self.logger.bind(tag=TAG).info(f"remote config: {remote_config}")
+ if remote_config and remote_config['enabled']:
+ fetch_config_ = await self.fetch_config(remote_config['url'])
+
+ self.logger.bind(tag=TAG).info(f"fetch_config: {fetch_config_}")
await self.lock_manager.acquire_lock(self.config_path)
try:
if os.path.exists(self.config_path):
@@ -30,6 +40,9 @@ class PrivateConfig:
else:
all_configs = {}
+ if fetch_config_:
+ all_configs[self.device_id] = fetch_config_
+
if self.device_id not in all_configs:
# Get selected module names
selected_modules = self.default_config['selected_module']
@@ -64,9 +77,9 @@ class PrivateConfig:
all_configs[self.device_id] = device_config
- # Save updated configs
- with open(self.config_path, 'w', encoding='utf-8') as f:
- yaml.dump(all_configs, f, allow_unicode=True)
+ # Save updated configs
+ with open(self.config_path, 'w', encoding='utf-8') as f:
+ yaml.dump(all_configs, f, allow_unicode=True)
self.private_config = all_configs[self.device_id]
@@ -238,4 +251,60 @@ class PrivateConfig:
def get_owner(self) -> Optional[str]:
"""获取设备当前所有者"""
- return self.private_config.get('owner')
\ No newline at end of file
+ return self.private_config.get('owner')
+
+ async def fetch_config(self, fetch_url: str) -> Dict[str, Any]:
+ """通过HTTP请求远程获取配置"""
+ url = f"{fetch_url}{self.device_id}"
+ headers = {
+ 'device_id': self.device_id
+ }
+ try:
+ response = requests.get(url, headers=headers)
+ response.raise_for_status() # 如果响应状态码不是200,会抛出异常
+ # 检查响应内容类型是否为JSON
+ if response.headers.get('Content-Type') != 'application/json':
+ self.logger.bind(tag=TAG).error("Invalid content type: expected application/json")
+ return {}
+
+ # 解析JSON数据
+ config_data = response.json()
+
+ # 验证返回的数据结构
+ if not isinstance(config_data, dict):
+ self.logger.bind(tag=TAG).error("Invalid data format: expected a dictionary")
+ return {}
+
+ if config_data['code'] != 0:
+ self.logger.bind(tag=TAG).error(f"Fetch config error: {config_data['msg']}")
+ return {}
+
+ config_data_ = config_data['data']
+ if not isinstance(config_data_, dict):
+ self.logger.bind(tag=TAG).error("Invalid config data format: expected a dictionary")
+ return {}
+
+
+ # 检查必要的字段是否存在
+ required_fields = ['selected_module', 'prompt', 'LLM', 'TTS', 'ASR', 'VAD']
+ for field in required_fields:
+ if field not in config_data_:
+ self.logger.bind(tag=TAG).error(f"Missing required field: {field}")
+ return {}
+
+ # 检查每个模块的配置是否正确
+ for module in ['LLM', 'TTS', 'ASR', 'VAD']:
+ if not isinstance(config_data_[module], dict):
+ self.logger.bind(tag=TAG).error(f"Invalid data format for {module}: expected a dictionary")
+ return {}
+
+ selected_module = config_data_['selected_module'].get(module)
+ if selected_module not in config_data_[module]:
+ self.logger.bind(tag=TAG).error(f"Selected {module} not found in config: {selected_module}")
+ return {}
+
+ return config_data_
+
+ except requests.exceptions.RequestException as e:
+ self.logger.bind(tag=TAG).error(f"Error fetching config: {e}")
+ return {}
\ No newline at end of file