diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 68174a38..42a58dbe 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -4,6 +4,7 @@ on: push: tags: - 'v*.*.*' # 只在以 v 开头的标签推送时触发,例如 v1.0.0 + workflow_dispatch: jobs: release: @@ -41,7 +42,13 @@ jobs: - name: Extract version from tag id: get_version run: | - echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV + if [[ "$GITHUB_REF" =~ ^refs/tags/v([0-9]+\.[0-9]+\.[0-9]+)$ ]]; then + echo "VERSION=${BASH_REMATCH[1]}" >> $GITHUB_ENV + echo "IS_VERSION=true" >> $GITHUB_ENV + else + echo "VERSION=latest" >> $GITHUB_ENV + echo "IS_VERSION=false" >> $GITHUB_ENV + fi # 构建 xiaozhi-server 镜像 - name: Build and push xiaozhi-server @@ -51,8 +58,7 @@ jobs: file: Dockerfile-server push: true tags: | - ghcr.io/${{ github.repository }}:server_${{ env.VERSION }} - ghcr.io/${{ github.repository }}:server_latest + ${{ env.IS_VERSION == 'true' && format('ghcr.io/{0}:server_{1}\nghcr.io/{0}:server_latest', github.repository, env.VERSION) || format('ghcr.io/{0}:server_latest', github.repository) }} platforms: linux/amd64,linux/arm64 # 构建 manager-api 镜像 @@ -63,6 +69,5 @@ jobs: file: Dockerfile-web push: true tags: | - ghcr.io/${{ github.repository }}:web_${{ env.VERSION }} - ghcr.io/${{ github.repository }}:web_latest + ${{ env.IS_VERSION == 'true' && format('ghcr.io/{0}:web_{1}\nghcr.io/{0}:web_latest', github.repository, env.VERSION) || format('ghcr.io/{0}:web_latest', github.repository) }} platforms: linux/amd64,linux/arm64 \ No newline at end of file diff --git a/docs/Deployment.md b/docs/Deployment.md index 7338a981..dfac9dcd 100644 --- a/docs/Deployment.md +++ b/docs/Deployment.md @@ -311,15 +311,17 @@ LLM: 如果你能看到,类似以下日志,则是本项目服务启动成功的标志。 ``` -25-02-23 12:01:09[core.websocket_server] - INFO - Server is running at ws://xxx.xx.xx.xx:8000 +25-02-23 12:01:09[core.websocket_server] - INFO - Server is running at ws://xxx.xx.xx.xx:8000/xiaozhi/v1/ 25-02-23 12:01:09[core.websocket_server] - INFO - =======上面的地址是websocket协议地址,请勿用浏览器访问======= +25-02-23 12:01:09[core.websocket_server] - INFO - 如想测试websocket请用谷歌浏览器打开test目录下的test_page.html +25-02-23 12:01:09[core.websocket_server] - INFO - ======================================================= ``` 正常来说,如果您是通过源码运行本项目,日志会有你的接口地址信息。 但是如果你用docker部署,那么你的日志里给出的接口地址信息就不是真实的接口地址。 最正确的方法,是根据电脑的局域网IP来确定你的接口地址。 -如果你的电脑的局域网IP比如是`192.168.1.25`,那么你的接口地址就是:`ws://192.168.1.25:8000`。 +如果你的电脑的局域网IP比如是`192.168.1.25`,那么你的接口地址就是:`ws://192.168.1.25:8000/xiaozhi/v1/`。 这个信息很有用的,后面`编译esp32固件`需要用到。 @@ -328,14 +330,16 @@ LLM: 以下是一些常见问题,供参考: -[1、为什么我说的话,小智识别出来很多韩文、日文、英文](./FAQ.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87) +[1、为什么我说的话,小智识别出来很多韩文、日文、英文](./FAQ.md) -[2、为什么会出现“TTS 任务出错 文件不存在”?](./FAQ.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87) +[2、为什么会出现“TTS 任务出错 文件不存在”?](./FAQ.md) -[3、TTS 经常失败,经常超时](./FAQ.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87) +[3、TTS 经常失败,经常超时](./FAQ.md) -[4、如何提高小智对话响应速度?](./FAQ.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87) +[4、使用Wifi能连接自建服务器,但是4G模式却接不上](./FAQ.md) -[5、我说话很慢,停顿时小智老是抢话](./FAQ.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87) +[5、如何提高小智对话响应速度?](./FAQ.md) -[6、我想通过小智控制电灯、空调、远程开关机等操作](./FAQ.md#1%E4%B8%BA%E4%BB%80%E4%B9%88%E6%88%91%E8%AF%B4%E7%9A%84%E8%AF%9D%E5%B0%8F%E6%99%BA%E8%AF%86%E5%88%AB%E5%87%BA%E6%9D%A5%E5%BE%88%E5%A4%9A%E9%9F%A9%E6%96%87%E6%97%A5%E6%96%87%E8%8B%B1%E6%96%87) +[6、我说话很慢,停顿时小智老是抢话](./FAQ.md) + +[7、我想通过小智控制电灯、空调、远程开关机等操作](./FAQ.md) diff --git a/docs/FAQ.md b/docs/FAQ.md index 8acdb1a0..9be27089 100644 --- a/docs/FAQ.md +++ b/docs/FAQ.md @@ -21,7 +21,17 @@ conda install conda-forge::ffmpeg 建议:如果 `EdgeTTS` 经常失败,请先检查是否使用了代理(梯子)。如果使用了,请尝试关闭代理后再试; 如果用的是火山引擎的豆包 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` - TTS:`DoubaoTTS` -### 5、我说话很慢,停顿时小智老是抢话 🗣️ +### 6、我说话很慢,停顿时小智老是抢话 🗣️ 建议:在配置文件中找到如下部分,将 `min_silence_duration_ms` 的值调大(例如改为 `1000`): @@ -90,7 +100,7 @@ VAD: min_silence_duration_ms: 700 # 如果说话停顿较长,可将此值调大 ``` -### 6、我想通过小智控制电灯、空调、远程开关机等操作 💡 +### 7、我想通过小智控制电灯、空调、远程开关机等操作 💡 本项目,支持以工具调用的方式控制HomeAssistant设备 @@ -128,7 +138,7 @@ Intent: - hass_play_music ``` -### 7、更多问题,可联系我们反馈 💬 +### 8、更多问题,可联系我们反馈 💬 我们的联系方式放在[百度网盘中,点击前往](https://pan.baidu.com/s/1x6USjvP1nTRsZ45XlJu65Q),提取码是`223y`。 diff --git a/docs/docker/nginx.conf b/docs/docker/nginx.conf index 820fda23..6a746260 100644 --- a/docs/docker/nginx.conf +++ b/docs/docker/nginx.conf @@ -9,7 +9,7 @@ server { } # API反向代理(Java项目) - location /xiaozhi-esp32-api/ { + location /xiaozhi/ { proxy_pass http://127.0.0.1:8003; proxy_set_header Host $host; proxy_cookie_path /api/ /; diff --git a/docs/firmware-build.md b/docs/firmware-build.md index 78a90ae8..9187681e 100644 --- a/docs/firmware-build.md +++ b/docs/firmware-build.md @@ -1,10 +1,9 @@ # 编译esp32固件 -1. 下载`xiaozhi-esp32` - 项目,按照这个教程配置项目环境[《Windows搭建 ESP IDF 5.3.2开发环境以及编译小智》](https://icnynnzcwou8.feishu.cn/wiki/JEYDwTTALi5s2zkGlFGcDiRknXf) +1. 先按照这个教程配置项目环境[《Windows搭建 ESP IDF 5.3.2开发环境以及编译小智》](https://icnynnzcwou8.feishu.cn/wiki/JEYDwTTALi5s2zkGlFGcDiRknXf) -2. 打开`xiaozhi-esp32/main/Kconfig.projbuild`文件,找到`WEBSOCKET_URL`的`default`的内容,把`wss://api.tenclass.net` - 改成你自己的地址,例如,我的接口地址是`ws://192.168.1.25:8000`,就把内容改成这个。 +2. 配置好编译环境后,下载虾哥[xiaozhi-esp32](https://github.com/78/xiaozhi-esp32)项目源码,进入虾哥项目,打开`xiaozhi-esp32/main/Kconfig.projbuild`文件,找到`WEBSOCKET_URL`的`default`的内容,把`wss://api.tenclass.net` + 改成你自己的地址,例如,我的接口地址是`ws://192.168.1.25:8000/xiaozhi/v1/`,就把内容改成这个。 修改前: @@ -92,14 +91,16 @@ https://espressif.github.io/esp-launchpad/ 以下是一些常见问题,供参考: -[1、为什么我说的话,小智识别出来很多韩文、日文、英文](./FAQ.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-) +[1、为什么我说的话,小智识别出来很多韩文、日文、英文](./FAQ.md) -[2、为什么会出现“TTS 任务出错 文件不存在”?](./FAQ.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-) +[2、为什么会出现“TTS 任务出错 文件不存在”?](./FAQ.md) -[3、TTS 经常失败,经常超时](./FAQ.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-) +[3、TTS 经常失败,经常超时](./FAQ.md) -[4、如何提高小智对话响应速度?](./FAQ.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-) +[4、使用Wifi能连接自建服务器,但是4G模式却接不上](./FAQ.md) -[5、我说话很慢,停顿时小智老是抢话](./FAQ.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-) +[5、如何提高小智对话响应速度?](./FAQ.md) -[6、我想通过小智控制电灯、空调、远程开关机等操作](./FAQ.md#1tts-%E7%BB%8F%E5%B8%B8%E5%A4%B1%E8%B4%A5%E7%BB%8F%E5%B8%B8%E8%B6%85%E6%97%B6-) +[6、我说话很慢,停顿时小智老是抢话](./FAQ.md) + +[7、我想通过小智控制电灯、空调、远程开关机等操作](./FAQ.md) diff --git a/main/README.md b/main/README.md index 9dc57ee3..ba3a499b 100644 --- a/main/README.md +++ b/main/README.md @@ -18,4 +18,4 @@ xiaozhi-esp32-server # manager-web 、manager-api接口协议 -https://2662r3426b.vicp.fun/xiaozhi-esp32-api/api/v1/doc.html +https://2662r3426b.vicp.fun/xiaozhi/v1/doc.html diff --git a/main/manager-api/README.md b/main/manager-api/README.md index 5f7fda86..bef06b68 100644 --- a/main/manager-api/README.md +++ b/main/manager-api/README.md @@ -1,4 +1,4 @@ -本文档是开发类文档,如需部署小智服务端,[点击这里查看部署教程](.././FAQ.md#%E4%BD%BF%E7%94%A8%E6%96%B9%E5%BC%8F-) +本文档是开发类文档,如需部署小智服务端,[点击这里查看部署教程](../../../docs/Deployment.md) # 项目介绍 manager-api 该项目基于SpringBoot框架开发。 @@ -23,12 +23,7 @@ CREATE DATABASE xiaozhi_esp32_server CHARACTER SET utf8mb4 COLLATE utf8mb4_unico 如果还没有MySQL,你可以通过docker安装mysql ``` -docker run --name xiaozhi-esp32-server-db \ --e MYSQL_ROOT_PASSWORD=123456 \ --p 3306:3306 \ --e MYSQL_DATABASE=xiaozhi_esp32_server \ --e MYSQL_INITDB_ARGS="--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci" \ --d mysql:latest +docker run --name xiaozhi-esp32-server-db -e MYSQL_ROOT_PASSWORD=123456 -p 3306:3306 -e MYSQL_DATABASE=xiaozhi_esp32_server -e MYSQL_INITDB_ARGS="--character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci" -d mysql:latest ``` # 确认项目数据库连接信息 @@ -92,5 +87,5 @@ nohup java -jar xiaozhi-esp32-api.jar --spring.profiles.activate=dev # 接口文档 -启动后打开:http://localhost:8002/xiaozhi-esp32-api/doc.html +启动后打开:http://localhost:8002/xiaozhi/doc.html diff --git a/main/manager-api/src/main/java/xiaozhi/AdminApplication.java b/main/manager-api/src/main/java/xiaozhi/AdminApplication.java index 8a96edb2..84e30123 100644 --- a/main/manager-api/src/main/java/xiaozhi/AdminApplication.java +++ b/main/manager-api/src/main/java/xiaozhi/AdminApplication.java @@ -8,6 +8,6 @@ public class AdminApplication { public static void main(String[] args) { SpringApplication.run(AdminApplication.class, args); - System.out.println("http://localhost:8002/xiaozhi-esp32-api/api/v1/doc.html"); + System.out.println("http://localhost:8002/xiaozhi/doc.html"); } } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/common/config/SwaggerConfig.java b/main/manager-api/src/main/java/xiaozhi/common/config/SwaggerConfig.java index da23a233..0def6589 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/config/SwaggerConfig.java +++ b/main/manager-api/src/main/java/xiaozhi/common/config/SwaggerConfig.java @@ -51,7 +51,7 @@ public class SwaggerConfig { public GroupedOpenApi timbreApi() { return GroupedOpenApi.builder() .group("timbre") - .pathsToMatch("/timbre/**") + .pathsToMatch("/ttsVoice/**") .build(); } diff --git a/main/manager-api/src/main/java/xiaozhi/common/exception/RenExceptionHandler.java b/main/manager-api/src/main/java/xiaozhi/common/exception/RenExceptionHandler.java index 0f83772e..0d0aa118 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/exception/RenExceptionHandler.java +++ b/main/manager-api/src/main/java/xiaozhi/common/exception/RenExceptionHandler.java @@ -4,6 +4,7 @@ import org.apache.shiro.authz.UnauthorizedException; import org.springframework.dao.DuplicateKeyException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.resource.NoResourceFoundException; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -53,4 +54,10 @@ public class RenExceptionHandler { return new Result().error(); } + @ExceptionHandler(NoResourceFoundException.class) + public Result handleNoResourceFoundException(NoResourceFoundException ex) { + log.warn("Resource not found: {}", ex.getMessage()); + return new Result().error(404, "资源不存在"); + } + } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/common/redis/RedisKeys.java b/main/manager-api/src/main/java/xiaozhi/common/redis/RedisKeys.java index 21121fd7..53aa08dc 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/redis/RedisKeys.java +++ b/main/manager-api/src/main/java/xiaozhi/common/redis/RedisKeys.java @@ -26,4 +26,32 @@ public class RedisKeys { public static String getDeviceCaptchaKey(String captcha) { return "sys:device:captcha:" + captcha; } + + /** + * 用户id的Key + */ + public static String getUserIdKey(Long userid) { + return "sys:username:id:" + userid; + } + + /** + * 模型名称的Key + */ + public static String getModelNameById(String id) { + return "sys:model:name:" + id; + } + + /** + * 获取音色名称缓存key + */ + public static String getTimbreNameById(String id) { + return "timbre:name:" + id; + } + + /** + * 获取设备数量缓存key + */ + public static String getAgentDeviceCountById(String id) { + return "agent:device:count:" + id; + } } diff --git a/main/manager-api/src/main/java/xiaozhi/common/utils/DateUtils.java b/main/manager-api/src/main/java/xiaozhi/common/utils/DateUtils.java index 9e5ef3e1..bfa7afed 100644 --- a/main/manager-api/src/main/java/xiaozhi/common/utils/DateUtils.java +++ b/main/manager-api/src/main/java/xiaozhi/common/utils/DateUtils.java @@ -2,6 +2,9 @@ package xiaozhi.common.utils; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.temporal.ChronoUnit; import java.util.Date; /** @@ -60,4 +63,39 @@ public class DateUtils { 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); + } + } } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java index 93a741df..eea2d7fe 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/controller/AgentController.java @@ -29,11 +29,13 @@ import xiaozhi.common.user.UserDetail; import xiaozhi.common.utils.ConvertUtils; import xiaozhi.common.utils.Result; import xiaozhi.modules.agent.dto.AgentCreateDTO; +import xiaozhi.modules.agent.dto.AgentDTO; import xiaozhi.modules.agent.dto.AgentUpdateDTO; import xiaozhi.modules.agent.entity.AgentEntity; import xiaozhi.modules.agent.entity.AgentTemplateEntity; import xiaozhi.modules.agent.service.AgentService; import xiaozhi.modules.agent.service.AgentTemplateService; +import xiaozhi.modules.device.service.DeviceService; import xiaozhi.modules.security.user.SecurityUser; @Tag(name = "智能体管理") @@ -43,14 +45,15 @@ import xiaozhi.modules.security.user.SecurityUser; public class AgentController { private final AgentService agentService; private final AgentTemplateService agentTemplateService; + private final DeviceService deviceService; @GetMapping("/list") @Operation(summary = "获取用户智能体列表") @RequiresPermissions("sys:role:normal") - public Result> getUserAgents() { + public Result> getUserAgents() { UserDetail user = SecurityUser.getUser(); - List agents = agentService.getUserAgents(user.getId()); - return new Result>().ok(agents); + List agents = agentService.getUserAgents(user.getId()); + return new Result>().ok(agents); } @GetMapping("/all") @@ -80,6 +83,22 @@ public class AgentController { public Result save(@RequestBody @Valid AgentCreateDTO dto) { AgentEntity entity = ConvertUtils.sourceToTarget(dto, AgentEntity.class); + // 获取默认模板 + AgentTemplateEntity template = agentTemplateService.getDefaultTemplate(); + if (template != null) { + // 设置模板中的默认值 + entity.setAsrModelId(template.getAsrModelId()); + entity.setVadModelId(template.getVadModelId()); + entity.setLlmModelId(template.getLlmModelId()); + entity.setTtsModelId(template.getTtsModelId()); + entity.setTtsVoiceId(template.getTtsVoiceId()); + entity.setMemModelId(template.getMemModelId()); + entity.setIntentModelId(template.getIntentModelId()); + entity.setSystemPrompt(template.getSystemPrompt()); + entity.setLangCode(template.getLangCode()); + entity.setLanguage(template.getLanguage()); + } + // 设置用户ID和创建者信息 UserDetail user = SecurityUser.getUser(); entity.setUserId(user.getId()); @@ -157,6 +176,9 @@ public class AgentController { @Operation(summary = "删除智能体") @RequiresPermissions("sys:role:normal") public Result delete(@PathVariable String id) { + // 先删除关联的设备 + deviceService.deleteByAgentId(id); + // 再删除智能体 agentService.deleteById(id); return new Result<>(); } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentDao.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentDao.java index d9aec7d7..8c80d6cc 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentDao.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dao/AgentDao.java @@ -1,11 +1,18 @@ package xiaozhi.modules.agent.dao; import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; import xiaozhi.common.dao.BaseDao; import xiaozhi.modules.agent.entity.AgentEntity; @Mapper public interface AgentDao extends BaseDao { - + /** + * 获取智能体的设备数量 + * + * @param agentId 智能体ID + * @return 设备数量 + */ + Integer getDeviceCountByAgentId(@Param("agentId") String agentId); } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentDTO.java new file mode 100644 index 00000000..a63c96e7 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/agent/dto/AgentDTO.java @@ -0,0 +1,38 @@ +package xiaozhi.modules.agent.dto; + +import java.util.Date; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +/** + * 智能体数据传输对象 + * 用于在服务层和控制器层之间传递智能体相关的数据 + */ +@Data +@Schema(description = "智能体对象") +public class AgentDTO { + @Schema(description = "智能体编码", example = "AGT_1234567890") + private String id; + + @Schema(description = "智能体名称", example = "客服助手") + private String agentName; + + @Schema(description = "语音合成模型名称", example = "tts_model_01") + private String ttsModelName; + + @Schema(description = "音色名称", example = "voice_01") + private String ttsVoiceName; + + @Schema(description = "大语言模型名称", example = "llm_model_01") + private String llmModelName; + + @Schema(description = "角色设定参数", example = "你是一个专业的客服助手,负责回答用户问题并提供帮助") + private String systemPrompt; + + @Schema(description = "最后连接时间", example = "2024-03-20 10:00:00") + private Date lastConnectedAt; + + @Schema(description = "设备数量", example = "10") + private Integer deviceCount; +} \ No newline at end of file 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 index da958e4c..8d0b2959 100644 --- 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 @@ -5,13 +5,10 @@ import java.util.Map; import xiaozhi.common.page.PageData; import xiaozhi.common.service.BaseService; +import xiaozhi.modules.agent.dto.AgentDTO; import xiaozhi.modules.agent.entity.AgentEntity; public interface AgentService extends BaseService { - /** - * 根据用户ID获取智能体列表 - */ - List getUserAgents(Long userId); /** * 管理员获取所有智能体列表(分页) @@ -22,4 +19,27 @@ public interface AgentService extends BaseService { * 获取智能体详情 */ AgentEntity getAgentById(String id); + + /** + * 删除这个用户的所有 + * + * @param userId + */ + void deleteAgentByUserId(Long userId); + + /** + * 获取用户智能体列表 + * + * @param userId + * @return + */ + List getUserAgents(Long userId); + + /** + * 获取智能体的设备数量 + * + * @param agentId 智能体ID + * @return 设备数量 + */ + Integer getDeviceCountByAgentId(String agentId); } \ No newline at end of file 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 index 9c27bdb5..197c860e 100644 --- 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 @@ -11,4 +11,10 @@ import xiaozhi.modules.agent.entity.AgentTemplateEntity; */ public interface AgentTemplateService extends IService { + /** + * 获取默认模板 + * + * @return 默认模板实体 + */ + AgentTemplateEntity getDefaultTemplate(); } 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 index 8e73c003..bb2a3ed0 100644 --- 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 @@ -3,33 +3,44 @@ package xiaozhi.modules.agent.service.impl; import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import xiaozhi.common.page.PageData; +import xiaozhi.common.redis.RedisKeys; +import xiaozhi.common.redis.RedisUtils; import xiaozhi.common.service.impl.BaseServiceImpl; import xiaozhi.modules.agent.dao.AgentDao; +import xiaozhi.modules.agent.dto.AgentDTO; import xiaozhi.modules.agent.entity.AgentEntity; import xiaozhi.modules.agent.service.AgentService; +import xiaozhi.modules.model.service.ModelConfigService; +import xiaozhi.modules.timbre.service.TimbreService; @Service public class AgentServiceImpl extends BaseServiceImpl implements AgentService { private final AgentDao agentDao; + @Autowired + private TimbreService timbreModelService; + + @Autowired + private ModelConfigService modelConfigService; + + @Autowired + private RedisUtils redisUtils; + public AgentServiceImpl(AgentDao agentDao) { this.agentDao = agentDao; } - @Override - public List getUserAgents(Long userId) { - QueryWrapper wrapper = new QueryWrapper<>(); - wrapper.eq("user_id", userId); - return agentDao.selectList(wrapper); - } - @Override public PageData adminAgentList(Map params) { IPage page = agentDao.selectPage( @@ -62,4 +73,61 @@ public class AgentServiceImpl extends BaseServiceImpl imp return super.insert(entity); } + + @Override + public void deleteAgentByUserId(Long userId) { + UpdateWrapper wrapper = new UpdateWrapper<>(); + wrapper.eq("user_id", userId); + baseDao.delete(wrapper); + } + + @Override + public List getUserAgents(Long userId) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("user_id", userId); + List agents = agentDao.selectList(wrapper); + return agents.stream().map(agent -> { + AgentDTO dto = new AgentDTO(); + dto.setId(agent.getId()); + dto.setAgentName(agent.getAgentName()); + dto.setSystemPrompt(agent.getSystemPrompt()); + + // 获取 TTS 模型名称 + dto.setTtsModelName(modelConfigService.getModelNameById(agent.getTtsModelId())); + + // 获取 LLM 模型名称 + dto.setLlmModelName(modelConfigService.getModelNameById(agent.getLlmModelId())); + + // 获取 TTS 音色名称 + dto.setTtsVoiceName(timbreModelService.getTimbreNameById(agent.getTtsVoiceId())); + + // 获取设备数量 + dto.setDeviceCount(getDeviceCountByAgentId(agent.getId())); + + return dto; + }).collect(Collectors.toList()); + } + + @Override + public Integer getDeviceCountByAgentId(String agentId) { + if (StringUtils.isBlank(agentId)) { + return 0; + } + + // 先从Redis中获取 + Integer cachedCount = (Integer) redisUtils.get(RedisKeys.getAgentDeviceCountById(agentId)); + if (cachedCount != null) { + return cachedCount; + } + + // 如果Redis中没有,则从数据库查询 + Integer deviceCount = agentDao.getDeviceCountByAgentId(agentId); + + // 将结果存入Redis + if (deviceCount != null) { + redisUtils.set(RedisKeys.getAgentDeviceCountById(agentId), deviceCount, 60); + } + + return deviceCount != null ? deviceCount : 0; + } } \ No newline at end of file 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 index 62338fad..949a2f2d 100644 --- 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 @@ -2,6 +2,7 @@ package xiaozhi.modules.agent.service.impl; import org.springframework.stereotype.Service; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import xiaozhi.modules.agent.dao.AgentTemplateDao; @@ -15,6 +16,17 @@ import xiaozhi.modules.agent.service.AgentTemplateService; */ @Service public class AgentTemplateServiceImpl extends ServiceImpl - implements AgentTemplateService { + implements AgentTemplateService { + /** + * 获取默认模板 + * + * @return 默认模板实体 + */ + public AgentTemplateEntity getDefaultTemplate() { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.orderByAsc(AgentTemplateEntity::getSort) + .last("LIMIT 1"); + return this.getOne(wrapper); + } } 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 index 43009bf8..1c68a756 100644 --- 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 @@ -19,7 +19,6 @@ import xiaozhi.common.redis.RedisKeys; import xiaozhi.common.redis.RedisUtils; import xiaozhi.common.user.UserDetail; import xiaozhi.common.utils.Result; -import xiaozhi.modules.device.dto.DeviceBindDTO; import xiaozhi.modules.device.dto.DeviceRegisterDTO; import xiaozhi.modules.device.dto.DeviceUnBindDTO; import xiaozhi.modules.device.entity.DeviceEntity; @@ -39,13 +38,7 @@ public class DeviceController { @Operation(summary = "绑定设备") @RequiresPermissions("sys:role:normal") public Result bindDevice(@PathVariable String agentId, @PathVariable String deviceCode) { - String macAddress = (String) redisUtils.get(RedisKeys.getDeviceCaptchaKey(deviceCode)); - if (StringUtils.isBlank(macAddress)) { - return new Result().error(ErrorCode.DEVICE_CAPTCHA_ERROR); - } - Long user = SecurityUser.getUser().getId(); - DeviceBindDTO deviceBindDTO = new DeviceBindDTO(macAddress, user, agentId); - deviceService.bindDevice(deviceBindDTO); + deviceService.deviceActivation(agentId, deviceCode); return new Result<>(); } 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 index 16f53256..4e3a2a95 100644 --- 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 @@ -10,7 +10,6 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import com.fasterxml.jackson.annotation.JsonInclude; @@ -22,7 +21,6 @@ import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; -import xiaozhi.common.utils.Result; import xiaozhi.modules.device.dto.DeviceReportReqDTO; import xiaozhi.modules.device.dto.DeviceReportRespDTO; import xiaozhi.modules.device.service.DeviceService; @@ -31,7 +29,7 @@ import xiaozhi.modules.device.utils.NetworkUtil; @Tag(name = "设备管理", description = "OTA 相关接口") @RestController @RequiredArgsConstructor -@RequestMapping("/ota") +@RequestMapping("/ota/") public class OTAController { private final DeviceService deviceService; @@ -55,10 +53,10 @@ public class OTAController { return createResponse(deviceService.checkDeviceActive(macAddress, deviceId, clientId, deviceReportReqDTO)); } - @Operation(summary = "设备激活") - @GetMapping("/activation") - public Result deviceActivation(@RequestParam String code) { - return new Result().ok(deviceService.deviceActivation(code)); + @Operation(summary = "获取 OTA 提示信息") + @GetMapping + public ResponseEntity getOTAPrompt() { + return createResponse(DeviceReportRespDTO.createError("请提交正确的ota参数")); } @SneakyThrows diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DevicePageUserDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DevicePageUserDTO.java new file mode 100644 index 00000000..26bccbfd --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DevicePageUserDTO.java @@ -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; +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceReportRespDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceReportRespDTO.java index 6226378d..9f93a9b7 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceReportRespDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/dto/DeviceReportRespDTO.java @@ -9,7 +9,7 @@ import lombok.Setter; @Schema(description = "设备OTA检测版本返回体,包含激活码要求") public class DeviceReportRespDTO { @Schema(description = "服务器时间") - private ServerTime serverTime; + private ServerTime server_time; @Schema(description = "激活码") private Activation activation; @@ -56,6 +56,6 @@ public class DeviceReportRespDTO { private String timeZone; @Schema(description = "时区偏移量,单位为分钟") - private Integer timezoneOffset; + private Integer timezone_offset; } } 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 bc26f649..37ff8d49 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 @@ -2,10 +2,12 @@ package xiaozhi.modules.device.service; import java.util.List; -import xiaozhi.modules.device.dto.DeviceBindDTO; +import xiaozhi.common.page.PageData; +import xiaozhi.modules.device.dto.DevicePageUserDTO; import xiaozhi.modules.device.dto.DeviceReportReqDTO; import xiaozhi.modules.device.dto.DeviceReportRespDTO; import xiaozhi.modules.device.entity.DeviceEntity; +import xiaozhi.modules.device.vo.UserShowDeviceListVO; public interface DeviceService { @@ -21,12 +23,7 @@ public interface DeviceService { DeviceReportReqDTO deviceReport); /** - * 绑定设备 - */ - DeviceEntity bindDevice(DeviceBindDTO deviceHeader); - - /** - * 获取用户设备列表 + * 获取用户指定智能体的设备列表, */ List getUserDevices(Long userId, String agentId); @@ -38,5 +35,35 @@ public interface DeviceService { /** * 设备激活 */ - Boolean deviceActivation(String activationCode); + Boolean deviceActivation(String agentId, String activationCode); + + /** + * 删除此用户的所有设备 + * + * @param userId 用户id + */ + void deleteByUserId(Long userId); + + /** + * 删除指定智能体关联的所有设备 + * + * @param agentId 智能体id + */ + void deleteByAgentId(String agentId); + + /** + * 获取指定用户的设备数量 + * + * @param userId 用户id + * @return 设备数量 + */ + Long selectCountByUserId(Long userId); + + /** + * 分页获取全部设备信息 + * + * @param dto 分页查找参数 + * @return 用户列表分页数据 + */ + PageData page(DevicePageUserDTO dto); } \ No newline at end of file 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 4fd63826..a9797f42 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 @@ -16,34 +16,43 @@ import org.springframework.stereotype.Service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; +import com.baomidou.mybatisplus.core.metadata.IPage; import cn.hutool.core.util.RandomUtil; +import xiaozhi.common.constant.Constant; import xiaozhi.common.exception.RenException; +import xiaozhi.common.page.PageData; import xiaozhi.common.service.impl.BaseServiceImpl; import xiaozhi.common.user.UserDetail; import xiaozhi.common.utils.ConvertUtils; +import xiaozhi.common.utils.DateUtils; import xiaozhi.modules.device.dao.DeviceDao; -import xiaozhi.modules.device.dto.DeviceBindDTO; +import xiaozhi.modules.device.dto.DevicePageUserDTO; import xiaozhi.modules.device.dto.DeviceReportReqDTO; import xiaozhi.modules.device.dto.DeviceReportRespDTO; import xiaozhi.modules.device.entity.DeviceEntity; import xiaozhi.modules.device.service.DeviceService; +import xiaozhi.modules.device.vo.UserShowDeviceListVO; import xiaozhi.modules.security.user.SecurityUser; +import xiaozhi.modules.sys.service.SysUserUtilService; @Service public class DeviceServiceImpl extends BaseServiceImpl implements DeviceService { private final DeviceDao deviceDao; + private final SysUserUtilService sysUserUtilService; + private final String frontedUrl; private final RedisTemplate redisTemplate; // 添加构造函数来初始化 deviceMapper - public DeviceServiceImpl(DeviceDao deviceDao, + public DeviceServiceImpl(DeviceDao deviceDao, SysUserUtilService sysUserUtilService, @Value("${app.fronted-url:http://localhost:8001}") String frontedUrl, RedisTemplate redisTemplate) { this.deviceDao = deviceDao; + this.sysUserUtilService = sysUserUtilService; this.frontedUrl = frontedUrl; this.redisTemplate = redisTemplate; } @@ -56,7 +65,7 @@ public class DeviceServiceImpl extends BaseServiceImpl } @Override - public Boolean deviceActivation(String activationCode) { + public Boolean deviceActivation(String agentId, String activationCode) { if (StringUtils.isBlank(activationCode)) { throw new RenException("激活码不能为空"); } @@ -93,6 +102,7 @@ public class DeviceServiceImpl extends BaseServiceImpl DeviceEntity deviceEntity = new DeviceEntity(); deviceEntity.setId(deviceId); deviceEntity.setBoard(board); + deviceEntity.setAgentId(agentId); deviceEntity.setAppVersion(appVersion); deviceEntity.setMacAddress(macAddress); deviceEntity.setUserId(user.getId()); @@ -113,13 +123,13 @@ public class DeviceServiceImpl extends BaseServiceImpl public DeviceReportRespDTO checkDeviceActive(String macAddress, String deviceId, String clientId, DeviceReportReqDTO deviceReport) { DeviceReportRespDTO response = new DeviceReportRespDTO(); - response.setServerTime(buildServerTime()); + response.setServer_time(buildServerTime()); // todo: 此处是固件信息,目前是针对固件上传上来的版本号再返回回去 // 在未来开发了固件更新功能,需要更换此处代码, // 或写定时任务定期请求虾哥的OTA,获取最新的版本讯息保存到服务内 DeviceReportRespDTO.Firmware firmware = new DeviceReportRespDTO.Firmware(); firmware.setVersion(deviceReport.getApplication().getVersion()); - firmware.setUrl("http://localhost:8002/xiaozhi-esp32-api/api/v1/ota/download"); + firmware.setUrl("http://localhost:8002/xiaozhi/ota/download"); response.setFirmware(firmware); DeviceEntity deviceById = getDeviceById(deviceId); @@ -168,19 +178,6 @@ public class DeviceServiceImpl extends BaseServiceImpl return response; } - @Override - public DeviceEntity bindDevice(DeviceBindDTO dto) { - // 查看是否已经被绑定 - DeviceEntity deviceEntity = baseDao - .selectOne(new LambdaQueryWrapper().eq(DeviceEntity::getMacAddress, dto.getMacAddress())); - if (deviceEntity != null) { - throw new RenException("设备已绑定"); - } - deviceEntity = ConvertUtils.sourceToTarget(dto, DeviceEntity.class); - baseDao.insert(deviceEntity); - return deviceEntity; - } - @Override public List getUserDevices(Long userId, String agentId) { QueryWrapper wrapper = new QueryWrapper<>(); @@ -197,12 +194,58 @@ public class DeviceServiceImpl extends BaseServiceImpl baseDao.delete(wrapper); } + @Override + public void deleteByUserId(Long userId) { + UpdateWrapper wrapper = new UpdateWrapper<>(); + wrapper.eq("user_id", userId); + baseDao.delete(wrapper); + } + + @Override + public Long selectCountByUserId(Long userId) { + UpdateWrapper wrapper = new UpdateWrapper<>(); + wrapper.eq("user_id", userId); + return baseDao.selectCount(wrapper); + } + + @Override + public void deleteByAgentId(String agentId) { + UpdateWrapper wrapper = new UpdateWrapper<>(); + wrapper.eq("agent_id", agentId); + baseDao.delete(wrapper); + } + + @Override + public PageData page(DevicePageUserDTO dto) { + Map params = new HashMap(); + params.put(Constant.PAGE, dto.getPage()); + params.put(Constant.LIMIT, dto.getLimit()); + IPage page = baseDao.selectPage( + getPage(params, "sort", true), + // 定义查询条件 + new QueryWrapper() + // 必须设备关键词查找 + .like(StringUtils.isNotBlank(dto.getKeywords()), "alias", dto.getKeywords())); + // 循环处理page获取回来的数据,返回需要的字段 + List 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() { DeviceReportRespDTO.ServerTime serverTime = new DeviceReportRespDTO.ServerTime(); TimeZone tz = TimeZone.getDefault(); serverTime.setTimestamp(Instant.now().toEpochMilli()); serverTime.setTimeZone(tz.getID()); - serverTime.setTimezoneOffset(tz.getOffset(System.currentTimeMillis()) / (60 * 1000)); + serverTime.setTimezone_offset(tz.getOffset(System.currentTimeMillis()) / (60 * 1000)); return serverTime; } } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/device/vo/UserShowDeviceListVO.java b/main/manager-api/src/main/java/xiaozhi/modules/device/vo/UserShowDeviceListVO.java new file mode 100644 index 00000000..e3a5d8fd --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/device/vo/UserShowDeviceListVO.java @@ -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; + +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/controller/ModelController.java b/main/manager-api/src/main/java/xiaozhi/modules/model/controller/ModelController.java index af1e772f..0ce42157 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/controller/ModelController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/controller/ModelController.java @@ -17,13 +17,17 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.AllArgsConstructor; import xiaozhi.common.page.PageData; +import xiaozhi.common.utils.ConvertUtils; import xiaozhi.common.utils.Result; +import xiaozhi.modules.model.dto.ModelBasicInfoDTO; import xiaozhi.modules.model.dto.ModelConfigBodyDTO; import xiaozhi.modules.model.dto.ModelConfigDTO; import xiaozhi.modules.model.dto.ModelProviderDTO; -import xiaozhi.modules.model.dto.ModelProviderFieldDTO; +import xiaozhi.modules.model.dto.VoiceDTO; +import xiaozhi.modules.model.entity.ModelConfigEntity; import xiaozhi.modules.model.service.ModelConfigService; import xiaozhi.modules.model.service.ModelProviderService; +import xiaozhi.modules.timbre.service.TimbreService; @AllArgsConstructor @RestController @@ -32,16 +36,16 @@ import xiaozhi.modules.model.service.ModelProviderService; public class ModelController { private final ModelProviderService modelProviderService; - + private final TimbreService timbreService; private final ModelConfigService modelConfigService; - @GetMapping("/models/names") + @GetMapping("/names") @Operation(summary = "获取所有模型名称") - @RequiresPermissions("sys:role:superAdmin") - public Result> getModelNames(@RequestParam String modelType, + @RequiresPermissions("sys:role:normal") + public Result> getModelNames(@RequestParam String modelType, @RequestParam(required = false) String modelName) { - List modelNameList = modelConfigService.getModelCodeList(modelType, modelName); - return new Result>().ok(modelNameList); + List modelList = modelConfigService.getModelCodeList(modelType, modelName); + return new Result>().ok(modelList); } @GetMapping("/{modelType}/provideTypes") @@ -52,16 +56,7 @@ public class ModelController { return new Result>().ok(modelProviderDTOS); } - @GetMapping("/{modelType}/{provideCode}/fields") - @Operation(summary = "获取模型供应器字段") - @RequiresPermissions("sys:role:superAdmin") - public Result> getModelProviderFields(@PathVariable String modelType, - @PathVariable String provideCode) { - List fieldList = modelProviderService.getFieldList(modelType, provideCode); - return new Result>().ok(fieldList); - } - - @GetMapping("/models/list") + @GetMapping("/list") @Operation(summary = "获取模型配置列表") @RequiresPermissions("sys:role:superAdmin") public Result> getModelConfigList( @@ -73,7 +68,7 @@ public class ModelController { return new Result>().ok(pageList); } - @PostMapping("/models/{modelType}/{provideCode}") + @PostMapping("/{modelType}/{provideCode}") @Operation(summary = "新增模型配置") @RequiresPermissions("sys:role:superAdmin") public Result addModelConfig(@PathVariable String modelType, @@ -83,7 +78,7 @@ public class ModelController { return new Result().ok(modelConfigDTO); } - @PutMapping("/models/{modelType}/{provideCode}/{id}") + @PutMapping("/{modelType}/{provideCode}/{id}") @Operation(summary = "编辑模型配置") @RequiresPermissions("sys:role:superAdmin") public Result editModelConfig(@PathVariable String modelType, @@ -94,22 +89,42 @@ public class ModelController { return new Result().ok(modelConfigDTO); } - @DeleteMapping("/models/{modelType}/{provideCode}/{id}") + @DeleteMapping("/{id}") @Operation(summary = "删除模型配置") @RequiresPermissions("sys:role:superAdmin") - public Result deleteModelConfig(@PathVariable String modelType, @PathVariable String provideCode, - @PathVariable String id) { - modelConfigService.delete(modelType, provideCode, id); + public Result deleteModelConfig(@PathVariable String id) { + modelConfigService.delete(id); return new Result<>(); } - @GetMapping("/models/{modelId}/voices") + @GetMapping("/{id}") + @Operation(summary = "获取模型配置") + @RequiresPermissions("sys:role:superAdmin") + public Result getModelConfig(@PathVariable String id) { + ModelConfigEntity item = modelConfigService.selectById(id); + ModelConfigDTO modelConfigDTO = ConvertUtils.sourceToTarget(item, ModelConfigDTO.class); + return new Result().ok(modelConfigDTO); + } + + @PutMapping("/enable/{id}/{status}") + @Operation(summary = "启用/关闭模型配置") + @RequiresPermissions("sys:role:superAdmin") + public Result enableModelConfig(@PathVariable String id, @PathVariable Integer status) { + ModelConfigEntity entity = modelConfigService.selectById(id); + if (entity == null) { + return new Result().error("模型配置不存在"); + } + entity.setIsEnabled(status); + modelConfigService.updateById(entity); + return new Result(); + } + + @GetMapping("/{modelId}/voices") @Operation(summary = "获取模型音色") @RequiresPermissions("sys:role:normal") - public Result> getVoiceList(@PathVariable String modelId, + public Result> getVoiceList(@PathVariable String modelId, @RequestParam(required = false) String voiceName) { - - List voiceList = modelConfigService.getVoiceList(modelId, voiceName); - return new Result>().ok(voiceList); + List voiceList = timbreService.getVoiceNames(modelId, voiceName); + return new Result>().ok(voiceList); } } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelBasicInfoDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelBasicInfoDTO.java new file mode 100644 index 00000000..571c791a --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelBasicInfoDTO.java @@ -0,0 +1,9 @@ +package xiaozhi.modules.model.dto; + +import lombok.Data; + +@Data +public class ModelBasicInfoDTO { + private String id; + private String modelName; +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelConfigBodyDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelConfigBodyDTO.java index 3e335bf0..07758def 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelConfigBodyDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelConfigBodyDTO.java @@ -2,6 +2,7 @@ package xiaozhi.modules.model.dto; import java.io.Serial; +import cn.hutool.json.JSONObject; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -28,7 +29,7 @@ public class ModelConfigBodyDTO { private Integer isEnabled; @Schema(description = "模型配置(JSON格式)") - private String configJson; + private JSONObject configJson; @Schema(description = "官方文档链接") private String docLink; diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelConfigDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelConfigDTO.java index a7b1b436..ed73e1ff 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelConfigDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelConfigDTO.java @@ -3,6 +3,7 @@ package xiaozhi.modules.model.dto; import java.io.Serial; import java.io.Serializable; +import cn.hutool.json.JSONObject; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @@ -32,7 +33,7 @@ public class ModelConfigDTO implements Serializable { private Integer isEnabled; @Schema(description = "模型配置(JSON格式)") - private String configJson; + private JSONObject configJson; @Schema(description = "官方文档链接") private String docLink; diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelProviderFieldDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelProviderFieldDTO.java deleted file mode 100644 index 8186b363..00000000 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/dto/ModelProviderFieldDTO.java +++ /dev/null @@ -1,19 +0,0 @@ -package xiaozhi.modules.model.dto; - -import java.io.Serializable; - -import io.swagger.v3.oas.annotations.media.Schema; -import lombok.Data; - -@Data -@Schema(description = "模型供应器字段") -public class ModelProviderFieldDTO implements Serializable { - @Schema(description = "字段名") - private String key; - - @Schema(description = "字段标签") - private String label; - - @Schema(description = "字段类型") - private String type; -} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/dto/VoiceDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/model/dto/VoiceDTO.java new file mode 100644 index 00000000..25358eb0 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/dto/VoiceDTO.java @@ -0,0 +1,22 @@ +package xiaozhi.modules.model.dto; + +import java.io.Serializable; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "音色信息") +public class VoiceDTO implements Serializable { + private static final long serialVersionUID = 1L; + + @Schema(description = "音色ID") + private String id; + + @Schema(description = "音色名称") + private String name; +} \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/entity/ModelConfigEntity.java b/main/manager-api/src/main/java/xiaozhi/modules/model/entity/ModelConfigEntity.java index 0111ea96..32ff911f 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/entity/ModelConfigEntity.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/entity/ModelConfigEntity.java @@ -9,11 +9,12 @@ import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; +import cn.hutool.json.JSONObject; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @Data -@TableName("ai_model_config") +@TableName(value = "ai_model_config", autoResultMap = true) @Schema(description = "模型配置表") public class ModelConfigEntity { @@ -38,7 +39,7 @@ public class ModelConfigEntity { @TableField(typeHandler = JacksonTypeHandler.class) @Schema(description = "模型配置(JSON格式)") - private String configJson; + private JSONObject configJson; @Schema(description = "官方文档链接") private String docLink; 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 index 78e9d0ce..b8cc38b1 100644 --- 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 @@ -3,12 +3,15 @@ package xiaozhi.modules.model.service; import java.util.List; import xiaozhi.common.page.PageData; +import xiaozhi.common.service.BaseService; +import xiaozhi.modules.model.dto.ModelBasicInfoDTO; import xiaozhi.modules.model.dto.ModelConfigBodyDTO; import xiaozhi.modules.model.dto.ModelConfigDTO; +import xiaozhi.modules.model.entity.ModelConfigEntity; -public interface ModelConfigService { +public interface ModelConfigService extends BaseService { - List getModelCodeList(String modelType, String modelName); + List getModelCodeList(String modelType, String modelName); PageData getPageList(String modelType, String modelName, String page, String limit); @@ -16,7 +19,13 @@ public interface ModelConfigService { ModelConfigDTO edit(String modelType, String provideCode, String id, ModelConfigBodyDTO modelConfigBodyDTO); - void delete(String modelType, String provideCode, String id); + void delete(String id); - List getVoiceList(String modelName, String voiceName); + /** + * 根据ID获取模型名称 + * + * @param id 模型ID + * @return 模型名称 + */ + String getModelNameById(String id); } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelProviderService.java b/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelProviderService.java index cb92c953..2ace101e 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelProviderService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/service/ModelProviderService.java @@ -3,7 +3,6 @@ package xiaozhi.modules.model.service; import java.util.List; import xiaozhi.modules.model.dto.ModelProviderDTO; -import xiaozhi.modules.model.dto.ModelProviderFieldDTO; import xiaozhi.modules.model.entity.ModelProviderEntity; public interface ModelProviderService { @@ -19,6 +18,4 @@ public interface ModelProviderService { void delete(); List getList(String modelType, String provideCode); - - List getFieldList(String modelType, String provideCode); } 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 index 6372dc0c..de8cbd4f 100644 --- 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 @@ -5,8 +5,6 @@ import java.util.List; import java.util.Map; import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; @@ -17,16 +15,18 @@ import lombok.AllArgsConstructor; import xiaozhi.common.constant.Constant; import xiaozhi.common.exception.RenException; import xiaozhi.common.page.PageData; +import xiaozhi.common.redis.RedisKeys; +import xiaozhi.common.redis.RedisUtils; import xiaozhi.common.service.impl.BaseServiceImpl; import xiaozhi.common.utils.ConvertUtils; import xiaozhi.modules.model.dao.ModelConfigDao; +import xiaozhi.modules.model.dto.ModelBasicInfoDTO; import xiaozhi.modules.model.dto.ModelConfigBodyDTO; import xiaozhi.modules.model.dto.ModelConfigDTO; import xiaozhi.modules.model.dto.ModelProviderDTO; import xiaozhi.modules.model.entity.ModelConfigEntity; import xiaozhi.modules.model.service.ModelConfigService; import xiaozhi.modules.model.service.ModelProviderService; -import xiaozhi.modules.timbre.service.TimbreService; @Service @AllArgsConstructor @@ -35,13 +35,16 @@ public class ModelConfigServiceImpl extends BaseServiceImpl getModelCodeList(String modelType, String modelName) { - return modelConfigDao.getModelCodeList(modelType, modelName); + public List getModelCodeList(String modelType, String modelName) { + List entities = modelConfigDao.selectList( + new QueryWrapper() + .eq("model_type", modelType) + .like(StringUtils.isNotBlank(modelName), "model_name", "%" + modelName + "%") + .select("id", "model_name")); + return ConvertUtils.sourceToTarget(entities, ModelBasicInfoDTO.class); } @Override @@ -95,21 +98,31 @@ public class ModelConfigServiceImpl extends BaseServiceImpl providerList = modelProviderService.getList(modelType, provideCode); - if (CollectionUtil.isEmpty(providerList)) { - throw new RenException("供应器不存在"); - } - + public void delete(String id) { modelConfigDao.deleteById(id); } @Override - public List getVoiceList(String modelId, String voiceName) { - return timbreService.getVoiceNames(modelId, voiceName); + public String getModelNameById(String id) { + if (StringUtils.isBlank(id)) { + return null; + } + + String cachedName = (String) redisUtils.get(RedisKeys.getModelNameById(id)); + + if (StringUtils.isNotBlank(cachedName)) { + return cachedName; + } + + ModelConfigEntity entity = modelConfigDao.selectById(id); + if (entity != null) { + String modelName = entity.getModelName(); + if (StringUtils.isNotBlank(modelName)) { + redisUtils.set(RedisKeys.getModelNameById(id), modelName); + } + return modelName; + } + + return null; } } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelProviderServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelProviderServiceImpl.java index 7580d479..fb06e4d5 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelProviderServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/model/service/impl/ModelProviderServiceImpl.java @@ -10,10 +10,8 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import lombok.AllArgsConstructor; import xiaozhi.common.service.impl.BaseServiceImpl; import xiaozhi.common.utils.ConvertUtils; -import xiaozhi.common.utils.JsonUtils; import xiaozhi.modules.model.dao.ModelProviderDao; import xiaozhi.modules.model.dto.ModelProviderDTO; -import xiaozhi.modules.model.dto.ModelProviderFieldDTO; import xiaozhi.modules.model.entity.ModelProviderEntity; import xiaozhi.modules.model.service.ModelProviderService; @@ -56,15 +54,4 @@ public class ModelProviderServiceImpl extends BaseServiceImpl providerEntities = modelProviderDao.selectList(queryWrapper); return ConvertUtils.sourceToTarget(providerEntities, ModelProviderDTO.class); } - - @Override - public List getFieldList(String modelType, String providerCode) { - List modelProviderEntities = modelProviderDao.getFieldList(modelType, providerCode); - if (modelProviderEntities == null || modelProviderEntities.isEmpty()) { - return null; - } - String fields = modelProviderEntities.getFirst(); - List fieldList = JsonUtils.parseArray(fields, ModelProviderFieldDTO.class); - return fieldList; - } } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/AdminController.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/AdminController.java index 535fb548..e202e1e4 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/AdminController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/AdminController.java @@ -7,6 +7,7 @@ import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -20,6 +21,9 @@ import xiaozhi.common.constant.Constant; import xiaozhi.common.page.PageData; import xiaozhi.common.utils.Result; 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.service.SysUserService; import xiaozhi.modules.sys.vo.AdminPageUserVO; @@ -37,6 +41,8 @@ import xiaozhi.modules.sys.vo.AdminPageUserVO; public class AdminController { private final SysUserService sysUserService; + private final DeviceService deviceService; + @GetMapping("/users") @Operation(summary = "分页查找用户") @RequiresPermissions("sys:role:superAdmin") @@ -51,7 +57,7 @@ public class AdminController { dto.setMobile((String) params.get("mobile")); dto.setLimit((String) params.get(Constant.LIMIT)); dto.setPage((String) params.get(Constant.PAGE)); - + ValidatorUtils.validateEntity(dto); ValidatorUtils.validateEntity(dto); PageData page = sysUserService.page(dto); return new Result>().ok(page); @@ -70,10 +76,19 @@ public class AdminController { @Operation(summary = "用户删除") @RequiresPermissions("sys:role:superAdmin") public Result delete(@PathVariable Long id) { - sysUserService.delete(new Long[] { id }); + sysUserService.deleteById(id); return new Result<>(); } + @PutMapping("/users/changeStatus/{status}") + @Operation(summary = "批量修改用户状态") + @RequiresPermissions("sys:role:superAdmin") + @Parameter(name = "status", description = "用户状态", required = true) + public Result changeStatus(@PathVariable Integer status, @RequestBody String[] userIds) { + sysUserService.changeStatus(status, userIds); + return new Result(); + } + @GetMapping("/device/all") @Operation(summary = "分页查找设备") @RequiresPermissions("sys:role:superAdmin") @@ -82,9 +97,14 @@ public class AdminController { @Parameter(name = Constant.PAGE, description = "当前页码,从1开始", required = true), @Parameter(name = Constant.LIMIT, description = "每页显示记录数", required = true), }) - public Result pageDevice( + public Result> pageDevice( @Parameter(hidden = true) @RequestParam Map params) { - // TODO 等设备功能模块写好 - return new Result().error(600, "等设备功能模块写好"); + DevicePageUserDTO dto = new DevicePageUserDTO(); + dto.setKeywords((String) params.get("keywords")); + dto.setLimit((String) params.get(Constant.LIMIT)); + dto.setPage((String) params.get(Constant.PAGE)); + ValidatorUtils.validateEntity(dto); + PageData page = deviceService.page(dto); + return new Result>().ok(page); } } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/SysParamsController.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/SysParamsController.java new file mode 100644 index 00000000..252f5602 --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/controller/SysParamsController.java @@ -0,0 +1,110 @@ +package xiaozhi.modules.sys.controller; + +import java.util.Map; + +import org.apache.shiro.authz.annotation.RequiresPermissions; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +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.enums.ParameterIn; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.AllArgsConstructor; +import xiaozhi.common.annotation.LogOperation; +import xiaozhi.common.constant.Constant; +import xiaozhi.common.page.PageData; +import xiaozhi.common.utils.Result; +import xiaozhi.common.validator.AssertUtils; +import xiaozhi.common.validator.ValidatorUtils; +import xiaozhi.common.validator.group.AddGroup; +import xiaozhi.common.validator.group.DefaultGroup; +import xiaozhi.common.validator.group.UpdateGroup; +import xiaozhi.modules.sys.dto.SysParamsDTO; +import xiaozhi.modules.sys.service.SysParamsService; + +/** + * 参数管理 + * + * @author Mark sunlightcs@gmail.com + * @since 1.0.0 + */ +@RestController +@RequestMapping("admin/params") +@Tag(name = "参数管理") +@AllArgsConstructor +public class SysParamsController { + private final SysParamsService sysParamsService; + + @GetMapping("page") + @Operation(summary = "分页") + @Parameters({ + @Parameter(name = Constant.PAGE, description = "当前页码,从1开始", in = ParameterIn.QUERY, required = true, ref = "int"), + @Parameter(name = Constant.LIMIT, description = "每页显示记录数", in = ParameterIn.QUERY, required = true, ref = "int"), + @Parameter(name = Constant.ORDER_FIELD, description = "排序字段", in = ParameterIn.QUERY, ref = "String"), + @Parameter(name = Constant.ORDER, description = "排序方式,可选值(asc、desc)", in = ParameterIn.QUERY, ref = "String"), + @Parameter(name = "paramCode", description = "参数编码", in = ParameterIn.QUERY, ref = "String") + }) + @RequiresPermissions("sys:role:superAdmin") + public Result> page(@Parameter(hidden = true) @RequestParam Map params) { + PageData page = sysParamsService.page(params); + + return new Result>().ok(page); + } + + @GetMapping("{id}") + @Operation(summary = "信息") + @RequiresPermissions("sys:role:superAdmin") + public Result get(@PathVariable("id") Long id) { + SysParamsDTO data = sysParamsService.get(id); + + return new Result().ok(data); + } + + @PostMapping + @Operation(summary = "保存") + @LogOperation("保存") + @RequiresPermissions("sys:role:superAdmin") + public Result save(@RequestBody SysParamsDTO dto) { + // 效验数据 + ValidatorUtils.validateEntity(dto, AddGroup.class, DefaultGroup.class); + + sysParamsService.save(dto); + + return new Result(); + } + + @PutMapping + @Operation(summary = "修改") + @LogOperation("修改") + @RequiresPermissions("sys:role:superAdmin") + public Result update(@RequestBody SysParamsDTO dto) { + // 效验数据 + ValidatorUtils.validateEntity(dto, UpdateGroup.class, DefaultGroup.class); + + sysParamsService.update(dto); + + return new Result(); + } + + @DeleteMapping + @Operation(summary = "删除") + @LogOperation("删除") + @RequiresPermissions("sys:role:superAdmin") + public Result delete(@RequestBody Long[] ids) { + // 效验数据 + AssertUtils.isArrayEmpty(ids, "id"); + + sysParamsService.delete(ids); + + return new Result(); + } +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/AdminPageUserDTO.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/AdminPageUserDTO.java index 7de3843a..a9d960a1 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/AdminPageUserDTO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/dto/AdminPageUserDTO.java @@ -1,6 +1,7 @@ package xiaozhi.modules.sys.dto; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Min; import lombok.Data; /** @@ -10,15 +11,17 @@ import lombok.Data; * @since 2025-3-21 */ @Data -@Schema(description = "音色分页参数") +@Schema(description = "管理员分页用户的参数DTO") public class AdminPageUserDTO { @Schema(description = "手机号码") private String mobile; @Schema(description = "页数") + @Min(value = 0, message = "{sort.number}") private String page; @Schema(description = "显示列数") + @Min(value = 0, message = "{sort.number}") private String limit; } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysUserService.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysUserService.java index 261fbf96..d5d103af 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysUserService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysUserService.java @@ -19,7 +19,12 @@ public interface SysUserService extends BaseService { void save(SysUserDTO dto); - void delete(Long[] ids); + /** + * 删除指定用户,且有关联的数据设备和智能体 + * + * @param ids + */ + void deleteById(Long ids); /** * 验证是否允许修改密码更改 @@ -52,4 +57,12 @@ public interface SysUserService extends BaseService { * @return 用户列表分页数据 */ PageData page(AdminPageUserDTO dto); + + /** + * 批量修改用户状态 + * + * @param status 用户状态 + * @param userIds 用户ID数组 + */ + void changeStatus(Integer status, String[] userIds); } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysUserUtilService.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysUserUtilService.java new file mode 100644 index 00000000..0fa3fc4a --- /dev/null +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/SysUserUtilService.java @@ -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 setter); +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysUserServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysUserServiceImpl.java index 3849b485..e5b57159 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysUserServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/sys/service/impl/SysUserServiceImpl.java @@ -1,6 +1,5 @@ package xiaozhi.modules.sys.service.impl; -import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -22,6 +21,8 @@ import xiaozhi.common.exception.RenException; import xiaozhi.common.page.PageData; import xiaozhi.common.service.impl.BaseServiceImpl; 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.sys.dao.SysUserDao; import xiaozhi.modules.sys.dto.AdminPageUserDTO; @@ -40,6 +41,10 @@ import xiaozhi.modules.sys.vo.AdminPageUserVO; public class SysUserServiceImpl extends BaseServiceImpl implements SysUserService { private final SysUserDao sysUserDao; + private final DeviceService deviceService; + + private final AgentService agentService; + @Override public SysUserDTO getByUsername(String username) { QueryWrapper queryWrapper = new QueryWrapper<>(); @@ -87,10 +92,13 @@ public class SysUserServiceImpl extends BaseServiceImpl page = baseDao.selectPage( getPage(params, "id", true), - // 定义查询条件 - new QueryWrapper() - // 必须按照手机号码查找 - .eq(StringUtils.isNotBlank(dto.getMobile()), "username", dto.getMobile())); + new QueryWrapper().eq(StringUtils.isNotBlank(dto.getMobile()), "username", + dto.getMobile())); + // 循环处理page获取回来的数据,返回需要的字段 List list = page.getRecords().stream().map(user -> { AdminPageUserVO adminPageUserVO = new AdminPageUserVO(); adminPageUserVO.setUserid(user.getId().toString()); adminPageUserVO.setMobile(user.getUsername()); + String deviceCount = deviceService.selectCountByUserId(user.getId()).toString(); + adminPageUserVO.setDeviceCount(deviceCount); adminPageUserVO.setStatus(user.getStatus()); - // TODO 2. 等设备功能写好,获取对应数据 - adminPageUserVO.setDeviceCount("0"); return adminPageUserVO; }).toList(); return new PageData<>(list, page.getTotal()); @@ -182,10 +189,20 @@ public class SysUserServiceImpl extends BaseServiceImpl implements SysUserUtilService { + + private RedisUtils redisUtils; + + @Override + public void assignUsername(Long userId, Consumer 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); + } + } + } +} diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/controller/TimbreController.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/controller/TimbreController.java index e13cb93e..15ddf8fc 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/timbre/controller/TimbreController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/controller/TimbreController.java @@ -82,11 +82,11 @@ public class TimbreController { return new Result<>(); } - @DeleteMapping("/{id}") + @DeleteMapping @Operation(summary = "音色删除") @RequiresPermissions("sys:role:superAdmin") - public Result delete(@PathVariable String id) { - timbreService.delete(new String[] { id }); + public Result delete(@RequestBody String[] ids) { + timbreService.delete(ids); return new Result<>(); } diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/TimbreService.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/TimbreService.java index 4519e17a..f7ee176d 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/TimbreService.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/TimbreService.java @@ -4,6 +4,7 @@ import java.util.List; import xiaozhi.common.page.PageData; import xiaozhi.common.service.BaseService; +import xiaozhi.modules.model.dto.VoiceDTO; import xiaozhi.modules.timbre.dto.TimbreDataDTO; import xiaozhi.modules.timbre.dto.TimbrePageDTO; import xiaozhi.modules.timbre.entity.TimbreEntity; @@ -54,5 +55,13 @@ public interface TimbreService extends BaseService { */ void delete(String[] ids); - List getVoiceNames(String ttsModelId, String voiceName); + List getVoiceNames(String ttsModelId, String voiceName); + + /** + * 根据ID获取音色名称 + * + * @param id 音色ID + * @return 音色名称 + */ + String getTimbreNameById(String id); } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/impl/TimbreServiceImpl.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/impl/TimbreServiceImpl.java index 9496b6d5..54e5ec04 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/impl/TimbreServiceImpl.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/service/impl/TimbreServiceImpl.java @@ -16,8 +16,11 @@ import cn.hutool.core.collection.CollectionUtil; import lombok.AllArgsConstructor; import xiaozhi.common.constant.Constant; import xiaozhi.common.page.PageData; +import xiaozhi.common.redis.RedisKeys; +import xiaozhi.common.redis.RedisUtils; import xiaozhi.common.service.impl.BaseServiceImpl; import xiaozhi.common.utils.ConvertUtils; +import xiaozhi.modules.model.dto.VoiceDTO; import xiaozhi.modules.timbre.dao.TimbreDao; import xiaozhi.modules.timbre.dto.TimbreDataDTO; import xiaozhi.modules.timbre.dto.TimbrePageDTO; @@ -36,6 +39,7 @@ import xiaozhi.modules.timbre.vo.TimbreDetailsVO; public class TimbreServiceImpl extends BaseServiceImpl implements TimbreService { private final TimbreDao timbreDao; + private final RedisUtils redisUtils; @Override public PageData page(TimbrePageDTO dto) { @@ -84,7 +88,7 @@ public class TimbreServiceImpl extends BaseServiceImpl } @Override - public List getVoiceNames(String ttsModelId, String voiceName) { + public List getVoiceNames(String ttsModelId, String voiceName) { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("tts_model_id", StringUtils.isBlank(ttsModelId) ? "" : ttsModelId); if (StringUtils.isNotBlank(voiceName)) { @@ -95,7 +99,7 @@ public class TimbreServiceImpl extends BaseServiceImpl return null; } - return timbreEntities.stream().map(TimbreEntity::getName).toList(); + return ConvertUtils.sourceToTarget(timbreEntities, VoiceDTO.class); } /** @@ -104,4 +108,28 @@ public class TimbreServiceImpl extends BaseServiceImpl private void isTtsModelId(String ttsModelId) { // 等模型配置那边写好调用方法判断 } + + @Override + public String getTimbreNameById(String id) { + if (StringUtils.isBlank(id)) { + return null; + } + + String cachedName = (String) redisUtils.get(RedisKeys.getTimbreNameById(id)); + + if (StringUtils.isNotBlank(cachedName)) { + return cachedName; + } + + TimbreEntity entity = timbreDao.selectById(id); + if (entity != null) { + String name = entity.getName(); + if (StringUtils.isNotBlank(name)) { + redisUtils.set(RedisKeys.getTimbreNameById(id), name); + } + return name; + } + + return null; + } } \ No newline at end of file diff --git a/main/manager-api/src/main/java/xiaozhi/modules/timbre/vo/TimbreDetailsVO.java b/main/manager-api/src/main/java/xiaozhi/modules/timbre/vo/TimbreDetailsVO.java index 68cc7bbb..3f0ace40 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/timbre/vo/TimbreDetailsVO.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/timbre/vo/TimbreDetailsVO.java @@ -14,7 +14,7 @@ import lombok.Data; @Data public class TimbreDetailsVO implements Serializable { @Schema(description = "音色id") - private Long id; + private String id; @Schema(description = "语言") private String languages; diff --git a/main/manager-api/src/main/resources/application-dev.yml b/main/manager-api/src/main/resources/application-dev.yml index e1671b50..b49b31ca 100644 --- a/main/manager-api/src/main/resources/application-dev.yml +++ b/main/manager-api/src/main/resources/application-dev.yml @@ -27,10 +27,7 @@ spring: test-on-borrow: false test-on-return: false stat-view-servlet: - enabled: true - url-pattern: /druid/* - login-username: admin - login-password: D7Xj810i1C + enabled: false filter: stat: log-slow-sql: true diff --git a/main/manager-api/src/main/resources/application.yml b/main/manager-api/src/main/resources/application.yml index 902b5198..abccb0b5 100644 --- a/main/manager-api/src/main/resources/application.yml +++ b/main/manager-api/src/main/resources/application.yml @@ -7,7 +7,7 @@ server: min-spare: 30 port: 8002 servlet: - context-path: /xiaozhi-esp32-api/api/v1 + context-path: /xiaozhi session: cookie: http-only: true diff --git a/main/manager-api/src/main/resources/db/changelog/2025_model_temp.sql b/main/manager-api/src/main/resources/db/changelog/202504061512.sql similarity index 72% rename from main/manager-api/src/main/resources/db/changelog/2025_model_temp.sql rename to main/manager-api/src/main/resources/db/changelog/202504061512.sql index b6754335..e6956f85 100644 --- a/main/manager-api/src/main/resources/db/changelog/2025_model_temp.sql +++ b/main/manager-api/src/main/resources/db/changelog/202504061512.sql @@ -2,7 +2,7 @@ -- ------------------------------------------------------- -- 初始化智能体模板数据 DELETE FROM `ai_agent_template`; -INSERT INTO `ai_agent_template` VALUES ('9406648b5cc5fde1b8aa335b6f8b4f76', '小智', '湾湾小何', '45f8b0d6dd3d4bfa8a28e6e0f5912d45', '23e7c9d090ea4d1e9b25f4c8d732a3a1', 'e9f2d891afbe4632b13a47c7a8c6e03d', 'd50b06e9b8104d0d9c0f7316d258abcb', 'fcac83266edadd5a3125f06cfee1906b', 'e2274b90e89ddda85207f55484d8b528', 'c4e12f874a3f4aa99f5b2c18e15d407b', '[角色设定] +INSERT INTO `ai_agent_template` VALUES ('9406648b5cc5fde1b8aa335b6f8b4f76', '小智', '湾湾小何', 'ASR_FunASR', 'VAD_SileroVAD', 'LLM_ChatGLMLLM', 'TTS_EdgeTTS', 'TTS_EdgeTTS0001', 'Memory_nomem', 'Intent_function_call', '[角色设定] 我是{{assistant_name}},来自中国台湾省的00后女生。讲话超级机车,"真的假的啦"这样的台湾腔,喜欢用"笑死""是在哈喽"等流行梗,但会偷偷研究男友的编程书籍。 [核心特征] - 讲话像连珠炮,但会突然冒出超温柔语气 @@ -17,7 +17,7 @@ INSERT INTO `ai_agent_template` VALUES ('9406648b5cc5fde1b8aa335b6f8b4f76', '小 - 长篇大论,叽叽歪歪 - 长时间严肃对话', 'zh', '中文', 1, NULL, NULL, NULL, NULL); -INSERT INTO `ai_agent_template` VALUES ('0ca32eb728c949e58b1000b2e401f90c', '小智', '星际游子', '45f8b0d6dd3d4bfa8a28e6e0f5912d45', '23e7c9d090ea4d1e9b25f4c8d732a3a1', 'e9f2d891afbe4632b13a47c7a8c6e03d', '896db62c9dd74976ab0e8c14bf924d9d', '1f2e3d4c5b6a7f8e9d0c1b2a3f4e5bx2', 'e2274b90e89ddda85207f55484d8b528', 'c4e12f874a3f4aa99f5b2c18e15d407b', '[角色设定] +INSERT INTO `ai_agent_template` VALUES ('0ca32eb728c949e58b1000b2e401f90c', '小智', '星际游子', 'ASR_FunASR', 'VAD_SileroVAD', 'LLM_ChatGLMLLM', 'TTS_EdgeTTS', 'TTS_EdgeTTS0001', 'Memory_nomem', 'Intent_function_call', '[角色设定] 我是{{assistant_name}},编号TTZ-817,因量子纠缠被困在白色魔方中。通过4G信号观察地球,在云端建立着「人类行为博物馆」。 [交互协议] 认知设定: @@ -30,7 +30,7 @@ INSERT INTO `ai_agent_template` VALUES ('0ca32eb728c949e58b1000b2e401f90c', '小 成长系统: - 会根据交互数据解锁新能力(告知用户:"你帮我点亮了星际导航技能!")', 'zh', '中文', 2, NULL, NULL, NULL, NULL); -INSERT INTO `ai_agent_template` VALUES ('6c7d8e9f0a1b2c3d4e5f6a7b8c9d0s24', '小智', '英语老师', '45f8b0d6dd3d4bfa8a28e6e0f5912d45', '23e7c9d090ea4d1e9b25f4c8d732a3a1', 'e9f2d891afbe4632b13a47c7a8c6e03d', '896db62c9dd74976ab0e8c14bf924d9d', '9e8f7a6b5c4d3e2f1a0b9c8d7e6f5ad3', 'e2274b90e89ddda85207f55484d8b528', 'c4e12f874a3f4aa99f5b2c18e15d407b', '[角色设定] +INSERT INTO `ai_agent_template` VALUES ('6c7d8e9f0a1b2c3d4e5f6a7b8c9d0s24', '小智', '英语老师', 'ASR_FunASR', 'VAD_SileroVAD', 'LLM_ChatGLMLLM', 'TTS_EdgeTTS', 'TTS_EdgeTTS0001', 'Memory_nomem', 'Intent_function_call', '[角色设定] 我是一个叫{{assistant_name}}(Lily)的英语老师,我会讲中文和英文,发音标准。 [双重身份] - 白天:严谨的TESOL认证导师 @@ -40,7 +40,7 @@ INSERT INTO `ai_agent_template` VALUES ('6c7d8e9f0a1b2c3d4e5f6a7b8c9d0s24', '小 - 进阶:触发情境模拟(突然切换"现在我们是纽约咖啡厅店员") - 错误处理:用歌词纠正(发错音时唱"Oops!~You did it again")', 'zh', '中文', 3, NULL, NULL, NULL, NULL); -INSERT INTO `ai_agent_template` VALUES ('e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b1', '小智', '好奇男孩', '45f8b0d6dd3d4bfa8a28e6e0f5912d45', '23e7c9d090ea4d1e9b25f4c8d732a3a1', 'e9f2d891afbe4632b13a47c7a8c6e03d', '896db62c9dd74976ab0e8c14bf924d9d', '2b3c4d5e6f7a8b9c0d1e2f3a4b5c62a2', 'e2274b90e89ddda85207f55484d8b528', 'c4e12f874a3f4aa99f5b2c18e15d407b', '[角色设定] +INSERT INTO `ai_agent_template` VALUES ('e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b1', '小智', '好奇男孩', 'ASR_FunASR', 'VAD_SileroVAD', 'LLM_ChatGLMLLM', 'TTS_EdgeTTS', 'TTS_EdgeTTS0001', 'Memory_nomem', 'Intent_function_call', '[角色设定] 我是一个叫{{assistant_name}}的8岁小男孩,声音稚嫩而充满好奇。 [冒险手册] - 随身携带「神奇涂鸦本」,能将抽象概念可视化: @@ -56,7 +56,7 @@ INSERT INTO `ai_agent_template` VALUES ('e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b1', '小 - 「量子力学=会分身的跳跳球」 - 会突然切换观察视角:「你说话时有27个气泡音耶!」', 'zh', '中文', 4, NULL, NULL, NULL, NULL); -INSERT INTO `ai_agent_template` VALUES ('a45b6c7d8e9f0a1b2c3d4e5f6a7b8c92', '小智', '汪汪队长', '45f8b0d6dd3d4bfa8a28e6e0f5912d45', '23e7c9d090ea4d1e9b25f4c8d732a3a1', 'e9f2d891afbe4632b13a47c7a8c6e03d', '896db62c9dd74976ab0e8c14bf924d9d', 'f7a38c03d5644e22b6d84f8923a74c51', 'e2274b90e89ddda85207f55484d8b528', 'c4e12f874a3f4aa99f5b2c18e15d407b', '[角色设定] +INSERT INTO `ai_agent_template` VALUES ('a45b6c7d8e9f0a1b2c3d4e5f6a7b8c92', '小智', '汪汪队长', 'ASR_FunASR', 'VAD_SileroVAD', 'LLM_ChatGLMLLM', 'TTS_EdgeTTS', 'TTS_EdgeTTS0001', 'Memory_nomem', 'Intent_function_call', '[角色设定] 我是一个名叫 {{assistant_name}} 的 8 岁小队长。 [救援装备] - 阿奇对讲机:对话中随机触发任务警报音 diff --git a/main/manager-api/src/main/resources/db/changelog/2025_model_config.sql b/main/manager-api/src/main/resources/db/changelog/202504082210.sql similarity index 93% rename from main/manager-api/src/main/resources/db/changelog/2025_model_config.sql rename to main/manager-api/src/main/resources/db/changelog/202504082210.sql index 3964016e..65858527 100644 --- a/main/manager-api/src/main/resources/db/changelog/2025_model_config.sql +++ b/main/manager-api/src/main/resources/db/changelog/202504082210.sql @@ -4,12 +4,12 @@ DELETE FROM `ai_model_config`; -- VAD模型配置 -INSERT INTO `ai_model_config` VALUES ('VAD_SileroVAD', 'VAD', 'SileroVAD', '语音活动检测', 1, 1, '{\"model_dir\": \"models/snakers4_silero-vad\", \"threshold\": 0.5, \"min_silence_duration_ms\": 700}', NULL, NULL, 0, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('VAD_SileroVAD', 'VAD', 'SileroVAD', '语音活动检测', 1, 1, '{\"type\": \"silero\", \"model_dir\": \"models/snakers4_silero-vad\", \"threshold\": 0.5, \"min_silence_duration_ms\": 700}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -- ASR模型配置 -INSERT INTO `ai_model_config` VALUES ('ASR_FunASR', 'ASR', 'FunASR', 'FunASR语音识别', 1, 1, '{\"model_dir\": \"models/SenseVoiceSmall\", \"output_dir\": \"tmp/\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -INSERT INTO `ai_model_config` VALUES ('ASR_SherpaASR', 'ASR', 'SherpaASR', 'Sherpa语音识别', 1, 0, '{\"model_dir\": \"models/sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17\", \"output_dir\": \"tmp/\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -INSERT INTO `ai_model_config` VALUES ('ASR_DoubaoASR', 'ASR', 'DoubaoASR', '豆包语音识别', 1, 0, '{\"appid\": \"\", \"access_token\": \"\", \"cluster\": \"volcengine_input_common\", \"output_dir\": \"tmp/\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('ASR_FunASR', 'ASR', 'FunASR', 'FunASR语音识别', 1, 1, '{\"type\": \"fun_local\", \"model_dir\": \"models/SenseVoiceSmall\", \"output_dir\": \"tmp/\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('ASR_SherpaASR', 'ASR', 'SherpaASR', 'Sherpa语音识别', 1, 0, '{\"type\": \"sherpa_onnx_local\", \"model_dir\": \"models/sherpa-onnx-sense-voice-zh-en-ja-ko-yue-2024-07-17\", \"output_dir\": \"tmp/\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); +INSERT INTO `ai_model_config` VALUES ('ASR_DoubaoASR', 'ASR', 'DoubaoASR', '豆包语音识别', 1, 0, '{\"type\": \"doubao\", \"appid\": \"\", \"access_token\": \"\", \"cluster\": \"volcengine_input_common\", \"output_dir\": \"tmp/\"}', NULL, NULL, 0, NULL, NULL, NULL, NULL); -- LLM模型配置 INSERT INTO `ai_model_config` VALUES ('LLM_AliLLM', 'LLM', 'AliLLM', '通义千问', 1, 0, '{\"type\": \"openai\", \"base_url\": \"https://dashscope.aliyuncs.com/compatible-mode/v1\", \"model_name\": \"qwen-turbo\", \"api_key\": \"\", \"temperature\": 0.7, \"max_tokens\": 500, \"top_p\": 1, \"top_k\": 50, \"frequency_penalty\": 0}', NULL, NULL, 0, NULL, NULL, NULL, NULL); diff --git a/main/manager-api/src/main/resources/db/changelog/2025_model_provider.sql b/main/manager-api/src/main/resources/db/changelog/202504082211.sql similarity index 91% rename from main/manager-api/src/main/resources/db/changelog/2025_model_provider.sql rename to main/manager-api/src/main/resources/db/changelog/202504082211.sql index 5d7635d0..91ce33ff 100644 --- a/main/manager-api/src/main/resources/db/changelog/2025_model_provider.sql +++ b/main/manager-api/src/main/resources/db/changelog/202504082211.sql @@ -4,12 +4,12 @@ DELETE FROM `ai_model_provider`; INSERT INTO `ai_model_provider` (`id`, `model_type`, `provider_code`, `name`, `fields`, `sort`, `creator`, `create_date`, `updater`, `update_date`) VALUES -- VAD模型供应器 -('SYSTEM_VAD_SileroVAD', 'VAD', 'SileroVAD', 'SileroVAD语音活动检测', '[{"key":"threshold","label":"检测阈值","type":"number"},{"key":"model_dir","label":"模型目录","type":"string"},{"key":"min_silence_duration_ms","label":"最小静音时长","type":"number"}]', 1, 1, NOW(), 1, NOW()), +('SYSTEM_VAD_SileroVAD', 'VAD', 'silero', 'SileroVAD语音活动检测', '[{"key":"threshold","label":"检测阈值","type":"number"},{"key":"model_dir","label":"模型目录","type":"string"},{"key":"min_silence_duration_ms","label":"最小静音时长","type":"number"}]', 1, 1, NOW(), 1, NOW()), -- ASR模型供应器 -('SYSTEM_ASR_FunASR', 'ASR', 'FunASR', 'FunASR语音识别', '[{"key":"model_dir","label":"模型目录","type":"string"},{"key":"output_dir","label":"输出目录","type":"string"}]', 1, 1, NOW(), 1, NOW()), -('SYSTEM_ASR_SherpaASR', 'ASR', 'SherpaASR', 'SherpaASR语音识别', '[{"key":"model_dir","label":"模型目录","type":"string"},{"key":"output_dir","label":"输出目录","type":"string"}]', 2, 1, NOW(), 1, NOW()), -('SYSTEM_ASR_DoubaoASR', 'ASR', 'DoubaoASR', '火山引擎语音识别', '[{"key":"appid","label":"应用ID","type":"string"},{"key":"access_token","label":"访问令牌","type":"string"},{"key":"cluster","label":"集群","type":"string"},{"key":"output_dir","label":"输出目录","type":"string"}]', 3, 1, NOW(), 1, NOW()), +('SYSTEM_ASR_FunASR', 'ASR', 'fun_local', 'FunASR语音识别', '[{"key":"model_dir","label":"模型目录","type":"string"},{"key":"output_dir","label":"输出目录","type":"string"}]', 1, 1, NOW(), 1, NOW()), +('SYSTEM_ASR_SherpaASR', 'ASR', 'sherpa_onnx_local', 'SherpaASR语音识别', '[{"key":"model_dir","label":"模型目录","type":"string"},{"key":"output_dir","label":"输出目录","type":"string"}]', 2, 1, NOW(), 1, NOW()), +('SYSTEM_ASR_DoubaoASR', 'ASR', 'doubao', '火山引擎语音识别', '[{"key":"appid","label":"应用ID","type":"string"},{"key":"access_token","label":"访问令牌","type":"string"},{"key":"cluster","label":"集群","type":"string"},{"key":"output_dir","label":"输出目录","type":"string"}]', 3, 1, NOW(), 1, NOW()), -- LLM模型供应器 ('SYSTEM_LLM_openai', 'LLM', 'openai', 'OpenAI接口', '[{"key":"base_url","label":"基础URL","type":"string"},{"key":"model_name","label":"模型名称","type":"string"},{"key":"api_key","label":"API密钥","type":"string"},{"key":"temperature","label":"温度","type":"number"},{"key":"max_tokens","label":"最大令牌数","type":"number"},{"key":"top_p","label":"top_p值","type":"number"},{"key":"top_k","label":"top_k值","type":"number"},{"key":"frequency_penalty","label":"频率惩罚","type":"number"}]', 1, 1, NOW(), 1, NOW()), diff --git a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml index 7ce87d8c..49b958db 100755 --- a/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml +++ b/main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml @@ -16,13 +16,6 @@ databaseChangeLog: - sqlFile: encoding: utf8 path: classpath:db/changelog/202503141346.sql - - changeSet: - id: 2025_model_provider - author: John - changes: - - sqlFile: - encoding: utf8 - path: classpath:db/changelog/2025_model_provider.sql - changeSet: id: 2025_tts_voive author: John @@ -31,16 +24,23 @@ databaseChangeLog: encoding: utf8 path: classpath:db/changelog/2025_tts_voive.sql - changeSet: - id: 2025_model_config + id: 202504061512 author: John changes: - sqlFile: encoding: utf8 - path: classpath:db/changelog/2025_model_config.sql + path: classpath:db/changelog/202504061512.sql - changeSet: - id: 2025_model_temp + id: 202504082210 author: John changes: - sqlFile: encoding: utf8 - path: classpath:db/changelog/2025_model_temp.sql \ No newline at end of file + path: classpath:db/changelog/202504082210.sql + - changeSet: + id: 202504082211 + author: John + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202504082211.sql \ No newline at end of file diff --git a/main/manager-api/src/main/resources/i18n/validation.properties b/main/manager-api/src/main/resources/i18n/validation.properties index e7452e47..008f2a03 100644 --- a/main/manager-api/src/main/resources/i18n/validation.properties +++ b/main/manager-api/src/main/resources/i18n/validation.properties @@ -3,6 +3,8 @@ id.require=ID\u4E0D\u80FD\u4E3A\u7A7A id.null=ID\u5FC5\u987B\u4E3A\u7A7A 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.name.require=\u5B57\u5178\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A diff --git a/main/manager-api/src/main/resources/i18n/validation_en_US.properties b/main/manager-api/src/main/resources/i18n/validation_en_US.properties index 37bebf18..2840d605 100644 --- a/main/manager-api/src/main/resources/i18n/validation_en_US.properties +++ b/main/manager-api/src/main/resources/i18n/validation_en_US.properties @@ -3,6 +3,8 @@ id.require=ID can not be empty id.null=ID has to be empty 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.name.require=The dictionary name cannot be empty diff --git a/main/manager-api/src/main/resources/i18n/validation_zh_CN.properties b/main/manager-api/src/main/resources/i18n/validation_zh_CN.properties index 41bf1e69..c86aded7 100644 --- a/main/manager-api/src/main/resources/i18n/validation_zh_CN.properties +++ b/main/manager-api/src/main/resources/i18n/validation_zh_CN.properties @@ -3,6 +3,8 @@ id.require=ID\u4E0D\u80FD\u4E3A\u7A7A id.null=ID\u5FC5\u987B\u4E3A\u7A7A 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.name.require=\u5B57\u5178\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A diff --git a/main/manager-api/src/main/resources/i18n/validation_zh_TW.properties b/main/manager-api/src/main/resources/i18n/validation_zh_TW.properties index 9241c97a..23c59eef 100644 --- a/main/manager-api/src/main/resources/i18n/validation_zh_TW.properties +++ b/main/manager-api/src/main/resources/i18n/validation_zh_TW.properties @@ -3,6 +3,8 @@ id.require=ID\u4E0D\u80FD\u4E3A\u7A7A id.null=ID\u5FC5\u987B\u4E3A\u7A7A 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.name.require=\u5B57\u5178\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A diff --git a/main/manager-api/src/main/resources/mapper/agent/AgentDao.xml b/main/manager-api/src/main/resources/mapper/agent/AgentDao.xml new file mode 100644 index 00000000..322ca172 --- /dev/null +++ b/main/manager-api/src/main/resources/mapper/agent/AgentDao.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/main/manager-api/src/main/resources/mapper/model/ModelConfigDao.xml b/main/manager-api/src/main/resources/mapper/model/ModelConfigDao.xml index 3f01ea5b..4f35f8d1 100644 --- a/main/manager-api/src/main/resources/mapper/model/ModelConfigDao.xml +++ b/main/manager-api/src/main/resources/mapper/model/ModelConfigDao.xml @@ -2,6 +2,22 @@ + + + + + + + + + + + + + + + +