由于公司在外面有很多产品,而公司的产品是部署在远程服务端的,在客户使用的过程中,为了解决随机出现的问题,我们总不能一出现问题就大老远跑过去解决,这样无论是时间还是人力成本都是非常高昂的。在此处,我们的解决办法就是通过抓取远程开放路径下的系统日志文件,然后在本地查看解决。
源日志格式如下:
日志内容如下:
档号;全宗号;全宗名称;目录号;案卷号;档案类别;导出种类;开始时间;结束时间;用时;完成百分比;预计剩余时间(小时);文件名;文件路径;md5;是否可用
9-0-555-53;9;南京市XX公司;1;0053;0;案卷;Mon Mar 18 19:14:01 -0400 2019;Mon Mar 18 19:14:04 -0400 2019;2.900846;6.71828160256499e-05;725.074067644962;;;;
9-0-555-54;9;南京市XX公司;1;0054;0;案卷;Mon Mar 18 19:14:04 -0400 2019;Mon Mar 18 19:14:07 -0400 2019;2.943011;6.83411404398853e-05;724.745752051916;;;;
9-0-555-55;9;南京市XX公司;1;0055;0;案卷;Mon Mar 18 19:14:07 -0400 2019;Mon Mar 18 19:14:10 -0400 2019;2.972452;6.94994648541206e-05;724.545356220889;;;;
9-0-555-56;9;南京市XX公司;1;0056;0;案卷;Mon Mar 18 19:14:10 -0400 2019;Mon Mar 18 19:14:13 -0400 2019;2.942553;7.0657789268356e-05;724.23223611118;;;;
通过观察日志,我们不难发现,由于有众多的公司,而公司又分布在众多的城市中,而城市对应的是省-市-区/县三级中的地级市,为了很方便的查看众多的项目日志信息,我们需要做一个web页面,该页面左侧是全国的行政区划树,分为三级,分别是省-市-公司,而点击公司之后,就会展示该公司下的日志信息。
由于我这里实现的是后台日志文件录入数据库功能,因而前台功能以及数据展示相关功能在此不做过多讨论。
在分析了上述情况后,我们需要三张表,分别是:
行政区划表的数据很简单,直接从网上下载一份,稍加改动,去掉第三级区/县即可搞定。
公司表需要录入公司名称以及公司所在的地级市,而源日志文件中并没有给出。不过由于源日志文件中都包含所在城市的关键字,因而我们通过关键字提取,就是通过循环遍历行政区划表,找出该区划包含在日志全宗名称中的城市,以全宗名称为公司名,然后将公司挂载到该城市下。
日志表比较简单,就是循环遍历日志文件,然后再循环遍历日内的每一行信息,将其字段对应,挂在到对应的公司下,批量录入数据库即可。
由于我这里使用的是JPA,因而实体类结构就是数据表结构。也就是说,如果行政区划表亦或是日志表不存在,则系统在启动时会自动创建而无需手动处理。
org.springframework.boot
spring-boot-starter-data-jpa
org.postgresql
postgresql
org.mybatis.spring.boot
mybatis-spring-boot-starter
1.3.1
SystemLogApplication
package com.lyc.systemLog;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author: zhangzhenyi
* @date: 2019/4/11 15:04
* @description: 系统日志 Application
**/
@SpringBootApplication
public class SystemLogApplication {
public static void main(String[] args) {
SpringApplication.run(SystemLogApplication.class,args);
}
}
FileNameUtil
package com.lyc.systemLog.util;
import com.google.common.base.Splitter;
import com.lyc.systemLog.common.CommonConstant;
import lombok.var;
/**
* @author: zhangzhenyi
* @date: 2019/4/12 10:30
* @description: 获取文件名工具类
**/
public class FileNameUtil {
/**
* 返回创建的FileNameUtil实例
* @return
*/
public static FileNameUtil newFileNameUtil(){
return new FileNameUtil();
}
/**
* 从文件路径中获取文件名
* @param filePath
* @return
*/
public String getFileName(String filePath){
// 将文件路径转换成文件夹名与文件名的list集合
var nameList = Splitter.on(CommonConstant.Separators.BACK_SLASH).splitToList(filePath);
// 从最后一项中获取文件名
String fileNameFull = nameList.get(nameList.size() - 1);
var fileNameList = Splitter.on(CommonConstant.Separators.DOT).splitToList(fileNameFull);
return fileNameList.get(0);
}
/**
* 获取公司名称
* @param fileName 文件名称
* @return
*/
public String getCompanyName(String fileName){
// 公司后面的最后一个词是公司,因而匹配该词所在位置
int lastIndex = fileName.lastIndexOf(CommonConstant.LogKeys.COMPANY);
// 返回截取获得的公司名称
return fileName.substring(0,lastIndex + 2);
}
}
ReadLogUtil
package com.lyc.systemLog.util;
import java.io.*;
/**
* @author: zhangzhenyi
* @date: 2019/4/11 16:04
* @description: 读取日志文件工具类
**/
public class ReadLogUtil {
private FileInputStream in = null;
private InputStreamReader read = null;
private BufferedReader bufferedReader = null;
/**
* 创建"读取日志文件工具类"工具类
* @return
*/
public static ReadLogUtil newReadLogUtil(){
return new ReadLogUtil();
}
/**
* 根据文件路径获取缓存文件流
* @param filePath
* @return
* @throws FileNotFoundException
* @throws UnsupportedEncodingException
*/
public BufferedReader getBufferedReader(String filePath) throws FileNotFoundException, UnsupportedEncodingException {
File file = new File(filePath);
in = new FileInputStream(file);
read = new InputStreamReader(in,"UTF-8");
bufferedReader = new BufferedReader(read);
return bufferedReader;
}
/**
* 关闭文件流
* @throws IOException
*/
public void close() throws IOException {
bufferedReader.close();
read.close();
in.close();
}
}
TimeFormatUtil
package com.lyc.systemLog.util;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/**
* @author: zhangzhenyi
* @date: 2019/4/11 16:52
* @description: 格式化时间字符串
**/
public class TimeFormatUtil {
// 开始时间
private long startTime = 0;
// 截止时间
private long endTime = 0;
/**
* 返回创建的TimeFormatUtil实例
* @return
*/
public static TimeFormatUtil newTimeFormatUtil(){
return new TimeFormatUtil();
}
/**
* 设置开始时间
* @return
*/
public void setStartTime(){
this.startTime = new Date().getTime();
}
/**
* 设置截止时间
* @return
*/
public void setEndTime(){
this.endTime = new Date().getTime();
}
/**
* 计算程序运行总耗时
* @return
*/
public String getSpendTime(){
// 获取耗时时间
long spendTime = this.endTime - this.startTime;
// 将耗时时间转换成时间字符串
return timeToString(spendTime);
}
/**
* 将long类型的时间转换成时间字符串
* @param spendTime
* @return
*/
private String timeToString(long spendTime) {
// 毫秒
long millis = spendTime % 1000;
// 将用时全部转换成秒
long secondTemp = spendTime / 1000;
// 时
long hour = secondTemp / 3600;
// 剩余的秒
secondTemp = secondTemp % 3600;
// 分
long minutes = secondTemp / 60;
// 秒
long second = secondTemp % 60;
// 返回计算的最终结果
return "本次执行耗时:" + hour + "小时" + minutes + "分钟" + second + "秒" + millis + "毫秒";
}
/**
* 将时间字符串格式化成时间对象
* @param dateString
* @return
*/
public String toDate(String dateString) throws ParseException {
Locale localeUS = new Locale("en","US");
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss Z yyyy",localeUS);
Date date = simpleDateFormat.parse(dateString);
//2019-03-19 07:14:04
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
}
}
CommonConstant
package com.lyc.systemLog.common;
/**
* @author: zhangzhenyi
* @date: 2019/4/11 16:02
* @description: 系统常量
**/
public class CommonConstant {
/**
* 分隔符
*/
public interface Separators{
// 反斜杠
String BACK_SLASH = "\\";
// 斜杠
String SLASH = "/";
// 横杠
String WHIPPLETREE = "-";
// 点
String DOT = ".";
}
/**
* 日志路径
*/
public interface LogPath{
// 日志文件夹路径
String FOLDER_PATH = "F:\\DB\\南京市XX公司-日志";
// 日志属性长度
int PROPERTY_LENGTH = 16;
}
/**
* 日志关键字
*/
public interface LogKeys{
// 城市关键字
String CITY = "市";
// 公司关键词
String COMPANY = "公司";
}
}
Company
package com.lyc.systemLog.entity;
import lombok.*;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.util.Date;
/**
* @author: zhangzhenyi
* @date: 2019/4/12 11:01
* @description: 公司实体类
**/
@Entity
@Getter
@Setter
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Company {
// 主键id
@Id
@GeneratedValue
private Long id;
// 公司名称
private String name;
// 所属城市代码
private String cityCode;
// 所属城市名称
private String cityName;
// 添加时间
private Date createTime;
}
Domain
package com.lyc.systemLog.entity;
import lombok.*;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
/**
* @author: zhangzhenyi
* @date: 2019/4/12 11:09
* @description: 行政区划实体类
**/
@Entity
@Getter
@Setter
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Domain {
// 主键ID
@Id
@GeneratedValue
private int id;
// 行政区划code
private String code;
// 行政区划名称
private String name;
// 行政区划父级code码
private String parentId;
}
SystemLog
package com.lyc.systemLog.entity;
import lombok.*;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
/**
* @author: zhangzhenyi
* @date: 2019/4/11 15:09
* @description: 系统日志实体类
**/
@Entity
@Getter
@Setter
@Builder
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class SystemLog {
// 主键id
@Id
@GeneratedValue
private Long id;
// 档号
// 9-0-555-53;
private String docNo;
// 全宗号
// 9;
private String recordGroupNo;
// 全宗名称;
// XX公司;
private String recordGroupName;
// 目录号;
// 1;
private String catalogueNo;
// 案卷号;
// 0053;
private String volumnNo;
// 档案类别;
// 0;
private String docType;
// 导出种类;
// 案卷;
private String exportType;
// 开始时间;
// Mon Mar 18 19:14:01 -0400 2019;
private String startTime;
// 结束时间;
// Mon Mar 18 19:14:04 -0400 2019;
private String endTime;
// 用时;
// 2.900846;
private double usedTime;
// 完成百分比;
// 6.71828160256499e-05;
private String percentageCompletion;
// 预计剩余时间(小时);
// 725.074067644962;
private double remainingTime;
// 文件名;
// ;
private String fileName;
// 文件路径;
// ;
private String filePath;
// md5;
// ;
private String md5;
// 是否可用
private String isUsefull;
// 关联的公司表的主键id
private Long companyId;
}
CompanyRepository
package com.lyc.systemLog.repository;
import com.lyc.systemLog.entity.Company;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
/**
* @author: zhangzhenyi
* @date: 2019/4/12 11:04
* @description: 公司 Repository
**/
public interface CompanyRepository extends JpaRepository<Company,Long> {
/**
* 查询公司名称是否存在
* @param companyName
* @return
*/
@Query("select c from Company c where c.name = :companyName")
Company selectByCompanyName(@Param("companyName") String companyName);
}
DomainRepository
package com.lyc.systemLog.repository;
import com.lyc.systemLog.entity.Domain;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* @author: zhangzhenyi
* @date: 2019/4/12 11:13
* @description: 行政区划 Repository
**/
public interface DomainRepository extends JpaRepository<Domain,Long> {
}
SystemLogRepository
package com.lyc.systemLog.repository;
import com.lyc.systemLog.entity.SystemLog;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* @author: zhangzhenyi
* @date: 2019/4/11 15:09
* @description: 系统日志 Repository
**/
public interface SystemLogRepository extends JpaRepository<SystemLog,Long> {
}
SystemLogConvertCore
package com.lyc.systemLog.service;
import com.google.common.base.Splitter;
import com.lyc.systemLog.common.CommonConstant;
import com.lyc.systemLog.entity.Company;
import com.lyc.systemLog.entity.SystemLog;
import com.lyc.systemLog.util.TimeFormatUtil;
import lombok.var;
import org.springframework.stereotype.Service;
import java.text.ParseException;
import java.util.List;
/**
* @author: zhangzhenyi
* @date: 2019/4/11 16:29
* @description: 系统日志数据转换
**/
@Service
public class SystemLogConvertCore {
/**
* 将传入的每一行数据转换成对应的List字符串数组
* @param lineText
* @return
*/
public List<String> getPropertyList(String lineText){
return Splitter.on(";").splitToList(lineText);
}
/**
* 将读取的每一行数据转换成实体类
* @param lineText
* @return
*/
public SystemLog toSystemLogEntity(String lineText, Company company) throws ParseException {
// 将字符串转换成字符串List数组
var strList = getPropertyList(lineText);
// 如果数组长度不等于规定的长度(因为有数据信息不完整的情况),则默认为数据是脏数据,直接丢弃
if(strList.size() == CommonConstant.LogPath.PROPERTY_LENGTH){
// 将List对象转换成实体类对象
return convert(strList, company);
}
return null;
}
/**
* 将List对象转换成实体类对象
* @param strList
* @return
*/
private SystemLog convert(List<String> strList, Company company) throws ParseException {
TimeFormatUtil timeFormatUtil = TimeFormatUtil.newTimeFormatUtil();
return SystemLog.builder()
.docNo(strList.get(0))
.recordGroupNo(strList.get(1))
.recordGroupName(strList.get(2))
.catalogueNo(strList.get(3))
.volumnNo(strList.get(4))
.docType(strList.get(5))
.exportType(strList.get(6))
.startTime(timeFormatUtil.toDate(strList.get(7)))
.endTime(timeFormatUtil.toDate(strList.get(8)))
.usedTime(Double.valueOf(strList.get(9)))
.percentageCompletion(strList.get(10))
.remainingTime(Double.valueOf(strList.get(11)))
.fileName(strList.get(12))
.filePath(strList.get(13))
.md5(strList.get(14))
.isUsefull(strList.get(15))
.companyId(company.getId())
.build();
}
}
SystemLogService
package com.lyc.systemLog.service;
import com.google.common.collect.Lists;
import com.lyc.systemLog.common.CommonConstant;
import com.lyc.systemLog.entity.Company;
import com.lyc.systemLog.entity.Domain;
import com.lyc.systemLog.entity.SystemLog;
import com.lyc.systemLog.repository.CompanyRepository;
import com.lyc.systemLog.repository.DomainRepository;
import com.lyc.systemLog.repository.SystemLogRepository;
import com.lyc.systemLog.util.FileNameUtil;
import com.lyc.systemLog.util.ReadLogUtil;
import com.lyc.systemLog.util.TimeFormatUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.BufferedReader;
import java.io.File;
import java.text.ParseException;
import java.util.Date;
import java.util.List;
/**
* @author: zhangzhenyi
* @date: 2019/4/11 15:09
* @description: 系统日志服务层
**/
@Slf4j
@Service
public class SystemLogService {
@Autowired
private SystemLogRepository systemLogRepository;
@Autowired
private SystemLogConvertCore systemLogConvertCore;
@Autowired
private DomainRepository domainRepository;
@Autowired
private CompanyRepository companyRepository;
/**
* 将日志文件中的内容保存到数据库中
* @param filePath 文件绝对路径
* @param company 公司
* @return
* @throws ParseException
*/
public boolean saveLog(String filePath, Company company) throws ParseException {
// 根据传入的文件路径,获取文件流
ReadLogUtil readLogUtil = ReadLogUtil.newReadLogUtil();
List<SystemLog> systemLogList = Lists.newArrayList();
String lineText = null;
int index = 0;
try {
// 获取BufferedReader文件流
BufferedReader bufferedReader = readLogUtil.getBufferedReader(filePath);
while ((lineText = bufferedReader.readLine()) != null) {
log.info("本行信息:{}",lineText);
// 如果是第一条信息,则默认是标题栏,直接跳过
if(index != 0){
// 将每行字符串转换成对象
SystemLog systemLog = systemLogConvertCore.toSystemLogEntity(lineText, company);
// 将转换后的实体类对象放入到gcSystemLogList容器中
if(systemLog != null){ // 如果信息有效,则将该信息放入到容器中
systemLogList.add(systemLog);
}
}
// 否则索引值自增1
index ++;
}
// 将对象数组持久化到数据库中
systemLogRepository.save(systemLogList);
// 关闭文件流
readLogUtil.close();
} catch (java.io.IOException e) {
log.info("日志文件读取出错,故障码:{}",e);
e.printStackTrace();
}
return true;
}
/**
* 保存文件夹下的所有文件
* @return
*/
public String saveFolderLog() {
TimeFormatUtil timeFormatUtil = TimeFormatUtil.newTimeFormatUtil();
// 标记程序开始执行时间
timeFormatUtil.setStartTime();
// 获取文件夹路径
String folderPath = CommonConstant.LogPath.FOLDER_PATH;
// 扫描文件夹下的所有文件
List<String> filePathList = getFolderFiles(folderPath);
// 遍历读取每一个文件
traverseFile(filePathList);
// 标记程序执行截止时间
timeFormatUtil.setEndTime();
// 程序耗时
String spendTime = timeFormatUtil.getSpendTime();
return "日志文件持久化到数据库中成功!" + spendTime;
}
/**
* 循环遍历读取每一个文件
* @param filePathList 文件路径列表
*/
private void traverseFile(List<String> filePathList) {
filePathList.forEach(filePath -> {
try {
// 将当前文件的路径打印到控制台中
log.info("当前文件的路径全称为:{}",filePath);
// 保存公司
Company company = saveCompany(filePath);
// 如果返回的信息存在,则在其下插入公司数据
if(company != null){
// 保存日志文件中的数据
saveLog(filePath, company);
}
} catch (ParseException e) {
e.printStackTrace();
log.info("日志文件保存失败!,故障码:{}",e);
}
});
}
/**
* 保存公司
* @param filePath 文件路径
*/
private Company saveCompany(String filePath) {
// 获取文件名称
FileNameUtil fileNameUtil = FileNameUtil.newFileNameUtil();
String fileName = fileNameUtil.getFileName(filePath);
// 获取公司名称
String companyName = fileNameUtil.getCompanyName(fileName);
// 获取公司所在的行政区划
Domain domain = getCompanyDomain(companyName);
// 循环遍历行政区划,找出其含在文件名称中的行政区划
// 将该公司保存到公司表中,同时关联行政区划表
if(domain != null){
return save(companyName,domain);
}
return null;
}
/**
* 将该公司保存到公司表中,同时关联行政区划表
* @param companyName
* @param domain
*/
private Company save(String companyName, Domain domain) {
// 查询公司名称是否存在
Company company = companyRepository.selectByCompanyName(companyName);
if(company == null){ // 如果不存在,则添加
Company company1 = Company.builder()
.name(companyName)
.cityCode(domain.getCode())
.cityName(domain.getName())
.createTime(new Date())
.build();
// 保存之后,返回保存之后的数据
return companyRepository.save(company1);
}
return company;
}
/**
* 获取公司所在的行政区划
* @param companyName 公司名称
* @return
*/
private Domain getCompanyDomain(String companyName) {
// 创建公司所在城市列表集合
List<Domain> inDomainList = Lists.newArrayList();
// 查询全部的行政区划
List<Domain> domainList = domainRepository.findAll();
// 循环遍历行政区划,找出其在公司名的行政区划
domainList.forEach(domain -> {
if(!domain.getName().equals(CommonConstant.LogKeys.CITY)){ // 如果不等于市
// 如果公司中包含该城市名称,则将该城市加入到inDomainList集合中
if(companyName.contains(domain.getName())){
inDomainList.add(domain);
}
}
});
// 获取并返回该行政区划
if(inDomainList.size() == 1){ // 如果只有一个城市,则结果判定正确,如果有多个,则判定错误!
// 返回正确的行政区划
return inDomainList.get(0);
} else {
log.info("城市有误!!!!!!!!!!!!!!!!!!");
return null;
}
}
/**
* 扫描文件夹下的所有文件
* @param folderPath 文件夹路径
* @return
*/
public List<String> getFolderFiles(String folderPath) {
List<String> fileList = Lists.newArrayList();
// 加载文件夹路径
File folder = new File(folderPath);
// 如果该文件夹存在,则执行后续逻辑
if(folder.exists()){
// 获取文件对象集合
File[] fileArray = folder.listFiles();
// 循环遍历所有文件
for(File file : fileArray){
if(file.isDirectory()){ // 如果该文件类型是文件夹,则暂时不做处理
} else { // 否则其必然是文件
// 获取文件绝对路径
String filePath = file.getAbsolutePath();
// 将当前路径添加到filePath集合中
fileList.add(filePath);
}
}
}
return fileList;
}
}
SystemLogController
package com.lyc.systemLog.controller;
import com.lyc.systemLog.service.SystemLogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author: zhangzhenyi
* @date: 2019/4/11 15:09
* @description: 系统日志 Controller
**/
@RestController
public class SystemLogController {
@Autowired
private SystemLogService systemLogService;
/**
* 将日志文件中的内容保存到数据库中
* @return
*/
@GetMapping("/saveLog")
public String saveLog(){
return systemLogService.saveFolderLog();
}
}
application.yml
spring:
application:
# 服务名称
name: postgresql-server
datasource:
# spring连接数据库驱动
driverClassName: org.postgresql.Driver
url: jdbc:postgresql://localhost:5432/gs
username: postgres
password: root
# jpa自动创建不存在的数据表
jpa:
show-sql: true
hibernate:
ddl-auto: update
use-new-id-generator-mappings: true
jackson:
serialization:
indent_output: false
# 服务端口号
server:
port: 8083
### mybatis config ###
mybatis:
type-aliases-package: com.lyc.postgresql.entity
通过访问http://localhost:8083/saveLog
,然后我们可以看到相应的信息已经录入成功,录入成功后的部分结果如下所示:
company表
2210722 320100 南京市 2019-04-23 16:42:57.586 南京市XX公司
system_log表
2210723 1 2210722 9-0-555-53 0 2019-03-19 07:14:04 案卷 6.71828160256499e-05 南京市XX公司 9 725.074067644962 2019-03-19 07:14:01 2.900846 0053
2210724 1 2210722 9-0-555-54 0 2019-03-19 07:14:07 案卷 6.83411404398853e-05 南京市XX公司 9 724.745752051916 2019-03-19 07:14:04 2.943011 0054
2210725 1 2210722 9-0-555-55 0 2019-03-19 07:14:10 案卷 6.94994648541206e-05 南京市XX公司 9 724.545356220889 2019-03-19 07:14:07 2.972452 0055
2210726 1 2210722 9-0-555-56 0 2019-03-19 07:14:13 案卷 7.0657789268356e-05 南京市XX公司 9 724.23223611118 2019-03-19 07:14:10 2.942553 0056