diff --git a/scbank_processor.py b/scbank_processor.py index d506f99..f21d1fc 100644 --- a/scbank_processor.py +++ b/scbank_processor.py @@ -4,39 +4,132 @@ import os import shutil import random import pandas as pd +import requests from datetime import datetime class InternalApiClient: """ - 内部接口客户端桩代码 (Mock) + 内部接口客户端 (Real) """ def __init__(self): - self.api_url = "http://internal-system.local/api" + # 生产环境 + # self.api_base_url = "https://hyt.86698.cn/open" + # 测试环境 (默认) + self.api_base_url = "http://120.55.12.245:8109" + # 占位符 AppId,实际需替换 + # self.app_id = "8ce4d435fa77492f84d2fafa241c2804" + self.app_id = "e699e6ef74504f4d86776b3d244ce602" + + def _post(self, path, data): + """发送 POST 请求""" + url = f"{self.api_base_url}{path}" + headers = { + "AppId": self.app_id, + "Content-Type": "application/json;charset=UTF-8" + } + try: + # 不验签模式: 直接发送 data json + # 真实场景需确认: 这里的 data 是作为 json body 发送,还是作为 params? + # 根据文档 '非加密请求: 按业务正常的请求参数进行传输' -> 通常是 json body + response = requests.post(url, json=data, headers=headers, timeout=10) + return response.json() + except Exception as e: + print(f"[API Error] {url}: {e}") + return None + def get_internal_product_id(self, scbank_goods_name): """ - Mock: 根据商城商品名查询内部商品编号 + 根据商城商品名查询内部商品编号 (Product Code) + 接口: /api/v1/open/cus/goods/list + 逻辑: 模糊搜索 -> 必须有且仅有1个结果 -> 返回 goods_num """ - # 模拟延时 - time.sleep(0.1) if not scbank_goods_name: return None - # 简单模拟 - if "iPhone" in scbank_goods_name: return "P_APPLE_001" - if "华为" in scbank_goods_name: return "P_HUAWEI_002" - return "P_UNKNOWN_999" + payload = { + "title": scbank_goods_name, + "page": 1, + "limit": 10 + } + + resp = self._post("/api/v1/open/cus/goods/list", payload) + + # 检查响应 + if resp and resp.get("code") == 200: + data = resp.get("data", {}) + # 兼容 data 可能是 list 或 dict (根据文档响应报文 data 包含 list 和 total) + # 文档: data -> {list: [], total: int} + product_list = data.get("list", []) + total = data.get("total", 0) + + if product_list and len(product_list) == 1: + return product_list[0].get("goods_num") + else: + print(f"[Match Fail] '{scbank_goods_name}' 匹配到 {len(product_list)} 个商品") + return None + + return None - def upload_order(self, order_detail): + def upload_order(self, order_detail, internal_pid): """ - Mock: 上传订单 - 返回: (Success: bool, Message: str) + 上传订单 + 接口: /api/v1/open/order/submit """ - time.sleep(0.2) - # 模拟随机成功率 90% - if random.random() < 0.9: + if not internal_pid: + return False, "商品匹配失败: 未找到或找到多个商品" + + # 映射字段 + # scbank detail -> internal api payload + + # 收件人信息 + receive_info = order_detail.get("mallOrderReceiveInfo", {}) + + # 构建 payload + payload = { + "orderBasic": { + "customer_order_num": order_detail.get("orderNo"), + "consignee": receive_info.get("receiverName", "未知"), + "consignee_mobile": receive_info.get("receiverMobile", "00000000000"), + + # 省市区编码 (文档必填但用户说是误写,传空或默认值) + "consignee_province_code": "", + "consignee_city_code": "", + "consignee_area_code": "", + + # 详细地址 (用户要求使用完整地址) + "consignee_address": receive_info.get("fullAddress", ""), + + # 客户备注 - 没找到,传空 + "in_remark": receive_info.get("remark", ""), + }, + "goodsList": [ + { + "goods_num": internal_pid, + "number": 1, # 默认为 1,需确认 scbank 数据是否有数量 + "sale_price": order_detail.get("orderAmt", 0) # 暂用订单总额作为单价 + } + ] + } + + # 修正数量与价格: 如果有 goodsInfoList,尝试获取更准确的数量 + goods_info = order_detail.get("goodsInfoList", []) + if goods_info: + first_good = goods_info[0] + payload["goodsList"][0]["number"] = first_good.get("count", 1) + payload["goodsList"][0]["sale_price"] = first_good.get("price", 0) + + # 查看 payload + print("=== 上传订单 Payload ===") + print(json.dumps(payload, ensure_ascii=False, indent=2)) + print("=== 上传订单 Payload End ===") + + resp = self._post("/api/v1/open/order/submit", payload) + + if resp and resp.get("code") == 200: return True, "上传成功" else: - return False, "模拟网络超时" + msg = resp.get("message") if resp else "请求失败" + return False, f"API错误: {msg}" class SCBankProcessor: def __init__(self): @@ -81,37 +174,49 @@ class SCBankProcessor: self.log("文件内容为空,跳过") return - # 3. 数据结构化 - # 提取详情数据 (以此为主) - details = [] - for r in raw_records: - if r.get("type") == "detail": - payload = r.get("payload", {}) - # 根据真实数据结构: payload -> body -> orderNo - body = payload.get("body", {}) - if body and "orderNo" in str(body): - # 如果 body 是字符串,尝试解析 - if isinstance(body, str): - try: body = json.loads(body) - except: pass - - details.append(body) - - # 去重 (按 orderNo) - # 注意:这里假设 payload 是 dict,如果已经是 dict 则直接用 - # 如果是字符串则需要 json.loads + # 3. 数据结构化 (合并列表与详情) unique_details = {} - for d in details: - # 兼容 payload 可能是字符串的情况 (虽然 Collector 存的是 dict) - if isinstance(d, str): - try: d = json.loads(d) + + for r in raw_records: + r_type = r.get("type") + payload = r.get("payload", {}) + body = payload.get("body", {}) + + # 兼容 body 可能是字符串的情况 + if isinstance(body, str): + try: body = json.loads(body) except: continue - order_no = d.get("orderNo") or d.get("orderId") - if order_no: - unique_details[order_no] = d + if not isinstance(body, dict): continue - self.log(f"解析出 {len(unique_details)} 条有效唯一订单详情") + if r_type == "list": + page_list = body.get("pageDataList", []) + if isinstance(page_list, list): + for item in page_list: + order_no = item.get("orderNo") + if not order_no: continue + + if order_no not in unique_details: + unique_details[order_no] = item + else: + # 列表数据补全:仅当字段缺失或为空时填充 + for k, v in item.items(): + # 列表中的 goodsInfoList 通常为空,跳过 + if k == "goodsInfoList" and not v: continue + if k not in unique_details[order_no] or not unique_details[order_no][k]: + unique_details[order_no][k] = v + + elif r_type == "detail": + order_no = body.get("orderNo") + if not order_no: continue + + if order_no not in unique_details: + unique_details[order_no] = body + else: + # 详情数据覆盖 (高优先级) + unique_details[order_no].update(body) + + self.log(f"解析出 {len(unique_details)} 条唯一订单 (列表+详情合并)") # 4. 业务处理与同步 results = [] @@ -127,7 +232,8 @@ class SCBankProcessor: internal_pid = self.client.get_internal_product_id(goods_name) # Step 2: 上传 - success, msg = self.client.upload_order(detail) + # 注意: 这里传入 internal_pid + success, msg = self.client.upload_order(detail, internal_pid) # 时间格式化: 2026-03-10T00:50:37.000+0000 -> 2026-03-10 00:50:37 order_time = detail.get("orderCreateTime")