Commit f3ec2e0c6d225caaa8f17a95eeb2ee9355512476

Authored by shaofan
1 parent 548c125f

完善开放平台Webhook配置校验,新增支持事件列表接口

src/main/java/com/diligrp/rider/controller/PlatformOpenAppController.java
... ... @@ -16,6 +16,7 @@ import lombok.RequiredArgsConstructor;
16 16 import org.springframework.web.bind.annotation.*;
17 17  
18 18 import java.util.List;
  19 +import java.util.Map;
19 20  
20 21 /**
21 22 * 平台后台:开放平台应用管理
... ... @@ -74,12 +75,18 @@ public class PlatformOpenAppController {
74 75 /** 更新 Webhook 配置 */
75 76 @PostMapping("/updateWebhook")
76 77 public Result<Void> updateWebhook(@RequestParam Long appId,
77   - @RequestParam String webhookUrl,
78   - @RequestParam String webhookEvents) {
  78 + @RequestParam(required = false) String webhookUrl,
  79 + @RequestParam(required = false) String webhookEvents) {
79 80 openAppService.updateWebhook(appId, webhookUrl, webhookEvents);
80 81 return Result.success();
81 82 }
82 83  
  84 + /** 支持订阅的 Webhook 事件 */
  85 + @GetMapping("/webhook/events")
  86 + public Result<List<Map<String, Object>>> webhookEvents() {
  87 + return Result.success(openAppService.supportedWebhookEvents());
  88 + }
  89 +
83 90 /** Webhook 推送日志 */
84 91 @GetMapping("/webhook/logs")
85 92 public Result<List<WebhookLog>> webhookLogs(@RequestParam Long appId,
... ...
src/main/java/com/diligrp/rider/service/OpenAppService.java
... ... @@ -3,6 +3,7 @@ package com.diligrp.rider.service;
3 3 import com.diligrp.rider.entity.OpenApp;
4 4  
5 5 import java.util.List;
  6 +import java.util.Map;
6 7  
7 8 public interface OpenAppService {
8 9 /** 创建应用,自动生成 AppKey/AppSecret,cityId 为必填(租户隔离) */
... ... @@ -15,6 +16,8 @@ public interface OpenAppService {
15 16 void setStatus(Long appId, int status);
16 17 /** 更新 Webhook 配置 */
17 18 void updateWebhook(Long appId, String webhookUrl, String webhookEvents);
  19 + /** 支持的 Webhook 事件列表 */
  20 + List<Map<String, Object>> supportedWebhookEvents();
18 21 /** 根据 AppKey 获取应用(用于签名验证) */
19 22 OpenApp getByAppKey(String appKey);
20 23 /** 验证签名 */
... ...
src/main/java/com/diligrp/rider/service/impl/OpenAppServiceImpl.java
... ... @@ -2,6 +2,9 @@ package com.diligrp.rider.service.impl;
2 2  
3 3 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
4 4 import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
  5 +import com.fasterxml.jackson.core.JsonProcessingException;
  6 +import com.fasterxml.jackson.core.type.TypeReference;
  7 +import com.fasterxml.jackson.databind.ObjectMapper;
5 8 import com.diligrp.rider.common.exception.BizException;
6 9 import com.diligrp.rider.entity.OpenApp;
7 10 import com.diligrp.rider.mapper.OpenAppMapper;
... ... @@ -10,17 +13,50 @@ import com.diligrp.rider.util.SignUtil;
10 13 import lombok.RequiredArgsConstructor;
11 14 import org.springframework.stereotype.Service;
12 15  
  16 +import java.net.URI;
  17 +import java.net.URISyntaxException;
  18 +import java.util.ArrayList;
  19 +import java.util.LinkedHashMap;
  20 +import java.util.LinkedHashSet;
13 21 import java.util.List;
  22 +import java.util.Map;
  23 +import java.util.Set;
14 24  
15 25 @Service
16 26 @RequiredArgsConstructor
17 27 public class OpenAppServiceImpl implements OpenAppService {
18 28  
19 29 private final OpenAppMapper openAppMapper;
  30 + private final ObjectMapper objectMapper;
  31 +
  32 + private static final List<Map<String, Object>> SUPPORTED_WEBHOOK_EVENTS = List.of(
  33 + event("订单创建", "order.created", 2, "外部系统推单成功,订单进入待接单"),
  34 + event("已派单", "order.dispatched", 3, "系统自动派单或后台指派骑手"),
  35 + event("骑手接单", "order.accepted", 3, "骑手主动抢单成功"),
  36 + event("骑手到店", "order.arrived_shop", 3, "骑手到达取货点"),
  37 + event("骑手取件", "order.picking", 4, "骑手取件并开始配送"),
  38 + event("订单完成", "order.completed", 6, "订单配送完成"),
  39 + event("订单取消", "order.cancelled", 10, "订单未接单前取消"),
  40 + event("订单转单", "order.transferred", 2, "转单审核通过,订单重新待接单")
  41 + );
  42 +
  43 + private static final Set<String> SUPPORTED_EVENT_VALUES = Set.of(
  44 + "*",
  45 + "order.created",
  46 + "order.dispatched",
  47 + "order.accepted",
  48 + "order.arrived_shop",
  49 + "order.picking",
  50 + "order.completed",
  51 + "order.cancelled",
  52 + "order.transferred"
  53 + );
20 54  
21 55 @Override
22 56 public OpenApp create(String appName, Long cityId, Long storeId, String webhookUrl, String webhookEvents, String remark) {
23 57 if (cityId == null || cityId < 1) throw new BizException("请选择关联城市/租户");
  58 + String normalizedUrl = normalizeWebhookUrl(webhookUrl);
  59 + String normalizedEvents = normalizeWebhookEvents(webhookEvents);
24 60 OpenApp app = new OpenApp();
25 61 app.setAppName(appName);
26 62 app.setAppKey(SignUtil.generateAppKey());
... ... @@ -28,8 +64,8 @@ public class OpenAppServiceImpl implements OpenAppService {
28 64 app.setCityId(cityId);
29 65 app.setStoreId(storeId != null ? storeId : 0L);
30 66 app.setStatus(1);
31   - app.setWebhookUrl(webhookUrl);
32   - app.setWebhookEvents(webhookEvents);
  67 + app.setWebhookUrl(normalizedUrl);
  68 + app.setWebhookEvents(normalizedEvents);
33 69 app.setRemark(remark);
34 70 app.setCreateTime(System.currentTimeMillis() / 1000);
35 71 openAppMapper.insert(app);
... ... @@ -64,10 +100,19 @@ public class OpenAppServiceImpl implements OpenAppService {
64 100  
65 101 @Override
66 102 public void updateWebhook(Long appId, String webhookUrl, String webhookEvents) {
  103 + OpenApp app = openAppMapper.selectById(appId);
  104 + if (app == null) throw new BizException("应用不存在");
  105 + String normalizedUrl = normalizeWebhookUrl(webhookUrl);
  106 + String normalizedEvents = normalizeWebhookEvents(webhookEvents);
67 107 openAppMapper.update(null, new LambdaUpdateWrapper<OpenApp>()
68 108 .eq(OpenApp::getId, appId)
69   - .set(OpenApp::getWebhookUrl, webhookUrl)
70   - .set(OpenApp::getWebhookEvents, webhookEvents));
  109 + .set(OpenApp::getWebhookUrl, normalizedUrl)
  110 + .set(OpenApp::getWebhookEvents, normalizedEvents));
  111 + }
  112 +
  113 + @Override
  114 + public List<Map<String, Object>> supportedWebhookEvents() {
  115 + return SUPPORTED_WEBHOOK_EVENTS;
71 116 }
72 117  
73 118 @Override
... ... @@ -83,4 +128,70 @@ public class OpenAppServiceImpl implements OpenAppService {
83 128 if (app == null || app.getStatus() != 1) return false;
84 129 return SignUtil.verify(appKey, timestamp, nonce, sign, app.getAppSecret());
85 130 }
  131 +
  132 + private String normalizeWebhookUrl(String webhookUrl) {
  133 + if (webhookUrl == null || webhookUrl.isBlank()) {
  134 + return "";
  135 + }
  136 + String url = webhookUrl.trim();
  137 + try {
  138 + URI uri = new URI(url);
  139 + String scheme = uri.getScheme();
  140 + if (scheme == null || !("http".equalsIgnoreCase(scheme) || "https".equalsIgnoreCase(scheme))
  141 + || uri.getHost() == null || uri.getHost().isBlank()) {
  142 + throw new BizException("Webhook 回调地址必须是有效的 http/https URL");
  143 + }
  144 + } catch (URISyntaxException e) {
  145 + throw new BizException("Webhook 回调地址格式错误");
  146 + }
  147 + return url;
  148 + }
  149 +
  150 + private String normalizeWebhookEvents(String webhookEvents) {
  151 + if (webhookEvents == null || webhookEvents.isBlank()) {
  152 + return "[]";
  153 + }
  154 + List<String> events;
  155 + try {
  156 + events = objectMapper.readValue(webhookEvents, new TypeReference<List<String>>() {});
  157 + } catch (Exception e) {
  158 + throw new BizException("订阅事件必须是 JSON 字符串数组");
  159 + }
  160 + if (events == null || events.isEmpty()) {
  161 + return "[]";
  162 + }
  163 +
  164 + LinkedHashSet<String> normalized = new LinkedHashSet<>();
  165 + for (String event : events) {
  166 + if (event == null || event.isBlank()) {
  167 + continue;
  168 + }
  169 + String value = event.trim();
  170 + if (!SUPPORTED_EVENT_VALUES.contains(value)) {
  171 + throw new BizException("不支持的 Webhook 事件:" + value);
  172 + }
  173 + normalized.add(value);
  174 + }
  175 + if (normalized.isEmpty()) {
  176 + return "[]";
  177 + }
  178 + if (normalized.contains("*")) {
  179 + normalized.clear();
  180 + normalized.add("*");
  181 + }
  182 + try {
  183 + return objectMapper.writeValueAsString(new ArrayList<>(normalized));
  184 + } catch (JsonProcessingException e) {
  185 + throw new BizException("订阅事件保存失败");
  186 + }
  187 + }
  188 +
  189 + private static Map<String, Object> event(String label, String value, Integer status, String description) {
  190 + Map<String, Object> item = new LinkedHashMap<>();
  191 + item.put("label", label);
  192 + item.put("value", value);
  193 + item.put("status", status);
  194 + item.put("description", description);
  195 + return item;
  196 + }
86 197 }
... ...