简介:面向旺店通WMS系统对接开发者的完整代码包,聚焦WebAPI接口地址、标准定制接口样例与具体调用流程三大实际问题。资源以销售出库单查询接口为实战主线,不仅解释了接口调用规范,还通过C# HttpClient演示了POST请求、签名构建、URL参数封装等关键环节,并附有包含请求地址、方法、密钥及参数设置的完整调用示例,适合有一定C#基础、需要快速完成WMS集成的开发人员直接参照编码。包体共32个文件,压缩后约380KB,以C#源文件(.cs)、工程文件(.csproj)、编译生成的DLL与PDB调试文件为主体,另含JSON配置、TXT说明及Python辅助脚本,覆盖从源码编译、配置读取到运行调试的完整链路,目录结构便于按模块检索。已有182人学习下载。通过该包可掌握旺店通WMS接口调用规范、签名算法和标准定制接口的扩展思路,同时获得可直接复用的请求示例与调试方法,有效减少对接初期的试错成本,提升业务接口联调效率。
1. 旺店通WMS对接流程在解决什么:订单进仓库,回执回ERP
订单在旺店通ERP里审核完,还要人工复制单号去WMS建单,这套流程日订单几百单时还能凑合,大促一来两个系统的手工操作员先崩溃。旺店通WMS对接流程,就是把旺店通ERP的订单、货品、库存通过开放接口同步给仓库侧的WMS系统,再把仓库的发货状态、物流单号、库存变动回写ERP,两边各自作业,账目却对得上。适合自研WMS或上了第三方WMS系统的电商团队。这里有个反直觉的判断:对接真正跑通不是“推单成功”,而是三件事同时成立——不重复建单、库存两边一致、凌晨出问题时没人盯也能自动回传。你判断一个WMS系统值不值得投入,先拿这三条去压一天的线上数据。
2. 对接前先理清边界:WMS类型、开放平台权限与字段映射
2.1 先确认你对的是哪类WMS:接口能力差别很大
“旺店通WMS”这个说法在从业者嘴里有点含糊:一种是指旺店通ERP去对接外部WMS,另一种是指旺店通自己的仓内模块。本文讲的是前者,也是仓库里最常见的需求:ERP在云端,WMS在仓内或者由第三方提供,中间要有一条数据通道。写代码之前,我先确认仓库那边的WMS是什么形态,是自研、第三方SaaS还是只有Excel导入能力的库房系统。这三种对接成本从两周到一个小时不等,差距比我想象的大得多。
如果是自研WMS,通常能协商出最顺的接口形态,REST/JSON、字段命名都能对齐,甚至可以让对方按你的报文开发,这是最舒服的情况。第三方SaaS WMS则只能用他开放的OpenAPI,字段名固定、回调机制固定,你的对接代码反而要做大量适配。最麻烦的是号称有接口实则只能导Excel的WMS,几百单时能接受,超过一千单后人工成本和误操作率会让你后悔当初没问清楚。这个决定会一路影响第4章的链路设计,所以我不建议绕过。
所以对接第一步不是写代码,是问五个问题:能不能查库存?能不能接收外部订单生成出库任务?能不能回传发货结果?能不能按外部单号反查状态?接口限流阈值是多少?这五个答案直接决定技术方案形态。如果文档里只能找到“订单导入”而找不到“异步回调”或“状态查询”,你基本可以判断这个WMS只支持半自动对接,后面所有“实时性”要求都得靠你自己轮询找补。
2.2 开放平台权限与接口签名:AppKey、AppSecret加签是第一步
确认WMS能力后,另一头是旺店通开放平台的调用权限。常见做法是在旺店通商家后台创建一个开放应用,拿到一对AppKey和AppSecret。AppKey在请求里明文传递,AppSecret用于计算签名,不能出现在前端页面上。放在后端对接服务里虽然看得见,但至少别提交到公开代码仓库,否则等于把密钥发在朋友圈。我一般会在配置中心里单独存AppSecret,代码仓库里只留占位符。
旺店通开放平台的调用风格和主流电商开放平台类似:一次HTTP POST请求,带着method(接口名)、app_key、timestamp、业务参数和sign。签名规则大多数是:把所有参数按字典序(ASCII升序)排好,逐个以“参数名参数值”方式拼接,末尾拼上AppSecret,取MD5后转大写。各家细节不同,有的要求URL编码,有的要求跳过空值,最终以你手上接口文档里的“签名示例”为准。我习惯把生成签名前的原始字符串打印出来,跟文档示例比对,签名排错能省一半时间。
旺店通开放平台的接口按业务分几类:货品、库存、订单、发货、售后。WMS对接通常只需要四类:拉待发货订单、查库存、下发发货单、回传发货结果。接口数量越少越好,每多接一个接口就多一条链路要维护。我一般会把需要的接口列成一个清单,逐个确认是否存在,做成下面这样:
| 功能 | 接口名示意 | 是否必需 |
|---|---|---|
| 查询待发货订单 | wdt.order.query | 必需 |
| 查询实时库存 | wdt.stock.query | 必需 |
| 下发发货单 | wdt.delivery.create | 必需 |
| 回传发货结果 | wdt.delivery.callback | 必需 |
| 查询发货状态 | wdt.delivery.query | 强烈建议 |
第五个“查询发货状态”是兜底用的,第4章的“先查后建”和“轮询补单”都靠它。如果两边任意一侧没有这个接口,你的重试策略就只能盲重试,风险显著上升。另外注意timestamp这个参数,平台靠它防重放,误差通常超过五分钟就会被拒。对接服务器的系统时间如果漂移,签名算法写对了也会报错,这个细节常在凌晨被线上告警吵醒时才想起来检查。
2.3 字段映射表:写代码前先把两边字段在纸上对齐
WMS对接最容易被低估的是字段映射。旺店通侧的“货号”和WMS的“SKU编码”、旺店通的“仓库”和WMS的“库房”,两边“物流公司”编码表,经常是两套完全不同的值。不要指望用一堆if/else去转物流公司编码,那是给自己埋雷。我一般先画一张映射表,让仓库主管确认过再写代码。常见的必备映射方向如下:
| 旺店通字段 | WMS字段 | 示例值 | 备注 |
|---|---|---|---|
| 货号 | sku_code | SPU0001 | 两边必须完全一致 |
| 仓库 | warehouse_no | WH-01 | 旺店通仓库编码对应WMS库房 |
| 店铺 | shop_no | SHOP-001 | 回传时区分渠道 |
| 物流公司 | logistics_company | SF/ZTO | 需要独立映射表 |
| 平台单号 | platform_order_no | 202501010001 | 全局唯一,幂等键 |
这张表里最容易翻车的是物流公司,旺店通用简称SF、ZTO,WMS可能是offline_code或者数字编码。常见做法是把映射表外置成配置文件,启动时加载,别让业务组的人来找你改代码。字段映射确认后再写第一行代码,顺序反了,仓库团队会觉得你只懂代码不懂业务。
还有个容易漏的:旺店通的“货品档案”和WMS的“商品档案”是两回事。旺店通叫货品,WMS叫SKU,连单位都可能不一致。推单前要先确保WMS建好对应货品,否则第一张单就报“货品不存在”。这个问题很常见,我放到第5章专门讲排查过程。
3. 用示例代码跑通旺店通WMS最小链路:库存查询、推单与回调
3.1 工程结构与最小依赖
我不建议一开始上微服务框架,对接进程两个礼拜就能写完的东西,拆成六个服务只会增加排查成本。我常用的工程结构是三个文件:wdt_client.py封装旺店通开放平台调用,wms_client.py封装WMS侧接口调用,callback_server.py接收WMS回调。定时触发放进cron,依赖只有requests和Flask,幂等状态先用内存集合演示,生产环境换成Redis。示例代码讲解尽量一个易错点对应一行注释。
这类项目最重要的思维是“把对接服务当成胶水层,而不是业务系统”。它只需要把两边的数据格式翻译正确、状态记录清楚、错误暴露出来,不要在胶水层里写复杂业务判断。代码仓库结构如下:
wdt-wms-bridge/ ├── wdt_client.py # 旺店通开放平台封装 ├── wms_client.py # WMS 出库单创建封装 ├── callback_server.py # 接收 WMS 回调 └── main.py # 定时任务入口3.2 签名与请求封装:所有接口共用的入口
先把调用旺店通的公共逻辑抽成函数,后续每个接口调用都复用。这个函数要干四件事:拼公共参数、计算签名、发送POST请求、解析统一返回结构。示例代码如下:
# wdt_client.py import hashlib import time import requests GATEWAY = "https://openapi.example.com/gateway" # 以旺店通开放平台文档为准 APP_KEY = "your-app-key" APP_SECRET = "your-app-secret" def build_sign(params: dict) -> str: # 过滤空值,sign 不参与签名 items = sorted((k, str(v)) for k, v in params.items() if v is not None and v != "") raw = "".join(f"{k}{v}" for k, v in items) + APP_SECRET return hashlib.md5(raw.encode("utf-8")).hexdigest().upper() def call_wdt(method: str, biz_params: dict, timeout: int = 8) -> dict: params = { "method": method, "app_key": APP_KEY, "timestamp": str(int(time.time())), **biz_params, } params["sign"] = build_sign(params) resp = requests.post(GATEWAY, data=params, timeout=timeout) result = resp.json() if result.get("code") != 0: raise RuntimeError(f"{method} error[{result.get('code')}]: {result.get('message')}") return result.get("data") or {}build_sign里最关键的是先过滤空值再排序。某些平台对空字符串和None处理不同,多拼一个空参数进去签名就错。raw这里用的是参数名和参数值直接拼接,如果你的平台文档要求name=value&这种形式,把join一行替换成对应的拼接即可,判断依据是文档里给出的待签串示例。timeout参数默认8秒,这个值在库存查询场景够用,推单场景我会手动调大到10秒。
3.3 库存查询:先确认仓库有货再推单
推送订单给WMS前,先查一次库存能避免后续超卖纠纷。这里查的是旺店通侧库存;如果两边库存没有实时同步,这步应该直接查WMS。我示例里按旺店通库存接口来写,逻辑一样:
# main.py from wdt_client import call_wdt def query_stock(sku: str, warehouse_no: str) -> int: data = call_wdt( "wdt.stock.query", { "sku": sku, "warehouse_no": warehouse_no, "page_no": 1, "page_size": 100, }, ) items = data.get("stock_list") or [] total = 0 for item in items: if item.get("warehouse_no") == warehouse_no: # available_qty 是可用库存,冻结库存不能算 total += int(item.get("available_qty") or 0) return totalsku传旺店通货号,warehouse_no传仓库编码,page_size控制每页条数。库存接口按仓库加库位维度返回,一个SKU可能有多行,需要按warehouse_no过滤后累加available_qty。如果返回的列表长度等于page_size,说明还有下一页,不翻页的话库存数会偏小,推单后WMS扣减时才发现超卖,比不查库存更被动。这里有个默认前提:旺店通库存和WMS库存是同步过的,如果你正在做第一次对接,两边数可能根本不相等,这步查出来只能当参考。
3.4 推单:把ERP订单变成WMS出库任务
库存确认后,把订单推给WMS。旺店通侧可以理解成“下发发货单”,WMS侧生成出库任务。示例里我直接调用WMS的创建出库单接口:
# wms_client.py import requests def create_wms_outbound(order_no: str, warehouse_no: str, goods: list) -> str: payload = { "req_id": f"{order_no}-{warehouse_no}", # 幂等键,WMS 要按它去重 "outbound_type": "SO", "warehouse_no": warehouse_no, "order_no": order_no, "goods_list": goods, } resp = requests.post("https://wms.example.com/api/outbound", json=payload, timeout=10) result = resp.json() if result.get("code") != 0: raise RuntimeError(f"WMS 推单失败: {result.get('message')}") return result["data"]["wms_order_no"]goods列表由旺店通订单明细组装,典型结构是这样:
goods = [ {"sku": "SPU0001", "qty": 2}, {"sku": "SPU0002", "qty": 1}, ]req_id是幂等键,WMS如果收到相同req_id的请求,应该直接返回已有出库单号而不是再建一张。这个字段决定了重复推单会不会把仓库作业池打爆,我在第5章还会展开。goods里要不要带batch_no批次号,要看WMS支不支持批次库存,不知道怎么定就先问仓库主管,别替业务做决定。还有order_no这个字段,我习惯传旺店通平台单号而不是ERP内部单号,两边客服沟通时都是拿着平台单号找单,统一口径能少很多扯皮。
3.5 接收WMS回调并回写旺店通
WMS完成出库后把结果回传。常见做法是对接服务提供一个Webhook地址,WMS检测到发货完成就POST过来。这里用Flask写一个最小接收端:
# callback_server.py from flask import Flask, request, jsonify from wdt_client import call_wdt app = Flask(__name__) processed = set() # 生产环境请换成 Redis / 数据库 @app.post("/wms/callback") def on_wms_callback(): payload = request.get_json(force=True) cb_id = payload.get("callback_id", "") if cb_id in processed: return jsonify({"code": 0, "message": "duplicated"}) processed.add(cb_id) if payload.get("status") == "DONE": call_wdt("wdt.delivery.callback", { "order_no": payload["order_no"], "logistics_no": payload["logistics_no"], "logistics_company": payload["logistics_company"], "wms_order_no": payload["wms_order_no"], }) return jsonify({"code": 0})callback_id是WMS回调序号,用它做幂等,避免旺店通里重复回传。旺店通回传接口名以你拿到的文档为准,这里用wdt.delivery.callback示意。注意一点:如果回调处理里调旺店通失败,返回给WMS的code要置为非0,让WMS按它的策略重试;不要自己把异常吞掉只记日志,不然发货状态会黑在中间层,仓库说发了,ERP说没收到,两边都在催你。
4. 从推单到回传的链路设计:轮询、重试与每日对账
4.1 主动轮询还是被动回调:两种链路怎么选
第3章展示的是回调链路,但真实项目里没有任何一条链路是单一的。我把数据流拆成两段看:旺店通到对接服务这段,可以轮询;WMS到对接服务这段,可以回调。两段的选择标准不一样:
| 链路 | 实时性 | 对公网要求 | 对接成本 | 典型场景 |
|---|---|---|---|---|
| 轮询 | 30秒到数分钟 | 低 | 低 | WMS没有回调能力 |
| 回调 | 秒级 | 高(需公网可达) | 中 | WMS支持Webhook |
| 轮询+回调混合 | 秒级 | 高 | 中高 | 生产环境推荐 |
旺店通侧如果没有稳定的Webhook或者你不想维护一套回调接收端,就用轮询。常见做法是每30秒调一次旺店通的待发货订单查询接口,新单推给WMS,同时处理之前已发货但没回传的单。这种模式的代价是把接口调用频率抬高,所以要控制page_size和查询时间窗口,别一次性把大促的历史单全部拉出来,那会把你自己的队列冲垮。轮询循环的骨架就三行:
import time while True: try: fetch_and_push_pending() # 拉新单、推WMS、回传状态 except Exception as e: log.error("cycle failed: %s", e) # 别让一次异常打断整个循环 time.sleep(30)这个写法看着简单,踩坑点在于异常处理:循环里任何一个环节抛异常,整个循环就停了,后面所有订单全部积压。所以要在这里拦一层,记录日志然后继续。从运维角度看,这条循环是一个黑匣子,只有日志能告诉你它每分钟干了什么。
4.2 重试、超时与幂等:接口调到一半断了怎么办
对接服务调WMS创建出库单超时,你无法确认WMS到底建没建单。最怕这时候直接重试,WMS可能已经建单并开始拣货,再来一发就是重复作业。正确做法是“先查后建”:WMS只要有按外部单号查询出库单的接口,超时后先查,有则用WMS单号继续,没有再重试新建。这个原则是WMS对接里最重要的幂等手段,没有它,线上的重复单会让你被仓库主管拉黑。
同理,旺店通回传也遵循这个原则。回传超时,旺店通里可能已经收到,重试前要确认这个order_no没有被回传过。我一般在本地维护一张流水表,记录order_no、推单时间、WMS单号、回传状态,用order_no做唯一索引。这张表是整个对接的黑匣子,出了问题先翻它,不要一上来就对着旺店通日志查。流水表结构可以参考:
CREATE TABLE delivery_bridge_log ( id BIGINT AUTO_INCREMENT PRIMARY KEY, order_no VARCHAR(64) NOT NULL, wms_order_no VARCHAR(64), status TINYINT NOT NULL DEFAULT 0, retry_count TINYINT NOT NULL DEFAULT 0, callback_at DATETIME, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_order_no (order_no) );status这个字段可以简化定义:0代表已推WMS未回传,1代表已回传旺店通,2代表两边确认异常。每次推单前先查这张表,status已经是1或2就跳过。这张表同时也为第4.3节的每日对账提供了数据源,一举两得。注意callback_at记录的是回传成功时间而不是WMS发货时间,两个时间都可能被问到,别只记一个。
4.3 每天对账一次:把两边的账平掉
代码写得再干净,两边库存也会因为漏单、重复扣减、人工操作产生偏差。我上线第一天就会加一个每日对账任务,它的职责不是修数据,是把偏差暴露出来。思路很简单:分别从旺店通和WMS拉当天已发货的单号集合,求差集。
def reconcile_deliveries(date: str): wdt_no = query_wdt_delivered(date) # 旺店通当天已发货单号 wms_no = query_wms_outbound(date) # WMS 当天已完成出库的来源单号 diff1 = wdt_no - wms_no # 旺店通已发,WMS 没有:回传可能丢了 diff2 = wms_no - wdt_no # WMS 已发,旺店通没记录:漏推或人工建单 print(f"diff1={len(diff1)}, diff2={len(diff2)}") print("diff1 sample:", list(diff1)[:5]) print("diff2 sample:", list(diff2)[:5])对账结果里,diff1往往是旺店通回传失败,需要重推回传;diff2通常是旺店通侧漏推或WMS手工建单,需要人工确认。对账脚本放进cron每天早上六点跑一次,输出一个文本文件,值早班的人扫一眼就能决定要不要处理。不要试图让脚本自动修复数据,自动修复在两边系统里是最危险的权限。这里再补一句:对账时间窗口选择要注意时区,电商仓库常到凌晨一两点还在发货,早上六点对账正好把最后一班夜班的发货数据含进去。
5. 旺店通WMS对接常见问题排查与避坑清单
我在这条对接链路上踩过的坑,按“现象、原因、解决”三条写,给后来人省点排查时间。这些问题不分先后,每一个都真实发生在我自己或身边同事的线上环境里。
5.1 签名失败:接口报“签名错误”却看不出哪里错
现象:按文档写了签名,接口还是返回“签名错误”。原因通常是三个。第一个,签名串里混进了sign本身,计算时把上一个请求的sign带进了参数集;第二个,AppSecret拷贝时带了空格或换行,肉眼看不出来;第三个,参数里有int型但你排序时转成了字符串,排序结果和文档示例不一致。解决:在build_sign里把raw通过日志打印出来,对照文档的待签串示例逐字符比对。确认请求里带上来的params和签名时用的params是同一份,不要一边改一边签。这个坑基本是所有开放平台对接的开门砖,过了它后面才谈得上业务。
5.2 推单报“重复单号”或WMS出现重复作业
现象:同一张订单被推了两次,WMS作业池里出现两个出库任务。原因:轮询拉单时状态标记只写在内存里,服务一重启内存状态就丢;或者超时后直接重试,WMS那边其实已经建单。解决:推单前先查本地流水表,order_no已存在就跳过。超时后用WMS的查询接口先查后建。还有一个兜底做法:在流水表的order_no上建唯一索引,让数据库拒绝重复订单,代码层漏了他还能接住。这个兜底是我被重复作业坑过两次之后才加上的,属于典型的后悔药。
5.3 库存回传后两边数据对不上
现象:旺店通库存和WMS库存每天差几个数。原因:两边扣减时机不同,旺店通常见的是审核即锁库,WMS常见的是拣货完成才扣减,这个时间差在正常业务里是合理的,不算故障。如果差值持续扩大,就要查有没有退款单、取消单被漏回传。解决:定义清楚以哪边库存为准,通常以WMS实物库存为准,每天全量同步一次。同步时注意冻结库存和可用库存要分开处理,别把冻结库存当可用库存往下发。我见过一个团队因为没区分冻结库存,大促期间把预留给售后换货的货全发出去了。
5.4 回调丢失:WMS发完货,ERP一直显示待发货
现象:仓库已经发货,ERP订单状态几个小时不更新。原因:WMS的回调URL指向了内网地址或已失效的公网地址;回调处理函数抛了异常却返回code 0,WMS认为已送达不再重试;或者回调报文里字段名和你解析的不一致,解析结果为空但没报错。解决:回调处理里对旺店通回传失败必须返回非0。同时加一个轮询兜底任务,每半小时把“已推WMS但超两小时未回传”的单子捞出来,主动向旺店通确认状态。这个兜底是上了生产之后再不敢省的部分,也是整个链路里最值得花时间做的功能。
5.5 货品档案不一致:推单到WMS报“货品不存在”
现象:订单推过去,WMS返回“货品不存在”或“SKU未建档”。原因:旺店通货号和WMS的sku_code对不上;或者WMS侧的货主没建对,货品建到了别的货主下。解决:上线前先跑一个货品档案全量同步,把旺店通货品列表拉到WMS建档。日常推单前先校验映射表里该货号是否存在。凡是碰到货品层面的错误,优先怀疑数据而不是代码,先查WMS基础资料再查代码逻辑,能少走半天弯路。还有单位问题,旺店通按“件”的货品,到WMS可能按“个”,数量对不上,这种问题查日志查不出来,只能靠现场盘点发现。
6. 验收清单与上线后的运维习惯:跑稳比跑通更重要
6.1 验收清单:把链路钉死再放量
我每次上线前都会按下面这张清单过一遍,全绿才敢推量:
| 验收项 | 操作方法 | 通过标准 |
|---|---|---|
| 幂等 | 同一订单连续请求两次 | WMS只有一个出库单 |
| 回传 | 在WMS手工标记完成 | 2分钟内旺店通可见物流单号 |
| 库存对账 | 跑第4.3节脚本 | 差集为空或已被说明 |
| 容错 | 停掉WMS十分钟再恢复 | 积压单自动补齐,无重复单 |
6.2 上线第一周必盯的三个指标
第一个指标是旺店通接口返回非0 code的比例,超过0.5%就该看日志。第二个是WMS回调成功率,直接反映链路通不通。第三个是本地流水表里status=2的异常单数量,这部分会越积越多,必须每天清一次。我习惯用一行命令守夜:
grep -E "error|cannot|timeout" /var/log/wdt_bridge.log | awk '{print $1}' | sort | uniq -c把这条命令挂到服务器终端上,早上起来先看一眼计数,比盯着面板报表更直接。前两周我会把日志级别调到DEBUG,两周后再降回INFO,DEBUG日志能帮你快速定位字段解析问题,但一直开着会占满磁盘。
6.3 一个我保留至今的习惯
接手这套对接后,我养成了一个习惯:所有订单号相关的日志,一律同时打印订单号和WMS单号,方便按任意一边去反向查。血泪经验是去年有一次生产翻车,两边客服各自按自己的单号查数据,对不上号,我只能翻原始请求报文手工拼接,那个下午过得非常煎熬。后来我把流水表的主查询从order_no扩展成order_no和wms_order_no双索引,再没出现过两边对不上证据的情况。跑稳一个对接方案,一半靠代码,另一半靠日志和表结构里多想一步。希望帮到你。
本文还有配套的精品资源,点击获取