zhanghua
2025-06-11 2ca169c85f61256fb5185c078dba1bfef2be5066
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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
package cn.lili.modules.logistics.plugin.shunfeng;
 
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.lili.common.enums.ResultCode;
import cn.lili.common.exception.ServiceException;
import cn.lili.common.utils.SpringContextUtil;
import cn.lili.modules.logistics.LogisticsPlugin;
import cn.lili.modules.logistics.entity.dto.LabelOrderDTO;
import cn.lili.modules.logistics.entity.enums.LogisticsEnum;
import cn.lili.modules.order.order.entity.dos.Order;
import cn.lili.modules.order.order.entity.dos.OrderItem;
import cn.lili.modules.order.order.entity.vo.OrderDetailVO;
import cn.lili.modules.store.entity.dto.StoreDeliverGoodsAddressDTO;
import cn.lili.modules.store.service.StoreDetailService;
import cn.lili.modules.system.entity.dos.Logistics;
import cn.lili.modules.system.entity.dto.LogisticsSetting;
import cn.lili.modules.system.entity.vo.Traces;
import com.sf.csim.express.service.CallExpressServiceTools;
import com.sf.csim.express.service.HttpClientUtil;
import com.sf.csim.express.service.IServiceCodeStandard;
import com.sf.csim.express.service.code.ExpressServiceCodeEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
 
import java.io.UnsupportedEncodingException;
import java.util.*;
 
/**
 * 顺丰插件
 * @author admin
 */
@Slf4j
public class ShunfengPlugin implements LogisticsPlugin {
 
    /**
     * ExpressServiceCodeEnum 对应速运类-快递APIs
     * POSTServiceCodeEnum 对应速运类-驿站APIs
     * YJTServiceCodeEnum 对应解决方案-医寄通APIs
     * EPSServiceCodeEnum 对应解决方案-快递管家APIs
     * 详情见code目录下枚举类,客户可自行修改引用的该类
     **/
    private LogisticsSetting logisticsSetting;
 
    public ShunfengPlugin(){}
 
    public ShunfengPlugin(LogisticsSetting logisticsSetting) {
        this.logisticsSetting = logisticsSetting;
    }
 
    @Override
    public LogisticsEnum pluginName() {
        return LogisticsEnum.SHUNFENG;
    }
 
    /**
     * 文档地址:https://open.sf-express.com/Api/ApiDetails?level3=393&interName=%E4%B8%8B%E8%AE%A2%E5%8D%95%E6%8E%A5%E5%8F%A3-EXP_RECE_CREATE_ORDER
     *
     * @param orderDetailVO
     */
    public String createOrder(OrderDetailVO orderDetailVO) {
        StoreDetailService storeService = SpringContextUtil.getBean(StoreDetailService.class);
        StoreDeliverGoodsAddressDTO storeDeliverGoodsAddressDTO = storeService.getStoreDeliverGoodsAddressDto(orderDetailVO.getOrder().getStoreId());
        if(storeDeliverGoodsAddressDTO == null){
            throw new ServiceException(ResultCode.STORE_DELIVER_ADDRESS_EXIST);
        }
        try {
            Order order = orderDetailVO.getOrder();
            Map<String, Object> msgDataMap = new HashMap<String, Object>();
            msgDataMap.put("language", "zh-CN");
            msgDataMap.put("orderId", order.getSn());
            //托寄物信息
            List<Map<String, Object>> cargoDetails = new ArrayList<>();
            for (OrderItem orderItem : orderDetailVO.getOrderItems()) {
                Map<String, Object> map = new HashMap<>();
                map.put("name", orderItem.getGoodsName());
                cargoDetails.add(map);
            }
            msgDataMap.put("cargoDetails", cargoDetails);
 
            //收寄双方信息
            List<Map<String, Object>> contactInfoList = new ArrayList<>();
            Map<String, Object> storeContactInfoMap = new HashMap<>();
            storeContactInfoMap.put("contactType", 1);
            storeContactInfoMap.put("contact", storeDeliverGoodsAddressDTO.getSalesConsignorName());
            storeContactInfoMap.put("mobile", storeDeliverGoodsAddressDTO.getSalesConsignorMobile());
            //国家或地区2位代码 参照附录
            storeContactInfoMap.put("country", "CN");
            //详细地址,若有四级行政区划,如镇/街道等信息可拼接至此字段,格式样例:镇/街道+详细地址。若province/city 字段的值不传,此字段必须包含省市信息,避免影响原寄地代码识别,如:广东省深圳市福田区新洲十一街万基商务大厦10楼;此字段地址必须详细,否则会影响目的地中转识别;
            storeContactInfoMap.put("address", storeDeliverGoodsAddressDTO.getSalesConsignorAddressPath() + storeDeliverGoodsAddressDTO.getSalesConsignorDetail());
            contactInfoList.add(storeContactInfoMap);
 
            Map<String, Object> memberContactInfoMap = new HashMap<>();
            memberContactInfoMap.put("contactType", 2);
            memberContactInfoMap.put("contact", order.getConsigneeName());
            memberContactInfoMap.put("mobile", order.getConsigneeMobile());
            //国家或地区2位代码 参照附录
            memberContactInfoMap.put("country", "CN");
            //详细地址,若有四级行政区划,如镇/街道等信息可拼接至此字段,格式样例:镇/街道+详细地址。若province/city 字段的值不传,此字段必须包含省市信息,避免影响原寄地代码识别,如:广东省深圳市福田区新洲十一街万基商务大厦10楼;此字段地址必须详细,否则会影响目的地中转识别;
            memberContactInfoMap.put("address", order.getConsigneeAddressPath() + order.getConsigneeDetail());
            contactInfoList.add(memberContactInfoMap);
            msgDataMap.put("contactInfoList", contactInfoList);
 
            msgDataMap.put("expressTypeId", 1);
            msgDataMap.put("isReturnRoutelabel", 1);
 
 
            String result = sendPost(ExpressServiceCodeEnum.EXP_RECE_CREATE_ORDER, msgDataMap);
            JSONObject resultData = JSONUtil.parseObj(result).getJSONObject("apiResultData");
            if(Boolean.TRUE.toString().equals(resultData.get("success").toString())){
                return resultData.getJSONObject("msgData").getJSONArray("waybillNoInfoList").getJSONObject(0).get("waybillNo").toString();
            }
            throw new ServiceException(resultData.get("errorMsg").toString());
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }
 
    /**
     * 文档地址:https://open.sf-express.com/Api/ApiDetails?apiServiceCode=EXP_RECE_SEARCH_ROUTES&category=1&apiClassify=1&interName=%E8%B7%AF%E7%94%B1%E6%9F%A5%E8%AF%A2%E6%8E%A5%E5%8F%A3-EXP_RECE_SEARCH_ROUTES
     *
     * @param logistics 物流公司
     * @param expNo
     * @param phone
     * @return
     */
    @Override
    public Traces pollQuery(Logistics logistics, String expNo, String phone) {
        try {
            Map<String, Object> msgDataMap = new HashMap<String, Object>();
            msgDataMap.put("language", "zh-CN");
            /**
             * 查询号类别:
             * 1:根据顺丰运单号查询,trackingNumber将被当作顺丰运单号处理
             * 2:根据客户订单号查询,trackingNumber将被当作客户订单号处理
             */
            msgDataMap.put("trackingType", 1);
            List<String> trackingNumber = new ArrayList<>();
            trackingNumber.add(expNo);
            msgDataMap.put("trackingNumber", trackingNumber);
            JSONObject result = JSONUtil.parseObj(sendPost(ExpressServiceCodeEnum.EXP_RECE_SEARCH_ROUTES, msgDataMap));
            JSONObject resultData = result.getJSONObject("apiResultData");
            if(Boolean.TRUE.toString().equals(resultData.get("success").toString())){
                JSONArray routesJson = resultData.getJSONObject("msgData").getJSONArray("routeResps").getJSONObject(0).getJSONArray("routes");
                List<Map> routes = routesJson.toList(Map.class);
                return new Traces(logistics.getName(),expNo,routes);
            }
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
        return null;
    }
 
    @Override
    public Traces pollMapTrack(Logistics logistics, String expNo, String phone, String from, String to) {
        return null;
    }
 
    /**
     * 文档地址:http://open.sf-express.com/Api/ApiDetails?level3=317&interName=%E4%BA%91%E6%89%93%E5%8D%B0%E9%9D%A2%E5%8D%952.0%E6%8E%A5%E5%8F%A3-COM_RECE_CLOUD_PRINT_WAYBILLS
     *
     * @param labelOrderDTO 电子面单DTO
     * @return
     */
    @Override
    public Map<String,Object> labelOrder(LabelOrderDTO labelOrderDTO) {
        try {
            Map<String, Object> msgDataMap = new HashMap<>();
            //模板编码
            //关联云打印接口后,点击查看,可在接口详情页获取模板编码,类似:fm_76130_standard_{partnerId}
            msgDataMap.put("templateCode", logisticsSetting.getTemplateCode());
            //业务数据
            Map<String, Object> documents = new HashMap<>();
            documents.put("masterWaybillNo", labelOrderDTO.getOrder().getLogisticsNo());
            msgDataMap.put("documents",documents);
            msgDataMap.put("sync",true);
            /**
             * 版本号,传固定值:2.0
             */
            msgDataMap.put("version", "2.0");
            JSONObject result = JSONUtil.parseObj(sendPost(ExpressServiceCodeEnum.COM_RECE_CLOUD_PRINT_WAYBILLS, msgDataMap));
            JSONObject resultData = result.getJSONObject("apiResultData");
            if(Boolean.TRUE.toString().equals(resultData.get("success").toString())){
                return resultData.getJSONObject("obj").getJSONArray("files").toList(Map.class).get(0);
            }
            throw new ServiceException(resultData.getJSONArray("errorMessage").get(0).toString());
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }
 
    /**
     * 文档地址:https://open.sf-express.com/Api/ApiDetails?level3=409&interName=%E9%A2%84%E8%AE%A1%E6%B4%BE%E9%80%81%E6%97%B6%E9%97%B4%E6%8E%A5%E5%8F%A3-EXP_RECE_SEARCH_PROMITM
     *
     * @param searchNo
     * @param checkNos
     */
    public String searchPromitm(String searchNo, String checkNos) {
        try {
            Map<String, Object> msgDataMap = new HashMap<String, Object>();
            //顺丰运单号
            msgDataMap.put("searchNo", searchNo);
            //校验类型 1,电话号码校验 2,月结卡号校验
            msgDataMap.put("checkType", 1);
            //校验值 当校验类型为1时传电话号码 当校验类型为2时传月结卡号
            List<String> mobileList= new ArrayList<>();
            mobileList.add(checkNos);
            msgDataMap.put("checkNos", mobileList);
            JSONObject result = JSONUtil.parseObj(sendPost(ExpressServiceCodeEnum.EXP_RECE_SEARCH_PROMITM, msgDataMap));
            JSONObject resultData = result.getJSONObject("apiResultData");
            if(Boolean.TRUE.toString().equals(resultData.get("success").toString())){
                return  resultData.getJSONObject("msgData").get("promiseTm").toString();
            }
            throw new ServiceException(resultData.get("errorMsg").toString());
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        }
    }
 
    private String sendPost(IServiceCodeStandard standardService, Map<String, Object> msgDataMap) throws UnsupportedEncodingException {
        CallExpressServiceTools tools = CallExpressServiceTools.getInstance();
        Map<String, String> params = new HashMap<String, String>();
        String timeStamp = String.valueOf(System.currentTimeMillis());
        // 顾客编码
        params.put("partnerID", logisticsSetting.getClientCode());
        params.put("requestID", UUID.randomUUID().toString().replace("-", ""));
        // 接口服务码
        params.put("serviceCode", standardService.getCode());
        params.put("timestamp", timeStamp);
        params.put("msgData", JSONUtil.toJsonStr(msgDataMap));
 
        params.put("msgDigest", tools.getMsgDigest(params.get("msgData"), timeStamp, logisticsSetting.getCheckWord()));
        String result = HttpClientUtil.post(logisticsSetting.getCallUrl(), params);
 
        log.info("===调用地址 ===" + logisticsSetting.getCallUrl());
        log.info("===顾客编码 ===" + logisticsSetting.getClientCode());
        log.info("===返回结果:" + result);
 
        return result;
    }
}