mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-22 07:03:53 +08:00
@@ -141,6 +141,11 @@ public interface Constant {
|
||||
*/
|
||||
String SERVER_MQTT_SECRET = "server.mqtt_signature_key";
|
||||
|
||||
/**
|
||||
* WebSocket认证开关
|
||||
*/
|
||||
String SERVER_AUTH_ENABLED = "server.auth.enabled";
|
||||
|
||||
/**
|
||||
* 无记忆
|
||||
*/
|
||||
|
||||
+54
-3
@@ -1,6 +1,8 @@
|
||||
package xiaozhi.modules.device.service.impl;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.time.Instant;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
@@ -169,7 +171,22 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
||||
DeviceReportRespDTO.Websocket websocket = new DeviceReportRespDTO.Websocket();
|
||||
// 从系统参数获取WebSocket URL,如果未配置则使用默认值
|
||||
String wsUrl = sysParamsService.getValue(Constant.SERVER_WEBSOCKET, true);
|
||||
websocket.setToken("");
|
||||
|
||||
// 检查是否启用认证并生成token
|
||||
String authEnabled = sysParamsService.getValue(Constant.SERVER_AUTH_ENABLED, true);
|
||||
if ("true".equalsIgnoreCase(authEnabled)) {
|
||||
try {
|
||||
// 生成token
|
||||
String token = generateWebSocketToken(clientId, macAddress);
|
||||
websocket.setToken(token);
|
||||
} catch (Exception e) {
|
||||
log.error("生成WebSocket token失败: {}", e.getMessage());
|
||||
websocket.setToken("");
|
||||
}
|
||||
} else {
|
||||
websocket.setToken("");
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(wsUrl) || wsUrl.equals("null")) {
|
||||
log.error("WebSocket地址未配置,请登录智控台,在参数管理找到【server.websocket】配置");
|
||||
wsUrl = "ws://xiaozhi.server.com:8000/xiaozhi/v1/";
|
||||
@@ -189,7 +206,7 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
||||
|
||||
// 添加MQTT UDP配置
|
||||
// 从系统参数获取MQTT Gateway地址,仅在配置有效时使用
|
||||
String mqttUdpConfig = sysParamsService.getValue(Constant.SERVER_MQTT_GATEWAY, false);
|
||||
String mqttUdpConfig = sysParamsService.getValue(Constant.SERVER_MQTT_GATEWAY, true);
|
||||
if (mqttUdpConfig != null && !mqttUdpConfig.equals("null") && !mqttUdpConfig.isEmpty()) {
|
||||
try {
|
||||
String groupId = deviceById != null && deviceById.getBoard() != null ? deviceById.getBoard()
|
||||
@@ -494,6 +511,40 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
||||
return Base64.getEncoder().encodeToString(signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成WebSocket认证token 遵循Python端AuthManager的实现逻辑:token = signature.timestamp
|
||||
*
|
||||
* @param clientId 客户端ID
|
||||
* @param username 用户名 (通常为deviceId/macAddress)
|
||||
* @return 认证token字符串
|
||||
*/
|
||||
private String generateWebSocketToken(String clientId, String username)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException {
|
||||
// 从系统参数获取密钥
|
||||
String secretKey = sysParamsService.getValue(Constant.SERVER_SECRET, false);
|
||||
if (StringUtils.isBlank(secretKey)) {
|
||||
throw new IllegalStateException("WebSocket认证密钥未配置(server.secret)");
|
||||
}
|
||||
|
||||
// 获取当前时间戳(秒)
|
||||
long timestamp = System.currentTimeMillis() / 1000;
|
||||
|
||||
// 构建签名内容: clientId|username|timestamp
|
||||
String content = String.format("%s|%s|%d", clientId, username, timestamp);
|
||||
|
||||
// 生成HMAC-SHA256签名
|
||||
Mac hmac = Mac.getInstance("HmacSHA256");
|
||||
SecretKeySpec keySpec = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
|
||||
hmac.init(keySpec);
|
||||
byte[] signature = hmac.doFinal(content.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// Base64 URL-safe编码签名(去除填充符=)
|
||||
String signatureBase64 = Base64.getUrlEncoder().withoutPadding().encodeToString(signature);
|
||||
|
||||
// 返回格式: signature.timestamp
|
||||
return String.format("%s.%d", signatureBase64, timestamp);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建MQTT配置信息
|
||||
*
|
||||
@@ -504,7 +555,7 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
||||
private DeviceReportRespDTO.MQTT buildMqttConfig(String macAddress, String groupId)
|
||||
throws Exception {
|
||||
// 从环境变量或系统参数获取签名密钥
|
||||
String signatureKey = sysParamsService.getValue("server.mqtt_signature_key", false);
|
||||
String signatureKey = sysParamsService.getValue("server.mqtt_signature_key", true);
|
||||
if (StringUtils.isBlank(signatureKey)) {
|
||||
log.warn("缺少MQTT_SIGNATURE_KEY,跳过MQTT配置生成");
|
||||
return null;
|
||||
|
||||
+4
-4
@@ -6,6 +6,7 @@ import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.PutMapping;
|
||||
@@ -23,7 +24,9 @@ import xiaozhi.common.exception.ErrorCode;
|
||||
import xiaozhi.common.exception.RenException;
|
||||
import xiaozhi.common.page.TokenDTO;
|
||||
import xiaozhi.common.user.UserDetail;
|
||||
import xiaozhi.common.utils.JsonUtils;
|
||||
import xiaozhi.common.utils.Result;
|
||||
import xiaozhi.common.utils.Sm2DecryptUtil;
|
||||
import xiaozhi.common.validator.AssertUtils;
|
||||
import xiaozhi.common.validator.ValidatorUtils;
|
||||
import xiaozhi.modules.security.dto.LoginDTO;
|
||||
@@ -32,8 +35,6 @@ import xiaozhi.modules.security.password.PasswordUtils;
|
||||
import xiaozhi.modules.security.service.CaptchaService;
|
||||
import xiaozhi.modules.security.service.SysUserTokenService;
|
||||
import xiaozhi.modules.security.user.SecurityUser;
|
||||
import xiaozhi.common.utils.Sm2DecryptUtil;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import xiaozhi.modules.sys.dto.PasswordDTO;
|
||||
import xiaozhi.modules.sys.dto.RetrievePasswordDTO;
|
||||
import xiaozhi.modules.sys.dto.SysUserDTO;
|
||||
@@ -41,7 +42,6 @@ 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;
|
||||
|
||||
/**
|
||||
* 登录控制层
|
||||
@@ -237,7 +237,7 @@ public class LoginController {
|
||||
config.put("sm2PublicKey", publicKey);
|
||||
|
||||
// 获取system-web.menu参数配置
|
||||
String menuConfig = sysParamsService.getValue("system-web.menu", false);
|
||||
String menuConfig = sysParamsService.getValue("system-web.menu", true);
|
||||
if (StringUtils.isNotBlank(menuConfig)) {
|
||||
config.put("systemWebMenu", JsonUtils.parseObject(menuConfig, Object.class));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
-- 删除server模块是否开启token认证参数
|
||||
delete from `sys_params` where param_code = 'server.auth.enabled';
|
||||
|
||||
-- 添加server模块是否开启token认证参数
|
||||
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES
|
||||
(122, 'server.auth.enabled', 'true', 'boolean', 1, 'server模块是否开启token认证');
|
||||
@@ -438,3 +438,10 @@ databaseChangeLog:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202512041515.sql
|
||||
- changeSet:
|
||||
id: 202512131453
|
||||
author: hrz
|
||||
changes:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202512131453.sql
|
||||
|
||||
@@ -713,7 +713,7 @@ export default {
|
||||
'paramManagement.deleteFailed': 'Löschen fehlgeschlagen, bitte versuchen Sie es erneut',
|
||||
'paramManagement.operationCancelled': 'Löschen abgebrochen',
|
||||
'paramManagement.operationClosed': 'Operation geschlossen',
|
||||
'paramManagement.updateSuccess': 'Aktualisierung erfolgreich',
|
||||
'paramManagement.updateSuccess': 'Aktualisierung erfolgreich. Einige Konfigurationen werden erst nach Neustart des xiaozhi-server-Moduls wirksam.',
|
||||
'paramManagement.addSuccess': 'Hinzufügen erfolgreich',
|
||||
'paramManagement.updateFailed': 'Aktualisierung fehlgeschlagen',
|
||||
'paramManagement.addFailed': 'Hinzufügen fehlgeschlagen',
|
||||
@@ -852,7 +852,7 @@ export default {
|
||||
'modelConfig.enableSuccess': 'Aktivieren erfolgreich',
|
||||
'modelConfig.disableSuccess': 'Deaktivieren erfolgreich',
|
||||
'modelConfig.operationFailed': 'Operation fehlgeschlagen',
|
||||
'modelConfig.setDefaultSuccess': 'Standardmodell erfolgreich gesetzt',
|
||||
'modelConfig.setDefaultSuccess': 'Standardmodell erfolgreich gesetzt, bitte starten Sie das xiaozhi-server-Modul zeitnah manuell neu',
|
||||
'modelConfig.itemsPerPage': '{items} Einträge/Seite',
|
||||
'modelConfig.firstPage': 'Erste Seite',
|
||||
'modelConfig.prevPage': 'Vorherige Seite',
|
||||
|
||||
@@ -713,7 +713,7 @@ export default {
|
||||
'paramManagement.deleteFailed': 'Deletion failed, please try again',
|
||||
'paramManagement.operationCancelled': 'Deletion cancelled',
|
||||
'paramManagement.operationClosed': 'Operation closed',
|
||||
'paramManagement.updateSuccess': 'Update successful',
|
||||
'paramManagement.updateSuccess': 'Update successful. Some configurations will take effect only after restarting the xiaozhi-server module.',
|
||||
'paramManagement.addSuccess': 'Add successful',
|
||||
'paramManagement.updateFailed': 'Update failed',
|
||||
'paramManagement.addFailed': 'Add failed',
|
||||
@@ -852,7 +852,7 @@ export default {
|
||||
'modelConfig.enableSuccess': 'Enable successful',
|
||||
'modelConfig.disableSuccess': 'Disable successful',
|
||||
'modelConfig.operationFailed': 'Operation failed',
|
||||
'modelConfig.setDefaultSuccess': 'Set default model successful',
|
||||
'modelConfig.setDefaultSuccess': 'Set default model successful, please restart the xiaozhi-server module manually in time',
|
||||
'modelConfig.itemsPerPage': '{items} items/page',
|
||||
'modelConfig.firstPage': 'First Page',
|
||||
'modelConfig.prevPage': 'Previous Page',
|
||||
|
||||
@@ -713,7 +713,7 @@ export default {
|
||||
'paramManagement.deleteFailed': 'Xóa thất bại, vui lòng thử lại',
|
||||
'paramManagement.operationCancelled': 'Đã hủy xóa',
|
||||
'paramManagement.operationClosed': 'Đã đóng thao tác',
|
||||
'paramManagement.updateSuccess': 'Cập nhật thành công',
|
||||
'paramManagement.updateSuccess': 'Cập nhật thành công. Một số cấu hình chỉ có hiệu lực sau khi khởi động lại mô-đun xiaozhi-server.',
|
||||
'paramManagement.addSuccess': 'Thêm thành công',
|
||||
'paramManagement.updateFailed': 'Cập nhật thất bại',
|
||||
'paramManagement.addFailed': 'Thêm thất bại',
|
||||
@@ -852,7 +852,7 @@ export default {
|
||||
'modelConfig.enableSuccess': 'Bật thành công',
|
||||
'modelConfig.disableSuccess': 'Tắt thành công',
|
||||
'modelConfig.operationFailed': 'Thao tác thất bại',
|
||||
'modelConfig.setDefaultSuccess': 'Đặt mô hình mặc định thành công',
|
||||
'modelConfig.setDefaultSuccess': 'Đặt mô hình mặc định thành công, vui lòng khởi động lại module xiaozhi-server thủ công kịp thời',
|
||||
'modelConfig.itemsPerPage': '{items} mục/trang',
|
||||
'modelConfig.firstPage': 'Trang đầu',
|
||||
'modelConfig.prevPage': 'Trang trước',
|
||||
|
||||
@@ -713,7 +713,7 @@ export default {
|
||||
'paramManagement.deleteFailed': '删除失败,请重试',
|
||||
'paramManagement.operationCancelled': '已取消删除操作',
|
||||
'paramManagement.operationClosed': '操作已关闭',
|
||||
'paramManagement.updateSuccess': '修改成功',
|
||||
'paramManagement.updateSuccess': '修改成功,部分配置需重启xiaozhi-server模块才生效',
|
||||
'paramManagement.addSuccess': '新增成功',
|
||||
'paramManagement.updateFailed': '更新失败',
|
||||
'paramManagement.addFailed': '新增失败',
|
||||
@@ -852,7 +852,7 @@ export default {
|
||||
'modelConfig.enableSuccess': '启用成功',
|
||||
'modelConfig.disableSuccess': '禁用成功',
|
||||
'modelConfig.operationFailed': '操作失败',
|
||||
'modelConfig.setDefaultSuccess': '设置默认模型成功',
|
||||
'modelConfig.setDefaultSuccess': '设置默认模型成功,请及时手动重启xiaozhi-server模块',
|
||||
'modelConfig.itemsPerPage': '{items}条/页',
|
||||
'modelConfig.firstPage': '首页',
|
||||
'modelConfig.prevPage': '上一页',
|
||||
|
||||
@@ -713,7 +713,7 @@ export default {
|
||||
'paramManagement.deleteFailed': '刪除失敗,請重試',
|
||||
'paramManagement.operationCancelled': '已取消刪除操作',
|
||||
'paramManagement.operationClosed': '操作已關閉',
|
||||
'paramManagement.updateSuccess': '修改成功',
|
||||
'paramManagement.updateSuccess': '修改成功,部分配置需重啟xiaozhi-server模組才生效',
|
||||
'paramManagement.addSuccess': '新增成功',
|
||||
'paramManagement.updateFailed': '更新失敗',
|
||||
'paramManagement.addFailed': '新增失敗',
|
||||
@@ -852,7 +852,7 @@ export default {
|
||||
'modelConfig.enableSuccess': '啟用成功',
|
||||
'modelConfig.disableSuccess': '禁用成功',
|
||||
'modelConfig.operationFailed': '操作失敗',
|
||||
'modelConfig.setDefaultSuccess': '設置默認模型成功',
|
||||
'modelConfig.setDefaultSuccess': '設置默認模型成功,請及時手動重啟xiaozhi-server模組',
|
||||
'modelConfig.itemsPerPage': '{items}條/頁',
|
||||
'modelConfig.firstPage': '首頁',
|
||||
'modelConfig.prevPage': '上一頁',
|
||||
|
||||
@@ -68,6 +68,7 @@ async def get_config_from_api_async(config):
|
||||
"url": config["manager-api"].get("url", ""),
|
||||
"secret": config["manager-api"].get("secret", ""),
|
||||
}
|
||||
auth_enabled = config_data.get("server", {}).get("auth", {}).get("enabled", False)
|
||||
# server的配置以本地为准
|
||||
if config.get("server"):
|
||||
config_data["server"] = {
|
||||
@@ -77,6 +78,7 @@ async def get_config_from_api_async(config):
|
||||
"vision_explain": config["server"].get("vision_explain", ""),
|
||||
"auth_key": config["server"].get("auth_key", ""),
|
||||
}
|
||||
config_data["server"]["auth"] = {"enabled": auth_enabled}
|
||||
# 如果服务器没有prompt_template,则从本地配置读取
|
||||
if not config_data.get("prompt_template"):
|
||||
config_data["prompt_template"] = config.get("prompt_template")
|
||||
|
||||
Reference in New Issue
Block a user