作者 朱兆平

FFM解析更新体积单位的适配

Cubic Centimetres CC
Cubic Feet CF
Cubic Inches CI
Cubic Metres MC
及重量单位的适配
Kilos K
Pounds L

Signed-off-by: mrz <17966059@qq.com>
... ... @@ -459,7 +459,7 @@ public class FFMInfo implements Serializable {
private FFMInfo WayBillParse(String line,String uld) throws FFMResolveException {
//运单格式适配,这里注意空格字符 与连字符"-"
String pattern = "^(\\d{3}-\\d{8})([A-Z]{3})([A-Z]{3})/(T|P|S|M|D)(\\d+)(K)([0-9\\.]+)(MC)?([0-9\\.]+)?(DG)?([0-9\\.]+)?(T)?([0-9]+)?/.{1,15}";
String pattern = "^(\\d{3}-\\d{8})([A-Z]{3})([A-Z]{3})/(T|P|S|M|D)(\\d+)(K|L)([0-9\\.]+)([MCFI]{2})?([0-9\\.]+)?(DG)?([0-9\\.]+)?(T)?([0-9]+)?/.{1,15}";
// 创建 Pattern 对象
Pattern r = Pattern.compile(pattern);
// 现在创建 matcher 对象
... ... @@ -473,6 +473,7 @@ public class FFMInfo implements Serializable {
String waybillSplit = m.group(4);
String waybillPiece = m.group(5);
String waybillWeight = m.group(7);
String waybillWeightCode = m.group(6);
String waybillVolume = m.group(9);
String waybillDensity = m.group(11);
String waybillTotalPiece = m.group(13);
... ...
... ... @@ -2,9 +2,13 @@
import com.sun.org.apache.bcel.internal.generic.IF_ACMPEQ;
import com.tianbo.analysis.NmmsAdminApplication;
import com.tianbo.analysis.exception.FFMResolveException;
import com.tianbo.analysis.model.FFMInfo;
import com.tianbo.analysis.model.ResultJson;
import com.tianbo.analysis.service.FFMResolve;
import com.tianbo.util.Date.DateUtil;
import lombok.extern.slf4j.Slf4j;
import org.junit.platform.commons.util.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import javax.validation.constraints.NotNull;
... ... @@ -20,6 +24,9 @@ import java.util.regex.Pattern;
@SpringBootTest(classes = NmmsAdminApplication.class)
@Slf4j
public class FFMTest {
@Autowired
FFMResolve ffmResolve;
private String ffm = "ZCZC\n" +
"QD SELKTCR SELKTCR ICNKO5X \n" +
".CGOFD1E 101732\n" +
... ... @@ -66,352 +73,14 @@ public class FFMTest {
"\n" +
"=\n" +
"NNNN";
private String flightNo;
private Date flightDate;
private String order;
private String originStation;
private String destinationStation="UNKONW";
private String planeNo;
private int currentLine = 0;
private static final String KEY_WORD_3 = "FFM,ULD,OSI,COR,OCI,DEG,DIM";
private static final String KEY_WORD_4 = "CONT,LAST";
private List<String> lineList = new ArrayList<>();
@org.junit.jupiter.api.Test
public void send(){
try{
if (StringUtils.isNotBlank(ffm)){
/**
* 检查报文是否包含尾部标识
*/
if(ffm.contains("CONT")|| ffm.contains("LAST")){
BufferedReader reader = new BufferedReader(new StringReader(ffm));
for ( String lineStr = reader.readLine();
lineStr != null;
lineStr = reader.readLine()){
// log.info("已读取行{}-{}",i,lineStr);
lineList.add(lineStr);
}
}else {
throw new FFMResolveException("报文尾部缺少文件结束标识LAST或者CONT");
}
resolve();
}
}catch (IOException ignored){
log.error(ignored.toString());
ignored.printStackTrace();
}catch (FFMResolveException ex){
log.error(ex.toString());
ex.printStackTrace();
}catch (ParseException e){
log.error("航班日期解析错误");
}
}
/**
* 寻找FFM节点并解析航班信息以及航班第一目的站信息
* 找到FFM节点后,开始根据currentLine的值逐步一行一行解析
*/
public void resolve() throws FFMResolveException,ParseException{
if (!lineList.isEmpty()){
int keyword_i = 0;
for (int i = 0; i < lineList.size(); i++) {
//根据行关键字走相应的解析逻辑
String line = lineList.get(i);
log.info("1.开始处理行[{}]-[{}]",i,line);
String keyword = keyword(line);
if (!"NOT_KEYWORD".equals(keyword)){
log.info("2.行[{}]包含关键字,开始处理",i);
if ("FFM".equals(keyword)){
currentLine = i;
//ffm版本
String ver = line.split("/")[1];
log.info("报文版本{}",ver.trim());
//处理航班信息
currentLine++;
flight(lineList.get(currentLine).trim());
/**
* 取第一目的站
* 这里注意,有的错误报文的航班节点后面没带或者忘了带目的站节点,
* 所以这里需要加个校验
*/
currentLine++;
destination(lineList.get(currentLine).trim());
currentLine++;
}else if ("CONT".equals(keyword) || "LAST".equals(keyword) ){
keyword_i++;
}
}
}
if (keyword_i==0){
throw new FFMResolveException("报文尾部缺少文件结束标识LAST或者CONT");
}
resolve_ULD_Waybill();
}
}
/**
* 判断是否有散舱
* 有BUP散舱先处理散舱,没有则进入ULD及ULD货物解析阶段
* 假设默认航班有散舱,将散舱的ULD号默认为 "BUP"
* 设置当前的ULD 为 散舱ULD "BUP"
* 那么散舱下的运单直到解析到真正的板箱号后,uld变成真正的货物板箱
*/
public void resolve_ULD_Waybill() throws FFMResolveException{
log.info("5. 开始解析舱单列表");
String uld = "BUP";
for (int i = currentLine; i < lineList.size(); i++) {
String line = lineList.get(i);
log.info("5.1 开始解析行[{}]-[{}]",i,line);
//行尾部结束标识检查
String keyword = keyword(line);
if ("CONT".equals(keyword) || "LAST".equals(keyword)){
log.info("[END] 已解析到文件结束标识[{}],解析完毕",keyword);
break;
}
/**
* 多航班目的站解析
*/
//校验正则1,取前三位验证是否是机场代码
String pattern_f = "^[A-Z]{3}$|^[A-Z]{3}/\\d{2}[A-Z]{3}\\d{4}";
// 创建 Pattern 对象
Pattern r_f = Pattern.compile(pattern_f);
// 现在创建 matcher 对象
Matcher mF = r_f.matcher(line);
if (mF.find()){
log.info("5-[FLIGHT] 适配到航班其他目的站");
String flightDes = line.substring(0,3);
if (!KEY_WORD_3.contains(flightDes)){
destinationStation = flightDes;
log.info("5-[FLIGHT] 新航班目的站为[{}]",destinationStation);
}
}
if ("UNKONW".equals(destinationStation)){
throw new FFMResolveException("航班目的站节点校验不通过.");
}
if ("ULD".equals(keyword)){
log.info("5-[ULD] 此行为ULD信息行");
//板箱格式适配
String patternULD = "^ULD/([A-Z]{3}\\S{5}\\S{2})";
Pattern rULD = Pattern.compile(patternULD);
// 现在创建 matcher 对象
Matcher mULD = rULD.matcher(line);
if (mULD.find()){
log.info("5-[ULD] 查到分组{}",mULD.groupCount());
// for (int j1 = 1; j1 < mULD.groupCount()+1; j1++) {
// log.info("5-[ULD] 分组{}内容为:{}",j1,mULD.group(j1));
// }
uld= mULD.group(1);
log.info("5-[ULD] 当前ULD已变更为{}",uld);
}else {
throw new FFMResolveException("ULD节点格式错误,无法适配正则(^ULD/([A-Z]{3})(\\S{5})(\\S{2})");
}
}
//运单格式适配
String pattern = "^(\\d{3}-\\d{8})([A-Z]{3})([A-Z]{3})/(T|P|S|M)(\\d+)(K)([0-9\\.]+)(MC)?([0-9\\.]+)?(T)?([0-9]+)?/(\\S+)";
// 创建 Pattern 对象
Pattern r = Pattern.compile(pattern);
// 现在创建 matcher 对象
Matcher m = r.matcher(line);
if(m.find()){
log.info("5-[AWB] 行[{}]属于运单行,开始解析,当前板箱号为:{}",i,uld);
log.info("5-[AWB] 查到分组{}",m.groupCount());
String waybillNo = m.group(1);
String waybillOrigin = m.group(2);
String waybillDes = m.group(3);
String waybillSplit = m.group(4);
String waybillPiece = m.group(5);
String waybillWeight = m.group(7);
String waybillVolume = m.group(9);
String waybillTotalPiece = m.group(11);
String waybillGoodsDes = m.group(12);
// for (int j = 1; j < m.groupCount()+1; j++) {
// log.info("5-[AWB] 分组{}内容为:{}",j,m.group(j));
// }
if(model7Check(waybillNo)){
log.info("5-[AWB] 运单-({})模七校验通过",waybillNo);
}else {
log.error("{}运单模七校验不通过",waybillNo);
throw new FFMResolveException(waybillNo+"运单模七校验不通过");
}
log.info("5-[AWB-INFO] 运单信息:报文序号:{} 所属航班:{}/{} 航班起始站/目的站:{}/{} 所属板箱:{} 运单号:{} 起始站/目的站:{}/{} 分批标识:{} 分批件重:{}/{} 体积:{} 总件数:{} 货物描述:{}",
order,
flightNo,
flightDate,
originStation,
destinationStation,
uld,
waybillNo,
waybillOrigin,
waybillDes,
waybillSplit,
waybillPiece,
waybillWeight,
waybillVolume,
waybillTotalPiece,
waybillGoodsDes);
}
}
}
/**
* 关键字识别
* @param text 每行的内容
* @return 识别为关键字的返回关键字,未被识别为关键字的返回NOT_KEYWORD;
* 返回的关键字 三字码关键字 机场代码关键字 文件结尾关键字 CONT,LAST
*/
public String keyword(@NotNull String text){
//取每行前三位
if (StringUtils.isNotBlank(text) && text.length()>3){
String s_3 = text.substring(0,3);
String s_4 = text.substring(0,4);
if(KEY_WORD_3.contains(s_3)){
return s_3;
}
if(KEY_WORD_4.contains(s_4)){
return s_4;
}
}else if(text.length()==3){
log.info("{}很大可能是机场目的站",text);
}
return "NOT_KEYWORD";
}
/**
* 解析航班第一目的站
*
*/
public void destination(String text) throws FFMResolveException{
log.info("4.开始校验处理航班目的站节点信息");
if (text.length()>=3){
//校验是否空货机
String pattern_nil = "[A-Z]{3}/NIL";
Pattern r_nil = Pattern.compile(pattern_nil);
// 现在创建 matcher 对象
Matcher m_nil = r_nil.matcher(text);
if (m_nil.find()){
log.info("4.1 航班目的站货物为空");
throw new FFMResolveException("航班第一目的站货物为空,解析结束");
}
//校验是否是目的站节点
//校验正则1,取前三位验证是否是机场代码
String pattern = "^[A-Z]{3}$|^[A-Z]{3}/\\d{2}[A-Z]{3}\\d{4}";
// 创建 Pattern 对象
Pattern r = Pattern.compile(pattern);
// 现在创建 matcher 对象
Matcher m = r.matcher(text);
if (m.find()){
log.info("4.1 航班目的站节点校验通过");
destinationStation = text.substring(0,3);
log.info("4.2 航班目的站一为[{}]",destinationStation);
}else {
log.error("4.1[ERROR] !!航班目的站节点校验不通过!!");
throw new FFMResolveException("航班目的站节点校验不通过.");
}
}else {
throw new FFMResolveException("航班目的站节点长度不对.");
}
}
public void flight(String text) throws FFMResolveException, ParseException {
log.info("3.开始处理航班信息");
String[] flight = text.split("/");
if (flight.length>3){
order = flight[0];
log.info("3.1 报文序号为{}",order);
flightNo = flight[1];
log.info("3.2 航班号为[{}]",flightNo);
//航班日期取出来的格式为10SEP或者10SEP1022,带时间的注意
String flightDateStr = flight[2];
log.info("3.3 航班日期信息为[{}]",flightDate);
String flightDateDay = flightDateStr.substring(0,2);
String flightDateMonth = flightDateStr.substring(2,5);
/**
* FFM跨年问题
* FFM报文中的月份为12月,当前服务器时间月份为1月,视为跨年.
* 跨年问题需要设置FFM报文的航班年份为当前年份-1
*/
Calendar cal = Calendar.getInstance();
//当前年份
int yearNow = cal.get(Calendar.YEAR);
int monthNow = cal.get(Calendar.MONTH);
if (monthNow == 1 && "DEC".equals(flightDateMonth)){
log.info("3.3.1 航班日期跨年");
yearNow = yearNow-1;
}
flightDateStr = flightDateStr+yearNow;
flightDate = DateUtil.dateFormatFlight(flightDateStr);
originStation = flight[3];
log.info("3.4 航班起始站为[{}]",originStation);
if (flight.length>4){
planeNo = flight[4];
log.info("3.4 航班飞机号为[{}]",planeNo);
}
}else {
log.error("航班信息节点不正确");
throw new FFMResolveException("航班信息节点不正确");
}
}
/**
* 模七校验
* @param waybillNo
* @return
*/
public boolean model7Check(String waybillNo){
String pattern = "\\d{3}-\\d{8}";
// 创建 Pattern 对象
Pattern r = Pattern.compile(pattern);
// 现在创建 matcher 对象
Matcher m = r.matcher(waybillNo);
if (m.find()){
String num = waybillNo.split("-")[1];
String num_7 = num.substring(0,7);
String num_end = num.substring(7,8);
if (Integer.parseInt(num_7)% 7 == Integer.parseInt(num_end)){
return true;
}else {
log.error("{}模七不通过",waybillNo);
}
}else {
log.error("{}运单格式不正确",waybillNo);
}
return false;
FFMInfo ffmInfo = new FFMInfo();
ffmInfo.text = ffm;
ResultJson result = ffmResolve.resolve(ffmInfo);
return;
}
}
... ...