# 企得宝ERP Java SDK

[![Java](https://img.shields.io/badge/Java-21%2B-orange)](https://www.oracle.com/java/technologies/downloads/)
[![Maven](https://img.shields.io/badge/Maven-3.6%2B-red)](https://maven.apache.org/)

**qdb-sdk** 是企得宝ERP开放平台的 Java SDK，提供对 73 个开放API接口的完整封装。支持签名生成、请求发送、响应解析等全链路功能。

---

## 📦 技术栈

| 技术 | 版本 | 用途 |
|------|------|------|
| Java | 21 | 开发语言 |
| Maven | 3.6+ | 项目构建 |
| OkHttp | 4.12.0 | HTTP 客户端 |
| FastJson2 | 2.0.53 | JSON 序列化/反序列化 |
| Lombok | 1.18.36 | 代码简化 |
| SLF4J | 2.0.13 | 日志门面 |

> **注意**：本 SDK 为独立 Maven 项目，不依赖 haha 零售系统任何内部模块，可独立使用或引入到任意 Java 项目。

---

## 🚀 快速开始

### 1. 引入依赖

将 `qdb-sdk` 安装到本地 Maven 仓库后，在项目中添加依赖：

```xml
<dependency>
    <groupId>com.qdb</groupId>
    <artifactId>qdb-sdk</artifactId>
    <version>1.0.0</version>
</dependency>
```

或直接引入源码作为模块：

```xml
<dependency>
    <groupId>com.qdb</groupId>
    <artifactId>qdb-sdk</artifactId>
    <version>1.0.0</version>
    <scope>system</scope>
    <systemPath>${project.basedir}/lib/qdb-sdk-1.0.0.jar</systemPath>
</dependency>
```

### 2. 初始化 SDK

```java
import com.qdb.sdk.QdbClient;
import com.qdb.sdk.QdbConfig;

// 创建配置（必填：clientId 和 clientSecret）
QdbConfig config = QdbConfig.builder()
    .clientId("your_client_id")          // 开放平台分配的 clientId
    .clientSecret("your_client_secret")  // 开放平台分配的 clientSecret
    .build();

// 校验配置
config.validate();

// 创建客户端（单例使用，线程安全）
QdbClient client = new QdbClient(config);
```

### 3. 选择环境

SDK 默认接入正式环境，切换测试环境只需修改 `apiUrl`：

```java
QdbConfig config = QdbConfig.builder()
    .clientId("your_client_id")
    .clientSecret("your_client_secret")
    .apiUrl("https://apitest.7debao.com/router/api")  // 测试环境
    .build();
```

| 环境 | 默认值 |
|------|--------|
| **正式环境** | `https://api.7debao.com/router/api` |
| **测试环境** | `https://apitest.7debao.com/router/api` |

---

## 🏗️ 架构说明

### 请求流程

```
┌─────────────────────────────────────────────────────┐
│                    调用方代码                          │
│  QdbClient.getXxxApi().method(request)               │
└─────────────────┬───────────────────────────────────┘
                  │
                  ▼
┌─────────────────────────────────────────────────────┐
│                  BaseQdbApi                          │
│  ① 序列化请求参数为 JSON                              │
│  ② 调用 HttpUtil.post()                              │
│  ③ 解析响应 QdbResponse<T>                           │
│  ④ 检查 success 标识，失败则抛出 QdbException         │
└─────────────────┬───────────────────────────────────┘
                  │
                  ▼
┌─────────────────────────────────────────────────────┐
│                   HttpUtil                           │
│  ① 构建公共参数 Map（TreeMap 自动排序）               │
│  ② 调用 SignUtil 计算 MD5 签名                       │
│  ③ 拼接完整 URL：baseUrl?公共参数+sign                │
│  ④ POST 请求，Body 为业务参数 JSON                    │
│  ⑤ 返回原始响应 JSON 字符串                           │
└─────────────────┬───────────────────────────────────┘
                  │
                  ▼
┌─────────────────────────────────────────────────────┐
│              企得宝ERP 开放平台                        │
│  https://api.7debao.com/router/api                   │
└─────────────────────────────────────────────────────┘
```

### 签名算法

签名规则（与企得宝开放平台文档一致）：

1. 将所有**公共参数**按照参数名的首字母先后顺序排列（字典序）
2. 把排序后的结果按照**参数名+参数值**的方式拼接
3. 拼装好的字符串**首尾拼接** `client_secret` 进行 MD5（小写32位）

```java
// 示例：SDK 内部自动完成签名
// 公共参数排序拼接后：client_idxxxformatjsonmethodxxxpartner_idxxx...
// 加签：secret + 拼接字符串 + secret → MD5
```

### 统一响应格式

所有 API 返回统一结构：

```json
{
    "errorCode": null,
    "msg": "操作成功",
    "data": { ... },
    "date": null,
    "version": null,
    "success": true
}
```

| 字段 | 类型 | 说明 |
|------|------|------|
| errorCode | String | 错误编码，成功时为 null |
| msg | String | 提示信息 |
| data | Object | 成功时返回数据，失败为 null |
| date | Date | 响应时间 |
| version | Integer | 版本信息 |
| success | Boolean | 成功为 true，失败为 false |

---

## 💻 使用示例

### 订单上传

```java
import com.qdb.sdk.QdbClient;
import com.qdb.sdk.QdbConfig;
import com.qdb.sdk.QdbException;
import com.qdb.sdk.model.request.*;
import com.qdb.sdk.model.response.QdbResponse;

import java.math.BigDecimal;
import java.util.List;

public class OrderExample {

    public static void main(String[] args) {
        // 1. 初始化客户端
        QdbConfig config = QdbConfig.builder()
            .clientId("your_client_id")
            .clientSecret("your_client_secret")
            .build();

        QdbClient client = new QdbClient(config);

        try {
            // 2. 构建订单商品
            OrderGoods goods = OrderGoods.builder()
                .platformGoodsCode("SKU001")           // 平台商品编码
                .platformGoodsName("测试商品")           // 平台商品标题
                .quantity(new BigDecimal("2"))          // 数量
                .price(new BigDecimal("49.90"))         // 单价
                .realPrice(new BigDecimal("45.00"))     // 实际售价
                .build();

            // 3. 构建支付信息（可选）
            OrderPay pay = OrderPay.builder()
                .payTypeCode("2")                       // 微信支付
                .payTime("2024-01-24 15:30:00")
                .payNo("WX202401241530001")
                .build();

            // 4. 构建订单上传请求
            OrderSaveRequest request = OrderSaveRequest.builder()
                .shopId(9900100000000136L)              // 店铺ID
                .tradeNo("T202401240001")               // 平台交易单号
                .platformTradeStatus("02")              // 待发货
                .platformRefundStatus("0")              // 未退款
                .orderTypeCode("2")                     // 在线交易
                .postAmount(new BigDecimal("10.00"))    // 运费
                .payAmount(new BigDecimal("100.00"))    // 付款金额
                .receiverName("张三")
                .receiverMobile("13800138000")
                .receiverProvince("广东省")
                .receiverCity("深圳市")
                .receiverDistrict("南山区")
                .receiverAddress("科技园南区A栋1001")
                .ordersGoods(List.of(goods))            // 商品列表
                .ordersPay(List.of(pay))                // 支付信息（可选）
                .buyerMessage("请尽快发货")
                .build();

            // 5. 调用订单上传接口
            QdbResponse<Void> response = client.getOrderApi().saveOrder(request);

            if (response.isSuccess()) {
                System.out.println("✅ 订单上传成功");
            }

        } catch (QdbException e) {
            System.err.println("❌ 订单上传失败: [" + e.getErrorCode() + "] " + e.getMessage());

        } finally {
            // 6. 关闭客户端
            client.shutdown();
        }
    }
}
```

### Spring Boot 集成

```java
import com.qdb.sdk.QdbClient;
import com.qdb.sdk.QdbConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@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
    public QdbClient qdbClient() {
        QdbConfig config = QdbConfig.builder()
            .clientId(clientId)
            .clientSecret(clientSecret)
            .apiUrl(apiUrl)
            .build();
        return new QdbClient(config);
    }
}
```

**application.yml 配置：**

```yaml
qdb:
  client-id: your_client_id
  client-secret: your_client_secret
  api-url: https://api.7debao.com/router/api
```

---

## 📋 API 模块列表

SDK 目前已实现以下 API 模块（共 **73** 个接口），完整接口明细请参考对应文档：

### 已实现

| 模块 | API 类 | 已实现方法 | 对应文档 |
|------|--------|-----------|----------|
| 订单管理 | `OrderApi` | `saveOrder()` 订单上传 | [04-订单管理.md](./04-订单管理.md) |

### 待扩展（基础框架已就绪，按需添加）

| 模块 | 接口数 | 对应文档 |
|------|--------|----------|
| 基础资料 | 9 | [01-基础资料.md](./01-基础资料.md) |
| 商品管理 | 6 | [02-商品管理.md](./02-商品管理.md) |
| 平台商品管理（个性） | 19 | [03-平台商品管理(个性).md](./03-平台商品管理(个性).md) |
| 订单管理（其余） | 15 | [04-订单管理.md](./04-订单管理.md) |
| 采购管理 | 5 | [05-采购管理.md](./05-采购管理.md) |
| 调拨管理 | 2 | [06-调拨管理.md](./06-调拨管理.md) |
| 其他出入库管理 | 3 | [07-其他出入库管理.md](./07-其他出入库管理.md) |
| 盘点管理 | 2 | [08-盘点管理.md](./08-盘点管理.md) |
| 库存管理 | 2 | [09-库存管理.md](./09-库存管理.md) |
| 消息推送（回调接收） | 8 | [10-消息推送.md](./10-消息推送.md) |
| 路由管理 | 1 | [11-路由管理.md](./11-路由管理.md) |

---

## ⚠️ 异常处理

SDK 统一使用 `QdbException` 异常类：

```java
import com.qdb.sdk.QdbException;

try {
    QdbResponse<Void> response = client.getOrderApi().saveOrder(request);
} catch (QdbException e) {
    // 错误编码（对应企得宝API返回的 errorCode）
    String errorCode = e.getErrorCode();
    // 错误信息
    String errorMsg = e.getMessage();
    // 原始异常（如有）
    Throwable cause = e.getCause();

    System.err.println("错误码: " + errorCode + ", 信息: " + errorMsg);
}
```

### 常见异常场景

| 异常场景 | 说明 |
|----------|------|
| `clientId`/`clientSecret` 为空 | 抛出 `IllegalArgumentException`，配置时需调用 `config.validate()` |
| 网络异常 | `QdbException` 包装 `IOException`，检查网络连通性 |
| HTTP 状态码非 200 | `QdbException("HTTP 请求失败，状态码: xxx")` |
| API 业务错误 | `QdbException` 包含 `errorCode` 和 `msg`（从企得宝返回） |
| 响应解析失败 | `QdbException("API 响应解析失败")` |

---

## 🔧 开发指南

### 项目结构

```
qdb-sdk/
├── pom.xml                              # Maven 构建文件（Java 21）
├── src/main/java/com/qdb/sdk/
│   ├── QdbClient.java                   # 主客户端入口
│   ├── QdbConfig.java                   # SDK 配置类
│   ├── QdbException.java                # 自定义异常
│   ├── api/
│   │   ├── BaseQdbApi.java              # API 基类（通用调用逻辑）
│   │   └── OrderApi.java                # 订单管理 API
│   ├── model/
│   │   ├── request/                     # 请求参数模型
│   │   │   ├── OrderSaveRequest.java    # 订单上传请求
│   │   │   ├── OrderGoods.java          # 订单商品
│   │   │   ├── OrderInvoice.java        # 订单发票
│   │   │   └── OrderPay.java            # 订单支付
│   │   └── response/
│   │       └── QdbResponse.java         # 统一响应对象
│   └── util/
│       ├── SignUtil.java                # MD5 签名工具
│       ├── HttpUtil.java                # HTTP 客户端（OkHttp）
│       └── JsonUtil.java                # JSON 工具（FastJson2）
├── 00-概述与接入指南.md                   # 企得宝 API 文档
├── 01-基础资料.md
├── ...                                  # 其他 API 文档
```

### 扩展新模块

新增一个业务 API 模块只需 3 步：

**1. 创建请求模型**（`model/request/` 目录下）：

```java
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ShopQueryRequest {
    private Long shopId;
    private String shopName;
    // ... 其他请求参数
}
```

**2. 创建 API 类**（`api/` 目录下）：

```java
public class ShopApi extends BaseQdbApi {
    private static final String METHOD_SHOP_LIST = "foonsu.erp.shop.shopInfo";

    public ShopApi(QdbClient client) {
        super(client);
    }

    public QdbResponse<List<ShopInfo>> queryShops(ShopQueryRequest request) throws QdbException {
        return execute(METHOD_SHOP_LIST, request);
    }
}
```

**3. 注册到 QdbClient**：

```java
// 在 QdbClient.java 中添加
private final ShopApi shopApi;

// 在构造方法中初始化
this.shopApi = new ShopApi(this);

// 添加 Getter
public ShopApi getShopApi() { return shopApi; }
```

### 构建与测试

```bash
# 编译
mvn clean compile

# 打包
mvn clean package

# 安装到本地仓库
mvn clean install

# 跳过测试打包
mvn clean package -DskipTests
```

---

## ❓ 常见问题

### Q1: SDK 是否线程安全？

**是的**。`QdbClient` 可在多线程环境中安全使用，建议作为单例管理（如在 Spring 中声明为 `@Bean`）。

### Q2: 签名计算失败怎么办？

检查 `clientId` 和 `clientSecret` 是否正确。SDK 内部已实现完整的签名逻辑，包括字典序排列、key+value 拼接、首尾加 secret、MD5 加密。

### Q3: 如何切换正式/测试环境？

修改 `QdbConfig.apiUrl`：
- 正式：`https://api.7debao.com/router/api`（默认）
- 测试：`https://apitest.7debao.com/router/api`

### Q4: 如何获取 clientId 和 clientSecret？

登录企得宝ERP开放平台后台，在应用管理中创建应用获取。

### Q5: 响应中 success=false 但 data 不为 null？

以 `success` 字段为准。SDK 内部通过 `isSuccess()` 判断，该方法只检查 `success` 是否为 `true`。

---

## 📄 参考文档

- [企得宝ERP 开放平台](https://open.7debao.com)
- [00-概述与接入指南](./00-概述与接入指南.md) - API 总览与接入规范
- [01-基础资料.md](./01-基础资料.md) - 店铺、仓库、物流公司等
- [02-商品管理.md](./02-商品管理.md) - 商品信息管理
- [03-平台商品管理(个性).md](./03-平台商品管理(个性).md) - 平台商品独立管理
- [04-订单管理.md](./04-订单管理.md) - 订单上传、查询、发货、售后
- [05-采购管理.md](./05-采购管理.md) - 供应商、采购订单、入库
- [06-调拨管理.md](./06-调拨管理.md) - 调拨单管理
- [07-其他出入库管理.md](./07-其他出入库管理.md) - 其他出入库单
- [08-盘点管理.md](./08-盘点管理.md) - 盘点单管理
- [09-库存管理.md](./09-库存管理.md) - 库存查询
- [10-消息推送.md](./10-消息推送.md) - 推送通知接收
- [11-路由管理.md](./11-路由管理.md) - 物流路由查询

---

**版本**: 1.0.0  
**Java**: 21+  
**更新日期**: 2026-04-30
