提交 d32484e9 authored 作者: 000516's avatar 000516

跨越回单获取

上级 9b7f0b29
......@@ -130,6 +130,11 @@
<groupId>com.kyexpress</groupId>
<artifactId>kye-openapi-sdk</artifactId>
</dependency>
<dependency>
<groupId>com.aliyun.oss</groupId>
<artifactId>aliyun-sdk-oss</artifactId>
</dependency>
</dependencies>
<build>
......
......@@ -15,4 +15,8 @@ public interface IOrdersSentDao {
OrdersSentDto getSent(String sentNo);
void updatePushqcByAhSentNo(String ahSentNo);
List<OrdersSentDto> getNotReceiptSent();
void updateByExpressNo(OrdersSentDto ordersSentDto);
}
package com.sfa.job.domain.order.dao.impl;
import com.alibaba.fastjson2.JSONArray;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.sfa.common.core.enums.ECode;
import com.sfa.common.core.exception.CheckedException;
......@@ -24,12 +25,22 @@ import java.util.Objects;
public class OrdersSentDaoImpl implements IOrdersSentDao {
@Autowired
private OrdersSentMapper ordersSentMapper;
/**
* 查询所有未push到勤策的发货单
* @return
*/
@Override
public List<OrdersSentDto> findByPushqcStatus() {
List<OrdersSent> sents = ordersSentMapper.findUnsyncQc();
return BeanUtils.transitionDtos(sents, OrdersSentDto.class);
}
/**
* 根据发货单编号查询发货单
* @param sentNo 发货单编号
* @return 发货单
*/
@Override
public OrdersSentDto getSent(String sentNo) {
OrdersSent sentDo = ordersSentMapper.selectBySentNo(sentNo);
......@@ -38,9 +49,27 @@ public class OrdersSentDaoImpl implements IOrdersSentDao {
}
return BeanUtils.transitionDto(sentDo, OrdersSentDto.class);
}
/**
* 根据发货单编号更新push_qc状态
* @param ahSentNo 发货单编号
*/
@Override
public void updatePushqcByAhSentNo(String ahSentNo) {
ordersSentMapper.updatePushqcByAhSentNo(ahSentNo);
}
/**
* 获取45天内未回单的发货单
*/
@Override
public List<OrdersSentDto> getNotReceiptSent() {
List<OrdersSent> sents = ordersSentMapper.getNotReceiptSent();
return BeanUtils.transitionDtos(sents, OrdersSentDto.class);
}
@Override
public void updateByExpressNo(OrdersSentDto ordersSentDto) {
OrdersSent ordersSent = BeanUtils.transitionDto(ordersSentDto, OrdersSent.class);
ordersSentMapper.updateByExpressNo(ordersSent);
}
}
......@@ -22,9 +22,13 @@ public class OrdersSent implements Serializable {
private Long sendId;
/**
* 单据编号
* 安徽单据编号
*/
private String ahSentNo;
/**
* 北京单据编号
*/
private String bjSentNo;
/**
......@@ -57,6 +61,11 @@ public class OrdersSent implements Serializable {
*/
private String transportName;
/**
* 经销商编码
*/
private String dealerCode;
/**
* 快递单号
*/
......@@ -72,6 +81,27 @@ public class OrdersSent implements Serializable {
*/
private Integer pushQc;
/**
* 签收时间,为空时代表未签收
*/
private Date operateEndDatetime;
private String sentStatus;
/**
* 回单状态:有/无/回单异常
*/
private String receiptFlag;
/**
* 回单图片
*/
private String receiptPhoto;
/**
* 回单图片是否完整
*/
private String receiptPhotoCompleteFlag;
/**
* 创建时间
*/
......
package com.sfa.job.domain.order.mapper;
import com.sfa.job.domain.order.entity.OrdersSent;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.sfa.job.domain.order.entity.OrdersSent;
import org.springframework.stereotype.Repository;
import java.util.List;
......@@ -20,6 +20,10 @@ public interface OrdersSentMapper extends BaseMapper<OrdersSent> {
OrdersSent selectBySentNo(String sentNo);
void updatePushqcByAhSentNo(String ahSentNo);
List<OrdersSent> getNotReceiptSent();
void updateByExpressNo(OrdersSent ordersSent);
}
......
......@@ -5,7 +5,7 @@ import lombok.Getter;
/**
* @author : liqiulin
* @date : 2025-04-08 18
* @describe :
* @describe : 飞书文档token记录
*/
@Getter
public enum FSRecordEnum {
......
package com.sfa.job.enums;
import lombok.Getter;
/**
* @author : liqiulin
* @date : 2025-08-13 10
* @describe :
*/
public interface KyeEnum {
/**
* 回单状态类型
* 10:回单原件(含回单照片),20:无需回单,30:回单照片,40:电子回单(传代码)
*/
@Getter
enum KyeReceiptEnum {
/**
* 回单原件(含回单照片)
*/
RECEIPT_ORIGINAL("10","回单原件"),
/**
* 无需回单
*/
NO_RECEIPT("20","无需回单"),
/**
* 回单照片
*/
RECEIPT_PHOTO("30","回单照片"),
/**
* 电子回单(传代码)
*/
ELECTRONIC_RECEIPT("40","电子回单");
private String type;
private String desc;
KyeReceiptEnum(String type, String desc) {
this.type = type;
this.desc = desc;
}
public static String getDesc(String type){
if (type == null){
return "回单异常";
}
for (KyeReceiptEnum value : values()) {
if (value.getType().equals(type)){
return value.getDesc();
}
}
return "回单异常";
}
public static boolean hasPhoto(String type){
return "10".equals(type) || "30".equals(type);
}
}
}
......@@ -66,6 +66,32 @@ public class OrdersSentDto implements Serializable {
* DD单号
*/
private String ddNo;
/**
* 经销商编码
*/
private String dealerCode;
private String sentStatus;
/**
* 签收时间,为空时代表未签收
*/
private Date operateEndDatetime;
/**
* 回单状态:有/无/回单异常
*/
private String receiptFlag;
/**
* 回单图片
*/
private String receiptPhoto;
/**
* 回单图片是否完整
*/
private String receiptPhotoCompleteFlag;
private List<OrderSentInfoResponse> sentInfo;
......
......@@ -12,4 +12,7 @@ public interface IOrdersSentQueryService {
void ordersSentToQince();
OrdersSentDto getSent(String sentNo);
void getOrderSentReceipt();
}
package com.sfa.job.service.order.impl;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.sfa.common.core.enums.ECode;
import com.sfa.common.core.exception.ServiceException;
import com.sfa.common.core.utils.DateUtils;
import com.sfa.job.domain.order.dao.IOrdersSentDao;
import com.sfa.job.enums.KyeEnum;
import com.sfa.job.pojo.response.OrdersSentDto;
import com.sfa.job.service.order.IOrdersSentQueryService;
import com.sfa.job.util.KyeUtil;
import com.sfa.job.util.QinCeUtils;
import com.sfa.job.util.aliyun.OssUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author : liqiulin
......@@ -27,6 +36,10 @@ public class OrdersSentQueryServiceImpl implements IOrdersSentQueryService {
private IOrdersSentDao ordersSentDao;
@Autowired
private QinCeUtils qinCeUtils;
@Autowired
private KyeUtil kyeUtil;
@Autowired
private OssUtil ossUtil;
@Override
public void ordersSentToQince() {
......@@ -44,6 +57,81 @@ public class OrdersSentQueryServiceImpl implements IOrdersSentQueryService {
return ordersSentDao.getSent(sentNo);
}
@Override
public void getOrderSentReceipt() {
List<OrdersSentDto> sents = ordersSentDao.getNotReceiptSent();
if (CollectionUtils.isEmpty(sents)) {
log.info("今日暂无,需回单单据");
return;
}
// 按物流公司分类
Map<String, List<OrdersSentDto>> transportMap = sents.stream().collect(Collectors.groupingBy(OrdersSentDto::getTransport));
getOrderSentReceiptBy109(transportMap.get("109"));
}
private void getOrderSentReceiptBy109(List<OrdersSentDto> ordersSentDtos) {
if (CollectionUtils.isEmpty(ordersSentDtos)) {
log.info("今日[109-跨越]暂无,需回单单据");
return;
}
// 填写Object完整路径(不包含Bucket名称),例:[path]/[文件名.扩展名]
String ossON = "wuliu/" + DateUtils.getYearMonth() + "/跨越/";
// 将ordersSentDtos按长度分割,每10个分割
List<List<OrdersSentDto>> split = ordersSentDtos.stream().collect(Collectors.groupingBy(it -> ordersSentDtos.indexOf(it) / 10)).values().stream().collect(Collectors.toList());
for (List<OrdersSentDto> os : split) {
List<String> exNos = os.stream().map(it -> it.getExpressNo()).collect(Collectors.toList());
JSONArray waybillBaseInfo = kyeUtil.getWaybillBaseInfo(exNos);
if (CollectionUtils.isEmpty(waybillBaseInfo)){
continue;
}
for (Object o : waybillBaseInfo) {
disReceipt((JSONObject) o,ossON);
}
// todo
break;
}
}
private void disReceipt(JSONObject infoJson,String ossON){
// 判断物流单是否已签收,未签收数据不处理
Date operateEndTime = infoJson.getDate("operateEndTime");
if (operateEndTime == null){
return;
}
String expressNo = infoJson.getString("waybillNumber");
String receiptFlag = infoJson.getString("receiptFlag");
OrdersSentDto ordersSentDto = new OrdersSentDto();
ordersSentDto.setExpressNo(expressNo);
ordersSentDto.setOperateEndDatetime(operateEndTime);
ordersSentDto.setReceiptFlag(KyeEnum.KyeReceiptEnum.getDesc(receiptFlag));
/**
* 回单类型为回单原件、回单照片时查询照片,否则跳过
*/
if (!KyeEnum.KyeReceiptEnum.hasPhoto(receiptFlag)){
ordersSentDao.updateByExpressNo(ordersSentDto);
return;
}
JSONArray objects = kyeUtil.queryWaybillPicture(expressNo);
if (CollectionUtils.isEmpty(objects)){
ordersSentDao.updateByExpressNo(ordersSentDto);
return;
}
List<String> urls = new ArrayList<>();
for (Object object : objects) {
JSONObject jsonObject = (JSONObject) object;
String url = jsonObject.getString("url");
String originalName = jsonObject.getString("originalName");
String ossUrl = ossUtil.uploadPhotoByte(url, ossON + originalName);
urls.add(ossUrl);
}
ordersSentDto.setReceiptPhoto(JSONObject.toJSONString(urls));
ordersSentDao.updateByExpressNo(ordersSentDto);
}
private void pushQc(OrdersSentDto sent){
try {
log.info("start push qc sent no [{}] 物流轨迹", sent.getAhSentNo());
......@@ -76,4 +164,5 @@ public class OrdersSentQueryServiceImpl implements IOrdersSentQueryService {
log.error("勤策推送物流地址失败,物流信息:{}", JSONObject.toJSONString(sent));
}
}
}
......@@ -10,6 +10,10 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author : liqiulin
* @date : 2025-07-29 17
......@@ -33,11 +37,16 @@ public class KyeUtil {
private Boolean IS_SANDBOX;
/**
* =================== JDTC API - month ===================
* =================== JDTC API - mothod ===================
*/
private final String QUERY_ROUTE_M = "open.api.openCommon.queryRoute";
private final String QUERY_PUBLIC_ROUTE_M = "open.api.openCommon.queryPublicRoute";
private final String GET_WAYBILL_BASE_INFO_M = "open.api.openCommon.getWaybillBaseInfo";
private final String QUERY_WAYBILL_PICTURE_M = "open.api.openCommon.queryWaybillPicture";
/**
* 物流轨迹
*/
public JSONArray getOrderTrace(String orderNo){
try {
Boolean isSandbox = IS_SANDBOX;
......@@ -59,4 +68,59 @@ public class KyeUtil {
throw new ServiceException(ECode.KYE_ORDER_TRACE_QUERY_ERROR);
}
}
/**
* 根据单号获取运单信息
*/
public JSONArray getWaybillBaseInfo(List<String> waybillNumbers) {
try {
Boolean isSandbox = IS_SANDBOX;
String appKey = APP_KEY;
String appSecret = APP_SECRET;
String customerCode = CUSTOMER_CODE;
Map<String,Object> param = new HashMap<>();
param.put("customerCode",customerCode);
param.put("waybillNumber",waybillNumbers);
String response = KyeDefaultOpenApi.builder(appKey, appSecret).env("prod").api(GET_WAYBILL_BASE_INFO_M).sandbox(isSandbox).body(JSONObject.toJSONString(param)).connectTimeout(3000).readTimeout(15000).request().response();
JSONObject rJson = JSONObject.parseObject(response);
if (!rJson.getBoolean("success")){
log.error("请求跨越[获取运单信息]接口返回异常:{}",response);
throw new ServiceException(ECode.KYE_ORDER_QUERY_ERROR);
}
return rJson.getJSONArray("data");
} catch (KyeOpenApiException e) {
throw new ServiceException(ECode.KYE_ORDER_QUERY_ERROR);
}
}
public JSONArray queryWaybillPicture(String expressNo) {
try {
Boolean isSandbox = IS_SANDBOX;
String appKey = APP_KEY;
String appSecret = APP_SECRET;
String customerCode = CUSTOMER_CODE;
Map<String,Object> param = new HashMap<>();
param.put("customerCode",customerCode);
param.put("waybillNumber",expressNo);
// 10=运单联、 20=签收联签字笔记、30=回单, 40=完整性照片、50=货物异常、60=签收联、70-货物开箱图、90-货物木架图
param.put("pictureType","30");
String response = KyeDefaultOpenApi.builder(appKey, appSecret).env("prod").api(QUERY_WAYBILL_PICTURE_M).sandbox(isSandbox).body(JSONObject.toJSONString(param)).connectTimeout(3000).readTimeout(15000).request().response();
JSONObject rJson = JSONObject.parseObject(response);
if (!rJson.getBoolean("success")){
if (Integer.valueOf(10010).equals(rJson.getInteger("code"))){
return new JSONArray();
}
log.error("请求跨越[下载图片]接口返回异常:{}:{}",expressNo,response);
throw new ServiceException(ECode.KYE_ORDER_QUERY_ERROR);
}
return rJson.getJSONObject("data").getJSONArray("filePictureInfoRes");
} catch (KyeOpenApiException e) {
log.error("跨越SDK异常:{}:{}",expressNo,e.getMessage());
throw new ServiceException(ECode.KYE_ORDER_QUERY_ERROR);
}
}
}
package com.sfa.job.util.aliyun;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;
/**
* @author : liqiulin
* @date : 2024-07-29 14
* @describe :
*/
public class HttpsUtils {
// 设置 https 请求
public static void trustAllHttpsCertificates() throws Exception {
HttpsURLConnection.setDefaultHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String str, SSLSession session) {
return true;
}
});
javax.net.ssl.TrustManager[] trustAllCerts = new javax.net.ssl.TrustManager[1];
javax.net.ssl.TrustManager tm = new miTM();
trustAllCerts[0] = tm;
javax.net.ssl.SSLContext sc = javax.net.ssl.SSLContext
.getInstance("SSL");
sc.init(null, trustAllCerts, null);
HttpsURLConnection.setDefaultSSLSocketFactory(sc
.getSocketFactory());
}
// 设置 https 请求证书
static class miTM implements javax.net.ssl.TrustManager, javax.net.ssl.X509TrustManager {
@Override
public java.security.cert.X509Certificate[] getAcceptedIssuers() {
return null;
}
public boolean isServerTrusted(
java.security.cert.X509Certificate[] certs) {
return true;
}
public boolean isClientTrusted(
java.security.cert.X509Certificate[] certs) {
return true;
}
@Override
public void checkServerTrusted(
java.security.cert.X509Certificate[] certs, String authType)
throws java.security.cert.CertificateException {
return;
}
@Override
public void checkClientTrusted(
java.security.cert.X509Certificate[] certs, String authType)
throws java.security.cert.CertificateException {
return;
}
}
}
package com.sfa.job.util.aliyun;
import com.aliyun.oss.*;
import com.aliyun.oss.common.auth.BasicCredentials;
import com.aliyun.oss.common.auth.DefaultCredentialProvider;
import com.aliyun.oss.common.comm.SignVersion;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.net.ssl.HttpsURLConnection;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
/**
* @author : liqiulin
* @date : 2025-08-13 15
* @describe :
*/
@Slf4j
@Component
public class OssUtil {
@Value("${aliyun.access-key}")
private String accessKey;
@Value("${aliyun.access-secret}")
private String accessSecret;
@Value("${aliyun.oss-sh.endpoint}")
private String endpoint;
@Value("${aliyun.oss-sh.bucket-name}")
private String bucketName;
@Value("${aliyun.oss-sh.region-id}")
private String region;
@Value("${aliyun.oss-sh.web-link}")
private String webLink;
/**
* 上传字节流
*/
public String uploadPhotoByte(String fileUrl,String objectName) {
OSS ossClient = null;
try {
DefaultCredentialProvider credentialsProvider = new DefaultCredentialProvider(new BasicCredentials(accessKey, accessSecret, null));
// 创建OSSClient实例,当OSSClient实例不再使用时,调用shutdown方法以释放资源。
ClientBuilderConfiguration clientBuilderConfiguration = new ClientBuilderConfiguration();
clientBuilderConfiguration.setSignatureVersion(SignVersion.V4);
ossClient = OSSClientBuilder.create()
.endpoint(endpoint)
.credentialsProvider(credentialsProvider)
.clientConfiguration(clientBuilderConfiguration)
.region(region)
.build();
// 填写Byte数组。
byte[] content = imageToByte(fileUrl);
// 创建PutObjectRequest对象。
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, new ByteArrayInputStream(content));
// 创建PutObject请求。
PutObjectResult result = ossClient.putObject(putObjectRequest);
return webLink + objectName;
} catch (OSSException oe) {
log.error("aliyun oss 拒绝字节流上传,Error Message:{};Error Code:{};Request ID:{};",oe.getErrorMessage(),oe.getErrorCode(),oe.getRequestId());
throw new RuntimeException(oe.getMessage());
} catch (ClientException ce) {
log.error("aliyun oss 内部错误,字节流上传失败:{}" + ce.getMessage());
throw new RuntimeException(ce.getMessage());
} catch (Exception e) {
log.error("Https类文件转换字节流异常:{}" + e.getMessage());
throw new RuntimeException(e.getMessage());
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
}
}
private byte[] imageToByte(String fileUrl) throws Exception {
URL url = new URL(fileUrl);
// 打开链接,并且跳过 SSL 证书验证
HttpsUtils.trustAllHttpsCertificates();
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(5 * 1000);
// 通过输入流获取图片数据
InputStream inStream = conn.getInputStream();
// 得到文件的二进制数据,以二进制封装得到数据,具有通用性
byte[] data = readInputStream(inStream);
// 关闭流
inStream.close();
return data;
}
private byte[] readInputStream(InputStream inStream) throws Exception {
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
// 每次读取的字符串长度,如果为-1,代表全部读取完毕
int len = 0;
// 使用一个输入流从buffer里把数据读取出来
while ((len = inStream.read(buffer)) != -1) {
// 用输出流往buffer里写入数据,中间参数代表从哪个位置开始读,len代表读取的长度
outStream.write(buffer, 0, len);
}
// 关闭输入流
inStream.close();
// 把outStream里的数据写入内存
return outStream.toByteArray();
}
}
......@@ -16,13 +16,21 @@ public class OrdersSentToQince {
@Autowired
private IOrdersSentQueryService ordersSentService;
/**
* 推送勤策发货单轨迹/物流承运商
*/
@XxlJob("push_qc_order_sent")
public void ordersSentToQince(){
ordersSentService.ordersSentToQince();
}
/**
* 查询发货单回单情况
*/
@XxlJob("get_order_sent_receipt")
public void getSentReceipt(){
ordersSentService.getOrderSentReceipt();
}
}
......@@ -17,6 +17,10 @@
<result property="expressNo" column="express_no" jdbcType="VARCHAR"/>
<result property="ddNo" column="dd_no" jdbcType="VARCHAR"/>
<result property="pushQc" column="push_qc" jdbcType="INTEGER"/>
<result property="operateEndDatetime" column="operate_end_dateTime" jdbcType="TIMESTAMP"/>
<result property="receiptFlag" column="receipt_flag" jdbcType="VARCHAR"/>
<result property="receiptPhoto" column="receipt_photo" jdbcType="VARCHAR"/>
<result property="receiptPhotoCompleteFlag" column="receipt_photo_complete_flag" jdbcType="VARCHAR"/>
<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
<result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
</resultMap>
......@@ -43,4 +47,20 @@
<update id="updatePushqcByAhSentNo" parameterType="java.lang.String">
update orders_sent set push_qc = 0 where ah_sent_no = #{ahSentNo}
</update>
<select id="getNotReceiptSent" resultMap="ordersSentResultMap">
select send_id, transport, transport_name, express_no, dd_no
from orders_sent
where transport = 109
and receipt_flag = '无'
and post_data &gt;= date_sub(CURDATE(), INTERVAL 45 DAY)
</select>
<update id="updateByExpressNo" parameterType="com.sfa.job.domain.order.entity.OrdersSent">
update orders_sent
set receipt_flag = #{receiptFlag},
operate_end_dateTime = #{operateEndDatetime},
receipt_photo = #{receiptPhoto}
where express_no = #{expressNo}
</update>
</mapper>
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论