电信采集项目

 

# 电信采集子项目我的总结: #

## (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

相关文章
相关标签/搜索