(注意:以下属于个人日常工作总结,思路梳理。细节处难免会有差错,欢迎指正)
一、任务描述:
——用 sftp每天定时到服务器指定路径下,拿到当天最新数据文本文件。按照一定要求,将文本文件解析成所需要的信息 覆盖到数据库中(oracle)。
——用到工具:SftpUtil、UUIDUtil
<!--sftp依赖-->
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.54</version>
</dependency>
——可能涉及到的技术:
①springboot框架
——文本文件
所需字段:
二、代码展示
1、目录如下(无关目录已划去):
entity:实体类(包含要求解析的字段)
service层:核心代码逻辑
task:定时任务(每天凌晨3点采集数据)
utils:工具类
resources:配置信息(服务器配置信息,生产环境prod,开发dev)
2、entity:
@Data
@Entity
@Table(name = "RM_DEVICE_4A")
public class DeviceFourAEntity implements Serializable {
private static final long serialVersionUID = 5702672284696028391L;
@Id
@GenericGenerator(name = "uuid", strategy = "uuid")
@GeneratedValue(generator = "uuid")
private String id;
@Column(name = "DEVICE_NAME")
private String deviceName;
@Column(name = "IPADDRESS")
private String ipAddress;
@Column(name = "VENDOR")
private String vendor;
@Column(name = "DEVICE_TYPE")
private String deviceType;
@Column(name = "CITY")
private String city;
@Column(name = "PROFESSION")
private String profession;
@Column(name = "INSERT_TIME")
private String insertTime;
}
3、service层
IDeviceFourAService 接口 (注意命名)
/**
*接口
*/
public interface IDeviceFourAService {
int managerFourA();
}
DeviceFourAServiceImpl(方法实现类)
@Slf4j
@Service
public class DeviceFourAServiceImpl implements IDeviceFourAService {
@Autowired
private DeviceFourARepository deviceFourARepository;
@Value("${sftp.zy.hostname:10.76.134.106}")
private String hostname;
@Value("${sftp.zy.port:22}")
private int port;
@Value("${sftp.zy.username:}")
private String username;
@Value("${sftp.zy.password:}")
private String password;
@Value("${sftp.zy.directory:}")
private String ftpath;
@Override
public int managerFourA() {
//日期操作把XXX换成目录和文件前缀
DateTimeFormatterBuilder builder=new DateTimeFormatterBuilder();
builder.appendPattern("yyyyMMdd");
String format = LocalDate.now().minusDays(1).format(builder.toFormatter());
String fileName="allResInfo_"+format+".txt";
String directory=ftpath+fileName;
//通过@Value注解注入,参数写进该方法
SftpUtil instance = SftpUtil.getInstance(hostname, port, username, password);
List<String> fileInfo = instance.getFileInfo(directory);
instance.delete(ftpath,fileName);
fileInfo.remove(0);
fileInfo.remove(1);
//从文本中解析出特定字段
List<DeviceFourAEntity> list=new ArrayList<>(fileInfo.size());
fileInfo.forEach(s -> {
String[] split = s.split("\\|");
DeviceFourAEntity entity=new DeviceFourAEntity();
entity.setId(UUIDUtil.getUUId());
entity.setDeviceName(split[1]);
entity.setIpAddress(split[0]);
entity.setVendor(split[9]);
entity.setDeviceType(split[10]);
entity.setCity(split[12]);
entity.setProfession(split[13]);
builder.appendPattern("yyyy-MM-dd HH:mm:ss");
entity.setInsertTime(LocalDateTime.now().format(builder.toFormatter()));
list.add(entity);
});
deviceFourARepository.deleteAll();
deviceFourARepository.batchSaveFourADevices(list);
return list.size();
}
}
4、task
@Slf4j
@Component
public class FourATask {
@Autowired
private IDeviceFourAService deviceFourAService;
@Scheduled(cron = "0 0 3 * * ?")
public void collectDeviceFourA() {
log.info("start FourATask.");
try {
int i = deviceFourAService.managerFourA();
log.info("started FourATask result {}",i);
} catch (Exception e) {
log.error("FourATask >>> deviceFourAService error: {}.", e.getMessage());
e.printStackTrace();
}
}
}
5、utils(网上可搜)
SftpUtil
public class SftpUtil {
private static SftpUtil sftpUtil;
private ChannelSftp sftp;
private String host;
private int port;
private String userName;
private String password;
public static synchronized SftpUtil getInstance(String host, int port,
String userName, String password) {
if (sftpUtil == null) {
sftpUtil = new SftpUtil(host, port, userName, password);
}
return sftpUtil;
}
private SftpUtil(String host, int port, String userName, String password) {
this.host = host;
this.port = port;
this.userName = userName;
this.password = password;
this.sftp=getSftp();
}
/**
* 连接初始化
*
* @Title: init void
* @author wangqinghua
* @date 2015-9-22 下午7:40:50
*/
public ChannelSftp getSftp() {
Channel channel = null;
try {
JSch jsch = new JSch();
Session sshSession = jsch.getSession(this.userName, this.host, this.port);
sshSession.setPassword(password);
Properties sshConfig = new Properties();
sshConfig.put("userauth.gssapi-with-mic", "no");
sshConfig.put("StrictHostKeyChecking", "no");
sshSession.setConfig(sshConfig);
sshSession.connect(20000);
channel = sshSession.openChannel("sftp");
channel.connect();
} catch (JSchException e) {
e.printStackTrace();
}
return (ChannelSftp) channel;
}
public void disconnect() throws Exception {
if (sftp != null) {
if (sftp.isConnected()) {
sftp.disconnect();
} else if (sftp.isClosed()) {
System.out.println("Session close...........");
}
}
}
/**
* 删除文件
*
* @param directory
* 要删除文件所在目录
* @param deleteFile
* 要删除的文件
*
* @throws Exception
*/
public void delete(String directory, String deleteFile){
try {
this.sftp.cd(directory);
this.sftp.rm(deleteFile);
} catch (SftpException e) {
e.printStackTrace();
}
}
public InputStream getInputStream(String directory) throws Exception {
InputStream streatm = sftp.get(directory);
return streatm;
}
public List<String> getFileInfo(String directory){
List<String> result=new ArrayList<>();
InputStream inputStream=null;
BufferedReader br=null;
try {
inputStream =getInputStream(directory);
br = new BufferedReader(new InputStreamReader(inputStream));
String s;
while((s=br.readLine()).length()!=0){
result.add(s);
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if(inputStream!=null){
inputStream.close();
}
if(br!=null){
br.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
}
UUIDUtil:(作用)
public class UUIDUtil {
public static String getUUId(){
return UUID.randomUUID().toString();
}
}
三、配置文件展示(配置时只能用空格,不能使用Tab)
application.yml(开关 )
spring:
profiles:
active:
- dev
#====================================================
# 服务器配置
#====================================================
server:
port: 11116
tomcat:
basedir: .
accesslog:
enabled: true
directory: logs
prefix: access
suffix: .log
renameOnRotate: true
pattern: '%h %l %u %t \"%r\" %s %b %T(s)'
max-threads: 100
min-spare-threads: 5
application-dev.yml
application-prod.yml
四、运行报错小结
1、空指针异常
2、无法解析文件
3、数组越界 9 数组长度是9 下标从0开始,所以下标最大值为8 取不到9 报越界错误
4、 file not found
检查路径、字符串拼接效果
五、代码调错 分享(根据以下条目,可自行选择查看后面详细解说)
1、一次性录入3万条时,GC异常
2、数组下标问题,自动补全。
3、空指针解决 (看判断 s != 0 && s !=null 缺一不可)
4、无效的日期格式(oracle)
5、删除命令代码 放置位置
6、postman与controller结合使用 测试接口 ,有效避免了定时任务的时间限制
详细分析
1、文本文件较大(12MB),导入过程中,大概有3万条记录,本地未设置GC内存,默认较小,所以内存溢出。工作过程中可根据个人情况设置GC内存大小,不过这点数据量,对于线上服务器不会造成太大影响(本地内存未设置,电脑多少内存,理论上就可以设置多少内存,出事内存很小,线上服务器没问题);
2、仔细观察上面的文本文件,不难发现前两行为说明信息,对于录入信息会有影响。观察以上所贴代码,我开始用的是如下操作:
但是我忽略了数组下标始终从0开始,也就是我执行了 fileInfo.remove(0)方法后,删除第一行,第二行下标就变成了0,而这时候。我再删除下标为1的那行时,其实是删除了一条有用数据。
3、空指针如下:
缺少条件, !=0 和!= null 缺一不可
4、无效的日期格式(用惯了Mysql的在用orcle注意了),以下为修改后
——ps.setDate(8, new Date(entity.getInsertTime().getTime()));
还有分页插入的sql,两种数据库也有差异,mysql limit就可以,方便;oracle麻烦些
5、删除命令代码 放置位置
6、postman与controller结合使用 测试接口 ,有效避免了定时任务的时间限制
本功能虽然不需要与前台交互,但因为有定时任务,所以调试过程中造成诸多不便。测试时可以建一个controller,结合postman来测试接口。
controller层test方法:
@RestController
@RequestMapping("4a")
public class DeviceFourAController {
@Resource
private IDeviceFourAService deviceFourAService;
@RequestMapping("test")
public String test4A(){
return deviceFourAService.managerFourA()+"";
}
}
(注意:以上属于个人日常工作总结,思路梳理。细节处难免会有差错,欢迎指正)
六、扩展延伸(后面会贴链接,朋友们根据需求查看)
1、sftp
2、常见异常
3、UUID
4、时间日期格式
5、定时任务
6、其他工具的使用
7、断点的使用
连接服务器:SecureCRT(使用)
向服务器上传文件:FileZilla(类似于xftp5)