Skip to content

Commit 488f247

Browse files
openrzgitjianyu
andauthored
update:增加单台设备每天最多聊天字数,防止被ddos
* 添加清空redis所有库的接口 --AdminController.java 添加了清除所有的接口 --RedisUtils.java 添加了清除redis所有key的方法,redisTemplate提供的清空方法已经被标记为弃用了,所有选择用执行lua脚本方式 * fix:修复使用本地配置时忘记附带提示词 * fix:修复意图识别插件名称格式bug * update:版本升级后强制刷新redis * update:增加单台设备每天最多聊天字数,防止被ddos --------- Co-authored-by: 剑雨 <[email protected]>
1 parent c74dbf0 commit 488f247

File tree

11 files changed

+144
-7
lines changed

11 files changed

+144
-7
lines changed

main/manager-api/src/main/java/xiaozhi/common/constant/Constant.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,4 +163,9 @@ public String getValue() {
163163
return value;
164164
}
165165
}
166+
167+
/**
168+
* 版本号
169+
*/
170+
public static final String VERSION = "0.3.6";
166171
}

main/manager-api/src/main/java/xiaozhi/common/redis/RedisKeys.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,11 @@ public static String getServerConfigKey() {
7575
public static String getTimbreDetailsKey(String id) {
7676
return "timbre:details:" + id;
7777
}
78+
79+
/**
80+
* 获取版本号Key
81+
*/
82+
public static String getVersionKey() {
83+
return "system:version";
84+
}
7885
}

main/manager-api/src/main/java/xiaozhi/common/redis/RedisUtils.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package xiaozhi.common.redis;
22

33
import java.util.Collection;
4+
import java.util.Collections;
5+
import java.util.List;
46
import java.util.Map;
57
import java.util.concurrent.TimeUnit;
68

79
import org.springframework.data.redis.core.HashOperations;
810
import org.springframework.data.redis.core.RedisTemplate;
11+
import org.springframework.data.redis.core.script.DefaultRedisScript;
912
import org.springframework.stereotype.Component;
1013

1114
import jakarta.annotation.Resource;
@@ -27,7 +30,7 @@ public class RedisUtils {
2730
/**
2831
* 过期时长为1小时,单位:秒
2932
*/
30-
public final static long HOUR_ONE_EXPIRE = 60 * 60 * 1L;
33+
public final static long HOUR_ONE_EXPIRE = (long) 60 * 60;
3134
/**
3235
* 过期时长为6小时,单位:秒
3336
*/
@@ -124,4 +127,24 @@ public void leftPush(String key, Object value, long expire) {
124127
public Object rightPop(String key) {
125128
return redisTemplate.opsForList().rightPop(key);
126129
}
130+
131+
132+
/**
133+
* 清空所有 Redis 数据库中的所有键
134+
*/
135+
public void emptyAll() {
136+
// Lua 脚本 FLUSHALL是redis清空所有库的命令
137+
String luaScript ="redis.call('FLUSHALL')";
138+
139+
// 创建 DefaultRedisScript 对象
140+
DefaultRedisScript<Void> redisScript = new DefaultRedisScript<>();
141+
redisScript.setScriptText(luaScript); // 设置 Lua 脚本内容
142+
redisScript.setResultType(Void.class); // 设置返回值类型
143+
144+
// 执行 Lua 脚本
145+
List<String> keys = Collections.emptyList(); // 如果脚本不依赖 key,可以传入空列表
146+
redisTemplate.execute(redisScript, keys);
147+
148+
}
149+
127150
}

main/manager-api/src/main/java/xiaozhi/modules/config/init/SystemInitConfig.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
import org.springframework.context.annotation.DependsOn;
66

77
import jakarta.annotation.PostConstruct;
8+
import xiaozhi.common.constant.Constant;
9+
import xiaozhi.common.redis.RedisKeys;
10+
import xiaozhi.common.redis.RedisUtils;
811
import xiaozhi.modules.config.service.ConfigService;
912
import xiaozhi.modules.sys.service.SysParamsService;
1013

@@ -18,8 +21,20 @@ public class SystemInitConfig {
1821
@Autowired
1922
private ConfigService configService;
2023

24+
@Autowired
25+
private RedisUtils redisUtils;
26+
2127
@PostConstruct
2228
public void init() {
29+
// 检查版本号
30+
String redisVersion = (String) redisUtils.get(RedisKeys.getVersionKey());
31+
if (!Constant.VERSION.equals(redisVersion)) {
32+
// 如果版本不一致,清空Redis
33+
redisUtils.emptyAll();
34+
// 存储新版本号
35+
redisUtils.set(RedisKeys.getVersionKey(), Constant.VERSION);
36+
}
37+
2338
sysParamsService.initServerSecret();
2439
configService.getConfig(false);
2540
}

main/manager-api/src/main/java/xiaozhi/modules/config/service/impl/ConfigServiceImpl.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ public Map<String, Object> getAgentModels(String macAddress, Map<String, String>
9191
}
9292
throw new RenException(ErrorCode.OTA_DEVICE_NOT_FOUND, "not found device");
9393
}
94+
9495
// 获取智能体信息
9596
AgentEntity agent = agentService.getAgentById(device.getAgentId());
9697
if (agent == null) {
@@ -104,7 +105,9 @@ public Map<String, Object> getAgentModels(String macAddress, Map<String, String>
104105
}
105106
// 构建返回数据
106107
Map<String, Object> result = new HashMap<>();
107-
108+
// 获取单台设备每天最多输出字数
109+
String deviceMaxOutputSize = sysParamsService.getValue("device_max_output_size", true);
110+
result.put("device_max_output_size", deviceMaxOutputSize);
108111
// 如果客户端已实例化模型,则不返回
109112
String alreadySelectedVadModelId = (String) selectedModule.get("VAD");
110113
if (alreadySelectedVadModelId != null && alreadySelectedVadModelId.equals(agent.getVadModelId())) {

main/manager-api/src/main/java/xiaozhi/modules/security/controller/LoginController.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import io.swagger.v3.oas.annotations.tags.Tag;
1616
import jakarta.servlet.http.HttpServletResponse;
1717
import lombok.AllArgsConstructor;
18+
import xiaozhi.common.constant.Constant;
1819
import xiaozhi.common.exception.ErrorCode;
1920
import xiaozhi.common.exception.RenException;
2021
import xiaozhi.common.page.TokenDTO;
@@ -121,7 +122,7 @@ public Result<?> changePassword(@RequestBody PasswordDTO passwordDTO) {
121122
@Operation(summary = "公共配置")
122123
public Result<Map<String, Object>> pubConfig() {
123124
Map<String, Object> config = new HashMap<>();
124-
config.put("version", "0.3.6");
125+
config.put("version", Constant.VERSION);
125126
config.put("allowUserRegister", sysUserService.getAllowUserRegister());
126127
return new Result<Map<String, Object>>().ok(config);
127128
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- 调整意图识别配置
2+
delete from `ai_model_config` where id = 'Intent_function_call';
3+
INSERT INTO `ai_model_config` VALUES ('Intent_function_call', 'Intent', 'function_call', '函数调用意图识别', 0, 1, '{\"type\": \"function_call\", \"functions\": \"change_role;get_weather;get_news;play_music\"}', NULL, NULL, 3, NULL, NULL, NULL, NULL);
4+
5+
-- 增加单台设备每天最多聊天句数
6+
delete from `sys_params` where id = 105;
7+
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES (105, 'device_max_output_size', '0', 'number', 1, '单台设备每天最多输出字数,0表示不限制');

main/manager-api/src/main/resources/db/changelog/db.changelog-master.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ databaseChangeLog:
5959
encoding: utf8
6060
path: classpath:db/changelog/202504151206.sql
6161
- changeSet:
62-
id: 202504181534
62+
id: 202504181536
6363
author: John
6464
changes:
6565
- sqlFile:
6666
encoding: utf8
67-
path: classpath:db/changelog/202504181534.sql
67+
path: classpath:db/changelog/202504181536.sql

main/xiaozhi-server/core/connection.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from core.mcp.manager import MCPManager
3030
from config.config_loader import get_private_config_from_api
3131
from config.manage_api_client import DeviceNotFoundException, DeviceBindException
32+
from core.utils.output_counter import add_device_output
3233

3334
TAG = __name__
3435

@@ -57,6 +58,7 @@ def __init__(
5758
self.session_id = None
5859
self.prompt = None
5960
self.welcome_msg = None
61+
self.max_output_size = 0
6062

6163
# 客户端状态相关
6264
self.client_abort = False
@@ -315,7 +317,6 @@ def _initialize_models(self, private_config):
315317
self.config["selected_module"]["LLM"] = private_config["selected_module"][
316318
"LLM"
317319
]
318-
319320
if private_config.get("Memory", None) is not None:
320321
init_memory = True
321322
self.config["Memory"] = private_config["Memory"]
@@ -328,6 +329,8 @@ def _initialize_models(self, private_config):
328329
self.config["selected_module"]["Intent"] = private_config[
329330
"selected_module"
330331
]["Intent"]
332+
if private_config.get("device_max_output_size", None) is not None:
333+
self.max_output_size = int(private_config["device_max_output_size"])
331334
try:
332335
modules = initialize_modules(
333336
self.logger,
@@ -845,6 +848,8 @@ def speak_and_play(self, text, text_index=0):
845848
self.logger.bind(tag=TAG).error(f"tts转换失败,{text}")
846849
return None, text, text_index
847850
self.logger.bind(tag=TAG).debug(f"TTS 文件生成完毕: {tts_file}")
851+
if self.max_output_size > 0:
852+
add_device_output(self.headers.get("device-id"), len(text))
848853
return tts_file, text, text_index
849854

850855
def clearSpeakStatus(self):

main/xiaozhi-server/core/handle/receiveAudioHandle.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
from config.logger import setup_logging
22
import time
3-
import asyncio
43
from core.utils.util import remove_punctuation_and_length
54
from core.handle.sendAudioHandle import send_stt_message
65
from core.handle.intentHandler import handle_user_intent
6+
from core.utils.output_counter import check_device_output_limit
77

88
TAG = __name__
99
logger = setup_logging()
@@ -51,6 +51,15 @@ async def startToChat(conn, text):
5151
if conn.need_bind:
5252
await check_bind_device(conn)
5353
return
54+
55+
# 如果当日的输出字数大于限定的字数
56+
if conn.max_output_size > 0:
57+
if check_device_output_limit(
58+
conn.headers.get("device-id"), conn.max_output_size
59+
):
60+
await max_out_size(conn)
61+
return
62+
5463
# 首先进行意图分析
5564
intent_handled = await handle_user_intent(conn, text)
5665

@@ -89,6 +98,18 @@ async def no_voice_close_connect(conn):
8998
await startToChat(conn, prompt)
9099

91100

101+
async def max_out_size(conn):
102+
text = "不好意思,我现在有点事情要忙,明天这个时候我们再聊,约好了哦!明天不见不散,拜拜!"
103+
await send_stt_message(conn, text)
104+
conn.tts_first_text_index = 0
105+
conn.tts_last_text_index = 0
106+
conn.llm_finish_task = True
107+
file_path = "config/assets/max_output_size.wav"
108+
opus_packets, _ = conn.tts.audio_to_opus_data(file_path)
109+
conn.audio_play_queue.put((opus_packets, text, 0))
110+
conn.close_after_chat = True
111+
112+
92113
async def check_bind_device(conn):
93114
if conn.bind_code:
94115
# 确保bind_code是6位数字
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import datetime
2+
from typing import Dict, Tuple
3+
4+
# 全局字典,用于存储每个设备的每日输出字数
5+
_device_daily_output: Dict[Tuple[str, datetime.date], int] = {}
6+
# 记录最后一次检查的日期
7+
_last_check_date: datetime.date = None
8+
9+
10+
def reset_device_output():
11+
"""
12+
重置所有设备的每日输出字数
13+
每天0点调用此函数
14+
"""
15+
_device_daily_output.clear()
16+
17+
18+
def get_device_output(device_id: str) -> int:
19+
"""
20+
获取设备当日的输出字数
21+
"""
22+
current_date = datetime.datetime.now().date()
23+
return _device_daily_output.get((device_id, current_date), 0)
24+
25+
26+
def add_device_output(device_id: str, char_count: int):
27+
"""
28+
增加设备的输出字数
29+
"""
30+
current_date = datetime.datetime.now().date()
31+
global _last_check_date
32+
33+
# 如果是第一次调用或者日期发生变化,清空计数器
34+
if _last_check_date is None or _last_check_date != current_date:
35+
_device_daily_output.clear()
36+
_last_check_date = current_date
37+
38+
current_count = _device_daily_output.get((device_id, current_date), 0)
39+
_device_daily_output[(device_id, current_date)] = current_count + char_count
40+
41+
42+
def check_device_output_limit(device_id: str, max_output_size: int) -> bool:
43+
"""
44+
检查设备是否超过输出限制
45+
:return: True 如果超过限制,False 如果未超过
46+
"""
47+
if not device_id:
48+
return False
49+
current_output = get_device_output(device_id)
50+
return current_output >= max_output_size

0 commit comments

Comments
 (0)