Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
W
wangxiaolu-sfa-module-job
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
sfa
wangxiaolu-sfa-module-job
Commits
28978247
提交
28978247
authored
3月 20, 2026
作者:
lidongxu
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
尝试添加顺丰查询物流接口
上级
e930640e
隐藏空白字符变更
内嵌
并排
正在显示
3 个修改的文件
包含
231 行增加
和
29 行删除
+231
-29
SentQueryController.java
...ava/com/sfa/job/controller/order/SentQueryController.java
+71
-21
OrderSentInfoResponse.java
...java/com/sfa/job/pojo/response/OrderSentInfoResponse.java
+22
-8
SfUtil.java
src/main/java/com/sfa/job/util/SfUtil.java
+138
-0
没有找到文件。
src/main/java/com/sfa/job/controller/order/SentQueryController.java
浏览文件 @
28978247
...
@@ -11,6 +11,7 @@ import com.sfa.job.pojo.response.OrdersSentDto;
...
@@ -11,6 +11,7 @@ import com.sfa.job.pojo.response.OrdersSentDto;
import
com.sfa.job.service.order.IOrdersSentQueryService
;
import
com.sfa.job.service.order.IOrdersSentQueryService
;
import
com.sfa.job.util.JdtcUtil
;
import
com.sfa.job.util.JdtcUtil
;
import
com.sfa.job.util.KyeUtil
;
import
com.sfa.job.util.KyeUtil
;
import
com.sfa.job.util.SfUtil
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.beans.factory.annotation.Autowired
;
import
org.springframework.util.CollectionUtils
;
import
org.springframework.util.CollectionUtils
;
import
org.springframework.web.bind.annotation.GetMapping
;
import
org.springframework.web.bind.annotation.GetMapping
;
...
@@ -19,10 +20,8 @@ import org.springframework.web.bind.annotation.RestController;
...
@@ -19,10 +20,8 @@ import org.springframework.web.bind.annotation.RestController;
import
java.util.ArrayList
;
import
java.util.ArrayList
;
import
java.util.List
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.concurrent.TimeUnit
;
import
java.util.concurrent.TimeUnit
;
/**
/**
* @author : liqiulin
* @author : liqiulin
* @date : 2025-07-08 13
* @date : 2025-07-08 13
...
@@ -31,31 +30,39 @@ import java.util.concurrent.TimeUnit;
...
@@ -31,31 +30,39 @@ import java.util.concurrent.TimeUnit;
@RestController
@RestController
@RequestMapping
(
"/sent"
)
@RequestMapping
(
"/sent"
)
public
class
SentQueryController
{
public
class
SentQueryController
{
@Autowired
@Autowired
private
JdtcUtil
jdtcUtil
;
private
JdtcUtil
jdtcUtil
;
@Autowired
@Autowired
private
KyeUtil
kyeUtil
;
private
KyeUtil
kyeUtil
;
@Autowired
@Autowired
private
SfUtil
sfUtil
;
@Autowired
private
IOrdersSentQueryService
orderSentQueryService
;
private
IOrdersSentQueryService
orderSentQueryService
;
@Autowired
@Autowired
private
RedisService
redisService
;
private
RedisService
redisService
;
/**
* 接口:GET /sent/query_p?sentNo=xxx
* 限频:每小时同一单号最多查询 5 次
*/
@GetMapping
(
"/query_p"
)
@GetMapping
(
"/query_p"
)
public
Object
query
(
String
sentNo
){
public
Object
query
(
String
sentNo
)
{
// 限制单号每小时只能查询5次
String
rKey
=
RedisKeyJob
.
QINCE_ORDER_SENT_INTERNET_COUNT
+
sentNo
;
String
rKey
=
RedisKeyJob
.
QINCE_ORDER_SENT_INTERNET_COUNT
+
sentNo
;
Object
cacheObject
=
redisService
.
getCacheObject
(
rKey
);
Object
cacheObject
=
redisService
.
getCacheObject
(
rKey
);
if
(
cacheObject
!=
null
&&
Integer
.
parseInt
(
cacheObject
.
toString
())
>=
5
)
{
if
(
cacheObject
!=
null
&&
Integer
.
parseInt
(
cacheObject
.
toString
())
>=
5
)
{
throw
new
ServiceException
(
ECode
.
SENT_NO_QUERY_COUNT_ERROR
);
throw
new
ServiceException
(
ECode
.
SENT_NO_QUERY_COUNT_ERROR
);
}
}
Object
sent
=
queryBySentNode
(
sentNo
);
Object
sent
=
queryBySentNode
(
sentNo
);
redisService
.
setCacheObject
(
rKey
,
cacheObject
==
null
?
1
:
Integer
.
parseInt
(
cacheObject
.
toString
())
+
1
,
1L
,
TimeUnit
.
HOURS
);
redisService
.
setCacheObject
(
rKey
,
cacheObject
==
null
?
1
:
Integer
.
parseInt
(
cacheObject
.
toString
())
+
1
,
1L
,
TimeUnit
.
HOURS
);
return
sent
;
return
sent
;
}
}
private
Object
queryBySentNode
(
String
sentNo
){
private
Object
queryBySentNode
(
String
sentNo
)
{
OrdersSentDto
sent
=
orderSentQueryService
.
getSent
(
sentNo
);
OrdersSentDto
sent
=
orderSentQueryService
.
getSent
(
sentNo
);
List
<
OrderSentInfoResponse
>
sentInfo
=
null
;
List
<
OrderSentInfoResponse
>
sentInfo
;
switch
(
sent
.
getTransport
())
{
switch
(
sent
.
getTransport
())
{
case
"134"
:
case
"134"
:
sentInfo
=
jdTC134
(
sent
);
sentInfo
=
jdTC134
(
sent
);
...
@@ -63,6 +70,10 @@ public class SentQueryController {
...
@@ -63,6 +70,10 @@ public class SentQueryController {
case
"109"
:
case
"109"
:
sentInfo
=
kye109
(
sent
);
sentInfo
=
kye109
(
sent
);
break
;
break
;
case
"136"
:
// 顺丰干配
case
"117"
:
// 顺丰
sentInfo
=
sf
(
sent
);
break
;
default
:
default
:
throw
new
ServiceException
(
ECode
.
SENT_ISNULL_ERROR
);
throw
new
ServiceException
(
ECode
.
SENT_ISNULL_ERROR
);
}
}
...
@@ -70,40 +81,79 @@ public class SentQueryController {
...
@@ -70,40 +81,79 @@ public class SentQueryController {
return
sent
;
return
sent
;
}
}
private
List
<
OrderSentInfoResponse
>
kye109
(
OrdersSentDto
sent
)
{
// =================== 顺丰 ===================
JSONArray
traces
=
kyeUtil
.
getOrderTrace
(
sent
.
getExpressNo
());
if
(
CollectionUtils
.
isEmpty
(
traces
)){
private
List
<
OrderSentInfoResponse
>
sf
(
OrdersSentDto
sent
)
{
// getOrderTrace 返回 routeResps 数组,每个元素含 mailNo + routes
JSONArray
routeResps
=
sfUtil
.
getOrderTrace
(
sent
.
getExpressNo
());
if
(
CollectionUtils
.
isEmpty
(
routeResps
))
{
return
null
;
return
null
;
}
}
JSONArray
exteriorRouteList
=
traces
.
getJSONObject
(
0
).
getJSONArray
(
"exteriorRouteList"
);
// 取第一个运单的 routes 列表
return
pKye109
(
exteriorRouteList
);
JSONObject
routeResp
=
routeResps
.
getJSONObject
(
0
);
JSONArray
routes
=
routeResp
.
getJSONArray
(
"routes"
);
if
(
CollectionUtils
.
isEmpty
(
routes
))
{
return
null
;
}
return
pSf
(
routes
);
}
private
List
<
OrderSentInfoResponse
>
pSf
(
JSONArray
routes
)
{
List
<
OrderSentInfoResponse
>
sentInfoList
=
new
ArrayList
<>();
routes
.
forEach
(
route
->
{
JSONObject
r
=
(
JSONObject
)
route
;
OrderSentInfoResponse
sentInfo
=
new
OrderSentInfoResponse
();
sentInfo
.
setOperateTime
(
r
.
getString
(
"acceptTime"
));
sentInfo
.
setOperateRemark
(
r
.
getString
(
"remark"
));
sentInfo
.
setAcceptAddress
(
r
.
getString
(
"acceptAddress"
));
sentInfo
.
setOpCode
(
r
.
getString
(
"opCode"
));
sentInfo
.
setFirstStatusCode
(
r
.
getString
(
"firstStatusCode"
));
sentInfo
.
setFirstStatusName
(
r
.
getString
(
"firstStatusName"
));
sentInfo
.
setSecondaryStatusCode
(
r
.
getString
(
"secondaryStatusCode"
));
sentInfo
.
setSecondaryStatusName
(
r
.
getString
(
"secondaryStatusName"
));
sentInfoList
.
add
(
sentInfo
);
});
return
sentInfoList
;
}
}
private
List
<
OrderSentInfoResponse
>
jdTC134
(
OrdersSentDto
sent
){
// =================== 京东 ===================
private
List
<
OrderSentInfoResponse
>
jdTC134
(
OrdersSentDto
sent
)
{
JSONArray
traces
=
jdtcUtil
.
getOrderTrace
(
sent
.
getBjSentNo
()
+
"-"
+
sent
.
getBjSentVersion
());
JSONArray
traces
=
jdtcUtil
.
getOrderTrace
(
sent
.
getBjSentNo
()
+
"-"
+
sent
.
getBjSentVersion
());
JSONObject
jb
=
traces
.
getJSONObject
(
0
);
JSONObject
jb
=
traces
.
getJSONObject
(
0
);
return
pJdTC134
(
jb
.
getJSONArray
(
"traceDetails"
));
return
pJdTC134
(
jb
.
getJSONArray
(
"traceDetails"
));
}
}
private
List
<
OrderSentInfoResponse
>
pJdTC134
(
JSONArray
exteriorRouteList
){
private
List
<
OrderSentInfoResponse
>
pJdTC134
(
JSONArray
exteriorRouteList
)
{
List
<
OrderSentInfoResponse
>
sentInfoList
=
new
ArrayList
<>();
List
<
OrderSentInfoResponse
>
sentInfoList
=
new
ArrayList
<>();
exteriorRouteList
.
forEach
(
exteriorRoute
->
{
exteriorRouteList
.
forEach
(
exteriorRoute
->
{
JSONObject
exteriorRouteJson
=
(
JSONObject
)
exteriorRoute
;
JSONObject
r
=
(
JSONObject
)
exteriorRoute
;
OrderSentInfoResponse
sentInfo
=
new
OrderSentInfoResponse
();
OrderSentInfoResponse
sentInfo
=
new
OrderSentInfoResponse
();
sentInfo
.
setOperateTime
(
exteriorRouteJson
.
getString
(
"operateTime"
));
sentInfo
.
setOperateTime
(
r
.
getString
(
"operateTime"
));
sentInfo
.
setOperateRemark
(
exteriorRouteJson
.
getString
(
"operateRemark"
));
sentInfo
.
setOperateRemark
(
r
.
getString
(
"operateRemark"
));
sentInfoList
.
add
(
sentInfo
);
sentInfoList
.
add
(
sentInfo
);
});
});
return
sentInfoList
;
return
sentInfoList
;
}
}
private
List
<
OrderSentInfoResponse
>
pKye109
(
JSONArray
exteriorRouteList
){
// =================== 快鱼 ===================
private
List
<
OrderSentInfoResponse
>
kye109
(
OrdersSentDto
sent
)
{
JSONArray
traces
=
kyeUtil
.
getOrderTrace
(
sent
.
getExpressNo
());
if
(
CollectionUtils
.
isEmpty
(
traces
))
{
return
null
;
}
JSONArray
exteriorRouteList
=
traces
.
getJSONObject
(
0
).
getJSONArray
(
"exteriorRouteList"
);
return
pKye109
(
exteriorRouteList
);
}
private
List
<
OrderSentInfoResponse
>
pKye109
(
JSONArray
exteriorRouteList
)
{
List
<
OrderSentInfoResponse
>
sentInfoList
=
new
ArrayList
<>();
List
<
OrderSentInfoResponse
>
sentInfoList
=
new
ArrayList
<>();
exteriorRouteList
.
forEach
(
exteriorRoute
->
{
exteriorRouteList
.
forEach
(
exteriorRoute
->
{
JSONObject
exteriorRouteJson
=
(
JSONObject
)
exteriorRoute
;
JSONObject
r
=
(
JSONObject
)
exteriorRoute
;
OrderSentInfoResponse
sentInfo
=
new
OrderSentInfoResponse
();
OrderSentInfoResponse
sentInfo
=
new
OrderSentInfoResponse
();
sentInfo
.
setOperateTime
(
exteriorRouteJson
.
getString
(
"uploadDate"
));
sentInfo
.
setOperateTime
(
r
.
getString
(
"uploadDate"
));
sentInfo
.
setOperateRemark
(
exteriorRouteJson
.
getString
(
"routeDescription"
));
sentInfo
.
setOperateRemark
(
r
.
getString
(
"routeDescription"
));
sentInfoList
.
add
(
sentInfo
);
sentInfoList
.
add
(
sentInfo
);
});
});
return
sentInfoList
;
return
sentInfoList
;
...
...
src/main/java/com/sfa/job/pojo/response/OrderSentInfoResponse.java
浏览文件 @
28978247
...
@@ -5,18 +5,32 @@ import lombok.Data;
...
@@ -5,18 +5,32 @@ import lombok.Data;
/**
/**
* @author : liqiulin
* @author : liqiulin
* @date : 2025-07-30 15
* @date : 2025-07-30 15
* @describe :
* @describe :
统一物流路由节点响应 DTO
*/
*/
@Data
@Data
public
class
OrderSentInfoResponse
{
public
class
OrderSentInfoResponse
{
/**
/** 路由节点时间(格式:YYYY-MM-DD HH24:MM:SS) */
* 路由节点描述
private
String
operateTime
;
*/
/** 路由节点描述 */
private
String
operateRemark
;
private
String
operateRemark
;
/**
/** 路由节点发生地点(顺丰) */
* 路由节点时间
private
String
acceptAddress
;
*/
private
String
operateTime
;
/** 路由节点操作码(顺丰) */
private
String
opCode
;
/** 一级状态编码(顺丰) */
private
String
firstStatusCode
;
/** 一级状态名称(顺丰) */
private
String
firstStatusName
;
/** 二级状态编码(顺丰) */
private
String
secondaryStatusCode
;
/** 二级状态名称(顺丰) */
private
String
secondaryStatusName
;
}
}
src/main/java/com/sfa/job/util/SfUtil.java
0 → 100644
浏览文件 @
28978247
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
java.net.URLEncoder
;
import
java.nio.charset.StandardCharsets
;
import
java.security.MessageDigest
;
import
java.util.Base64
;
import
java.util.Collections
;
import
java.util.HashMap
;
import
java.util.LinkedHashMap
;
import
java.util.Map
;
/**
* @author : lidongxu
* @date : 2025-02-05
* @describe : 顺丰物流工具类
*/
@Slf4j
@Component
public
class
SfUtil
{
@Value
(
"${sf.api_url}"
)
private
String
sfApiUrl
;
@Value
(
"${sf.partner_id}"
)
private
String
partnerId
;
@Value
(
"${sf.check_word}"
)
private
String
checkWord
;
/**
* 查询顺丰物流轨迹
*
* @param expressNo 顺丰运单号
* @return routeResps 列表(每个元素含 mailNo + routes)
*/
public
JSONArray
getOrderTrace
(
String
expressNo
)
{
try
{
long
timestamp
=
System
.
currentTimeMillis
();
// 构建 msgData(trackingNumber 必须为 List)
Map
<
String
,
Object
>
bizData
=
new
HashMap
<>();
bizData
.
put
(
"language"
,
"zh-CN"
);
bizData
.
put
(
"trackingType"
,
"1"
);
bizData
.
put
(
"trackingNumber"
,
Collections
.
singletonList
(
expressNo
));
bizData
.
put
(
"methodType"
,
"1"
);
String
msgData
=
JSONObject
.
toJSONString
(
bizData
);
// 构建公共请求参数(保持顺序,便于排查)
Map
<
String
,
String
>
requestBody
=
new
LinkedHashMap
<>();
requestBody
.
put
(
"partnerID"
,
partnerId
);
requestBody
.
put
(
"requestID"
,
String
.
valueOf
(
timestamp
));
requestBody
.
put
(
"serviceCode"
,
"EXP_RECE_SEARCH_ROUTES"
);
requestBody
.
put
(
"timestamp"
,
String
.
valueOf
(
timestamp
));
requestBody
.
put
(
"msgData"
,
msgData
);
requestBody
.
put
(
"msgDigest"
,
generateMsgDigest
(
msgData
,
String
.
valueOf
(
timestamp
)));
// 手动 URL 编码,确保顺丰服务端能正确解析
StringBuilder
bodyBuilder
=
new
StringBuilder
();
for
(
Map
.
Entry
<
String
,
String
>
entry
:
requestBody
.
entrySet
())
{
if
(
bodyBuilder
.
length
()
>
0
)
bodyBuilder
.
append
(
"&"
);
bodyBuilder
.
append
(
URLEncoder
.
encode
(
entry
.
getKey
(),
StandardCharsets
.
UTF_8
.
name
()))
.
append
(
"="
)
.
append
(
URLEncoder
.
encode
(
entry
.
getValue
(),
StandardCharsets
.
UTF_8
.
name
()));
}
log
.
info
(
"顺丰路由查询请求体:{}"
,
bodyBuilder
);
String
response
=
HttpUtil
.
createPost
(
sfApiUrl
)
.
header
(
"Content-Type"
,
"application/x-www-form-urlencoded;charset=UTF-8"
)
.
body
(
bodyBuilder
.
toString
())
.
timeout
(
15000
)
.
execute
()
.
body
();
log
.
info
(
"顺丰路由查询响应:{}"
,
response
);
JSONObject
responseJson
=
JSONObject
.
parseObject
(
response
);
// 外层成功码:A1000
if
(!
"A1000"
.
equals
(
responseJson
.
getString
(
"apiResultCode"
)))
{
log
.
error
(
"顺丰路由查询接口返回异常:{}"
,
response
);
throw
new
ServiceException
(
ECode
.
SF_ORDER_TRACE_API_ERROR
);
}
// apiResultData 是 String,需要再次 parse
String
apiResultDataStr
=
responseJson
.
getString
(
"apiResultData"
);
if
(
apiResultDataStr
==
null
)
{
return
new
JSONArray
();
}
JSONObject
apiResultData
=
JSONObject
.
parseObject
(
apiResultDataStr
);
// 内层业务成功码:S0000
if
(!
Boolean
.
TRUE
.
equals
(
apiResultData
.
getBoolean
(
"success"
)))
{
log
.
error
(
"顺丰路由查询业务返回失败:{}"
,
apiResultDataStr
);
throw
new
ServiceException
(
ECode
.
SF_ORDER_TRACE_API_ERROR
);
}
JSONObject
msgDataResult
=
apiResultData
.
getJSONObject
(
"msgData"
);
if
(
msgDataResult
==
null
)
{
return
new
JSONArray
();
}
JSONArray
routeResps
=
msgDataResult
.
getJSONArray
(
"routeResps"
);
return
routeResps
!=
null
?
routeResps
:
new
JSONArray
();
}
catch
(
ServiceException
e
)
{
throw
e
;
}
catch
(
Exception
e
)
{
log
.
error
(
"查询顺丰物流轨迹异常:{}"
,
e
.
getMessage
(),
e
);
throw
new
ServiceException
(
ECode
.
SF_ORDER_TRACE_QUERY_ERROR
);
}
}
/**
* 简易 MD5 数字签名:Base64( MD5( msgData + timestamp + checkWord ) )
*/
private
String
generateMsgDigest
(
String
msgData
,
String
timestamp
)
{
try
{
String
signStr
=
msgData
+
timestamp
+
checkWord
;
MessageDigest
md
=
MessageDigest
.
getInstance
(
"MD5"
);
byte
[]
digest
=
md
.
digest
(
signStr
.
getBytes
(
StandardCharsets
.
UTF_8
));
return
Base64
.
getEncoder
().
encodeToString
(
digest
);
}
catch
(
Exception
e
)
{
log
.
error
(
"生成顺丰签名失败"
,
e
);
throw
new
ServiceException
(
ECode
.
SF_SIGN_ERROR
);
}
}
}
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论