最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

自己動(dòng)手在Spring-Boot上加強(qiáng)國際化功能的示例

 更新時(shí)間:2018年04月24日 10:00:11   作者:zzzzbw  
這篇文章主要介紹了自己動(dòng)手在Spring-Boot上加強(qiáng)國際化功能的示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧

前言

公司將項(xiàng)目由Struts2轉(zhuǎn)到Springmvc了,由于公司業(yè)務(wù)是境外服務(wù),所以對(duì)國際化功能需求很高。Struts2自帶的國際化功能相對(duì)Springmvc來說更加完善,不過spring很大的特性就是可定定制化性強(qiáng),所以在公司項(xiàng)目移植的到Springmvc的時(shí)候增加了其國際化的功能。特此整理記錄并且完善了一下。

本文主要實(shí)現(xiàn)的功能:

從文件夾中直接加載多個(gè)國際化文件后臺(tái)設(shè)置前端頁面顯示國際化信息的文件利用攔截器和注解自動(dòng)設(shè)置前端頁面顯示國際化信息的文件

注:本文不詳細(xì)介紹怎么配置國際化,區(qū)域解析器等。

實(shí)現(xiàn)

國際化項(xiàng)目初始化

先創(chuàng)建一個(gè)基本的Spring-Boot+thymeleaf+國際化信息(message.properties)項(xiàng)目,如果有需要可以從我的Github下載。

簡(jiǎn)單看一下項(xiàng)目的目錄和文件

其中I18nApplication.java設(shè)置了一個(gè)CookieLocaleResolver,采用cookie來控制國際化的語言。還設(shè)置一個(gè)LocaleChangeInterceptor攔截器來攔截國際化語言的變化。

@SpringBootApplication
@Configuration
public class I18nApplication {
  public static void main(String[] args) {
    SpringApplication.run(I18nApplication.class, args);
  }

  @Bean
  public LocaleResolver localeResolver() {
    CookieLocaleResolver slr = new CookieLocaleResolver();
    slr.setCookieMaxAge(3600);
    slr.setCookieName("Language");//設(shè)置存儲(chǔ)的Cookie的name為Language
    return slr;
  }

  @Bean
  public WebMvcConfigurer webMvcConfigurer() {
    return new WebMvcConfigurer() {
      //攔截器
      @Override
      public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LocaleChangeInterceptor()).addPathPatterns("/**");
      }
    };
  }
}

我們?cè)倏匆幌耯ello.html中寫了什么:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
  <title>Hello World!</title>
</head>
<body>
<h1 th:text="#{i18n_page}"></h1>
<h3 th:text="#{hello}"></h3>
</body>
</html>

現(xiàn)在啟動(dòng)項(xiàng)目并且訪問http://localhost:9090/hello(我在application.properties)中設(shè)置了端口為9090。

由于瀏覽器默認(rèn)的語言是中文,所以他默認(rèn)會(huì)去messages_zh_CN.properties中找,如果沒有就會(huì)去messages.properties中找國際化詞。

然后我們?cè)跒g覽器中輸入http://localhost:9090/hello?locale=en_US,語言就會(huì)切到英文。同樣的如果url后參數(shù)設(shè)置為locale=zh_CH,語言就會(huì)切到中文。

從文件夾中直接加載多個(gè)國際化文件

在我們hello.html頁面中,只有'i18n_page'和'hello'兩個(gè)國際化信息,然而在實(shí)際項(xiàng)目中肯定不會(huì)只有幾個(gè)國際化信息那么少,通常都是成千上百個(gè)的,那我們肯定不能把這么多的國際化信息都放在messages.properties一個(gè)文件中,通常都是把國際化信息分類存放在幾個(gè)文件中。但是當(dāng)項(xiàng)目大了以后,這些國際化文件也會(huì)越來越多,這時(shí)候在application.properties文件中一個(gè)個(gè)的去配置這個(gè)文件也是不方便的,所以現(xiàn)在我們實(shí)現(xiàn)一個(gè)功能自動(dòng)加載制定目錄下所有的國際化文件。

繼承ResourceBundleMessageSource

在項(xiàng)目下創(chuàng)建一個(gè)類繼承ResourceBundleMessageSource或者ReloadableResourceBundleMessageSource,起名為MessageResourceExtension。并且注入到bean中起名為messageSource,這里我們繼承ResourceBundleMessageSource。

@Component("messageSource")
public class MessageResourceExtension extends ResourceBundleMessageSource {
}

注意這里我們的Component名字必須為'messageSource',因?yàn)樵诔跏蓟?code>ApplicationContext的時(shí)候,會(huì)查找bean名為'messageSource'的bean。這個(gè)過程在AbstractApplicationContext.java中,我們看一下源代碼

/**
* Initialize the MessageSource.
* Use parent's if none defined in this context.
*/
protected void initMessageSource() {
  ConfigurableListableBeanFactory beanFactory = getBeanFactory();
  if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {
    this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);
  ...
  }
}
...

在這個(gè)初始化MessageSource的方法中,beanFactory查找注入名為MESSAGE_SOURCE_BEAN_NAME(messageSource)的bean,如果沒有找到,就會(huì)在其父類中查找是否有該名的bean。

實(shí)現(xiàn)文件加載

現(xiàn)在我們可以開始在剛才創(chuàng)建的MessageResourceExtension

中寫加載文件的方法了。

@Component("messageSource")
public class MessageResourceExtension extends ResourceBundleMessageSource {

  private final static Logger logger = LoggerFactory.getLogger(MessageResourceExtension.class);

  /**
   * 指定的國際化文件目錄
   */
  @Value(value = "${spring.messages.baseFolder:i18n}")
  private String baseFolder;

  /**
   * 父MessageSource指定的國際化文件
   */
  @Value(value = "${spring.messages.basename:message}")
  private String basename;

  @PostConstruct
  public void init() {
    logger.info("init MessageResourceExtension...");
    if (!StringUtils.isEmpty(baseFolder)) {
      try {
        this.setBasenames(getAllBaseNames(baseFolder));
      } catch (IOException e) {
        logger.error(e.getMessage());
      }
    }
    //設(shè)置父MessageSource
    
    ResourceBundleMessageSource parent = new ResourceBundleMessageSource();
    parent.setBasename(basename);
    this.setParentMessageSource(parent);
  }

  /**
   * 獲取文件夾下所有的國際化文件名
   *
   * @param folderName 文件名
   * @return
   * @throws IOException
   */
  private String[] getAllBaseNames(String folderName) throws IOException {
    Resource resource = new ClassPathResource(folderName);
    File file = resource.getFile();
    List<String> baseNames = new ArrayList<>();
    if (file.exists() && file.isDirectory()) {
      this.getAllFile(baseNames, file, "");
    } else {
      logger.error("指定的baseFile不存在或者不是文件夾");
    }
    return baseNames.toArray(new String[baseNames.size()]);
  }

  /**
   * 遍歷所有文件
   *
   * @param basenames
   * @param folder
   * @param path
   */
  private void getAllFile(List<String> basenames, File folder, String path) {
    if (folder.isDirectory()) {
      for (File file : folder.listFiles()) {
        this.getAllFile(basenames, file, path + folder.getName() + File.separator);
      }
    } else {
      String i18Name = this.getI18FileName(path + folder.getName());
      if (!basenames.contains(i18Name)) {
        basenames.add(i18Name);
      }

    }
  }

  /**
   * 把普通文件名轉(zhuǎn)換成國際化文件名
   *
   * @param filename
   * @return
   */
  private String getI18FileName(String filename) {
    filename = filename.replace(".properties", "");
    for (int i = 0; i < 2; i++) {
      int index = filename.lastIndexOf("_");
      if (index != -1) {
        filename = filename.substring(0, index);
      }
    }
    return filename;
  }
}

依次解釋一下幾個(gè)方法。

  1. init()方法上有一個(gè)@PostConstruct注解,這會(huì)在MessageResourceExtension類被實(shí)例化之后自動(dòng)調(diào)用init()方法。這個(gè)方法獲取到baseFolder目錄下所有的國際化文件并設(shè)置到basenameSet中。并且設(shè)置一個(gè)ParentMessageSource,這會(huì)在找不到國際化信息的時(shí)候,調(diào)用父MessageSource來查找國際化信息。
  2. getAllBaseNames()方法獲取到baseFolder的路徑,然后調(diào)用getAllFile()方法獲取到該目錄下所有的國際化文件的文件名。
  3. getAllFile()遍歷目錄,如果是文件夾就繼續(xù)遍歷,如果是文件就調(diào)用getI18FileName()把文件名轉(zhuǎn)為'i18n/basename/‘格式的國際化資源名。

所以簡(jiǎn)單來說就是在MessageResourceExtension被實(shí)例化之后,把'i18n'文件夾下的資源文件的名字,加載到Basenames中?,F(xiàn)在來看一下效果。

首先我們?cè)赼pplication.properties文件中添加一個(gè)spring.messages.baseFolder=i18n,這會(huì)把'i18n'這個(gè)值賦值給MessageResourceExtension中的baseFolder。

在啟動(dòng)后看到控制臺(tái)里打印出了init信息,表示被@PostConstruct注解的init()方法已經(jīng)執(zhí)行。

然后我們?cè)賱?chuàng)建兩組國際化信息文件:'dashboard'和'merchant',里面分別只有一個(gè)國際化信息:'dashboard.hello'和'merchant.hello'。

之后再修改一下hello.html文件,然后訪問hello頁面。

...
<body>
<h1>國際化頁面!</h1>
<p th:text="#{hello}"></p>
<p th:text="#{merchant.hello}"></p>
<p th:text="#{dashboard.hello}"></p>
</body>
...

可以看到網(wǎng)頁中加載了'message','dashboard'和'merchant'中的國際化信息,說明我們已經(jīng)成功一次性加載了'i18n'文件夾下的文件。

后臺(tái)設(shè)置前端頁面顯示國際化信息的文件

s剛才那一節(jié)我們成功加載了多個(gè)國際化文件并顯示出了他們的國際化信息。但是'dashboard.properties'中的國際化信息為'dashboard.hello'而'merchant.properties'中的是'merchant.hello',這樣每個(gè)都要寫一個(gè)前綴豈不是很麻煩,現(xiàn)在我想要在'dashboard'和'merchant'的國際化文件中都只寫'hello'但是顯示的是'dashboard'或'merchant'中的國際化信息。

MessageResourceExtension重寫resolveCodeWithoutArguments方法(如果有字符格式化的需求就重寫resolveCode方法)。

@Component("messageSource")
public class MessageResourceExtension extends ResourceBundleMessageSource {
  ...
  public static String I18N_ATTRIBUTE = "i18n_attribute";
  
  @Override
  protected String resolveCodeWithoutArguments(String code, Locale locale) {
    // 獲取request中設(shè)置的指定國際化文件名
    ServletRequestAttributes attr = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
    final String i18File = (String) attr.getAttribute(I18N_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
    if (!StringUtils.isEmpty(i18File)) {
      //獲取在basenameSet中匹配的國際化文件名
      String basename = getBasenameSet().stream()
          .filter(name -> StringUtils.endsWithIgnoreCase(name, i18File))
          .findFirst().orElse(null);
      if (!StringUtils.isEmpty(basename)) {
        //得到指定的國際化文件資源
        ResourceBundle bundle = getResourceBundle(basename, locale);
        if (bundle != null) {
          return getStringOrNull(bundle, code);
        }
      }
    }
    //如果指定i18文件夾中沒有該國際化字段,返回null會(huì)在ParentMessageSource中查找
    return null;
  }
  ...
}

在我們重寫的resolveCodeWithoutArguments方法中,從HttpServletRequest中獲取到‘I18N_ATTRIBUTE'(等下再說這個(gè)在哪里設(shè)置),這個(gè)對(duì)應(yīng)我們想要顯示的國際化文件名,然后我們?cè)?code>BasenameSet中查找該文件,再通過getResourceBundle獲取到資源,最后再getStringOrNull獲取到對(duì)應(yīng)的國際化信息。

現(xiàn)在我們到我們的HelloController里加兩個(gè)方法。

@Controller
public class HelloController {

  @GetMapping("/hello")
  public String index(HttpServletRequest request) {
    request.setAttribute(MessageResourceExtension.I18N_ATTRIBUTE, "hello");
    return "system/hello";
  }

  @GetMapping("/dashboard")
  public String dashboard(HttpServletRequest request) {
    request.setAttribute(MessageResourceExtension.I18N_ATTRIBUTE, "dashboard");
    return "dashboard";
  }

  @GetMapping("/merchant")
  public String merchant(HttpServletRequest request) {
    request.setAttribute(MessageResourceExtension.I18N_ATTRIBUTE, "merchant");
    return "merchant";
  }
}

看到我們?cè)诿總€(gè)方法中都設(shè)置一個(gè)對(duì)應(yīng)的'I18N_ATTRIBUTE',這會(huì)在每次請(qǐng)求中設(shè)置對(duì)應(yīng)的國際化文件,然后在MessageResourceExtension中獲取。

這時(shí)我們看一下我們的國際化文件,我們可以看到所有關(guān)鍵字都是'hello',但是信息卻不同。

同時(shí)新增兩個(gè)html文件分別是'dashboard.html'和'merchant.html',里面只有一個(gè)'hello'的國際化信息和用于區(qū)分的標(biāo)題。

<!-- 這是hello.html -->
<body>
<h1>國際化頁面!</h1>
<p th:text="#{hello}"></p>
</body>
<!-- 這是dashboard.html -->
<body>
<h1>國際化頁面(dashboard)!</h1>
<p th:text="#{hello}"></p>
</body>
<!-- 這是merchant.html -->
<body>
<h1>國際化頁面(merchant)!</h1>
<p th:text="#{hello}"></p>
</body>

這時(shí)我們啟動(dòng)項(xiàng)目看一下。

可以看到雖然在每個(gè)頁面的國際化詞都是'hello',但是我們?cè)趯?duì)應(yīng)的頁面顯示了我們想要顯示的信息。

利用攔截器和注解自動(dòng)設(shè)置前端頁面顯示國際化信息的文件

雖然已經(jīng)可以指定對(duì)應(yīng)的國際化信息,但是這樣要在每個(gè)controller里的HttpServletRequest中設(shè)置國際化文件實(shí)在太麻煩了,所以現(xiàn)在我們實(shí)現(xiàn)自動(dòng)判定來顯示對(duì)應(yīng)的文件。

首先我們創(chuàng)建一個(gè)注解,這個(gè)注解可以放在類上或者方法上。

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface I18n {
  /**
   * 國際化文件名
   */
  String value();
}

然后我們把這個(gè)創(chuàng)建的I18n 注解放在剛才的Controller方法中,為了顯示他的效果,我們?cè)賱?chuàng)建一個(gè)ShopControllerUserController,同時(shí)也創(chuàng)建對(duì)應(yīng)的'shop'和'user'的國際化文件,內(nèi)容也都是一個(gè)'hello'。

@Controller
public class HelloController {
  @GetMapping("/hello")
  public String index() {
    return "system/hello";
  }

  @I18n("dashboard")
  @GetMapping("/dashboard")
  public String dashboard() {
    return "dashboard";
  }

  @I18n("merchant")
  @GetMapping("/merchant")
  public String merchant() {
    return "merchant";
  }
}
@I18n("shop")
@Controller
public class ShopController {
  @GetMapping("shop")
  public String shop() {
    return "shop";
  }
}
@Controller
public class UserController {
  @GetMapping("user")
  public String user() {
    return "user";
  }
}

我們把I18n注解分別放在HelloController下的dashboardmerchant方法下,和ShopController類上。并且去除了原來dashboardmerchant方法下設(shè)置‘I18N_ATTRIBUTE'的語句。

準(zhǔn)備工作都做好了,現(xiàn)在看看如何實(shí)現(xiàn)根據(jù)這些注解自動(dòng)的指定國際化文件。

public class MessageResourceInterceptor implements HandlerInterceptor {
  @Override
  public void postHandle(HttpServletRequest req, HttpServletResponse rep, Object handler, ModelAndView modelAndView) {

    // 在方法中設(shè)置i18路徑
    if (null != req.getAttribute(MessageResourceExtension.I18N_ATTRIBUTE)) {
      return;
    }

    HandlerMethod method = (HandlerMethod) handler;
    // 在method上注解了i18
    I18n i18nMethod = method.getMethodAnnotation(I18n.class);
    if (null != i18nMethod) {
      req.setAttribute(MessageResourceExtension.I18N_ATTRIBUTE, i18nMethod.value());
      return;
    }

    // 在Controller上注解了i18
    I18n i18nController = method.getBeanType().getAnnotation(I18n.class);
    if (null != i18nController) {
      req.setAttribute(MessageResourceExtension.I18N_ATTRIBUTE, i18nController.value());
      return;
    }

    // 根據(jù)Controller名字設(shè)置i18
    String controller = method.getBeanType().getName();
    int index = controller.lastIndexOf(".");
    if (index != -1) {
      controller = controller.substring(index + 1, controller.length());
    }
    index = controller.toUpperCase().indexOf("CONTROLLER");
    if (index != -1) {
      controller = controller.substring(0, index);
    }
    req.setAttribute(MessageResourceExtension.I18N_ATTRIBUTE, controller);
  }

  @Override
  public boolean preHandle(HttpServletRequest req, HttpServletResponse rep, Object handler) {
    // 在跳轉(zhuǎn)到該方法先清除request中的國際化信息
    req.removeAttribute(MessageResourceExtension.I18N_ATTRIBUTE);
    return true;
  }
}

簡(jiǎn)單講解一下這個(gè)攔截器。

首先,如果request中已經(jīng)有'I18N_ATTRIBUTE',說明在Controller的方法中指定設(shè)置了,就不再判斷。

然后判斷一下進(jìn)入攔截器的方法上有沒有I18n的注解,如果有就設(shè)置'I18N_ATTRIBUTE'到request中并退出攔截器,如果沒有就繼續(xù)。

再判斷進(jìn)入攔截的類上有沒有I18n的注解,如果有就設(shè)置'I18N_ATTRIBUTE'到request中并退出攔截器,如果沒有就繼續(xù)。

最后假如方法和類上都沒有I18n的注解,那我們可以根據(jù)Controller名自動(dòng)設(shè)置指定的國際化文件,比如'UserController'那么就會(huì)去找'user'的國際化文件。

現(xiàn)在我們?cè)龠\(yùn)行一下看看效果,看到每個(gè)鏈接都顯示的他們對(duì)應(yīng)的國際化信息里的內(nèi)容。

最后

剛才完成了我們整個(gè)國際化增強(qiáng)的基本功能,最后我把全部代碼整理了一下,并且整合了bootstrap4來展示了一下功能的實(shí)現(xiàn)效果。

詳細(xì)的代碼可以看我Github上Spring-Boot-I18n-Pro的代碼

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • Java之JFrame輸出Helloworld實(shí)例

    Java之JFrame輸出Helloworld實(shí)例

    這篇文章主要介紹了Java之JFrame輸出Helloworld的方法,以輸出Helloworld的實(shí)例分析了JFrame的簡(jiǎn)單入門技巧,需要的朋友可以參考下
    2015-02-02
  • 一文帶你搞懂什么是BIO

    一文帶你搞懂什么是BIO

    BIO英文全名是 blocking IO,也叫做 阻塞IO,是最容易理解、最容易實(shí)現(xiàn)的IO工作方式,本文就來通過一些簡(jiǎn)單的示例為大家講講什么是BIO吧
    2023-06-06
  • Springboot如何解決前端請(qǐng)求跨域的問題

    Springboot如何解決前端請(qǐng)求跨域的問題

    這篇文章主要介紹了Springboot如何解決前端請(qǐng)求跨域的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-07-07
  • JavaWeb 中Cookie實(shí)現(xiàn)記住密碼的功能示例

    JavaWeb 中Cookie實(shí)現(xiàn)記住密碼的功能示例

    cookie是一種WEB服務(wù)器通過瀏覽器在訪問者的硬盤上存儲(chǔ)信息的手段。Cookie的目的就是為用戶帶來方便,為網(wǎng)站帶來增值。這篇文章主要介紹了JavaWeb 中Cookie實(shí)現(xiàn)記住密碼的功能示例,需要的朋友可以參考下
    2017-06-06
  • SpringBoot創(chuàng)建RSocket服務(wù)器的全過程記錄

    SpringBoot創(chuàng)建RSocket服務(wù)器的全過程記錄

    RSocket應(yīng)用層協(xié)議支持 Reactive Streams語義, 例如:用RSocket作為HTTP的一種替代方案。這篇文章主要給大家介紹了關(guān)于SpringBoot創(chuàng)建RSocket服務(wù)器的相關(guān)資料,需要的朋友可以參考下
    2021-05-05
  • 淺談Java中的參數(shù)傳遞問題

    淺談Java中的參數(shù)傳遞問題

    這篇文章主要介紹了Java中的參數(shù)傳遞問題,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • 關(guān)于@Scheduled參數(shù)及cron表達(dá)式解釋

    關(guān)于@Scheduled參數(shù)及cron表達(dá)式解釋

    這篇文章主要介紹了關(guān)于@Scheduled參數(shù)及cron表達(dá)式解釋,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • SpringBoot整合EasyExcel實(shí)現(xiàn)導(dǎo)入導(dǎo)出數(shù)據(jù)

    SpringBoot整合EasyExcel實(shí)現(xiàn)導(dǎo)入導(dǎo)出數(shù)據(jù)

    這篇文章主要為大家詳細(xì)介紹了如何使用Vue、SpringBoot和EasyExcel實(shí)現(xiàn)導(dǎo)入導(dǎo)出數(shù)據(jù)功能,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2022-05-05
  • MyBatis-Plus工具使用之EntityWrapper解析

    MyBatis-Plus工具使用之EntityWrapper解析

    這篇文章主要介紹了MyBatis-Plus工具使用之EntityWrapper解析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • Springboot項(xiàng)目中如何讓非Spring管理的類獲得一個(gè)注入的Bean

    Springboot項(xiàng)目中如何讓非Spring管理的類獲得一個(gè)注入的Bean

    這篇文章主要介紹了Springboot項(xiàng)目中如何讓非Spring管理的類獲得一個(gè)注入的Bean問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-12-12

最新評(píng)論

乐至县| 利川市| 奉贤区| 灵璧县| 祁阳县| 灵川县| 古浪县| 余干县| 登封市| 蒲江县| 高陵县| 香港 | 金沙县| 瑞安市| 佳木斯市| 安丘市| 武义县| 信丰县| 沭阳县| 阜新| 黔江区| 龙川县| 赣榆县| 明光市| 板桥市| 会同县| 循化| 社旗县| 密云县| 娄底市| 三都| 常宁市| 大城县| 旬邑县| 土默特左旗| 怀仁县| 保山市| 阿城市| 襄樊市| 澄江县| 房山区|