空間站

          北極心空

            BlogJava :: 首頁 :: 聯系 :: 聚合  :: 管理
            15 Posts :: 393 Stories :: 160 Comments :: 0 Trackbacks

          http://fly-m.javaeye.com/blog/183534

          struts2亂碼與json插件(1)

              最近要用struts2做一個體育類的系統,由于系統本身的原因(要求可操作性強,用戶交互性高),所以不可避免地要用到ajax.在我的前一篇文章里已經提到了有關在struts2中運用ajax的一些東西了.這次重新再拾起來,仔細對比下其中的差別.
              在前一個例子性系統中,由于所有的編碼都是以UTF-8進行編碼的,所以也沒有碰到過有關亂碼的問題.普通調用和ajax調用都很正常地運行了.而在新的系統中,要求所有的頁面(包括數據庫)都要求運用GBK編碼.這樣,一些有關于亂碼的問題就出現了,亂碼...與struts2有關. 
              大家都知道,在struts2的配置文件中,有一個配置項直接跟編碼有關."struts.i18n.encoding"這個配置項表示struts2將對數據進行何種形式的編碼操作,默認的編碼為utf-8,這也是為什么在前一個新聞系統中沒有亂碼的原因,因為它本身都和系統默認編碼一致.而在新的系統中,由于要求必須將編碼改成GBK,配置如下:

           

          Xml代碼 復制代碼
          1. <constant name="struts.i18n.encoding" value="GBK"/>  

             在這種情況下,加上在tomcat serve.xml中修改URIEncoding=GBK,保證傳送到服務器的數據都是GBK格式.實際測試中,這種方式是正確的,編碼正確.
             然而好境不長,當運用到ajax時,問題出現了.我最先采用的是struts2的json插件,看它的要求,它要求工程編碼應該為UTF-8,而我的工程編碼實際為GBK,這是不可能改回去的事情.先不管它,在頁面中配置完成,進行ajax調用.果然,數據亂碼.在action中,對數據進行了測試,發現數據在進行action時已經是亂碼.對數據進行轉碼操作,發現將數據按

          Java代碼 復制代碼
          1. x = new String(x.getBytes("GBK"),"UTF-8");  

           時,數據正常了.這就表示數據是按照utf-8格式進行傳送的,而在sturts中可能將數據又轉回了gbk,導致亂碼產生.一開始從js入手,因為我用的prototype.js采用post進行傳送.設置的encoding為"UTF-8",修改js的encoding為"GBK",發現并沒有按照想像的方向發展,仍然亂碼.而采用get方式發送時,數據仍然是亂碼.
               只有從java方向入手了,處理方向有兩種,就像我在前面兩篇有關jsp亂碼中提到一樣.第一種是加攔截器,攔截到ajax請求時,將編碼重新轉碼操作.第二種就是像過濾器一樣,在進行參數編碼前設置正確的編碼.在struts2中采用的是第二種方法.其實struts.i18n.encoding采用的也是這種方法.相應的代碼如下:

          Java代碼 復制代碼
          1. public void prepare(HttpServletRequest request, HttpServletResponse response) {    
          2.         String encoding = null;    
          3.         if (defaultEncoding != null) {    
          4.             encoding = defaultEncoding;    
          5.         }   
          6.   
          7.         Locale locale = null;   
          8.   
          9.         if (defaultLocale != null) {   
          10.             locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());   
          11.         }   
          12.         if (encoding != null) {    
          13.             try {    
          14.                 request.setCharacterEncoding(encoding);    
          15.             } catch (Exception e) {    
          16.                 LOG.error("Error setting character encoding to '" + encoding + "' - ignoring.", e);    
          17.             }   
          18.         }    
          19.         if (locale != null) {    
          20.             response.setLocale(locale);    
          21.         }    
          22.         if (paramsWorkaroundEnabled) {    
          23.             request.getParameter("foo"); // simply read any parameter (existing or not) to "prime" the request    
          24.         }    
          25.     }  

           這個方法這是org.apache.struts2.dispatcher.Dispatcher中而被FilterDispatcher調用,后都大家都知道吧,是struts2的標準過濾器.它調用的地方如下:

          Java代碼 復制代碼
          1. protected HttpServletRequest prepareDispatcherAndWrapRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException {    
          2.     
          3.         Dispatcher du = Dispatcher.getInstance();    
          4.     
          5.         // Prepare and wrap the request if the cleanup filter hasn't already, cleanup filter should be    
          6.         // configured first before struts2 dispatcher filter, hence when its cleanup filter's turn,    
          7.         // static instance of Dispatcher should be null.    
          8.         if (du == null) {    
          9.     
          10.             Dispatcher.setInstance(dispatcher);    
          11.     
          12.             // prepare the request no matter what - this ensures that the proper character encoding    
          13.             // is used before invoking the mapper (see WW-9127)    
          14.             dispatcher.prepare(request, response);    
          15.         } else {    
          16.             dispatcher = du;    
          17.         }    
          18.             
          19.         try {    
          20.             // Wrap request first, just in case it is multipart/form-data    
          21.             // parameters might not be accessible through before encoding (ww-1278)    
          22.             request = dispatcher.wrapRequest(request, getServletContext());    
          23.         } catch (IOException e) {    
          24.             String message = "Could not wrap servlet request with MultipartRequestWrapper!";    
          25.             LOG.error(message, e);    
          26.             throw new ServletException(message, e);    
          27.         }    
          28.     
          29.         return request;    
          30.     }  

           由上可以看出,filter在實例化dispatcher后,調用其的prepare方法,而prepare方法中,好像涉及到的其他相關操作不多,只是操作request 和 response的,在prepare方法中,判斷encoding是不是空,如果不為空則將其設置編碼入request中.而在defaultEncoding的設置上,可以看出這個參數是跟struts.i18n.encoding相關的,相關代碼如下:

          Java代碼 復制代碼
          1. @Inject(StrutsConstants.STRUTS_I18N_ENCODING)    
          2.     public static void setEncoding(String val) {    
          3.         encoding = val;    
          4.     }  

           在上面這個方法中,將把struts.i18n.encoding注入到encoding中,也就是說,如果我們設置encoding為GBK,無論在何種條件下,它就是GBK編碼了.
                嘗試修改這種方式,因為直接影響的就是

          Java代碼 復制代碼
          1. prepareDispatcherAndWrapRequest(HttpServletRequest request, HttpServletResponse response)   

          這個方法,于是直接修改如為如下:

          Java代碼 復制代碼
          1. public class TextFilter extends FilterDispatcher{   
          2.     private static final Log log = LogFactory.getLog(TextFilter.class);   
          3.     private FilterConfig filterConfig;   
          4.   
          5.     private static String defaultEncoding;   
          6.     private static String defaultLocale;   
          7.     private static String paramsWorkaroundEnabled = "false";   
          8.   
          9.     @Inject(org.apache.struts2.StrutsConstants.STRUTS_DISPATCHER_PARAMETERSWORKAROUND)   
          10.     public static void setParamsWorkaroundEnabled(String enabled) {   
          11.         paramsWorkaroundEnabled = enabled;   
          12.     }   
          13.   
          14.     @Inject(StrutsConstants.STRUTS_I18N_ENCODING)   
          15.     public static void setEncoding(String encoding) {   
          16.         defaultEncoding = encoding;   
          17.     }   
          18.   
          19.     @Inject(value = StrutsConstants.STRUTS_LOCALE, required = false)   
          20.     public static void setLocale(String locale) {   
          21.         defaultLocale = locale;   
          22.     }   
          23.   
          24.     public void init(FilterConfig filterConfig) throws ServletException {   
          25.         super.init(filterConfig);   
          26.         this.filterConfig = filterConfig;   
          27.     }   
          28.   
          29.     protected HttpServletRequest prepareDispatcherAndWrapRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException {   
          30.         Dispatcher du = Dispatcher.getInstance();   
          31.   
          32.         if(du == null) {   
          33.             Dispatcher.setInstance(dispatcher);   
          34.             prepare(request, response);   
          35.         } else {   
          36.             dispatcher = du;   
          37.         }   
          38.   
          39.         try {   
          40.             request = dispatcher.wrapRequest(request, getServletContext());   
          41.         } catch(IOException e) {   
          42.             String message = "Could not wrap servlet request with MultipartRequestWrapper!";   
          43.             log.error(message, e);   
          44.             throw new ServletException(message, e);   
          45.         }   
          46.   
          47.         return request;   
          48.     }   
          49.   
          50.     private void prepare(HttpServletRequest request, HttpServletResponse response) {   
          51.         String encoding = request.getCharacterEncoding();   
          52.         if(encoding == null) {   
          53.             encoding = defaultEncoding;   
          54.         }   
          55.   
          56.         Locale locale = null;   
          57.         if(defaultLocale != null) {   
          58.             locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());   
          59.         }   
          60.   
          61.         if(encoding != null) {   
          62.             try {   
          63.                 request.setCharacterEncoding(encoding);   
          64.             } catch(Exception e) {   
          65.                 log.error("Error setting character encoding to '" + encoding + "' - ignoring.", e);   
          66.             }   
          67.         }   
          68.   
          69.         if(locale != null) {   
          70.             response.setLocale(locale);   
          71.         }   
          72.   
          73.         if(isParamsWorkaroundEnabled()) {   
          74.             request.getParameter("foo");   
          75.         }   
          76.     }   
          77.   
          78.     public boolean isParamsWorkaroundEnabled() {   
          79.         ServletContext servletContext = filterConfig.getServletContext();   
          80.         return servletContext != null && servletContext.getServerInfo() != null && servletContext.getServerInfo().indexOf("WebLogic") >= 0 ||   
          81.                 paramsWorkaroundEnabled.equalsIgnoreCase("true");   
          82.     }   
          83. }   
          84.    

           可以看出,整個就是繼承filterDispatcher,再修改其中的一個方法.將dispatcher.prepare()這一名改成filter.prepare的形式,實際上效果是一樣的.惟一讓人感覺不爽的就是,由于在filter中設置的各種變量都是私有而且是是靜態的(我嘗試用反射都不能得到它的值),導致直接不能得到父類的屬性值,沒辦法,只有再加變量了.在上面的prepare中,判斷request中的編碼是不是為空,一般來說,從jsp頁面傳送的編碼都是空的.當由js進行傳送時,由于已經設置傳送編碼為UTF-8,故getCharacterEncoding()不為空,則不再進行設置編碼了.其他由設置傳送編碼為defaultEncoding(即sturts.i18n.encoding).
          在上面的有一句

          Java代碼 復制代碼
          1. if(isParamsWorkaroundEnabled()) {           request.getParameter("foo");        }  

          專為weblogic設置,由于沒有接觸過,略過.值得不提的是,request.getParamter("foo").這一句,在tomcat里面是直接將編碼固定化,即只要調用了這一句,都將使得request(在tomcat的實現中)不能再接受其他編碼(參數已經被轉化了).
          最后,將filter設置在struts.xml中,以便窗口將參數@inject注入到filter中.

          Java代碼 復制代碼
          1. <bean class="m_ylf.cs.sicau.struts2.TextFilter" static="true"/>  

           上面一句必須要,不然相應的靜態參數都沒有被注入,是會產生NullPointerException的哦.
          先解決這一個問題,下一個問題將介紹struts2的json插件,及改進方法.

          struts2與json插件(2)

           

          在前一篇中<struts2與json插件(1)> ,講到了解決在struts2中出現的一點點亂碼問題,就想看看json中的數據處理方式.由struts2的處理流程來看,主要處理result的代碼如下在defaultActionInvocation中:

          Java代碼 復制代碼
          1. private void executeResult() throws Exception {   
          2.         result = createResult();   
          3.   
          4.         String timerKey = "executeResult: "+getResultCode();   
          5.         try {   
          6.             UtilTimerStack.push(timerKey);   
          7.             if (result != null) {   
          8.                 result.execute(this);   
          9.             } else if (resultCode != null && !Action.NONE.equals(resultCode)) {   
          10.                 throw new ConfigurationException("No result defined for action " + getAction().getClass().getName()    
          11.                         + " and result " + getResultCode(), proxy.getConfig());   
          12.             } else {   
          13.                 if (LOG.isDebugEnabled()) {   
          14.                     LOG.debug("No result returned for action "+getAction().getClass().getName()+" at "+proxy.getConfig().getLocation());   
          15.                 }   
          16.             }   
          17.         } finally {   
          18.             UtilTimerStack.pop(timerKey);   
          19.         }   
          20.     }  

           
          如上所示,result就是返回的result,而resultCode就是我們通用的String返回類型了.而json插件中,采用默認值Action.Success來得到json類型,而在result中處理,即調用上面的result.invoke(this).json中的主要處理代碼如下(省略中間一些代碼):

          Java代碼 復制代碼
          1. public void execute(ActionInvocation invocation) throws Exception {   
          2.     ActionContext actionContext = invocation.getInvocationContext();   
          3.     HttpServletRequest request = (HttpServletRequest) actionContext   
          4.         .get(StrutsStatics.HTTP_REQUEST);   
          5.     HttpServletResponse response = (HttpServletResponse) actionContext   
          6.         .get(StrutsStatics.HTTP_RESPONSE);   
          7.   
          8.     try {   
          9.         String json;   
          10.         Object rootObject;   
          11.         if (this.enableSMD) {   
          12.             //generate SMD   
          13.             rootObject = this.writeSMD(invocation);   
          14.         } else {   
          15.             // generate JSON   
          16.             if (this.root != null) {   
          17.                 ValueStack stack = invocation.getStack();   
          18.                 rootObject = stack.findValue(this.root);   
          19.             } else {   
          20.                 rootObject = invocation.getAction();   
          21.             }   
          22.         }   
          23.         json = JSONUtil   
          24.             .serialize(rootObject, this.excludeProperties, ignoreHierarchy, enumAsBean);   
          25.   
          26.         boolean writeGzip = enableGZIP && JSONUtil.isGzipInRequest(request);   
          27.           
          28.         JSONUtil.writeJSONToResponse(response, this.defaultEncoding,   
          29.             isWrapWithComments(), json, false, writeGzip);   
          30.   
          31.     } catch (IOException exception) {   
          32.         log.error(exception);   
          33.         throw exception;   
          34.     }   
          35. }  

           

          可以看出,json插件的功能(在處理json上),就是將invocation中的action對象轉化成json對象,再輸出到頁面上.
              在上面的應用上來看,用json插件必須注冊json的resultType,而且,返回類型一定是json,也就是說,你必須給每一個json調用定義一個json的返回類型,而實際上這個類型就是轉向jsonResult上,和一般的調用不一樣,它不返回任何視圖.即json這個返回類型感覺上完全是多余的,除了標明是json返回之外沒有其他作用.而且對于解析來說,它是解析一個action對象,所就是說,如果你的action是富對象,里面有很多的屬性,而實際你需要的不多時,那這個解析就完全失去了作用了.
               話說這么多,改進方法,從根本上來說,就是改進這種解析機制.是不解析當前的action對象,而是像spring一樣,解析只需要解析的對象一樣.如返回a,就只需要解析a.查看上面的executeResult()方法,其中對result和resultCode進行了處理.其中有這么一句

          Java代碼 復制代碼
          1. if (result != null) {   
          2.                 result.execute(this);   
          3.             } else if (resultCode != null && !Action.NONE.equals(resultCode)) {   
          4.                 throw new ConfigurationException("No result defined for action " + getAction().getClass().getName()    
          5.                         + " and result " + getResultCode(), proxy.getConfig());   
          6.             } else {   
          7.                 if (LOG.isDebugEnabled()) {   
          8.                     LOG.debug("No result returned for action "+getAction().getClass().getName()+" at "+proxy.getConfig().getLocation());   
          9.                 }   
          10.             }  

           

          第一句,也就是一般我們進行處理的地方,調用result進行最后的處理.而第二句,則是對resultCode進行判斷,而第三句什么都不做.所以,對于其他的處理,可以從resultCode下手.方法有兩種,一是返回無類型,即void類型,二是返回Action.NONE.當是這兩種類型的時候,struts2就不會對result進行主動處理了.詳細可參見struts2對Action.NONE的說明,如下:


          Java代碼 復制代碼
          1. /**  
          2.      * The action execution was successful but do not  
          3.      * show a view. This is useful for actions that are  
          4.      * handling the view in another fashion like redirect.  
          5.      */  
          6.     public static final String NONE = "none";  

           

           

           即我們只需要在action方法中,處理ajax調用,而返回void或者"none"就行了.參考方法調用就如下:


          Java代碼 復制代碼
          1. public void showCategoryListAjax2() throws Exception {   
          2.         this.category = this.service.getCategory(this.id);   
          3.         /** 如果該類別為空 **/  
          4.         if(this.category == null) {   
          5.             AjaxResponse.sendAjaxText(null, ServletActionContext.getResponse());   
          6.             return;   
          7.         }   
          8.   
          9.         this.categoryList = this.service.getCategoryListBySuperCategory(this.category);   
          10.   
          11.         AjaxResponse.sendAjaxText(this.categoryList, ServletActionContext.getResponse());   
          12.     }  

           

           

           上面的返回類別是一種取巧的方法,更好的方法是返回Action.NONE.這樣在struts.xml配置就像這樣:

          Java代碼 復制代碼
          1. <action name="taa" class="articleAction" method="topArticleAjax"/>  

           是不是更好,惟一要做的就是在action方法處理json轉換.不過這不是主要問題,在參考json的轉換模式上,我對json轉換進行了改進.在Fly_m的注解上如下:

          Java代碼 復制代碼
          1. public @Interface Fly_m{   
          2.       String name() default "";    
          3.       String format() default "";    
          4.       boolean exclude() default false;    
          5. }  

           支持如json一樣的名稱和format以及exclude(如果為真則不進行轉換).另外,支持排除指定類(如果轉換對象類型和排除類一致而不進行轉換)和指定名稱的對象(如果對象名稱和排除對象名稱一致,則被忽略,只限于對象和map.這對于hibernate這種相互調用的持久層對象最好了).如對象a.b.c的形式.默認的json轉換是完全轉換,在json插件上,如果在b上設定json(exclude),則無論如何b都不會被轉換,這種方法太過固定,不支持臨時配置.而在改進中,只需要加一個"a.b"形式的excludeProperties參數就可以了.如果想轉換時,把參數去掉就行了.相應的轉換方法如下:

          Java代碼 復制代碼
          1. public static String convertToJson(Object obj, String... excludeProperties) {   
          2.         return convertToJson(obj, nullnull, excludeProperties);   
          3.     }  

            

          Java代碼 復制代碼
          1. public static String convertToJson(Object obj, Format format, Class[] excludeClasses, String... excludeProperties) {   
          2.         JsonHandle jsonHandle = new JsonHandle();   
          3.         jsonHandle.excludeProperties = excludeProperties;   
          4.         jsonHandle.excludeClasses = excludeClasses;   
          5.         if(format != null)   
          6.             jsonHandle.defaultFormat = format;   
          7.         return jsonHandle.convertToJson(null0, obj);   
          8.     }  

            而在jsonHandle中是這樣處理的.

          Java代碼 復制代碼
          1. public String convertToJson(String s, int depth, Object obj) {   
          2.             if(obj == null || obj.getClass().getName().indexOf("$$EnhancerByCGLIB$$") != -1 || contains(excludeClasses, obj.getClass())) {   
          3.                 sb.append("null");   
          4.             } else if(isSimpleType(obj.getClass())) {   
          5.                 if(obj instanceof Character) {   
          6.                     sb.append("'").append(obj).append("'");   
          7.                 } else {   
          8.                     sb.append(obj);   
          9.                 }   
          10.             } else  
          11.             if(String.class.isAssignableFrom(obj.getClass()) || StringBuffer.class.isAssignableFrom(obj.getClass())   
          12.                     || StringBuilder.class.isAssignableFrom(obj.getClass())) {   
          13.                 sb.append('"');   
          14.                 CharacterIterator it = new StringCharacterIterator(obj.toString());   
          15.   
          16.                 for(char c = it.first(); c != CharacterIterator.DONE; c = it.next()) {   
          17.                     if(c == '"') {   
          18.                         sb.append("\\\"");   
          19.                     } else if(c == '\\') {   
          20.                         sb.append("\\\\");   
          21.                     } else if(c == '/') {   
          22.                         sb.append("\\/");   
          23.                     } else if(c == '\b') {   
          24.                         sb.append("\\b");   
          25.                     } else if(c == '\f') {   
          26.                         sb.append("\\f");   
          27.                     } else if(c == '\n') {   
          28.                         sb.append("\\n");   
          29.                     } else if(c == '\r') {   
          30.                         sb.append("\\r");   
          31.                     } else if(c == '\t') {   
          32.                         sb.append("\\t");   
          33.                     } else if(Character.isISOControl(c)) {   
          34.                         sb.append(unicode(c));   
          35.                     } else {   
          36.                         sb.append(c);   
          37.                     }   
          38.                 }   
          39.   
          40.                 sb.append('"');   
          41.             } else if(obj instanceof Collection) {   
          42.                 boolean hibernateFlag;   
          43.                 try {   
          44.                     ((Collection) obj).size();   
          45.                     hibernateFlag = true;   
          46.                 } catch(Exception ex) {   
          47.                     hibernateFlag = false;   
          48.                 }   
          49.   
          50.                 if(hibernateFlag) {   
          51.                     sb.append("[");   
          52.   
          53.                     for(Iterator iterator = ((Collection) obj).iterator(); iterator.hasNext();) {   
          54.                         convertToJson(s, depth, iterator.next());   
          55.   
          56.                         if(iterator.hasNext()) {   
          57.                             sb.append(",\n");   
          58.                         }   
          59.                     }   
          60.   
          61.                     sb.append("]");   
          62.                 } else {   
          63.                     sb.append("null");   
          64.                 }   
          65.   
          66.             } else if(obj.getClass().isArray()) {   
          67.                 sb.append("[");   
          68.   
          69.                 int max = java.lang.reflect.Array.getLength(obj);   
          70.   
          71.                 for(int i = 0; i < max; i++) {   
          72.                     if(i > 0) {   
          73.                         sb.append(",");   
          74.                     }   
          75.                     convertToJson(s, depth, java.lang.reflect.Array.get(obj, i));   
          76.                 }   
          77.   
          78.                 sb.append("]");   
          79.             } else if(java.util.Map.class.isAssignableFrom(obj.getClass())) {   
          80.                 if(sb.length() > 0 && sb.lastIndexOf(",") != -1) {   
          81.                     sb.insert(sb.lastIndexOf(",") + 1"\n");   
          82.                 }   
          83.                 sb.append("{");   
          84.   
          85.                 for(Map.Entry e : ((Map<?, ?>) obj).entrySet()) {   
          86.                     if(!(e.getKey() instanceof String))   
          87.                         continue;   
          88.                     if(contains(excludeProperties, e.getKey().toString())) {   
          89.                         continue;   
          90.                     }   
          91.                     if(sb.length() > 0 && sb.charAt(sb.length() - 1) == ',' && sb.charAt(sb.length() - 2) == '}') {   
          92.                         sb.insert(sb.length(), "\n");   
          93.                     }   
          94.                     sb.append("\"").append(e.getKey()).append("\"").append(":");   
          95.   
          96.                     if(depth <= DEFAULT_DEPTH) {   
          97.                         convertToJson(add(s, e.getKey().toString()), depth + 1, e.getValue());   
          98.                     } else {   
          99.                         sb.append("undefined");   
          100.                     }   
          101.   
          102.                     sb.append(",");   
          103.                 }   
          104.   
          105.                 if(sb.length() > 3) {   
          106.                     sb.deleteCharAt(sb.length() - 1);   
          107.                 }   
          108.   
          109.                 sb.append("}");   
          110.             } else {   
          111.                 Map map = null;   
          112.                 try {   
          113.                     map = getPropertiesByReflect(this, obj);   
          114.                 } catch(Exception ex) {   
          115.                     ex.printStackTrace();   
          116.                 }   
          117.   
          118.                 convertToJson(s, depth, map);   
          119.             }   
          120.             return sb.toString();   
          121.         }   
          122.     }  

           

           相關的轉換方法都參照了其他的處理方式,具體解析方法請參照附件中的JsonUtils類.

           這篇文章只是說明了一種對于json的一種新的處理方式,并不是對于json插件的不滿,當然我還是喜歡自己的處理方式.加上前面對亂碼的處理,算是對struts2的一種補充吧.

          • JsonUtil.rar (2.1 KB)
          • 描述: JsonUtils.java 源代碼.
          • 下載次數: 53
          posted on 2008-11-20 17:04 蘆葦 閱讀(3037) 評論(0)  編輯  收藏 所屬分類: Struts
          主站蜘蛛池模板: 永清县| 忻州市| 平度市| 永修县| 庆安县| 高唐县| 兴宁市| 沁源县| 宿州市| 潮安县| 扶余县| 蓬莱市| 兴国县| 美姑县| 陇川县| 江孜县| 华坪县| 三原县| 衡南县| 巩义市| 津市市| 巴彦淖尔市| 曲麻莱县| 海晏县| 东安县| 怀宁县| 普兰县| 周口市| 偏关县| 淮北市| 南木林县| 永德县| 汽车| 宿松县| 崇阳县| 水城县| 林芝县| 谢通门县| 页游| 东海县| 文安县|