OpenAppServiceImpl.java 7.62 KB
package com.diligrp.rider.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.diligrp.rider.common.exception.BizException;
import com.diligrp.rider.entity.OpenApp;
import com.diligrp.rider.mapper.OpenAppMapper;
import com.diligrp.rider.service.OpenAppService;
import com.diligrp.rider.util.SignUtil;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

@Service
@RequiredArgsConstructor
public class OpenAppServiceImpl implements OpenAppService {

    private final OpenAppMapper openAppMapper;
    private final ObjectMapper objectMapper;

    private static final List<Map<String, Object>> SUPPORTED_WEBHOOK_EVENTS = List.of(
            event("订单创建", "order.created", 2, "外部系统推单成功,订单进入待接单"),
            event("已派单", "order.dispatched", 3, "系统自动派单或后台指派骑手"),
            event("骑手接单", "order.accepted", 3, "骑手主动抢单成功"),
            event("骑手到店", "order.arrived_shop", 3, "骑手到达取货点"),
            event("骑手取件", "order.picking", 4, "骑手取件并开始配送"),
            event("订单完成", "order.completed", 6, "订单配送完成"),
            event("订单取消", "order.cancelled", 10, "订单未接单前取消"),
            event("订单转单", "order.transferred", 2, "转单审核通过,订单重新待接单")
    );

    private static final Set<String> SUPPORTED_EVENT_VALUES = Set.of(
            "*",
            "order.created",
            "order.dispatched",
            "order.accepted",
            "order.arrived_shop",
            "order.picking",
            "order.completed",
            "order.cancelled",
            "order.transferred"
    );

    @Override
    public OpenApp create(String appName, Long cityId, Long storeId, String webhookUrl, String webhookEvents, String remark) {
        if (cityId == null || cityId < 1) throw new BizException("请选择关联城市/租户");
        String normalizedUrl = normalizeWebhookUrl(webhookUrl);
        String normalizedEvents = normalizeWebhookEvents(webhookEvents);
        OpenApp app = new OpenApp();
        app.setAppName(appName);
        app.setAppKey(SignUtil.generateAppKey());
        app.setAppSecret(SignUtil.generateAppSecret());
        app.setCityId(cityId);
        app.setStoreId(storeId != null ? storeId : 0L);
        app.setStatus(1);
        app.setWebhookUrl(normalizedUrl);
        app.setWebhookEvents(normalizedEvents);
        app.setRemark(remark);
        app.setCreateTime(System.currentTimeMillis() / 1000);
        openAppMapper.insert(app);
        return app;
    }

    @Override
    public List<OpenApp> list(int page) {
        int offset = (page - 1) * 20;
        return openAppMapper.selectList(new LambdaQueryWrapper<OpenApp>()
                .orderByDesc(OpenApp::getId)
                .last("LIMIT " + offset + ",20"));
    }

    @Override
    public String resetSecret(Long appId) {
        OpenApp app = openAppMapper.selectById(appId);
        if (app == null) throw new BizException("应用不存在");
        String newSecret = SignUtil.generateAppSecret();
        openAppMapper.update(null, new LambdaUpdateWrapper<OpenApp>()
                .eq(OpenApp::getId, appId)
                .set(OpenApp::getAppSecret, newSecret));
        return newSecret;
    }

    @Override
    public void setStatus(Long appId, int status) {
        openAppMapper.update(null, new LambdaUpdateWrapper<OpenApp>()
                .eq(OpenApp::getId, appId)
                .set(OpenApp::getStatus, status));
    }

    @Override
    public void updateWebhook(Long appId, String webhookUrl, String webhookEvents) {
        OpenApp app = openAppMapper.selectById(appId);
        if (app == null) throw new BizException("应用不存在");
        String normalizedUrl = normalizeWebhookUrl(webhookUrl);
        String normalizedEvents = normalizeWebhookEvents(webhookEvents);
        openAppMapper.update(null, new LambdaUpdateWrapper<OpenApp>()
                .eq(OpenApp::getId, appId)
                .set(OpenApp::getWebhookUrl, normalizedUrl)
                .set(OpenApp::getWebhookEvents, normalizedEvents));
    }

    @Override
    public List<Map<String, Object>> supportedWebhookEvents() {
        return SUPPORTED_WEBHOOK_EVENTS;
    }

    @Override
    public OpenApp getByAppKey(String appKey) {
        return openAppMapper.selectOne(new LambdaQueryWrapper<OpenApp>()
                .eq(OpenApp::getAppKey, appKey)
                .last("LIMIT 1"));
    }

    @Override
    public boolean verifySign(String appKey, String timestamp, String nonce, String sign) {
        OpenApp app = getByAppKey(appKey);
        if (app == null || app.getStatus() != 1) return false;
        return SignUtil.verify(appKey, timestamp, nonce, sign, app.getAppSecret());
    }

    private String normalizeWebhookUrl(String webhookUrl) {
        if (webhookUrl == null || webhookUrl.isBlank()) {
            return "";
        }
        String url = webhookUrl.trim();
        try {
            URI uri = new URI(url);
            String scheme = uri.getScheme();
            if (scheme == null || !("http".equalsIgnoreCase(scheme) || "https".equalsIgnoreCase(scheme))
                    || uri.getHost() == null || uri.getHost().isBlank()) {
                throw new BizException("Webhook 回调地址必须是有效的 http/https URL");
            }
        } catch (URISyntaxException e) {
            throw new BizException("Webhook 回调地址格式错误");
        }
        return url;
    }

    private String normalizeWebhookEvents(String webhookEvents) {
        if (webhookEvents == null || webhookEvents.isBlank()) {
            return "[]";
        }
        List<String> events;
        try {
            events = objectMapper.readValue(webhookEvents, new TypeReference<List<String>>() {});
        } catch (Exception e) {
            throw new BizException("订阅事件必须是 JSON 字符串数组");
        }
        if (events == null || events.isEmpty()) {
            return "[]";
        }

        LinkedHashSet<String> normalized = new LinkedHashSet<>();
        for (String event : events) {
            if (event == null || event.isBlank()) {
                continue;
            }
            String value = event.trim();
            if (!SUPPORTED_EVENT_VALUES.contains(value)) {
                throw new BizException("不支持的 Webhook 事件:" + value);
            }
            normalized.add(value);
        }
        if (normalized.isEmpty()) {
            return "[]";
        }
        if (normalized.contains("*")) {
            normalized.clear();
            normalized.add("*");
        }
        try {
            return objectMapper.writeValueAsString(new ArrayList<>(normalized));
        } catch (JsonProcessingException e) {
            throw new BizException("订阅事件保存失败");
        }
    }

    private static Map<String, Object> event(String label, String value, Integer status, String description) {
        Map<String, Object> item = new LinkedHashMap<>();
        item.put("label", label);
        item.put("value", value);
        item.put("status", status);
        item.put("description", description);
        return item;
    }
}