个人开发者接入拼多多开放平台

123 阅读8分钟

大家好,我是小悟。

一、拼多多开放平台概述

拼多多开放平台(Pinduoduo Open Platform)是拼多多为第三方开发者提供的一套API接口服务体系,允许开发者获取拼多多的商品、订单、物流、营销等数据,并实现与拼多多系统的集成。平台主要提供以下能力:

1.1 核心功能模块

  • 商品管理:获取商品列表、详情、上下架商品等
  • 订单管理:订单查询、发货、退款处理
  • 物流管理:物流跟踪、电子面单
  • 营销工具:优惠券、拼团活动管理
  • 数据服务:店铺数据统计、商品分析

1.2 技术特性

  • RESTful API设计
  • 使用OAuth 2.0授权
  • 支持HTTPS协议
  • 数据格式为JSON
  • 需要签名验证

二、详细实现步骤

2.1 前期准备

2.1.1 注册开发者账号

  1. 访问拼多多开放平台官网
  2. 注册个人开发者账号
  3. 完成实名认证

2.1.2 创建应用

  1. 在控制台创建新应用
  2. 获取Client ID和Client Secret
  3. 配置回调地址和权限

2.2 SpringBoot项目搭建

2.2.1 创建项目

<!-- pom.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.0</version>
    </parent>
    
    <groupId>com.pdd</groupId>
    <artifactId>pdd-open-platform</artifactId>
    <version>1.0.0</version>
    
    <properties>
        <java.version>11</java.version>
    </properties>
    
    <dependencies>
        <!-- SpringBoot Web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!-- Lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        
        <!-- HTTP客户端 -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>4.5.13</version>
        </dependency>
        
        <!-- JSON处理 -->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
        </dependency>
        
        <!-- 配置文件加密 -->
        <dependency>
            <groupId>com.github.ulisesbocchio</groupId>
            <artifactId>jasypt-spring-boot-starter</artifactId>
            <version>3.0.4</version>
        </dependency>
        
        <!-- 测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

2.3 核心配置类

server:
  port: 8080
  servlet:
    context-path: /pdd

spring:
  application:
    name: pdd-open-platform

# 拼多多开放平台配置
pdd:
  config:
    # 正式环境
    production:
      gateway: https://gw-api.pinduoduo.com/api/router
    # 应用配置
    app:
      client-id: ENC(你的ClientId加密值)
      client-secret: ENC(你的ClientSecret加密值)
      redirect-uri: http://localhost:8080/pdd/auth/callback
package com.pdd.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Data
@Component
@ConfigurationProperties(prefix = "pdd.config")
public class PddProperties {
    private Sandbox sandbox;
    private Production production;
    private App app;
    
    @Data
    public static class Sandbox {
        private String gateway;
    }
    
    @Data
    public static class Production {
        private String gateway;
    }
    
    @Data
    public static class App {
        private String clientId;
        private String clientSecret;
        private String redirectUri;
    }
}

2.4 签名工具类

package com.pdd.util;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.stereotype.Component;

import java.util.*;

@Slf4j
@Component
public class PddSignUtil {
    
    /**
     * 生成拼多多API签名
     * @param params 请求参数
     * @param clientSecret 客户端密钥
     * @return 签名
     */
    public static String generateSign(Map<String, String> params, String clientSecret) {
        // 1. 参数排序
        List<String> keys = new ArrayList<>(params.keySet());
        Collections.sort(keys);
        
        // 2. 拼接字符串
        StringBuilder sb = new StringBuilder();
        sb.append(clientSecret);
        for (String key : keys) {
            String value = params.get(key);
            if (value != null && !value.isEmpty()) {
                sb.append(key).append(value);
            }
        }
        sb.append(clientSecret);
        
        // 3. MD5加密并转大写
        String signStr = sb.toString();
        String md5Hex = DigestUtils.md5Hex(signStr).toUpperCase();
        
        log.debug("生成签名, 原字符串: {}, 签名结果: {}", signStr, md5Hex);
        return md5Hex;
    }
    
    /**
     * 验证签名
     */
    public static boolean verifySign(Map<String, String> params, String clientSecret, String sign) {
        String generatedSign = generateSign(params, clientSecret);
        return generatedSign.equals(sign);
    }
}

2.5 HTTP客户端封装

package com.pdd.client;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.pdd.config.PddProperties;
import com.pdd.util.PddSignUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@Component
public class PddHttpClient {
    
    @Autowired
    private PddProperties pddProperties;
    
    @Autowired
    private ObjectMapper objectMapper;
    
    /**
     * 执行API调用
     */
    public Map<String, Object> execute(String type, String version, 
                                      String accessToken, Map<String, Object> bizParams) {
        try {
            // 构建基础参数
            Map<String, String> params = new HashMap<>();
            params.put("type", type);
            params.put("client_id", pddProperties.getApp().getClientId());
            params.put("access_token", accessToken);
            params.put("timestamp", String.valueOf(System.currentTimeMillis() / 1000));
            params.put("data_type", "JSON");
            params.put("version", version);
            
            // 添加业务参数
            if (bizParams != null && !bizParams.isEmpty()) {
                String dataJson = objectMapper.writeValueAsString(bizParams);
                params.put("data", dataJson);
            }
            
            // 生成签名
            String sign = PddSignUtil.generateSign(params, 
                pddProperties.getApp().getClientSecret());
            params.put("sign", sign);
            
            // 发送请求
            String url = pddProperties.getProduction().getGateway();
            String response = doPost(url, params);
            
            // 解析响应
            return objectMapper.readValue(response, Map.class);
            
        } catch (Exception e) {
            log.error("调用拼多多API失败", e);
            throw new RuntimeException("API调用失败", e);
        }
    }
    
    /**
     * 执行POST请求
     */
    private String doPost(String url, Map<String, String> params) throws Exception {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            HttpPost httpPost = new HttpPost(url);
            
            // 设置请求头
            httpPost.setHeader("Content-Type", "application/json;charset=UTF-8");
            
            // 设置请求体
            String jsonParams = objectMapper.writeValueAsString(params);
            StringEntity entity = new StringEntity(jsonParams, StandardCharsets.UTF_8);
            httpPost.setEntity(entity);
            
            // 执行请求
            try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
                HttpEntity responseEntity = response.getEntity();
                return EntityUtils.toString(responseEntity, StandardCharsets.UTF_8);
            }
        }
    }
}

2.6 OAuth授权服务

package com.pdd.service;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.pdd.config.PddProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import java.util.HashMap;
import java.util.Map;

@Slf4j
@Service
public class PddAuthService {
    
    @Autowired
    private PddProperties pddProperties;
    
    @Autowired
    private RestTemplate restTemplate;
    
    @Autowired
    private ObjectMapper objectMapper;
    
    /**
     * 获取授权URL
     */
    public String getAuthUrl(String state) {
        String clientId = pddProperties.getApp().getClientId();
        String redirectUri = pddProperties.getApp().getRedirectUri();
        
        return String.format("https://mai.pinduoduo.com/h5-login.html?" +
                "response_type=code&client_id=%s&redirect_uri=%s&state=%s",
                clientId, redirectUri, state);
    }
    
    /**
     * 通过授权码获取访问令牌
     */
    public Map<String, Object> getAccessToken(String code) {
        String url = "https://open-api.pinduoduo.com/oauth/token";
        
        Map<String, String> params = new HashMap<>();
        params.put("client_id", pddProperties.getApp().getClientId());
        params.put("client_secret", pddProperties.getApp().getClientSecret());
        params.put("grant_type", "authorization_code");
        params.put("code", code);
        params.put("redirect_uri", pddProperties.getApp().getRedirectUri());
        
        try {
            String response = restTemplate.postForObject(url, params, String.class);
            return objectMapper.readValue(response, Map.class);
        } catch (Exception e) {
            log.error("获取Access Token失败", e);
            throw new RuntimeException("授权失败", e);
        }
    }
    
    /**
     * 刷新访问令牌
     */
    public Map<String, Object> refreshToken(String refreshToken) {
        String url = "https://open-api.pinduoduo.com/oauth/token";
        
        Map<String, String> params = new HashMap<>();
        params.put("client_id", pddProperties.getApp().getClientId());
        params.put("client_secret", pddProperties.getApp().getClientSecret());
        params.put("grant_type", "refresh_token");
        params.put("refresh_token", refreshToken);
        
        try {
            String response = restTemplate.postForObject(url, params, String.class);
            return objectMapper.readValue(response, Map.class);
        } catch (Exception e) {
            log.error("刷新Token失败", e);
            throw new RuntimeException("刷新令牌失败", e);
        }
    }
}

2.7 业务API服务示例

package com.pdd.service;

import com.pdd.client.PddHttpClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;
import java.util.Map;

@Slf4j
@Service
public class PddGoodsService {
    
    @Autowired
    private PddHttpClient pddHttpClient;
    
    /**
     * 获取商品列表
     */
    public Map<String, Object> getGoodsList(String accessToken, 
                                          Integer page, 
                                          Integer pageSize) {
        Map<String, Object> bizParams = new HashMap<>();
        bizParams.put("page", page);
        bizParams.put("page_size", pageSize);
        
        return pddHttpClient.execute(
            "pdd.goods.list.get", 
            "v1", 
            accessToken, 
            bizParams
        );
    }
    
    /**
     * 获取商品详情
     */
    public Map<String, Object> getGoodsDetail(String accessToken, Long goodsId) {
        Map<String, Object> bizParams = new HashMap<>();
        bizParams.put("goods_id", goodsId);
        
        return pddHttpClient.execute(
            "pdd.goods.detail.get", 
            "v1", 
            accessToken, 
            bizParams
        );
    }
    
    /**
     * 上架商品
     */
    public Map<String, Object> onSaleGoods(String accessToken, Long goodsId) {
        Map<String, Object> bizParams = new HashMap<>();
        bizParams.put("goods_id", goodsId);
        
        return pddHttpClient.execute(
            "pdd.goods.on.sale", 
            "v1", 
            accessToken, 
            bizParams
        );
    }
}

2.8 控制器层

package com.pdd.controller;

import com.pdd.service.PddAuthService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.util.Map;

@Slf4j
@RestController
@RequestMapping("/auth")
public class AuthController {
    
    @Autowired
    private PddAuthService pddAuthService;
    
    /**
     * 跳转到授权页面
     */
    @GetMapping("/login")
    public String login(@RequestParam(required = false) String state) {
        String authUrl = pddAuthService.getAuthUrl(state != null ? state : "default");
        return "redirect:" + authUrl;
    }
    
    /**
     * 授权回调
     */
    @GetMapping("/callback")
    public Map<String, Object> callback(HttpServletRequest request) {
        String code = request.getParameter("code");
        String state = request.getParameter("state");
        
        log.info("收到授权回调,code: {}, state: {}", code, state);
        
        // 获取访问令牌
        Map<String, Object> tokenInfo = pddAuthService.getAccessToken(code);
        
        // 存储令牌信息(实际项目中应存入数据库)
        String accessToken = (String) tokenInfo.get("access_token");
        String refreshToken = (String) tokenInfo.get("refresh_token");
        Integer expiresIn = (Integer) tokenInfo.get("expires_in");
        
        log.info("获取Token成功,access_token: {}, expires_in: {}", 
                accessToken, expiresIn);
        
        return tokenInfo;
    }
}
package com.pdd.controller;

import com.pdd.service.PddGoodsService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

@Slf4j
@RestController
@RequestMapping("/goods")
public class GoodsController {
    
    @Autowired
    private PddGoodsService pddGoodsService;
    
    /**
     * 获取商品列表
     */
    @GetMapping("/list")
    public Map<String, Object> getGoodsList(
            @RequestParam String accessToken,
            @RequestParam(defaultValue = "1") Integer page,
            @RequestParam(defaultValue = "20") Integer pageSize) {
        
        return pddGoodsService.getGoodsList(accessToken, page, pageSize);
    }
    
    /**
     * 获取商品详情
     */
    @GetMapping("/detail/{goodsId}")
    public Map<String, Object> getGoodsDetail(
            @RequestParam String accessToken,
            @PathVariable Long goodsId) {
        
        return pddGoodsService.getGoodsDetail(accessToken, goodsId);
    }
}

2.9 异常处理

package com.pdd.handler;

import com.pdd.exception.PddApiException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.HashMap;
import java.util.Map;

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
    
    /**
     * 处理拼多多API异常
     */
    @ExceptionHandler(PddApiException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Map<String, Object> handlePddApiException(PddApiException e) {
        log.error("拼多多API调用异常", e);
        
        Map<String, Object> result = new HashMap<>();
        result.put("code", e.getCode());
        result.put("message", e.getMessage());
        result.put("success", false);
        
        return result;
    }
    
    /**
     * 处理通用异常
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Map<String, Object> handleException(Exception e) {
        log.error("系统异常", e);
        
        Map<String, Object> result = new HashMap<>();
        result.put("code", 500);
        result.put("message", "系统内部错误");
        result.put("success", false);
        
        return result;
    }
}

2.10 定时任务(令牌刷新)

package com.pdd.task;

import com.pdd.service.PddAuthService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.util.Map;

@Slf4j
@Component
public class TokenRefreshTask {
    
    @Autowired
    private PddAuthService pddAuthService;
    
    /**
     * 每天凌晨刷新令牌(示例)
     */
    @Scheduled(cron = "0 0 0 * * ?")
    public void refreshTokens() {
        log.info("开始刷新拼多多访问令牌");
        
        // 从数据库获取需要刷新的令牌信息
        List<TokenInfo> tokens = tokenService.getExpiringTokens();
        
        for (TokenInfo token : tokens) {
            try {
                Map<String, Object> result = pddAuthService
                    .refreshToken(token.getRefreshToken());
                
                // 更新数据库中的令牌信息
                tokenService.updateToken(token.getId(), result);
                
                log.info("刷新令牌成功: {}", token.getId());
            } catch (Exception e) {
                log.error("刷新令牌失败: {}", token.getId(), e);
            }
         }
        
        log.info("拼多多令牌刷新任务完成");
    }
}

2.11 配置类

package com.pdd.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

import java.text.SimpleDateFormat;

@Configuration
public class AppConfig {
    
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
    
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
        objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        return objectMapper;
    }
}

三、使用示例

3.1 启动应用

package com.pdd;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class PddOpenPlatformApplication {
    public static void main(String[] args) {
        SpringApplication.run(PddOpenPlatformApplication.class, args);
    }
}

3.2 测试API调用

// 测试类示例
package com.pdd.test;

import com.pdd.service.PddGoodsService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.Map;

@SpringBootTest
public class PddApiTest {
    
    @Autowired
    private PddGoodsService pddGoodsService;
    
    @Test
    public void testGetGoodsList() {
        String accessToken = "your_access_token";
        Map<String, Object> result = pddGoodsService
            .getGoodsList(accessToken, 1, 20);
        
        System.out.println("API调用结果: " + result);
    }
}

四、总结

4.1 实现要点回顾

  1. 认证授权流程:实现了完整的OAuth 2.0授权流程,包括获取授权码、交换访问令牌、刷新令牌等步骤。
  2. API调用封装:统一封装了拼多多API的调用逻辑,包括参数构建、签名生成、请求发送和响应处理。
  3. 安全性考虑
    • 使用HTTPS协议保障传输安全
    • 实现签名验证防止请求篡改
    • 令牌定期刷新机制
    • 敏感信息加密存储
  4. 可扩展性设计
    • 模块化设计,便于新增API接口
    • 统一的异常处理机制
    • 配置外部化管理

4.2 开发注意事项

  1. API限制:拼多多开放平台对API调用有频率限制,需要合理控制调用频率,必要时实现限流机制。
  2. 错误处理:需要完善处理各种API错误,包括网络错误、业务错误、令牌过期等场景。
  3. 数据安全:妥善保管Client Secret和访问令牌,建议使用加密存储,避免泄露。
  4. 版本兼容:注意API版本更新,及时适配新版本接口。
  5. 日志记录:详细记录API调用日志,便于问题排查和监控分析。

4.3 扩展建议

  1. 缓存优化:对频繁调用的接口结果进行缓存,减少API调用次数。
  2. 异步处理:对于耗时操作,可以采用异步方式处理,提高系统响应速度。
  3. 监控告警:集成监控系统,对API调用成功率、响应时间等指标进行监控。
  4. 重试机制:实现智能重试策略,处理网络抖动等临时性故障。
  5. 多店铺支持:为支持多个拼多多店铺,需要设计多租户架构。

4.4 建议

  1. 关注官方文档:拼多多开放平台会不定期更新,要及时关注官方文档变化。
  2. 合理申请权限:根据实际需求申请API权限,避免过度申请。
  3. 遵守平台规则:严格遵守拼多多开放平台的使用规范,避免违规操作。
  4. 性能优化:考虑到个人服务器资源有限,需要特别注意代码性能和资源利用。

通过以上实现,个人开发者可以基于SpringBoot快速构建拼多多开放平台的集成应用,实现对拼多多平台商品、订单等数据的自动化管理,为后续业务开发奠定坚实基础。

SpringBoot实现个人开发者接入拼多多开放平台.png

谢谢你看我的文章,既然看到这里了,如果觉得不错,随手点个赞、转发、在看三连吧,感谢感谢。那我们,下次再见。

您的一键三连,是我更新的最大动力,谢谢

山水有相逢,来日皆可期,谢谢阅读,我们再会

我手中的金箍棒,上能通天,下能探海