feat: 订单

pull/1121/head
梁宇奇 2025-04-17 18:02:03 +08:00
parent 56f22d1e5d
commit 7a1d5d0a34
15 changed files with 582 additions and 21 deletions

View File

@ -29,8 +29,8 @@ import java.util.Map;
*/
@Api(tags = "支付宝回调接口")
@RestController
@RequestMapping("/rest/v1/alipay")
public class AlipayController extends XktBaseController {
@RequestMapping("/rest/v1/alipay-callback")
public class AlipayCallbackController extends XktBaseController {
private static final String SUCCESS = "success";
private static final String FAILURE = "failure";

View File

@ -0,0 +1,53 @@
package com.ruoyi.web.controller.xkt;
import cn.hutool.core.util.StrUtil;
import com.ruoyi.common.core.controller.XktBaseController;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.web.controller.xkt.vo.express.ZtoPrintOrderReqVO;
import com.ruoyi.web.controller.xkt.vo.express.ZtoPrintOrderRespVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.TimeUnit;
/**
* @author liangyq
* @date 2025-04-17 14:18
*/
@Api(tags = "物流回调接口")
@RestController
@RequestMapping("/rest/v1/express-callback")
public class ExpressCallbackController extends XktBaseController {
@Autowired
private RedisCache redisCache;
/**
* -/PDF
* <p>
* httpUTF-8ContentType"application/json"POST
* status
* false16
*
* @param vo
* @return
*/
@ApiOperation("中通-生成面单图片/PDF回推")
@PostMapping("zto/print-order")
public ZtoPrintOrderRespVO ztoPrintOrder(@RequestBody ZtoPrintOrderReqVO vo) {
if (StrUtil.isNotEmpty(vo.getBillCode())
&& StrUtil.isNotEmpty(vo.getResult())) {
//缓存30分钟
redisCache.setCacheObject("ZTO-"+vo.getBillCode(),vo.getResult(),30, TimeUnit.MINUTES);
return ZtoPrintOrderRespVO.success();
}
return ZtoPrintOrderRespVO.failure();
}
}

View File

@ -0,0 +1,27 @@
package com.ruoyi.web.controller.xkt.vo.express;
import lombok.Data;
/**
* @author liangyq
* @date 2025-04-17 16:40
*/
@Data
public class ZtoPrintOrderReqVO {
/**
*
*/
private String billCode;
/**
* (0:JPG, 1:PDF.PDF)
*/
private String printType;
/**
* PDFBase64
*/
private String result;
/**
* dataDigest=base64(md5(result+billCode))
*/
private String dataDigest;
}

View File

@ -0,0 +1,46 @@
package com.ruoyi.web.controller.xkt.vo.express;
import lombok.Data;
/**
* @author liangyq
* @date 2025-04-17 16:34
*/
@Data
public class ZtoPrintOrderRespVO {
/**
* falsestatus=true
*/
private Boolean status;
/**
*
*/
private String message;
/**
*
*/
private String result;
/**
*
*/
private String statusCode;
public static ZtoPrintOrderRespVO success() {
ZtoPrintOrderRespVO vo = new ZtoPrintOrderRespVO();
vo.setStatus(true);
vo.setMessage("success");
vo.setResult("success");
vo.setStatusCode("0");
return vo;
}
public static ZtoPrintOrderRespVO failure() {
ZtoPrintOrderRespVO vo = new ZtoPrintOrderRespVO();
vo.setStatus(false);
vo.setMessage("failure");
vo.setResult("failure");
vo.setStatusCode("0");
return vo;
}
}

View File

@ -178,6 +178,14 @@ zto:
accountId: test
accountPassword: ZTO123
# 圆通配置
yto:
appKey: K9991024989
appSecret: u2Z1F7Fh
appKey2: TEST
appSecret2: 123456
gatewayUrl: https://openuat.yto56test.com:6443/
# 支付宝配置
alipay:
appId: 9021000144616672

View File

@ -116,7 +116,9 @@ public class SecurityConfig
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
//支付宝回调
.antMatchers("/rest/v1/alipay-notify").permitAll()
.antMatchers("/rest/v1/alipay-callback/**").permitAll()
//物流回调
.antMatchers("/rest/v1/express-callback/**").permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
})

View File

@ -11,7 +11,9 @@ import lombok.Getter;
@AllArgsConstructor
public enum EExpressChannel {
ZTO(1, "中通", 1L, "ZTO");
ZTO(1, "中通", 1L, "ZTO"),
YTO(2, "圆通", 2L, "YTO"),
;
private final Integer value;
private final String label;

View File

@ -0,0 +1,132 @@
package com.ruoyi.xkt.manager.impl;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.xkt.dto.express.ExpressShipReqDTO;
import com.ruoyi.xkt.enums.EExpressChannel;
import com.ruoyi.xkt.manager.ExpressManager;
import com.ruoyi.xkt.thirdpart.yto.YtoCreateOrderParam;
import com.ruoyi.xkt.thirdpart.yto.YtoPrintOrderParam;
import com.ruoyi.xkt.thirdpart.yto.YtoPublicRequest;
import com.ruoyi.xkt.thirdpart.yto.YtoSignUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
/**
* @author liangyq
* @date 2025-04-17 16:55
*/
@Slf4j
@Component
public class YtoExpressManagerImpl implements ExpressManager {
@Value("${yto.appKey:}")
private String appKey;
@Value("${yto.appSecret:}")
private String appSecret;
@Value("${yto.appKey2:}")
private String appKey2;
@Value("${yto.appSecret2:}")
private String appSecret2;
@Value("${yto.gatewayUrl:}")
private String gatewayUrl;
@Override
public EExpressChannel channel() {
return EExpressChannel.YTO;
}
@Override
public String shipStoreOrder(ExpressShipReqDTO shipReqDTO) {
YtoCreateOrderParam createOrderParam = trans2CreateOrderReq(shipReqDTO);
try {
String param = JSONUtil.toJsonStr(createOrderParam);
String sign = YtoSignUtil.sign("privacy_create_adapter", "v1", param, appSecret);
YtoPublicRequest request = YtoPublicRequest.builder()
.timestamp(System.currentTimeMillis())
.param(param)
.format(YtoPublicRequest.EFormat.JSON)
.sign(sign).build();
String rtnStr = HttpUtil.post(gatewayUrl + "open/privacy_create_adapter/v1/N364gM/" + appKey,
JSONUtil.toJsonStr(request));
log.info("圆通订单创建返回信息: {}", rtnStr);
JSONObject rtnJson = JSONUtil.parseObj(rtnStr);
String waybillNo = rtnJson.getStr("mailNo");
if (StrUtil.isNotEmpty(waybillNo)) {
return waybillNo;
}
} catch (Exception e) {
log.error("圆通订单创建异常", e);
}
throw new ServiceException("圆通订单创建失败");
}
@Override
public String printOrder(String waybillNo) {
YtoPrintOrderParam printOrderParam = YtoPrintOrderParam.builder().waybillNo(waybillNo).build();
try {
String param = JSONUtil.toJsonStr(printOrderParam);
String sign = YtoSignUtil.sign("waybill_print_adapter", "v1", param, appSecret);
YtoPublicRequest request = YtoPublicRequest.builder()
.timestamp(System.currentTimeMillis())
.param(param)
.format(YtoPublicRequest.EFormat.JSON)
.sign(sign).build();
String rtnStr = HttpUtil.post(gatewayUrl + "open/waybill_print_adapter/v1/N364gM/" + appKey,
JSONUtil.toJsonStr(request));
log.info("圆通打印回单返回信息: {}", rtnStr);
JSONObject rtnJson = JSONUtil.parseObj(rtnStr);
String rtn = rtnJson.getJSONObject("data").getStr("pdfBase64");
if (StrUtil.isNotEmpty(rtn)) {
return rtn;
}
} catch (Exception e) {
log.error("圆通打印回单异常", e);
}
throw new ServiceException("圆通打印回单失败");
}
private YtoCreateOrderParam trans2CreateOrderReq(ExpressShipReqDTO shipReqDTO) {
YtoCreateOrderParam reqDTO = new YtoCreateOrderParam();
reqDTO.setLogisticsNo(shipReqDTO.getExpressReqNo());
reqDTO.setSenderName(shipReqDTO.getOriginContactName());
reqDTO.setSenderProvinceName(shipReqDTO.getOriginProvinceName());
reqDTO.setSenderCityName(shipReqDTO.getOriginCityName());
reqDTO.setSenderCountyName(shipReqDTO.getOriginCountyName());
reqDTO.setSenderAddress(shipReqDTO.getOriginDetailAddress());
reqDTO.setSenderMobile(shipReqDTO.getOriginContactPhoneNumber());
reqDTO.setRecipientName(shipReqDTO.getDestinationContactName());
reqDTO.setRecipientProvinceName(shipReqDTO.getDestinationProvinceName());
reqDTO.setRecipientCityName(shipReqDTO.getDestinationCityName());
reqDTO.setRecipientCountyName(shipReqDTO.getDestinationCountyName());
reqDTO.setRecipientAddress(shipReqDTO.getDestinationDetailAddress());
reqDTO.setRecipientMobile(shipReqDTO.getDestinationContactPhoneNumber());
int goodsSize = shipReqDTO.getOrderItems().size();
if (shipReqDTO.getOrderItems().size() > 20) {
throw new ServiceException("圆通每次发货物品在20个以内");
}
reqDTO.setGoods(new ArrayList<>(goodsSize));
for (ExpressShipReqDTO.OrderItem item : shipReqDTO.getOrderItems()) {
YtoCreateOrderParam.OrderGoods goods = new YtoCreateOrderParam.OrderGoods();
reqDTO.getGoods().add(goods);
goods.setName(item.getName());
goods.setQuantity(item.getQuantity());
}
return reqDTO;
}
}

View File

@ -2,24 +2,30 @@ package com.ruoyi.xkt.manager.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.xkt.dto.express.ExpressShipReqDTO;
import com.ruoyi.xkt.enums.EExpressChannel;
import com.ruoyi.xkt.manager.ExpressManager;
import com.ruoyi.xkt.thirdpart.zto.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* @author liangyq
* @date 2025-04-15 15:45
*/
@Slf4j
@Component
public class ZtoExpressManagerImpl implements ExpressManager {
public class ZtoExpressManagerImpl implements ExpressManager, InitializingBean {
private static final String CREATE_ORDER_URI = "zto.open.createOrder";
@ -42,19 +48,23 @@ public class ZtoExpressManagerImpl implements ExpressManager {
@Value("${zto.accountPassword:}")
private String accountPassword;
@Autowired
private RedisCache redisCache;
private ZopClient client;
@Override
public EExpressChannel channel() {
return EExpressChannel.ZTO;
return EExpressChannel.YTO;
}
@Override
public String shipStoreOrder(ExpressShipReqDTO shipReqDTO) {
Assert.notNull(shipReqDTO);
Assert.notEmpty(shipReqDTO.getExpressReqNo());
ZtoCreateOrderReqDTO createOrderReq = trans2CreateOrderReq(shipReqDTO);
ZopClient client = new ZopClient(appKey, appSecret);
ZtoCreateOrderParam createOrderParam = trans2CreateOrderReq(shipReqDTO);
ZopPublicRequest request = new ZopPublicRequest();
request.setBody(JSONUtil.toJsonStr(createOrderReq));
request.setBody(JSONUtil.toJsonStr(createOrderParam));
request.setUrl(gatewayUrl + CREATE_ORDER_URI);
request.setEncryptionType(EncryptionType.MD5);
try {
@ -75,9 +85,8 @@ public class ZtoExpressManagerImpl implements ExpressManager {
@Override
public String printOrder(String waybillNo) {
Assert.notEmpty(waybillNo);
ZopClient client = new ZopClient(appKey, appSecret);
ZopPublicRequest request = new ZopPublicRequest();
request.setBody(JSONUtil.toJsonStr(new ZtoPrintOrderReqDTO(1,waybillNo,true)));
request.setBody(JSONUtil.toJsonStr(new ZtoPrintOrderParam(1, waybillNo, true)));
request.setUrl(gatewayUrl + ORDER_PRINT_URI);
request.setEncryptionType(EncryptionType.MD5);
try {
@ -87,7 +96,12 @@ public class ZtoExpressManagerImpl implements ExpressManager {
boolean success = bodyJson.getBool("status");
if (success) {
//TODO 测试环境接口不通
return bodyJson.getJSONObject("result").getStr("billCode");
//等待推送
TimeUnit.SECONDS.sleep(3);
String rtn = redisCache.getCacheObject("ZTO_" + waybillNo);
if (StrUtil.isNotEmpty(rtn)) {
return rtn;
}
}
} catch (Exception e) {
log.error("中通订单打印异常", e);
@ -102,7 +116,6 @@ public class ZtoExpressManagerImpl implements ExpressManager {
* @return
*/
public JSONObject structureNamePhoneAddress(String str) {
ZopClient client = new ZopClient(appKey, appSecret);
ZopPublicRequest request = new ZopPublicRequest();
JSONObject body = new JSONObject();
body.set("address", str);
@ -123,8 +136,8 @@ public class ZtoExpressManagerImpl implements ExpressManager {
throw new ServiceException("中通智能解析失败");
}
private ZtoCreateOrderReqDTO trans2CreateOrderReq(ExpressShipReqDTO expressShipReqDTO) {
ZtoCreateOrderReqDTO reqDTO = new ZtoCreateOrderReqDTO();
private ZtoCreateOrderParam trans2CreateOrderReq(ExpressShipReqDTO expressShipReqDTO) {
ZtoCreateOrderParam reqDTO = new ZtoCreateOrderParam();
//合作模式 1集团客户2非集团客户
reqDTO.setPartnerType("2");
//partnerType为1时orderType1全网件 2预约件。 partnerType为2时orderType1全网件 2预约件返回运单号 3预约件不返回运单号 4星联全网件
@ -133,7 +146,7 @@ public class ZtoExpressManagerImpl implements ExpressManager {
reqDTO.setPartnerOrderCode(expressShipReqDTO.getExpressReqNo());
//账号信息
ZtoCreateOrderReqDTO.AccountInfo accountInfo = new ZtoCreateOrderReqDTO.AccountInfo();
ZtoCreateOrderParam.AccountInfo accountInfo = new ZtoCreateOrderParam.AccountInfo();
reqDTO.setAccountInfo(accountInfo);
//电子面单账号partnerType为2orderType传1,2,4时必传
accountInfo.setAccountId(accountId);
@ -145,7 +158,7 @@ public class ZtoExpressManagerImpl implements ExpressManager {
accountInfo.setCustomerId(null);
//发件人信息
ZtoCreateOrderReqDTO.SenderInfo senderInfo = new ZtoCreateOrderReqDTO.SenderInfo();
ZtoCreateOrderParam.SenderInfo senderInfo = new ZtoCreateOrderParam.SenderInfo();
reqDTO.setSenderInfo(senderInfo);
senderInfo.setSenderName(expressShipReqDTO.getOriginCountyName());
senderInfo.setSenderPhone(expressShipReqDTO.getOriginContactPhoneNumber());
@ -155,7 +168,7 @@ public class ZtoExpressManagerImpl implements ExpressManager {
senderInfo.setSenderAddress(expressShipReqDTO.getOriginDetailAddress());
//收件人信息
ZtoCreateOrderReqDTO.ReceiveInfo receiveInfo = new ZtoCreateOrderReqDTO.ReceiveInfo();
ZtoCreateOrderParam.ReceiveInfo receiveInfo = new ZtoCreateOrderParam.ReceiveInfo();
reqDTO.setReceiveInfo(receiveInfo);
receiveInfo.setReceiverName(expressShipReqDTO.getDestinationContactName());
receiveInfo.setReceiverPhone(expressShipReqDTO.getDestinationContactPhoneNumber());
@ -166,10 +179,14 @@ public class ZtoExpressManagerImpl implements ExpressManager {
//货物信息
reqDTO.setOrderItems(BeanUtil.copyToList(expressShipReqDTO.getOrderItems(),
ZtoCreateOrderReqDTO.OrderItem.class));
ZtoCreateOrderParam.OrderItem.class));
return reqDTO;
}
@Override
public void afterPropertiesSet() throws Exception {
this.client = new ZopClient(appKey, appSecret);
}
}

View File

@ -0,0 +1,179 @@
package com.ruoyi.xkt.thirdpart.yto;
import lombok.Data;
import java.util.List;
/**
* @author liangyq
* @date 2025-04-15 19:20
*/
@Data
public class YtoCreateOrderParam {
/**
* 7
*/
private String logisticsNo;
/**
*
*/
private String senderName;
/**
*
*/
private String senderProvinceName;
/**
*
*/
private String senderCityName;
/**
*
*/
private String senderCountyName;
/**
*
*/
private String senderTownName;
/**
*
*/
private String senderAddress;
/**
*
*/
private String senderMobile;
/**
*
*/
private String recipientName;
/**
*
*/
private String recipientProvinceName;
/**
*
*/
private String recipientCityName;
/**
*
*/
private String recipientCountyName;
/**
*
*/
private String recipientTownName;
/**
*
*/
private String recipientAddress;
/**
*
*/
private String recipientMobile;
/**
*
*/
private String remark;
/**
*
*/
private String gotCode;
/**
*
*/
private List<OrderIncrement> increments;
/**
* 20
*/
private List<OrderGoods> goods;
/**
* yyyy-MM-dd HH:mm:ss00:00:00+623:59:59
*/
private String startTime;
/**
* yyyy-MM-dd HH:mm:ss00:00:00+623:59:59
*/
private String endTime;
/**
*
*/
private String cstBusinessType;
/**
*
*/
private String cstOrderNo;
/**
*
*/
private String realNameInfo;
/**
*
*/
private String weight;
/**
* YZD-XTCTK-HKJ-PK-PK
*/
private String productCode;
@Data
public static class OrderIncrement {
/**
* 1-2-4-
*/
private Integer type;
/**
* [3,20000][1,5000][100,30000]
*/
private String amount;
}
@Data
public static class OrderGoods {
/**
*
*/
private String name;
/**
*
*/
private String weight;
/**
*
*/
private String length;
/**
*
*/
private String width;
/**
*
*/
private String height;
/**
*
*/
private String price;
/**
*
*/
private Integer quantity;
}
@Data
public static class RealNameInfo {
/**
*
*/
private String name;
/**
*
*/
private String cerNum;
/**
* 111213簿14151617181920101102103
*/
private String cerType;
}
}

View File

@ -0,0 +1,25 @@
package com.ruoyi.xkt.thirdpart.yto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author liangyq
* @date 2025-04-15 19:20
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class YtoPrintOrderParam {
/**
*
*/
private String waybillNo;
/**
* 130
*/
private String remark;
}

View File

@ -0,0 +1,39 @@
package com.ruoyi.xkt.thirdpart.yto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author liangyq
* @date 2025-03-14
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class YtoPublicRequest {
/**
*
*/
private String sign;
/**
*
*/
private Long timestamp;
/**
*
*/
private String param;
/**
* param
*/
private EFormat format;
public enum EFormat {
JSON,
XML
}
}

View File

@ -0,0 +1,31 @@
package com.ruoyi.xkt.thirdpart.yto;
import cn.hutool.core.codec.Base64;
import cn.hutool.crypto.digest.DigestUtil;
/**
* @author liangyq
* @date 2025-03-14
*/
public class YtoSignUtil {
/**
*
* 1 POSTsign
* 2 param+method+v data data
* 3 data opentest partnerId123456 opentest123456opentest123456MD5base64 md5(16byte)base64 YLstCNa3x8ijQx16e/jqOA==
*
* @return
*/
public static String sign(String method, String version, String param, String secret) {
String data = param + method + version;
return sign(data, secret);
}
public static String sign(String data, String secret) {
//进行md5加密,然后对数组进行base64编码
byte[] bytes = DigestUtil.md5(data + secret);
return Base64.encodeStr(bytes, false, false);
}
}

View File

@ -11,7 +11,7 @@ import java.util.List;
* @date 2025-04-15 19:20
*/
@Data
public class ZtoCreateOrderReqDTO {
public class ZtoCreateOrderParam {
/**
* 12
*/

View File

@ -11,7 +11,7 @@ import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ZtoPrintOrderReqDTO {
public class ZtoPrintOrderParam {
/**
* (0:JPG, 1:PDF.PDF)
*/