feat: 实现IP白名单严格拦截、三方Webhook多租户事件订阅与MCP专线业务调用

This commit is contained in:
wjh
2026-10-09 11:51:23 +08:00
parent c6bd736e7f
commit 00a9dcaf2f
11 changed files with 603 additions and 58 deletions
@@ -9,6 +9,8 @@ import com.iot.gateway.mapper.OpenWebhookMapper;
import com.iot.gateway.webhook.WebhookDispatchService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@@ -24,7 +26,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@Tag(name = "开放平台管理 - Webhook 通道管理")
@Tag(name = "开放平台管理 - Webhook 事件订阅与通道管理")
@RestController
@RequestMapping("/api/open/webhook")
public class OpenWebhookAdminController {
@@ -35,12 +37,65 @@ public class OpenWebhookAdminController {
@Autowired
private WebhookDispatchService dispatchService;
@Autowired(required = false)
private org.springframework.jdbc.core.JdbcTemplate jdbcTemplate;
@Operation(summary = "获取所有可订阅的标准开放事件 (Events) 枚举字典")
@GetMapping("/events")
public Result<List<Map<String, Object>>> listEvents() {
return Result.success(List.of(
Map.of(
"code", "alarm.triggered",
"name", "设备告警触发",
"category", "ALARM",
"desc", "现场物理注塑机发生工艺超温、高压超标、停机等严重报警",
"level", "CRITICAL"
),
Map.of(
"code", "alarm.recovered",
"name", "设备告警消除",
"category", "ALARM",
"desc", "报警条件解除,设备故障自动或手动复位消除",
"level", "INFO"
),
Map.of(
"code", "alarm.acknowledged",
"name", "告警已被确认",
"category", "ALARM",
"desc", "现场操作人员或值班工程师已在平台确认该告警",
"level", "INFO"
),
Map.of(
"code", "device.online",
"name", "设备上线与重连",
"category", "DEVICE",
"desc", "边缘智能网关或现场物理注塑机通过 MQTT 成功建立连接",
"level", "INFO"
),
Map.of(
"code", "device.offline",
"name", "设备失联与掉线",
"category", "DEVICE",
"desc", "设备失联或心跳超时断开,可能存在断电或网络故障",
"level", "WARNING"
),
Map.of(
"code", "device.status_changed",
"name", "设备运行状态切换",
"category", "DEVICE",
"desc", "注塑机在运行 (RUNNING)、待机 (STANDBY)、停机 (STOPPED) 间切换",
"level", "INFO"
)
));
}
@Operation(summary = "Webhook 通道分页列表")
@GetMapping("/page")
public Result<Page<OpenWebhook>> page(
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize,
@RequestParam(required = false) Long tenantId,
@RequestParam(required = false) String clientId,
@RequestParam(required = false) String keyword) {
Page<OpenWebhook> page = new Page<>(pageNum, pageSize);
@@ -48,21 +103,73 @@ public class OpenWebhookAdminController {
if (tenantId != null) {
qw.eq(OpenWebhook::getTenantId, tenantId);
}
if (StringUtils.hasText(clientId)) {
qw.eq(OpenWebhook::getClientId, clientId);
}
if (StringUtils.hasText(keyword)) {
qw.and(w -> w.like(OpenWebhook::getChannelName, keyword)
.or().like(OpenWebhook::getWebhookUrl, keyword));
}
qw.orderByDesc(OpenWebhook::getCreateTime);
return Result.success(webhookMapper.selectPage(page, qw));
Page<OpenWebhook> resPage = webhookMapper.selectPage(page, qw);
populateTenantAndClientNames(resPage.getRecords());
return Result.success(resPage);
}
private void populateTenantAndClientNames(List<OpenWebhook> webhooks) {
if (webhooks == null || webhooks.isEmpty() || jdbcTemplate == null) {
return;
}
try {
Map<Long, String> tenantNameMap = new HashMap<>();
List<Map<String, Object>> tenantRows = jdbcTemplate.queryForList("SELECT id, tenant_name FROM sys_tenant");
for (Map<String, Object> r : tenantRows) {
Object idObj = r.get("id");
Object nameObj = r.get("tenant_name");
if (idObj instanceof Number && nameObj != null) {
tenantNameMap.put(((Number) idObj).longValue(), nameObj.toString());
}
}
Map<String, String> clientNameMap = new HashMap<>();
List<Map<String, Object>> clientRows = jdbcTemplate.queryForList("SELECT client_id, client_name FROM open_client");
for (Map<String, Object> r : clientRows) {
Object cId = r.get("client_id");
Object cName = r.get("client_name");
if (cId != null && cName != null) {
clientNameMap.put(cId.toString(), cName.toString());
}
}
for (OpenWebhook w : webhooks) {
if (w.getTenantId() != null) {
w.setTenantName(tenantNameMap.getOrDefault(w.getTenantId(), "租户 #" + w.getTenantId()));
}
if (StringUtils.hasText(w.getClientId())) {
w.setClientName(clientNameMap.getOrDefault(w.getClientId(), w.getClientId()));
}
}
} catch (Exception e) {
log.warn("填充 Webhook 关联租户与应用信息异常: {}", e.getMessage());
}
}
@Operation(summary = "新建 Webhook 通道")
@PostMapping
public Result<OpenWebhook> create(@RequestBody OpenWebhook req) {
req.setId(null);
if (req.getTenantId() == null) req.setTenantId(1L);
if (!StringUtils.hasText(req.getEvents())) req.setEvents("device.alarm");
if (req.getStatus() == null) req.setStatus(1);
if (req.getTenantId() == null) {
req.setTenantId(1L);
}
if (!StringUtils.hasText(req.getEvents())) {
req.setEvents("alarm.triggered");
}
if (req.getStatus() == null) {
req.setStatus(1);
}
if (!StringUtils.hasText(req.getChannelType())) {
req.setChannelType("http");
}
webhookMapper.insert(req);
return Result.success(req);
@@ -72,14 +179,24 @@ public class OpenWebhookAdminController {
@PutMapping("/{id}")
public Result<Void> update(@PathVariable Long id, @RequestBody OpenWebhook req) {
OpenWebhook item = webhookMapper.selectById(id);
if (item == null) throw new BizException("通道不存在");
if (item == null) {
throw new BizException("通道不存在");
}
if (req.getTenantId() != null) {
item.setTenantId(req.getTenantId());
}
item.setClientId(req.getClientId());
item.setChannelName(req.getChannelName());
item.setChannelType(req.getChannelType());
item.setWebhookUrl(req.getWebhookUrl());
item.setSecret(req.getSecret());
if (req.getEvents() != null) item.setEvents(req.getEvents());
if (req.getStatus() != null) item.setStatus(req.getStatus());
if (req.getEvents() != null) {
item.setEvents(req.getEvents());
}
if (req.getStatus() != null) {
item.setStatus(req.getStatus());
}
item.setRemark(req.getRemark());
webhookMapper.updateById(item);
@@ -97,12 +214,14 @@ public class OpenWebhookAdminController {
@PostMapping("/{id}/test")
public Result<Map<String, Object>> testSend(@PathVariable Long id, @RequestBody(required = false) Map<String, String> body) {
OpenWebhook item = webhookMapper.selectById(id);
if (item == null) throw new BizException("通道不存在");
if (item == null) {
throw new BizException("通道不存在");
}
String title = body != null && body.containsKey("title") ? body.get("title") : "【IoT平台测试】告警通知";
String content = body != null && body.containsKey("content") ? body.get("content") : "这是一条来自开放平台的连通性测试消息。";
String title = body != null && body.containsKey("title") ? body.get("title") : "【IoT开放平台测试】三方事件回调连通性验证";
String content = body != null && body.containsKey("content") ? body.get("content") : "这是一条来自物联网开放平台的模拟业务事件通知,包含签名验证与数据包投递。";
boolean ok = dispatchService.sendDirect(item.getChannelType(), item.getWebhookUrl(), title, content);
return Result.success(Map.of("success", ok, "message", ok ? "测试消息已成功送达" : "发送失败,请检查 URL 与网络配置"));
boolean ok = dispatchService.sendDirect(item.getChannelType(), item.getWebhookUrl(), title, content, item.getSecret());
return Result.success(Map.of("success", ok, "message", ok ? "测试事件已成功投递" : "投递失败,请检查回调 URL 连通性或服务响应状态"));
}
}