ERP对接模块.md 16 KB

ERP对接模块说明

本文档描述企得宝 ERP 系统集成方案,涵盖 SDK 封装、数据同步、商品/店铺关联管理的完整架构。 帮助开发人员理解 ERP 对接的设计决策和关键流程。


一、模块概述

1.1 对接目标

通过企得宝 ERP 开放平台 API(https://api.7debao.com/router/api),实现:

  • 将本地商品与 ERP 商品建立一对一关联
  • 将本地设备与 ERP 店铺建立一对一绑定(ERP 店铺对应系统设备)
  • 后续补货单可推送至 ERP 生成采购单据

1.2 架构总览

┌─────────────────────────────────────────────────────┐
│                   haha-admin-web                     │
│  erp-goods/index.vue     erp-shop/index.vue          │
└──────────────────┬──────────────────────────────────┘
                   │ HTTP API
┌──────────────────▼──────────────────────────────────┐
│                   haha-admin                         │
│  ErpGoodsController     ErpShopController            │
│  QdbSdkConfig (QdbClient Bean)                      │
└──────────────────┬──────────────────────────────────┘
                   │
┌──────────────────▼──────────────────────────────────┐
│                   haha-service                       │
│  ErpGoodsService        ErpShopService               │
│  (sync / bind / unbind / autoBind)                   │
└──────────────────┬──────────────────────────────────┘
                   │
┌──────────────────▼──────────────────────────────────┐
│                   qdb-sdk (子模块)                    │
│  QdbClient → HttpUtil (签名 + HTTP)                  │
│  GoodsApi     ShopApi     OrderApi                   │
└──────────────────┬──────────────────────────────────┘
                   │
┌──────────────────▼──────────────────────────────────┐
│              企得宝 ERP API                           │
│          api.7debao.com/router/api                   │
│  foonsu.erp.sysGoods.list    (系统商品查询)           │
│  foonsu.erp.sysGoods.save    (系统商品上传)           │
│  foonsu.erp.sysShop.list     (店铺查询)               │
│  foonsu.erp.order.save       (订单上传)               │
└─────────────────────────────────────────────────────┘

二、数据库设计

2.1 ER图(核心表)

t_product                    t_erp_goods              企得宝ERP
┌──────────────┐            ┌──────────────────┐      ┌──────────┐
│ id           │            │ id               │      │ goodsId  │
│ code         │            │ erp_goods_id ◄───┼──────┤ goodsCode │
│ name ◄───────┼────────────┤ goods_code       │      │ goodsName│
│ erp_goods_id ├───────────►│ goods_name       │      │ specs    │
│ barcode      │            │ specs            │      │ skUnit   │
│ pic          │            │ sale_price       │      │ picPath  │
│ ...          │            │ bar_code(JSON)   │      │ ...      │
└──────────────┘            │ modify_date      │      └──────────┘
                            │ status           │
t_device                    └──────────────────┘      企得宝ERP
┌──────────────┐            t_erp_shop                 ┌──────────┐
│ id           │            ┌──────────────────┐      │ shopId   │
│ deviceId ◄───┼────────────┤ shop_code        │      │ shopCode │
│ erp_shop_id  ├───────────►│ erp_shop_id ◄────┼──────┤ shopName │
│ name         │            │ shop_name        │      │ ...      │
│ ...          │            │ nick_name        │      └──────────┘
└──────────────┘            │ status           │
                            └──────────────────┘

2.2 关联关系

  • t_product.erp_goods_idt_erp_goods.erp_goods_id:本地商品与 ERP 商品的一对一关联
  • t_device.erp_shop_idt_erp_shop.erp_shop_id:本地设备与 ERP 店铺的一对一绑定(ERP 店铺对应系统中的设备,ERP 的 shopCode = 设备的 deviceId
  • t_product.name = t_erp_goods.goods_code:商品名称即 ERP 编码,是一键自动关联的依据
  • t_device.deviceId = t_erp_shop.shopCode:设备ID即 ERP 店铺编码,是一键自动绑定的依据

2.3 DDL脚本

脚本 用途
docs/database/create_erp_goods.sql 创建 t_erp_goods 缓存表
docs/database/create_erp_shop.sql 创建 t_erp_shop 缓存表
docs/database/add_erp_goods_id.sql t_product 增加 erp_goods_id 字段
docs/database/add_erp_shop_id.sql t_shop 增加 erp_shop_id 字段
docs/database/add_device_erp_shop_id.sql t_device 增加 erp_shop_id 字段
docs/database/add_erp_goods_bar_code.sql t_erp_goods 增加 bar_codemodify_date
docs/database/menu_add_erp_goods.sql 插入"ERP商品关联"菜单
docs/database/menu_add_erp_shop.sql 插入"ERP店铺管理"菜单
docs/database/add_erp_permissions.sql 插入 ERP 功能权限数据

三、SDK集成 (qdb-sdk)

3.1 模块定位

qdb-sdkhaha-parent 的子模块(com.haha:qdb-sdk),统一版本管理。提供企得宝 ERP 开放 API 的 Java SDK 封装。

3.2 核心类

职责
QdbConfig SDK 配置(clientId、clientSecret、apiUrl、超时等)
QdbClient SDK 入口,持有各业务 API 实例 + HttpUtil
HttpUtil HTTP 请求封装(签名、OkHttp 调用)
SignUtil MD5 签名算法
BaseQdbApi API 基类,提供 execute() 通用调用方法
GoodsApi 商品管理 API(查询、上传)
ShopApi 店铺管理 API(查询)
OrderApi 订单管理 API(上传、查询)

3.3 Bean 配置

QdbSdkConfig(haha-admin 模块):

@Configuration
public class QdbSdkConfig {
    @Value("${qdb.client-id:}")
    private String clientId;
    @Value("${qdb.client-secret:}")
    private String clientSecret;
    @Value("${qdb.api-url:https://api.7debao.com/router/api}")
    private String apiUrl;

    @Bean
    @ConditionalOnProperty(prefix = "qdb", name = {"client-id", "client-secret"})
    public QdbClient qdbClient() { ... }
}

配置项(application.yml):

qdb:
  client-id: fs2009263
  client-secret: your_secret_here
  api-url: https://api.7debao.com/router/api

安全提醒client-idclient-secret 应使用环境变量或外部配置文件管理,避免提交到代码仓库。

3.4 API 调用方式

ERP API 统一 POST 到 https://api.7debao.com/router/api

  • 公共参数(client_id, method, timestamp, sign...)拼接到 URL Query String
  • 业务参数以 JSON 放入 Request Body
  • 签名:公共参数按 key 字典序排列 → 拼接 &key=clientSecret → MD5

四、ERP商品同步

4.1 同步策略

不是拉取 ERP 全量商品,而是以本地商品为基准逐条反查

原因:ERP 中有多个客户的数据,全量拉取无法区分哪些是本系统需要的。系统只关心已在哈哈平台上架的商品。

4.2 同步流程

① SELECT t_product WHERE is_deleted=0 AND name IS NOT NULL
     → 获取所有本地商品(name 作为 ERP goodsCode)

② 逐条调用 foonsu.erp.sysGoods.list
     参数: { pageIndex:1, pageSize:1, goodsCode: product.name, status:1, combination:0 }
     频率控制: 每 10 条暂停 200ms

③ ERP 返回: { data: { total: N, data: [{ goodsId, goodsCode, goodsName, ... }] } }
     → GoodsApi 自动拆包提取内层数组 → List<SysGoodsVO>

④ 命中 → upsert 到 t_erp_goods(按 erp_goods_id 判断新增/更新)
   未命中 → 记入日志,在同步完成后整齐打印清单

4.3 异步执行

ErpGoodsServiceImpl.syncFromErp() 标注 @Async("taskExecutor"),接口 POST /erp-goods/sync 立即返回"同步任务已启动",后台线程池执行。

4.4 保存字段

同步时保存:goodsId, goodsCode, goodsName, shortGoodsName, specs, skUnit, goodsKindCode, goodsKindName, brand, salePrice, costPrice, providerName, batchType, combination, articleNo, picPath, weight, barCodeList(JSON), modifyDate


五、商品关联

5.1 手动关联/解除

  • POST /erp-goods/bind — 选择本地商品与 ERP 商品一对一关联
  • POST /erp-goods/unbind — 解除关联(使用 LambdaUpdateWrapper.set(null) 强制写 NULL,避免 MP 默认跳过 null 字段)

5.2 一键自动关联

  • POST /erp-goods/auto-bind — 遍历所有 ERP 缓存商品,用 goodsCode 匹配本地 product.name
  • 唯一匹配 → 自动绑定;0 个或多于 1 个匹配 → 跳过
  • 批量优化
    • 查询已绑定:1 次 WHERE erp_goods_id IN (...)
    • 查询匹配:每 100 个 code 一批 WHERE name IN (...)
    • 批量 UPDATE:CASE WHEN SQL,每 100 条一批

六、ERP店铺管理(设备绑定)

6.1 同步

  • POST /erp-shops/sync — 从 ERP 全量翻页查询店铺列表
  • 名称过滤:只保存店铺名称包含"自助售卖机"的记录到 t_erp_shop
  • 异步执行@Async("taskExecutor"),接口立即返回
  • 日志区分 syncedCount / filteredCount

6.2 手动绑定/解绑

  • POST /erp-shops/bind — 选择本地设备与 ERP 店铺一对一绑定
  • POST /erp-shops/unbind — 解除绑定(使用 LambdaUpdateWrapper.set(null) 强制写 NULL)
  • 绑定对象是 设备t_device),非门店。ERP 店铺的 shopCode 对应设备的 deviceId

6.3 一键自动绑定

  • POST /erp-shops/auto-bind — 遍历所有 ERP 缓存店铺,用 shopCode 匹配设备 deviceId
  • 匹配成功 → 自动绑定;未匹配到设备 → 跳过
  • 批量优化
    • 查询已绑定:1 次 WHERE erp_shop_id IN (...)
    • 查询设备:每 100 个 deviceId 一批 WHERE device_id IN (...)
    • 批量 UPDATE:CASE WHEN SQL,每 100 条一批

6.4 列表页

  • 分页展示(10/20/50 条/页),支持搜索店铺名称/卖家账号
  • 列:ERP店铺ID、店铺名称、卖家账号、设备ID、绑定设备、操作
  • 设备绑定标签悬停浮窗展示设备详情(deviceId、名称、门店、地址)

七、权限配置

7.1 权限码

权限码 说明 类型
erp:goods:sync ERP商品同步 execute
erp:goods:read ERP商品查看 read
erp:goods:bind ERP商品关联/解绑/一键关联 update
erp:shop:sync ERP店铺同步 execute
erp:shop:read ERP店铺查看 read
erp:shop:bind ERP店铺绑定/解绑 update

7.2 菜单

菜单ID 父菜单 路径 名称
404 商品管理(400) /product/erp-goods ERP商品关联
305 运营管理(300) /operation/erp-shop ERP店铺管理

7.3 权限校验机制

  • @RequirePermission 注解通过 AOP 切面(PermissionAspect)校验
  • 超级管理员(role_id=1)直接放行,不查权限表
  • 非超管角色需在 t_data_permission 表有对应权限码,且在 t_role_menu 表有菜单授权

八、前端页面

8.1 ERP商品关联 erp-goods/index.vue

功能 描述
列表 分页展示 ERP 商品(图片、编码、名称、规格、单位、售价、关联状态)
搜索 按商品名称/编码搜索
同步 异步后台执行,完成后需手动刷新
一键关联 按 goodsCode 自动匹配本地商品
手动关联弹窗 展示 ERP 商品信息卡片 + 带缩略图的下拉选择框
解除关联 确认弹窗后解除
关联浮窗 悬停已关联标签时展示本地商品详情(含图片预览)

8.2 ERP店铺管理 erp-shop/index.vue

功能 描述
列表 分页展示 ERP 店铺(店铺ID、名称、卖家账号、设备ID、绑定状态)
搜索 按店铺名称/卖家账号搜索
同步 异步后台执行,过滤"自助售卖机"名称
一键绑定 按 shopCode 自动匹配设备 deviceId
手动绑定弹窗 选择本地设备进行绑定
解除绑定 确认弹窗后解除
设备浮窗 悬停已绑定设备标签时展示设备详情(deviceId、名称、门店、地址)

8.3 Vite 代理配置

/erp-goods/erp-shops 路径需在 vite.config.tsapiPaths 数组中注册,否则开发环境请求不会被转发到后端。


九、关键设计决策

9.1 为什么不用 GoodsApi 的泛型直接解析?

ERP 的 foonsu.erp.sysGoods.list 返回的 data 字段是嵌套结构 { total: N, data: [...] },不是扁平数组。GoodsApi.listSysGoods() 内部用 JSONObject 接收原始 data,再手动提取内层数组,避免 fastjson2 泛型解析的类型不匹配。

9.2 为什么同步用 goodsCode 逐条查而不是全量翻页?

ERP 中有多客户的商品数据,全量拉取无法过滤。只有本地商品的 name 字段才对应 ERP 的 goodsCode,以此为 key 逐条精确查询是最准确的方式。

9.3 为什么解除关联用 LambdaUpdateWrapper.set(null)?

MyBatis-Plus 默认 FieldStrategy.NOT_NULLupdateById(entity) 会跳过 null 值字段。设置 erpGoodsId = null 时需要使用 LambdaUpdateWrapper.set(Product::getErpGoodsId, null) 强制写入。

9.4 为什么 qdb-sdk 改为 haha-parent 子模块?

com.qdb:qdb-sdk:1.0.0 是独立模块,与 com.haha:qdb-sdk 并存会导致 classpath 上有两份 GoodsApi.class,旧版本优先加载 → NoSuchMethodError / ClassCastException。改为子模块统一 version,删除 Maven 本地旧 jar 后解决。

9.5 为什么 ERP 店铺绑定的是设备而不是门店?

ERP 中的店铺(自助售卖机)在业务上对应的是系统中的一个设备(售货机)。ERP 的 shopCode 字段存储的是设备 ID(deviceId),因此绑定关系建立在 t_device 表上。一键绑定时用 shopCodedeviceId 做精确匹配。

9.6 为什么同步和自动绑定都要用 CASE WHEN 批量 UPDATE?

逐条 UPDATE 在数据量较大时性能很差(N 次网络往返)。CASE WHEN 将多条 UPDATE 合并为一条 SQL,每 100 行一批,大幅减少数据库交互次数。


十、待完成

项目 状态 说明
补货单推送 ERP TODO ReplenishmentOrderServiceImpl.syncToErp() 当前为 no-op
商品增量同步 未实现 每次全量遍历本地商品,无时间戳过滤
店铺增量同步 未实现 每次全量翻页,无时间戳过滤

创建日期:2026-07-09 维护者:开发团队