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

news2025/2/24 23:58:47

文章目录

    • 简介
    • 名词解释
    • 奇门对接方案
    • 前期准备
    • 系统调用流程
    • 代码实现思路
    • 关键点(个人观点)
    • 奇门对接关键代码
    • 可能遇到的问题

简介

  • 淘宝奇门项目支持 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 只需要与奇门数据总线对接一次即可完成所有系统的适配(特殊场景可能采用扩展字段的方式给与支持),如下图:
    在这里插入图片描述

前期准备

  • 首先要在淘宝开放平台上线对应的服务。
    在这里插入图片描述
  • 具体上线申请参考:应用接入流程 , 奇门仓储接入说明,奇门仓储标准白皮书
  • 上线完成后,拿到对应开发语言的sdk,appkey,secret。
    在这里插入图片描述
  • 创建一个场景
    在这里插入图片描述
    在这里插入图片描述
  • 点击进入。
    在这里插入图片描述
  • 然后就可以开始开发了。

系统调用流程

  • 正向调用:前端 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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/619167.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

day07——决策树

决策树 一、认识决策树二、API三、案例&#xff1a;泰坦尼克号乘客生存预测1&#xff0c;题目概述2&#xff0c;分析3&#xff0c;代码4&#xff0c;保存树结构 一、认识决策树 决策树思想的来源非常朴素&#xff0c;程序设计中的条件分支结构就是if-then结构&#xff0c;最早…

从2023年Q1,看当下的量子产业

光子盒研究院 一旦实现商业化&#xff0c;量子计算将带领人类进入一个全新的领域。 今天&#xff0c;人工智能(AI)、ChatGPT等大语言模型的处理能力受限于芯片有限的表面积&#xff1a;超过一定数量的GPU&#xff0c;每个GPU的批处理量就会变小——进一步增加数量反而会增大成本…

httprunner 2.x介绍与使用

一、原文地址&#xff1a; 使用说明_httprunner2.0 概述及使用说明 httprunner2接口测试框架说明文档 - 简书 二、介绍 HttpRunner是一款面向 HTTP(S) 协议的通用测试框架&#xff0c;只需编写维护一份 YAML/JSON 脚本&#xff0c;即可实现自动化测试、性能测试、线上监控、…

HTML5、CSS3和JavaScript的基础知识以及从入门到精通的学习路径

摘要&#xff1a; HTML5、CSS3和JavaScript是现代Web开发的核心技术&#xff0c;掌握它们对于想要从事Web开发的人来说至关重要。本文将介绍HTML5、CSS3和JavaScript的基础知识以及从入门到精通的学习路径。通过深入理解这三种技术的特性和用法&#xff0c;并结合实践项目&…

MacOS怎么查看进程占用内存是多少

一、背景 在Linux下可以使用 free 命令来方便的查看内存占用情况&#xff0c;如 free -g、free -m等&#xff0c;但MacOS下没有这个命令。 既然如此&#xff0c;那么MacOS里是否有类似的工具呢&#xff1f; 而我们又该如何查看整个PC的内存占用情况&#xff0c;及指定进程的…

3.1 PTQ与QAT的介绍

1. 前言 TensorRT有两种量化模式&#xff0c;分别是implicitly量化(隐式量化)以及explicitly量化(显性量化)。 隐式量化(trt7 版本之前) 只具备 PTQ 一种量化形式各层精度不可控显示量化 显性量化(trt8 版本之后) 支持带 QDQ 节点的 PTQ 以及 支持带 QDQ 节点的 QAT 两种量…

从零开始,申请开通微信小程序全流程

本系列文章适合三类同学&#xff1a;1.希望学习小程序开发&#xff1b;2.希望无代码、低代码拥有自己的小程序&#xff1b;3.快速搭建小程序交作业、交毕设的大学生 本系列文章将推出配套桌面端软件&#xff0c;配合软件&#xff0c;可实现傻瓜式开发小程序&#xff0c;请有需求…

计算机体系结构-期末复习

计算机体系结构-期末复习 第一章 量化设计与分析基础 | 1.2.6 并行度与并行体系结构的分类 应用程序中主要有两种并行&#xff1a; 数据级并行&#xff1a;同时操作许多数据项实现的并行任务级并行&#xff1a;创建能够单独处理并大量采用并行方式执行的工作任务 所有计算…

如何理解网络—网络框架介绍

目录 前言 一.计算机网络背景 二.局域网和广域网 三.网络协议 3.1产生的背景 3.2分层实现 四.OSI七层模型 4.1OSI七层模型的结构 4.2如何理解OSI七层模型 五.TCP/IP五层(或四层)模型 六.网络传输基本流程 7.网络中的地址管理 7.1IP地址 7.2MAC地址 7.3MAC地址和IP地址的区别和联…

2021-2023浙江省内八大MBA项目招生情况:注意大小年啊~

现如今国内的MBA教育呈现出一片繁华景象&#xff0c;过去的这些年来每年几乎都有新增加的MBA招生院校&#xff0c;浙江省内目前共有九大MBA招生院校&#xff0c;除了浙大独领风骚之外&#xff0c;其余八个MBA项目也都有自己的一席之地。纵观2023年的招生录取&#xff0c;小立老…

ValueError: Object arrays cannot be loaded when allow_pickle=False

一、问题 使用numpy读取数据时出现错误&#xff0c;ValueError: Object arrays cannot be loaded when allow_pickleFalse。 查了一下numpy.load()函数 用法 numpy.load(file, mmap_modeNone, allow_pickLeFalse, fix_mportsTrue, encoding‘ASCII’) 参数 file&#xff1a;…

【Android】AMS(三)APP启动流程

启动方式 在 Android 系统中&#xff0c;启动一个应用程序可以分为三种启动方式&#xff1a;热启动、冷启动和温启动。它们分别表示了不同的启动方式和启动过程。 热启动 热启动是指在已经打开并处于后台运行的应用程序中&#xff0c;再次通过图标进入应用程序的启动方式。这…

Spring Security OAuth停更了?探索官方进化版Spring Authorization Server的革新之处!

1、背景 Spring Security OAuth(spring-security-oauth2)停更 主要意思是&#xff1a;生命周期终止通知 Spring Security OAuth(spring-security-oauth2)项目已达到生命周期结束&#xff0c;不再由VMware&#xff0c;Inc.积极维护。 此项目已被Spring Security和Spring Author…

信创办公–基于WPS的EXCEL最佳实践系列 (设置多级列表)

信创办公–基于WPS的EXCEL最佳实践系列 &#xff08;设置多级列表&#xff09; 目录 应用背景操作步骤1、删除重复项2、部门绑定3、填入相关信息 应用背景 当我们在使用电子表格时&#xff0c;很多类型重复输入很麻烦&#xff0c;看起来也很复杂&#xff0c;我们就可以设置多级…

关于输入输出格式符的测试

对输出%m.nf的测试 m代表宽度&#xff0c;表示数据可以占m列n代表精确&#xff0c;表示小数占n列 以下用%6.3f进行测试&#xff0c;有两个问题&#xff1a; 1、这个m是包括小数点位数吗&#xff1f;&#xff08;todo未果&#xff09; 2、精确度n超过了是怎么处理的&#xff1f…

2023年第六届广西大学生程序设计竞赛(正式赛)题解

比赛题目链接&#xff0c;可以继续提交代码: 2023年第六届广西大学生程序设计竞赛&#xff08;正式赛&#xff09; | 知乎&#xff1a;如何评价第六届广西大学生程序设计竞赛? 难度题号备注签到题A J K已给出题解和代码普通题B D E H已给出题解和代码中等题C G–难题F I L M–…

机器学习方法在生态经济学领域中的应用

查看原文>>>基于R语言机器学习方法在生态经济学领域中的实践技术 近年来&#xff0c;人工智能领域已经取得突破性进展&#xff0c;对经济社会各个领域都产生了重大影响&#xff0c;结合了统计学、数据科学和计算机科学的机器学习是人工智能的主流方向之一&#xff0c…

moviepy快速切分视频并保存片段

文章目录 1、直接使用ffmepg2、使用moviepy本身 moviepy安装最新版本&#xff1a; pip install moviepy --pre --upgrade版本是v2.0.0.dev2。 有两种方法一种快速的&#xff1a; 1、直接使用ffmepg from moviepy.video.io.ffmpeg_tools import ffmpeg_extract_subclip ffmpeg…

Lecture 12 Discourse

目录 Discourse 语篇三个关键的语篇任务Discourse Segmentation 语篇分段Unsupervised Approaches 无监督方法Supervised Approaches 有监督方法有监督语篇分段器Discourse Analysis 语篇解析语篇解析RST: Discourse UnitsRST: Discourse RelationsNucleus vs. Satellite 核心 …

2022计算机系统期末

直接导入无图片&#xff0c;先不改了&#xff0c;不过图片都是原题目&#xff0c;不影响对答案。 计算机系统2022期末 本课程的复习请以知识点复习为重&#xff0c;全部内容共有大小280个知识点&#xff0c;都可能在期末考试出现&#xff0c;仅通过往年试卷复习是远远不够的&…