Synchronize Migu orders to Migufang
foreword
Requirement: Synchronize Migu-related orders from Xiaoxizhong to Migufang.
train of thought
The idea is as follows:
- Define the request body and response information
- Define Migu related configuration information in nacos (for later verifying whether the request body is correct)
- write interface
accomplish
1. Define the request body and response information
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 defines Migu related configuration information
joolun-thirdparty-api-dev.yml:
#migu
migu:
partnerId:
secretkey:
corpId:
3. Synchronize MIGU parameter configuration
/**
* @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
The method needs to make the following judgments:
1. Judging whether the parameters of the request body are equal to the configuration parameters of nacos
2. Judging the idempotence of the interface (because this interface is called by Migu Fang, so it is necessary to prevent the interface call from retrying over time) 3. Conduct
testing sign
code show as below:
@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 generate parameters dictionary sort signature
@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 encryption
/**
* @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;
}
test
The request is as follows:
Return result:
Successfully pulled brothers and sisters! ! ! ! !
My master read my code and praised me! ! ! !