CONAN ZONE

          你越掙扎我就越興奮

          BlogJava 首頁 新隨筆 聯系 聚合 管理
            0 Posts :: 282 Stories :: 0 Comments :: 0 Trackbacks
          前一段時間剛來公司,看到一個項目中以前有人寫的struts代碼。是使用了FormFile來處理關于文件上傳的模塊。但是用力一段時間后,發現出問題了。寫完的這個模塊,上傳文件是沒有問題的,但是當服務器的空間較小的時候,穿一個比較大的文件就出問題了,文件還沒有上傳完,就拋出一個錯誤的頁面,報告上傳模塊出了問題,而且是Tomcat默認的出錯頁面。 于是想辦法,修改,查看源代碼,發現原來寫這段代碼的人是默認等文件上傳完以后進入Action了才判斷文件大小是否超出了限制。 但是,默認配置下使用struts的FormFile比較特殊,FormFile是struts包對外的一個接口,而且org.apache.struts.upload包是使用的commons-fileupload-1.0進行的封裝。如果使用了它來實現文件上傳的功能,則必須是FormFile對象在被初始化以后才能使用,那什么時候它才是被初始化的呢? 答案是:在進入Action之前就已經初始化好了! 因此,原先的設計:在Action中判斷文件大小是根本不能在上傳過程中起到提示作用的,因為這時候文件已經上傳完了。而且這個設計還有一個確定就是不能捕獲上傳過程中出現的任何問題。也就是說:在Action里我們得到的FormFile對象是上傳的一個結果,而不是一個未上傳好就可以使用的對象! 那如何控制FormFile上傳的過程呢?顯然,在Action里處理已經不能奏效了,想想別的辦法,讓我們翻看一下Struts的源代碼找找靈感吧。 這是struts1.1的org.apache.struts.upload包的描述: (見附件) 從上圖我們可以看出有有CommonsMultipartRequestHandler和DiskMultipartRequestHandler兩個類實現了MultipartRequestHandler接口。 大家都知道,Commons-fileupload控件在上傳的時候,使用的enctype為:enctype="multipart/form-data",因此不難看出MultipartRequestHandler的實現就是來處理enctype="multipart/form-data"這樣的post請求的。 但是這里有兩個類,CommonsMultipartRequestHandler和DiskMultipartRequestHandler。到底哪個是處理FormFile的上傳的呢?這個問題應該從org.apache.struts.config包里來找。 org.apache.struts.config包是用來處理struts配置文件的數據的包。找到org.apache.struts.config. ControllerConfig。 看這幾行:

           1 protected String multipartClass =     
           2 "org.apache.struts.upload.CommonsMultipartRequestHandler";      
           3         
           4 public String getMultipartClass()     
           5 {             
           6      return (this.multipartClass);         
           7 }          
           8    
           9 public void setMultipartClass(String multipartClass)     
          10 {             
          11      if (configured)     
          12      {                 
          13            throw new IllegalStateException("Configuration is frozen");                      
          14      }      
          15             
          16      this.multipartClass = multipartClass;        
          17 }     
          18 


           1 /**        
           2 * The fully qualified Java class name of the MultipartRequestHandler        
           3 * class to be used.        
           4 */        
           5 protected String multipartClass =    
           6 "org.apache.struts.upload.CommonsMultipartRequestHandler";     
           7        
           8 public String getMultipartClass()    
           9 {            
          10      return (this.multipartClass);        
          11 }         
          12   
          13 public void setMultipartClass(String multipartClass)    
          14 {            
          15      if (configured)    
          16      {                
          17            throw new IllegalStateException("Configuration is frozen");                     
          18      }     
          19            
          20      this.multipartClass = multipartClass;       
          21 }    
          22 

          這幾行的意思很明白,如果沒有在配置文件中配置MultipartRequestHandler實現類的絕對路徑,那就使用org.apache.struts.upload.CommonsMultipartRequestHandler類默認處理。 ^_^,這就是關鍵了:struts是默認使用org.apache.struts.upload.CommonsMultipartRequestHandler類來處理FormFile指定的上傳文件的。 馬上轉到org.apache.struts.upload.CommonsMultipartRequestHandler來看看:

          1 /**       
          2 *默認文件上傳的大小是250M       
          3 */        
          4 public static final long DEFAULT_SIZE_MAX = 250 * 1024 * 1024;         
          5 /**       
          6 *上傳文件在內存中使用的緩沖區大小,超過次數值的數據寫入硬盤。       
          7 */        
          8 public static final int DEFAULT_SIZE_THRESHOLD = 256 * 1024;   

          1 /**      
          2 *默認文件上傳的大小是250M      
          3 */       
          4 public static final long DEFAULT_SIZE_MAX = 250 * 1024 * 1024;        
          5 /**      
          6 *上傳文件在內存中使用的緩沖區大小,超過次數值的數據寫入硬盤。      
          7 */       
          8 public static final int DEFAULT_SIZE_THRESHOLD = 256 * 1024;   


          還有,最最重要的實現方法:

           1 /**       
           2 * Parses the input stream and partitions the parsed items into a set of       
           3 * form fields and a set of file items. In the process, the parsed items       
           4 * are translated from Commons FileUpload <code>FileItem</code> instances       
           5 * to Struts <code>FormFile</code> instances.    
           6 @param request The multipart request to be processed.     
           7 @throws ServletException if an unrecoverable error occurs.                  
           8 就是這個函數處理上傳文件的request,把request交給Commons FileUpload 控件處理,并解析FileItem轉換成Struts的FormFile。       
           9 */   
          10 public void handleRequest(HttpServletRequest request) throws ServletException    
          11 

           1 /**      
           2 * Parses the input stream and partitions the parsed items into a set of      
           3 * form fields and a set of file items. In the process, the parsed items      
           4 * are translated from Commons FileUpload <code>FileItem</code> instances      
           5 * to Struts <code>FormFile</code> instances.   
           6 @param request The multipart request to be processed.    
           7 @throws ServletException if an unrecoverable error occurs.                 
           8 就是這個函數處理上傳文件的request,把request交給Commons FileUpload 控件處理,并解析FileItem轉換成Struts的FormFile。      
           9 */  
          10 public void handleRequest(HttpServletRequest request) throws ServletException 

          再看看這個函數內部是怎么實現的吧?

          1 // 使用了DiskFileUpload。      
          2 // (Commons-FileUpload很老版本的一個上傳實現類了,還在用?我的顯示是Deprecated)        
          3 DiskFileUpload upload = new DiskFileUpload();        
          4 // 上傳最大值        
          5 upload.setSizeMax((int) getSizeMax(ac));        
          6 // 上傳文件在內存中使用的緩沖區大小        
          7 upload.setSizeThreshold((int) getSizeThreshold(ac));        
          8 // 存在硬盤的什么地方,一般是默認    
          9 pload.setRepositoryPath(getRepositoryPath(ac));    


          1 // 使用了DiskFileUpload。     
          2 // (Commons-FileUpload很老版本的一個上傳實現類了,還在用?我的顯示是Deprecated)       
          3 DiskFileUpload upload = new DiskFileUpload();       
          4 // 上傳最大值       
          5 upload.setSizeMax((int) getSizeMax(ac));       
          6 // 上傳文件在內存中使用的緩沖區大小       
          7 upload.setSizeThreshold((int) getSizeThreshold(ac));       
          8 // 存在硬盤的什么地方,一般是默認   
          9 pload.setRepositoryPath(getRepositoryPath(ac));    


          接著看handleRequest如何處理request的:

           1 // Parse the request into file items.                
           2 List items = null;                
           3 try     
           4 {                    
           5      items = upload.parseRequest(request);                
           6 }    
           7 //這里是關鍵:上傳過程中出了超出最大值的異常了,如何處理?          
           8 catch (DiskFileUpload.SizeLimitExceededException e)     
           9 {                     
          10      // Special handling for uploads that are too big          
          11      request.setAttribute(    
          12 MultipartRequestHandler.ATTRIBUTE_MAX_LENGTH_EXCEEDED,    
          13            Boolean.TRUE);                    
          14      return;                
          15 }                     
          16 //出了其他異常,如enctype不對,磁盤空間不足怎么辦?        
          17 catch (FileUploadException e)     
          18 {                    
          19      log.error("Failed to parse multipart request", e);                    
          20      throw new ServletException(e);                
          21 }    
          22 


           1 // Parse the request into file items.               
           2 List items = null;               
           3 try    
           4 {                   
           5      items = upload.parseRequest(request);               
           6 }   
           7 //這里是關鍵:上傳過程中出了超出最大值的異常了,如何處理?         
           8 catch (DiskFileUpload.SizeLimitExceededException e)    
           9 {                    
          10      // Special handling for uploads that are too big         
          11      request.setAttribute(   
          12 MultipartRequestHandler.ATTRIBUTE_MAX_LENGTH_EXCEEDED,   
          13            Boolean.TRUE);                   
          14      return;               
          15 }                    
          16 //出了其他異常,如enctype不對,磁盤空間不足怎么辦?       
          17 catch (FileUploadException e)    
          18 {                   
          19      log.error("Failed to parse multipart request", e);                   
          20      throw new ServletException(e);               
          21 }   
          22 


          這次一目了然了: Struts根本沒有把上傳過程中出的超出最大值的異常帶到Action,而是把它放到了rquest的Attribute里。 而出了其他異常如enctype不對,磁盤空間不足怎么辦?很遺憾,Struts沒有去處理它,而是log了一下,拋給了上一層了。 那我一定要獲得這些全部異常咋辦呢?沒辦法,自己定制一個MultipartRequestHandler吧,那樣就能徹底解決上傳過程中的控制問題了! 在此之前,我們得先去最新版的commons-fileupload控件看看上傳過程中可能拋出多少異常?

           1 //所有上傳異常的父類    
           2 org.apache.commons.fileupload.FileUploadException     
           3 //注意:這個類的類名是FileUploadBase.SizeLimitExceededException是個public內    
           4 //部類。上傳的formdata總的數據超出了規定大小    
           5 org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException     
           6 //注意:也是個內部類。這個才是上傳的文件超出了規定大小    
           7 org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededException    
           8 //其它的,也看看吧:    
           9 org.apache.commons.fileupload.FileUploadBase.FileUploadIOException    
          10 org.apache.commons.fileupload.FileUploadBase.InvalidContentTypeException    
          11 org.apache.commons.fileupload.FileUploadBase.IOFileUploadException    
          12 org.apache.commons.fileupload.FileUploadBase.UnknownSizeException

           1 //所有上傳異常的父類   
           2 org.apache.commons.fileupload.FileUploadException    
           3 //注意:這個類的類名是FileUploadBase.SizeLimitExceededException是個public內   
           4 //部類。上傳的formdata總的數據超出了規定大小   
           5 org.apache.commons.fileupload.FileUploadBase.SizeLimitExceededException    
           6 //注意:也是個內部類。這個才是上傳的文件超出了規定大小   
           7 org.apache.commons.fileupload.FileUploadBase.FileSizeLimitExceededException   
           8 //其它的,也看看吧:   
           9 org.apache.commons.fileupload.FileUploadBase.FileUploadIOException   
          10 org.apache.commons.fileupload.FileUploadBase.InvalidContentTypeException   
          11 org.apache.commons.fileupload.FileUploadBase.IOFileUploadException   
          12 org.apache.commons.fileupload.FileUploadBase.UnknownSizeException    

          要想獲得盡可能仔細的數據就在處理的try/catch塊里把上面的異常都catch一下,放到request的attribute里去就OK了。 另外還有要說的是,最好用commons-fileupload控件的最新版本,因為DiskFileUpload這個類,commons-fileupload已經棄用了,取而代之的是ServletFileUpload類了,所以一定要注意!切記,切記….. 這是我寫的CommonsMultipartRequestHandler替代類的public void handleRequest(HttpServletRequest request) throws ServletException函數:

            1 public void handleRequest(HttpServletRequest request) throws ServletException    
            2 {    
            3      // Get the app config for the current request.    
            4      ModuleConfig ac = (ModuleConfig) request.getAttribute(Globals.MODULE_KEY);    
            5    
            6      // DiskFileItem工廠,主要用來設定上傳文件的參數    
            7      DiskFileItemFactory fileItemFactory = new DiskFileItemFactory();    
            8    
            9      // 上傳文件所用到的緩沖區大小,超過此緩沖區的部分將被寫入到磁盤    
           10      fileItemFactory.setSizeThreshold((intthis.getSizeThreshold(ac));    
           11    
           12      // 上傳文件用到的臨時文件存放位置    
           13      fileItemFactory.setRepository(this.getRepository(ac));    
           14    
           15      // 使用fileItemFactory為參數實例化一個ServletFileUpload對象    
           16      // 注意:該對象為commons-fileupload-1.2新增的類.    
           17      // 對于1.2以下的commons-fileupload版本并不存在此類.    
           18      ServletFileUpload upload = new ServletFileUpload(fileItemFactory);    
           19    
           20      // 從session中讀取對本次上傳文件的最大值的限制    
           21      String maxUploadSize = (String)request.getSession().    
           22 getAttribute(BasicConstants.maxUploadSize);    
           23    
           24      // 獲取struts-config文件中controller標簽的maxFileSize屬性來確定默認上傳的限制    
           25      // 如果struts-config文件中controller標簽的maxFileSize屬性沒設置則使用默認的上傳限制250M.    
           26      long defaultOrConfigedMaxUploadSize = this.getSizeMax(ac);    
           27      if (maxUploadSize != null && maxUploadSize != "")    
           28      {    
           29           // 如果maxUploadSize設定不正確則上傳限制為defaultOrConfigedMaxUploadSize的值    
           30    
           31           // 正確則為maxUploadSize轉換成的字節數    
           32           upload.setSizeMax((longthis.convertSizeToBytes(    
           33 maxUploadSize, defaultOrConfigedMaxUploadSize));    
           34           
           35       }    
           36       else   
           37       {    
           38           // 如果maxUploadSize沒設置則使用默認的上傳限制     
           39           upload.setSizeMax(defaultOrConfigedMaxUploadSize);    
           40       }     
           41    
           42       // 從session中清空maxUploadSize    
           43       request.getSession().removeAttribute("maxUploadSize");     
           44    
           45       // Create the hash tables to be populated.    
           46       elementsText = new Hashtable();    
           47       elementsFile = new Hashtable();    
           48       elementsAll = new Hashtable();     
           49    
           50       // Parse the request into file items.    
           51       List items = null;    
           52       // ServletFileUpload類來處理表單請求    
           53       // 拋出的異常為FileUploadException的子異常    
           54       // 如果捕獲這些異常就將捕獲的異常放到session中返回.    
           55    
           56       try   
           57       {    
           58             items = upload.parseRequest(request);    
           59    
           60       }    
           61       catch (FileUploadBase.SizeLimitExceededException e)    
           62       {    
           63    
           64             // 請求數據的size超出了規定的大小.    
           65             request.getSession().setAttribute(    
           66                 BasicConstants.baseSizeLimitExceededException, e);    
           67             return;    
           68       }    
           69       catch (FileUploadBase.FileSizeLimitExceededException e)    
           70       {    
           71             // 請求文件的size超出了規定的大小.    
           72             request.getSession().setAttribute(    
           73                 BasicConstants.baseFileSizeLimitExceededException, e);    
           74             return;    
           75       }    
           76       catch (FileUploadBase.IOFileUploadException e)    
           77       {    
           78             // 文件傳輸出現錯誤,例如磁盤空間不足等.    
           79             request.getSession().setAttribute(    
           80                 BasicConstants.baseIOFileUploadException, e);    
           81             return;    
           82       }    
           83       catch (FileUploadBase.InvalidContentTypeException e)    
           84       {    
           85             // 無效的請求類型,即請求類型enctype != "multipart/form-data"    
           86             request.getSession().setAttribute(    
           87                 BasicConstants.baseInvalidContentTypeException, e);    
           88             return;    
           89       }    
           90       catch (FileUploadException e)    
           91       {    
           92            // 如果都不是以上子異常,則拋出此總的異常,出現此異常原因無法說明.    
           93            request.getSession().setAttribute(    
           94                 BasicConstants.FileUploadException, e);    
           95            return;    
           96       }    
           97    
           98       // Partition the items into form fields and files.    
           99       Iterator iter = items.iterator();    
          100           
          101       while (iter.hasNext())    
          102       {    
          103             FileItem item = (FileItem) iter.next();    
          104    
          105             if (item.isFormField())    
          106             {    
          107                    addTextParameter(request, item);    
          108             }    
          109             else   
          110             {    
          111                    addFileParameter(item);    
          112             }    
          113       }    
          114 }   
          115 

            1 public void handleRequest(HttpServletRequest request) throws ServletException   
            2 {   
            3      // Get the app config for the current request.   
            4      ModuleConfig ac = (ModuleConfig) request.getAttribute(Globals.MODULE_KEY);   
            5   
            6      // DiskFileItem工廠,主要用來設定上傳文件的參數   
            7      DiskFileItemFactory fileItemFactory = new DiskFileItemFactory();   
            8   
            9      // 上傳文件所用到的緩沖區大小,超過此緩沖區的部分將被寫入到磁盤   
           10      fileItemFactory.setSizeThreshold((intthis.getSizeThreshold(ac));   
           11   
           12      // 上傳文件用到的臨時文件存放位置   
           13      fileItemFactory.setRepository(this.getRepository(ac));   
           14   
           15      // 使用fileItemFactory為參數實例化一個ServletFileUpload對象   
           16      // 注意:該對象為commons-fileupload-1.2新增的類.   
           17      // 對于1.2以下的commons-fileupload版本并不存在此類.   
           18      ServletFileUpload upload = new ServletFileUpload(fileItemFactory);   
           19   
           20      // 從session中讀取對本次上傳文件的最大值的限制   
           21      String maxUploadSize = (String)request.getSession().   
           22 getAttribute(BasicConstants.maxUploadSize);   
           23   
           24      // 獲取struts-config文件中controller標簽的maxFileSize屬性來確定默認上傳的限制   
           25      // 如果struts-config文件中controller標簽的maxFileSize屬性沒設置則使用默認的上傳限制250M.   
           26      long defaultOrConfigedMaxUploadSize = this.getSizeMax(ac);   
           27      if (maxUploadSize != null && maxUploadSize != "")   
           28      {   
           29           // 如果maxUploadSize設定不正確則上傳限制為defaultOrConfigedMaxUploadSize的值   
           30   
           31           // 正確則為maxUploadSize轉換成的字節數   
           32           upload.setSizeMax((longthis.convertSizeToBytes(   
           33 maxUploadSize, defaultOrConfigedMaxUploadSize));   
           34          
           35       }   
           36       else  
           37       {   
           38           // 如果maxUploadSize沒設置則使用默認的上傳限制    
           39           upload.setSizeMax(defaultOrConfigedMaxUploadSize);   
           40       }    
           41   
           42       // 從session中清空maxUploadSize   
           43       request.getSession().removeAttribute("maxUploadSize");    
           44   
           45       // Create the hash tables to be populated.   
           46       elementsText = new Hashtable();   
           47       elementsFile = new Hashtable();   
           48       elementsAll = new Hashtable();    
           49   
           50       // Parse the request into file items.   
           51       List items = null;   
           52       // ServletFileUpload類來處理表單請求   
           53       // 拋出的異常為FileUploadException的子異常   
           54       // 如果捕獲這些異常就將捕獲的異常放到session中返回.   
           55   
           56       try  
           57       {   
           58             items = upload.parseRequest(request);   
           59   
           60       }   
           61       catch (FileUploadBase.SizeLimitExceededException e)   
           62       {   
           63   
           64             // 請求數據的size超出了規定的大小.   
           65             request.getSession().setAttribute(   
           66                 BasicConstants.baseSizeLimitExceededException, e);   
           67             return;   
           68       }   
           69       catch (FileUploadBase.FileSizeLimitExceededException e)   
           70       {   
           71             // 請求文件的size超出了規定的大小.   
           72             request.getSession().setAttribute(   
           73                 BasicConstants.baseFileSizeLimitExceededException, e);   
           74             return;   
           75       }   
           76       catch (FileUploadBase.IOFileUploadException e)   
           77       {   
           78             // 文件傳輸出現錯誤,例如磁盤空間不足等.   
           79             request.getSession().setAttribute(   
           80                 BasicConstants.baseIOFileUploadException, e);   
           81             return;   
           82       }   
           83       catch (FileUploadBase.InvalidContentTypeException e)   
           84       {   
           85             // 無效的請求類型,即請求類型enctype != "multipart/form-data"   
           86             request.getSession().setAttribute(   
           87                 BasicConstants.baseInvalidContentTypeException, e);   
           88             return;   
           89       }   
           90       catch (FileUploadException e)   
           91       {   
           92            // 如果都不是以上子異常,則拋出此總的異常,出現此異常原因無法說明.   
           93            request.getSession().setAttribute(   
           94                 BasicConstants.FileUploadException, e);   
           95            return;   
           96       }   
           97   
           98       // Partition the items into form fields and files.   
           99       Iterator iter = items.iterator();   
          100          
          101       while (iter.hasNext())   
          102       {   
          103             FileItem item = (FileItem) iter.next();   
          104   
          105             if (item.isFormField())   
          106             {   
          107                    addTextParameter(request, item);   
          108             }   
          109             else  
          110             {   
          111                    addFileParameter(item);   
          112             }   
          113       }   
          114 }  
          115 

          其它部分均未做什么大改變。 好了,替代類寫好了,我們怎么去用呢? 這樣:在struts-config文件中寫配置:

           1 <!-- ========== Controller Configuration ================================ -->   
           2    
           3        <controller>   
           4    
           5               <!-- The "input" parameter on "action" elements is the name of a    
           6    
           7                      local or global "forward" rather than a module-relative path -->   
           8    
           9               <set-property value="true" property="inputForward" />   
          10               <set-property value="text/html; charset=UTF-8"   
          11                      property="contentType" />   
          12               <!-- 通過寫類的全名來替代struts默認的MultipartRequestHandler -->                 
          13    
          14               <set-property property="multipartClass"     
          15 value="com.amplesky.commonmodule.struts.AmpleskyMultipartRequestHandler" />   
          16               <!-- 規定的上傳文件的最大值 -->   
          17               <set-property property="maxFileSize" value="15M" />   
          18               <!--  緩沖區大小 -->   
          19               <set-property property="memFileSize" value="5M" />   
          20        </controller>   
          21 

           1 <!-- ========== Controller Configuration ================================ -->  
           2   
           3        <controller>  
           4   
           5               <!-- The "input" parameter on "action" elements is the name of a   
           6   
           7                      local or global "forward" rather than a module-relative path -->  
           8   
           9               <set-property value="true" property="inputForward" />  
          10               <set-property value="text/html; charset=UTF-8"  
          11                      property="contentType" />  
          12               <!-- 通過寫類的全名來替代struts默認的MultipartRequestHandler -->                
          13   
          14               <set-property property="multipartClass"    
          15 value="com.amplesky.commonmodule.struts.AmpleskyMultipartRequestHandler" />  
          16               <!-- 規定的上傳文件的最大值 -->  
          17               <set-property property="maxFileSize" value="15M" />  
          18               <!--  緩沖區大小 -->  
          19               <set-property property="memFileSize" value="5M" />  
          20        </controller>  
          21 

          好了!現在我們再用FormFile上傳文件,可以在上傳之前動態設置或者從配置文件設置上傳文件的大小,一旦上傳過程中出現了異常,就會被寫入request的attributs里。當進入action的時候,通過在Action里獲取異常就可以判斷上傳過程中出了什么問題了,而且在上傳過程中文件一旦超出了規定大小,或者磁盤大小不足的情況會立即中斷上傳的。這樣我們的功能就實現了。 最后,感慨一下, 1.感覺commons-fileupload還是挺好用的,但是FormFile的使用不大好,基本上誤導能力很強,網上關于FormFile的資料說明很少,以上這些都是我自己摸索出來的。 2.Struts 1都到了1.3版本了,但是對于FormFile的實現依然使用commons-fileupload-1.0版本的DiskFileUpload類,可見更新也不怎么樣。其實我覺得這是apache大部分開源工具的一個通病:更新確實不怎么快,commons框架里邊的源代碼和jar包不一致,還有很多是2004或者2003年的…要命的是居然不支持javase5的新特性???? 3. 認為把異常放到request的attributs里不好,曾經嘗試過拋出異常然后通過配置exception-controller來抓去異常處理,但是抓不到,怎么都抓不到,后來翻了大半天源碼才發現:struts在處理異常請求的時候將出現的ServletException和IOExcepton都交給了上層去處理了,根本不會拋出來。所以這兩種異常是抓不到的。


          posted on 2008-07-10 19:25 CONAN 閱讀(3498) 評論(0)  編輯  收藏 所屬分類: Struts
          主站蜘蛛池模板: 丹棱县| 昌宁县| 青浦区| 个旧市| 江油市| 恭城| 阿拉善右旗| 鹤庆县| 武山县| 萍乡市| 永城市| 墨脱县| 乌兰县| 桑日县| 新津县| 孝感市| 溧阳市| 定陶县| 嘉善县| 龙胜| 咸阳市| 湖口县| 石家庄市| 石屏县| 珲春市| 桃园市| 恩平市| 黑龙江省| 武鸣县| 沙雅县| 泰安市| 皋兰县| 鄂托克前旗| 德兴市| 宜宾市| 梁平县| 察雅县| 建水县| 资源县| 武山县| 宣威市|