大家好,我是小悟。
一、拼多多开放平台概述
拼多多开放平台(Pinduoduo Open Platform)是拼多多为第三方开发者提供的一套API接口服务体系,允许开发者获取拼多多的商品、订单、物流、营销等数据,并实现与拼多多系统的集成。平台主要提供以下能力:
1.1 核心功能模块
- 商品管理:获取商品列表、详情、上下架商品等
- 订单管理:订单查询、发货、退款处理
- 物流管理:物流跟踪、电子面单
- 营销工具:优惠券、拼团活动管理
- 数据服务:店铺数据统计、商品分析
1.2 技术特性
- RESTful API设计
- 使用OAuth 2.0授权
- 支持HTTPS协议
- 数据格式为JSON
- 需要签名验证
二、详细实现步骤
2.1 前期准备
2.1.1 注册开发者账号
- 访问拼多多开放平台官网
- 注册个人开发者账号
- 完成实名认证
2.1.2 创建应用
- 在控制台创建新应用
- 获取Client ID和Client Secret
- 配置回调地址和权限
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 实现要点回顾
- 认证授权流程:实现了完整的OAuth 2.0授权流程,包括获取授权码、交换访问令牌、刷新令牌等步骤。
- API调用封装:统一封装了拼多多API的调用逻辑,包括参数构建、签名生成、请求发送和响应处理。
- 安全性考虑:
- 使用HTTPS协议保障传输安全
- 实现签名验证防止请求篡改
- 令牌定期刷新机制
- 敏感信息加密存储
- 可扩展性设计:
- 模块化设计,便于新增API接口
- 统一的异常处理机制
- 配置外部化管理
4.2 开发注意事项
- API限制:拼多多开放平台对API调用有频率限制,需要合理控制调用频率,必要时实现限流机制。
- 错误处理:需要完善处理各种API错误,包括网络错误、业务错误、令牌过期等场景。
- 数据安全:妥善保管Client Secret和访问令牌,建议使用加密存储,避免泄露。
- 版本兼容:注意API版本更新,及时适配新版本接口。
- 日志记录:详细记录API调用日志,便于问题排查和监控分析。
4.3 扩展建议
- 缓存优化:对频繁调用的接口结果进行缓存,减少API调用次数。
- 异步处理:对于耗时操作,可以采用异步方式处理,提高系统响应速度。
- 监控告警:集成监控系统,对API调用成功率、响应时间等指标进行监控。
- 重试机制:实现智能重试策略,处理网络抖动等临时性故障。
- 多店铺支持:为支持多个拼多多店铺,需要设计多租户架构。
4.4 建议
- 关注官方文档:拼多多开放平台会不定期更新,要及时关注官方文档变化。
- 合理申请权限:根据实际需求申请API权限,避免过度申请。
- 遵守平台规则:严格遵守拼多多开放平台的使用规范,避免违规操作。
- 性能优化:考虑到个人服务器资源有限,需要特别注意代码性能和资源利用。
通过以上实现,个人开发者可以基于SpringBoot快速构建拼多多开放平台的集成应用,实现对拼多多平台商品、订单等数据的自动化管理,为后续业务开发奠定坚实基础。
谢谢你看我的文章,既然看到这里了,如果觉得不错,随手点个赞、转发、在看三连吧,感谢感谢。那我们,下次再见。
您的一键三连,是我更新的最大动力,谢谢
山水有相逢,来日皆可期,谢谢阅读,我们再会
我手中的金箍棒,上能通天,下能探海