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

Spring?Boot+STOMP+SockJS+Vue實現(xiàn)WebSocket連接教程示例

 更新時間:2026年07月24日 09:40:35   作者:倚肆  
STOMP是一種簡單的流式文本傳輸協(xié)議,對于不支持websocket的瀏覽器我們需要通過STOMP來兼容,這篇文章主要介紹了Spring?Boot+STOMP+SockJS+Vue實現(xiàn)WebSocket連接的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下

一、整體架構(gòu)介紹

本示例實現(xiàn)了一個基于Spring Boot STOMP協(xié)議的WebSocket服務(wù),支持:

  • 公共頻道:無需認(rèn)證,直接連接
  • 私有頻道:需要HTTP會話認(rèn)證
  • 心跳機(jī)制:保持連接活躍
  • 線程池優(yōu)化:高性能消息處理

二、后端Spring Boot配置

1. 核心依賴 (pom.xml)

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- 安全認(rèn)證(如需) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!-- Lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
</dependencies>

2. WebSocket主配置類

@Configuration
@RequiredArgsConstructor
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
private final WebSocketHandshakeInterceptor handshakeInterceptor;
private final WebSocketAuthInterceptor authInterceptor;

/**
 * 配置消息代理
 */
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
    // 啟用簡單內(nèi)存消息代理,處理訂閱前綴
    config.enableSimpleBroker("/public", "/private")
          .setHeartbeatValue(new long[]{10000, 10000}); // 心跳:10秒
    
    // 客戶端發(fā)送消息的前綴
    config.setApplicationDestinationPrefixes("/hanhan");
    
    // 用戶目的地前綴(用于點(diǎn)對點(diǎn)消息)
    config.setUserDestinationPrefix("/user");
}

/**
 * 配置入站通道(客戶端→服務(wù)器)
 */
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
    registration.taskExecutor()
            .corePoolSize(10)        // 核心線程數(shù)
            .maxPoolSize(20)         // 最大線程數(shù)
            .queueCapacity(100)      // 隊列容量
            .keepAliveSeconds(60);   // 線程空閑時間
    
    registration.interceptors(authInterceptor); // 添加認(rèn)證攔截器
}

/**
 * 配置出站通道(服務(wù)器→客戶端)
 */
@Override
public void configureClientOutboundChannel(ChannelRegistration registration) {
    registration.taskExecutor()
            .corePoolSize(10)
            .maxPoolSize(20)
            .queueCapacity(100)
            .keepAliveSeconds(60);
}

/**
 * 注冊STOMP端點(diǎn)
 */
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
    registry.addEndpoint("/ws/hanhan")
            .addInterceptors(handshakeInterceptor) // 握手?jǐn)r截器
            .setAllowedOriginPatterns(
                "http://localhost:5173", 
                "https://hanhanys.cpolar.cn"
            )
            .withSockJS()           // 支持SockJS降級
            .setDisconnectDelay(30 * 1000); // 斷開延遲30秒
}
}

3. 握手?jǐn)r截器(HandshakeInterceptor)

@Slf4j
@Component
public class WebSocketHandshakeInterceptor implements HandshakeInterceptor {
private static final String CONNECT_WS_PREFIX = "/ws/hanhan";
private static final String PUBLIC_WS_PREFIX = "/ws/public/";
private static final String PRIVATE_WS_PREFIX = "/ws/private/";

@Override
public boolean beforeHandshake(ServerHttpRequest request,
                               ServerHttpResponse response,
                               WebSocketHandler wsHandler,
                               Map<String, Object> attributes) {
    try {
        if (!(request instanceof ServletServerHttpRequest)) {
            log.warn("非Servlet請求,拒絕握手");
            return false;
        }

        String requestPath = request.getURI().getPath();
        log.info("WebSocket握手請求路徑: {}", requestPath);
        
        // 1. 處理公共連接端點(diǎn)(/ws/hanhan)
        if (requestPath.startsWith(CONNECT_WS_PREFIX)) {
            String channelID = requestPath.substring(CONNECT_WS_PREFIX.length());
            attributes.put("channelID", channelID);
            attributes.put("channelType", "PUBLIC");
            attributes.put("authentication", null);
            log.info("公共連接端點(diǎn): channelID={}", channelID);
            return true;
        }
        
        // 2. 處理公共頻道(/ws/public/{channelId})
        if (requestPath.startsWith(PUBLIC_WS_PREFIX)) {
            String channelID = requestPath.substring(PUBLIC_WS_PREFIX.length());
            attributes.put("channelID", channelID);
            attributes.put("channelType", "PUBLIC");
            attributes.put("authentication", null);
            log.info("公共頻道連接: channelID={}", channelID);
            return true;
        }
        
        // 3. 處理私有頻道(/ws/private/{channelId})需要認(rèn)證
        if (requestPath.startsWith(PRIVATE_WS_PREFIX)) {
            ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
            HttpServletRequest httpRequest = servletRequest.getServletRequest();
            
            // 從現(xiàn)有HTTP會話獲取認(rèn)證信息
            HttpSession httpSession = httpRequest.getSession(false);
            if (httpSession == null) {
                log.warn("私有頻道需要認(rèn)證,但無HTTP會話");
                return false;
            }
            
            Authentication auth = (Authentication) httpSession.getAttribute("authentication");
            if (auth == null || !auth.isAuthenticated()) {
                log.warn("用戶未認(rèn)證或認(rèn)證已過期");
                return false;
            }
            
            String channelID = requestPath.substring(PRIVATE_WS_PREFIX.length());
            attributes.put("channelID", channelID);
            attributes.put("channelType", "PRIVATE");
            attributes.put("authentication", auth);
            
            log.info("私有頻道握手成功: 用戶={}, channelID={}", 
                    auth.getName(), channelID);
            return true;
        }
        
        log.warn("未知的WebSocket路徑: {}", requestPath);
        return false;
        
    } catch (Exception e) {
        log.error("握手過程異常: {}", e.getMessage(), e);
        return false;
    }
}

@Override
public void afterHandshake(ServerHttpRequest request,
                           ServerHttpResponse response,
                           WebSocketHandler wsHandler,
                           Exception exception) {
    // 握手后處理,可記錄日志等
    if (exception != null) {
        log.error("握手后處理異常: {}", exception.getMessage());
    }
}
}

4. 認(rèn)證攔截器(ChannelInterceptor)

@Slf4j
@Component
public class WebSocketAuthInterceptor implements ChannelInterceptor {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
    StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(
        message, StompHeaderAccessor.class
    );
    
    if (accessor == null) {
        return message;
    }
    
    // 處理CONNECT命令(連接建立)
    if (StompCommand.CONNECT.equals(accessor.getCommand())) {
        Map<String, Object> sessionAttrs = accessor.getSessionAttributes();
        
        if (sessionAttrs == null) {
            log.warn("WebSocket連接被拒絕:無session屬性");
            return null; // 拒絕連接
        }
        
        // 檢查是否為公共頻道
        if ("PUBLIC".equals(sessionAttrs.get("channelType"))) {
            String channelId = (String) sessionAttrs.get("channelID");
            log.info("公共頻道連接成功: channelID={}", channelId);
            return message;
        }
        
        // 檢查私有頻道的認(rèn)證信息
        Authentication auth = (Authentication) sessionAttrs.get("authentication");
        if (auth != null && auth.isAuthenticated()) {
            // 設(shè)置安全上下文
            SecurityContextHolder.getContext().setAuthentication(auth);
            // 設(shè)置STOMP用戶
            accessor.setUser(auth);
            log.info("私有頻道連接成功: 用戶={}", auth.getName());
            return message;
        }
        
        log.warn("WebSocket連接被拒絕:認(rèn)證失敗");
        return null; // 拒絕連接
    }
    
    return message;
}
}

5. 消息控制器示例

@Controller
@Slf4j
public class WebSocketController {
/**
 * 處理發(fā)送到 /hanhan/message 的消息
 */
@MessageMapping("/message")
@SendTo("/public/chat")  // 廣播到所有訂閱者
public ChatMessage handleMessage(ChatMessage message) {
    log.info("收到消息: {}", message);
    message.setTimestamp(new Date());
    return message;
}

/**
 * 點(diǎn)對點(diǎn)消息
 */
@MessageMapping("/private")
public void sendPrivateMessage(@Payload PrivateMessage message,
                               @Header("simpSessionId") String sessionId) {
    log.info("私密消息: from={}, to={}", message.getFrom(), message.getTo());
    
    // 通過消息模板發(fā)送給特定用戶
    simpMessagingTemplate.convertAndSendToUser(
        message.getTo(), 
        "/queue/private", 
        message
    );
}
}
@Data
class ChatMessage {
private String from;
private String content;
private Date timestamp;
}
@Data
class PrivateMessage {
private String from;
private String to;
private String content;
}

三、前端實現(xiàn)(Vue 3 + SockJS + STOMP)

1. 安裝依賴

npm install sockjs-client @stomp/stompjs
或
yarn add sockjs-client @stomp/stompjs

2. WebSocket工具類

// utils/websocket.js
import SockJS from 'sockjs-client';
import { Client } from '@stomp/stompjs';
class WebSocketService {
constructor() {
this.stompClient = null;
this.subscriptions = new Map(); // 保存訂閱引用
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
}
/**
 * 連接公共頻道(無需認(rèn)證)
 * @param {string} channelId - 頻道ID
 * @param {function} onMessage - 消息回調(diào)
 */
connectToPublicChannel(channelId, onMessage) {
    const socketUrl = 'http://localhost:8080/ws/hanhan';
    const socket = new SockJS(socketUrl);
    
    this.stompClient = new Client({
        webSocketFactory: () => socket,
        reconnectDelay: 5000,
        heartbeatIncoming: 10000,
        heartbeatOutgoing: 10000,
        onConnect: (frame) => {
            console.log('公共頻道連接成功', frame);
            
            // 訂閱公共頻道
            const subscription = this.stompClient.subscribe(
                `/public/${channelId}`,
                (message) => {
                    const parsed = JSON.parse(message.body);
                    onMessage(parsed);
                }
            );
            
            this.subscriptions.set(`public_${channelId}`, subscription);
        },
        onStompError: (frame) => {
            console.error('STOMP協(xié)議錯誤:', frame);
        },
        onWebSocketError: (event) => {
            console.error('WebSocket連接錯誤:', event);
            this.handleReconnect();
        }
    });
    
    this.stompClient.activate();
}

/**
 * 連接私有頻道(需要認(rèn)證)
 * @param {string} channelId - 頻道ID
 * @param {string} token - 認(rèn)證令牌
 * @param {function} onMessage - 消息回調(diào)
 */
connectToPrivateChannel(channelId, token, onMessage) {
    const socketUrl = `http://localhost:8080/ws/private/${channelId}`;
    
    // 添加認(rèn)證頭
    const socket = new SockJS(socketUrl, null, {
        headers: {
            'Authorization': `Bearer ${token}`
        }
    });
    
    this.stompClient = new Client({
        webSocketFactory: () => socket,
        reconnectDelay: 5000,
        heartbeatIncoming: 10000,
        heartbeatOutgoing: 10000,
        onConnect: (frame) => {
            console.log('私有頻道連接成功', frame);
            
            // 訂閱私有頻道
            const subscription = this.stompClient.subscribe(
                `/private/${channelId}`,
                (message) => {
                    const parsed = JSON.parse(message.body);
                    onMessage(parsed);
                }
            );
            
            // 訂閱用戶專屬隊列(點(diǎn)對點(diǎn)消息)
            const userSubscription = this.stompClient.subscribe(
                `/user/queue/private`,
                (message) => {
                    const parsed = JSON.parse(message.body);
                    console.log('收到私信:', parsed);
                }
            );
            
            this.subscriptions.set(`private_${channelId}`, subscription);
            this.subscriptions.set('user_queue', userSubscription);
        },
        onDisconnect: () => {
            console.log('WebSocket連接斷開');
        }
    });
    
    this.stompClient.activate();
}

/**
 * 發(fā)送消息到服務(wù)器
 * @param {string} destination - 目標(biāo)地址
 * @param {object} payload - 消息內(nèi)容
 */
sendMessage(destination, payload) {
    if (this.stompClient && this.stompClient.connected) {
        this.stompClient.publish({
            destination: `/hanhan${destination}`,
            body: JSON.stringify(payload)
        });
    } else {
        console.warn('WebSocket未連接,無法發(fā)送消息');
    }
}

/**
 * 發(fā)送私信
 * @param {string} toUser - 目標(biāo)用戶
 * @param {string} content - 消息內(nèi)容
 */
sendPrivateMessage(toUser, content) {
    this.sendMessage('/private', {
        from: 'currentUser',
        to: toUser,
        content: content,
        timestamp: new Date().toISOString()
    });
}

/**
 * 取消訂閱
 * @param {string} subscriptionKey - 訂閱鍵值
 */
unsubscribe(subscriptionKey) {
    const subscription = this.subscriptions.get(subscriptionKey);
    if (subscription) {
        subscription.unsubscribe();
        this.subscriptions.delete(subscriptionKey);
    }
}

/**
 * 斷開連接
 */
disconnect() {
    if (this.stompClient) {
        // 取消所有訂閱
        this.subscriptions.forEach(sub => sub.unsubscribe());
        this.subscriptions.clear();
        
        this.stompClient.deactivate();
        this.stompClient = null;
        console.log('WebSocket已斷開連接');
    }
}

/**
 * 重連處理
 */
handleReconnect() {
    if (this.reconnectAttempts < this.maxReconnectAttempts) {
        this.reconnectAttempts++;
        console.log(`嘗試重連 (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
        
        setTimeout(() => {
            if (this.stompClient) {
                this.stompClient.activate();
            }
        }, 3000);
    }
}
}
export default new WebSocketService();

3. Vue組件使用示例

<template>
<div class="chat-room">
<div :class="['status', { connected: isConnected }]">
{{ isConnected ? '已連接' : '未連接' }}
</div>
<!-- 頻道選擇 -->
<div class="channel-selector">
  <button @click="connectPublic('general')">連接公共聊天室</button>
  <button @click="connectPrivate('private-room', userToken)">
    連接私有房間
  </button>
  <button @click="disconnect" :disabled="!isConnected">斷開連接</button>
</div>

<!-- 消息列表 -->
<div class="message-list">
  <div v-for="(msg, index) in messages" :key="index" class="message">
    <span class="sender">{{ msg.from }}:</span>
    <span class="content">{{ msg.content }}</span>
    <span class="time">{{ formatTime(msg.timestamp) }}</span>
  </div>
</div>

<!-- 消息發(fā)送 -->
<div class="message-input">
  <input 
    v-model="inputMessage" 
    @keyup.enter="sendMessage"
    placeholder="輸入消息..."
    :disabled="!isConnected"
  />
  <button @click="sendMessage" :disabled="!isConnected">發(fā)送</button>
</div>

<!-- 私信發(fā)送 -->
<div class="private-message">
  <input v-model="privateTo" placeholder="目標(biāo)用戶" />
  <input v-model="privateContent" placeholder="私信內(nèi)容" />
  <button @click="sendPrivate">發(fā)送私信</button>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import websocket from '@/utils/websocket';
const isConnected = ref(false);
const messages = ref([]);
const inputMessage = ref('');
const privateTo = ref('');
const privateContent = ref('');
const userToken = ref('your-auth-token'); // 從登錄狀態(tài)獲取
// 連接公共頻道
const connectPublic = (channelId) => {
websocket.connectToPublicChannel(channelId, (message) => {
messages.value.push(message);
console.log('收到公共消息:', message);
});
isConnected.value = true;
};
// 連接私有頻道
const connectPrivate = (channelId, token) => {
websocket.connectToPrivateChannel(channelId, token, (message) => {
messages.value.push(message);
console.log('收到私有消息:', message);
});
isConnected.value = true;
};
// 發(fā)送公共消息
const sendMessage = () => {
if (!inputMessage.value.trim()) return;
const message = {
from: '當(dāng)前用戶',
content: inputMessage.value,
type: 'public'
};
websocket.sendMessage('/message', message);
inputMessage.value = '';
};
// 發(fā)送私信
const sendPrivate = () => {
if (!privateTo.value || !privateContent.value) return;
websocket.sendPrivateMessage(privateTo.value, privateContent.value);
privateContent.value = '';
};
// 斷開連接
const disconnect = () => {
websocket.disconnect();
isConnected.value = false;
messages.value = [];
};
// 格式化時間
const formatTime = (timestamp) => {
return new Date(timestamp).toLocaleTimeString();
};
// 組件生命周期
onMounted(() => {
console.log('聊天室組件已加載');
});
onUnmounted(() => {
disconnect();
});
</script>
<style scoped>
.chat-room {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.status {
padding: 8px 16px;
border-radius: 4px;
margin-bottom: 20px;
background: #ff6b6b;
color: white;
}
.status.connected {
background: #51cf66;
}
.channel-selector {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.channel-selector button {
padding: 10px 20px;
background: #339af0;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
.channel-selector button:disabled {
background: #ccc;
cursor: not-allowed;
}
.message-list {
height: 400px;
overflow-y: auto;
border: 1px solid #ddd;
border-radius: 4px;
padding: 10px;
margin-bottom: 20px;
}
.message {
padding: 8px;
border-bottom: 1px solid #eee;
}
.message .sender {
font-weight: bold;
margin-right: 10px;
color: #339af0;
}
.message .time {
float: right;
color: #999;
font-size: 0.8em;
}
.message-input, .private-message {
display: flex;
gap: 10px;
margin-top: 10px;
}
input {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
}
button {
padding: 10px 20px;
background: #51cf66;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
}
</style>

4. 主應(yīng)用入口

// main.js
import { createApp } from 'vue';
import App from './App.vue';
// 全局掛載WebSocket服務(wù)
const app = createApp(App);
// 可選:提供全局WebSocket實例
app.config.globalProperties.$websocket = websocket;
app.mount('#app');

總結(jié) 

到此這篇關(guān)于Spring Boot+STOMP+SockJS+Vue實現(xiàn)WebSocket連接的文章就介紹到這了,更多相關(guān)SpringBoot連接WebSocket服務(wù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • MybatisPlus,無XML分分鐘實現(xiàn)CRUD操作

    MybatisPlus,無XML分分鐘實現(xiàn)CRUD操作

    這篇文章主要介紹了MybatisPlus,無XML分分鐘實現(xiàn)CRUD操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-08-08
  • JAVA流控及超流控后的延遲處理實例

    JAVA流控及超流控后的延遲處理實例

    這篇文章主要介紹了JAVA流控及超流控后的延遲處理,以實例形式較為詳細(xì)的分析了Java進(jìn)行流量控制的技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2014-12-12
  • Java?OkHttp框架源碼超詳細(xì)解析

    Java?OkHttp框架源碼超詳細(xì)解析

    okhttp是一個第三方類庫,用于android中請求網(wǎng)絡(luò)。這是一個開源項目,是安卓端最火熱的輕量級框架,由移動支付Square公司貢獻(xiàn)(該公司還貢獻(xiàn)了Picasso和LeakCanary)?。用于替代HttpUrlConnection和Apache?HttpClient
    2022-11-11
  • Java判斷對象是否為空的四種方法小結(jié)

    Java判斷對象是否為空的四種方法小結(jié)

    這篇文章主要介紹了Java判斷對象是否為空的四種方法,判斷對象是否為空有多種方法,包括使用==或!=運(yùn)算符直接比較對象與null,使用Objects.isNull()方法,以及用instanceof運(yùn)算符或Optional類進(jìn)行更安全的空值處理,需要的朋友可以參考下
    2024-10-10
  • IDEA連接postgressql數(shù)據(jù)庫操作

    IDEA連接postgressql數(shù)據(jù)庫操作

    這篇文章主要介紹了IDEA連接postgressql數(shù)據(jù)庫操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-08-08
  • springboot中使用jpa下hibernate的ddl-auto方式

    springboot中使用jpa下hibernate的ddl-auto方式

    這篇文章主要介紹了springboot中使用jpa下hibernate的ddl-auto方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-02-02
  • Java畢業(yè)設(shè)計實戰(zhàn)之在線蛋糕銷售商城的實現(xiàn)

    Java畢業(yè)設(shè)計實戰(zhàn)之在線蛋糕銷售商城的實現(xiàn)

    這是一個使用了java+JSP+Springboot+maven+mysql+ThymeLeaf+FTP開發(fā)的在線蛋糕銷售商城,是一個畢業(yè)設(shè)計的實戰(zhàn)練習(xí),具有線上蛋糕商城該有的所有功能,感興趣的朋友快來看看吧
    2022-01-01
  • Java中正則表達(dá)式的語法以及matches方法的使用方法

    Java中正則表達(dá)式的語法以及matches方法的使用方法

    正則表達(dá)式(Regular Expression)是一門簡單語言的語法規(guī)范,是強(qiáng)大、便捷、高效的文本處理工具,這篇文章主要給大家介紹了關(guān)于Java中正則表達(dá)式的語法以及matches方法的使用方法,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-05-05
  • Java 十大排序算法之冒泡排序刨析

    Java 十大排序算法之冒泡排序刨析

    冒泡排序是一種簡單的排序算法,它也是一種穩(wěn)定排序算法。其實現(xiàn)原理是重復(fù)掃描待排序序列,并比較每一對相鄰的元素,當(dāng)該對元素順序不正確時進(jìn)行交換。一直重復(fù)這個過程,直到?jīng)]有任何兩個相鄰元素可以交換,就表明完成了排序
    2021-11-11
  • Kafka?Producer中的消息緩存模型圖解詳解

    Kafka?Producer中的消息緩存模型圖解詳解

    Kafka中消息是以Topic進(jìn)行分類的,生產(chǎn)者生產(chǎn)消息,消費(fèi)者消費(fèi)消息,都是面向Topic的,下面這篇文章主要給大家介紹了關(guān)于Kafka?Producer中消息緩存模型的相關(guān)資料,需要的朋友可以參考下
    2022-04-04

最新評論

团风县| 大关县| 昌都县| 定结县| 海晏县| 巴青县| 凌云县| 镇巴县| 离岛区| 辉南县| 虎林市| 方正县| 内乡县| 舞阳县| 五指山市| 镇雄县| 青神县| 沐川县| 晋江市| 绍兴县| 榆社县| 库尔勒市| 青海省| 凤山市| 普格县| 抚顺市| 收藏| 红河县| 资兴市| 扎赉特旗| 彰化县| 大新县| 杨浦区| 安国市| 西充县| 河北区| 镇康县| 如东县| 罗山县| 循化| 曲阳县|