同步咪咕订单给咪咕方
前言
需求:把小西中与咪咕相关的订单同步给咪咕方。
思路
思路如下:
- 定义好请求体和响应信息
- 在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;
}
测试
请求如下:
返回结果:
成功拉兄弟姐妹们!!!!!
我师父看了我的代码表扬我了!!!!
文章评论