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

简介

  • 淘宝奇门项目支持 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

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

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 好的,我可以使用C语言进行编程。C语言是一种广泛使用的计算机编程语言,它被广泛用于系统编程、游戏开发、移动应用开发等各个领域。使用C语言进行编程需要具备一定的计算机编程知识,包括基本的语法、变量类型、条件语句、循环语句等等。在编写程序时,需要使用C语言编译器将源代码编译成可执行文件,然后可以在计算机上运行程序。C语言非常灵活和高效,是许多编程任务的首选语言。 ### 回答2: C是一种编程语言,最初由丹尼斯·里奇在20世纪70年代初开发。C语言由于其简洁、高效和可移植的特性而成为广泛应用于系统级编程和嵌入式设备开发的一种语言。 首先,C语言具有简单的语法结构,非常容易学习和理解。它的语法规则较为简洁,基本上是由一系列的关键词、操作符和语句组成。这种简单性使得C语言非常适合初学者学习编程。 其次,C语言具有高效的执行速度。C语言的编译器能够将C代码直接编译成机器码,并且对底层硬件的操作相对较为直接。这就使得C语言的程序执行速度相对较快,特别适合对性能要求较高的应用程序。 此外,C语言具有广泛的可移植性。由于C语言的代码相对于特定的机器或操作系统来说是较为中立的,因此可以方便地移植到不同的平台上运行。这使得开发人员能够以相对较小的代价将程序移植到不同的操作系统或硬件平台上。 最后,C语言具有强大的功能和丰富的库支持。C语言提供了许多底层的操作接口,如文件操作、内存管理和指针操作等,使得程序员能够更灵活地控制程序的行为。此外,有众多的开源库可以供开发人员使用,使得功能的实现变得更加简单和高效。 总而言之,C语言作为一种古老而经典的编程语言,具有简洁、高效、可移植和丰富的功能等特点,因此仍然是许多程序员和开发人员首选的语言之一。 ### 回答3: 唔,您来问我关于使用C语言的问题了吗?好吧,让我用300字来回答您吧。 首先,C语言是一种广泛使用的计算机编程语言,它是一种通用的高级语言,同时也是一种编译型语言。由于其简洁、高效的特点,C语言在系统级编程、嵌入式系统和低级硬件操作等领域得到了广泛的应用。正因为如此,学习C语言可以让我们更了解计算机的底层工作原理,提高编程的能力和效率。 学习和使用C语言要先掌握一些基本概念和语法规则,如变量、数据类型、运算符、流程控制语句、函数等。同时还要熟悉C语言的库函数,如输入输出函数、字符串处理函数等。掌握了这些基础知识后,我们可以开始编写一些简单的程序来实现一些功能,如计算器、简单的游戏等。 另外,C语言还支持指针操作,这是它与其他高级语言最大的不同之处。通过指针,我们可以更加灵活地操作内存和数据,提高程序的性能和效率。不过指针的使用也需要谨慎,因为指针操作容易引发一些错误,如空指针引用、野指针等。 总之,学习和使用C语言需要一定的时间和耐心,但它会给我们带来很多好处。它不仅可以提高我们的编程能力,也可以让我们更好地了解计算机的工作原理。因此,如果您对计算机编程感兴趣,我推荐您学习C语言。希望我的回答能对您有所帮助,谢谢!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值