# 电信采集子项目个人总结: #
## (1)功能分析: ##
记录使用电信宽带的登录人员信息,获得他们的上线时长,为后面的计费模块做铺垫。
## (2)需求分析: ##
数据采集:将采集到的数据文件通过io流读入到内存,并将数据保存在java对象中;
网络模块:将存储数据的对象集合从客户端发送给服务器,
数据入库:服务器拿到的数据应该保存在数据库中,采用jdbc技术进行java和数据库的交互;
备份模块:是在数据采集中边采集边备份,将异常信息进行备份在一个文件中,并且可以通过唯一标识(登入人员的ip)查询到他们的登录信息;
日志打印:记录一下项目的全过程,采用log4j技术;
配置模块:将配置信息(不要写死在代码中的常量信息)写入xml文件用来做配置文件,采用dom4j技术,进行解析xml文件并将配置信息的值传入每个模块中进行初始化。
## (3)各模块关键性代码分析: ##
# 数据采集: #
## 第一步 ##:有两种读取文件的方式:用字节流或字符流读入
A.采用FileInputStream读入数据文件,最后肯定要有一个read()方法,
使用啥read()方法好呢? readLine()
将字节流转换成字符流读入文件内容会更加方便,而BufferedReader流就有一个readLine()方法;
`FileInputStream file=new FileInputStream(filepath);`
`BufferedReader bf=new BufferedReader(new InputStreamReader(file));`
B.采用FileReader读入数据文件,代码如下:
`FileReader file=new FileReader(filepath);`
`BufferedReader bf=new BufferedReader(file);`
## 第二步 ##:遍历文件中的内容,根据|进行字段分隔,并将分隔字段保存在split数组中
//#briup1660|037:wKgB1660A|7|1239110900|44.211.221.24
使用while循环进行遍历:
String line=null;
while ((line=bf.readLine())!=null) {
String[] split=line.split("[|]");
1.根据数组下标得到数组中的每一个分隔字段
******substring():截取字符串
String user=split[0].substring(1);//用户名
String NAS_ip=split[1];//NAS_ip
String flag=split[2];//登录标志:7上8下
String time=split[3];//上线时间或下线时间
String login_ip=split[4];//登录ip
2.根据登录标志分情况把每一个分隔字段保存在BIDR这个类的对象中,用bidr的set()方法进行赋值
******flag是String类型,所以用equals()方法进行判断,返回boolean类型
if(flag.equals("7")){
BIDR bidr=new BIDR();
bidr.setAAA_login_name(user);
bidr.setNAS_ip(NAS_ip);
注意:通过读入文件拿到的time是String类型,然而上线时间和下线时间是Timestamp(时间戳)类型,所以需要进行转换
new Timestamp的实例,需要传入一个long类型的值,而string转long用
Long.parseLong(string);
Timestamp time_login=new Timestamp(Long.parseLong(time));
bidr.setLogin_date(time_login);
bidr.setLogin_ip(login_ip);
这样就可以得到多个bidr对象,那么用什么来保存这些对象呢?集合
啥集合好呢?map(k,v)采用键值成对,键是唯一标识的登录ip,值是bidr对象
if(!map.containsKey(login_ip)){
map.put(login_ip, bidr);
}
}
3.现在我们已经将所有的上线数据保存在了map集合中,接下来要将登录者的下线数据与上线数据根据登录ip进行匹配,并将正常数据保存在list集合中;
然后移除map集合中正常数据的对象,使map集合中只保存异常数据(有7没8);
else if(flag.equals("8")){
BIDR bidr = map.get(login_ip);
Timestamp login_date = bidr.getLogin_date();//获取上线时间
Timestamp time_logOut=new Timestamp(Long.parseLong(time));//将下线时间转为时间戳类型为了计算上线时长
int time_deration=(int)(time_logOut.getTime()-login_date.getTime());//获取上线时长=下线时间-上线时间,其中用了getTime()方法进行计算
bidr.setTime_deration(time_deration);//上线时长
将bidr对象用add()方法添加进list集合;
list.add(bidr);
用remove()方法从map集合中移除数据(有7有8)
map.remove(login_ip);
}
温馨提示:在采集的同时,我们也应该将异常数据进行备份!!!
得到备份的实例化,并且调用它的备份方法,传入map集合;
return list;
}
# 网络模块 #:将存放数据的集合从客户端发送给服务器
Socket:网络套接字,包含IP、端口号,能够向网络发送请求,能够对其它主机的请求进行响应
基于TCP协议网络客户端编程步骤:
1)创建Socket,指定服务器地址、端口
Socket s = new Socket(ip,port);
2)获取服务器端的输入、输出流
s.getInputStream();
s.getOutputStream();
3)封装输入、输出流
将输入输出的字节流根据需求封装成文件、对象等输入、输出流
4)进行读、写操作
read(),writer()
5)释放资源
close()
代码如下:
Socket socket=new Socket(ip, port);
OutputStream out=socket.getOutputStream();
ObjectOutputStream ob=new ObjectOutputStream(out);
ob.writeObject(arg0);
if(ob!=null){
ob.flush();
ob.close();
}
if(socket!=null){
socket.close();
}
基于TCP协议网络服务器端编程步骤:
1)创建服务器端Socket,并绑定在某一端口上
ServerSocket ss = new ServerSocket(port);
2)接收客户请求,获取客户端Socket
Socket s = ss.accept();
3)通过客户端Socket,获取客户端的输入、输出流
s.getInputStream();
s.getOutputStream();
4)封装输入、输出流
将输入输出的字节流根据需求封装成文件、对象等输入、输出流
5)进行读、写操作
read(),writer()
6)释放资源
close()
代码如下:
ServerSocket server=new ServerSocket(port);
Socket socket = server.accept();
InputStream in=socket.getInputStream();
ObjectInputStream ob=new ObjectInputStream(in);
Collection<BIDR> bidr = (Collection<BIDR>) ob.readObject();
return bidr;
# 数据库入库模块 #:采用jdbc技术,将服务器接收到的数据集合保存在数据库中,进行java和数据库的交互
jdbc编程步骤:
1)准备四大参数
private String driver;
private String url;
private String username;
private String password;
2)注册/加载驱动
Class.forName(driver);
3)获取连接:DriverManager.getConnection()方法
Connection connection = DriverManager.getConnection(url,username, password);
4)创建statement或者preparedstatement对象
一般使用preparedstatement,因为它可以通过?进行动态传值,为了防止sql攻击。
给?号传值使用的是方法是setString(),setInt()等根据类型判断用setXXX()方法
pstmt.setType(index,value);
index从1开始:表示第几个?
5)执行sql语句:三种方法
execute():返回boolean类型的值,代表是否有结果集返回
executeUpdate():返回int类型的值,代表操作执行完成后受影响的数据库的行数(针对于insert,update,delete)
executeQuery:返回的是ResultSet结果集,专门针对于select语句
6)处理结果:有结果集,处理结果集
遍历结果集的方法:next()和getXXX()方法
while(rs.next()){
rs.getType(index/columnName);注意:如果传的是index,那么索引是从1开始的。
7)关闭资源:遵循后开的先关原则
statement
connection
具体代码如下:
//准备四大参数:
private static String driver;
private static String url;
private static String username;
private static String password;
//注册驱动
Class.forName(driver);
//获取连接
Connection conn=DriverManager.getConnection(url,username,password);
//设置手动提交
conn.setAutoCommit(false);
//将服务器端接收的集合强转为list集合
List<BIDR> list=(List<BIDR>) arg0;
//遍历该集合,得到每一个bidr对象;
//创建preparedstatement对象,传入sql语句:插入语句,将每一个bidr对象插入进数据库
for (int i = 0; i < list.size(); i++) {
BIDR bidr=list.get(i);
//获取日期是当前月份的某一天:才知道插入哪一张表,跟sql语句中的day相对应
int day=bidr.getLogin_date().getDate();
String sql="insert into t_detail_"+day+" values(?,?,?,?,?,?)";//注意:sql语句需采用字符串进行拼接
*********用log4j日志记录sql语句:
*********//new LoggerImpl().debug(sql);
//采用动态传值的方法加载参数
PreparedStatement pst=conn.prepareStatement(sql);
pst.setString(1,bidr.getAAA_login_name());
pst.setString(2, bidr.getLogin_ip());
pst.setTimestamp(3, bidr.getLogin_date());
pst.setTimestamp(4, bidr.getLogout_date());
pst.setString(5, bidr.getNAS_ip());
pst.setInt(6, bidr.getTime_deration()/1000/60);
//执行sql语句
pst.execute();
//提交数据
conn.commit();
//关闭资源
pst.close();
}
# 备份模块:边采集边备份,所以要在采集模块中将异常数据传给备份模块,而备份的异常数据需要保存在文件中,并且我们可以在该文件中根据登录ip查询到该登录者的相关信息#
用io流将采集模块的异常数据写入备份文件中(异常数据保存在map集合中),也就是说采集模块传给备份模块一个map集合,那么应该用ObjectOutputStream流进行接收map集合,并写入到备份文件中(用writeObject()方法);
ObjectOutputStream oos =new ObjectOutputStream (new FileOutputStream(filepath));
oos.writeObject(map);
oos.flush();//刷新
oos.close();//关流
同样用io流读取备份文件,根据登录ip查询异常数据
代码如下:
ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filepath));
Map<String, BIDR> map=(Map<String, BIDR>) ois.readObject();
for(String key:map.keySet()){
//根据参数中的IP值,获取对应的对象
if (key.equals(arg0)) {
return map.get(key);
}
}
ois.close();//关流
# 日志模块:用日志记录项目的流程,该模块我们需要掌握的是采用log4j技术写一个自定义日志输出器,也就是写一个自定义的log4j的配置文件 #
log4j配置文件编程步骤:
1)配置日志记录器 Logger:分为五个级别:DEBUG、INFO、WARN、ERROR和FATAL。
Log4j有一个规则:只输出级别不低于设定级别的日志信息,假设Loggers级别设定为INFO,则INFO、WARN、ERROR和FATAL级别的日志信息都会输出,而级别比INFO低的DEBUG则不会输出。
配置语法:log4j.rootLogger = 日志级别,appenderName,appenderName (配置根记录器)
log4j.logger.自定义记录器名= 日志级别,appenderName ,appenderName (配置自定义记录器)
2)配置日志信息输出的地方 Appender:允许把日志输出到不同的地方,如控制台(Console)、文件(Files),可以根据天数或者文件大小产生新的文件,可以以流的形式发送到其它地方等等。
配置语法:log4j.appender.appenderName = className
className=org.apache.log4j.ConsoleAppender(控制台)
className=org.apache.log4j.FileAppender(文件)
className=org.apache.log4j.DailyRollingFileAppender(每天产生一个日志文件)
className=org.apache.log4j.RollingFileAppender(文件大小到达指定尺寸的时候产生一个新的文件)
className=org.apache.log4j.WriterAppender(将日志信息以流格式发送到任意指定的地方)
若是FileAppender选项有四个参数需要注意:
Threshold=日志级别:指定日志信息的最低输出级别,默认为DEBUG。
ImmediateFlush=true:表示所有消息都会被立即输出,设为false则不输出,默认值是true。
Append=false:true表示消息增加到指定文件中,false则将消息覆盖指定的文件内容,默认值是true。
File=文件路径:指定消息输出到某个文件中。
log4j.appender.file1 = org.apache.log4j.FileAppender
log4j.appender.file1.File = 文件路径
log4j.appender.file1.Append = true
3)配置日志信息的布局 Layout:提供四种日志输出样式,如根据HTML样式、自由指定样式、包含日志级别与信息的样式和包含日志时间、线程、类别等信息的样式。
log4j.appender.appenderName.layout =className
className=org.apache.log4j.HTMLLayout(以HTML表格形式布局)
className=org.apache.log4j.PatternLayout(可以灵活地指定布局模式)
className=org.apache.log4j.SimpleLayout(包含日志信息的级别和信息字符串)
className=org.apache.log4j.TTCCLayout(包含日志产生的时间、线程、类别等信息)
若是自由布局模式,应添加一个参数:写入具体的自定义的布局样式
log4j.appender.appender2.layout.ConversionPattern=利用%x的特定含义进行自定义。
-: 信息输出时左对齐;
%p: 输出日志信息优先级,即DEBUG,INFO,WARN,ERROR,FATAL,
%d: 输出日志时间点的日期或时间,默认格式为ISO8601,也可以在其后指定格式,比如:%d{yyy MMM dd HH:mm:ss,SSS},输出类似:2002年10月18日 22:10:28,921
%r: 输出自应用启动到输出该log信息耗费的毫秒数
%c: 输出日志信息所属的类目,通常就是所在类的全名
%t: 输出产生该日志事件的线程名
%l: 输出日志事件的发生位置,相当于%C.%M(%F:%L)的组合,包括类目名、发生的线程,以及在代码中的行数。
%x: 输出和当前线程相关联的NDC(嵌套诊断环境),尤其用到像java servlets这样的多客户多线程的应用中。
%%: 输出一个"%"字符
%F: 输出日志消息产生时所在的文件名称
%L: 输出代码中的行号
%m: 输出代码中指定的消息,产生的日志具体信息
%n: 输出一个回车换行符,Windows平台为"\r\n",Unix平台为"\n"输出日志信息换行
## 配置根记录器代码 ##
log4j.rootLogger = debug,console1,file1
log4j.appender.console1 = org.apache.log4j.ConsoleAppender
log4j.appender.console1.layout = org.apache.log4j.SimpleLayout
log4j.appender.file1 = org.apache.log4j.FileAppender
log4j.appender.file1.File = log.txt
log4j.appender.file1.Append = true
log4j.appender.file1.layout = org.apache.log4j.PatternLayout
log4j.appender.file1.layout.ConversionPattern=[woss_gather] -%d %p -------%m%n
## 配置自定义记录器代码 ##
log4j.logger.mylogger= debug,console,file
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.layout = org.apache.log4j.SimpleLayout
log4j.appender.appender2 = org.apache.log4j.FileAppender
log4j.appender.file.File = mylogger.txt
log4j.appender.file1.Append = true
log4j.appender.file.layout = org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[woss_gather]-%d %-5p -[%m]%n
## log4j日志的使用##
注册log4j: PropertyConfigurator.configure("log4j配置文件路径");
配置不同的级别的日志:
Logger.getLogger("记录器名").日志级别(String string);
# 配置模块:将配置信息(不要写死在代码中的常量信息)写入xml文件用来做配置文件,采用dom4j技术,进行解析xml文件并将配置信息的值传入每个模块中进行初始化 #
采用dom4j的解析步骤:
1)导入dom4j的jar包
2)创建解析器
SAXReader reader=new SAXReader();
3)获取document对象:使用read()方法读入解析文件
Document doc=reader.read("解析文件的路径");
4)获取根元素:使用getRootElement()方法
Element root = doc.getRootElement();
5)获取所有一级子元素:使用elements()方法,返回元素集合对象
List<Element> element1 = root.elements();//记得加泛型哦
6)遍历一级子元素的集合:使用增强for循环
for (Element e1 : element1) {
String name=e1.getName();//得到一级子元素的标签名
7)获取所有一级子元素的属性:使用attributes()方法,返回属性集合对象
List<Attribute> attribute = e1.attributes();//记得加泛型哦
//遍历所有一级子元素的属性的集合:使用增强for循环
for (Attribute attribute : attribute) {
String attName=attribute.getName();//得到属性名
String attValue=attribute.getValue();//得到属性值
}
8)获取所有二级子元素:使用elements()方法,返回元素集合对象
List<Element> element2 = element.elements();
for (Element e2 : element2) {
String name2=e2.getName();
String value2 = e2.getText();
}
}
## 配置模块编程步骤: ##
第一步:解析xml文件:采用dom4j技术
第二步:将解析好的配置信息以键值对的方式保存在properties对象中;
private static Properties properties=new Properties();//存放的是一级子元素的标签名和属性值,为了通过反射拿到该类的实例
properties.setProperty(name, attValue);
private static Properties properties2=new Properties();//存放的是二级子元素的标签名和文本内容,为了对每个实例需要的常量信息传值(即初始化)
properties2.setProperty(name2, value2);
private static Properties properties3=new Properties();//存放的是一级子元素的标签名和二级子元素的所有内容
注意:properties3起的是唯一标识作用(通过一级子元素的名字找到它相应的二级子元素的值),但是假设properties2这个存放二级子元素的集合中出现了相同的子元素,值却不相同,这时后一个值会覆盖掉前一个值的内容,这也是一个需要改进的地方哦。
第三步:通过反射得到每个模块的实例,并且调用它们的init()方法进行动态传值。
以备份模块为例:
public BackUP getBackup() throws Exception {
String classname=properties.getProperty("backup");//得到全限定名:包名+类名
BackUP backup=(BackUP) Class.forName(classname).newInstance();//通过反射拿到备份模块的实例
backup.init((Properties) properties3.get("backup"));//调用备份模块的init()方法进行传值
return backup;
}
这时backup的init()方法应该拿到配置信息,使用getProperty()方法,并且传入二级子元素的标签名
public void init(Properties arg0) {
backfile=arg0.getProperty("back-temp");
}
其他模块代码同理可得。
注意:配置模块已完成,那么进行测试是不能在new一个类的实例,而是首先得到配置模块的实例,调用它相对应的方法进行对其他类的实例化。
new ConfigurationImp1().getBackup();//得到了备份模块的一个实例
## 优化代码: 既然每个模块都需要通过反射获取实例并且调用init()方法进行初始化,那么我们可以将这几行代码进行封装##
private WossModule getModule(String name) throws Exception{
String className=properties1.getProperty(name);
WossModule wm=(WossModule) Class.forName(className).newInstance();
wm.init((Properties)properties2.get(name));
return wm;
}
WossModule是所有类的父类;
以备份模块举例进行调用该方法;
public BackUP getBackup() throws Exception {
return (BackUP)getModule("backup");
}
其他模块调用该方法同理可得。