点击▲关注 “悟空码字” 给公众号标星置顶
更多精彩 第一时间直达
大家好,我是小悟。
一、拼多多开放平台概述
拼多多开放平台(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"?><projectxmlns="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;
importlombok.Data;importorg.springframework.boot.context.properties.ConfigurationProperties;importorg.springframework.stereotype.Component;
@Data@Component@ConfigurationProperties(prefix ="pdd.config")publicclassPddProperties{ privateSandboxsandbox; privateProductionproduction; privateAppapp;
@Data publicstaticclassSandbox{ privateStringgateway; }
@Data publicstaticclassProduction{ privateStringgateway; }
@Data publicstaticclassApp{ privateStringclientId; privateStringclientSecret; privateStringredirectUri; }}
2.4 签名工具类
package com.pdd.util;
importlombok.extern.slf4j.Slf4j;importorg.apache.commons.codec.digest.DigestUtils;importorg.springframework.stereotype.Component;
importjava.util.*;
@Slf4j@ComponentpublicclassPddSignUtil{
/** * 生成拼多多API签名 *@paramparams 请求参数 *@paramclientSecret 客户端密钥 *@return签名 */ publicstaticStringgenerateSign(Map<String,String> params,StringclientSecret) { // 1. 参数排序 List<String> keys =newArrayList<>(params.keySet()); Collections.sort(keys);
// 2. 拼接字符串 StringBuildersb =newStringBuilder(); sb.append(clientSecret); for(Stringkey : keys) { Stringvalue = params.get(key); if(value !=null&& !value.isEmpty()) { sb.append(key).append(value); } } sb.append(clientSecret);
// 3. MD5加密并转大写 StringsignStr = sb.toString(); Stringmd5Hex =DigestUtils.md5Hex(signStr).toUpperCase();
log.debug("生成签名, 原字符串: {}, 签名结果: {}", signStr, md5Hex); returnmd5Hex; }
/** * 验证签名 */ publicstaticbooleanverifySign(Map<String,String> params,StringclientSecret,Stringsign) { StringgeneratedSign =generateSign(params, clientSecret); returngeneratedSign.equals(sign); }}
2.5 HTTP客户端封装
package com.pdd.client;
importcom.fasterxml.jackson.databind.ObjectMapper;importcom.pdd.config.PddProperties;importcom.pdd.util.PddSignUtil;importlombok.extern.slf4j.Slf4j;importorg.apache.http.HttpEntity;importorg.apache.http.client.methods.CloseableHttpResponse;importorg.apache.http.client.methods.HttpPost;importorg.apache.http.entity.StringEntity;importorg.apache.http.impl.client.CloseableHttpClient;importorg.apache.http.impl.client.HttpClients;importorg.apache.http.util.EntityUtils;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Component;
importjava.nio.charset.StandardCharsets;importjava.util.HashMap;importjava.util.Map;
@Slf4j@ComponentpublicclassPddHttpClient{
@Autowired privatePddPropertiespddProperties;
@Autowired privateObjectMapperobjectMapper;
/** * 执行API调用 */ publicMap<String,Object>execute(Stringtype,Stringversion, StringaccessToken,Map<String,Object> bizParams) { try{ // 构建基础参数 Map<String,String> params =newHashMap<>(); 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()) { StringdataJson = objectMapper.writeValueAsString(bizParams); params.put("data", dataJson); }
// 生成签名 Stringsign =PddSignUtil.generateSign(params, pddProperties.getApp().getClientSecret()); params.put("sign", sign);
// 发送请求 Stringurl = pddProperties.getProduction().getGateway(); Stringresponse =doPost(url, params);
// 解析响应 returnobjectMapper.readValue(response,Map.class);
}catch(Exceptione) { log.error("调用拼多多API失败", e); thrownewRuntimeException("API调用失败", e); } }
/** * 执行POST请求 */ privateStringdoPost(Stringurl,Map<String,String> params) throwsException{ try(CloseableHttpClienthttpClient =HttpClients.createDefault()) { HttpPosthttpPost =newHttpPost(url);
// 设置请求头 httpPost.setHeader("Content-Type","application/json;charset=UTF-8");
// 设置请求体 StringjsonParams = objectMapper.writeValueAsString(params); StringEntityentity =newStringEntity(jsonParams,StandardCharsets.UTF_8); httpPost.setEntity(entity);
// 执行请求 try(CloseableHttpResponseresponse = httpClient.execute(httpPost)) { HttpEntityresponseEntity = response.getEntity(); returnEntityUtils.toString(responseEntity,StandardCharsets.UTF_8); } } }}
2.6 OAuth授权服务
package com.pdd.service;
importcom.fasterxml.jackson.databind.ObjectMapper;importcom.pdd.config.PddProperties;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;importorg.springframework.web.client.RestTemplate;
importjava.util.HashMap;importjava.util.Map;
@Slf4j@ServicepublicclassPddAuthService{
@Autowired privatePddPropertiespddProperties;
@Autowired privateRestTemplaterestTemplate;
@Autowired privateObjectMapperobjectMapper;
/** * 获取授权URL */ publicStringgetAuthUrl(Stringstate) { StringclientId = pddProperties.getApp().getClientId(); StringredirectUri = pddProperties.getApp().getRedirectUri();
returnString.format("https://mai.pinduoduo.com/h5-login.html?" "response_type=code&client_id=%s&redirect_uri=%s&state=%s", clientId, redirectUri, state); }
/** * 通过授权码获取访问令牌 */ publicMap<String,Object>getAccessToken(Stringcode) { Stringurl ="https://open-api.pinduoduo.com/oauth/token";
Map<String,String> params =newHashMap<>(); 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{ Stringresponse = restTemplate.postForObject(url, params,String.class); returnobjectMapper.readValue(response,Map.class); }catch(Exceptione) { log.error("获取Access Token失败", e); thrownewRuntimeException("授权失败", e); } }
/** * 刷新访问令牌 */ publicMap<String,Object>refreshToken(StringrefreshToken) { Stringurl ="https://open-api.pinduoduo.com/oauth/token";
Map<String,String> params =newHashMap<>(); 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{ Stringresponse = restTemplate.postForObject(url, params,String.class); returnobjectMapper.readValue(response,Map.class); }catch(Exceptione) { log.error("刷新Token失败", e); thrownewRuntimeException("刷新令牌失败", e); } }}
2.7 业务API服务示例
package com.pdd.service;
importcom.pdd.client.PddHttpClient;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;
importjava.util.HashMap;importjava.util.Map;
@Slf4j@ServicepublicclassPddGoodsService{
@Autowired privatePddHttpClientpddHttpClient;
/** * 获取商品列表 */ publicMap<String,Object>getGoodsList(StringaccessToken, Integerpage, IntegerpageSize) { Map<String,Object> bizParams =newHashMap<>(); bizParams.put("page", page); bizParams.put("page_size", pageSize);
returnpddHttpClient.execute( "pdd.goods.list.get", "v1", accessToken, bizParams ); }
/** * 获取商品详情 */ publicMap<String,Object>getGoodsDetail(StringaccessToken,LonggoodsId) { Map<String,Object> bizParams =newHashMap<>(); bizParams.put("goods_id", goodsId);
returnpddHttpClient.execute( "pdd.goods.detail.get", "v1", accessToken, bizParams ); }
/** * 上架商品 */ publicMap<String,Object>onSaleGoods(StringaccessToken,LonggoodsId) { Map<String,Object> bizParams =newHashMap<>(); bizParams.put("goods_id", goodsId);
returnpddHttpClient.execute( "pdd.goods.on.sale", "v1", accessToken, bizParams ); }}
2.8 控制器层
package com.pdd.controller;
importcom.pdd.service.PddAuthService;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.bind.annotation.*;
importjavax.servlet.http.HttpServletRequest;importjava.util.Map;
@Slf4j@RestController@RequestMapping("/auth")publicclassAuthController{
@Autowired privatePddAuthServicepddAuthService;
/** * 跳转到授权页面 */ @GetMapping("/login") publicStringlogin(@RequestParam(required =false)Stringstate) { StringauthUrl = pddAuthService.getAuthUrl(state !=null? state :"default"); return"redirect:" authUrl; }
/** * 授权回调 */ @GetMapping("/callback") publicMap<String,Object>callback(HttpServletRequestrequest) { Stringcode = request.getParameter("code"); Stringstate = request.getParameter("state");
log.info("收到授权回调,code: {}, state: {}", code, state);
// 获取访问令牌 Map<String,Object> tokenInfo = pddAuthService.getAccessToken(code);
// 存储令牌信息(实际项目中应存入数据库) StringaccessToken = (String) tokenInfo.get("access_token"); StringrefreshToken = (String) tokenInfo.get("refresh_token"); IntegerexpiresIn = (Integer) tokenInfo.get("expires_in");
log.info("获取Token成功,access_token: {}, expires_in: {}", accessToken, expiresIn);
returntokenInfo; }}
package com.pdd.controller;
importcom.pdd.service.PddGoodsService;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.bind.annotation.*;
importjava.util.Map;
@Slf4j@RestController@RequestMapping("/goods")publicclassGoodsController{
@Autowired privatePddGoodsServicepddGoodsService;
/** * 获取商品列表 */ @GetMapping("/list") publicMap<String,Object>getGoodsList( @RequestParamStringaccessToken, @RequestParam(defaultValue ="1")Integerpage, @RequestParam(defaultValue ="20")IntegerpageSize) {
returnpddGoodsService.getGoodsList(accessToken, page, pageSize); }
/** * 获取商品详情 */ @GetMapping("/detail/{goodsId}") publicMap<String,Object>getGoodsDetail( @RequestParamStringaccessToken, @PathVariableLonggoodsId) {
returnpddGoodsService.getGoodsDetail(accessToken, goodsId); }}
2.9 异常处理
package com.pdd.handler;
importcom.pdd.exception.PddApiException;importlombok.extern.slf4j.Slf4j;importorg.springframework.http.HttpStatus;importorg.springframework.web.bind.annotation.ExceptionHandler;importorg.springframework.web.bind.annotation.ResponseStatus;importorg.springframework.web.bind.annotation.RestControllerAdvice;
importjava.util.HashMap;importjava.util.Map;
@Slf4j@RestControllerAdvicepublicclassGlobalExceptionHandler{
/** * 处理拼多多API异常 */ @ExceptionHandler(PddApiException.class) @ResponseStatus(HttpStatus.BAD_REQUEST) publicMap<String,Object>handlePddApiException(PddApiExceptione) { log.error("拼多多API调用异常", e);
Map<String,Object> result =newHashMap<>(); result.put("code", e.getCode()); result.put("message", e.getMessage()); result.put("success",false);
returnresult; }
/** * 处理通用异常 */ @ExceptionHandler(Exception.class) @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) publicMap<String,Object>handleException(Exceptione) { log.error("系统异常", e);
Map<String,Object> result =newHashMap<>(); result.put("code",500); result.put("message","系统内部错误"); result.put("success",false);
returnresult; }}
2.10 定时任务(令牌刷新)
package com.pdd.task;
importcom.pdd.service.PddAuthService;importlombok.extern.slf4j.Slf4j;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.scheduling.annotation.Scheduled;importorg.springframework.stereotype.Component;
importjava.util.Map;
@Slf4j@ComponentpublicclassTokenRefreshTask{
@Autowired privatePddAuthServicepddAuthService;
/** * 每天凌晨刷新令牌(示例) */ @Scheduled(cron ="0 0 0 * * ?") publicvoidrefreshTokens() { log.info("开始刷新拼多多访问令牌");
// 从数据库获取需要刷新的令牌信息 List<TokenInfo> tokens = tokenService.getExpiringTokens();
for(TokenInfotoken : tokens) { try{ Map<String,Object> result = pddAuthService .refreshToken(token.getRefreshToken());
// 更新数据库中的令牌信息 tokenService.updateToken(token.getId(), result);
log.info("刷新令牌成功: {}", token.getId()); }catch(Exceptione) { log.error("刷新令牌失败: {}", token.getId(), e); } }
log.info("拼多多令牌刷新任务完成"); }}
2.11 配置类
package com.pdd.config;
importcom.fasterxml.jackson.databind.ObjectMapper;importcom.fasterxml.jackson.databind.SerializationFeature;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.web.client.RestTemplate;
importjava.text.SimpleDateFormat;
@ConfigurationpublicclassAppConfig{
@Bean publicRestTemplaterestTemplate() { returnnewRestTemplate(); }
@Bean publicObjectMapperobjectMapper() { ObjectMapperobjectMapper =newObjectMapper(); objectMapper.setDateFormat(newSimpleDateFormat("yyyy-MM-dd HH:mm:ss")); objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); returnobjectMapper; }}
三、使用示例
3.1 启动应用
package com.pdd;
importorg.springframework.boot.SpringApplication;importorg.springframework.boot.autoconfigure.SpringBootApplication;importorg.springframework.scheduling.annotation.EnableScheduling;
@SpringBootApplication@EnableSchedulingpublicclassPddOpenPlatformApplication{ publicstaticvoidmain(String[] args) { SpringApplication.run(PddOpenPlatformApplication.class, args); }}
3.2 测试API调用
// 测试类示例package com.pdd.test;
importcom.pdd.service.PddGoodsService;importorg.junit.jupiter.api.Test;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.boot.test.context.SpringBootTest;
importjava.util.Map;
@SpringBootTestpublicclassPddApiTest{
@Autowired privatePddGoodsServicepddGoodsService;
@Test publicvoidtestGetGoodsList() { StringaccessToken ="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快速构建拼多多开放平台的集成应用,实现对拼多多平台商品、订单等数据的自动化管理,为后续业务开发奠定坚实基础。
谢谢你看我的文章,既然看到这里了,如果觉得不错,随手点个赞、转发、在看三连吧,感谢感谢。那我们,下次再见。
实用系统推荐
可以帮助服务商更好地管理多个商家小程序,无需管理多个商家小程序的账号密码或者appId和secret,大大提升效率。不需要频繁登录小程序后台就能完成上传代码、认证、备案、提交代码审核、发布小程序等操作。
最后,扫码关注视频号还有更多精彩内容等你来看。
有任何咨询,扫码添加微信畅聊。
您的一键三连,是我更新的最大动力,谢谢
山水有相逢,来日皆可期,谢谢阅读,我们再会
我手中的金箍棒,上能通天,下能探海
欢迎大家关注【悟空码字】公众号
PS:因为公众号平台更改了推送规则,如果不想错过内容,记得读完给个“赞”,点个“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里哦。点“在看”支持一下吧!
