《多店铺京东订单统一拉取:JOS API + 分布式调度 + 幂等设计实战》(附python源码)

简介: 本方案实现多店铺京东订单统一拉取:每店独立AccessToken、增量时间窗拉取、Redis分布式锁防并发、(tid, shop_id)数据库幂等、断点续跑防漏单。附可落地Python源码,含JOS签名、锁管理、幂等入库与断点持久化,支持APScheduler/Celery调度。(239字)

多店铺京东订单统一拉取的核心在于:每个店铺独立 AccessToken + 增量时间窗拉取 jingdong.pop.order.search + 以 (tid, shop_id) 为唯一键做数据库幂等 + 分布式锁/Redis NX 防多节点并发重复处理 + 断点续跑防漏单。下面给你一套可直接落地的 Python 实战源码。

一、整体架构

┌────────────────────┐
│ 分布式调度器 │ APScheduler / Celery Beat
│ 遍历 shop_auth 表 │
└───────┬────────────┘
│ 每店铺独立 token

┌────────────────────┐
│ JdOrderSyncPerShop │
│ 1. 获取分布式锁 │ Redis SET shop_id:sync NX EX
│ 2. 增量拉取订单 │ start_modified ~ end_modified
│ 3. 逐单查明细 │ jd.order.detail.get
│ 4. 幂等入库 │ INSERT ON CONFLICT(tid,shop_id) DO NOTHING
│ 5. 更新断点时间 │ last_sync_time
│ 6. 释放锁 │
└────────────────────┘

二、核心源码(Python · 含签名 / 分布式锁 / 幂等 / 断点)

jd_multi_shop_order_sync.py

"""
多店铺京东订单统一拉取实战

  • JOS MD5签名(秒级timestamp)
  • Redis分布式锁(防止多节点同时跑同一店铺)
  • 增量同步 modified 时间窗
  • 幂等入库 (tid, shop_id) UNIQUE
  • 断点续跑(记录 last_sync_time)
    依赖:
    pip install requests redis apscheduler
    """
    import hashlib
    import json
    import time
    import datetime
    import requests
    import redis
    from typing import Dict, List

================== 配置 ==================

APP_KEY = "YOUR_JD_APP_KEY"
APP_SECRET = "YOUR_JD_APP_SECRET"
REDIS_HOST = "localhost"
REDIS_PORT = 6379
REDIS_DB = 0
SYNC_INTERVAL_MIN = 30 # 增量窗口分钟数
LOCK_TTL_SEC = 300 # 分布式锁过期时间

==========================================

rds = redis.Redis(host=REDIS_HOST, port=REDIS_PORT, db=REDIS_DB)

---------- JOS Client ----------

class JdJosClient:
GW = "https://api.jd.com/routerjson"

def __init__(self, app_key, app_secret):

, self.ask = app_key, app_secret

def _sign(self, params: Dict) -> str:
    filt = sorted(
        (k, v) for k, v in params.items()
        if k != "sign" and v is not None and str(v).strip() != ""
    )
    qs = "".join(f"{k}{v}" for k, v in filt)
    return hashlib.md5(f"{self.ask}{qs}{self.ask}".encode()).hexdigest().upper()

def call(self, method, biz, token):
    p = {
        "app_key": self.ak,
        "method": method,
        "timestamp": str(int(time.time())),   # 秒级!
        "format": "json",
        "v": "2.0",
        "sign_method": "md5",
        "360buy_param_json": json.dumps(biz, ensure_ascii=False,
                                         separators=(',', ':')),
        "access_token": token
    }
    p["sign"] = self._sign(p)
    r = requests.post(self.GW, data=p, timeout=15)
    r.raise_for_status()
    d = r.json()

    resp_key = method.replace(".", "_") + "_response"
    if resp_key not in d:
        for k in d:
            if k.endswith("_response"):
                resp_key = k
                break
    data = d.get(resp_key, d)
    if "error_response" in str(data):
        err = d.get(resp_key, {}).get("error_response") or d.get("error_response")
        if err:
            raise Exception(f"JOS[{err.get('code')}]: {err.get('zh_desc')}")
    return data

def list_orders(self, token, start, end, page=1, page_size=100):
    return self.call("jingdong.pop.order.search", {
        "start_modified": start,
        "end_modified": end,
        "order_state": "WAIT_SELLER_STOCK_OUT,WAIT_BUYER_CONFIRM_GOODS,FINISHED",
        "page": page,
        "page_size": min(page_size, 100)
    }, token)

def order_detail(self, token, order_id):
    return self.call("jd.order.detail.get", {
        "orderId": str(order_id),
        "fields": "orderId,status,payment,buyer_nick,receiver_name,"
                  "receiver_mobile,orders.skuId,orders.outerSkuId,"
                  "orders.num,orders.price,created,modified"
    }, token)

---------- 分布式锁 ----------

def acquire_lock(shop_id: str) -> str:
"""Redis SET NX EX,返回 identifier 或 None"""
identifier = str(time.time())
lock_key = f"jd_sync_lock:{shop_id}"
ok = rds.set(lock_key, identifier, nx=True, ex=LOCK_TTL_SEC)
return identifier if ok else None

def release_lock(shop_id: str, identifier: str):
lock_key = f"jd_sync_lock:{shop_id}"

# Lua等效:仅当值匹配才删,防误释放
with rds.pipeline() as pipe:
    while True:
        try:
            pipe.watch(lock_key)
            if pipe.get(lock_key) == identifier.encode():
                pipe.multi()
                pipe.delete(lock_key)
                pipe.execute()
            break
        except redis.WatchError:
            continue

---------- 断点 & 幂等(SQLite示例,生产换 MySQL/PG) ----------

def init_db(db="jd_erp.db"):
conn = import("sqlite3").connect(db)
conn.execute("""
CREATE TABLE IF NOT EXISTS erp_order(
tid TEXT,
shop_id TEXT,
status TEXT,
payment REAL,
buyer_nick TEXT,
created TEXT,
PRIMARY KEY(tid, shop_id)
)
""")
conn.execute("""
CREATE TABLE IF NOT EXISTS shop_sync_point(
shop_id TEXT PRIMARY KEY,
last_sync_time TEXT
)
""")
conn.commit()
return conn

def load_last_time(conn, shop_id):
cur = conn.execute("SELECT last_sync_time FROM shop_sync_point WHERE shop_id=?",
(shop_id,))
row = cur.fetchone()
return row[0] if row else None

def save_last_time(conn, shop_id, t):
conn.execute("""
INSERT INTO shop_sync_point(shop_id,last_sync_time)
VALUES(?,?) ON CONFLICT(shop_id) DO UPDATE SET last_sync_time=excluded.last_sync_time
""", (shop_id, t))
conn.commit()

def upsert_order(conn, shop_id, order):
conn.execute("""
INSERT INTO erp_order(tid,shop_id,status,payment,buyer_nick,created)
VALUES(?,?,?,?,?,?)
ON CONFLICT(tid,shop_id) DO NOTHING
""", (
str(order.get("orderId")),
shop_id,
order.get("status"),
float(order.get("payment") or 0),
order.get("buyer_nick", ""),
order.get("created", "")
))
conn.commit()

---------- 单店铺同步 ----------

def sync_one_shop(client: JdJosClient, conn, shop: Dict):
shop_id = shop["shop_id"]
token = shop["access_token"]

lock_id = acquire_lock(shop_id)
if not lock_id:
    print(f"⚠️  店铺[{shop_id}] 正在其他节点同步,跳过")
    return

try:
    now = datetime.datetime.now()
    last = load_last_time(conn, shop_id)
    if last:
        start = last
    else:
        start = (now - datetime.timedelta(minutes=SYNC_INTERVAL_MIN * 2)
                ).strftime("%Y-%m-%d %H:%M:%S")
    end = now.strftime("%Y-%m-%d %H:%M:%S")

    print(f"▶ 店铺[{shop_id}] 增量 {start} ~ {end}")

    page = 1
    while True:
        rsp = client.list_orders(token, start, end, page=page)
        order_list = (rsp.get("popOrderSearch", {})
                      .get("orderSearch", {})
                      .get("orderInfoList", [])) or []
        total = rsp.get("popOrderSearch", {}).get("orderSearch", {}
                                                  ).get("orderTotal", 0)

        for o in order_list:
            tid = str(o.get("orderId"))
            # 明细(按需,列表已有基础字段可先upsert)
            detail = client.order_detail(token, tid)
            order_data = detail.get("orderInfo", o)
            upsert_order(conn, shop_id, order_data)

        if len(order_list) < 100 or page * 100 >= total:
            break
        page += 1
        time.sleep(0.15)   # QPS保护

    # 成功更新断点(用当前end,下次从此继续)
    save_last_time(conn, shop_id, end)
    print(f"✅ 店铺[{shop_id}] 同步完成,断点更新至 {end}")

except Exception as e:
    print(f"❌ 店铺[{shop_id}] 同步异常: {e}")
finally:
    release_lock(shop_id, lock_id)

---------- 多店铺入口 ----------

def sync_all_shops(shop_list: List[Dict]):
client = JdJosClient(APP_KEY, APP_SECRET)
conn = init_db()
for shop in shop_list:
try:
sync_one_shop(client, conn, shop)
except Exception as e:
print(f"❌ 顶层异常 shop={shop.get('shop_id')}: {e}")

===================== 示例 shop_list =====================

if name == "main":
SHOP_LIST = [
{"shop_id": "SHOP001", "access_token": "SELLER_TOKEN_A"},
{"shop_id": "SHOP002", "access_token": "SELLER_TOKEN_B"},
]
sync_all_shops(SHOP_LIST)

# ---- 常驻调度示例(取消注释)----
# from apscheduler.schedulers.blocking import BlockingScheduler
# sched = BlockingScheduler()
# sched.add_job(lambda: sync_all_shops(SHOP_LIST),
#               'interval', minutes=SYNC_INTERVAL_MIN, id='jd_multi_shop_sync')
# sched.start()

三、关键设计点说明

  1. 多店铺隔离
    每个店铺一条 shop_auth 记录,存 shop_id / access_token / refresh_token,同步时按 shop_id 独立跑、独立锁、独立断点时间。

  2. 分布式锁(Redis SET NX EX)
    防止多节点 / 多进程同时跑同一店铺导致重复调用API、重复入库。

  3. 幂等设计
    本地订单表 PRIMARY KEY (tid, shop_id),处理前数据库层 ON CONFLICT DO NOTHING,即使重试/重复推送也不重复建单。

  4. 增量 + 断点续跑
    记录 last_sync_time,每次用 start_modified=last_sync_time, end_modified=now 拉取,服务重启从中继续,避免漏单与超日额度。

  5. QPS保护
    分页间 time.sleep(0.15) + 令牌桶(前文京东限流篇已给),遇 code=16 / ISP_FLOW_CONTROL 指数退避重试。

  6. 对账兜底
    每日凌晨全量比对一次最近 N 天订单,修正极端情况下漏掉的状态变更(回调丢失/网络抖动)。

四、避坑清单

坑 现象 解决

用毫秒时间戳 Invalid Timestamp JOS用秒级 int(time.time())

订单403 传买家token / 个人应用 企业认证 + 卖家OAuth AccessToken

多节点重复入库 无幂等/无锁 (tid,shop_id) 唯一索引 + Redis锁

重启后重复拉全量 无断点记录 持久化 last_sync_time

超日额度 limited-by-app-access-count 增量同步 + 断点,避免全量翻页

一句话总结:多店铺京东订单同步 = 每店铺独立 Seller Token → 增量 modified 时间窗 → Redis NX 锁防并发 → (tid,shop_id) 幂等入库 → 记录 last_sync_time 断点续跑 → 日对账兜底,签名/QPS 同单店铺,仅调度与隔离维度不同。

相关文章
|
4天前
|
缓存 人工智能 安全
GPT-5.6 Terra与GPT-5.5性能实测:成本减半后的跑分对比与快速迁移指南
GPT-5.6 Terra 的定价为每百万 token 输入 2.50/输出 15。GPT-5.5 则是 5/ 30。Terra 的每一项费率,包括 $0.25/M 的缓存读取,都恰好是 GPT-5.5 的一半,因此在任何工作负载组合下,Terra 都固定 便宜 2.0x。以每天 10 万次请求、3K token 提示词计算,大约是 Terra 每天 2,000,GPT−5.5每天 4,000,即每月约 60,000对 120,000。问题在于:OpenAI 没有发布任何针对 Terra 的编码基准。那个著名的 91.9% Terminal-Bench 数字是 Sol 在 Ul
|
4天前
|
SQL 人工智能 自然语言处理
大模型内容安全实时防护:恶意Prompt注入拦截、越权阻断与熔断机制方案.166
本文系统阐述大模型输入安全防护体系,涵盖提示词注入、恶意Prompt拦截、越权阻断与输入熔断四大核心风险及应对方案。提出四层防护架构(预处理、检测、鉴权、熔断),结合规则引擎、语义识别与RBAC权限控制,实现全链路实时防护,保障业务合规、数据安全与服务稳定。
237 1
|
28天前
|
Linux 程序员 数据格式
【2026最新】Notepad++下载、安装和使用一篇搞定(附中文版安装包)
Notepad++ 是一款免费开源、轻量高效的 Windows 文本编辑器,支持 C/Python/HTML 等 80+ 语言语法高亮、代码折叠、正则替换、编码转换及插件扩展,专为程序员与文本处理用户打造,完美替代系统记事本。(239字)
|
4天前
|
存储 人工智能 JSON
Qwen 本地部署搭配 ComfyUI 生成 AI 漫剧完整实操指南(小白零基础可落地,零成本无限生成+角色一致性天花板)
2026全网最优本地漫剧流水线:零成本、离线运行、角色统一、低配(8G显卡)可跑。融合Qwen本地大模型+ComfyUI双引擎,实现剧本生成→分镜绘图→动态成片全自动,隐私安全、无审核限流,新手30分钟上手,日更无忧。(239字)
|
12天前
|
人工智能 编解码 物联网
2026 最新Stable Diffusion 本地部署教程 下载安装使用详细图解(含官网安装包)
Stable Diffusion(SD)是2022年发布的开源文生图模型,由Stability AI等联合开发。支持文生图、图生图、局部重绘等,依托VAE降低算力需求,可在消费级显卡运行。本文提供秋葉aaaki制作的Windows整合包(含图形界面与插件),开箱即用,零配置启动。
|
4天前
|
人工智能 缓存 JSON
刚刚 GPT-5.6 发布,吊打 Claude 5 和 Grok 4.5?一手实测来啦!
GPT-5.6 刚发布,跑分号称超越 Fable 5,真的假的?附一手实测,让 3 个最强 AI 编程模型 GPT-5.6 Sol、Claude Fable 5、Grok 4.5 在 Cursor 里同时一把梭开发网页游戏,看看最新模型到底谁更能打。
290 0
|
22天前
|
存储 人工智能 监控
QoderWork完全指南:从入门到精通,把“AI实习生”变成你的全能工作搭档
阿里云2026年推出的桌面端AI工作助手QoderWork,不止聊天,更可动手干活:本地运行、安全可控,支持文件整理、数据分析、PPT生成、网页开发等;内置专家套件、多Agent协作与自定义Skills,让AI真正成为你身边的“AI实习生”。
|
13天前
|
人工智能 缓存 安全
Claude Code 封号真实原因曝光,这次彻底不装了,直接针对国内开发者的账号下手?
Claude Code 封号潮背后:逆向扒出客户端隐写区域标记,Anthropic 政策收紧叠加 DeepSeek 7 月涨价,国产替代更紧迫。