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

Springboot中使用Redisson+AOP+自定義注解實(shí)現(xiàn)訪問限流與黑名單攔截

 更新時間:2024年02月29日 09:53:09   作者:牽著貓散步的鼠鼠  
本文主要介紹了Springboot中使用Redisson+AOP+自定義注解實(shí)現(xiàn)訪問限流與黑名單攔截,包含針對用戶IP限流,整個接口的訪問限流,以及對某個參數(shù)字段的限流,并且支持請求限流后處理回調(diào),感興趣的可以了解一下

前言

在開發(fā)高并發(fā)系統(tǒng)時有三把利器用來保護(hù)系統(tǒng):緩存、降級和限流。  限流的目的是通過對并發(fā)訪問請求進(jìn)行限速或者一個時間窗口內(nèi)的的請求數(shù)量進(jìn)行限速來保護(hù)系統(tǒng),一旦達(dá)到限制速率則可以拒絕服務(wù)、排隊(duì)或等待

我們上次講解了如何使用Sentinel來實(shí)現(xiàn)服務(wù)限流,今天我們來講解下如何使用Redisson+AOP+自定義注解+反射優(yōu)雅的實(shí)現(xiàn)服務(wù)限流,本文講解的限流實(shí)現(xiàn)支持針對用戶IP限流,整個接口的訪問限流,以及對某個參數(shù)字段的限流,并且支持請求限流后處理回調(diào)

1.導(dǎo)入Redisson

引入依賴

我們首先導(dǎo)入Redisson所需要的依賴,我們這里的springboot版本為2.7.12

<dependency>
   <groupId>org.redisson</groupId>
   <artifactId>redisson-spring-boot-starter</artifactId>
   <version>3.23.4</version>
</dependency>

編寫配置

# Redisson客戶端
redis:
  sdk:
    config:
      host: redis服務(wù)IP
      port: 6379
      password: redis密碼,沒有可刪掉這行
      pool-size: 10
      min-idle-size: 5
      idle-timeout: 30000
      connect-timeout: 5000
      retry-attempts: 3
      retry-interval: 1000
      ping-interval: 60000
      keep-alive: true

聲明Redisson客戶端Bean

配置映射類RedisCientConfigProperties

@Data
@ConfigurationProperties(prefix = "redis.sdk.config", ignoreInvalidFields = true)
public class RedisCientConfigProperties {
    /** host:ip */
    private String host;
    /** 端口 */
    private int port;
    /** 賬密 */
    private String password;
    /** 設(shè)置連接池的大小,默認(rèn)為64 */
    private int poolSize = 64;
    /** 設(shè)置連接池的最小空閑連接數(shù),默認(rèn)為10 */
    private int minIdleSize = 10;
    /** 設(shè)置連接的最大空閑時間(單位:毫秒),超過該時間的空閑連接將被關(guān)閉,默認(rèn)為10000 */
    private int idleTimeout = 10000;
    /** 設(shè)置連接超時時間(單位:毫秒),默認(rèn)為10000 */
    private int connectTimeout = 10000;
    /** 設(shè)置連接重試次數(shù),默認(rèn)為3 */
    private int retryAttempts = 3;
    /** 設(shè)置連接重試的間隔時間(單位:毫秒),默認(rèn)為1000 */
    private int retryInterval = 1000;
    /** 設(shè)置定期檢查連接是否可用的時間間隔(單位:毫秒),默認(rèn)為0,表示不進(jìn)行定期檢查 */
    private int pingInterval = 0;
    /** 設(shè)置是否保持長連接,默認(rèn)為true */
    private boolean keepAlive = true;
}
Configuration
@EnableConfigurationProperties(RedisCientConfigProperties.class)
public class RedisClientConfig {

    @Bean("redissonClient")
    public RedissonClient redissonClient(ConfigurableApplicationContext applicationContext, RedisCientConfigProperties properties) {
        Config config = new Config();
        // 根據(jù)需要可以設(shè)定編解碼器;https://github.com/redisson/redisson/wiki/4.-%E6%95%B0%E6%8D%AE%E5%BA%8F%E5%88%97%E5%8C%96
        // config.setCodec(new RedisCodec());

        config.useSingleServer()
                .setAddress("redis://" + properties.getHost() + ":" + properties.getPort())
               .setPassword(properties.getPassword())
                .setConnectionPoolSize(properties.getPoolSize())
                .setConnectionMinimumIdleSize(properties.getMinIdleSize())
                .setIdleConnectionTimeout(properties.getIdleTimeout())
                .setConnectTimeout(properties.getConnectTimeout())
                .setRetryAttempts(properties.getRetryAttempts())
                .setRetryInterval(properties.getRetryInterval())
                .setPingConnectionInterval(properties.getPingInterval())
                .setKeepAlive(properties.isKeepAlive())
        ;

        RedissonClient redissonClient = Redisson.create(config);

        // 注冊消息發(fā)布訂閱主題Topic
        // 找到所有實(shí)現(xiàn)了Redisson中MessageListener接口的bean名字
        String[] beanNamesForType = applicationContext.getBeanNamesForType(MessageListener.class);
        for (String beanName : beanNamesForType) {
            // 通過bean名字獲取到監(jiān)聽bean
            MessageListener bean = applicationContext.getBean(beanName, MessageListener.class);

            Class<? extends MessageListener> beanClass = bean.getClass();

            // 如果bean的注解里包含我們的自定義注解RedisTopic.class,則以RedisTopic注解的值作為name將該bean注冊到bean工廠,方便在別處注入
            if (beanClass.isAnnotationPresent(RedisTopic.class)) {
                RedisTopic redisTopic = beanClass.getAnnotation(RedisTopic.class);

                RTopic topic = redissonClient.getTopic(redisTopic.topic());
                topic.addListener(String.class, bean);

                ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
                beanFactory.registerSingleton(redisTopic.topic(), topic);
            }
        }

        return redissonClient;
    }

    static class RedisCodec extends BaseCodec {

        private final Encoder encoder = in -> {
            ByteBuf out = ByteBufAllocator.DEFAULT.buffer();
            try {
                ByteBufOutputStream os = new ByteBufOutputStream(out);
                JSON.writeJSONString(os, in, SerializerFeature.WriteClassName);
                return os.buffer();
            } catch (IOException e) {
                out.release();
                throw e;
            } catch (Exception e) {
                out.release();
                throw new IOException(e);
            }
        };

        private final Decoder<Object> decoder = (buf, state) -> JSON.parseObject(new ByteBufInputStream(buf), Object.class);

        @Override
        public Decoder<Object> getValueDecoder() {
            return decoder;
        }

        @Override
        public Encoder getValueEncoder() {
            return encoder;
        }

    }

}

2.自定義注解

我們這里自定義一個注解來作為后續(xù)AOP切面編程的切點(diǎn)

根據(jù)注解Key屬性的值,我們會有如下情況

all:針對整個接口限流

request_ip:針對各個用戶的訪問IP限流

其他str:根據(jù)參數(shù)作為標(biāo)識符限流,比如我這里key=userid,那么我會根據(jù)參數(shù)中的userid來限流

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface AccessInterceptor {

    /** 用哪個字段作為攔截標(biāo)識符,配置all則是對整個接口限流,配置request_ip,
     * 則是對訪問ip限流,配置其他str,則會到參數(shù)中尋找對應(yīng)名稱的屬性值(包括對象內(nèi)部屬性) */
    String key() default "all";

    /** 限制頻次(每秒請求次數(shù)) */
    long permitsPerSecond();

    /** 黑名單攔截(多少次限制后加入黑名單)0 不限制 */
    double blacklistCount() default 0;

    /** 攔截后的執(zhí)行方法 */
    String fallbackMethod();

}

3.AOP切面編程

導(dǎo)入依賴

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

編寫AOP限流代碼

我們doRouter切面函數(shù)以AccessInterceptor注解為切點(diǎn),根據(jù)注解的各類配置來執(zhí)行整個限流過程。

我們通過使用Redisson的RRateLimiter限流器,基于令牌桶實(shí)現(xiàn)訪問限流,并對已經(jīng)限流的訪問記錄黑名單次數(shù),超過設(shè)置的黑名單閾值就會被加入黑名單中,較長時間無法訪問

代碼較長,為了縮短篇幅就一次性放上來了,各處已經(jīng)打上了詳細(xì)注解,若有疑問可評論區(qū)留言。

@Slf4j
@Aspect
public class RateLimiterAOP {
    // 注入我們聲明的redisson客戶端
    @Resource
    private RedissonClient redissonClient;
    // 限流RateLimiter緩存前綴
    private static final String rateLimiterName = "test:RateLimiter:";
    // 黑名單原子計數(shù)器緩存前綴
    private static final String blacklistPrefix = "test:RateBlockList:";

    @Around("@annotation(accessInterceptor)")
    public Object doRouter(ProceedingJoinPoint jp, AccessInterceptor accessInterceptor) throws Throwable {
        // 獲取注解配置的字段key
        String key = accessInterceptor.key();
        if (StringUtils.isBlank(key)) {
            log.error("限流RateLimiter注解中的 Key 屬性為空!");
            throw new RuntimeException("RateLimiter注解中的 Key 屬性為空!");
        }
        log.info("限流攔截關(guān)鍵字為 {}", key);

        // 根據(jù)key獲取攔截標(biāo)識符字段
        String keyAttr = getAttrValue(key, jp.getArgs());

        // 黑名單攔截,非法訪問次數(shù)超過黑名單閾值
        if (!"all".equals(keyAttr) && accessInterceptor.blacklistCount() != 0 && redissonClient.getAtomicLong(blacklistPrefix + keyAttr).get() > accessInterceptor.blacklistCount()) {
            log.info("限流-黑名單攔截:{}", keyAttr);
            return fallbackMethodResult(jp, accessInterceptor.fallbackMethod());
        }

        // 獲取限流器 -> Redisson RRateLimiter
        RRateLimiter rateLimiter = redissonClient.getRateLimiter(rateLimiterName + keyAttr);

        if (!rateLimiter.isExists()) {
            // 創(chuàng)建令牌桶數(shù)據(jù)模型,單位時間內(nèi)產(chǎn)生多少令牌
            rateLimiter.trySetRate(RateType.PER_CLIENT,1, accessInterceptor.permitsPerSecond(), RateIntervalUnit.MINUTES);
        }

        // 限流判斷,沒有獲取到令牌,超出頻率
        if (!rateLimiter.tryAcquire()) {
            // 如果開啟了黑名單限制,那么就記錄當(dāng)前的非法訪問次數(shù)
            if (accessInterceptor.blacklistCount() != 0) {
                RAtomicLong atomicLong = redissonClient.getAtomicLong(blacklistPrefix + keyAttr);
                atomicLong.incrementAndGet(); // 原子自增
                atomicLong.expire(24, TimeUnit.HOURS); // 刷新黑名單原子計數(shù)器器過期時間為24小時
            }
            log.info("限流-頻率過高攔截:{}", keyAttr);
            return fallbackMethodResult(jp, accessInterceptor.fallbackMethod());
        }

        // 返回結(jié)果
        return jp.proceed();
    }

    /**
     * 調(diào)用用戶配置的回調(diào)方法,使用反射機(jī)制實(shí)現(xiàn)。
     */
    private Object fallbackMethodResult(JoinPoint jp, String fallbackMethod) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        // 通過JoinPoint對象獲取方法的簽名(Signature)
        Signature sig = jp.getSignature();
        // 將方法簽名轉(zhuǎn)換為MethodSignature對象,以便獲取方法的詳細(xì)信息
        MethodSignature methodSignature = (MethodSignature) sig;
        // 獲取到具體的方法對象,通過方法名和參數(shù)(所以回調(diào)函數(shù)參數(shù)一定要和原方法一致)
        Method method = jp.getTarget().getClass().getMethod(fallbackMethod, methodSignature.getParameterTypes());
        // 調(diào)用目標(biāo)對象的方法,并傳入當(dāng)前對象(jp.getThis())和方法的參數(shù)(jp.getArgs())。
        return method.invoke(jp.getThis(), jp.getArgs());
    }

    /**
     * 根據(jù)JoinPoint對象獲取其所代表的方法對象
     */
    private Method getMethod(JoinPoint jp) throws NoSuchMethodException {
        Signature sig = jp.getSignature();
        MethodSignature methodSignature = (MethodSignature) sig;
        return jp.getTarget().getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
    }

    /**
     * 實(shí)際根據(jù)自身業(yè)務(wù)調(diào)整,主要是為了獲取通過某個值做攔截
     */
    public String getAttrValue(String attr, Object[] args) {
        String filedValue = null;

        for (Object arg : args) {
            try {
                // 找到HttpServletRequest對象來獲取請求IP地址(如果是根據(jù)IP攔截的話)
                if ("request_ip".equals(attr) && arg instanceof HttpServletRequest) {
                    HttpServletRequest request = (HttpServletRequest) arg;
                    filedValue = IPUtils.getIpAddr(request);
                }
                // 找到了值,返回
                if (StringUtils.isNotBlank(filedValue)) {
                    break;
                }
                // fix: 使用lombok時,uId這種字段的get方法與idea生成的get方法不同,會導(dǎo)致獲取不到屬性值,改成反射獲取解決
                filedValue = String.valueOf(this.getValueByName(arg, attr));
            } catch (Exception e) {
                log.error("獲取路由屬性值失敗 attr:{}", attr, e);
            }
        }
        return filedValue;
    }

    /**
     * 獲取對象的特定屬性值(反射)
     *
     * @param item 對象
     * @param name 屬性名
     * @return 屬性值
     * @author tang
     */
    private Object getValueByName(Object item, String name) {
        try {
            // 獲取指定對象中對應(yīng)屬性名的Field對象
            Field field = getFieldByName(item, name);
            // 獲取到的Field對象為null,表示屬性不存在,直接返回null。
            if (field == null) {
                return null;
            }
            // 將Field對象設(shè)置為可訪問,以便獲取私有屬性的值。
            field.setAccessible(true);
            // 獲取屬性值,并將其賦值給變量o。
            Object o = field.get(item);
            // 將Field對象設(shè)置為不可訪問,以保持對象的封裝性。
            field.setAccessible(false);
            return o;
        } catch (IllegalAccessException e) {
            return null;
        }
    }

    /**
     * 根據(jù)名稱獲取方法,該方法同時兼顧繼承類獲取父類的屬性
     *
     * @param item 對象
     * @param name 屬性名
     * @return 該屬性對應(yīng)方法
     * @author tang
     */
    private Field getFieldByName(Object item, String name) {
        try {
            Field field;
            try {
                // 獲取指定對象中對應(yīng)屬性名的Field對象。
                field = item.getClass().getDeclaredField(name);
            } catch (NoSuchFieldException e) {
                // 沒有找到,拋出NoSuchFieldException異常,嘗試獲取父類中對應(yīng)屬性名的Field對象
                field = item.getClass().getSuperclass().getDeclaredField(name);
            }
            return field;
        } catch (NoSuchFieldException e) {
            // 父類也沒找到對應(yīng)屬性名的Field對象,寄,返回null
            return null;
        }
    }

}

以上代碼用到了自己寫的一個工具類IPUtils來獲取請求的IP地址,內(nèi)容如下

public class IPUtils {
    private static Logger logger = LoggerFactory.getLogger(IPUtils.class);
    private static final String IP_UTILS_FLAG = ",";
    private static final String UNKNOWN = "unknown";
    private static final String LOCALHOST_IP = "0:0:0:0:0:0:0:1";
    private static final String LOCALHOST_IP1 = "127.0.0.1";

    /**
     * 獲取IP地址
     * <p>
     * 使用Nginx等反向代理軟件, 則不能通過request.getRemoteAddr()獲取IP地址
     * 如果使用了多級反向代理的話,X-Forwarded-For的值并不止一個,而是一串IP地址,X-Forwarded-For中第一個非unknown的有效IP字符串,則為真實(shí)IP地址
     */
    public static String getIpAddr(HttpServletRequest request) {
        String ip = null;
        try {
            //以下兩個獲取在k8s中,將真實(shí)的客戶端IP,放到了x-Original-Forwarded-For。而將WAF的回源地址放到了 x-Forwarded-For了。
            ip = request.getHeader("X-Original-Forwarded-For");
            if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getHeader("X-Forwarded-For");
            }
            //獲取nginx等代理的ip
            if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getHeader("x-forwarded-for");
            }
            if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getHeader("Proxy-Client-IP");
            }
            if (StringUtils.isEmpty(ip) || ip.length() == 0 || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getHeader("WL-Proxy-Client-IP");
            }
            if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_CLIENT_IP");
            }
            if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getHeader("HTTP_X_FORWARDED_FOR");
            }
            //兼容k8s集群獲取ip
            if (StringUtils.isEmpty(ip) || UNKNOWN.equalsIgnoreCase(ip)) {
                ip = request.getRemoteAddr();
                if (LOCALHOST_IP1.equalsIgnoreCase(ip) || LOCALHOST_IP.equalsIgnoreCase(ip)) {
                    //根據(jù)網(wǎng)卡取本機(jī)配置的IP
                    InetAddress iNet = null;
                    try {
                        iNet = InetAddress.getLocalHost();
                    } catch (UnknownHostException e) {
                        logger.error("getClientIp error: {}", e);
                    }
                    ip = iNet.getHostAddress();
                }
            }
        } catch (Exception e) {
            logger.error("IPUtils ERROR ", e);
        }
        //使用代理,則獲取第一個IP地址
        if (!StringUtils.isEmpty(ip) && ip.indexOf(IP_UTILS_FLAG) > 0) {
            ip = ip.substring(0, ip.indexOf(IP_UTILS_FLAG));
        }

        return ip;
    }

}

4.接口使用自定義注解實(shí)現(xiàn)限流

使用自定義限流注解

比如我在用戶controller層的登錄接口上使用注解,key為request_ip,表示根據(jù)用戶IP限流,回調(diào)函數(shù)為fallbackMethod,每分鐘訪問限制10次

    @PostMapping(value = "/login")
    @AccessInterceptor(key = "request_ip", fallbackMethod = "loginErr", permitsPerSecond = 1L, blacklistCount = 10)
    public Response<String> doLogin(@RequestParam String code, HttpServletRequest request){

綁定限流回調(diào)函數(shù)

這里需要注意的是,回調(diào)函數(shù)的參數(shù)必須和你使用限流注解的方法參數(shù)一致,否則報對應(yīng)方法找不到的錯誤(因?yàn)檫@里是通過反射機(jī)制找到回調(diào)函數(shù)執(zhí)行的)

public Response<String> loginErr(String code, HttpServletRequest request) {
        System.out.println("限流觸發(fā)回調(diào),參數(shù)信息:" + code);
        return Response.<String>builder()
                .code(Constants.ResponseCode.FREQUENCY_LIMITED.getCode())
                .info(Constants.ResponseCode.FREQUENCY_LIMITED.getInfo())
                .data(code)
                .build();
    }

總結(jié)

以上通過Redission+自定義注解+AOP+反射實(shí)現(xiàn)了對不同標(biāo)識符的限流和黑名單攔截,并且可以綁定限流回調(diào)函數(shù)來處理限流后的邏輯,代碼篇幅較長,各位小伙伴也可以嘗試?yán)^續(xù)優(yōu)化一下這里的設(shè)計,減少request_ip這種魔法值(實(shí)在懶得改了),更多相關(guān)Springboot 訪問限流與黑名單攔截內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 用遞歸查找有序二維數(shù)組的方法詳解

    用遞歸查找有序二維數(shù)組的方法詳解

    本篇文章是對用遞歸查找有序二維數(shù)組的方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
    2013-06-06
  • java 線程池的實(shí)現(xiàn)方法

    java 線程池的實(shí)現(xiàn)方法

    在本篇文章里小編給大家整理了關(guān)于java 線程池的實(shí)現(xiàn)方法,有興趣的朋友們可以學(xué)習(xí)參考下。
    2020-02-02
  • SpringCloud Zuul過濾器和谷歌Gauva實(shí)現(xiàn)限流

    SpringCloud Zuul過濾器和谷歌Gauva實(shí)現(xiàn)限流

    這篇文章主要介紹了SpringCloud Zuul過濾器和谷歌Gauva實(shí)現(xiàn)限流,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-03-03
  • 用java等語言仿360首頁拼音輸入全模糊搜索和自動換膚

    用java等語言仿360首頁拼音輸入全模糊搜索和自動換膚

    這篇文章主要為大家詳細(xì)介紹了仿360首頁支持拼音輸入全模糊搜索和自動換膚的相關(guān)資料,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-10-10
  • 詳談Feign的配置類是如何生效的

    詳談Feign的配置類是如何生效的

    這篇文章主要介紹了Feign的配置類是如何生效的,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • IDEA中設(shè)置背景顏色的步驟

    IDEA中設(shè)置背景顏色的步驟

    在IntelliJ IDEA中,用戶可以通過訪問【Settings】或【Preferences】菜單,進(jìn)入【Editor】>【ColorScheme】選項(xiàng)來選擇和調(diào)整編輯區(qū)域的顏色方案,此外,通過【Appearance & Behavior】>【Appearance】選項(xiàng)
    2024-09-09
  • Java中使用正則表達(dá)式的一個簡單例子及常用正則分享

    Java中使用正則表達(dá)式的一個簡單例子及常用正則分享

    這篇文章主要介紹了Java中使用正則表達(dá)式的一個簡單例子及常用正則分享,本文用一個驗(yàn)證Email的例子講解JAVA中如何使用正則,并羅列了一些常用的正則表達(dá)式,需要的朋友可以參考下
    2015-06-06
  • Spring中OpenFeign的使用方法最佳實(shí)踐

    Spring中OpenFeign的使用方法最佳實(shí)踐

    這篇文章主要介紹了Spring中OpenFeign使用的相關(guān)資料,OpenFeign是一個聲明式的WebService客戶端,簡化了微服務(wù)之間的調(diào)用,類似于Controller調(diào)用Service,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2025-02-02
  • springBoot解決static和@Component遇到的bug

    springBoot解決static和@Component遇到的bug

    這篇文章主要介紹了springBoot解決static和@Component遇到的bug,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-02-02
  • SpringMVC Controller 返回值的可選類型詳解

    SpringMVC Controller 返回值的可選類型詳解

    本篇文章主要介紹了SpringMVC Controller 返回值的可選類型詳解 ,spring mvc 支持如下的返回方式:ModelAndView, Model, ModelMap, Map,View, String, void,有興趣的可以了解一下
    2017-05-05

最新評論

柘城县| 香河县| 化德县| 镇平县| 木兰县| 云安县| 湖南省| 西安市| 西畴县| 三原县| 和平县| 化州市| 景洪市| 视频| 金川县| 虞城县| 永善县| 南木林县| 湟中县| 栾城县| 嘉义市| 阜城县| 大化| 讷河市| 阿拉尔市| 大港区| 山西省| 慈溪市| 凤翔县| 三明市| 邳州市| 苍山县| 二连浩特市| 湖南省| 太湖县| 攀枝花市| 新丰县| 湖南省| 大洼县| 玉山县| 麻城市|