Commit f3ec2e0c6d225caaa8f17a95eeb2ee9355512476
1 parent
548c125f
完善开放平台Webhook配置校验,新增支持事件列表接口
Showing
3 changed files
with
127 additions
and
6 deletions
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 | } | ... | ... |