仓储WMS对接淘宝奇门详细说明【亲测可用】

文章介绍了淘宝的奇门项目,这是一个旨在简化ERP和WMS系统之间集成的解决方案。通过奇门,系统间的对接从原来的N*N倍工作量减少到一对一的模式,降低了开发和维护成本。文章详细阐述了对接流程,包括XML与对象的转换、接口调用及验签过程,并提供了部分代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

简介

  • 淘宝奇门项目支持 ERP、WMS 之间的系统标准化对接,通过构建 ERP、WMS 系统之间标准通信协议来实现不同系统之间的打通;对商家来说,省去了更换系统软件所带来的额外开发成本;对 ISV(独立软件开发商) 来说,省去了与多家 ERP、WMS 系统对接难的问题,ERP 通过一次对接奇门项目,打通与所有 WMS 之间的通信,WMS 通过一次对接奇门项目,可以适配所有 ERP 软件;后期也将加入更多系统的支持,例如 CRM 与 ERP 的标准化对接,CRM 与在线订购类营销工具的标准化对接;

名词解释

在这里插入图片描述

奇门对接方案

无奇门的情况

  • 目前商家使用的各个业务系统之间依靠 ISV 帮助实现 ERP 到 WMS 的对接,如果有多仓需求的商家还需要使用到 2 套以上的第三方仓储服务所提供的WMS 软件,ERP、WMS 各自对接,对接的总工作量为 N*N 倍,不但给 ISV的开发带来了极大的成本,对于后期维护,也将是一项艰巨的任务,如下图所示:
    在这里插入图片描述

有奇门的情况

  • 通过奇门项目后可使原有的网状对接结构变为一对一的对接方式,ERP、WMS 只需要与奇门数据总线对接一次即可完成所有系统的适配(特殊场景可能采用扩展字段的方式给与支持),如下图:
    在这里插入图片描述

前期准备

系统调用流程

  • 正向调用:前端 ERP 系统通过 TOP 接口与奇门项目应用进行交互,对于想要发送到 WMS 的请求首先发送到奇门应用,由奇门负责数据的解析、字段映射、数据翻译,再将处理后的数据通过 ERP 系统所请求的目的地发送至 WMS系统;WMS 系统收到请求后,将返回结果送回至奇门应用,由奇门应用统一返回至 ERP 系统;
  • 反向调用:WMS 系统主动向 ERP 系统发出状态更新请求也是类似以上的访问步骤;

软件流程图:

在这里插入图片描述

代码实现思路

  1. 接收奇门主动请求接口收到的XML类型的参数,将XML转换为奇门定义的对象,然后将奇门对象转换为我们自己系统的对象。
  2. 判断请求的奇门接口名称走不同的业务实现方法,接口返回数据请参照淘宝奇门接口API文档
  3. 各实现方法返回Map格式数据,转换为xml格式返回。

关键点(个人观点)

  1. xml转为对象
  2. 对象转为xml
  3. 封装统一的接口响应
  4. 对应各个接口的实现和数据返回

奇门对接关键代码


@RestController
@RequestMapping("/qiMen")
@RequiredArgsConstructor
@Slf4j
public class QiMenController {

    private final QiMenService qiMenService;

    @ApiModelProperty("奇门调用WMS数据")
    @RequestMapping(value="/apiRealization",produces="text/xml;charset=UTF-8")
    public byte[] apiRealization(HttpServletRequest request, HttpServletResponse response) throws IOException {

        byte[] result;
        Map<String, Object> resultMap = new HashMap<>();

        // API接口名称
        String methodVal = request.getParameter("method");
        // 验签
        CheckResult checkResult = QiMenUtils.checkSign(request);
        log.info("验签:{}",checkResult.isSuccess());
        if(!checkResult.isSuccess()) {
            resultMap.put("sub_code","sign-check-failure");
            resultMap.put("sub_message","Illegal request");
            resultMap.put("flag","failure");
            result = QiMenUtils.multilayerMapToXml(resultMap, false).getBytes(StandardCharsets.UTF_8);
            return result;
        }

        try {
            // 解析xml参数
            log.info("xml参数:{}", checkResult.getRequestBody());
            JSONObject jsonObject = JSONUtil.xmlToJson(checkResult.getRequestBody());
            log.info("jsonObject:{}", jsonObject);
            if(Objects.nonNull(jsonObject)) {
                if(!StringUtils.isBlank(methodVal)) {
                    switch (methodVal) {
                        case "entryorder.create":
                            // 入库单创建接口
                            resultMap = qiMenService.entryorderCreate(jsonObject);
                            break;
                        case "stockout.create":
                            // 出库单创建接口
                            resultMap = qiMenService.stockoutCreate(jsonObject);
                            break;
                        default:
                            resultMap = RCode.failure(RCode.FAILURE,"接口名称method填写有误");
                            break;
                    }
                }else {
                     resultMap = RCode.failure(RCode.FAILURE,"接口名称method不能为空");
                }
                result = QiMenUtils.multilayerMapToXml(resultMap, false).getBytes("UTF-8");
            }else {
                result = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>".getBytes("UTF-8");
            }
        } catch (BizException e) {
            resultMap = RCode.failure(RCode.FAILURE,e.getMessage());
            try {
                result = QiMenUtils.multilayerMapToXml(resultMap, false).getBytes("UTF-8");
            } catch (UnsupportedEncodingException e1) {
                return null;
            }
        } catch (Exception e) {
            log.error("操作失败:" + e + "-----" + e.getMessage());
            resultMap = RCode.failure(RCode.FAILURE,"操作失败");
            try {
                result = QiMenUtils.multilayerMapToXml(resultMap, false).getBytes("UTF-8");
            } catch (UnsupportedEncodingException e1) {
                return null;
            }
        }
        log.info("ERP调用响应结果 resultMap:{}",resultMap);
        return result;
    }


    @ApiModelProperty("WMS主动推送奇门数据")
    @PostMapping("/push/{method}")
    public Result pushQiMen(@PathVariable String method,@RequestBody JSONObject jsonObject) {
        QimenResponse response = null;
        switch (method) {
            case "entryorder.confirm":
                // 入库单确认
                response = qiMenService.entryorderConfirm(jsonObject);
                break;
            case "stockout.confirm":
                // 出库单确认
                response = qiMenService.stockoutConfirm(jsonObject);
                break;
            default:
                break;
        }
        return RCode.result(response);
    }
}

@RequiredArgsConstructor
@Slf4j
@Service
public class QiMenServiceImpl implements QiMenService {

    public static final String XML_KEY = "request";

    /**
     * 入库单创建
     *
     * @param jsonObject
     * @return
     */
    @Override
    public Map<String, Object> entryorderCreate(JSONObject jsonObject) {
    	// 将请求头的数据转换为当前系统对应的dto类
        EntryorderCreateRequest bean = jsonObject.getBean(XML_KEY, EntryorderCreateRequest.class);
        List<JSONObject> list = jsonObject.getByPath("request.orderLines.orderLine", List.class);
        if (CollUtil.isEmpty(list)) {
            throw new BizException("入库单详情列表为空!");
        }
        List<EntryorderCreateRequest.OrderLine> orderLines = new ArrayList<>();
        for (JSONObject object : list) {
            orderLines.add(object.toBean(EntryorderCreateRequest.OrderLine.class));
        }
        bean.setOrderLines(orderLines);
        QiMenReceiptOrderDTO receiptOrderDTO = QiMenConvert.entryorderCreate(bean);
        // 远程调用创建入库单
        Result result = qiMenRequestInBound.qiMenRequestInBound(receiptOrderDTO);
        // 包装响应结果
        return RCode.result(result);
    }

    /**
     * WMS调用接口,回传入库单信息;
     *
     * @param jsonObject
     * @return
     */
    @Override
    public QimenResponse entryorderConfirm(JSONObject jsonObject) {
        // wms dto 转换为 EntryorderConfirmRequest
        QiMenInboundAckVO bean = JSONUtil.toBean(jsonObject, QiMenInboundAckVO.class);
        EntryorderConfirmRequest req = QiMenConvert.entryorderConfirm(bean);
        req.setVersion("2.0");
        req.setCustomerId("mockCustomerId");  // mockCustomerId 挡板测试的客户id
        QimenClient client = getClient();
        EntryorderConfirmResponse rsp = new EntryorderConfirmResponse();
        try {
            rsp = client.execute(req);
        } catch (ApiException e) {
            log.error("调用奇门异常:",e);
            rsp.setMessage(e.getMessage());
        }
        return rsp;
    }
public class QiMenUtils {

	private final static String secret = "xxxxxxxxxxxxxxxxxxxxxxxxxxx";
    private final static String appkey = "xxxx";
    // 联调地址 http://qimen.api.taobao.com/router/qmtest
    // 正式地址 https://qimen.api.taobao.com/router/qimen/service
    private final static String url = "https://qimen.api.taobao.com/router/qimen/service";


	/**
     * wms主动发起请求奇门的接口  创建一个连接
     * @return
     */
	public static QimenClient getClient() {
        return new DefaultQimenClient(url, appkey, secret);
    }

	/**
     * 验签 调用sdk中的签名验证方法
     * @param request
     * @return
     */
    @SneakyThrows
    public static CheckResult checkSign(HttpServletRequest request) {
        return SpiUtils.checkSign(request, secret);
    }

    /**
     * (多层)map转换为xml格式字符串
     *
     * @param map 需要转换为xml的map
     * @param isCDATA 是否加入CDATA标识符 true:加入 false:不加入
     * @return xml字符串
     * @throws UnsupportedEncodingException
     */
    public static String multilayerMapToXml(Map<String, Object> map, boolean isCDATA) throws UnsupportedEncodingException{
        String parentName = "response";
        Document doc = DocumentHelper.createDocument();
        doc.addElement(parentName);
        String xml = recursionMapToXml(doc.getRootElement(), parentName, map, isCDATA);
        return formatXML(xml);
    }


    /**
     * multilayerMapToXml核心方法,递归调用
     *
     * @param element 节点元素
     * @param parentName 根元素属性名
     * @param map 需要转换为xml的map
     * @param isCDATA 是否加入CDATA标识符 true:加入 false:不加入
     * @return xml字符串
     */
    @SuppressWarnings("unchecked")
    private static String recursionMapToXml(Element element, String parentName, Map<String, Object> map, boolean isCDATA) {
        Element xmlElement = element.addElement(parentName);
        map.keySet().forEach(key -> {
            Object obj = map.get(key);
            if (obj instanceof Map) {
                recursionMapToXml(xmlElement, key, (Map<String, Object>)obj, isCDATA);
            } else {
                String value = obj == null ? "" : obj.toString();
                if (isCDATA) {
                    xmlElement.addElement(key).addCDATA(value);
                } else {
                    xmlElement.addElement(key).addText(value);
                }
            }
        });
        return xmlElement.asXML();
    }

    /**
     * 格式化xml,显示为容易看的XML格式
     *
     * @param xml 需要格式化的xml字符串
     * @return
     */
    public static String formatXML(String xml) {
        String requestXML = null;
        try {
            // 解析器
            SAXReader reader = new SAXReader();
            Document document = reader.read(new StringReader(xml));
            if (null != document) {
                StringWriter stringWriter = new StringWriter();
                // 格式化,每一级前的空格
                OutputFormat format = new OutputFormat("", true);
                // xml声明与内容是否添加空行
                format.setNewLineAfterDeclaration(false);
                // 是否设置xml声明头部
                format.setSuppressDeclaration(false);
                // 是否分行
                format.setNewlines(true);
                XMLWriter writer = new XMLWriter(stringWriter, format);
                writer.write(document);
                writer.flush();
                writer.close();
                requestXML = stringWriter.getBuffer().toString();
            }
            return requestXML;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

可能遇到的问题

xml转换为对象

对象list转换为xml

你知道的越多,你不知道的越多。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值