请求报文
# 请求报文
请求报文是指调用第三方接口需要发送的数据。
调用第三方接口无非是 数据处理、组装报文数据、发送请求、响应结果、数据解析返回 这些步骤。
往往一个通道会有很多接口,每个接口或多或少都存在重复性代码,为不做重复的事在开发过程中总结并封装成了通用功能。
在系统中分为:报文模板、报文处理器、请求对象定义、响应对象定义、通用报文发送器、
# 报文处理器
- 报文处理器写一次就够了,多个接口调用同一个处理器
/**
* @title 快递100通道处理器
*/
public class Kuaidi100Handler extends AbstractChannelHandler {
/**
* 公共参数设置
* @param {Object} Channel channel 通道配置参数
* @param {Object} Kv data 请求参数
*/
@Override
public void commonPara(Channel channel,Kv data) {
data.put("customer", channel.getChannelMerchantCode());
data.put("key", channel.getConfigs().getStr("key"));
}
/**
* 签名处理
* @param {Object} Channel channel 通道配置参数
* @param {Object} String src 待签名数据
*/
@Override
public String sign(Channel channel,String src) {
return DigestUtils.md5Hex(src).toUpperCase();
}
/**
* 发送数据报文
*/
@Override
public <T extends ChannelResponse> T send(AbstractChannelRequest<T> request){
//请求参数
Kv data = request.getData();
//通道配置参数
Channel channel = request.getChannel();
//获取报文数据
String datagram=getSendDatagram(data);
//使用LinkedHashMap防止重排序
Map<String,String> jsonObject = JSONObject.parseObject(datagram,new TypeReference<LinkedHashMap<String,String>>() {}, Feature.OrderedField);
//获取请求地址
String url=getSendUrl(channel,request.getRequestType().getCode(),data);
//发送报文
ChannelResponse channelResponse=DatagramSender.post(url,channel.getCharset(),jsonObject);
//把结果丢给实现类处理
T response = request.getResponseBean();
response.set(channelResponse,channel);
response.resolver();
return response;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# 请求对象定义
继承通道请求抽象类:AbstractChannelRequest
@Data
public class KuaiDiQueryRequest extends AbstractChannelRequest<KuaiDiQueryResponse> {
public KuaiDiQueryRequest() {
//默认快递100通道处理器处理,下面两个参数不能填错
this.setChannelCode(ChannelEnum.Code.KT100);//通道编号
this.setRequestType(ChannelEnum.BT.KT_QUERY);//业务类型
}
@NotNull(message = "手机号不能为空")
@NotEmpty(message = "手机号不能为空")
private String phone;
@NotNull(message = "运单编号不能为空")
@NotEmpty(message = "运单编号不能为空")
private String expressNo;
@NotNull(message = "快递类型不能为空")
@NotEmpty(message = "快递类型不能为空")
private String logisticsType;
//选填
private String form;
@NotNull(message = "到达地区不能为空")
@NotEmpty(message = "到达地区不能为空")
private String to;
public void setPhone(String phone) {
this.phone = phone;
this.setData("phone",phone);
}
public void setExpressNo(String expressNo) {
this.expressNo = expressNo;
this.setData("expressNo",expressNo);
}
public void setLogisticsType(String logisticsType) {
this.logisticsType = logisticsType;
this.setData("logisticsType",logisticsType);
}
public void setTo(String to) {
this.to = to;
this.setData("to",to);
}
@Override
public KuaiDiQueryResponse getResponseBean() {
//返回响应对象
return new KuaiDiQueryResponse();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# 响应对象定义
继承通道响应类:ChannelResponse
以下为ChannelResponse对象中主要数据
- 响应原始数据:responseData
- 响应结果编码:resultCode (这个可以判断是否请求成功)
- 通道配置信息:channel
@Data
public class KuaiDiQueryResponse extends ChannelResponse {
//结果实例
//{"result":false,"returnCode":"500","message":"查询无结果,请隔段时间再查"}
//{"message":"ok","nu":"75474284244821","ischeck":"1","condition":"F00","com":"zhongtong","status":"200","state":"3","data":[{"time":"2021-06-07 17:09:20","ftime":"2021-06-07 17:09:20","context":"【北京市】 您的快递已签收, 签收人在【近邻宝的北京大学】(北京市海淀区颐和园路5号)领取。如有疑问请电联:(16619807456), 投诉电话:(15601138815), 您的快递已经妥投。风里来雨里去, 只为客官您满意。上有老下有小, 赏个好评好不好?【请在评价快递员处帮忙点亮五颗星星哦~】"},{"time":"2021-06-07 15:45:18","ftime":"2021-06-07 15:45:18","context":"【北京市】 快件已被【近邻宝的北京大学】代收,如有问题请电联(15601138815),感谢使用中通快递,期待再次为您服务!"},{"time":"2021-06-07 13:54:17","ftime":"2021-06-07 13:54:17","context":"【北京市】 【北京中关村一部】 的近邻宝16619807456(16619807456) 正在第1次派件, 请保持电话畅通,并耐心等待(95720为中通快递员外呼专属号码,请放心接听)"},{"time":"2021-06-07 13:54:10","ftime":"2021-06-07 13:54:10","context":"【北京市】 快件已经到达 【北京中关村一部】"},{"time":"2021-06-07 08:07:39","ftime":"2021-06-07 08:07:39","context":"【保定市】 快件离开 【京南转运中心】 已发往 【北京中关村一部】"},{"time":"2021-06-07 08:02:10","ftime":"2021-06-07 08:02:10","context":"【保定市】 快件已经到达 【京南转运中心】"},{"time":"2021-06-06 00:47:33","ftime":"2021-06-06 00:47:33","context":"【揭阳市】 快件离开 【潮汕中心】 已发往 【京南转运中心】"},{"time":"2021-06-06 00:45:48","ftime":"2021-06-06 00:45:48","context":"【揭阳市】 快件已经到达 【潮汕中心】"},{"time":"2021-06-05 22:23:31","ftime":"2021-06-05 22:23:31","context":"【揭阳市】 快件离开 【揭阳】 已发往 【京南转运中心】"},{"time":"2021-06-05 18:22:17","ftime":"2021-06-05 18:22:17","context":"【揭阳市】 【揭阳】(0663-8580666、0663-8386858、0663-8554100) 的 黄俊鹏榕城业务(18122668557) 已揽收"}]}
// 200 查询成功 查询成功
// 400 找不到对应公司 提交数据不完整或者账号未充值, 检查提交的格式是否为x-www-form-urlencoded的post格式
// 500 查询无结果,请隔段时间再查 表示查询失败,或没有POST提交
// 501 服务器错误 快递100的服务器出理间隙或临时性异常,有时如果因为不按规范提交请求,比如快递公司参数没有按照文档规定填写等,也会报此错误
// 502 服务器繁忙 快递100的服务器出理间隙或临时性异常,请联系快递100排查原因
// 503 验证签名失败 请检查加密方式,param + key + customer 的顺序进行MD5加密,加密后字符串转大写
// 601 key已过期 没有可用单量,账号需要充值使用
private Integer expressStatus;
private Integer status;
private String remark;
private Date solicitationTime;//揽收时间
private Date dispatchTime;//派件时间
private Date signTime;//签收时间
private String lastInfo;
/**
* @title 解析数据
* @author Yudao 王冬明(woyuwodao@gmail.com)
* @date 2021/6/16 下午2:13
* @version 1.0
* @package kim.lln.common.channel.response
*/
public void resolver(){
if(requestSuccess()){
String responseData = getResponseData();
if(StringUtil.isBlank(responseData)){
setResultCode(RC.RESULT_FAIL.getCode());
setResultDesc("响应数据为空");
}else{
Channel channel = getChannel();
JSONObject responseJson = JSONObject.parseObject(responseData);
String state = responseJson.getString("state");//判断签收状态是否存在
if(state==null){
//查询失败
String resultCode = responseJson.getString("returnCode");//结果编号
String resultDesc = responseJson.getString("message");//结果描述
String remark=channel.getConfigs().getStr("state"+resultCode);
setRemark(remark==null?"查询物流失败":remark);
setResultCode(resultCode);
setResultDesc(resultDesc);
}else{
//查询成功
expressStatus=Integer.valueOf(state);
// 1.正在打包 2.打包完毕 3.已托管物流 4.已完成(已签收、拒签、其他物流状态)
// 0 在途 快件处于运输过程中
// 1 揽收 快件已由快递公司揽收
// 2 疑难 快递100无法解析的状态,或者是需要人工介入的状态, 比方说收件人电话错误。
// 3 签收 正常签收
// 4 退签 货物退回发货人并签收
// 5 派件 货物正在进行派件
// 6 退回 货物正处于返回发货人的途中
// 7 转投 货物转给其他快递公司邮寄
// 10 待清关 货物等待清关
// 11 清关中 货物正在清关流程中
// 12 已清关 货物已完成清关流程
// 13 清关异常 货物在清关过程中出现异常
// 14 拒签 收件人明确拒收
if(expressStatus==2
|| expressStatus==3
|| expressStatus==4
|| expressStatus==14
){
status= 4;//已完成OrderEnum.FlowStatus.FINISH.getCode()
}
String remark=channel.getConfigs().getStr("state"+state);
String resultDesc=responseJson.getString("status")+"|"+state+"|"+responseJson.getString("message");
JSONArray datas = responseJson.getJSONArray("data");
for (int i = 0; i < datas.size(); i++) {
JSONObject data = datas.getJSONObject(i);
if(data!=null){
String context = data.getString("context")+"";
if(context.contains("签收")){
String ftime = data.getString("ftime");
setSignTime(DateUtil.parse(ftime));
}else if(context.contains("派件") || context.contains("派送")){
String ftime = data.getString("ftime");
setDispatchTime(DateUtil.parse(ftime));
}else if(context.contains("揽收") || context.contains("收取快件")){
String ftime = data.getString("ftime");
setSolicitationTime(DateUtil.parse(ftime));
}
this.lastInfo=context;
}
}
setRemark(remark);
setResultDesc(resultDesc);
setResultCode(RC.SUCCESS);//查询成功
}
}
}else{
setResultCode(RC.RESULT_FAIL.getCode());
setResultDesc("请求失败");
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# 报文模板定义
模板标识符: 请求地址:业务类型_URL 定义报文执行步骤:业务类型_STEP
/**无需步骤**/ NONE, /**签名**/ SIGN, /**加密**/ ENCRYPT, /**结果 用于获取数据并发送请求**/ RESULT, /**结果 用于获取数据而不发送请求**/ GET_DATA,
执行步骤:业务类型_数字 步骤从0开始
### 定义业务信息参考ChannelEnum.java BT
### 定义步骤参考ChannelDatagramStep.java SIGN,ENCRYPT,RESULT
-----------------KT_QUERY订单查询-------------------
#tp("KT_QUERY_URL")
https://poll.kuaidi100.com/poll/query.do
#end
### 定义步骤
#tp("KT_QUERY_STEP")
GET_DATA,SIGN,RESULT
#end
### 报文参数-不能有空格
#tp("KT_QUERY_0")
{"com":"#(logisticsType)","phone":"#(phone)","to":"#(to)","resultv2":"0","num":"#(expressNo)","show":"0","order":"desc"}
#end
### 报文签名
#tp("KT_QUERY_1")
#(KT_QUERY0)#(key)#(customer)
#end
### 报文结果
#tp("KT_QUERY_2")
{"param":#(KT_QUERY0),"sign":"#(KT_QUERY1)","customer":"#(customer)"}
#end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 调用通道接口
KuaiDiQueryRequest request=new KuaiDiQueryRequest();
request.setMerchantCode(dto.getMerchantCode());//商户号
request.setChannelCode(ChannelEnum.Code.KT100);//通道编号 可以不设置
request.setRequestType(ChannelEnum.BT.KT_QUERY);//业务类型 每个接口都是一个业务类型
request.setExpressNo(dto.getExpressNo());//运单号
request.setPhone(phone);//手机号
request.setTo(to);//发货地址
request.setLogisticsType(CommonEnum.LogisticsType.getByCode(dto.getLogisticsType()).getKt100());//物流编码
//执行查询
KuaiDiQueryResponse response = DatagramHandlerUtil.handler(request);
//response为查询结果
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15