【快递100功能开发】系统模块集成快递订阅推送服务(二)——定时扫描快递单号,批量订阅
配置 account.properties
resources
目录下新建account.properties
文件
该文件,是快递100接口扫描账号信息的文件,具体代码可以查看 PropertiesReader
。
account.properties
文件内容如下:
#快递100的基础账号信息,可以在这里获取
# https://poll.kuaidi100.com/manager/page/myinfo/enterprise
key = RI9X9Ah9cXAWq69X9A066
customer = 9FAF61DDB3F5DC91DDBE61DDB80C11DDBCE
secret = eec334c30bc349c3c9b60c311c3431c36
userid = 6136983402942534171f54d56386799389
#回调地址(写上真实的回调地址)
url = http://localhost:8080/express/data/callback
#开通行政区域解析功能以及物流轨迹增加物流状态值
resultv2 = 1
#是否记录快递100接口返回结果,建议记录日志或者入库,方便后期有问题双方排查(true:启用 false: 关闭 )
log.return.record = false
创建定时任务
SysExpressController.java
/**
* @author layman
* @description: 快递100接口
* @date 2021/11/22
*/
@RestController
@RequestMapping("/express/data")
public class SysExpressController{
@Autowired
private ISysExpressService expressService;
/**
* 该接口为自动订阅接口
* 每隔15分钟扫描sys_express表中,距今一个月,状态为未签收,订阅状态为未成功的快递单
* 然后批量向快递100注册推送服务
*/
@Scheduled(cron = "0 0/15 * * * ?")
public void autoSubscribe() throws Exception {
System.out.println("每隔15分钟执行一次:" + new Date());
// 获取距今一个月内,所有未签收,且未订阅推送的快递单
List<SysExpress> allExpress = expressService.selectNotCheckAndNotSign();
if(allExpress != null && allExpress.size() > 0){
// 订阅快递信息推送服务
expressService.subscribe(allExpress);
}
}
/**
* 快递100回调接口
* @param request
*/
@PostMapping("/callback")
public SubscribeResp callBack(HttpServletRequest request) throws Exception {
return expressService.handleCallBack(request);
}
SysExpressServiceImpl 实现类
/**
* @author layman
* @description: 快递100接口服务类
* @date 2021/11/23
*/
@Service
public class SysExpressServiceImpl implements ISysExpressService {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private SysExpressInfoMapper expressInfoMapper;
@Autowired
private SysExpressMapper expressMapper;
@Autowired
private SysExpressDataMapper expressDataMapper;
// 授权key
private String key = PropertiesReader.get("key");
private String customer = PropertiesReader.get("customer");
// 接口回调地址
private String callbackUrl = PropertiesReader.get("url");
// 开启行政区域解析
private String resultv2 = PropertiesReader.get("resultv2");
/**
* 订阅快递信息推送服务
* @param allExpress 快递单集合
*/
@Override
@Transactional
public void subscribe(List<SysExpress> allExpress) throws Exception {
//附加参数信息(回调地址,签名,是否开启行政区域解析等)
SubscribeParameters parameter = getSubscribeParameters();
// 设置参数(设置key)
SubscribeParam param = getSubscribeParam(parameter);
// 设置请求参数
SubscribeReq request = getSubscribeReq();
// 设置请求接口
IBaseClient subscribe = new Subscribe();
for (SysExpress express : allExpress) {
param.setCompany(express.getCompanyCode());
param.setNumber(express.getExpressNum());
request.setParam(ChangeToJson(param));
// 订阅推送服务
HttpResult httpResult = subscribe.execute(request);
SubscribeResp response = new Gson().fromJson(httpResult.getBody(), SubscribeResp.class);
String returnCode = response.getReturnCode();
if("501".equals(returnCode)){
// 重复订阅的请求,不改变订阅成功的状态,只保存报文(以防误操作)
express.setSubStatus(200L);
}else{
express.setSubStatus(Long.valueOf(returnCode));
}
express.setSubParam(request.getParam());
express.setSubResult(httpResult.getBody());
expressMapper.updateSysExpress(express);
}
}
/**
* 处理快递100推送请求并响应
*
* @param request 快递100的推送请求
* @return 响应
*/
@Override
@Transactional
public SubscribeResp handleCallBack(HttpServletRequest request) throws Exception {
// 获取参数
String param = request.getParameter("param");
String sign = request.getParameter("sign");
// 解析报文
SubscribePushParamResp backResp = new Gson().fromJson(param, SubscribePushParamResp.class);
handleResponse(param,backResp);
return getSubscribeResp();
}
/**
* 业务处理
*/
@Transactional
public void handleResponse(String param,SubscribePushParamResp backResp) throws Exception {
// 每次推送的报文,都要入库,方便出问题后排查
String expressNum = backResp.getLastResult().getNu();
SysExpressData expressData = new SysExpressData();
expressData.setExpressNum(expressNum);
expressData.setReceiveText(param);
expressData.setStatus("0");
expressDataMapper.insertSysExpressData(expressData);
// 业务处理
SysExpress express = expressMapper.selectSysExpressByNum(expressNum);
//当message为“3天查询无记录”或“60天无变化时”status= abort,处理逻辑是重新订阅推送服务
if("abort".equals(backResp.getStatus().trim())){
express.setRemark("status= abort,重新订阅推送服务");
List<SysExpress> expressList = new ArrayList<>();
expressList.add(express);
subscribe(expressList);
}
// shutdown说明快递被签收,推送服务中止
if("shutdown".equals(backResp.getStatus().trim())){
express.setStatus(3L);
expressMapper.updateSysExpress(express);
}
// 获取data数据集,解析后存入数据库
List<SubscribePushData> dataList = backResp.getLastResult().getData();
List<SysExpressInfo> infoList = new ArrayList<>();
for (SubscribePushData data : dataList) {
SysExpressInfo info = new SysExpressInfo();
info.setCompanyCode(backResp.getLastResult().getCom());
info.setContext(data.getContext());
info.setExpressNum(expressNum);
info.setStatus(data.getStatus());
info.setTime(DateUtils.parseDate(data.getFtime()));
infoList.add(info);
}
if(infoList.size() > 0){
// 入库操作前,先删除sys_express_info表中的数据,只保留最新数据。
expressInfoMapper.deleteSysExpressInfoByNum(expressNum);
expressInfoMapper.insertSysExpressBatch(infoList);
}
}
public SubscribeParameters getSubscribeParameters(){
SubscribeParameters parameter = new SubscribeParameters();
// 设置回调地址,在account.properties中配置
parameter.setCallbackurl(callbackUrl);
//开通行政区域解析功能以及物流轨迹增加物流状态值
parameter.setResultv2(resultv2);
return parameter;
}
public SubscribeParam getSubscribeParam(SubscribeParameters parameter){
SubscribeParam param = new SubscribeParam();
param.setKey(key);
param.setParameters(parameter);
return param;
}
public SubscribeReq getSubscribeReq(){
SubscribeReq request = new SubscribeReq();
request.setSchema(ApiInfoConstant.SUBSCRIBE_SCHEMA);
return request;
}
public SubscribeResp getSubscribeResp(){
SubscribeResp response = new SubscribeResp();
response.setResult(Boolean.TRUE);
response.setReturnCode("200");
response.setMessage("成功");
return response;
}
public String ChangeToJson(Object src) {
return new Gson().toJson(src);
}
/**
* 查询所有未签收且未订阅成功的快递单号
*
* @return 快递单集合
*/
@Override
public List<SysExpress> selectNotCheckAndNotSign() {
return expressMapper.selectNotCheckAndNotSign();
}
}
expressMapper.selectNotCheckAndNotSign的SQL如下:
<sql id="selectSysExpressVo">
select express_id, express_num, company_code, status, sub_status, sub_param, sub_result, create_time, sub_time, remark from sys_express
</sql>
<select id="selectNotCheckAndNotSign" parameterType="SysExpress" resultMap="SysExpressResult">
<include refid="selectSysExpressVo"/> where status = 400
and sub_status != 200
and create_time >= date_add(now(),interval -30 DAY)
</select>
代码逻辑
- 订阅服务
- 每隔15分钟,从sys_express扫描近一个月内,状态为未签收且订阅状态为未订阅的快递单集合
- 遍历该集合,向快递100批量订阅推送服务
- 将订阅推送的请求参数,返回参数,订阅状态,订阅时间更新到数据库,方便日后排查问题
- 回调服务
- 开放8080端口,接收回调请求
- 快递100调用回调请求,传回的报文,首先进行入库操作(回调报文,接收时间)
- 解析报文,如果status=abort,则重新订阅推送,如果status=shutdown,则说明快递被签收
- 如果快递被签收,则更新sys_express表中的状态,下次不再扫描。
- 获取data数据集,解析后存入集合
- 如果集合中物流轨迹信息存在,则先删除轨迹表sys_express_info中对应快递单号的数据。
- 然后遍历集合,进行入库操作(保证数据库中的数据永远为最新数据)