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