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 ff6069bf..78a17b02 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 @@ -72,10 +72,12 @@ public class DeviceController { return new Result().error(ErrorCode.MCA_NOT_NULL); } // 生成六位验证码 - String code = String.valueOf(Math.random()).substring(2, 8); - String key = RedisKeys.getDeviceCaptchaKey(code); + String code; + String key; String existsMac = null; do { + code = String.valueOf(Math.random()).substring(2, 8); + key = RedisKeys.getDeviceCaptchaKey(code); existsMac = (String) redisUtils.get(key); } while (StringUtils.isNotBlank(existsMac)); diff --git a/main/manager-api/src/main/java/xiaozhi/modules/security/controller/LoginController.java b/main/manager-api/src/main/java/xiaozhi/modules/security/controller/LoginController.java index a008c25b..19bc2944 100644 --- a/main/manager-api/src/main/java/xiaozhi/modules/security/controller/LoginController.java +++ b/main/manager-api/src/main/java/xiaozhi/modules/security/controller/LoginController.java @@ -41,6 +41,7 @@ import xiaozhi.modules.sys.service.SysDictDataService; import xiaozhi.modules.sys.service.SysParamsService; import xiaozhi.modules.sys.service.SysUserService; import xiaozhi.modules.sys.vo.SysDictDataItem; +import xiaozhi.common.utils.JsonUtils; /** * 登录控制层 @@ -89,13 +90,13 @@ public class LoginController { @Operation(summary = "登录") public Result login(@RequestBody LoginDTO login) { String password = login.getPassword(); - + // 使用工具类解密并验证验证码 String actualPassword = Sm2DecryptUtil.decryptAndValidateCaptcha( password, login.getCaptchaId(), captchaService, sysParamsService); - + login.setPassword(actualPassword); - + // 按照用户名获取用户 SysUserDTO userDTO = sysUserService.getByUsername(login.getUsername()); // 判断用户是否存在 @@ -108,8 +109,6 @@ public class LoginController { } return sysUserTokenService.createToken(userDTO.getId()); } - - @PostMapping("/register") @Operation(summary = "注册") @@ -117,15 +116,15 @@ public class LoginController { if (!sysUserService.getAllowUserRegister()) { throw new RenException(ErrorCode.USER_REGISTER_DISABLED); } - + String password = login.getPassword(); - + // 使用工具类解密并验证验证码 String actualPassword = Sm2DecryptUtil.decryptAndValidateCaptcha( password, login.getCaptchaId(), captchaService, sysParamsService); - + login.setPassword(actualPassword); - + // 是否开启手机注册 Boolean isMobileRegister = sysParamsService .getValueObject(Constant.SysMSMParam.SERVER_ENABLE_MOBILE_REGISTER.getValue(), Boolean.class); @@ -204,11 +203,11 @@ public class LoginController { } String password = dto.getPassword(); - + // 使用工具类解密并验证验证码 String actualPassword = Sm2DecryptUtil.decryptAndValidateCaptcha( password, dto.getCaptchaId(), captchaService, sysParamsService); - + dto.setPassword(actualPassword); sysUserService.changePasswordDirectly(userDTO.getId(), dto.getPassword()); @@ -229,7 +228,7 @@ public class LoginController { config.put("beianIcpNum", sysParamsService.getValue(Constant.SysBaseParam.BEIAN_ICP_NUM.getValue(), true)); config.put("beianGaNum", sysParamsService.getValue(Constant.SysBaseParam.BEIAN_GA_NUM.getValue(), true)); config.put("name", sysParamsService.getValue(Constant.SysBaseParam.SERVER_NAME.getValue(), true)); - + // SM2公钥 String publicKey = sysParamsService.getValue(Constant.SM2_PUBLIC_KEY, true); if (StringUtils.isBlank(publicKey)) { @@ -237,6 +236,12 @@ public class LoginController { } config.put("sm2PublicKey", publicKey); + // 获取system-web.menu参数配置 + String menuConfig = sysParamsService.getValue("system-web.menu", true); + if (StringUtils.isNotBlank(menuConfig)) { + config.put("systemWebMenu", JsonUtils.parseObject(menuConfig, Object.class)); + } + return new Result>().ok(config); } } \ No newline at end of file diff --git a/main/manager-api/src/main/resources/db/changelog/202512031514.sql b/main/manager-api/src/main/resources/db/changelog/202512031514.sql new file mode 100644 index 00000000..adfff855 --- /dev/null +++ b/main/manager-api/src/main/resources/db/changelog/202512031514.sql @@ -0,0 +1,6 @@ +-- 添加系统功能菜单配置参数 +delete from `sys_params` where param_code = 'system-web.menu'; + +-- 添加系统功能菜单配置参数 +INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES +(600, 'system-web.menu', '{"voiceprintRecognition":{"name":"feature.voiceprintRecognition.name","enabled":false,"description":"feature.voiceprintRecognition.description"},"voiceClone":{"name":"feature.voiceClone.name","enabled":false,"description":"feature.voiceClone.description"},"knowledgeBase":{"name":"feature.knowledgeBase.name","enabled":false,"description":"feature.knowledgeBase.description"},"mcpAccessPoint":{"name":"feature.mcpAccessPoint.name","enabled":false,"description":"feature.mcpAccessPoint.description"},"vad":{"name":"feature.vad.name","enabled":false,"description":"feature.vad.description"},"asr":{"name":"feature.asr.name","enabled":false,"description":"feature.asr.description"}}', 'json', 1, '系统功能菜单配置'); \ No newline at end of file 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 e0eb949b..700b16f8 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 @@ -430,3 +430,10 @@ databaseChangeLog: - sqlFile: encoding: utf8 path: classpath:db/changelog/202512041515.sql + - changeSet: + id: 202512031514 + author: hrz + changes: + - sqlFile: + encoding: utf8 + path: classpath:db/changelog/202512031514.sql \ No newline at end of file diff --git a/main/manager-web/src/components/DeviceItem.vue b/main/manager-web/src/components/DeviceItem.vue index bfbbb97f..c29d3243 100644 --- a/main/manager-web/src/components/DeviceItem.vue +++ b/main/manager-web/src/components/DeviceItem.vue @@ -23,7 +23,7 @@
{{ $t('home.configureRole') }}
-
+
{{ $t('home.voiceprintRecognition') }}
@@ -49,7 +49,15 @@ import i18n from '@/i18n'; export default { name: 'DeviceItem', props: { - device: { type: Object, required: true } + device: { type: Object, required: true }, + featureStatus: { + type: Object, + default: () => ({ + voiceprintRecognition: false, + voiceClone: false, + knowledgeBase: false + }) + } }, data() { return { switchValue: false } diff --git a/main/manager-web/src/components/FunctionDialog.vue b/main/manager-web/src/components/FunctionDialog.vue index 23adce78..076cfa9f 100644 --- a/main/manager-web/src/components/FunctionDialog.vue +++ b/main/manager-web/src/components/FunctionDialog.vue @@ -106,7 +106,7 @@
-
+
@@ -171,6 +171,7 @@ + + \ No newline at end of file diff --git a/main/manager-web/src/views/home.vue b/main/manager-web/src/views/home.vue index 01cf74fb..fea5af89 100644 --- a/main/manager-web/src/views/home.vue +++ b/main/manager-web/src/views/home.vue @@ -39,8 +39,9 @@
@@ -61,6 +62,7 @@ import ChatHistoryDialog from '@/components/ChatHistoryDialog.vue'; import DeviceItem from '@/components/DeviceItem.vue'; import HeaderBar from '@/components/HeaderBar.vue'; import VersionFooter from '@/components/VersionFooter.vue'; +import featureManager from '@/utils/featureManager'; export default { name: 'HomePage', @@ -76,15 +78,33 @@ export default { skeletonCount: localStorage.getItem('skeletonCount') || 8, showChatHistory: false, currentAgentId: '', - currentAgentName: '' + currentAgentName: '', + // 功能状态 + featureStatus: { + voiceprintRecognition: false, + voiceClone: false, + knowledgeBase: false + } } }, - mounted() { + async mounted() { this.fetchAgentList(); + await this.loadFeatureStatus(); }, methods: { + // 加载功能状态 + async loadFeatureStatus() { + await featureManager.waitForInitialization(); + const config = featureManager.getConfig(); + this.featureStatus = { + voiceprintRecognition: config.voiceprintRecognition, + voiceClone: config.voiceClone, + knowledgeBase: config.knowledgeBase + }; + }, + showAddDialog() { this.addDeviceDialogVisible = true }, diff --git a/main/manager-web/src/views/login.vue b/main/manager-web/src/views/login.vue index 9d56fdc5..cdcf3754 100644 --- a/main/manager-web/src/views/login.vue +++ b/main/manager-web/src/views/login.vue @@ -156,6 +156,7 @@ import VersionFooter from "@/components/VersionFooter.vue"; import i18n, { changeLanguage } from "@/i18n"; import { getUUID, goToPage, showDanger, showSuccess, sm2Encrypt, validateMobile } from "@/utils"; import { mapState } from "vuex"; +import featureManager from "@/utils/featureManager"; export default { name: "login", @@ -214,6 +215,13 @@ export default { this.$store.dispatch("fetchPubConfig").then(() => { // 根据配置决定默认登录方式 this.isMobileLogin = this.enableMobileRegister; + + // pub-config接口调用完成后,重新初始化featureManager以确保使用最新的配置 + featureManager.waitForInitialization().then(() => { + console.log('featureManager重新初始化完成,使用pub-config配置'); + }).catch(error => { + console.warn('featureManager重新初始化失败:', error); + }); }); }, methods: { diff --git a/main/manager-web/src/views/roleConfig.vue b/main/manager-web/src/views/roleConfig.vue index 9f1ab92f..16e0f19e 100644 --- a/main/manager-web/src/views/roleConfig.vue +++ b/main/manager-web/src/views/roleConfig.vue @@ -121,7 +121,11 @@
- +
- +
diff --git a/main/xiaozhi-server/core/handle/sendAudioHandle.py b/main/xiaozhi-server/core/handle/sendAudioHandle.py index 14f43232..ea952fbe 100644 --- a/main/xiaozhi-server/core/handle/sendAudioHandle.py +++ b/main/xiaozhi-server/core/handle/sendAudioHandle.py @@ -116,6 +116,7 @@ async def _sendAudio_single(conn, opus_packet, send_delay, frame_duration=60): if packet_count < pre_buffer_count or send_delay > 0: # 预缓冲阶段或固定延迟模式,直接发送 await _do_send_audio(conn, opus_packet, flow_control, frame_duration) + conn.client_is_speaking = True if send_delay > 0 and packet_count >= pre_buffer_count: await asyncio.sleep(send_delay) @@ -127,6 +128,7 @@ async def _sendAudio_single(conn, opus_packet, send_delay, frame_duration=60): await _do_send_audio(conn, packet, flow_control, frame_duration) await rate_controller.check_queue(send_callback) + conn.client_is_speaking = True # 更新流控状态 flow_control["packet_count"] += 1 diff --git a/main/xiaozhi-server/core/websocket_server.py b/main/xiaozhi-server/core/websocket_server.py index 9cbbbe19..168b436e 100644 --- a/main/xiaozhi-server/core/websocket_server.py +++ b/main/xiaozhi-server/core/websocket_server.py @@ -1,8 +1,35 @@ import asyncio -import json +import logging import websockets from config.logger import setup_logging + + +class SuppressInvalidHandshakeFilter(logging.Filter): + """过滤掉无效握手错误日志(如HTTPS访问WS端口)""" + + def filter(self, record): + msg = record.getMessage() + suppress_keywords = [ + "opening handshake failed", + "did not receive a valid HTTP request", + "connection closed while reading HTTP request", + "line without CRLF", + ] + return not any(keyword in msg for keyword in suppress_keywords) + + +def _setup_websockets_logger(): + """配置 websockets 相关的所有 logger,过滤无效握手错误""" + filter_instance = SuppressInvalidHandshakeFilter() + for logger_name in ["websockets", "websockets.server", "websockets.client"]: + logger = logging.getLogger(logger_name) + logger.addFilter(filter_instance) + + +_setup_websockets_logger() + + from core.connection import ConnectionHandler from config.config_loader import get_config_from_api_async from core.auth import AuthManager, AuthenticationError