update:优化无用组件

This commit is contained in:
hrz
2025-05-20 22:58:42 +08:00
parent f1105e6822
commit b8da0714c2
5 changed files with 102 additions and 210 deletions
@@ -1,16 +1,28 @@
package xiaozhi.modules.sys.controller; package xiaozhi.modules.sys.controller;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.socket.WebSocketHttpHeaders;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.socket.WebSocketHttpHeaders;
import xiaozhi.common.annotation.LogOperation; import xiaozhi.common.annotation.LogOperation;
import xiaozhi.common.constant.Constant; import xiaozhi.common.constant.Constant;
import xiaozhi.common.exception.RenException; import xiaozhi.common.exception.RenException;
@@ -22,11 +34,6 @@ import xiaozhi.modules.sys.enums.ServerActionEnum;
import xiaozhi.modules.sys.service.SysParamsService; import xiaozhi.modules.sys.service.SysParamsService;
import xiaozhi.modules.sys.utils.WebSocketClientManager; import xiaozhi.modules.sys.utils.WebSocketClientManager;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/** /**
* 服务端管理控制器 * 服务端管理控制器
*/ */
@@ -34,11 +41,10 @@ import java.util.concurrent.TimeUnit;
@RequestMapping("admin/server") @RequestMapping("admin/server")
@Tag(name = "服务端管理") @Tag(name = "服务端管理")
@AllArgsConstructor @AllArgsConstructor
public class ServerSideManageController public class ServerSideManageController {
{
private final SysParamsService sysParamsService; private final SysParamsService sysParamsService;
private static final ObjectMapper objectMapper; private static final ObjectMapper objectMapper;
static { static {
objectMapper = new ObjectMapper(); objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
} }
@@ -54,7 +60,7 @@ public class ServerSideManageController
@Operation(summary = "通知python服务端更新配置") @Operation(summary = "通知python服务端更新配置")
@PostMapping("/emit-action") @PostMapping("/emit-action")
@LogOperation("通知python服务端更新配置") @LogOperation("通知python服务端更新配置")
// @RequiresPermissions("sys:role:superAdmin") @RequiresPermissions("sys:role:superAdmin")
public Result<Boolean> emitServerAction(@RequestBody @Valid EmitSeverActionDTO emitSeverActionDTO) { public Result<Boolean> emitServerAction(@RequestBody @Valid EmitSeverActionDTO emitSeverActionDTO) {
if (emitSeverActionDTO.getAction() == null) { if (emitSeverActionDTO.getAction() == null) {
throw new RenException("无效服务端操作"); throw new RenException("无效服务端操作");
@@ -78,8 +84,8 @@ public class ServerSideManageController
} }
String serverSK = sysParamsService.getValue(Constant.SERVER_SECRET, true); String serverSK = sysParamsService.getValue(Constant.SERVER_SECRET, true);
WebSocketHttpHeaders headers = new WebSocketHttpHeaders(); WebSocketHttpHeaders headers = new WebSocketHttpHeaders();
headers.add("Authorization", "Bearer " + serverSK); headers.add("device-id", UUID.randomUUID().toString());
headers.add("device-id", serverSK); headers.add("client-id", UUID.randomUUID().toString());
try (WebSocketClientManager client = new WebSocketClientManager.Builder() try (WebSocketClientManager client = new WebSocketClientManager.Builder()
.connectTimeout(3, TimeUnit.SECONDS) .connectTimeout(3, TimeUnit.SECONDS)
@@ -91,22 +97,20 @@ public class ServerSideManageController
client.sendJson( client.sendJson(
ServerActionPayloadDTO.build( ServerActionPayloadDTO.build(
actionEnum, actionEnum,
Map.of("secret", serverSK) Map.of("secret", serverSK)));
));
// 等待服务端响应并持续监听信息 // 等待服务端响应并持续监听信息
client.listener((jsonText)-> { client.listener((jsonText) -> {
if (StringUtils.isBlank(jsonText)) { if (StringUtils.isBlank(jsonText)) {
return false; return false;
} }
try { try {
return ServerActionResponseDTO.isSuccess(objectMapper.readValue(jsonText, ServerActionResponseDTO.class)); return ServerActionResponseDTO
} .isSuccess(objectMapper.readValue(jsonText, ServerActionResponseDTO.class));
catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
return false; return false;
} }
}); });
} } catch (Exception e) {
catch (Exception e) {
// 捕获全部错误,由全局异常处理器返回 // 捕获全部错误,由全局异常处理器返回
throw new RenException("WebSocket连接失败或连接超时"); throw new RenException("WebSocket连接失败或连接超时");
} }
+22 -144
View File
@@ -1,14 +1,9 @@
<template> <template>
<div class="welcome"> <div class="welcome">
<HeaderBar/> <HeaderBar />
<div class="operation-bar"> <div class="operation-bar">
<h2 class="page-title">服务端管理</h2> <h2 class="page-title">服务端管理</h2>
<div class="right-operations">
<el-input placeholder="请输入服务端ws地址查询" v-model="searchCode" class="search-input"
@keyup.enter.native="handleSearch" clearable/>
<el-button class="btn-search" @click="handleSearch">搜索</el-button>
</div>
</div> </div>
<div class="main-wrapper"> <div class="main-wrapper">
@@ -16,9 +11,8 @@
<div class="content-area"> <div class="content-area">
<el-card class="params-card" shadow="never"> <el-card class="params-card" shadow="never">
<el-table ref="paramsTable" :data="paramsList" class="transparent-table" v-loading="loading" <el-table ref="paramsTable" :data="paramsList" class="transparent-table" v-loading="loading"
element-loading-text="拼命加载中" element-loading-spinner="el-icon-loading" element-loading-text="拼命加载中" element-loading-spinner="el-icon-loading"
element-loading-background="rgba(255, 255, 255, 0.7)" element-loading-background="rgba(255, 255, 255, 0.7)" :header-cell-class-name="headerCellClassName">
:header-cell-class-name="headerCellClassName">
<el-table-column label="选择" align="center" width="120"> <el-table-column label="选择" align="center" width="120">
<template slot-scope="scope"> <template slot-scope="scope">
<el-checkbox v-model="scope.row.selected"></el-checkbox> <el-checkbox v-model="scope.row.selected"></el-checkbox>
@@ -28,34 +22,11 @@
<el-table-column label="操作" prop="operator" align="center" show-overflow-tooltip> <el-table-column label="操作" prop="operator" align="center" show-overflow-tooltip>
<template slot-scope="scope"> <template slot-scope="scope">
<el-button size="medium" type="text" @click="emitAction(scope.row, actionMap.restart)">重启</el-button> <el-button size="medium" type="text" @click="emitAction(scope.row, actionMap.restart)">重启</el-button>
<el-button size="medium" type="text" @click="emitAction(scope.row, actionMap.update_config)">更新配置</el-button> <el-button size="medium" type="text"
@click="emitAction(scope.row, actionMap.update_config)">更新配置</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<div class="table_bottom">
<div class="custom-pagination">
<el-select v-model="pageSize" @change="handlePageSizeChange" class="page-size-select">
<el-option v-for="item in pageSizeOptions" :key="item" :label="`${item}条/页`"
:value="item">
</el-option>
</el-select>
<button class="pagination-btn" :disabled="currentPage === 1" @click="goFirst">
首页
</button>
<button class="pagination-btn" :disabled="currentPage === 1" @click="goPrev">
上一页
</button>
<button v-for="page in visiblePages" :key="page" class="pagination-btn"
:class="{ active: page === currentPage }" @click="goToPage(page)">
{{ page }}
</button>
<button class="pagination-btn" :disabled="currentPage === pageCount" @click="goNext">
下一页
</button>
<span class="total-text">{{ total }}条记录</span>
</div>
</div>
</el-card> </el-card>
</div> </div>
</div> </div>
@@ -63,7 +34,7 @@
<el-footer> <el-footer>
<version-footer/> <version-footer />
</el-footer> </el-footer>
</div> </div>
</template> </template>
@@ -75,10 +46,9 @@ import ParamDialog from "@/components/ParamDialog.vue";
import VersionFooter from "@/components/VersionFooter.vue"; import VersionFooter from "@/components/VersionFooter.vue";
export default { export default {
components: {HeaderBar, ParamDialog, VersionFooter}, components: { HeaderBar, ParamDialog, VersionFooter },
data() { data() {
return { return {
searchCode: "",
paramsList: [], paramsList: [],
actionMap: { actionMap: {
restart: { restart: {
@@ -144,25 +114,21 @@ export default {
fetchParams() { fetchParams() {
this.loading = true; this.loading = true;
Api.admin.getWsServerList( Api.admin.getWsServerList(
{}, {},
({data}) => { ({ data }) => {
this.loading = false; this.loading = false;
if (data.code === 0) { if (data.code === 0) {
this.paramsList = data.data.map(item => ({address: item})); this.paramsList = data.data.map(item => ({ address: item }));
this.total = data.data.length; this.total = data.data.length;
} else { } else {
this.$message.error({ this.$message.error({
message: data.msg || '获取参数列表失败', message: data.msg || '获取参数列表失败',
showClose: true showClose: true
}); });
}
} }
}
); );
}, },
handleSearch() {
this.currentPage = 1;
this.fetchParams();
},
emitAction(rowItem, actionItem) { emitAction(rowItem, actionItem) {
if (actionItem === undefined || rowItem.address === undefined) { if (actionItem === undefined || rowItem.address === undefined) {
return; return;
@@ -175,7 +141,7 @@ export default {
Api.admin.sendWsServerAction({ Api.admin.sendWsServerAction({
targetWs: rowItem.address, targetWs: rowItem.address,
action: actionItem.value action: actionItem.value
}, ({data}) => { }, ({ data }) => {
if (data.code !== 0) { if (data.code !== 0) {
this.$message.error({ this.$message.error({
message: data.msg || '操作失败', message: data.msg || '操作失败',
@@ -190,31 +156,11 @@ export default {
}) })
}) })
}, },
headerCellClassName({columnIndex}) { headerCellClassName({ columnIndex }) {
if (columnIndex === 0) { if (columnIndex === 0) {
return "custom-selection-header"; return "custom-selection-header";
} }
return ""; return "";
},
goFirst() {
this.currentPage = 1;
this.fetchParams();
},
goPrev() {
if (this.currentPage > 1) {
this.currentPage--;
this.fetchParams();
}
},
goNext() {
if (this.currentPage < this.pageCount) {
this.currentPage++;
this.fetchParams();
}
},
goToPage(page) {
this.currentPage = page;
this.fetchParams();
} }
}, },
}; };
@@ -356,74 +302,6 @@ export default {
} }
} }
.custom-pagination {
display: flex;
align-items: center;
gap: 10px;
.el-select {
margin-right: 8px;
}
.pagination-btn:first-child,
.pagination-btn:nth-child(2),
.pagination-btn:nth-last-child(2),
.pagination-btn:nth-child(3) {
min-width: 60px;
height: 32px;
padding: 0 12px;
border-radius: 4px;
border: 1px solid #e4e7ed;
background: #dee7ff;
color: #606266;
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
background: #d7dce6;
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
.pagination-btn:not(:first-child):not(:nth-child(3)):not(:nth-child(2)):not(:nth-last-child(2)) {
min-width: 28px;
height: 32px;
padding: 0;
border-radius: 4px;
border: 1px solid transparent;
background: transparent;
color: #606266;
font-size: 14px;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
background: rgba(245, 247, 250, 0.3);
}
}
.pagination-btn.active {
background: #5f70f3 !important;
color: #ffffff !important;
border-color: #5f70f3 !important;
&:hover {
background: #6d7cf5 !important;
}
}
.total-text {
color: #909399;
font-size: 14px;
margin-left: 10px;
}
}
:deep(.transparent-table) { :deep(.transparent-table) {
background: white; background: white;
flex: 1; flex: 1;
@@ -490,7 +368,7 @@ export default {
padding-bottom: 16px; padding-bottom: 16px;
} }
& + tr { &+tr {
margin-top: 10px; margin-top: 10px;
} }
} }
+3 -36
View File
@@ -22,6 +22,7 @@ from core.utils.util import (
initialize_modules, initialize_modules,
check_vad_update, check_vad_update,
check_asr_update, check_asr_update,
filter_sensitive_info,
) )
from concurrent.futures import ThreadPoolExecutor, TimeoutError from concurrent.futures import ThreadPoolExecutor, TimeoutError
from core.handle.sendAudioHandle import sendAudioMessage from core.handle.sendAudioHandle import sendAudioMessage
@@ -276,7 +277,7 @@ class ConnectionHandler:
"type": "server", "type": "server",
"status": "success", "status": "success",
"message": "服务器重启中...", "message": "服务器重启中...",
"content": {'action': "restart"} "content": {"action": "restart"},
} }
) )
) )
@@ -306,7 +307,7 @@ class ConnectionHandler:
"type": "server", "type": "server",
"status": "error", "status": "error",
"message": f"Restart failed: {str(e)}", "message": f"Restart failed: {str(e)}",
'content': {'action': "restart"} "content": {"action": "restart"},
} }
) )
) )
@@ -1089,37 +1090,3 @@ class ConnectionHandler:
break break
except Exception as e: except Exception as e:
self.logger.bind(tag=TAG).error(f"超时检查任务出错: {e}") self.logger.bind(tag=TAG).error(f"超时检查任务出错: {e}")
def filter_sensitive_info(config: dict) -> dict:
"""
过滤配置中的敏感信息
Args:
config: 原始配置字典
Returns:
过滤后的配置字典
"""
sensitive_keys = [
"api_key",
"personal_access_token",
"access_token",
"token",
"secret",
"access_key_secret",
"secret_key",
]
def _filter_dict(d: dict) -> dict:
filtered = {}
for k, v in d.items():
if any(sensitive in k.lower() for sensitive in sensitive_keys):
filtered[k] = "***"
elif isinstance(v, dict):
filtered[k] = _filter_dict(v)
elif isinstance(v, list):
filtered[k] = [_filter_dict(i) if isinstance(i, dict) else i for i in v]
else:
filtered[k] = v
return filtered
return _filter_dict(copy.deepcopy(config))
+14 -6
View File
@@ -1,7 +1,7 @@
import json import json
from core.handle.abortHandle import handleAbortMessage from core.handle.abortHandle import handleAbortMessage
from core.handle.helloHandle import handleHelloMessage from core.handle.helloHandle import handleHelloMessage
from core.utils.util import remove_punctuation_and_length from core.utils.util import remove_punctuation_and_length, filter_sensitive_info
from core.handle.receiveAudioHandle import startToChat, handleAudioMessage from core.handle.receiveAudioHandle import startToChat, handleAudioMessage
from core.handle.sendAudioHandle import send_stt_message, send_tts_message from core.handle.sendAudioHandle import send_stt_message, send_tts_message
from core.handle.iotHandle import handleIotDescriptors, handleIotStatus from core.handle.iotHandle import handleIotDescriptors, handleIotStatus
@@ -13,17 +13,20 @@ TAG = __name__
async def handleTextMessage(conn, message): async def handleTextMessage(conn, message):
"""处理文本消息""" """处理文本消息"""
conn.logger.bind(tag=TAG).info(f"收到文本消息:{message}")
try: try:
msg_json = json.loads(message) msg_json = json.loads(message)
if isinstance(msg_json, int): if isinstance(msg_json, int):
conn.logger.bind(tag=TAG).info(f"收到文本消息:{message}")
await conn.websocket.send(message) await conn.websocket.send(message)
return return
if msg_json["type"] == "hello": if msg_json["type"] == "hello":
conn.logger.bind(tag=TAG).info(f"收到hello消息:{message}")
await handleHelloMessage(conn, msg_json) await handleHelloMessage(conn, msg_json)
elif msg_json["type"] == "abort": elif msg_json["type"] == "abort":
conn.logger.bind(tag=TAG).info(f"收到abort消息:{message}")
await handleAbortMessage(conn) await handleAbortMessage(conn)
elif msg_json["type"] == "listen": elif msg_json["type"] == "listen":
conn.logger.bind(tag=TAG).info(f"收到listen消息:{message}")
if "mode" in msg_json: if "mode" in msg_json:
conn.client_listen_mode = msg_json["mode"] conn.client_listen_mode = msg_json["mode"]
conn.logger.bind(tag=TAG).debug( conn.logger.bind(tag=TAG).debug(
@@ -64,11 +67,16 @@ async def handleTextMessage(conn, message):
# 否则需要LLM对文字内容进行答复 # 否则需要LLM对文字内容进行答复
await startToChat(conn, text) await startToChat(conn, text)
elif msg_json["type"] == "iot": elif msg_json["type"] == "iot":
conn.logger.bind(tag=TAG).info(f"收到iot消息:{message}")
if "descriptors" in msg_json: if "descriptors" in msg_json:
asyncio.create_task(handleIotDescriptors(conn, msg_json["descriptors"])) asyncio.create_task(handleIotDescriptors(conn, msg_json["descriptors"]))
if "states" in msg_json: if "states" in msg_json:
asyncio.create_task(handleIotStatus(conn, msg_json["states"])) asyncio.create_task(handleIotStatus(conn, msg_json["states"]))
elif msg_json["type"] == "server": elif msg_json["type"] == "server":
# 记录日志时过滤敏感信息
conn.logger.bind(tag=TAG).info(
f"收到服务器消息:{filter_sensitive_info(msg_json)}"
)
# 如果配置是从API读取的,则需要验证secret # 如果配置是从API读取的,则需要验证secret
if not conn.read_config_from_api: if not conn.read_config_from_api:
return return
@@ -98,7 +106,7 @@ async def handleTextMessage(conn, message):
"type": "server", "type": "server",
"status": "error", "status": "error",
"message": "无法获取服务器实例", "message": "无法获取服务器实例",
"content": {"action": "update_config"} "content": {"action": "update_config"},
} }
) )
) )
@@ -111,7 +119,7 @@ async def handleTextMessage(conn, message):
"type": "server", "type": "server",
"status": "error", "status": "error",
"message": "更新服务器配置失败", "message": "更新服务器配置失败",
"content": {"action": "update_config"} "content": {"action": "update_config"},
} }
) )
) )
@@ -124,7 +132,7 @@ async def handleTextMessage(conn, message):
"type": "server", "type": "server",
"status": "success", "status": "success",
"message": "配置更新成功", "message": "配置更新成功",
"content": {"action": "update_config"} "content": {"action": "update_config"},
} }
) )
) )
@@ -136,7 +144,7 @@ async def handleTextMessage(conn, message):
"type": "server", "type": "server",
"status": "error", "status": "error",
"message": f"更新配置失败: {str(e)}", "message": f"更新配置失败: {str(e)}",
"content": {"action": "update_config"} "content": {"action": "update_config"},
} }
) )
) )
+36 -1
View File
@@ -9,6 +9,7 @@ import opuslib_next
from pydub import AudioSegment from pydub import AudioSegment
from typing import Dict, Any from typing import Dict, Any
from core.utils import tts, llm, intent, memory, vad, asr from core.utils import tts, llm, intent, memory, vad, asr
import copy
TAG = __name__ TAG = __name__
emoji_map = { emoji_map = {
@@ -319,7 +320,7 @@ def initialize_modules(
modules["memory"] = memory.create_instance( modules["memory"] = memory.create_instance(
memory_type, memory_type,
config["Memory"][select_memory_module], config["Memory"][select_memory_module],
config.get('summaryMemory', None), config.get("summaryMemory", None),
) )
logger.bind(tag=TAG).info(f"初始化组件: memory成功 {select_memory_module}") logger.bind(tag=TAG).info(f"初始化组件: memory成功 {select_memory_module}")
@@ -956,3 +957,37 @@ def check_asr_update(before_config, new_config):
) )
update_asr = current_asr_type != new_asr_type update_asr = current_asr_type != new_asr_type
return update_asr return update_asr
def filter_sensitive_info(config: dict) -> dict:
"""
过滤配置中的敏感信息
Args:
config: 原始配置字典
Returns:
过滤后的配置字典
"""
sensitive_keys = [
"api_key",
"personal_access_token",
"access_token",
"token",
"secret",
"access_key_secret",
"secret_key",
]
def _filter_dict(d: dict) -> dict:
filtered = {}
for k, v in d.items():
if any(sensitive in k.lower() for sensitive in sensitive_keys):
filtered[k] = "***"
elif isinstance(v, dict):
filtered[k] = _filter_dict(v)
elif isinstance(v, list):
filtered[k] = [_filter_dict(i) if isinstance(i, dict) else i for i in v]
else:
filtered[k] = v
return filtered
return _filter_dict(copy.deepcopy(config))