得物开放平台接入得物SDK
📔 千寻简笔记介绍
千寻简文库已开源,Gitee与GitHub搜索chihiro-doc
,包含笔记源文件.md
,以及PDF版本方便阅读,文库采用精美主题,阅读体验更佳,如果文章对你有帮助请帮我点一个Star
~
更新:支持在线阅读文章,根据发布日期分类。
@[toc]
简介
本文接入得物开放平台,
本文关键词
得物开放平台
、得物SDK
、得物PUSH
、获取订单
、虚拟发货
实现步骤
1 引入依赖
在得物开放平台下载JDK,本文以得物JDK1.3.8.RELEASE为基础进行接入。
下载后有三个文件:
- open-sdk-java-1.3.8-okhttp.RELEASE.jar
- open-sdk-java-1.3.8-urlconnect.RELEASE.jar
- 得物开放平台Java版sdk使用说明v1.0.1.pdf
1.1 Maven导入依赖
我们将下载的文件放在项目根目录下的libs文件夹中。
pox.xml
配置,引入jdk
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.0</version>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.8.1</version>
</dependency>
<dependency>
<groupId>open-sdk-java-1.3.8-okhttp</groupId>
<artifactId>open-sdk-java-1.3.8-okhttp</artifactId>
<version>1.3.8</version>
</dependency>
<dependency>
<groupId>open-sdk-java-1.3.8-urlconnect</groupId>
<artifactId>open-sdk-java-1.3.8-urlconnect</artifactId>
<version>1.3.8</version>
</dependency>
1.2 引入失败
引入后如果没成功,可以尝试使用以下命令。
# 注意文件路径不要有中文
mvn install:install-file -Dfile=.\libs\open-sdk-java-1.3.8-okhttp.RELEASE.jar -DgroupId=open-sdk-java-1.3.8-okhttp -DartifactId=open-sdk-java-1.3.8-okhttp -Dversion=1.3.8 -Dpackaging=jar
mvn install:install-file -Dfile=.\libs\open-sdk-java-1.3.8-urlconnect.RELEASE.jar -DgroupId=open-sdk-java-1.3.8-urlconnect -DartifactId=open-sdk-java-1.3.8-urlconnect -Dversion=1.3.8 -Dpackaging=jar
刷新Maven。
2 配置
在得物开放平台中 -> 控制台 -> 应用管理 -> 我的应用 -> 应用详情 -> 应用信息
即可查看应用证书,AppKey、App secret
2.1 配置公钥私钥
application-dev.yml
新增得物配置信息,其他环境一样配置
### 得物配置
dewu:
gatewayHost: https://openapi.dewu.com
appKey: xxx
secret: xxxx
2.2 配置类
用于绑定配置文件中的配置参数。
DewuProperties.java
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties("dewu")
public class DewuProperties {
private String gatewayHost;
private String appKey;
private String secret;
public String getGatewayHost() {
return gatewayHost;
}
public DewuProperties setGatewayHost(String gatewayHost) {
this.gatewayHost = gatewayHost;
return this;
}
public String getAppKey() {
return appKey;
}
public DewuProperties setAppKey(String appKey) {
this.appKey = appKey;
return this;
}
public String getSecret() {
return secret;
}
public DewuProperties setSecret(String secret) {
this.secret = secret;
return this;
}
}
2.3得物配置类
DewuConfig.java
DewuFactoryConfig
:方法在启动时设置全局变量- gatewayHost(api调⽤⽹关)
- appKey(应⽤key)
- secret(应⽤秘钥)
- authHost(授权⽹ 关)
- connectTimeout(链接超时时间,默认15秒)
- readTimeout(读取超时时间,默认15 秒)
- accessToken(如果是⾃研商家,这个可以全局设置,如果是isv请在请求的request参数⾥设 置)
serviceExecutor
:方法创建一个用于进行异步处理的线程池执行器。
import com.dewu.sdk.base.BaseClient;
import com.dewu.sdk.base.constans.CommonConstants;
import com.dewu.sdk.factory.Factory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import javax.annotation.Resource;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
@EnableAsync
public class DewuConfig {
@Resource
private DewuProperties dewuProperties;
@Bean("DewuFactoryConfig")
public void DewuFactoryConfig(){
System.out.println("注入得物配置");
BaseClient.Config config = new BaseClient.Config();
config.setGatewayHost(dewuProperties.getGatewayHost());
config.setAppKey(dewuProperties.getAppKey());
config.setSecret(dewuProperties.getSecret());
config.setProtocol(CommonConstants.HTTPS);
Factory.setOptions(config);
}
@Bean("sellerDeliveryExecutor")
public Executor serviceExecutor(){
ThreadPoolTaskExecutor executor =
new ThreadPoolTaskExecutor();
//核心线程数量:当前机器的核心数
executor.setCorePoolSize(
Runtime.getRuntime().availableProcessors());
//最大线程数
executor.setMaxPoolSize(
Runtime.getRuntime().availableProcessors()*2);
//队列大小
executor.setQueueCapacity(Integer.MAX_VALUE);
//线程池中的线程名前缀
executor.setThreadNamePrefix("sellerDeliveryService-");
//拒绝策略:由提交任务的线程来执行该任务
executor.setRejectedExecutionHandler(
new ThreadPoolExecutor.CallerRunsPolicy()
);
//执行初始化
executor.initialize();
return executor;
}
}
3 配置
在得物开放平台中 -> 控制台 -> 应用管理 -> 我的应用 -> 应用详情 -> 应用信息
即可查看应用证书,AppKey、App secret
3.1 新建接口接收得物回调
DewuController.java
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.Map;
/**
* 得物控制器
*/
@RestController
@RequestMapping("/app/dewu")
public class DewuController extends BaseController {
private static final Logger log = LoggerFactory.getLogger(DewuController.class);
@Resource
private IDewuService iDewuService;
@Resource
private DewuProperties dewuProperties;
/**
* 接收push请求
* 建议采用异步的方式接收消息
* 来源:https://open.dewu.com/#/doc?id=1010000048&pid=1010525077&type=article
*/
@PostMapping("/receivePush")
public Map<String, Object> receivePush(@RequestBody ParamWrapper paramWrapper, @RequestHeader("sign") String sign, @RequestHeader("msg_type") String msgType) {
log.info("接收到push请求,sign:{}, msgType:{}, 参数:{}", sign, msgType, JSONUtil.toJsonStr(paramWrapper));
/*************** sign验证开始,可不作校验***************/
if (sign == null) {
throw new ServiceException("sign为空,非法请求");
}
// 正式环境
String appKey = dewuProperties.getAppKey(); // 自己的appKey
String appSecret = dewuProperties.getSecret(); // 自己的appSecret
String newSign = DeWuUtils.generateSign(appKey, appSecret, paramWrapper);
if (!newSign.equals(sign)) {
throw new ServiceException("sign验证不通过,非法请求");
}
/*************** sign验证结束 *************************/
// 参数解密
if (!StringUtils.isEmpty(paramWrapper.getMsg())) {
String msgContent = DeWuUtils.aesDecode(appSecret, paramWrapper.getMsg());
if (StringUtils.isEmpty(msgContent)) {
throw new ServiceException("消息解密失败");
}
log.info("解密后的信息:"+msgContent);
// 获取订单号
// 将JSON字符串转换为JSONObject
JSONObject jsonObject = new JSONObject(msgContent);
String orderNo = jsonObject.getStr("orderNo");
iDewuService.sellerDelivery(orderNo);
}
return DeWuUtils.respSuccess(paramWrapper.getUuid());
}
}
3.2业务处理
DewuServiceImpl.java
- 使用了异步线程、分布式锁进行订单的操作
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.dewu.sdk.base.OpenResult;
import com.dewu.sdk.base.util.JsonUtil;
import com.dewu.sdk.factory.Factory;
import com.dewu.sdk.order.req.GenericOrderQueryRequest;
import com.dewu.sdk.order.req.VirtualOrderDeliveryReq;
import com.dewu.sdk.order.res.OrderTrade;
import com.dewu.sdk.order.res.OrderTradeResponse;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
/**
* Service业务层处理
*/
@Service
public class DewuServiceImpl implements IDewuService {
Logger log = LoggerFactory.getLogger(DewuServiceImpl.class);
@Resource
private DewuProperties dewuProperties;
@Resource
private DewuOrderLogMapper dewuOrderLogMapper;
@Autowired
private RedissonClient redissonClient;
@Autowired
private RedisCache redisCache;
/**
* 卖家发货
*
* @param orderNo 订单号
*/
@Override
@Async("sellerDeliveryExecutor")
public void sellerDelivery(String orderNo) {
OpenResult<OrderTradeResponse> result = this.getGenericOrdersByOrderNo(orderNo);
if (result.getCode() == 200) {
return;
}
if (CollectionUtils.isNotEmpty(result.getData().getOrders())) {
return;
}
for (OrderTrade order : result.getData().getOrders()) {
Long skuId = order.getSku_id();
// 买家收货信息
String buyerAddress = order.getBuyer_address();
// 解密买家收货信息
String decBuyerAddress = DeWuUtils.aesDecode(dewuProperties.getSecret(), buyerAddress);
JSONObject jsonObject = new JSONObject(decBuyerAddress);
// 获取value字段的值
String phoneNumber = jsonObject.getJSONArray("additional_receive_info_list")
.getJSONObject(0)
.getStr("value");
log.info("解密后的手机号: " + phoneNumber);
log.info("当前处理的skuid:" + skuId);
RLock lock = redissonClient.getLock(CacheConstants.DEWU_ORDER +order.getOrder_no());
try {
lock.lock();
// todo 业务处理、查询订单是否已经派发、派发、派发后插入日志
// 调用得物发货接口
VirtualOrderDeliveryReq deliverRequest = new VirtualOrderDeliveryReq();
// 订单号
deliverRequest.setOrder_no(order.getOrder_no());
// 卡号,当虚拟订单核销方式为卡券时(virtual_delivery_type=1),选填。字段需要加密,加密方法参考:https://open.dewu.com/#/doc?id=1010000048&pid=1010525077&type=article
// deliverRequest.setCard_no("xxxx");
// 卡密,当虚拟订单核销方式为卡券时(virtual_delivery_type=1),必填。字段需要加密,加密方法参考:https://open.dewu.com/#/doc?id=1010000048&pid=1010525077&type=article
deliverRequest.setCard_secret(DeWuUtils.aesEncode(dewuProperties.getSecret(), "卡券已派发"));
// 发货
this.deliver(deliverRequest);
} finally {
lock.unlock();
}
}
}
/**
* 得物-订单列表(支持按类型查询)-根据订单号查询
* 参数说明:https://open.dewu.com/#/api/body?id=1&apiId=1201&title=%E8%AE%A2%E5%8D%95%E6%9C%8D%E5%8A%A1
*/
private OpenResult<OrderTradeResponse> getGenericOrdersByOrderNo(String orderNo) {
GenericOrderQueryRequest request = new GenericOrderQueryRequest();
request.setOrder_no(orderNo);
// 得物接口,查询订单
OpenResult<OrderTradeResponse> result = Factory.Order.client().getGenericOrders(request);
log.info("==============得物-订单列表返回结果 开始===================");
log.info(JsonUtil.obj2String(result));
log.info("==============得物-订单列表返回结果 结束===================");
return result;
}
/**
* 得物-商家订单发货【虚拟类订单】
* 参数说明:https://open.dewu.com/#/api/body?id=1&apiId=1297&title=%E8%AE%A2%E5%8D%95%E6%9C%8D%E5%8A%A1
*/
private void deliver(VirtualOrderDeliveryReq request) {
// VirtualOrderDeliveryReq request = new VirtualOrderDeliveryReq();
// // 订单号
// request.setOrder_no("xxxx");
// // 卡号,当虚拟订单核销方式为卡券时(virtual_delivery_type=1),选填。字段需要加密,加密方法参考:https://open.dewu.com/#/doc?id=1010000048&pid=1010525077&type=article
// request.setCard_no("xxxx");
// // 卡密,当虚拟订单核销方式为卡券时(virtual_delivery_type=1),必填。字段需要加密,加密方法参考:https://open.dewu.com/#/doc?id=1010000048&pid=1010525077&type=article
// request.setCard_secret("xxxx");
OpenResult<Void> result = Factory.Order.virtualClient().deliver(request);
log.info("==============得物-商家订单发货 开始===================");
log.info(JsonUtil.obj2String(result));
log.info("==============得物-商家订单发货 结束===================");
}
}
3.3工具类
DeWuUtils.java
import cn.hutool.json.JSONUtil;
import com.dewu.sdk.base.util.CypherUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
.
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import static com.dewu.sdk.base.util.CypherUtils.base64Encode;
/**
* 得物相关的工具方法
*/
public class DeWuUtils {
private static final Logger log = LoggerFactory.getLogger(DeWuUtils.class);
/**
* 生成推送的sign。用于标志push请求
* 生成方法:MD5(appKey_appSecret_param)
* @param appKey
* @param appSecret
* @param wrapper
* @return
*/
public static String generateSign(String appKey, String appSecret, ParamWrapper wrapper){
StringBuilder sb = new StringBuilder(appKey).append("_").append(appSecret).append("_").append(JSONUtil.toJsonStr(wrapper));
return CypherUtils.md5(sb.toString());
}
/**
* 返回成功
* @param uuid
* @return
*/
public static Map<String, Object> respSuccess(String uuid){
Map<String, Object> map = new HashMap<>();
map.put("code", 200);
map.put("msg", "SUCCESS");
map.put("data", uuid);
return map;
}
/**
* 生成失败返回
* @param message
* @return
*/
private static Map<String, Object> respFail(String message){
Map<String, Object> map = new HashMap<>();
map.put("code", 100); // 任意一个非200的数字即可
map.put("msg", message);
map.put("data", null);
return map;
}
/**
* aes解密
* @param encodeKey
* @param content
* @return
*/
public static String aesDecode(String encodeKey, String content) {
try {
//1.构造密钥生成器,指定为AES算法,不区分大小写
KeyGenerator keygen = KeyGenerator.getInstance("AES");
//2.根据ecnodeRules规则初始化密钥生成器
//生成一个128位的随机源,根据传入的字节数组
//keygen.init(128, new SecureRandom(encodeRules.getBytes()));
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(encodeKey.getBytes());
keygen.init(128, secureRandom);
//3.产生原始对称密钥
SecretKey originalKey = keygen.generateKey();
//4.获得原始对称密钥的字节数组
byte[] raw = originalKey.getEncoded();
//5.根据字节数组生成AES密钥
SecretKey key = new SecretKeySpec(raw, "AES");
//6.根据指定算法AES自成密码器
Cipher cipher = Cipher.getInstance("AES");
//7.初始化密码器,第一个参数为加密(Encrypt_mode)或者解密(Decrypt_mode)操作,第二个参数为使用的KEY
cipher.init(Cipher.DECRYPT_MODE, key);
//8.将加密并编码后的内容解码成字节数组
byte[] byteContent = base64DecodeBuffer(content);
/*
* 解密
*/
byte[] byteDecode = cipher.doFinal(byteContent);
String AESDecode = new String(byteDecode, "utf-8");
return AESDecode;
} catch (Exception e) {
log.error("aes解密密失败", e);
}
return null;
}
/**
* aes加密
* @param encodeRules appKey对应的secret
* @param content 需要加密的原文内容
* @return
*/
public static String aesEncode(String encodeRules, String content) {
try {
//1.构造密钥生成器,指定为AES算法,不区分大小写
KeyGenerator keygen = KeyGenerator.getInstance("AES");
//2.根据ecnodeRules规则初始化密钥生成器
//生成一个128位的随机源,根据传入的字节数组
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(encodeRules.getBytes());
keygen.init(128, secureRandom);
//3.产生原始对称密钥
SecretKey originalKey = keygen.generateKey();
//4.获得原始对称密钥的字节数组
byte[] raw = originalKey.getEncoded();
//5.根据字节数组生成AES密钥
SecretKey key = new SecretKeySpec(raw, "AES");
//6.根据指定算法AES自成密码器
Cipher cipher = Cipher.getInstance("AES");
//7.初始化密码器,第一个参数为加密(Encrypt_mode)或者解密解密(Decrypt_mode)操作,第二个参数为使用的KEY
cipher.init(Cipher.ENCRYPT_MODE, key);
//8.获取加密内容的字节数组(这里要设置为utf-8)不然内容中如果有中文和英文混合中文就会解密为乱码
byte[] byteEncode = content.getBytes("utf-8");
//9.根据密码器的初始化方式--加密:将数据加密
byte[] byteAES = cipher.doFinal(byteEncode);
//10.将加密后的数据转换为字符串
//这里用Base64Encoder中会找不到包
//解决办法:
//在项目的Build path中先移除JRE System Library,再添加库JRE System Library,重新编译后就一切正常了。
String AESEncode = base64Encode(byteAES);
//11.将字符串返回
return AESEncode;
} catch (Exception e) {
log.error("aes加密失败", e);
}
return null;
}
/**
* base64解密
*
* @param str
* @return
*/
public static String base64Decode(String str) {
try {
return new String(base64DecodeBuffer(str), "UTF-8");
} catch (UnsupportedEncodingException e) {
log.error("base64结果转换失败", e);
}
return null;
}
private static byte[] base64DecodeBuffer(String str) {
return Base64.getDecoder().decode(str);
}
}