同步咪咕订单给咪咕方
- 前言
 - 思路
 - 实现
 - 1、定义请求体和响应信息
 - MiGuOrderSyncReq
 - MiGuOrderSyncResp
 
- 2、nacos定义好咪咕相关配置信息
 - 3、同步咪咕参数配置
 - 4、MiGuOrderSyncControl
 - 5、MiGuOrderSyncService
 - 6、MiGuOrderSyncServiceImpl
 - CreateAscIISignUtil 生成参数 字典排序 签名
 - Hmacsha256Util 加密
 
- 测试
 
前言
需求:把小西中与咪咕相关的订单同步给咪咕方。
思路
思路如下:
- 定义好请求体和响应信息
 - 在nacos定义好咪咕相关配置信息(用于之后验证请求体是否正确)
 - 写接口
 
实现
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;
	}
 
测试
请求如下:
 
 返回结果:
 
 成功拉兄弟姐妹们!!!!!
 我师父看了我的代码表扬我了!!!!
 



















