weijianghai 2 years ago
commit
c4a180bd3b

+ 526 - 0
.gitignore

@@ -0,0 +1,526 @@
+HELP.md
+target/
+!.mvn/wrapper/maven-wrapper.jar
+!**/src/main/**
+!**/src/test/**
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+build/
+
+### VS Code ###
+.vscode/
+### Java template
+# Compiled class file
+*.class
+
+# Log file
+*.log
+
+# BlueJ files
+*.ctxt
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.nar
+*.ear
+*.zip
+*.tar.gz
+*.rar
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+### Maven template
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+dependency-reduced-pom.xml
+buildNumber.properties
+.mvn/timing.properties
+# https://github.com/takari/maven-wrapper#usage-without-binary-jar
+.mvn/wrapper/maven-wrapper.jar
+
+### VisualStudioCode template
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+# Local History for Visual Studio Code
+.history/
+
+### JetBrains template
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn.  Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### Example user template template
+### Example user template
+
+# IntelliJ project files
+out
+gen
+### VisualStudio template
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+a/
+distinct/
+download/
+.mvn/
+mvnw
+mvnw.cmd

+ 4 - 0
README.md

@@ -0,0 +1,4 @@
+# 工程简介
+
+# 延伸阅读
+

+ 19 - 0
build-dir/gpload/pm_nr_gpload.sh

@@ -0,0 +1,19 @@
+#!/bin/bash
+
+source /usr/local/greenplum-db-clients/greenplum_loaders_path.sh
+filename=$1
+
+# 修改gpload配置文件
+sed -i 's/\/data1\/pm_5g\/distinct\/pm_5g_hour_[0-9]\{10\}.csv/\/data1\/pm_5g\/distinct\/'${filename}'/' /data1/pm_5g/gpload/pm_nr_gpload.yml
+
+# 由于gpload需要输入密码,这里需要使用expect执行
+password=Richr00t#
+expect -c "
+set timeout 300
+spawn gpload -f /data1/pm_5g/gpload/pm_nr_gpload.yml
+expect {
+\"connecting (yes/no)?\" { send \"yes\n\";exp_continue }
+\"Password:\" { send \"${password}\n\"; exp_continue}
+timeout { puts \"超时\" exit 2}
+}
+"

+ 27 - 0
build-dir/gpload/pm_nr_gpload.yml

@@ -0,0 +1,27 @@
+VERSION: 1.0.0.1
+DATABASE: sqmmt
+USER: gpadmin
+HOST: 192.168.70.109
+PORT: 5432
+GPLOAD:
+  PRELOAD:
+    - TRUNCATE: false
+    - REUSE_TABLES: true
+    - STAGING_TABLE: extrnal_table_pm_5g_gpload
+    - FAST_MATCH: true
+  INPUT:
+    - SOURCE:
+        LOCAL_HOSTNAME:
+          - 192.168.70.130
+        PORT: 54321
+        FILE:
+          - /data1/pm_5g/distinct/pm_5g_hour_2022052409.csv
+    - FORMAT: csv
+    - DELIMITER: ','
+    - HEADER: true
+    - ENCODING: utf-8
+    - ERROR_LIMIT: 20000000
+    - LOG_ERRORS: true
+  OUTPUT:
+    - TABLE: tsfx.dw_sa_omc_ci_h
+    - MODE: insert

+ 1 - 0
build-dir/run.sh

@@ -0,0 +1 @@
+nohup java -jar /data1/pm_5g/pmInterface5g.jar > /data1/pm_5g/output.out 2>&1 &

+ 6 - 0
build-dir/stop.sh

@@ -0,0 +1,6 @@
+#!/bin/bash
+for i in `ps -ef|grep pmInterface5g.jar |grep -v grep|awk '{print $2}'`
+do 
+kill -9 $i;
+done
+

+ 14 - 0
doc/192.168.70.109.md

@@ -0,0 +1,14 @@
+# 192.168.70.109 GP数据库master节点
+
+| 账号 | 密码     |
+| ------ | ---------- |
+| do   | Richr00t |
+| root | Richr!@# |
+
+## GP数据库
+
+jdbc:postgresql://192.168.70.109:5432/sqmmt
+gpadmin
+Richr00t#
+sqmdb
+sqmdb_1QAZ

BIN
doc/5Gpm表头.xlsx


BIN
doc/5Gpm解析字段需求.xlsx


BIN
doc/5g数据库字段.xlsx


+ 12 - 0
doc/readme.md

@@ -0,0 +1,12 @@
+# pm数据接口
+
+当前lte pm数据入库程序部署位置 133.96.94.108 /data1/pm
+
+5Gpm小时数据导出:10.17.180.55:/data/out2/pm_5g_hour/pm_5g_hour_2022072910.csv
+
+数据源 10.17.180.55 /data/out2/pm_4g_hour
+pm_4g_hour_2022053005.csv
+
+133.96.94.19
+esbftp Esb2019ftp!
+nokia   Nokia*123

+ 68 - 0
pom.xml

@@ -0,0 +1,68 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <groupId>com.nokia</groupId>
+        <artifactId>hb_springboot_parent</artifactId>
+        <version>1.0</version>
+        <relativePath/>
+    </parent>
+
+    <modelVersion>4.0.0</modelVersion>
+    <artifactId>pm_interface_5g</artifactId>
+    <version>0.0.1-SNAPSHOT</version>
+    <name>pm_interface_5g</name>
+    <description>pm_interface_5g</description>
+
+    <properties>
+        <java.version>8</java.version>
+        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+        <skipTests>true</skipTests>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-web</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-test</artifactId>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.junit.vintage</groupId>
+                    <artifactId>junit-vintage-engine</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-csv</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>com.jcraft</groupId>
+            <artifactId>jsch</artifactId>
+            <version>0.1.55</version>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <finalName>pmInterface5g</finalName>
+        <plugins>
+            <plugin>
+                <groupId>org.springframework.boot</groupId>
+                <artifactId>spring-boot-maven-plugin</artifactId>
+                <configuration>
+                    <includeSystemScope>true</includeSystemScope>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+</project>

+ 86 - 0
src/main/java/com/nokia/common/gpload/GploadUtil.java

@@ -0,0 +1,86 @@
+package com.nokia.common.gpload;
+
+import com.nokia.common.gpload.entity.GploadResult;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+
+@Slf4j
+public class GploadUtil {
+
+    private static Process process = null;
+    private static BufferedReader reader = null;
+
+    public static GploadResult gpload(String gploadCommand) {
+        GploadResult result = new GploadResult();
+        try {
+            Process process = Runtime.getRuntime().exec(gploadCommand);
+            int exitCode = process.waitFor();
+            result.setTaskStatus(exitCode == 0);
+            reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
+            List<String> lines = new ArrayList<>();
+            String line;
+            while ((line = reader.readLine()) != null) {
+                lines.add(line);
+            }
+            for (int i = 0; i < lines.size(); i++) {
+                if (lines.get(i).contains("|INFO|rows Inserted")) {
+                    // 找到第一行 对应的是插入数据的数量
+                    // s4可以判断gpload是否成功
+                    String s4 = lines.get(i + 3);
+                    if (s4.endsWith("succeeded")) {
+                        // 插入数量
+                        String s1 = lines.get(i);
+                        s1 = s1.substring(s1.indexOf('=') + 2);
+                        // 更新数量
+                        String s2 = lines.get(i + 1);
+                        s2 = s2.substring(s2.indexOf('=') + 2);
+                        // 错误数量
+                        String s3 = lines.get(i + 2);
+                        s3 = s3.substring(s3.indexOf('=') + 2);
+                        result.setInsertedCount(Integer.parseInt(s1));
+                        result.setUpdatedCount(Integer.parseInt(s2));
+                        result.setErrorCount(Integer.parseInt(s3));
+                    } else {
+                        StringBuilder sb = new StringBuilder();
+                        for (int j = i - 1; j > 0; j--) {
+                            sb.insert(0, lines.get(i));
+                            if (lines.get(i).contains("|ERROR|ERROR:")) {
+                                sb.insert(0, lines.get(i));
+                                break;
+                            }
+                        }
+                        result.setMessage(sb.toString());
+                    }
+                }
+            }
+        } catch (IOException | InterruptedException e) {
+            e.printStackTrace();
+            result.setTaskStatus(false);
+            result.setMessage(e.getMessage());
+        } finally {
+            log.debug("gpload的结果为: {}", result);
+            destroy();
+        }
+        return result;
+    }
+
+    private static void destroy() {
+        if (reader != null) {
+            try {
+                reader.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+            reader = null;
+        }
+        if (process != null) {
+            process.destroy();
+            process = null;
+        }
+    }
+}

+ 12 - 0
src/main/java/com/nokia/common/gpload/entity/GploadResult.java

@@ -0,0 +1,12 @@
+package com.nokia.common.gpload.entity;
+
+import lombok.Data;
+
+@Data
+public class GploadResult {
+    private Boolean taskStatus;
+    private Integer insertedCount;
+    private Integer updatedCount;
+    private Integer errorCount;
+    private String message;
+}

+ 353 - 0
src/main/java/com/nokia/common/ssh/SSHUtil.java

@@ -0,0 +1,353 @@
+package com.nokia.common.ssh;
+
+import com.jcraft.jsch.*;
+import com.nokia.common.ssh.entity.SSHServer;
+import com.nokia.common.ssh.entity.UserInfoImpl;
+import com.nokia.common.ssh.exception.SSHUtilException;
+import com.nokia.common.ssh.exception.ScpAckErrorException;
+import lombok.Getter;
+import lombok.Setter;
+import lombok.extern.slf4j.Slf4j;
+
+import java.io.*;
+import java.nio.file.NoSuchFileException;
+import java.util.*;
+
+/**
+ * 使用jsch库实现的ssh的工具类
+ * <p>
+ * todo: scpTo和scpFrom 在本机和targetServer默认编码不一致的时候,文件名中的中文会乱码,但是不会影响到文件内容,
+ */
+
+@Slf4j
+public class SSHUtil {
+
+    @Getter
+    @Setter
+    private SSHServer targetServer = new SSHServer();
+    private Session session = null;
+    private Channel channel = null;
+    private JSch jSch = null;
+    private FileInputStream fileInputStream = null;
+    private FileOutputStream fileOutputStream = null;
+    private OutputStream outputStream = null;
+    private InputStream inputStream = null;
+    private ChannelSftp channelSftp = null;
+
+    public SSHUtil() {
+    }
+
+    public SSHUtil(String host, String user, String password) {
+        targetServer = new SSHServer(host, 22, user, password);
+    }
+
+    public SSHUtil(String host, Integer port, String user, String password) {
+        targetServer = new SSHServer(host, port, user, password);
+    }
+
+    /**
+     * 获取文件列表
+     */
+    public List<String> ls(String path) throws JSchException, SftpException {
+        session = getConnectSession();
+        channelSftp = (ChannelSftp) session.openChannel("sftp");
+        channelSftp.connect();
+        List<String> fileNameList = new ArrayList<>();
+        Vector fileList = channelSftp.ls(path);
+        for (Object o : fileList) {
+            String fileName = ((ChannelSftp.LsEntry) o).getFilename();
+            if (".".equals(fileName) || "..".equals(fileName)) {
+                continue;
+            }
+
+            fileNameList.add(fileName);
+            channelSftp.quit();
+            session.disconnect();
+        }
+
+        return fileNameList;
+    }
+
+    /**
+     * 删除文件
+     */
+    public void delete(String fileName) throws JSchException, SftpException {
+        session = getConnectSession();
+        channelSftp = (ChannelSftp) session.openChannel("sftp");
+        channelSftp.connect();
+        channelSftp.rm(fileName);
+        channelSftp.quit();
+        session.disconnect();
+    }
+
+    /**
+     * 远程执行指令
+     */
+    public String exec(String command) throws JSchException, IOException {
+        StringBuilder stringBuilder = new StringBuilder();
+        try {
+            session = getConnectSession();
+            channel = session.openChannel("exec");
+            // jsch的登陆是无环境登陆即非login状态登陆,因此是没有环境变量的,
+            String execCommand;
+            // 在命令前添加 bash --login -c "command"以获取环境变量
+            // source .bashrc && command 也可以解决问题, 但是可能环境加载不全
+            if (command.startsWith("bash --login -c")) {
+                execCommand = command;
+            } else {
+                execCommand = String.format("bash --login -c \"%s\"", command);
+            }
+            ((ChannelExec) channel).setCommand(execCommand);
+            channel.setInputStream(null);
+            ((ChannelExec) channel).setErrStream(System.err);
+            InputStream in = channel.getInputStream();
+            channel.connect();
+            byte[] tmp = new byte[1024];
+            while (true) {
+                while (in.available() > 0) {
+                    int i = in.read(tmp, 0, 1024);
+                    if (i < 0) {
+                        break;
+                    }
+                    stringBuilder.append(new String(tmp, 0, i));
+                }
+                if (channel.isClosed()) {
+                    if (in.available() > 0) {
+                        continue;
+                    }
+                    break;
+                }
+            }
+        } finally {
+            disconnect();
+        }
+        return stringBuilder.toString();
+    }
+
+    /**
+     * 使用SCP把本地文件推送到targetServer目录下
+     * <p>
+     * 注意,文件名不能包含中文
+     */
+    public boolean scpTo(String sourceFilePath, String targetPath) throws JSchException, IOException, SSHUtilException {
+        try {
+            session = getConnectSession();
+            // scp内置了两个参数 -t 和 -f ,这两个参数是隐藏的,不会被用户显式提供,
+            // 两个scp进程之间传输数据时,远端机器上的scp进程被本地scp进程启动起来时提供上去。
+            // 需要说明的是,这是通过本地scp进程经ssh远程过去开启远端机器的scp进程来实现的。
+            // -t 指定为to 也就是目的端模式 指定的对象就是session对应的连接对象targetServer
+            String command = "scp " + "-t " + targetPath;
+            channel = session.openChannel("exec");
+            ((ChannelExec) channel).setCommand(command);
+            outputStream = channel.getOutputStream();
+            inputStream = channel.getInputStream();
+            channel.connect();
+            if (checkAck(inputStream) != 0) {
+                log.error("scpTo 执行失败");
+                return false;
+            }
+            File sourceFile = new File(sourceFilePath);
+            if (sourceFile.isDirectory()) {
+                log.error("sourceFilePath 必须是文件");
+                return false;
+            }
+            long fileSize = sourceFile.length();
+            command = "C0644 " + fileSize + " " + sourceFile.getName() + "\n";
+            outputStream.write(command.getBytes());
+            outputStream.flush();
+            if (checkAck(inputStream) != 0) {
+                log.error("scpTo 执行失败");
+                return false;
+            }
+            fileInputStream = new FileInputStream(sourceFile);
+            byte[] buffer = new byte[1024];
+            while (true) {
+                int len = fileInputStream.read(buffer, 0, buffer.length);
+                if (len <= 0) {
+                    break;
+                }
+                outputStream.write(buffer, 0, len);
+            }
+            buffer[0] = 0;
+            outputStream.write(buffer, 0, 1);
+            outputStream.flush();
+            return checkAck(inputStream) == 0;
+        } finally {
+            disconnect();
+        }
+    }
+
+    /**
+     * 使用scp把targetServer目录下的文件复制到本地
+     */
+    public boolean scpFrom(String sourceFilePath, String targetPath) throws JSchException, IOException, SSHUtilException {
+        try {
+            log.debug(sourceFilePath);
+            session = getConnectSession();
+            // scp内置了两个参数 -t 和 -f ,这两个参数是隐藏的,不会被用户显式提供,
+            // 两个scp进程之间传输数据时,远端机器上的scp进程被本地scp进程启动起来时提供上去。
+            // 需要说明的是,这是通过本地scp进程经ssh远程过去开启远端机器的scp进程来实现的。
+            // -f 指定对端为from 也就是源端模式 指定的对象就是session对应的连接对象targetServer
+            String command = "scp -f " + sourceFilePath;
+            Channel channel = session.openChannel("exec");
+            ((ChannelExec) channel).setCommand(command);
+            outputStream = channel.getOutputStream();
+            inputStream = channel.getInputStream();
+            channel.connect();
+            byte[] buf = new byte[1024];
+            // 发送指令 '0'
+            // 源端会一直等宿端的回应, 直到等到回应才会传输下一条协议文本.
+            // 在送出最后一条协议文本后, 源端会传出一个大小为零的字符'0'来表示真正文件传输的开始.
+            // 当文件接收完成后, 宿端会给源端发送一个'0'
+            buf[0] = 0;
+            outputStream.write(buf, 0, 1);
+            outputStream.flush();
+            // 接收C0644 这条消息携带了文件的信息
+            while (true) {
+                int c = checkAck(inputStream);
+                // 遇到C时跳出循环
+                if (c == 'C') {
+                    break;
+                }
+            }
+            // 接收 '0644 ' 这段字符表示文件的权限
+            inputStream.read(buf, 0, 5);
+            // 获取filesize
+            long filesize = 0L;
+            while (true) {
+                if (inputStream.read(buf, 0, 1) < 0) {
+                    break;
+                }
+                if (buf[0] == ' ') {
+                    break;
+                }
+                filesize = filesize * 10L + (long) (buf[0] - '0');
+            }
+            // 从 C0644命令读取文件名,命令中的文件名是不带路径的
+            String file = null;
+            for (int i = 0; ; i++) {
+                inputStream.read(buf, i, 1);
+                // 0x0a 是LF 换行符
+                if (buf[i] == (byte) 0x0a) {
+                    file = new String(buf, 0, i);
+                    break;
+                }
+            }
+            log.debug("filesize={}, file={}", filesize, file);
+            // 发送 '0'
+            buf[0] = 0;
+            outputStream.write(buf, 0, 1);
+            outputStream.flush();
+            // 如果目标是目录,则需要加上文件名
+            File target = new File(targetPath);
+            if (target.isDirectory()) {
+                log.debug("{} 是目录,需要添加文件名", target.getAbsolutePath());
+                target = new File(targetPath + File.separator + file);
+            }
+
+            fileOutputStream = new FileOutputStream(target);
+            int foo;
+            while (true) {
+                if (buf.length < filesize) {
+                    foo = buf.length;
+                } else {
+                    foo = (int) filesize;
+                }
+                foo = inputStream.read(buf, 0, foo);
+                if (foo < 0) {
+                    break;
+                }
+                fileOutputStream.write(buf, 0, foo);
+                filesize -= foo;
+                if (filesize == 0L) {
+                    break;
+                }
+            }
+            if (checkAck(inputStream) != 0) {
+                return false;
+            }
+            // 发送 '0'
+            buf[0] = 0;
+            outputStream.write(buf, 0, 1);
+            outputStream.flush();
+            log.debug("scp from {}@{}:{}{} to {} 完成", targetServer.getUser(), targetServer.getHost(), targetServer.getPort(), sourceFilePath, target.getAbsolutePath());
+            return true;
+        } finally {
+            disconnect();
+        }
+    }
+
+    private Session getConnectSession() throws JSchException {
+        jSch = new JSch();
+        session = jSch.getSession(targetServer.getUser(), targetServer.getHost(), targetServer.getPort());
+        session.setPassword(targetServer.getPassword());
+        session.setUserInfo(new UserInfoImpl());
+        // 不需要输入保存ssh安全密钥的yes或no
+        Properties properties = new Properties();
+        properties.put("StrictHostKeyChecking", "no");
+        session.setConfig(properties);
+        session.connect();
+        log.debug("已连接到{}@{}:{}", targetServer.getUser(), targetServer.getHost(), targetServer.getPort());
+        return session;
+    }
+
+    private void disconnect() throws IOException {
+        if (fileOutputStream != null) {
+            fileOutputStream.close();
+            fileOutputStream = null;
+        }
+        if (fileInputStream != null) {
+            fileInputStream.close();
+            fileInputStream = null;
+        }
+        if (outputStream != null) {
+            outputStream.close();
+            outputStream = null;
+        }
+        if (channel != null) {
+            channel.disconnect();
+            channel = null;
+        }
+        if (session != null) {
+            session.disconnect();
+            session = null;
+        }
+        jSch = null;
+    }
+
+    /**
+     * 来自源端的每条消息和每个传输完毕的文件都需要宿端的确认和响应.
+     * 宿端会返回三种确认消息: 0(正常), 1(警告)或2(严重错误, 将中断连接).
+     * 消息1和2可以跟一个字符串和一个换行符, 这个字符串将显示在scp的源端. 无论这个字符串是否为空, 换行符都是不可缺少的.
+     */
+    private static int checkAck(InputStream in) throws IOException, SSHUtilException {
+        int b = in.read();
+        // b 取值为0表示成功
+        if (b == 0) {
+            return b;
+        }
+        if (b == -1) {
+            return b;
+        }
+
+        // 1表示警告 2表示严重错误,将中断连接
+        // 1和2 后面会携带一条错误信息,以\n结尾
+        if (b == 1 || b == 2) {
+            // 打印消息后面跟的字符串
+            StringBuilder sb = new StringBuilder();
+            int c;
+            do {
+                // 读取字符串直到遇到换行符
+                c = in.read();
+                sb.append((char) c);
+            } while (c != '\n');
+            log.debug("checkAck发现错误消息: ack={}-msg={}", b, sb);
+            if (b == 1 && sb.toString().endsWith("No such file or directory")) {
+                throw new NoSuchFileException(sb.toString());
+            } else {
+                throw new ScpAckErrorException(sb.toString());
+            }
+        }
+        return b;
+    }
+}

+ 15 - 0
src/main/java/com/nokia/common/ssh/entity/SSHServer.java

@@ -0,0 +1,15 @@
+package com.nokia.common.ssh.entity;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class SSHServer {
+    private String host;
+    private int port = 22;
+    private String user;
+    private String password;
+}

+ 35 - 0
src/main/java/com/nokia/common/ssh/entity/UserInfoImpl.java

@@ -0,0 +1,35 @@
+package com.nokia.common.ssh.entity;
+
+import com.jcraft.jsch.UserInfo;
+
+public class UserInfoImpl implements UserInfo {
+    @Override
+    public String getPassphrase() {
+        return null;
+    }
+
+    @Override
+    public String getPassword() {
+        return null;
+    }
+
+    @Override
+    public boolean promptPassword(String s) {
+        return false;
+    }
+
+    @Override
+    public boolean promptPassphrase(String s) {
+        return false;
+    }
+
+    @Override
+    public boolean promptYesNo(String s) {
+        return false;
+    }
+
+    @Override
+    public void showMessage(String s) {
+
+    }
+}

+ 8 - 0
src/main/java/com/nokia/common/ssh/exception/SSHUtilException.java

@@ -0,0 +1,8 @@
+package com.nokia.common.ssh.exception;
+
+public class SSHUtilException extends Exception{
+
+    public SSHUtilException(String message) {
+        super(message);
+    }
+}

+ 7 - 0
src/main/java/com/nokia/common/ssh/exception/ScpAckErrorException.java

@@ -0,0 +1,7 @@
+package com.nokia.common.ssh.exception;
+
+public class ScpAckErrorException extends SSHUtilException {
+    public ScpAckErrorException(String message) {
+        super(message);
+    }
+}

+ 15 - 0
src/main/java/com/nokia/pm_interface_5g/PmInterface5gApplication.java

@@ -0,0 +1,15 @@
+package com.nokia.pm_interface_5g;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.scheduling.annotation.EnableScheduling;
+
+@EnableScheduling
+@SpringBootApplication
+public class PmInterface5gApplication {
+
+    public static void main(String[] args) {
+        SpringApplication.run(PmInterface5gApplication.class, args);
+    }
+
+}

+ 319 - 0
src/main/java/com/nokia/pm_interface_5g/task/FiveGPmTask.java

@@ -0,0 +1,319 @@
+package com.nokia.pm_interface_5g.task;
+
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.SftpException;
+import com.nokia.common.gpload.GploadUtil;
+import com.nokia.common.gpload.entity.GploadResult;
+import com.nokia.common.ssh.SSHUtil;
+import com.nokia.common.ssh.exception.SSHUtilException;
+import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.csv.CSVFormat;
+import org.apache.commons.csv.CSVParser;
+import org.apache.commons.csv.CSVPrinter;
+import org.apache.commons.csv.CSVRecord;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Data
+@Component
+@Slf4j
+public class FiveGPmTask {
+
+    @Value("${fiveg.pm.download.host:10.17.180.55}")
+    private String host;
+    @Value("${fiveg.pm.download.port:22}")
+    private Integer port;
+    @Value("${fiveg.pm.download.username:nokia}")
+    private String username;
+    @Value("${fiveg.pm.download.password:Nokia*123}")
+    private String password;
+    @Value("${fiveg.pm.download.sourceDir:/data/out2/pm_5g_hour}")
+    private String sourceDir;
+    @Value("${fiveg.pm.download.targetDir:download/}")
+    private String downloadTargetDir;
+    @Value("${fiveg.pm.filePrefix:pm_5g_hour_}")
+    private String filePrefix;
+    @Value("${fiveg.pm.distinct.targetDir:distinct/}")
+    private String distinctTargetDir;
+    private final DateFormat dateFormat = new SimpleDateFormat("yyyyMMddHH");
+
+    /**
+     * 定时任务
+     *
+     */
+    @Scheduled(cron = "0 */10 * * * ?")
+    public void cronTask() throws JSchException, SSHUtilException, IOException, SftpException {
+        // 创建文件夹
+        Files.createDirectories(Paths.get(downloadTargetDir));
+        Files.createDirectories(Paths.get(distinctTargetDir));
+        SSHUtil sshUtil = new SSHUtil(host, port, username, password);
+        // 获取文件列表
+        List<String> list = sshUtil.ls(sourceDir);
+        if (CollectionUtils.isEmpty(list)) {
+            return;
+        }
+
+        log.debug("扫描到的文件: {}", list);
+        for (String t : list) {
+            singleTask(t);
+        }
+    }
+
+    /**
+     * 单一任务
+     *
+     * @param filename 文件名
+     */
+    public void singleTask(String filename) throws JSchException, SSHUtilException, IOException, SftpException {
+        download(filename);
+        distinct(filename);
+        gpload(filename);
+    }
+
+    /**
+     * 下载文件
+     *
+     * @param filename 文件名
+     */
+    public void download(String filename) throws JSchException, SSHUtilException, IOException, SftpException {
+        SSHUtil sshUtil = new SSHUtil(host, port, username, password);
+        String sourceFilePath = sourceDir + "/" + filename;
+        File targetFile = new File(downloadTargetDir + filename);
+        if (!targetFile.exists()) {
+            targetFile.getParentFile().mkdirs();
+        }
+        String targetPath = targetFile.getAbsolutePath();
+        boolean b = sshUtil.scpFrom(sourceFilePath, targetPath);
+        if (b) {
+            log.debug("文件 {} 下载成功...", targetPath);
+            //删除远程文件
+            sshUtil.delete(sourceFilePath);
+        }
+    }
+
+    /**
+     * 去重
+     *
+     * @param filename 文件名
+     */
+    public void distinct(String filename) throws IOException {
+        String inputFilePath = downloadTargetDir + filename;
+        String outputFilePath = distinctTargetDir + filename;
+        Path inputPath = Paths.get(inputFilePath);
+        try (CSVParser parser = CSVFormat.DEFAULT.builder().build()
+                .parse(new InputStreamReader(Files.newInputStream(inputPath), StandardCharsets.UTF_8));
+             OutputStreamWriter osw = new OutputStreamWriter(Files.newOutputStream(Paths.get(outputFilePath)),
+                     StandardCharsets.UTF_8);
+             CSVPrinter printer = new CSVPrinter(osw, CSVFormat.DEFAULT);) {
+            Map<String, CSVRecord> map = new HashMap<>();
+            // 去重
+            for (CSVRecord t : parser) {
+                map.put(t.get(2), t);
+            }
+            // 重排
+            rerrange(printer, map);
+        }
+
+        // 删除本地源文件
+        Files.deleteIfExists(inputPath);
+    }
+
+    public void gpload(String filename) throws IOException {
+        String gploadCommand = "sh /data1/pm_5g/gpload/pm_nr_gpload.sh " + filename;
+        GploadResult gpload = GploadUtil.gpload(gploadCommand);
+        if (Boolean.TRUE.equals(gpload.getTaskStatus())) {
+            log.debug("gpload完成: {}", gpload);
+            // 删除重排文件
+            Files.deleteIfExists(Paths.get(distinctTargetDir + filename));
+        } else {
+            log.error("gpload 失败: {}", gpload.getMessage());
+        }
+    }
+
+    /**
+     * 保留4位小数
+     */
+    BigDecimal format(String s) {
+        if (!StringUtils.hasText(s)) {
+            return null;
+        }
+
+        return new BigDecimal(s).setScale(4, RoundingMode.UP);
+    }
+
+    /**
+     * 重排
+     */
+    private void rerrange(CSVPrinter printer, Map<String, CSVRecord> map) throws IOException {
+        for (CSVRecord t : map.values()) {
+            String nciStr = t.get(2);
+            String enbid = "";
+            String cellId = "";
+            if (!StringUtils.hasText(nciStr)) {
+                continue;
+            }
+
+            if (nciStr.contains(".")) {
+                String[] a = nciStr.split("\\.");
+                enbid = a[1];
+                cellId = a[2];
+            } else if (nciStr.contains("-")) {
+                String[] a = nciStr.split("-");
+                enbid = a[1];
+                cellId = a[2];
+            }
+
+            if (!StringUtils.hasText(enbid) || !StringUtils.hasText(cellId)) {
+                continue;
+            }
+            String enbCell = enbid + "_" + cellId;
+            int nci = Integer.parseInt(enbid) * 4096 + Integer.parseInt(cellId);
+            printer.printRecord(
+                    t.get(1),
+                    t.get(3),
+                    nci,
+                    enbid,
+                    cellId,
+                    enbCell,
+                    t.get(6),
+                    format(t.get(10)),
+                    format(t.get(18)),
+                    format(t.get(14)),
+                    format(t.get(23)),
+                    format(t.get(79)),
+                    format(t.get(59)),
+                    format(t.get(60)),
+                    format(t.get(53)),
+                    format(t.get(56)),
+                    format(t.get(80)),
+                    format(t.get(11)),
+                    format(t.get(81)),
+                    format(t.get(82)),
+                    format(t.get(83)),
+                    format(t.get(84)),
+                    format(t.get(85)),
+                    format(t.get(86)),
+                    format(t.get(87)),
+                    format(t.get(88)),
+                    format(t.get(89)),
+                    format(t.get(90)),
+                    format(t.get(91)),
+                    format(t.get(92)),
+                    format(t.get(93)),
+                    format(t.get(94)),
+                    format(t.get(95)),
+                    format(t.get(96)),
+                    format(t.get(97)),
+                    format(t.get(98)),
+                    format(t.get(99)),
+                    format(t.get(100)),
+                    format(t.get(101)),
+                    format(t.get(102)),
+                    format(t.get(103)),
+                    format(t.get(104)),
+                    format(t.get(62)),
+                    format(t.get(63)),
+                    format(t.get(105)),
+                    format(t.get(106)),
+                    format(t.get(57)),
+                    format(t.get(58)),
+                    format(t.get(107)),
+                    format(t.get(108)),
+                    format(t.get(109)),
+                    format(t.get(110)),
+                    t.get(111),
+                    t.get(112),
+                    t.get(113),
+                    t.get(8),
+                    t.get(7),
+                    format(t.get(20)),
+                    format(t.get(19)),
+                    format(t.get(12)),
+                    format(t.get(13)),
+                    format(t.get(114)),
+                    format(t.get(115)),
+                    format(t.get(116)),
+                    t.get(117),
+                    t.get(118),
+                    t.get(119),
+                    t.get(120),
+                    t.get(121),
+                    t.get(122),
+                    t.get(123),
+                    t.get(124),
+                    format(t.get(125)),
+                    format(t.get(126)),
+                    format(t.get(59)),
+                    format(t.get(60)),
+                    format(t.get(127)),
+                    format(t.get(128)),
+                    format(t.get(129)),
+                    format(t.get(130)),
+                    format(t.get(131)),
+                    format(t.get(132)),
+                    format(t.get(133)),
+                    t.get(134),
+                    t.get(135),
+                    t.get(136),
+                    t.get(137),
+                    t.get(138),
+                    t.get(139),
+                    t.get(140),
+                    t.get(141),
+                    format(t.get(142)),
+                    format(t.get(143)),
+                    t.get(144),
+                    t.get(145),
+                    t.get(147),
+                    t.get(148),
+                    t.get(149),
+                    t.get(150),
+                    t.get(151),
+                    t.get(152),
+                    t.get(153),
+                    t.get(154),
+                    t.get(155),
+                    t.get(156),
+                    t.get(157),
+                    t.get(158),
+                    t.get(159),
+                    t.get(160),
+                    format(t.get(161)),
+                    format(t.get(162)),
+                    format(t.get(163)),
+                    format(t.get(164)),
+                    format(t.get(165)),
+                    format(t.get(57)),
+                    format(t.get(58)),
+                    format(t.get(41)),
+                    format(t.get(166)),
+                    t.get(167),
+                    t.get(168),
+                    format(t.get(169)),
+                    format(t.get(65)),
+                    format(t.get(64)),
+                    format(t.get(61))
+            );
+        }
+    }
+}

+ 3 - 0
src/main/resources/application.properties

@@ -0,0 +1,3 @@
+server.port=12092
+spring.application.name=pm_interface_5g
+logging.level.com.nokia=debug

+ 213 - 0
src/test/java/com/nokia/pm_interface_5g/PmInterface5gApplicationTests.java

@@ -0,0 +1,213 @@
+package com.nokia.pm_interface_5g;
+
+import com.jcraft.jsch.JSchException;
+import com.jcraft.jsch.SftpException;
+import com.nokia.common.ssh.exception.SSHUtilException;
+import com.nokia.pm_interface_5g.task.FiveGPmTask;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.csv.CSVFormat;
+import org.apache.commons.csv.CSVParser;
+import org.apache.commons.csv.CSVPrinter;
+import org.apache.commons.csv.CSVRecord;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.util.StringUtils;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+
+@Slf4j
+@SpringBootTest
+class PmInterface5gApplicationTests {
+    @Autowired
+    private FiveGPmTask fiveGPmTask;
+
+    @Test
+    void test() throws JSchException, IOException, SSHUtilException, SftpException {
+        fiveGPmTask.cronTask();
+    }
+
+    @Test
+    void testRerrange() {
+        String readPath = "a/pm_5g_hour_2022072912.csv";
+        String resultPath = "a/";
+        try {
+            Files.createDirectories(Paths.get(resultPath));
+        } catch (IOException e) {
+            e.printStackTrace();
+            return;
+        }
+        String writePath = resultPath + "result.csv";
+        try (CSVParser parser = CSVFormat.DEFAULT.builder().build()
+                .parse(new InputStreamReader(Files.newInputStream(Paths.get(readPath)), StandardCharsets.UTF_8));
+             OutputStreamWriter osw = new OutputStreamWriter(Files.newOutputStream(Paths.get(writePath)),
+                     StandardCharsets.UTF_8);
+             CSVPrinter printer = new CSVPrinter(osw, CSVFormat.DEFAULT);) {
+            for (CSVRecord t : parser) {
+                String nciStr = t.get(2);
+                String enbid = "";
+                String cellId = "";
+                if (!StringUtils.hasText(nciStr)) {
+                    continue;
+                }
+                if (nciStr.contains(".")) {
+                    String[] a = nciStr.split("\\.");
+                    enbid = a[1];
+                    cellId = a[2];
+                } else if (nciStr.contains("-")) {
+                    String[] a = nciStr.split("-");
+                    enbid = a[1];
+                    cellId = a[2];
+                }
+                if (!StringUtils.hasText(enbid) || !StringUtils.hasText(cellId)) {
+                    continue;
+                }
+                String enbCell = enbid + "_" + cellId;
+                int nci = Integer.parseInt(enbid) * 4096 + Integer.parseInt(cellId);
+                printer.printRecord(
+                        t.get(1),
+                        t.get(3),
+                        nci,
+                        enbid,
+                        cellId,
+                        enbCell,
+                        t.get(6),
+                        format(t.get(10)),
+                        format(t.get(18)),
+                        format(t.get(14)),
+                        format(t.get(23)),
+                        format(t.get(79)),
+                        format(t.get(59)),
+                        format(t.get(60)),
+                        format(t.get(53)),
+                        format(t.get(56)),
+                        format(t.get(80)),
+                        format(t.get(11)),
+                        format(t.get(81)),
+                        format(t.get(82)),
+                        format(t.get(83)),
+                        format(t.get(84)),
+                        format(t.get(85)),
+                        format(t.get(86)),
+                        format(t.get(87)),
+                        format(t.get(88)),
+                        format(t.get(89)),
+                        format(t.get(90)),
+                        format(t.get(91)),
+                        format(t.get(92)),
+                        format(t.get(93)),
+                        format(t.get(94)),
+                        format(t.get(95)),
+                        format(t.get(96)),
+                        format(t.get(97)),
+                        format(t.get(98)),
+                        format(t.get(99)),
+                        format(t.get(100)),
+                        format(t.get(101)),
+                        format(t.get(102)),
+                        format(t.get(103)),
+                        format(t.get(104)),
+                        format(t.get(62)),
+                        format(t.get(63)),
+                        format(t.get(105)),
+                        format(t.get(106)),
+                        format(t.get(57)),
+                        format(t.get(58)),
+                        format(t.get(107)),
+                        format(t.get(108)),
+                        format(t.get(109)),
+                        format(t.get(110)),
+                        t.get(111),
+                        t.get(112),
+                        t.get(113),
+                        t.get(8),
+                        t.get(7),
+                        format(t.get(20)),
+                        format(t.get(19)),
+                        format(t.get(12)),
+                        format(t.get(13)),
+                        format(t.get(114)),
+                        format(t.get(115)),
+                        format(t.get(116)),
+                        t.get(117),
+                        t.get(118),
+                        t.get(119),
+                        t.get(120),
+                        t.get(121),
+                        t.get(122),
+                        t.get(123),
+                        t.get(124),
+                        format(t.get(125)),
+                        format(t.get(126)),
+                        format(t.get(59)),
+                        format(t.get(60)),
+                        format(t.get(127)),
+                        format(t.get(128)),
+                        format(t.get(129)),
+                        format(t.get(130)),
+                        format(t.get(131)),
+                        format(t.get(132)),
+                        format(t.get(133)),
+                        t.get(134),
+                        t.get(135),
+                        t.get(136),
+                        t.get(137),
+                        t.get(138),
+                        t.get(139),
+                        t.get(140),
+                        t.get(141),
+                        format(t.get(142)),
+                        format(t.get(143)),
+                        t.get(144),
+                        t.get(145),
+                        t.get(147),
+                        t.get(148),
+                        t.get(149),
+                        t.get(150),
+                        t.get(151),
+                        t.get(152),
+                        t.get(153),
+                        t.get(154),
+                        t.get(155),
+                        t.get(156),
+                        t.get(157),
+                        t.get(158),
+                        t.get(159),
+                        t.get(160),
+                        format(t.get(161)),
+                        format(t.get(162)),
+                        format(t.get(163)),
+                        format(t.get(164)),
+                        format(t.get(165)),
+                        format(t.get(57)),
+                        format(t.get(58)),
+                        format(t.get(41)),
+                        format(t.get(166)),
+                        t.get(167),
+                        t.get(168),
+                        format(t.get(169)),
+                        format(t.get(65)),
+                        format(t.get(64)),
+                        format(t.get(61))
+                );
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+    }
+
+    BigDecimal format(String s) {
+        if (!StringUtils.hasText(s)) {
+            return null;
+        }
+
+        return new BigDecimal(s).setScale(4, RoundingMode.UP);
+    }
+}