# 電信采集子項目個人總結: #
## (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");
}
其他模塊調用該方法同理可得。