mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-22 15:13:55 +08:00
Merge pull request #3196 from xinnan-tech/py-device-calling
Py device calling
This commit is contained in:
@@ -146,6 +146,11 @@ public interface Constant {
|
||||
*/
|
||||
String SERVER_AUTH_ENABLED = "server.auth.enabled";
|
||||
|
||||
/**
|
||||
* 系统功能菜单配置
|
||||
*/
|
||||
String SYSTEM_WEB_MENU = "system-web.menu";
|
||||
|
||||
/**
|
||||
* 无记忆
|
||||
*/
|
||||
|
||||
@@ -188,4 +188,11 @@ public class RedisKeys {
|
||||
return "ota:upload:count:" + username;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设备通讯录缓存Key
|
||||
*/
|
||||
public static String getAddressBookKey() {
|
||||
return "device:address_book:all";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+9
-2
@@ -14,7 +14,7 @@ public interface AgentPluginMappingService extends IService<AgentPluginMapping>
|
||||
|
||||
/**
|
||||
* 根据智能体id获取插件参数
|
||||
*
|
||||
*
|
||||
* @param agentId
|
||||
* @return
|
||||
*/
|
||||
@@ -22,8 +22,15 @@ public interface AgentPluginMappingService extends IService<AgentPluginMapping>
|
||||
|
||||
/**
|
||||
* 根据智能体id删除插件参数
|
||||
*
|
||||
*
|
||||
* @param agentId
|
||||
*/
|
||||
void deleteByAgentId(String agentId);
|
||||
|
||||
/**
|
||||
* 根据插件ID删除所有智能体的插件映射
|
||||
*
|
||||
* @param pluginId 插件ID
|
||||
*/
|
||||
void deleteByPluginId(String pluginId);
|
||||
}
|
||||
|
||||
+7
@@ -104,4 +104,11 @@ public class AgentPluginMappingServiceImpl extends ServiceImpl<AgentPluginMappin
|
||||
agentPluginMappingMapper.delete(updateWrapper);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteByPluginId(String pluginId) {
|
||||
UpdateWrapper<AgentPluginMapping> updateWrapper = new UpdateWrapper<>();
|
||||
updateWrapper.eq("plugin_id", pluginId);
|
||||
agentPluginMappingMapper.delete(updateWrapper);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import xiaozhi.common.constant.Constant;
|
||||
import xiaozhi.common.redis.RedisKeys;
|
||||
import xiaozhi.common.redis.RedisUtils;
|
||||
import xiaozhi.modules.config.service.ConfigService;
|
||||
import xiaozhi.modules.device.service.DeviceAddressBookService;
|
||||
import xiaozhi.modules.sys.service.SysParamsService;
|
||||
|
||||
@Configuration
|
||||
@@ -24,6 +25,9 @@ public class SystemInitConfig {
|
||||
@Autowired
|
||||
private RedisUtils redisUtils;
|
||||
|
||||
@Autowired
|
||||
private DeviceAddressBookService deviceAddressBookService;
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
// 检查版本号
|
||||
@@ -37,5 +41,8 @@ public class SystemInitConfig {
|
||||
|
||||
sysParamsService.initServerSecret();
|
||||
configService.getConfig(false);
|
||||
|
||||
// 初始化设备通讯录缓存
|
||||
deviceAddressBookService.refreshCache();
|
||||
}
|
||||
}
|
||||
+46
-1
@@ -22,12 +22,15 @@ import xiaozhi.common.redis.RedisKeys;
|
||||
import xiaozhi.common.redis.RedisUtils;
|
||||
import xiaozhi.common.user.UserDetail;
|
||||
import xiaozhi.common.utils.Result;
|
||||
import xiaozhi.modules.device.dto.DeviceAddressBookAliasDTO;
|
||||
import xiaozhi.modules.device.dto.DeviceAddressBookPermissionDTO;
|
||||
import xiaozhi.modules.device.dto.DeviceManualAddDTO;
|
||||
import xiaozhi.modules.device.dto.DeviceRegisterDTO;
|
||||
import xiaozhi.modules.device.dto.DeviceToolsCallReqDTO;
|
||||
import xiaozhi.modules.device.dto.DeviceUnBindDTO;
|
||||
import xiaozhi.modules.device.dto.DeviceUpdateDTO;
|
||||
import xiaozhi.modules.device.entity.DeviceEntity;
|
||||
import xiaozhi.modules.device.service.DeviceAddressBookService;
|
||||
import xiaozhi.modules.device.service.DeviceService;
|
||||
import xiaozhi.modules.device.vo.UserShowDeviceListVO;
|
||||
import xiaozhi.modules.security.user.SecurityUser;
|
||||
@@ -38,11 +41,13 @@ import xiaozhi.modules.sys.service.SysParamsService;
|
||||
@RequestMapping("/device")
|
||||
public class DeviceController {
|
||||
private final DeviceService deviceService;
|
||||
private final DeviceAddressBookService deviceAddressBookService;
|
||||
private final RedisUtils redisUtils;
|
||||
private final SysParamsService sysParamsService;
|
||||
|
||||
public DeviceController(DeviceService deviceService, RedisUtils redisUtils, SysParamsService sysParamsService) {
|
||||
public DeviceController(DeviceService deviceService, DeviceAddressBookService deviceAddressBookService, RedisUtils redisUtils, SysParamsService sysParamsService) {
|
||||
this.deviceService = deviceService;
|
||||
this.deviceAddressBookService = deviceAddressBookService;
|
||||
this.redisUtils = redisUtils;
|
||||
this.sysParamsService = sysParamsService;
|
||||
}
|
||||
@@ -160,4 +165,44 @@ public class DeviceController {
|
||||
response.setMsg("Tools called successfully");
|
||||
return response.ok(result);
|
||||
}
|
||||
|
||||
@GetMapping("/address-book/{macAddress}")
|
||||
@Operation(summary = "获取设备通讯录")
|
||||
@RequiresPermissions("sys:role:normal")
|
||||
public Result<Object> getAddressBook(@PathVariable String macAddress) {
|
||||
return new Result<Object>().ok(deviceAddressBookService.getAddressBookList(macAddress));
|
||||
}
|
||||
|
||||
@GetMapping("/address-book/lookup")
|
||||
@Operation(summary = "根据昵称查找目标设备")
|
||||
public Result<Map<String, String>> lookupByNickname(String callerMac, String nickname) {
|
||||
Map<String, String> result = deviceAddressBookService.lookupByNickname(callerMac, nickname);
|
||||
if (result == null) {
|
||||
return new Result<Map<String, String>>().error("未找到对应设备");
|
||||
}
|
||||
return new Result<Map<String, String>>().ok(result);
|
||||
}
|
||||
|
||||
@PutMapping("/address-book/alias")
|
||||
@Operation(summary = "更新设备通讯录别名")
|
||||
@RequiresPermissions("sys:role:normal")
|
||||
public Result<Void> updateAlias(@Valid @RequestBody DeviceAddressBookAliasDTO dto) {
|
||||
deviceAddressBookService.saveOrUpdate(dto.getMacAddress(), dto.getTargetMac(), dto.getAlias(), null);
|
||||
return new Result<Void>();
|
||||
}
|
||||
|
||||
@PutMapping("/address-book/permission")
|
||||
@Operation(summary = "更新设备通讯录权限")
|
||||
@RequiresPermissions("sys:role:normal")
|
||||
public Result<Void> updatePermission(@Valid @RequestBody DeviceAddressBookPermissionDTO dto) {
|
||||
deviceAddressBookService.saveOrUpdate(dto.getMacAddress(), dto.getTargetMac(), null, dto.getHasPermission());
|
||||
return new Result<Void>();
|
||||
}
|
||||
|
||||
@GetMapping("/call/forward")
|
||||
@Operation(summary = "转发呼叫请求到网关")
|
||||
public Result<Map<String, Object>> forwardCallRequest(String callerMac, String targetMac, String callerNickname) {
|
||||
Map<String, Object> result = deviceService.forwardCallRequest(callerMac, targetMac, callerNickname);
|
||||
return new Result<Map<String, Object>>().ok(result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package xiaozhi.modules.device.dao;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
|
||||
import xiaozhi.modules.device.entity.DeviceAddressBookEntity;
|
||||
|
||||
@Mapper
|
||||
public interface DeviceAddressBookDao extends BaseMapper<DeviceAddressBookEntity> {
|
||||
|
||||
/**
|
||||
* 获取设备通讯录列表
|
||||
*/
|
||||
List<DeviceAddressBookEntity> getAddressBookList(@Param("macAddress") String macAddress);
|
||||
|
||||
/**
|
||||
* 更新别名
|
||||
*/
|
||||
void updateAlias(@Param("macAddress") String macAddress, @Param("targetMac") String targetMac, @Param("alias") String alias);
|
||||
|
||||
/**
|
||||
* 更新权限
|
||||
*/
|
||||
void updatePermission(@Param("macAddress") String macAddress, @Param("targetMac") String targetMac, @Param("hasPermission") Boolean hasPermission);
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package xiaozhi.modules.device.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "更新设备通讯录别名")
|
||||
public class DeviceAddressBookAliasDTO {
|
||||
|
||||
@NotBlank(message = "MAC地址不能为空")
|
||||
@Schema(description = "本设备MAC地址")
|
||||
private String macAddress;
|
||||
|
||||
@NotBlank(message = "目标MAC地址不能为空")
|
||||
@Schema(description = "对方设备MAC地址")
|
||||
private String targetMac;
|
||||
|
||||
@Schema(description = "我对对方的称呼")
|
||||
private String alias;
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package xiaozhi.modules.device.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@Schema(description = "更新设备通讯录权限")
|
||||
public class DeviceAddressBookPermissionDTO {
|
||||
|
||||
@NotBlank(message = "MAC地址不能为空")
|
||||
@Schema(description = "本设备MAC地址")
|
||||
private String macAddress;
|
||||
|
||||
@NotBlank(message = "目标MAC地址不能为空")
|
||||
@Schema(description = "对方设备MAC地址")
|
||||
private String targetMac;
|
||||
|
||||
@Schema(description = "是否有权限呼叫")
|
||||
private Boolean hasPermission;
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
package xiaozhi.modules.device.entity;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldFill;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@TableName("ai_device_address_book")
|
||||
@Schema(description = "设备通讯录")
|
||||
public class DeviceAddressBookEntity {
|
||||
|
||||
@TableId(type = IdType.INPUT)
|
||||
@Schema(description = "本设备MAC地址")
|
||||
private String macAddress;
|
||||
|
||||
@Schema(description = "对方设备MAC地址")
|
||||
private String targetMac;
|
||||
|
||||
@Schema(description = "我对对方的称呼")
|
||||
private String alias;
|
||||
|
||||
@Schema(description = "是否有权限呼叫")
|
||||
private Boolean hasPermission;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
@Schema(description = "创建人")
|
||||
private Long creator;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
@Schema(description = "创建时间")
|
||||
private Date createDate;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
@Schema(description = "更新人")
|
||||
private Long updater;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT_UPDATE)
|
||||
@Schema(description = "更新时间")
|
||||
private Date updateDate;
|
||||
}
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
package xiaozhi.modules.device.service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import xiaozhi.modules.device.entity.DeviceAddressBookEntity;
|
||||
|
||||
public interface DeviceAddressBookService {
|
||||
|
||||
/**
|
||||
* 获取设备通讯录列表
|
||||
*/
|
||||
List<DeviceAddressBookEntity> getAddressBookList(String macAddress);
|
||||
|
||||
/**
|
||||
* 获取所有设备的通讯录(全局缓存用)
|
||||
*/
|
||||
Map<String, Map<String, String>> getAllAddressBooks();
|
||||
|
||||
/**
|
||||
* 更新别名
|
||||
*/
|
||||
void updateAlias(String macAddress, String targetMac, String alias);
|
||||
|
||||
/**
|
||||
* 更新权限
|
||||
*/
|
||||
void updatePermission(String macAddress, String targetMac, Boolean hasPermission);
|
||||
|
||||
/**
|
||||
* 添加或更新通讯录记录
|
||||
*/
|
||||
void saveOrUpdate(String macAddress, String targetMac, String alias, Boolean hasPermission);
|
||||
|
||||
/**
|
||||
* 刷新通讯录缓存
|
||||
*/
|
||||
void refreshCache();
|
||||
|
||||
/**
|
||||
* 根据昵称查找目标设备信息
|
||||
* @param callerMac 主叫方MAC地址
|
||||
* @param nickname 被叫方昵称
|
||||
* @return {targetMac: 目标MAC, callerNickname: 目标如何称呼主叫方}
|
||||
*/
|
||||
Map<String, String> lookupByNickname(String callerMac, String nickname);
|
||||
}
|
||||
@@ -138,4 +138,10 @@ public interface DeviceService extends BaseService<DeviceEntity> {
|
||||
*/
|
||||
Object callDeviceTool(String deviceId, String toolName, Map<String, Object> arguments);
|
||||
|
||||
/**
|
||||
* 转发呼叫请求到网关
|
||||
* @return 网关响应 {status, message}
|
||||
*/
|
||||
Map<String, Object> forwardCallRequest(String callerMac, String targetMac, String callerNickname);
|
||||
|
||||
}
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
package xiaozhi.modules.device.service.impl;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
|
||||
import xiaozhi.common.redis.RedisKeys;
|
||||
import xiaozhi.common.redis.RedisUtils;
|
||||
import xiaozhi.modules.device.dao.DeviceAddressBookDao;
|
||||
import xiaozhi.modules.device.entity.DeviceAddressBookEntity;
|
||||
import xiaozhi.modules.device.service.DeviceAddressBookService;
|
||||
|
||||
@Service
|
||||
public class DeviceAddressBookServiceImpl implements DeviceAddressBookService {
|
||||
|
||||
private final DeviceAddressBookDao deviceAddressBookDao;
|
||||
private final RedisUtils redisUtils;
|
||||
|
||||
public DeviceAddressBookServiceImpl(DeviceAddressBookDao deviceAddressBookDao, RedisUtils redisUtils) {
|
||||
this.deviceAddressBookDao = deviceAddressBookDao;
|
||||
this.redisUtils = redisUtils;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DeviceAddressBookEntity> getAddressBookList(String macAddress) {
|
||||
return deviceAddressBookDao.getAddressBookList(macAddress);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Map<String, Map<String, String>> getAllAddressBooks() {
|
||||
Object cached = redisUtils.get(RedisKeys.getAddressBookKey());
|
||||
if (cached != null) {
|
||||
return (Map<String, Map<String, String>>) cached;
|
||||
}
|
||||
refreshCache();
|
||||
return (Map<String, Map<String, String>>) redisUtils.get(RedisKeys.getAddressBookKey());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> lookupByNickname(String callerMac, String nickname) {
|
||||
Map<String, Map<String, String>> allBooks = getAllAddressBooks();
|
||||
Map<String, String> callerBook = allBooks.get(callerMac.toLowerCase());
|
||||
if (callerBook == null) {
|
||||
return null;
|
||||
}
|
||||
// 从缓存获取 targetMac,格式: "mac|permission"
|
||||
String targetMacWithPerm = callerBook.get(nickname);
|
||||
if (targetMacWithPerm == null) {
|
||||
return null;
|
||||
}
|
||||
// 解析 targetMac 和 permission
|
||||
String[] parts = targetMacWithPerm.split("\\|");
|
||||
String targetMac = parts[0];
|
||||
boolean hasPermission = parts.length > 1 && "1".equals(parts[1]);
|
||||
|
||||
Map<String, String> targetBook = allBooks.get(targetMac.toLowerCase());
|
||||
if (targetBook == null) {
|
||||
return null;
|
||||
}
|
||||
// 检查双向关系:目标设备是否也添加了主叫方为联系人
|
||||
String callerNickname = targetBook.get(callerMac.toLowerCase());
|
||||
Map<String, String> result = new HashMap<>();
|
||||
result.put("targetMac", targetMac);
|
||||
result.put("callerNickname", callerNickname);
|
||||
result.put("hasPermission", hasPermission ? "true" : "false");
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void refreshCache() {
|
||||
Map<String, Map<String, String>> result = new HashMap<>();
|
||||
List<DeviceAddressBookEntity> allRecords = deviceAddressBookDao.selectList(null);
|
||||
Map<String, String> reverseMap = new HashMap<>();
|
||||
Map<String, Boolean> permissionMap = new HashMap<>();
|
||||
for (DeviceAddressBookEntity entity : allRecords) {
|
||||
String macA = entity.getMacAddress().toLowerCase();
|
||||
String macB = entity.getTargetMac().toLowerCase();
|
||||
String alias = entity.getAlias();
|
||||
Boolean hasPermission = entity.getHasPermission();
|
||||
if (alias != null && !alias.isEmpty()) {
|
||||
// 反向记录: (macB, macA) = B对A的称呼
|
||||
reverseMap.put(macB + ":" + macA, alias);
|
||||
}
|
||||
// 记录 A 对 B 是否有权限呼叫
|
||||
permissionMap.put(macA + ":" + macB, hasPermission != null && hasPermission);
|
||||
}
|
||||
for (DeviceAddressBookEntity entity : allRecords) {
|
||||
String macA = entity.getMacAddress().toLowerCase();
|
||||
String macB = entity.getTargetMac().toLowerCase();
|
||||
String aliasAtoB = entity.getAlias();
|
||||
result.computeIfAbsent(macA, k -> new HashMap<>());
|
||||
result.computeIfAbsent(macB, k -> new HashMap<>());
|
||||
if (aliasAtoB != null && !aliasAtoB.isEmpty()) {
|
||||
// A对B的映射: nickname -> macB|permission
|
||||
Boolean perm = entity.getHasPermission();
|
||||
String permStr = (perm != null && perm) ? "1" : "0";
|
||||
result.get(macA).put(aliasAtoB, macB + "|" + permStr);
|
||||
}
|
||||
// B对A的映射: macA -> nickname(查反向记录)
|
||||
String aliasBtoA = reverseMap.get(macA + ":" + macB);
|
||||
if (aliasBtoA != null && !aliasBtoA.isEmpty()) {
|
||||
result.get(macB).put(macA, aliasBtoA);
|
||||
}
|
||||
}
|
||||
redisUtils.set(RedisKeys.getAddressBookKey(), result);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateAlias(String macAddress, String targetMac, String alias) {
|
||||
deviceAddressBookDao.updateAlias(macAddress, targetMac, alias);
|
||||
refreshCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updatePermission(String macAddress, String targetMac, Boolean hasPermission) {
|
||||
deviceAddressBookDao.updatePermission(macAddress, targetMac, hasPermission);
|
||||
refreshCache();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveOrUpdate(String macAddress, String targetMac, String alias, Boolean hasPermission) {
|
||||
QueryWrapper<DeviceAddressBookEntity> wrapper = new QueryWrapper<>();
|
||||
wrapper.eq("mac_address", macAddress).eq("target_mac", targetMac);
|
||||
DeviceAddressBookEntity record = deviceAddressBookDao.selectOne(wrapper);
|
||||
if (record == null) {
|
||||
DeviceAddressBookEntity entity = new DeviceAddressBookEntity();
|
||||
entity.setMacAddress(macAddress);
|
||||
entity.setTargetMac(targetMac);
|
||||
entity.setAlias(alias);
|
||||
entity.setHasPermission(hasPermission);
|
||||
deviceAddressBookDao.insert(entity);
|
||||
} else {
|
||||
if (alias != null) {
|
||||
deviceAddressBookDao.updateAlias(macAddress, targetMac, alias);
|
||||
}
|
||||
if (hasPermission != null) {
|
||||
deviceAddressBookDao.updatePermission(macAddress, targetMac, hasPermission);
|
||||
}
|
||||
}
|
||||
refreshCache();
|
||||
}
|
||||
}
|
||||
+66
@@ -892,4 +892,70 @@ public class DeviceServiceImpl extends BaseServiceImpl<DeviceDao, DeviceEntity>
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> forwardCallRequest(String callerMac, String targetMac, String callerNickname) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("status", "error");
|
||||
result.put("message", "网关配置缺失");
|
||||
|
||||
// 从系统参数获取网关配置
|
||||
String mqttGatewayUrl = sysParamsService.getValue("server.mqtt_manager_api", true);
|
||||
String mqttSignatureKey = sysParamsService.getValue("server.mqtt_signature_key", true);
|
||||
|
||||
if (StringUtils.isBlank(mqttGatewayUrl) || "null".equals(mqttGatewayUrl)) {
|
||||
log.error("MQTT网关地址未配置");
|
||||
result.put("message", "MQTT网关地址未配置");
|
||||
return result;
|
||||
}
|
||||
if (StringUtils.isBlank(mqttSignatureKey) || "null".equals(mqttSignatureKey)) {
|
||||
log.error("MQTT签名密钥未配置");
|
||||
result.put("message", "MQTT签名密钥未配置");
|
||||
return result;
|
||||
}
|
||||
|
||||
// 生成token: SHA256(date + secret)
|
||||
String dateStr = new java.text.SimpleDateFormat("yyyy-MM-dd").format(new Date());
|
||||
try {
|
||||
java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-256");
|
||||
byte[] hash = md.digest((dateStr + mqttSignatureKey).getBytes(StandardCharsets.UTF_8));
|
||||
StringBuilder hexString = new StringBuilder();
|
||||
for (byte b : hash) {
|
||||
String hex = Integer.toHexString(0xff & b);
|
||||
if (hex.length() == 1) {
|
||||
hexString.append('0');
|
||||
}
|
||||
hexString.append(hex);
|
||||
}
|
||||
String token = hexString.toString();
|
||||
|
||||
// 构建请求体
|
||||
Map<String, Object> body = new HashMap<>();
|
||||
body.put("caller_mac", callerMac);
|
||||
body.put("target_mac", targetMac);
|
||||
body.put("caller_nickname", callerNickname);
|
||||
|
||||
// 发送请求并获取响应
|
||||
String url = "http://" + mqttGatewayUrl + "/api/call/request";
|
||||
String response = cn.hutool.http.HttpRequest.post(url)
|
||||
.header("Authorization", "Bearer " + token)
|
||||
.header("Content-Type", "application/json")
|
||||
.body(JSONUtil.toJsonStr(body))
|
||||
.timeout(5000)
|
||||
.execute()
|
||||
.body();
|
||||
|
||||
// 解析网关响应
|
||||
if (StringUtils.isNotBlank(response)) {
|
||||
Map<String, Object> gwResult = JSONUtil.parseObj(response);
|
||||
result.put("status", gwResult.get("status"));
|
||||
result.put("message", gwResult.get("message"));
|
||||
}
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
log.error("转发呼叫请求失败: {}", e.getMessage());
|
||||
result.put("message", "呼叫请求转发失败: " + e.getMessage());
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +87,8 @@ public class ShiroConfig {
|
||||
filterMap.put("/user/retrieve-password", "anon");
|
||||
// 将config路径使用server服务过滤器
|
||||
filterMap.put("/config/**", "server");
|
||||
filterMap.put("/device/address-book/lookup", "server");
|
||||
filterMap.put("/device/call/forward", "server");
|
||||
filterMap.put("/agent/chat-history/report", "server");
|
||||
filterMap.put("/agent/chat-history/download/**", "anon");
|
||||
filterMap.put("/agent/chat-summary/**", "server");
|
||||
|
||||
+6
-1
@@ -114,7 +114,12 @@ public class SysParamsController {
|
||||
// 校验mqtt密钥长度
|
||||
validateMqttSecretLength(dto.getParamCode(), dto.getParamValue());
|
||||
|
||||
sysParamsService.update(dto);
|
||||
// 如果是系统功能菜单配置,使用特殊处理
|
||||
if (Constant.SYSTEM_WEB_MENU.equals(dto.getParamCode())) {
|
||||
sysParamsService.updateSystemWebMenu(dto.getParamValue());
|
||||
} else {
|
||||
sysParamsService.update(dto);
|
||||
}
|
||||
configService.getConfig(false);
|
||||
return new Result<Void>();
|
||||
}
|
||||
|
||||
@@ -53,4 +53,19 @@ public interface SysParamsService extends BaseService<SysParamsEntity> {
|
||||
* 初始化服务器密钥
|
||||
*/
|
||||
void initServerSecret();
|
||||
|
||||
/**
|
||||
* 获取系统功能菜单配置
|
||||
*
|
||||
* @param fromCache 是否从缓存获取
|
||||
* @return 系统功能菜单配置JSON字符串
|
||||
*/
|
||||
String getSystemWebMenu(boolean fromCache);
|
||||
|
||||
/**
|
||||
* 更新系统功能菜单配置(自动处理功能相关的插件清理)
|
||||
*
|
||||
* @param configJson 新的系统功能菜单配置JSON
|
||||
*/
|
||||
void updateSystemWebMenu(String configJson);
|
||||
}
|
||||
|
||||
+61
-1
@@ -22,6 +22,7 @@ import xiaozhi.common.service.impl.BaseServiceImpl;
|
||||
import xiaozhi.common.utils.ConvertUtils;
|
||||
import xiaozhi.common.utils.JsonUtils;
|
||||
import xiaozhi.common.utils.SM2Utils;
|
||||
import xiaozhi.modules.agent.service.AgentPluginMappingService;
|
||||
import xiaozhi.modules.sys.dao.SysParamsDao;
|
||||
import xiaozhi.modules.sys.dto.SysParamsDTO;
|
||||
import xiaozhi.modules.sys.entity.SysParamsEntity;
|
||||
@@ -35,6 +36,7 @@ import xiaozhi.modules.sys.service.SysParamsService;
|
||||
@Service
|
||||
public class SysParamsServiceImpl extends BaseServiceImpl<SysParamsDao, SysParamsEntity> implements SysParamsService {
|
||||
private final SysParamsRedis sysParamsRedis;
|
||||
private final AgentPluginMappingService agentPluginMappingService;
|
||||
|
||||
@Override
|
||||
public PageData<SysParamsDTO> page(Map<String, Object> params) {
|
||||
@@ -270,4 +272,62 @@ public class SysParamsServiceImpl extends BaseServiceImpl<SysParamsDao, SysParam
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public String getSystemWebMenu(boolean fromCache) {
|
||||
return getValue(Constant.SYSTEM_WEB_MENU, fromCache);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateSystemWebMenu(String configJson) {
|
||||
// 获取当前配置
|
||||
String currentConfig = getSystemWebMenu(false);
|
||||
Map<String, Object> currentMap = null;
|
||||
Map<String, Object> newMap = null;
|
||||
|
||||
try {
|
||||
if (StringUtils.isNotBlank(currentConfig)) {
|
||||
currentMap = JsonUtils.parseObject(currentConfig, Map.class);
|
||||
}
|
||||
if (StringUtils.isNotBlank(configJson)) {
|
||||
newMap = JsonUtils.parseObject(configJson, Map.class);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RenException(ErrorCode.PARAM_JSON_INVALID);
|
||||
}
|
||||
|
||||
// 检查addressBook功能是否被关闭
|
||||
if (currentMap != null && newMap != null) {
|
||||
Map<String, Object> currentFeatures = (Map<String, Object>) currentMap.get("features");
|
||||
Map<String, Object> newFeatures = (Map<String, Object>) newMap.get("features");
|
||||
|
||||
if (currentFeatures != null && newFeatures != null) {
|
||||
Object currentAddressBookObj = currentFeatures.get("addressBook");
|
||||
Object newAddressBookObj = newFeatures.get("addressBook");
|
||||
|
||||
Boolean currentEnabled = false;
|
||||
Boolean newEnabled = false;
|
||||
|
||||
if (currentAddressBookObj instanceof Map) {
|
||||
Map<String, Object> currentAddressBook = (Map<String, Object>) currentAddressBookObj;
|
||||
currentEnabled = currentAddressBook.get("enabled") != null
|
||||
? (Boolean) currentAddressBook.get("enabled") : false;
|
||||
}
|
||||
|
||||
if (newAddressBookObj instanceof Map) {
|
||||
Map<String, Object> newAddressBook = (Map<String, Object>) newAddressBookObj;
|
||||
newEnabled = newAddressBook.get("enabled") != null
|
||||
? (Boolean) newAddressBook.get("enabled") : false;
|
||||
}
|
||||
|
||||
// 如果之前是启用状态,现在被禁用,删除所有call_device插件
|
||||
if (Boolean.TRUE.equals(currentEnabled) && !Boolean.TRUE.equals(newEnabled)) {
|
||||
agentPluginMappingService.deleteByPluginId("SYSTEM_PLUGIN_CALL_DEVICE");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新配置
|
||||
updateValueByCode(Constant.SYSTEM_WEB_MENU, configJson);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
-- 新增呼叫设备工具配置
|
||||
SET @data_exists = (SELECT COUNT(*) FROM ai_model_provider WHERE id = 'SYSTEM_PLUGIN_CALL_DEVICE');
|
||||
SET @sql = IF(@data_exists = 0,
|
||||
'INSERT INTO `ai_model_provider` (`id`, `model_type`, `provider_code`, `name`, `fields`, `sort`, `creator`, `create_date`, `updater`,
|
||||
`update_date`) VALUES (''SYSTEM_PLUGIN_CALL_DEVICE'', ''Plugin'', ''call_device'', ''设备呼叫设备'', ''[]'', 85, 1988490863118454785, ''2026-05-18
|
||||
12:00:00'', 1988490863118454785, ''2026-05-18 12:00:00'')',
|
||||
'SELECT ''data already exists, skip'' AS msg');
|
||||
PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
|
||||
|
||||
-- 更新系统菜单配置,增加联系人管理菜单
|
||||
UPDATE sys_params
|
||||
SET param_value = CAST(
|
||||
JSON_SET(
|
||||
CAST(param_value AS JSON),
|
||||
'$.addressBook',
|
||||
JSON_OBJECT(
|
||||
'name', 'feature.addressBook.name',
|
||||
'enabled', FALSE,
|
||||
'description', 'feature.addressBook.description'
|
||||
)
|
||||
) AS CHAR
|
||||
)
|
||||
WHERE param_code = 'system-web.menu'
|
||||
AND NOT JSON_CONTAINS_PATH(CAST(param_value AS JSON), 'one', '$.addressBook');
|
||||
|
||||
-- 创建设备通讯录表
|
||||
CREATE TABLE IF NOT EXISTS `ai_device_address_book` (
|
||||
`mac_address` VARCHAR(64) NOT NULL COMMENT '本设备MAC地址',
|
||||
`target_mac` VARCHAR(64) NOT NULL COMMENT '对方设备MAC地址',
|
||||
`alias` VARCHAR(64) DEFAULT NULL COMMENT '别名',
|
||||
`has_permission` TINYINT(1) DEFAULT TRUE COMMENT '是否有权限呼叫',
|
||||
`creator` BIGINT DEFAULT NULL COMMENT '创建人',
|
||||
`create_date` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`updater` BIGINT DEFAULT NULL COMMENT '更新人',
|
||||
`update_date` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`mac_address`, `target_mac`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='设备通讯录表';
|
||||
@@ -669,3 +669,10 @@ databaseChangeLog:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202605121509.sql
|
||||
- changeSet:
|
||||
id: 202605251426
|
||||
author: RanChen
|
||||
changes:
|
||||
- sqlFile:
|
||||
encoding: utf8
|
||||
path: classpath:db/changelog/202605251426.sql
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
|
||||
<mapper namespace="xiaozhi.modules.device.dao.DeviceAddressBookDao">
|
||||
|
||||
<select id="getAddressBookList" resultType="xiaozhi.modules.device.entity.DeviceAddressBookEntity">
|
||||
SELECT mac_address, target_mac, alias, has_permission, creator, create_date, updater, update_date
|
||||
FROM ai_device_address_book
|
||||
WHERE mac_address = #{macAddress}
|
||||
ORDER BY update_date DESC
|
||||
</select>
|
||||
|
||||
<update id="updateAlias">
|
||||
UPDATE ai_device_address_book
|
||||
SET alias = #{alias}, update_date = NOW()
|
||||
WHERE mac_address = #{macAddress} AND target_mac = #{targetMac}
|
||||
</update>
|
||||
|
||||
<update id="updatePermission">
|
||||
UPDATE ai_device_address_book
|
||||
SET has_permission = #{hasPermission}, update_date = NOW()
|
||||
WHERE mac_address = #{macAddress} AND target_mac = #{targetMac}
|
||||
</update>
|
||||
|
||||
<insert id="insert">
|
||||
INSERT INTO ai_device_address_book (mac_address, target_mac, alias, has_permission, creator, create_date, updater, update_date)
|
||||
VALUES (#{macAddress}, #{targetMac}, #{alias}, #{hasPermission}, #{creator}, NOW(), #{updater}, NOW())
|
||||
</insert>
|
||||
</mapper>
|
||||
Reference in New Issue
Block a user