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;
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.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
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.constant.Constant;
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.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")
@Tag(name = "服务端管理")
@AllArgsConstructor
public class ServerSideManageController
{
public class ServerSideManageController {
private final SysParamsService sysParamsService;
private static final ObjectMapper objectMapper;
static {
static {
objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
@@ -54,7 +60,7 @@ public class ServerSideManageController
@Operation(summary = "通知python服务端更新配置")
@PostMapping("/emit-action")
@LogOperation("通知python服务端更新配置")
// @RequiresPermissions("sys:role:superAdmin")
@RequiresPermissions("sys:role:superAdmin")
public Result<Boolean> emitServerAction(@RequestBody @Valid EmitSeverActionDTO emitSeverActionDTO) {
if (emitSeverActionDTO.getAction() == null) {
throw new RenException("无效服务端操作");
@@ -78,8 +84,8 @@ public class ServerSideManageController
}
String serverSK = sysParamsService.getValue(Constant.SERVER_SECRET, true);
WebSocketHttpHeaders headers = new WebSocketHttpHeaders();
headers.add("Authorization", "Bearer " + serverSK);
headers.add("device-id", serverSK);
headers.add("device-id", UUID.randomUUID().toString());
headers.add("client-id", UUID.randomUUID().toString());
try (WebSocketClientManager client = new WebSocketClientManager.Builder()
.connectTimeout(3, TimeUnit.SECONDS)
@@ -91,22 +97,20 @@ public class ServerSideManageController
client.sendJson(
ServerActionPayloadDTO.build(
actionEnum,
Map.of("secret", serverSK)
));
Map.of("secret", serverSK)));
// 等待服务端响应并持续监听信息
client.listener((jsonText)-> {
client.listener((jsonText) -> {
if (StringUtils.isBlank(jsonText)) {
return false;
}
try {
return ServerActionResponseDTO.isSuccess(objectMapper.readValue(jsonText, ServerActionResponseDTO.class));
}
catch (JsonProcessingException e) {
return ServerActionResponseDTO
.isSuccess(objectMapper.readValue(jsonText, ServerActionResponseDTO.class));
} catch (JsonProcessingException e) {
return false;
}
});
}
catch (Exception e) {
} catch (Exception e) {
// 捕获全部错误,由全局异常处理器返回
throw new RenException("WebSocket连接失败或连接超时");
}
+22 -144
View File
@@ -1,14 +1,9 @@
<template>
<div class="welcome">
<HeaderBar/>
<HeaderBar />
<div class="operation-bar">
<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 class="main-wrapper">
@@ -16,9 +11,8 @@
<div class="content-area">
<el-card class="params-card" shadow="never">
<el-table ref="paramsTable" :data="paramsList" class="transparent-table" v-loading="loading"
element-loading-text="拼命加载中" element-loading-spinner="el-icon-loading"
element-loading-background="rgba(255, 255, 255, 0.7)"
:header-cell-class-name="headerCellClassName">
element-loading-text="拼命加载中" element-loading-spinner="el-icon-loading"
element-loading-background="rgba(255, 255, 255, 0.7)" :header-cell-class-name="headerCellClassName">
<el-table-column label="选择" align="center" width="120">
<template slot-scope="scope">
<el-checkbox v-model="scope.row.selected"></el-checkbox>
@@ -28,34 +22,11 @@
<el-table-column label="操作" prop="operator" align="center" show-overflow-tooltip>
<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.update_config)">更新配置</el-button>
<el-button size="medium" type="text"
@click="emitAction(scope.row, actionMap.update_config)">更新配置</el-button>
</template>
</el-table-column>
</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>
</div>
</div>
@@ -63,7 +34,7 @@
<el-footer>
<version-footer/>
<version-footer />
</el-footer>
</div>
</template>
@@ -75,10 +46,9 @@ import ParamDialog from "@/components/ParamDialog.vue";
import VersionFooter from "@/components/VersionFooter.vue";
export default {
components: {HeaderBar, ParamDialog, VersionFooter},
components: { HeaderBar, ParamDialog, VersionFooter },
data() {
return {
searchCode: "",
paramsList: [],
actionMap: {
restart: {
@@ -144,25 +114,21 @@ export default {
fetchParams() {
this.loading = true;
Api.admin.getWsServerList(
{},
({data}) => {
this.loading = false;
if (data.code === 0) {
this.paramsList = data.data.map(item => ({address: item}));
this.total = data.data.length;
} else {
this.$message.error({
message: data.msg || '获取参数列表失败',
showClose: true
});
}
{},
({ data }) => {
this.loading = false;
if (data.code === 0) {
this.paramsList = data.data.map(item => ({ address: item }));
this.total = data.data.length;
} else {
this.$message.error({
message: data.msg || '获取参数列表失败',
showClose: true
});
}
}
);
},
handleSearch() {
this.currentPage = 1;
this.fetchParams();
},
emitAction(rowItem, actionItem) {
if (actionItem === undefined || rowItem.address === undefined) {
return;
@@ -175,7 +141,7 @@ export default {
Api.admin.sendWsServerAction({
targetWs: rowItem.address,
action: actionItem.value
}, ({data}) => {
}, ({ data }) => {
if (data.code !== 0) {
this.$message.error({
message: data.msg || '操作失败',
@@ -190,31 +156,11 @@ export default {
})
})
},
headerCellClassName({columnIndex}) {
headerCellClassName({ columnIndex }) {
if (columnIndex === 0) {
return "custom-selection-header";
}
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) {
background: white;
flex: 1;
@@ -490,7 +368,7 @@ export default {
padding-bottom: 16px;
}
& + tr {
&+tr {
margin-top: 10px;
}
}
+3 -36
View File
@@ -22,6 +22,7 @@ from core.utils.util import (
initialize_modules,
check_vad_update,
check_asr_update,
filter_sensitive_info,
)
from concurrent.futures import ThreadPoolExecutor, TimeoutError
from core.handle.sendAudioHandle import sendAudioMessage
@@ -276,7 +277,7 @@ class ConnectionHandler:
"type": "server",
"status": "success",
"message": "服务器重启中...",
"content": {'action': "restart"}
"content": {"action": "restart"},
}
)
)
@@ -306,7 +307,7 @@ class ConnectionHandler:
"type": "server",
"status": "error",
"message": f"Restart failed: {str(e)}",
'content': {'action': "restart"}
"content": {"action": "restart"},
}
)
)
@@ -1089,37 +1090,3 @@ class ConnectionHandler:
break
except Exception as 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
from core.handle.abortHandle import handleAbortMessage
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.sendAudioHandle import send_stt_message, send_tts_message
from core.handle.iotHandle import handleIotDescriptors, handleIotStatus
@@ -13,17 +13,20 @@ TAG = __name__
async def handleTextMessage(conn, message):
"""处理文本消息"""
conn.logger.bind(tag=TAG).info(f"收到文本消息:{message}")
try:
msg_json = json.loads(message)
if isinstance(msg_json, int):
conn.logger.bind(tag=TAG).info(f"收到文本消息:{message}")
await conn.websocket.send(message)
return
if msg_json["type"] == "hello":
conn.logger.bind(tag=TAG).info(f"收到hello消息:{message}")
await handleHelloMessage(conn, msg_json)
elif msg_json["type"] == "abort":
conn.logger.bind(tag=TAG).info(f"收到abort消息:{message}")
await handleAbortMessage(conn)
elif msg_json["type"] == "listen":
conn.logger.bind(tag=TAG).info(f"收到listen消息:{message}")
if "mode" in msg_json:
conn.client_listen_mode = msg_json["mode"]
conn.logger.bind(tag=TAG).debug(
@@ -64,11 +67,16 @@ async def handleTextMessage(conn, message):
# 否则需要LLM对文字内容进行答复
await startToChat(conn, text)
elif msg_json["type"] == "iot":
conn.logger.bind(tag=TAG).info(f"收到iot消息:{message}")
if "descriptors" in msg_json:
asyncio.create_task(handleIotDescriptors(conn, msg_json["descriptors"]))
if "states" in msg_json:
asyncio.create_task(handleIotStatus(conn, msg_json["states"]))
elif msg_json["type"] == "server":
# 记录日志时过滤敏感信息
conn.logger.bind(tag=TAG).info(
f"收到服务器消息:{filter_sensitive_info(msg_json)}"
)
# 如果配置是从API读取的,则需要验证secret
if not conn.read_config_from_api:
return
@@ -98,7 +106,7 @@ async def handleTextMessage(conn, message):
"type": "server",
"status": "error",
"message": "无法获取服务器实例",
"content": {"action": "update_config"}
"content": {"action": "update_config"},
}
)
)
@@ -111,7 +119,7 @@ async def handleTextMessage(conn, message):
"type": "server",
"status": "error",
"message": "更新服务器配置失败",
"content": {"action": "update_config"}
"content": {"action": "update_config"},
}
)
)
@@ -124,7 +132,7 @@ async def handleTextMessage(conn, message):
"type": "server",
"status": "success",
"message": "配置更新成功",
"content": {"action": "update_config"}
"content": {"action": "update_config"},
}
)
)
@@ -136,7 +144,7 @@ async def handleTextMessage(conn, message):
"type": "server",
"status": "error",
"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 typing import Dict, Any
from core.utils import tts, llm, intent, memory, vad, asr
import copy
TAG = __name__
emoji_map = {
@@ -319,7 +320,7 @@ def initialize_modules(
modules["memory"] = memory.create_instance(
memory_type,
config["Memory"][select_memory_module],
config.get('summaryMemory', None),
config.get("summaryMemory", None),
)
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
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))