提交 7f444811 authored 作者: 000516's avatar 000516

1、订单 - 发货单物流查询(限制次数5/h);2、定时向勤策回写轨迹数据并变更推送状态

...@@ -7,4 +7,5 @@ package com.sfa.job.constants; ...@@ -7,4 +7,5 @@ package com.sfa.job.constants;
*/ */
public interface RedisKeyJob { public interface RedisKeyJob {
String FEISHU_EVENT_CREATE_DEPT = "job:feishu_event:create_dept_"; String FEISHU_EVENT_CREATE_DEPT = "job:feishu_event:create_dept_";
String QINCE_ORDER_SENT_INTERNET_COUNT = "job:qc_order_sent:sent_no_";
} }
package com.sfa.job.controller.order;
import com.alibaba.fastjson2.JSONArray;
import com.sfa.common.core.enums.ECode;
import com.sfa.common.core.exception.ServiceException;
import com.sfa.common.redis.service.RedisService;
import com.sfa.job.constants.RedisKeyJob;
import com.sfa.job.pojo.response.OrdersSentDto;
import com.sfa.job.service.order.IOrdersSentQueryService;
import com.sfa.job.util.JdtcUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author : liqiulin
* @date : 2025-07-08 13
* @describe : 订单 - 发货单物流查询
*/
@RestController
@RequestMapping("/sent")
public class SentQueryController {
@Autowired
private JdtcUtil jdtcUtil;
@Autowired
private IOrdersSentQueryService orderSentQueryService;
@Autowired
private RedisService redisService;
@GetMapping("/query_p")
public Object query(String sentNo){
// 限制单号每小时只能查询5次
String rKey = RedisKeyJob.QINCE_ORDER_SENT_INTERNET_COUNT + sentNo;
Object cacheObject = redisService.getCacheObject(rKey);
if (cacheObject != null && Integer.parseInt(cacheObject.toString()) >= 5) {
throw new ServiceException(ECode.SENT_NO_QUERY_COUNT_ERROR);
}
Object sent = queryBySentNode(sentNo);
redisService.setCacheObject(rKey, cacheObject == null ? 1 : Integer.parseInt(cacheObject.toString()) + 1);
return sent;
}
private Object queryBySentNode(String sentNo){
OrdersSentDto sent = orderSentQueryService.getSent(sentNo);
Object sentInfo = null;
switch (sent.getTransport()) {
case "134":
sentInfo = jdTC134(sent);
break;
default:
throw new ServiceException(ECode.SENT_ISNULL_ERROR);
}
sent.setSentInfo(sentInfo);
return sent;
}
private Object jdTC134(OrdersSentDto sent){
JSONArray traces = jdtcUtil.getOrderTrace(sent.getBjSentNo() + "-" + sent.getBjSentVersion());
return CollectionUtils.isEmpty(traces) ? null : traces.get(0);
}
}
package com.sfa.job.domain.order.dao;
import com.sfa.job.pojo.response.OrdersSentDto;
import java.util.List;
/**
* @author : liqiulin
* @date : 2025-07-10 16
* @describe :
*/
public interface IOrdersSentDao {
List<OrdersSentDto> findByPushqcStatus();
OrdersSentDto getSent(String sentNo);
void updatePushqcByAhSentNo(String ahSentNo);
}
package com.sfa.job.domain.order.dao.impl;
import com.baomidou.dynamic.datasource.annotation.DS;
import com.sfa.common.core.enums.ECode;
import com.sfa.common.core.exception.CheckedException;
import com.sfa.common.core.utils.bean.BeanUtils;
import com.sfa.job.domain.order.dao.IOrdersSentDao;
import com.sfa.job.domain.order.entity.OrdersSent;
import com.sfa.job.domain.order.mapper.OrdersSentMapper;
import com.sfa.job.pojo.response.OrdersSentDto;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Objects;
/**
* @author : liqiulin
* @date : 2025-07-10 16
* @describe :
*/
@DS("bi")
@Service
public class OrdersSentDaoImpl implements IOrdersSentDao {
@Autowired
private OrdersSentMapper ordersSentMapper;
@Override
public List<OrdersSentDto> findByPushqcStatus() {
List<OrdersSent> sents = ordersSentMapper.findUnsyncQc();
return BeanUtils.transitionDtos(sents, OrdersSentDto.class);
}
@Override
public OrdersSentDto getSent(String sentNo) {
OrdersSent sentDo = ordersSentMapper.selectBySentNo(sentNo);
if (Objects.isNull(sentDo)){
throw new CheckedException(ECode.PARAM_CODE_ISNULL_ERROR);
}
return BeanUtils.transitionDto(sentDo, OrdersSentDto.class);
}
@Override
public void updatePushqcByAhSentNo(String ahSentNo) {
ordersSentMapper.updatePushqcByAhSentNo(ahSentNo);
}
}
package com.sfa.job.domain.order.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
import java.util.Date;
import lombok.Data;
/**
* T100出货单中的物流单据
* @TableName orders_sent
*/
@TableName(value ="orders_sent")
@Data
public class OrdersSent implements Serializable {
/**
* 发货单自增ID
*/
@TableId(type = IdType.AUTO)
private Long sendId;
/**
* 单据编号
*/
private String ahSentNo;
private String bjSentNo;
/**
* 版本号
*/
private Integer bjSentVersion;
/**
* X06:销售出库单;N02:杂发单
*/
private String type;
/**
* 过账日期
*/
private Date postData;
/**
* 状态
*/
private String status;
/**
* 运输公司编码
*/
private String transport;
/**
* 运输公司
*/
private String transportName;
/**
* 快递单号
*/
private String expressNo;
/**
* DD单号
*/
private String ddNo;
/**
* 0:已推送;1:未推送(推送勤策)
*/
private Integer pushQc;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}
\ No newline at end of file
package com.sfa.job.domain.order.mapper;
import com.sfa.job.domain.order.entity.OrdersSent;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* @author a02200059
* @description 针对表【orders_sent(T100出货单中的物流单据)】的数据库操作Mapper
* @createDate 2025-07-10 16:20:04
* @Entity com.sfa.job.domain.order.entity.OrdersSent
*/
@Repository
public interface OrdersSentMapper extends BaseMapper<OrdersSent> {
List<OrdersSent> findUnsyncQc();
OrdersSent selectBySentNo(String sentNo);
void updatePushqcByAhSentNo(String ahSentNo);
}
package com.sfa.job.pojo.response;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* T100出货单中的物流单据
*/
@Data
public class OrdersSentDto implements Serializable {
/**
* 发货单自增ID
*/
private Long sendId;
/**
* 单据编号
*/
/**
* 单据编号
*/
private String ahSentNo;
private String bjSentNo;
/**
* 版本号
*/
private Integer bjSentVersion;
/**
* X06:销售出库单;N02:杂发单
*/
private String type;
/**
* 过账日期
*/
private Date postData;
/**
* 状态
*/
private String status;
/**
* 运输公司编码
*/
private String transport;
/**
* 运输公司
*/
private String transportName;
/**
* 快递单号
*/
private String expressNo;
/**
* DD单号
*/
private String ddNo;
private Object sentInfo;
@TableField(exist = false)
private static final long serialVersionUID = 1L;
}
\ No newline at end of file
package com.sfa.job.service.order;
import com.sfa.job.pojo.response.OrdersSentDto;
/**
* @author : liqiulin
* @date : 2025-07-10 16
* @describe :
*/
public interface IOrdersSentQueryService {
void ordersSentToQince();
OrdersSentDto getSent(String sentNo);
}
package com.sfa.job.service.order.impl;
import com.alibaba.fastjson.JSONObject;
import com.sfa.job.domain.order.dao.IOrdersSentDao;
import com.sfa.job.pojo.response.OrdersSentDto;
import com.sfa.job.service.order.IOrdersSentQueryService;
import com.sfa.job.util.QinCeUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
/**
* @author : liqiulin
* @date : 2025-07-10 16
* @describe :
*/
@Slf4j
@Service
public class OrdersSentQueryServiceImpl implements IOrdersSentQueryService {
@Autowired
private IOrdersSentDao ordersSentDao;
@Autowired
private QinCeUtils qinCeUtils;
@Override
public void ordersSentToQince() {
// 查询所有未push到勤策的发货单
List<OrdersSentDto> sents = ordersSentDao.findByPushqcStatus();
log.info("查询未推送到勤策的发货单:{}", sents.size());
for (OrdersSentDto sent : sents) {
// 修改勤策物流https地址
pushQc(sent);
}
}
@Override
public OrdersSentDto getSent(String sentNo) {
return ordersSentDao.getSent(sentNo);
}
private void pushQc(OrdersSentDto sent){
try {
log.info("start push qc sent no [{}] 物流轨迹", sent.getAhSentNo());
String wlHtmlPath = qinCeUtils.wlHtmlPath;
wlHtmlPath += sent.getAhSentNo();
// 勤策中发货单按安徽匹配
Map<String, Object> params = qinCeUtils.modifySentDefinedValParams(sent.getAhSentNo(), wlHtmlPath);
String url = qinCeUtils.builderUrl(QinCeUtils.MODIFY_SENT_DEFINED_VAL, params);
qinCeUtils.postQC(url, params);
// 推送成功
ordersSentDao.updatePushqcByAhSentNo(sent.getAhSentNo());
log.info("end push qc sent no [{}] 物流轨迹", sent.getAhSentNo());
}catch (Exception e) {
log.error("勤策推送物流地址失败,物流信息:{}", JSONObject.toJSONString(sent));
}
}
}
package com.sfa.job.util;
import cn.hutool.http.HttpUtil;
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 lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.time.LocalDateTime;
import java.time.OffsetTime;
import java.time.format.DateTimeFormatter;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* @author : liqiulin
* @date : 2025-07-08 16
* @describe :
*/
@Slf4j
@Component
public class JdtcUtil {
/**
* =================== JDTC API - config ===================
*/
@Value("${jdtc.url}")
private String JDTC_URL;
@Value("${jdtc.accesstoken}")
private String TOKEN;
@Value("${jdtc.wl_appkey}")
private String WL_APPKEY;
@Value("${jdtc.wl_appsecret}")
private String WL_APPSECRET;
@Value("${jdtc.pin}")
private String PIN;
/**
* =================== JDTC API - path ===================
*/
private final String ORDER_TRACE_QUERY_PATH = "/TransferCenterService/order/trace/query/v1";
private final String DOMAIN = "TransferCenterService";
private final String HEX_CHARACTERS = "0123456789ABCDEF";
private final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public JSONArray getOrderTrace(String orderNo) {
try {
String baseUri = JDTC_URL;
String appKey = WL_APPKEY;
String appSecret = WL_APPSECRET;
String accessToken = TOKEN;
String algorithm = "md5-salt";
String body = "[{\"pin\":\""+PIN+"\",\"purchaseOrderNo\":\""+orderNo+"\"}]";
String timestamp = DATE_TIME_FORMATTER.format(LocalDateTime.now());
String content = String.join("", new String[]{
appSecret,
"access_token", accessToken,
"app_key", appKey,
"method", ORDER_TRACE_QUERY_PATH,
"param_json", body,
"timestamp", timestamp,
"v", "2.0",
appSecret
});
Map<String, String> urlParams = new HashMap<>();
urlParams.put("LOP-DN", DOMAIN);
urlParams.put("access_token", accessToken);
urlParams.put("app_key", appKey);
urlParams.put("timestamp", timestamp);
urlParams.put("v", "2.0");
urlParams.put("sign", sign(algorithm, content.getBytes(StandardCharsets.UTF_8), appSecret.getBytes(StandardCharsets.UTF_8)));
urlParams.put("algorithm", algorithm);
Map<String, String> headers = new HashMap<>();
// lop-tz代表时区,为接口调用当地的时区;删去后默认为东八区
headers.put("lop-tz", String.valueOf(OffsetTime.now().getOffset().getTotalSeconds() / 3600));
// 用于开放平台识别客户调用API方式,客户无需修改
headers.put("User-Agent", "lop-http/java");
headers.put("content-type", "application/json;charset=utf-8");
String req = HttpUtil.createPost(baseUri + ORDER_TRACE_QUERY_PATH + "?" + httpBuildQuery(urlParams)).addHeaders(headers).body(body).execute().body();
JSONObject reqJson = JSONObject.parseObject(req);
if (!reqJson.getString("code").equals("1000")){
log.error("请求京东TC物流轨迹接口返回异常:{}",reqJson);
throw new ServiceException(ECode.JINGDONG_TC_ORDER_TRACE_API_ERROR);
}
JSONObject data = reqJson.getJSONObject("data");
return data.containsKey("purchaseTraceList") ? data.getJSONArray("purchaseTraceList") : new JSONArray();
} catch (GeneralSecurityException | UnsupportedEncodingException e) {
throw new ServiceException(ECode.JINGDONG_TC_ORDER_TRACE_QUERY_ERROR);
}
}
private String sign(String algorithm, byte[] data, byte[] secret) throws GeneralSecurityException {
if (Objects.equals(algorithm, "md5-salt")) {
return bytesToHex(MessageDigest.getInstance("md5").digest(data));
} else if (Objects.equals(algorithm, "HMacMD5")) {
Mac mac = Mac.getInstance(algorithm);
mac.init(new SecretKeySpec(secret, algorithm));
return Base64.getEncoder().encodeToString(mac.doFinal(data));
} else if (Objects.equals(algorithm, "HMacSHA1")) {
Mac mac = Mac.getInstance(algorithm);
mac.init(new SecretKeySpec(secret, algorithm));
return Base64.getEncoder().encodeToString(mac.doFinal(data));
} else if (Objects.equals(algorithm, "HMacSHA256")) {
Mac mac = Mac.getInstance(algorithm);
mac.init(new SecretKeySpec(secret, algorithm));
return Base64.getEncoder().encodeToString(mac.doFinal(data));
} else if (Objects.equals(algorithm, "HMacSHA512")) {
Mac mac = Mac.getInstance(algorithm);
mac.init(new SecretKeySpec(secret, algorithm));
return Base64.getEncoder().encodeToString(mac.doFinal(data));
}
throw new GeneralSecurityException("Algorithm " + algorithm + " not supported yet");
}
private String bytesToHex(byte[] bytes) {
StringBuilder stringBuilder = new StringBuilder(bytes.length * 2);
for (byte b : bytes) {
stringBuilder.append(HEX_CHARACTERS.charAt((b >>> 4) & 0x0F));
stringBuilder.append(HEX_CHARACTERS.charAt(b & 0x0F));
}
return stringBuilder.toString();
}
private String httpBuildQuery(Map<String, String> query) throws UnsupportedEncodingException {
StringBuilder stringBuilder = new StringBuilder();
boolean first = true;
for (Map.Entry<String, String> entry : query.entrySet()) {
if (!first) {
stringBuilder.append("&");
} else {
first = false;
}
stringBuilder.append(entry.getKey()).append("=").append(URLEncoder.encode(entry.getValue(), StandardCharsets.UTF_8.name()));
}
return stringBuilder.toString();
}
}
...@@ -9,6 +9,7 @@ import org.springframework.beans.factory.annotation.Value; ...@@ -9,6 +9,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
...@@ -31,7 +32,8 @@ public class QinCeUtils { ...@@ -31,7 +32,8 @@ public class QinCeUtils {
private String OPEN_ID; private String OPEN_ID;
@Value("${qince.app_key}") @Value("${qince.app_key}")
private String APP_KEY; private String APP_KEY;
@Value("${wxl.wl.html_path}")
public String wlHtmlPath;
/** /**
* =================== 勤策API - path =================== * =================== 勤策API - path ===================
...@@ -40,6 +42,9 @@ public class QinCeUtils { ...@@ -40,6 +42,9 @@ public class QinCeUtils {
public static final String MODIFY_DEALER = "/api/dealer/v1/modifyDealer/"; public static final String MODIFY_DEALER = "/api/dealer/v1/modifyDealer/";
public static final String MODIFY_STORE = "/api/store/v1/modifyStore/"; public static final String MODIFY_STORE = "/api/store/v1/modifyStore/";
public static final String QUERY_CUS_VISIT_RECORD = "/api/cusVisit/v1/queryCusVisitRecord/"; public static final String QUERY_CUS_VISIT_RECORD = "/api/cusVisit/v1/queryCusVisitRecord/";
// 直营发货单自定义字段更新
public static final String MODIFY_SENT_DEFINED_VAL = "/api/dmssent/v1/modifyUseDefinedVal/";
public String builderUrl(String sidepath, Map<String, Object> params) { public String builderUrl(String sidepath, Map<String, Object> params) {
String msgId = UUID.randomUUID().toString(); String msgId = UUID.randomUUID().toString();
...@@ -58,6 +63,16 @@ public class QinCeUtils { ...@@ -58,6 +63,16 @@ public class QinCeUtils {
return params; return params;
} }
public Map<String, Object> modifySentDefinedValParams(String sentNo,String wlHtmlPath){
Map<String,Object> vals = new HashMap<>();
vals.put("ext_key","物流轨迹");
vals.put("ext_value",wlHtmlPath);
Map<String,Object> params = new HashMap<>();
params.put("sent_no",sentNo);
params.put("exts", Arrays.asList(vals));
return params;
}
public JSONObject postQC(String url, Object params) throws Exception { public JSONObject postQC(String url, Object params) throws Exception {
...@@ -65,7 +80,7 @@ public class QinCeUtils { ...@@ -65,7 +80,7 @@ public class QinCeUtils {
JSONObject resultJson = JSONObject.parseObject(requestBody); JSONObject resultJson = JSONObject.parseObject(requestBody);
String returnCode = resultJson.getString("return_code"); String returnCode = resultJson.getString("return_code");
if (!"0".equals(returnCode)) { if (!"0".equals(returnCode)) {
throw new RuntimeException("OkHttp.post请求error,详情:" + requestBody); throw new RuntimeException("OkHttp.post 勤策接口返回值异常,详情:" + requestBody);
} }
return resultJson; return resultJson;
} }
......
package com.sfa.job.xxljob.order;
import com.sfa.job.service.order.IOrdersSentQueryService;
import com.xxl.job.core.handler.annotation.XxlJob;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author : liqiulin
* @date : 2025-07-10 16
* @describe : 勤策订单
*/
@Component
public class OrdersSentToQince {
@Autowired
private IOrdersSentQueryService ordersSentService;
@XxlJob("push_qc_order_sent")
public void ordersSentToQince(){
ordersSentService.ordersSentToQince();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.sfa.job.domain.order.mapper.OrdersSentMapper">
<resultMap id="BaseResultMap" type="com.sfa.job.domain.order.entity.OrdersSent">
<id property="sendId" column="send_id" jdbcType="BIGINT"/>
<result property="ahSentNo" column="ah_sent_no" jdbcType="VARCHAR"/>
<result property="bjSentNo" column="bj_sent_no" jdbcType="VARCHAR"/>
<result property="bjSentVersion" column="bj_sent_version" jdbcType="INTEGER"/>
<result property="type" column="type" jdbcType="CHAR"/>
<result property="postData" column="post_data" jdbcType="DATE"/>
<result property="status" column="status" jdbcType="CHAR"/>
<result property="transport" column="transport" jdbcType="VARCHAR"/>
<result property="transportName" column="transport_name" jdbcType="VARCHAR"/>
<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="createTime" column="create_time" jdbcType="TIMESTAMP"/>
<result property="updateTime" column="update_time" jdbcType="TIMESTAMP"/>
</resultMap>
<sql id="Base_Column_List">
send_id
,sent_no,sent_version,
type,post_data,status,
transport,transport_name,express_no,
dd_no,push_qc,create_time,
update_time
</sql>
<select id="findUnsyncQc" resultMap="BaseResultMap">
select send_id, ah_sent_no, bj_sent_no, transport,transport_name, express_no
from orders_sent
where push_qc = 1
</select>
<select id="selectBySentNo" resultMap="BaseResultMap">
select ah_sent_no,bj_sent_no,bj_sent_version,transport,transport_name,express_no,dd_no
from orders_sent
<where>
<if test="sentNo.startsWith('BJHQ')">
bj_sent_no = #{sentNo}
</if>
<if test="sentNo.startsWith('AHSD')">
ah_sent_no = #{sentNo}
</if>
</where>
</select>
<update id="updatePushqcByAhSentNo" parameterType="java.lang.String">
update orders_sent set push_qc = 0 where ah_sent_no = #{ahSentNo}
</update>
</mapper>
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论