package xiaozhi.modules.sys.controller; import java.util.Map; import org.apache.commons.lang3.StringUtils; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; import io.swagger.v3.oas.annotations.enums.ParameterIn; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.AllArgsConstructor; import xiaozhi.common.annotation.LogOperation; import xiaozhi.common.constant.Constant; import xiaozhi.common.exception.RenException; import xiaozhi.common.exception.ErrorCode; import xiaozhi.common.page.PageData; import xiaozhi.common.utils.Result; import xiaozhi.common.validator.AssertUtils; import xiaozhi.common.validator.ValidatorUtils; import xiaozhi.common.validator.group.AddGroup; import xiaozhi.common.validator.group.DefaultGroup; import xiaozhi.common.validator.group.UpdateGroup; import xiaozhi.modules.config.service.ConfigService; import xiaozhi.modules.sys.dto.SysParamsDTO; import xiaozhi.modules.sys.service.SysParamsService; import xiaozhi.modules.sys.utils.WebSocketValidator; /** * 参数管理 * * @author Mark sunlightcs@gmail.com * @since 1.0.0 */ @RestController @RequestMapping("admin/params") @Tag(name = "参数管理") @AllArgsConstructor public class SysParamsController { private final SysParamsService sysParamsService; private final ConfigService configService; private final RestTemplate restTemplate; @GetMapping("page") @Operation(summary = "分页") @Parameters({ @Parameter(name = Constant.PAGE, description = "当前页码,从1开始", in = ParameterIn.QUERY, required = true, ref = "int"), @Parameter(name = Constant.LIMIT, description = "每页显示记录数", in = ParameterIn.QUERY, required = true, ref = "int"), @Parameter(name = Constant.ORDER_FIELD, description = "排序字段", in = ParameterIn.QUERY, ref = "String"), @Parameter(name = Constant.ORDER, description = "排序方式,可选值(asc、desc)", in = ParameterIn.QUERY, ref = "String"), @Parameter(name = "paramCode", description = "参数编码或参数备注", in = ParameterIn.QUERY, ref = "String") }) @RequiresPermissions("sys:role:superAdmin") public Result> page(@Parameter(hidden = true) @RequestParam Map params) { PageData page = sysParamsService.page(params); return new Result>().ok(page); } @GetMapping("{id}") @Operation(summary = "信息") @RequiresPermissions("sys:role:superAdmin") public Result get(@PathVariable("id") Long id) { SysParamsDTO data = sysParamsService.get(id); return new Result().ok(data); } @PostMapping @Operation(summary = "保存") @LogOperation("保存") @RequiresPermissions("sys:role:superAdmin") public Result save(@RequestBody SysParamsDTO dto) { // 效验数据 ValidatorUtils.validateEntity(dto, AddGroup.class, DefaultGroup.class); sysParamsService.save(dto); configService.getConfig(false); return new Result(); } @PutMapping @Operation(summary = "修改") @LogOperation("修改") @RequiresPermissions("sys:role:superAdmin") public Result update(@RequestBody SysParamsDTO dto) { // 效验数据 ValidatorUtils.validateEntity(dto, UpdateGroup.class, DefaultGroup.class); // 验证WebSocket地址列表 validateWebSocketUrls(dto.getParamCode(), dto.getParamValue()); // 验证OTA地址 validateOtaUrl(dto.getParamCode(), dto.getParamValue()); // 验证MCP地址 validateMcpUrl(dto.getParamCode(), dto.getParamValue()); // 验证声纹地址 validateVoicePrint(dto.getParamCode(), dto.getParamValue()); // 校验mqtt密钥长度 validateMqttSecretLength(dto.getParamCode(), dto.getParamValue()); // 如果是系统功能菜单配置,使用特殊处理 if (Constant.SYSTEM_WEB_MENU.equals(dto.getParamCode())) { sysParamsService.updateSystemWebMenu(dto.getParamValue()); } else { sysParamsService.update(dto); } configService.getConfig(false); return new Result(); } /** * 验证WebSocket地址列表 * * @param urls WebSocket地址列表,以分号分隔 * @return 验证结果 */ private void validateWebSocketUrls(String paramCode, String urls) { if (!paramCode.equals(Constant.SERVER_WEBSOCKET)) { return; } String[] wsUrls = urls.split("\\;"); if (wsUrls.length == 0) { throw new RenException(ErrorCode.WEBSOCKET_URLS_EMPTY); } for (String url : wsUrls) { if (StringUtils.isNotBlank(url)) { // 检查是否包含localhost或127.0.0.1 if (url.contains("localhost") || url.contains("127.0.0.1")) { throw new RenException(ErrorCode.WEBSOCKET_URL_LOCALHOST); } // 验证WebSocket地址格式 if (!WebSocketValidator.validateUrlFormat(url)) { throw new RenException(ErrorCode.WEBSOCKET_URL_FORMAT_ERROR); } // 测试WebSocket连接 if (!WebSocketValidator.testConnection(url)) { throw new RenException(ErrorCode.WEBSOCKET_CONNECTION_FAILED); } } } } @PostMapping("/delete") @Operation(summary = "删除") @LogOperation("删除") @RequiresPermissions("sys:role:superAdmin") public Result delete(@RequestBody String[] ids) { // 效验数据 AssertUtils.isArrayEmpty(ids, "id"); sysParamsService.delete(ids); configService.getConfig(false); return new Result(); } /** * 验证OTA地址 */ private void validateOtaUrl(String paramCode, String url) { if (!paramCode.equals(Constant.SERVER_OTA)) { return; } if (StringUtils.isBlank(url) || url.equals("null")) { return; } // 检查是否包含localhost或127.0.0.1 if (url.contains("localhost") || url.contains("127.0.0.1")) { throw new RenException(ErrorCode.OTA_URL_LOCALHOST); } // 验证URL格式 if (!url.toLowerCase().startsWith("http")) { throw new RenException(ErrorCode.OTA_URL_PROTOCOL_ERROR); } if (!url.endsWith("/ota/")) { throw new RenException(ErrorCode.OTA_URL_FORMAT_ERROR); } try { // 发送GET请求 ResponseEntity response = restTemplate.getForEntity(url, String.class); if (response.getStatusCode() != HttpStatus.OK) { throw new RenException(ErrorCode.OTA_INTERFACE_ACCESS_FAILED); } // 检查响应内容是否包含OTA相关信息 String body = response.getBody(); if (body == null || !body.contains("OTA")) { throw new RenException(ErrorCode.OTA_INTERFACE_FORMAT_ERROR); } } catch (Exception e) { throw new RenException(ErrorCode.OTA_INTERFACE_VALIDATION_FAILED); } } private void validateMcpUrl(String paramCode, String url) { if (!paramCode.equals(Constant.SERVER_MCP_ENDPOINT)) { return; } if (StringUtils.isBlank(url) || url.equals("null")) { return; } if (url.contains("localhost") || url.contains("127.0.0.1")) { throw new RenException(ErrorCode.MCP_URL_LOCALHOST); } if (!url.toLowerCase().contains("key")) { throw new RenException(ErrorCode.MCP_URL_INVALID); } try { // 发送GET请求 ResponseEntity response = restTemplate.getForEntity(url, String.class); if (response.getStatusCode() != HttpStatus.OK) { throw new RenException(ErrorCode.MCP_INTERFACE_ACCESS_FAILED); } // 检查响应内容是否包含mcp相关信息 String body = response.getBody(); if (body == null || !body.contains("success")) { throw new RenException(ErrorCode.MCP_INTERFACE_FORMAT_ERROR); } } catch (Exception e) { throw new RenException(ErrorCode.MCP_INTERFACE_VALIDATION_FAILED); } } // 验证声纹接口地址是否正常 private void validateVoicePrint(String paramCode, String url) { if (!paramCode.equals(Constant.SERVER_VOICE_PRINT)) { return; } if (StringUtils.isBlank(url) || url.equals("null")) { return; } if (url.contains("localhost") || url.contains("127.0.0.1")) { throw new RenException(ErrorCode.VOICEPRINT_URL_LOCALHOST); } if (!url.toLowerCase().contains("key")) { throw new RenException(ErrorCode.VOICEPRINT_URL_INVALID); } // 验证URL格式 if (!url.toLowerCase().startsWith("http")) { throw new RenException(ErrorCode.VOICEPRINT_URL_PROTOCOL_ERROR); } try { // 发送GET请求 ResponseEntity response = restTemplate.getForEntity(url, String.class); if (response.getStatusCode() != HttpStatus.OK) { throw new RenException(ErrorCode.VOICEPRINT_INTERFACE_ACCESS_FAILED); } // 检查响应内容 String body = response.getBody(); if (body == null || !body.contains("healthy")) { throw new RenException(ErrorCode.VOICEPRINT_INTERFACE_FORMAT_ERROR); } } catch (Exception e) { throw new RenException(ErrorCode.VOICEPRINT_INTERFACE_VALIDATION_FAILED); } } // 校验mqtt密钥长度和复杂度 private void validateMqttSecretLength(String paramCode, String secret) { if (!paramCode.equals(Constant.SERVER_MQTT_SECRET)) { return; } if (StringUtils.isBlank(secret) || secret.equals("null")) { throw new RenException(ErrorCode.MQTT_SECRET_EMPTY); } if (secret.length() < 8) { throw new RenException(ErrorCode.MQTT_SECRET_LENGTH_INSECURE); } // 检查是否同时包含大小写字母 if (!secret.matches(".*[a-z].*") || !secret.matches(".*[A-Z].*")) { throw new RenException(ErrorCode.MQTT_SECRET_CHARACTER_INSECURE); } // 不允许包含弱密码 String[] weakPasswords = { "test", "1234", "admin", "password", "qwerty", "xiaozhi" }; for (String weakPassword : weakPasswords) { if (secret.toLowerCase().contains(weakPassword)) { throw new RenException(ErrorCode.MQTT_SECRET_WEAK_PASSWORD); } } } }