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

使用SQLite實(shí)現(xiàn)CacheHelper的示例代碼

 更新時(shí)間:2025年10月23日 11:16:57   作者:xyy123  
CacheHelper是一個(gè)基于SQLite的靜態(tài)緩存工具類,旨在為 .NET 應(yīng)用程序提供一個(gè)簡(jiǎn)單、高效、持久化且線程安全的緩存解決方案,本文主要介紹了使用SQLite實(shí)現(xiàn)CacheHelper的示例代碼,感興趣的可以了解一下

1. 概述

CacheHelper 是一個(gè)基于 SQLite 的靜態(tài)緩存工具類,旨在為 .NET 應(yīng)用程序提供一個(gè)簡(jiǎn)單、高效、持久化且線程安全的緩存解決方案。它將緩存數(shù)據(jù)存儲(chǔ)在應(yīng)用程序根目錄下的 cache.db 文件中,這意味著即使應(yīng)用程序重啟,緩存數(shù)據(jù)依然存在(只要未過(guò)期)。

該助手類封裝了常見(jiàn)的緩存操作,并內(nèi)置了絕對(duì)過(guò)期滑動(dòng)過(guò)期兩種策略,通過(guò)“Get-Or-Set”模式極大地簡(jiǎn)化了從數(shù)據(jù)源(如數(shù)據(jù)庫(kù)、API)獲取并緩存數(shù)據(jù)的業(yè)務(wù)邏輯。

2. 安裝與環(huán)境準(zhǔn)備 (Prerequisites)

要使 CacheHelper 類正常工作,您必須在您的項(xiàng)目中安裝以下兩個(gè)核心的 NuGet 包。

您可以使用 .NET CLI 命令來(lái)安裝它們:

# 1. 用于操作 SQLite 數(shù)據(jù)庫(kù)
dotnet add package Microsoft.Data.Sqlite

# 2. 用于高效的對(duì)象序列化/反序列化
dotnet add package MessagePack

或者通過(guò) Visual Studio 的 NuGet 包管理器搜索并安裝 Microsoft.Data.SqliteMessagePack。

封裝好的方法如下:

using Microsoft.Data.Sqlite;
using System;
using System.IO;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using pei_repspark_admin_webapi.Entities.Constants;
using MessagePack.Resolvers;
using MessagePack; // 請(qǐng)確保您的常量命名空間正確

namespace YOU_PROJECT.Utils
{
    public static class CacheHelper
    {
        private static readonly string _dbPath;
        private static readonly string _connectionString;
        private static readonly MessagePackSerializerOptions _serializerOptions = ContractlessStandardResolver.Options;
        private static readonly int default_limit = CacheConstants.FILTER_LIMIT;

        // 用于線程安全的鎖管理器,確保每個(gè) key 都有一個(gè)獨(dú)立的鎖對(duì)象
        private static readonly ConcurrentDictionary<string, object> _locks = new ConcurrentDictionary<string, object>();

        /// <summary>
        /// 靜態(tài)構(gòu)造函數(shù),在類第一次被訪問(wèn)時(shí)自動(dòng)運(yùn)行一次,用于初始化數(shù)據(jù)庫(kù)。
        /// </summary>
        static CacheHelper()
        {
            // 將數(shù)據(jù)庫(kù)文件放在應(yīng)用程序的根目錄下,確保路徑一致性
            _dbPath = Path.Combine(AppContext.BaseDirectory, "cache.db");
            _connectionString = $"Data Source={_dbPath}";
            InitializeDatabase();
        }

        /// <summary>
        /// 初始化數(shù)據(jù)庫(kù):如果數(shù)據(jù)庫(kù)文件或表不存在,則創(chuàng)建它們。
        /// </summary>
        private static void InitializeDatabase()
        {
            try
            {
                using var connection = new SqliteConnection(_connectionString);
                connection.Open();

                // 創(chuàng)建一個(gè)可重用的 command 對(duì)象
                var command = connection.CreateCommand();

                // 1. 執(zhí)行 PRAGMA 指令以優(yōu)化并發(fā)性能
                command.CommandText = "PRAGMA journal_mode=WAL;";
                command.ExecuteNonQuery();

                // 2. 重用同一個(gè) command 對(duì)象,執(zhí)行 CREATE TABLE 指令
                command.CommandText = @"
                CREATE TABLE IF NOT EXISTS CacheStore (
                    Key TEXT NOT NULL PRIMARY KEY,
                    Value BLOB NOT NULL,
                    InsertionTimeUtc INTEGER NOT NULL,
                    ExpirationTimeUtc INTEGER NOT NULL,
                    SlidingExpirationMinutes INTEGER NOT NULL
                );";
                command.ExecuteNonQuery();
            }
            catch (Exception ex)
            {
                // 如果數(shù)據(jù)庫(kù)初始化失敗,這是個(gè)嚴(yán)重問(wèn)題,需要記錄下來(lái)
                // 在生產(chǎn)環(huán)境中,應(yīng)使用專業(yè)的日志庫(kù)(如 Serilog, NLog)
                ($"[CACHE CRITICAL] Database initialization failed. Error: {ex.Message}").LogErr();
                // 拋出異常,因?yàn)槿绻麛?shù)據(jù)庫(kù)無(wú)法初始化,整個(gè)緩存服務(wù)都無(wú)法工作
                throw;
            }
        }

        #region Public Cache Modification Methods (Add, Clean)

        public static void AddCacheWithAbsolute(string key, object value, int minute)
        {
            Set(key, value, TimeSpan.FromMinutes(minute), isSliding: false);
        }

        public static void AddCacheWithRelative(string key, object value, int minute)
        {
            Set(key, value, TimeSpan.FromMinutes(minute), isSliding: true);
        }

        public static void AddCache(string key, object value)
        {
            // 對(duì)于永久緩存,我們?cè)O(shè)置一個(gè)極大的過(guò)期時(shí)間
            var longLivedTimeSpan = TimeSpan.FromDays(30); // 30 days
            Set(key, value, longLivedTimeSpan, isSliding: false);
        }

        public static void CleanCache(string key)
        {
            try
            {
                using var connection = new SqliteConnection(_connectionString);
                connection.Open();

                var command = connection.CreateCommand();
                command.CommandText = "DELETE FROM CacheStore WHERE Key = $key;";
                command.Parameters.AddWithValue("$key", key);
                command.ExecuteNonQuery();
            }
            catch (Exception ex)
            {
                ($"[CACHE ERROR] Failed to clean cache for key '{key}'. Error: {ex.Message}").LogErr();
            }
        }

        #endregion

        #region Public Cache Retrieval Methods (Get, GetOrQuery)

        public static object? GetCache(string key)
        {
            var (found, value) = TryGetCache<object>(key);
            return found ? value : null;
        }

        public static T GetCacheOrQuery<T>(string key, Func<T> myFunc)
        {
            return GetCacheOrQuery<T>(key, default_limit, myFunc);
        }

        public static T GetCacheOrQuery<T>(string key, int minute, Func<T> myFunc)
        {
            return GetOrSet(key, minute, () => myFunc());
        }

        public static r GetCacheOrQuery<p, r>(string key, Func<p, r> myFunc, p param1)
        {
            return GetCacheOrQuery<p, r>(key, default_limit, myFunc, param1);
        }

        public static r GetCacheOrQuery<p, r>(string key, int minter, Func<p, r> myFunc, p param1)
        {
            return GetOrSet(key, minter, () => myFunc(param1));
        }

        // --- 為了簡(jiǎn)潔,這里省略了剩余的 GetCacheOrQuery 重載 ---
        // --- 您可以按照下面的 `GetOrSet` 模式輕松地實(shí)現(xiàn)它們 ---
        // 示例:
        public static r GetCacheOrQuery<p1, p2, r>(string key, int minter, Func<p1, p2, r> myFunc, p1 param1, p2 param2)
        {
            return GetOrSet(key, minter, () => myFunc(param1, param2));
        }

        public static r GetCacheOrQuery<p1, p2, r>(string key, Func<p1, p2, r> myFunc, p1 param1, p2 param2)
        {
            return GetCacheOrQuery(key, default_limit, myFunc, param1, param2);
        }

        // ... 請(qǐng)為其他所有重載方法應(yīng)用相同的模式 ...

        #endregion

        #region Core Logic (Private Methods)

        /// <summary>
        /// 核心的 "Get-Or-Set" 方法,實(shí)現(xiàn)了雙重檢查鎖定以確保線程安全。
        /// </summary>
        private static T GetOrSet<T>(string key, int minute, Func<T> queryFunc)
        {
            // 第一次檢查(在鎖之外),這是為了在緩存命中的情況下獲得最高性能
            var (found, value) = TryGetCache<T>(key);
            if (found)
            {
                Console.WriteLine($"Get [{key}] cache, survival time is [{minute}] minutes");
                return value!;
            }

            // 獲取或?yàn)楫?dāng)前 key 創(chuàng)建一個(gè)唯一的鎖對(duì)象
            var lockObject = _locks.GetOrAdd(key, k => new object());

            // 進(jìn)入鎖代碼塊,確保同一時(shí)間只有一個(gè)線程能為這個(gè) key 生成緩存
            lock (lockObject)
            {
                // 第二次檢查(在鎖之內(nèi)),防止在等待鎖的過(guò)程中,其他線程已經(jīng)生成了緩存
                (found, value) = TryGetCache<T>(key);
                if (found)
                {
                    return value!;
                }

                // 執(zhí)行昂貴的數(shù)據(jù)查詢操作
                var result = queryFunc();

                // 將查詢結(jié)果存入緩存
                if (result != null)
                {
                    Set(key, result, TimeSpan.FromMinutes(minute), isSliding: false);
                }
                Console.WriteLine($"Added [{key}] cache, survival time is [{minute}] minutes");
                return result;
            }
        }

        /// <summary>
        /// 統(tǒng)一的緩存寫入方法。
        /// </summary>
        private static void Set(string key, object value, TimeSpan expiration, bool isSliding)
        {
            if (value == null) return;

            try
            {
                var serializedValue = MessagePackSerializer.Serialize(value, _serializerOptions);
                var now = DateTime.UtcNow;

                using var connection = new SqliteConnection(_connectionString);
                connection.Open();

                var command = connection.CreateCommand();
                command.CommandText = @"
                    INSERT OR REPLACE INTO CacheStore (Key, Value, InsertionTimeUtc, ExpirationTimeUtc, SlidingExpirationMinutes)
                    VALUES ($key, $value, $insertion, $expiration, $sliding);";

                command.Parameters.AddWithValue("$key", key);
                command.Parameters.AddWithValue("$value", serializedValue);
                command.Parameters.AddWithValue("$insertion", now.Ticks);
                command.Parameters.AddWithValue("$expiration", now.Add(expiration).Ticks);
                command.Parameters.AddWithValue("$sliding", isSliding ? expiration.TotalMinutes : 0);

                command.ExecuteNonQuery();
            }
            catch (Exception ex)
            {
                ($"[CACHE ERROR] Failed to set cache for key '{key}'. Error: {ex.Message}").LogErr();
                // 吞掉異常,保證主程序繼續(xù)運(yùn)行
            }
        }

        /// <summary>
        /// 嘗試從緩存中獲取數(shù)據(jù),并處理滑動(dòng)過(guò)期的更新邏輯。
        /// </summary>
        private static (bool found, T? value) TryGetCache<T>(string key)
        {
            try
            {
                using var connection = new SqliteConnection(_connectionString);
                connection.Open();

                var command = connection.CreateCommand();
                command.CommandText = @"
                    SELECT Value, SlidingExpirationMinutes FROM CacheStore
                    WHERE Key = $key AND ExpirationTimeUtc > $now;";
                command.Parameters.AddWithValue("$key", key);
                command.Parameters.AddWithValue("$now", DateTime.UtcNow.Ticks);

                using var reader = command.ExecuteReader();
                if (reader.Read())
                {
                    var blob = reader.GetFieldValue<byte[]>(0);
                    var slidingMinutes = reader.GetInt64(1);

                    // 如果是滑動(dòng)過(guò)期項(xiàng),則更新其過(guò)期時(shí)間
                    if (slidingMinutes > 0)
                    {
                        try
                        {
                            var updateCmd = connection.CreateCommand();
                            updateCmd.CommandText = "UPDATE CacheStore SET ExpirationTimeUtc = $newExpiration WHERE Key = $key;";
                            updateCmd.Parameters.AddWithValue("$key", key);
                            updateCmd.Parameters.AddWithValue("$newExpiration", DateTime.UtcNow.AddMinutes(slidingMinutes).Ticks);
                            updateCmd.ExecuteNonQuery();
                        }
                        catch (Exception updateEx)
                        {
                            // 滑動(dòng)過(guò)期更新失敗不是致命錯(cuò)誤,只記錄警告
                            ($"[CACHE WARNING] Failed to update sliding expiration for key '{key}'. Error: {updateEx.Message}").LogErr();
                        }
                    }

                    var deserializedValue = MessagePackSerializer.Deserialize<T>(blob, _serializerOptions);
                    return (true, deserializedValue);
                }
            }
            catch (Exception ex)
            {
                ($"[CACHE ERROR] Failed to get cache for key '{key}'. Error: {ex.Message}").LogErr();
            }

            // 如果發(fā)生任何錯(cuò)誤或未找到,都返回“未命中”
            return (false, default);
        }

        #endregion
    }
}

3. 公共 API 參考 (封裝方法說(shuō)明)

這是與 CacheHelper 交互的公共方法列表。

3.1 核心模式:獲取或查詢 (Get-Or-Set)

這是最推薦的使用方式。它將“檢查緩存、執(zhí)行查詢、設(shè)置緩存”的邏輯封裝為一步,確保了代碼的簡(jiǎn)潔和線程安全。

GetCacheOrQuery<...>(...)

  • 描述: 嘗試根據(jù) key 從緩存中獲取數(shù)據(jù)。如果緩存存在且未過(guò)期,則直接返回緩存數(shù)據(jù);否則,執(zhí)行您提供的查詢方法 (myFunc) 來(lái)獲取最新數(shù)據(jù),然后將結(jié)果存入緩存,并最終返回該結(jié)果。

  • 重載 (Overloads): 該方法提供多個(gè)重載版本,以支持無(wú)參、單參數(shù)、雙參數(shù)等不同簽名的查詢方法。

  • 參數(shù):

    • string key: 緩存的唯一標(biāo)識(shí)符。
    • int minute (可選): 緩存的有效期(分鐘)。如果未提供,將使用一個(gè)默認(rèn)值(例如60分鐘)。
    • Func<...> myFunc: 一個(gè)委托或 Lambda 表達(dá)式。當(dāng)緩存未命中時(shí),此函數(shù)將被調(diào)用以獲取數(shù)據(jù)。
    • p1, p2, ... (可選): 傳遞給 myFunc 的參數(shù)。
  • 示例:

    // 示例1: 無(wú)參數(shù)的查詢
    string allProductsKey = "products:all";
    var products = CacheHelper.GetCacheOrQuery(allProductsKey, 30, () => {
        // 這段代碼只會(huì)在緩存未命中時(shí)執(zhí)行
        Console.WriteLine("從數(shù)據(jù)庫(kù)獲取所有產(chǎn)品...");
        return database.GetAllProducts();
    });
    
    // 示例2: 帶一個(gè)參數(shù)的查詢
    int userId = 123;
    string userKey = $"user:{userId}";
    var user = CacheHelper.GetCacheOrQuery(userKey, 60, (id) => {
        // 這段代碼只會(huì)在緩存未命中時(shí)執(zhí)行
        Console.WriteLine($"從數(shù)據(jù)庫(kù)獲取ID為 {id} 的用戶...");
        return database.GetUserById(id);
    }, userId);
    

3.2 直接緩存管理

這些方法允許您更直接地控制緩存的添加和更新。

AddCache(string key, object value)

  • 描述: 添加一個(gè)“永久”緩存(內(nèi)部設(shè)置為10年有效期)。適用于極少變動(dòng)的基礎(chǔ)數(shù)據(jù)。
  • 示例: CacheHelper.AddCache("global_settings", siteSettings);

AddCacheWithAbsolute(string key, object value, int minute)

  • 描述: 添加一個(gè)具有絕對(duì)過(guò)期策略的緩存。緩存將在 minute 分鐘后過(guò)期,無(wú)論期間是否被訪問(wèn)。
  • 示例: CacheHelper.AddCacheWithAbsolute("daily_report", reportData, 1440); // 緩存24小時(shí)

AddCacheWithRelative(string key, object value, int minute)

  • 描述: 添加一個(gè)具有滑動(dòng)過(guò)期策略的緩存。如果在 minute 分鐘內(nèi)沒(méi)有被訪問(wèn),緩存將過(guò)期。每次訪問(wèn)都會(huì)重置其生命周期。常用于用戶會(huì)話等場(chǎng)景。
  • 示例: CacheHelper.AddCacheWithRelative("user_session:xyz", sessionData, 20); // 20分鐘不活動(dòng)則過(guò)期

3.3 緩存移除

CleanCache(string key)

  • 描述: 從緩存中手動(dòng)移除一個(gè)指定的項(xiàng)。這在底層數(shù)據(jù)更新后,需要強(qiáng)制讓緩存失效時(shí)非常有用。
  • 示例:
    // 更新了用戶ID為123的個(gè)人信息
    database.UpdateUser(updatedUser);
    // 立即清除舊的緩存,確保下次請(qǐng)求獲取的是最新數(shù)據(jù)
    CacheHelper.CleanCache("user:123");
    

4. 核心特性

  • 持久化存儲(chǔ): 使用 SQLite 文件數(shù)據(jù)庫(kù),緩存內(nèi)容在應(yīng)用程序重啟后依然保留。
  • 線程安全: 采用雙重檢查鎖定(Double-Checked Locking)模式和基于Key的鎖,有效防止在高并發(fā)場(chǎng)景下的“緩存擊穿”問(wèn)題。
  • 高效序列化: 使用 MessagePack 對(duì)緩存對(duì)象進(jìn)行二進(jìn)制序列化,相比 JSON 序列化,性能更高,占用空間更小。
  • 兩種過(guò)期策略:
    • 絕對(duì)過(guò)期 (Absolute Expiration): 緩存項(xiàng)在設(shè)定的固定時(shí)間點(diǎn)后失效。
    • 滑動(dòng)過(guò)期 (Sliding Expiration): 緩存項(xiàng)在一段時(shí)間內(nèi)未被訪問(wèn)則失效;每次訪問(wèn)都會(huì)重置其生命周期。
  • 簡(jiǎn)潔的 API: 提供了簡(jiǎn)單易用的 GetCacheOrQuery 方法,將“檢查緩存、獲取數(shù)據(jù)、存入緩存”的邏輯封裝為原子操作。

5. 核心概念深入解析

5.1 數(shù)據(jù)庫(kù)結(jié)構(gòu) (cache.db)

CacheHelper 會(huì)自動(dòng)創(chuàng)建名為 CacheStore 的表,其結(jié)構(gòu)如下:

字段名類型描述
KeyTEXT主鍵。緩存項(xiàng)的唯一標(biāo)識(shí)符。
ValueBLOB存儲(chǔ)經(jīng) MessagePack 序列化后的二進(jìn)制數(shù)據(jù)。
InsertionTimeUtcINTEGER緩存項(xiàng)的創(chuàng)建時(shí)間 (UTC Ticks)。
ExpirationTimeUtcINTEGER緩存項(xiàng)的過(guò)期時(shí)間點(diǎn) (UTC Ticks)。這是判斷緩存是否有效的核心字段。
SlidingExpirationMinutesINTEGER滑動(dòng)過(guò)期策略的關(guān)鍵。0 表示絕對(duì)過(guò)期;>0 的值表示這是一個(gè)滑動(dòng)過(guò)期的項(xiàng),其值為滑動(dòng)的分鐘數(shù)。

5.2 滑動(dòng)過(guò)期 (SlidingExpirationMinutes) 的工作原理

SlidingExpirationMinutes 字段的設(shè)計(jì)非常巧妙,它同時(shí)扮演了**“標(biāo)記”“時(shí)長(zhǎng)”**兩個(gè)角色。

  1. 設(shè)置緩存時(shí):

    • 調(diào)用 AddCacheWithAbsolute 時(shí),SlidingExpirationMinutes 被設(shè)為 0。
    • 調(diào)用 AddCacheWithRelative(key, value, 30) 時(shí),SlidingExpirationMinutes 被設(shè)為 30。
  2. 獲取緩存時(shí) (TryGetCache 內(nèi)部邏輯):

    • 系統(tǒng)首先檢查 ExpirationTimeUtc 是否已過(guò)期。
    • 如果未過(guò)期且成功讀取數(shù)據(jù),系統(tǒng)會(huì)檢查 SlidingExpirationMinutes 字段的值。
    • 如果值為 0,則不執(zhí)行任何額外操作。
    • 如果值大于 0(例如 30),系統(tǒng)識(shí)別出這是一個(gè)滑動(dòng)緩存項(xiàng),會(huì)立即執(zhí)行一個(gè) UPDATE 命令,將該項(xiàng)的 ExpirationTimeUtc 更新為 當(dāng)前時(shí)間 + 30分鐘。

這個(gè)“讀取并續(xù)期”的原子操作,完美地實(shí)現(xiàn)了滑動(dòng)過(guò)期的邏輯:只要你在它過(guò)期前訪問(wèn)它,它的生命就在不斷延續(xù)。

6. 并發(fā)安全機(jī)制

在高并發(fā)環(huán)境下,多個(gè)線程可能同時(shí)請(qǐng)求同一個(gè)不存在的緩存項(xiàng)。如果沒(méi)有鎖定機(jī)制,這些線程會(huì)全部穿透緩存去執(zhí)行昂貴的數(shù)據(jù)查詢,這就是“緩存擊穿”。

CacheHelper 通過(guò) GetOrSet 方法中的 雙重檢查鎖定模式 解決了這個(gè)問(wèn)題:

  1. 第一次檢查 (無(wú)鎖): 在進(jìn)入 lock 之前快速檢查緩存是否存在。對(duì)于絕大多數(shù)緩存命中的情況,可以無(wú)鎖返回,性能極高。
  2. 獲取Key專用鎖: 如果第一次檢查未命中,系統(tǒng)會(huì)從一個(gè) ConcurrentDictionary 中為當(dāng)前 key 獲取一個(gè)專用的鎖對(duì)象。這確保了對(duì)不同 key 的請(qǐng)求不會(huì)互相阻塞。
  3. 第二次檢查 (有鎖): 在獲得鎖之后,再次檢查緩存。這是為了防止在等待鎖的過(guò)程中,已有其他線程完成了數(shù)據(jù)查詢和緩存設(shè)置。
  4. 執(zhí)行查詢與設(shè)置: 只有當(dāng)?shù)诙螜z查仍然未命中時(shí),當(dāng)前線程才會(huì)去執(zhí)行數(shù)據(jù)查詢,并將結(jié)果寫入緩存。

這個(gè)機(jī)制確保了對(duì)于任意一個(gè)key,在同一時(shí)刻最多只有一個(gè)線程在執(zhí)行數(shù)據(jù)源的查詢操作。

7. 注意事項(xiàng)與最佳實(shí)踐

  • 緩存鍵 (Key) 的命名: 緩存鍵應(yīng)具有唯一性和良好的描述性。推薦使用如 object_type:id 的格式,例如 user:123products:all
  • 緩存失效: 當(dāng)?shù)讓訑?shù)據(jù)發(fā)生變化時(shí)(例如,用戶信息被修改),應(yīng)主動(dòng)調(diào)用 CacheHelper.CleanCache("user:123") 來(lái)清除舊緩存,以避免數(shù)據(jù)不一致。
  • 可序列化對(duì)象: 存入緩存的對(duì)象必須能被 MessagePack 序列化。絕大多數(shù) POCO (Plain Old C# Object) 對(duì)象都沒(méi)有問(wèn)題。
  • 異常處理: CacheHelper 內(nèi)部已對(duì)數(shù)據(jù)庫(kù)操作和序列化等步驟進(jìn)行了 try-catch 封裝。緩存操作失敗時(shí)會(huì)向控制臺(tái)輸出錯(cuò)誤日志,但不會(huì)拋出異常中斷主程序流程,保證了系統(tǒng)的穩(wěn)定性。

到此這篇關(guān)于使用SQLite實(shí)現(xiàn)CacheHelper的示例代碼的文章就介紹到這了,更多相關(guān)SQLite實(shí)現(xiàn)CacheHelper內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論

安乡县| 抚州市| 瑞金市| 穆棱市| 建水县| 潼关县| 页游| 万安县| 塔城市| 丹棱县| 洪雅县| 三穗县| 洛隆县| 平凉市| 汽车| 巨鹿县| 宁武县| 东丽区| 锡林浩特市| 龙川县| 汉川市| 常熟市| 康平县| 峨边| 丹巴县| 自贡市| 南城县| 图们市| 全椒县| 张家口市| 巩留县| 浏阳市| 阿鲁科尔沁旗| 阳山县| 牡丹江市| 额敏县| 安吉县| 五家渠市| 滕州市| 吴忠市| 昌乐县|