【小西】同步咪咕订单给咪咕方(写接口给第三方)

前言

需求:把小西中与咪咕相关的订单同步给咪咕方。

思路

思路如下:

  1. 定义好请求体和响应信息
  2. 在nacos定义好咪咕相关配置信息(用于之后验证请求体是否正确)
  3. 写接口

实现

1、定义请求体和响应信息

MiGuOrderSyncReq
@Data
@ApiModel(description = "咪咕订单同步请求参数")
public class MiGuOrderSyncReq implements Serializable {
	private static final long serialVersionUID = 1L;


	@JsonProperty("header")
	@Valid
	private ReqHeader header;

	@JsonProperty("body")
	@Valid
	private ReqBody body;

	@Data
	public static class ReqHeader implements Serializable {

		private static final long serialVersionUID = 8807000967257080242L;
		/**
		 * 企业id
		 */
		@ApiModelProperty(value = "企业id", required = true)
		@NotEmpty(message = "企业id不能为空")
		private String corpId;
		/**
		 * 合作伙伴(合众游戏平台)提供(类似appKey)
		 */
		@ApiModelProperty(value = "合作伙伴ID", required = true)
		@NotEmpty(message = "合作伙伴ID不能为空")
		private String partnerId;
		/**
		 * 32位字母数字字符串,请求ID。用于请求去重。
		 */
		@ApiModelProperty(value = "请求流水号", required = true)
		@NotEmpty(message = "请求流水号不能为空")
		private String nonce;
		/**
		 * HMAC('SHA256')请求的签名
		 */
		@ApiModelProperty(value = "签名", required = true)
		@NotEmpty(message = "签名不能为空")
		private String signature;

	}

	@Data
	public static class ReqBody implements Serializable {
		/**
		 * 开始时间
		 */
		@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
		@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
		@NotNull(message = "开始时间不能为空")
		private Date startTime;
		/**
		 * 结束时间
		 */
		@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
		@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
		@NotNull(message = "结束时间不能为空")
		private Date endTime;
	}

}
MiGuOrderSyncResp
@Data
public class MiGuOrderSyncResp implements Serializable {
	private static final long serialVersionUID = -1383580636250379564L;

	private String resultCode;

	private String resultDesc;

	public MiGuOrderSyncResp() {
		this.setResultCode(ErrorCode.SUCCESS.getCode());
		this.setResultDesc(ErrorCode.SUCCESS.getMsg());
	}

	public  MiGuOrderSyncResp(ErrorCode errorCode) {
		this.setResultCode(errorCode.getCode());
		this.setResultDesc(errorCode.getMsg());
	}

	public MiGuOrderSyncResp(List<QueryMiGuOrderSyncRespBody> result) {
		this.setResultCode(ErrorCode.SUCCESS.getCode());
		this.setResultDesc(ErrorCode.SUCCESS.getMsg());
		this.setResult(result);
	}

	@JsonProperty("result")
	private List<QueryMiGuOrderSyncRespBody> result;

    @Data
	public static class QueryMiGuOrderSyncRespBody implements Serializable {

		private static final long serialVersionUID = 1L;

		// 订单id
		private String orderId;
		// 商品Id
		private String spuId;
		// 商品名
		private String spuName;
		// 规格信息
		private String specInfo;
		// 图片
		private String picUrl;
		// 商品数量
		private Integer quantity;
		// 咪咕奖励编码
		private String prizeCode;
		//咪咕订单号
		private String miguOrderNo;
		//昵称
		private String nickName;
		// 用户id
		private String userId;
		// 支付金额(销售金额+运费金额-积分抵扣金额-电子券抵扣金额)
		private BigDecimal paymentPrice;
		//付款时间
		private LocalDateTime paymentTime;
		// 订单状态1、待发货 2、待收货 3、确认收货/已完成 5、已关闭 10、拼团中
		private String orderStatus;
	}
}

2、nacos定义好咪咕相关配置信息

joolun-thirdparty-api-dev.yml:

#migu
migu: 
  partnerId: 
  secretkey: 
  corpId: 

3、同步咪咕参数配置

/**
 * @Description:  同步咪咕参数
 */
@Data
@RefreshScope
@Configuration
@ConfigurationProperties(prefix = "migu")
public class MiGuConfigProperties {
	/**
	 * 合作伙伴ID-tsp平台提供(类似appKey)
	 */
	private String partnerId;
	/**
	 * 企业id
	 */
	private String corpId;
	/**
	 * secretkey
	 */
	private String secretkey;
}

4、MiGuOrderSyncControl

@RestController
@AllArgsConstructor
@RequestMapping("sv")
@Slf4j
@Api(value = "MiGu_Order_Sync", tags = "咪咕订单同步模块API")
public class MiGuOrderSyncControl {
	@Autowired
	private MiGuOrderSyncService miGuOrderSyncService;

    /**
     * @Description: 咪咕同步订单对接
     */
	@ApiOperation(value = "咪咕订单同步任务")
	@PostMapping(value = "/app/miGuOrderSync")
	public MiGuOrderSyncResp miGuOrderSync(@Valid @RequestBody MiGuOrderSyncReq req) {
		log.info("MiGuOrderSyncDTO param:[{}]", JSON.toJSONString(req));
		MiGuOrderSyncResp resp = miGuOrderSyncService.miGuOrderSync(req);
		log.info("MiGuOrderSyncDTO resp:[{}]", JSON.toJSONString(resp));
		return resp;
	}
}

5、MiGuOrderSyncService

/**
 1. @Description: 咪咕同步订单对接
 */
public interface MiGuOrderSyncService {

	MiGuOrderSyncResp miGuOrderSync(MiGuOrderSyncReq miGuOrderSyncReq);
}

6、MiGuOrderSyncServiceImpl

方法需进行以下判断:
1.判断请求体的参数是否和nacos配置参数相等
2. 判断接口幂等性(因为这接口是给咪咕方调用,因此要防止接口调用超时重试)
3. 进行验签

代码如下:

@Service
@Slf4j
@AllArgsConstructor
public class MiGuOrderSyncServiceImpl implements MiGuOrderSyncService {
	private final MiGuConfigProperties miGuConfigProperties;
	private final RedisTemplate<String, String> redisTemplate;
	@Autowired
	private MiGuOrderSyncMapper miGuOrderSyncMapper;

	@Override
	public MiGuOrderSyncResp miGuOrderSync(MiGuOrderSyncReq req) {
		log.info("miGuOrderSync param:{}", JSON.toJSONString(req));
		MiGuOrderSyncReq.ReqHeader header = req.getHeader();
		MiGuOrderSyncReq.ReqBody body = req.getBody();
		if (!StrUtil.equals(miGuConfigProperties.getCorpId(), header.getCorpId())) {
			log.error("miGuOrderSync fail! corpId is error!");
			return new MiGuOrderSyncResp(ErrorCode.MIGU_ORDER_SYNC_ERROR1);
		}
		if (!StrUtil.equals(miGuConfigProperties.getPartnerId(), header.getPartnerId())) {
			log.error("miGuOrderSync fail! partnerId is error!");
			return new MiGuOrderSyncResp(ErrorCode.MIGU_ORDER_SYNC_ERROR2);
		}
		if (!validateApi(body, header)) {
			log.error("miGuOrderSync fail! request repeat!");
			return new MiGuOrderSyncResp(ErrorCode.IO_POINTS_ISSUE_ERROR5);
		}
		boolean signFlag = validateSign(header, body);
		if (!signFlag) {
			log.error("miGuOrderSync fail! sign is error!");
			return new MiGuOrderSyncResp(ErrorCode.MIGU_ORDER_SYNC_ERROR3);
		}
		List<MiGuOrderSyncDTO> miGuOrderSyncList = miGuOrderSyncMapper.queryMiGuOrderSync(body.getStartTime(),body.getEndTime());
		if (CollUtil.isEmpty(miGuOrderSyncList)) {
			log.info("miGuOrderSyncList is Empty!");
			return new MiGuOrderSyncResp(new ArrayList<>());
		}
		List<MiGuOrderSyncResp.QueryMiGuOrderSyncRespBody> result = BeanConvertUtils.convert(miGuOrderSyncList, MiGuOrderSyncResp.QueryMiGuOrderSyncRespBody.class);
		return new MiGuOrderSyncResp(result);
	}

	/**
	 * @Description: 接口幂等性
	 */
	private boolean validateApi(MiGuOrderSyncReq.ReqBody body, MiGuOrderSyncReq.ReqHeader header) {
		String key = body.getStartTime() + "_" + header.getNonce() + "_" + header.getSignature();
		if (incr(key, 2L) > 1) {
			return false;
		}
		return true;
	}

	/**
	 * @Description: Redis原子性自增
	 */
	private long incr(String key, long expireTime) {
		long next = new RedisAtomicLong(key, redisTemplate.getConnectionFactory()).incrementAndGet();
		redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
		return next;
	}

	/**
	 * @Description: 验签
	 */
	private boolean validateSign(MiGuOrderSyncReq.ReqHeader header, MiGuOrderSyncReq.ReqBody body) {
		Map<String, Object> params = BeanUtil.beanToMap(header);
		params.put("startTime", body.getStartTime());
		params.put("endTime", body.getEndTime());
		Map<String, Object> validateParams = new HashMap<>();
		validateParams.putAll(params);
		String signVal = MapUtil.getStr(validateParams, SyncDeptAndEmpConst.SIGNATURE);
		validateParams.remove(SyncDeptAndEmpConst.SIGNATURE);
		String val = CreateAscIISignUtil.getSignToken(validateParams);
		String sign = Hmacsha256Util.hmacMD5(val, miGuConfigProperties.getSecretkey());
		log.info("miGuOrderSync validateSign param:{}, sign:{},signVal:{}", val, sign, signVal);
		if (StrUtil.isNotBlank(signVal) && signVal.equals(sign)) {
			log.info("验签成功 param:{}, sign:{},signVal:{}", val, sign, signVal);
			return true;
		}
		log.error("验签失败 param:{}, sign:{},signVal:{}", val, sign, signVal);
		return false;
	}

}
CreateAscIISignUtil 生成参数 字典排序 签名
@Slf4j
public class CreateAscIISignUtil {
    /**
     * @MethodName: getSignToken
     * @Description: 生成签名
     */
    public static String getSignToken(Map<String, Object> map) {
        String result = "";
        try {
            List<Map.Entry<String, Object>> infoIds = new ArrayList<Map.Entry<String, Object>>(map.entrySet());
            // 对所有传入参数按照字段名的 ASCII 码从小到大排序(字典序)
            Collections.sort(infoIds, new Comparator<Map.Entry<String, Object>>() {
                @Override
                public int compare(Map.Entry<String, Object> o1, Map.Entry<String, Object> o2) {
                    return (o1.getKey()).toString().compareTo(o2.getKey());
                }
            });
            // 构造签名键值对的格式
            StringBuilder sb = new StringBuilder();
            for (Map.Entry<String, Object> item : infoIds) {
                if (StrUtil.isNotEmpty(item.getKey())) {
                    String key = item.getKey();
                    String val = StrUtil.toString(item.getValue());
                    if (StrUtil.isNotEmpty(val)) {
                        sb.append(key + "=" + val + "&");
                    }
                }
            }
            result = StrUtil.sub(sb, 0, sb.length()-1);
        } catch (Exception e) {
            log.error("CreateAscIISignUtil error = [{}]", e.getMessage(), e);
            return null;
        }
        return result;
    }
}

Hmacsha256Util 加密
	 /**
	 * @MethodName: hmacMD5
	 * @Description: HmacMD5加密
	 * @Param: [message加密原文, secret秘钥]
	 * @Return: java.lang.String加密后字符串
	 */
	public static String hmacMD5(String message, String secret) {

		String hash = "";
		try {
			Mac sha256_HMAC = Mac.getInstance("HmacMD5");
			SecretKeySpec secret_key = new SecretKeySpec(secret.getBytes(Charset.forName("UTF-8")), "HmacMD5");
			sha256_HMAC.init(secret_key);
			byte[] bytes = sha256_HMAC.doFinal(message.getBytes(Charset.forName("UTF-8")));
			hash = byteArrayToHexString(bytes);
		} catch (Exception e) {
			log.error("Hmacsha256Util hmacMD5 error = [{}]", e);
		}
		return hash;
	}

测试

请求如下:
在这里插入图片描述
返回结果:
在这里插入图片描述
成功拉兄弟姐妹们!!!!!
我师父看了我的代码表扬我了!!!!
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值