同步咪咕订单给咪咕方
- 前言
- 思路
- 实现
- 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")@Validprivate ReqHeader header;@JsonProperty("body")@Validprivate ReqBody body;@Datapublic 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;}@Datapublic 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;@Datapublic static class QueryMiGuOrderSyncRespBody implements Serializable {private static final long serialVersionUID = 1L;// 订单idprivate String orderId;// 商品Idprivate String spuId;// 商品名private String spuName;// 规格信息private String specInfo;// 图片private String picUrl;// 商品数量private Integer quantity;// 咪咕奖励编码private String prizeCode;//咪咕订单号private String miguOrderNo;//昵称private String nickName;// 用户idprivate 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 {@Autowiredprivate 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;@Autowiredprivate MiGuOrderSyncMapper miGuOrderSyncMapper;@Overridepublic 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>>() {@Overridepublic 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;}
测试
请求如下:
返回结果:
成功拉兄弟姐妹们!!!!!
我师父看了我的代码表扬我了!!!!