得物开放平台接入得物SDK

时间:2024-01-26 15:31:26

得物开放平台接入得物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文件夹中。

得物开放平台接入得物SDK_spring

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);
    }

}