zoukankan      html  css  js  c++  java
  • 【NET Core】 缓存 MemoryCache 和 Redis

     缓存接口 ICacheService

    using System;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    
    namespace WebAppNetCore.Interface
    {
        public interface ICacheService
        {
            /// <summary>
            /// 验证缓存项是否存在
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <returns></returns>
            bool Exists(string key);
    
            /// <summary>
            /// 验证缓存项是否存在(异步方式)
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <returns></returns>
            Task<bool> ExistsAsync(string key);
    
            /// <summary>
            /// 添加缓存
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <param name="value">缓存Value</param>
            /// <returns></returns>
            bool Add(string key, object value);
    
            /// <summary>
            /// 添加缓存(异步方式)
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <param name="value">缓存Value</param>
            /// <returns></returns>
            Task<bool> AddAsync(string key, object value);
    
            /// <summary>
            /// 添加缓存
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <param name="value">缓存Value</param>
            /// <param name="expiresSliding">滑动过期时长(如果在过期时间内有操作,则以当前时间点延长过期时间)</param>
            /// <param name="expiressAbsoulte">绝对过期时长</param>
            /// <returns></returns>
            bool Add(string key, object value, TimeSpan expiresSliding, TimeSpan expiressAbsoulte);
    
            /// <summary>
            /// 添加缓存(异步方式)
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <param name="value">缓存Value</param>
            /// <param name="expiresSliding">滑动过期时长(如果在过期时间内有操作,则以当前时间点延长过期时间)</param>
            /// <param name="expiressAbsoulte">绝对过期时长</param>
            /// <returns></returns>
            Task<bool> AddAsync(string key, object value, TimeSpan expiresSliding, TimeSpan expiressAbsoulte);
    
            /// <summary>
            /// 添加缓存
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <param name="value">缓存Value</param>
            /// <param name="expiresIn">缓存时长</param>
            /// <param name="isSliding">是否滑动过期(如果在过期时间内有操作,则以当前时间点延长过期时间)</param>
            /// <returns></returns>
            bool Add(string key, object value, TimeSpan expiresIn, bool isSliding = false);
    
            /// <summary>
            /// 添加缓存(异步方式)
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <param name="value">缓存Value</param>
            /// <param name="expiresIn">缓存时长</param>
            /// <param name="isSliding">是否滑动过期(如果在过期时间内有操作,则以当前时间点延长过期时间)</param>
            /// <returns></returns>
            Task<bool> AddAsync(string key, object value, TimeSpan expiresIn, bool isSliding = false);
    
            /// <summary>
            /// 删除缓存
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <returns></returns>
            bool Remove(string key);
    
            /// <summary>
            /// 删除缓存(异步方式)
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <returns></returns>
            Task<bool> RemoveAsync(string key);
    
            /// <summary>
            /// 批量删除缓存
            /// </summary>
            /// <param name="key">缓存Key集合</param>
            /// <returns></returns>
            void RemoveAll(IEnumerable<string> keys);
    
            /// <summary>
            /// 批量删除缓存(异步方式)
            /// </summary>
            /// <param name="key">缓存Key集合</param>
            /// <returns></returns>
            Task RemoveAllAsync(IEnumerable<string> keys);
    
            /// <summary>
            /// 获取缓存
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <returns></returns>
            T Get<T>(string key) where T : class;
    
            /// <summary>
            /// 获取缓存(异步方式)
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <returns></returns>
            Task<T> GetAsync<T>(string key) where T : class;
    
            /// <summary>
            /// 获取缓存
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <returns></returns>
            object Get(string key);
    
            /// <summary>
            /// 获取缓存(异步方式)
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <returns></returns>
            Task<object> GetAsync(string key);
    
            /// <summary>
            /// 获取缓存集合
            /// </summary>
            /// <param name="keys">缓存Key集合</param>
            /// <returns></returns>
            IDictionary<string, object> GetAll(IEnumerable<string> keys);
    
            /// <summary>
            /// 获取缓存集合(异步方式)
            /// </summary>
            /// <param name="keys">缓存Key集合</param>
            /// <returns></returns>
            Task<IDictionary<string, object>> GetAllAsync(IEnumerable<string> keys);
    
            /// <summary>
            /// 修改缓存
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <param name="value">新的缓存Value</param>
            /// <returns></returns>
            bool Replace(string key, object value);
    
            /// <summary>
            /// 修改缓存(异步方式)
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <param name="value">新的缓存Value</param>
            /// <returns></returns>
            //Task<bool> ReplaceAsync(string key, object value);
    
            /// <summary>
            /// 修改缓存
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <param name="value">新的缓存Value</param>
            /// <param name="expiresSliding">滑动过期时长(如果在过期时间内有操作,则以当前时间点延长过期时间)</param>
            /// <param name="expiressAbsoulte">绝对过期时长</param>
            /// <returns></returns>
            bool Replace(string key, object value, TimeSpan expiresSliding, TimeSpan expiressAbsoulte);
    
            /// <summary>
            /// 修改缓存(异步方式)
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <param name="value">新的缓存Value</param>
            /// <param name="expiresSliding">滑动过期时长(如果在过期时间内有操作,则以当前时间点延长过期时间)</param>
            /// <param name="expiressAbsoulte">绝对过期时长</param>
            /// <returns></returns>
            //Task<bool> ReplaceAsync(string key, object value, TimeSpan expiresSliding, TimeSpan expiressAbsoulte);
    
            /// <summary>
            /// 修改缓存
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <param name="value">新的缓存Value</param>
            /// <param name="expiresIn">缓存时长</param>
            /// <param name="isSliding">是否滑动过期(如果在过期时间内有操作,则以当前时间点延长过期时间)</param>
            /// <returns></returns>
            bool Replace(string key, object value, TimeSpan expiresIn, bool isSliding = false);
    
            /// <summary>
            /// 修改缓存(异步方式)
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <param name="value">新的缓存Value</param>
            /// <param name="expiresIn">缓存时长</param>
            /// <param name="isSliding">是否滑动过期(如果在过期时间内有操作,则以当前时间点延长过期时间)</param>
            /// <returns></returns>
            //Task<bool> ReplaceAsync(string key, object value, TimeSpan expiresIn, bool isSliding = false);
        }
    }
    View Code

    缓存实现类 MemoryCacheService

    using Microsoft.Extensions.Caching.Memory;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using WebAppNetCore.Interface;
    
    namespace WebAppNetCore.Services
    {
        public class MemoryCacheService : ICacheService
        {
            protected IMemoryCache _cache;
            public MemoryCacheService(IMemoryCache cache)
            {
                _cache = cache;
            }
    
            /// <summary>
            /// 添加缓存
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <param name="value">缓存Value</param>
            /// <returns></returns>
            public bool Add(string key, object value)
            {
                if (key == null)
                {
                    throw new ArgumentNullException(nameof(key));
                }
                if (value == null)
                {
                    throw new ArgumentNullException(nameof(value));
                }
                _cache.Set(key, value);
                return Exists(key);
            }
            /// <summary>
            /// 添加缓存
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <param name="value">缓存Value</param>
            /// <param name="expiresSliding">滑动过期时长(如果在过期时间内有操作,则以当前时间点延长过期时间)</param>
            /// <param name="expiressAbsoulte">绝对过期时长</param>
            /// <returns></returns>
            public bool Add(string key, object value, TimeSpan expiresSliding, TimeSpan expiressAbsoulte)
            {
                if (key == null)
                {
                    throw new ArgumentNullException(nameof(key));
                }
                if (value == null)
                {
                    throw new ArgumentNullException(nameof(value));
                }
                _cache.Set(key, value,
                        new MemoryCacheEntryOptions()
                        .SetSlidingExpiration(expiresSliding)
                        .SetAbsoluteExpiration(expiressAbsoulte)
                        );
    
                return Exists(key);
            }
            /// <summary>
            /// 添加缓存
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <param name="value">缓存Value</param>
            /// <param name="expiresIn">缓存时长</param>
            /// <param name="isSliding">是否滑动过期(如果在过期时间内有操作,则以当前时间点延长过期时间)</param>
            /// <returns></returns>
            public bool Add(string key, object value, TimeSpan expiresIn, bool isSliding = false)
            {
                if (key == null)
                {
                    throw new ArgumentNullException(nameof(key));
                }
                if (value == null)
                {
                    throw new ArgumentNullException(nameof(value));
                }
                if (isSliding)
                    _cache.Set(key, value,
                        new MemoryCacheEntryOptions()
                        .SetSlidingExpiration(expiresIn)
                        );
                else
                    _cache.Set(key, value,
                    new MemoryCacheEntryOptions()
                    .SetAbsoluteExpiration(expiresIn)
                    );
    
                return Exists(key);
            }
            public Task<bool> AddAsync(string key, object value)
            {
                throw new NotImplementedException();
            }
    
            public Task<bool> AddAsync(string key, object value, TimeSpan expiresSliding, TimeSpan expiressAbsoulte)
            {
                throw new NotImplementedException();
            }
    
            public Task<bool> AddAsync(string key, object value, TimeSpan expiresIn, bool isSliding = false)
            {
                throw new NotImplementedException();
            }
    
            /// <summary>
            /// 验证缓存项是否存在
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <returns></returns>
            public bool Exists(string key)
            {
                if (key == null)
                {
                    throw new ArgumentNullException(nameof(key));
                }
                object cached;
                return _cache.TryGetValue(key, out cached);
            }
    
            public Task<bool> ExistsAsync(string key)
            {
                throw new NotImplementedException();
            }
    
            /// <summary>
            /// 获取缓存
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <returns></returns>
            public T Get<T>(string key) where T : class
            {
                if (key == null)
                {
                    throw new ArgumentNullException(nameof(key));
                }
                return _cache.Get(key) as T;
            }
            /// <summary>
            /// 获取缓存
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <returns></returns>
            public object Get(string key)
            {
                if (key == null)
                {
                    throw new ArgumentNullException(nameof(key));
                }
                return _cache.Get(key);
            }
            /// <summary>
            /// 获取缓存集合
            /// </summary>
            /// <param name="keys">缓存Key集合</param>
            /// <returns></returns>
            public IDictionary<string, object> GetAll(IEnumerable<string> keys)
            {
                if (keys == null)
                {
                    throw new ArgumentNullException(nameof(keys));
                }
    
                var dict = new Dictionary<string, object>();
    
                keys.ToList().ForEach(item => dict.Add(item, _cache.Get(item)));
    
                return dict;
            }
    
            public Task<IDictionary<string, object>> GetAllAsync(IEnumerable<string> keys)
            {
                throw new NotImplementedException();
            }
    
            public Task<T> GetAsync<T>(string key) where T : class
            {
                throw new NotImplementedException();
            }
    
            public Task<object> GetAsync(string key)
            {
                throw new NotImplementedException();
            }
    
            /// <summary>
            /// 删除缓存
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <returns></returns>
            public bool Remove(string key)
            {
                if (key == null)
                {
                    throw new ArgumentNullException(nameof(key));
                }
                _cache.Remove(key);
    
                return !Exists(key);
            }
            /// <summary>
            /// 批量删除缓存
            /// </summary>
            /// <param name="key">缓存Key集合</param>
            /// <returns></returns>
            public void RemoveAll(IEnumerable<string> keys)
            {
                if (keys == null)
                {
                    throw new ArgumentNullException(nameof(keys));
                }
    
                keys.ToList().ForEach(item => _cache.Remove(item));
            }
    
            public Task RemoveAllAsync(IEnumerable<string> keys)
            {
                throw new NotImplementedException();
            }
    
            public Task<bool> RemoveAsync(string key)
            {
                throw new NotImplementedException();
            }
    
            /// <summary>
            /// 修改缓存
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <param name="value">新的缓存Value</param>
            /// <returns></returns>
            public bool Replace(string key, object value)
            {
                if (key == null)
                {
                    throw new ArgumentNullException(nameof(key));
                }
                if (value == null)
                {
                    throw new ArgumentNullException(nameof(value));
                }
                if (Exists(key))
                    if (!Remove(key)) return false;
    
                return Add(key, value);
    
            }
            /// <summary>
            /// 修改缓存
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <param name="value">新的缓存Value</param>
            /// <param name="expiresSliding">滑动过期时长(如果在过期时间内有操作,则以当前时间点延长过期时间)</param>
            /// <param name="expiressAbsoulte">绝对过期时长</param>
            /// <returns></returns>
            public bool Replace(string key, object value, TimeSpan expiresSliding, TimeSpan expiressAbsoulte)
            {
                if (key == null)
                {
                    throw new ArgumentNullException(nameof(key));
                }
                if (value == null)
                {
                    throw new ArgumentNullException(nameof(value));
                }
                if (Exists(key))
                    if (!Remove(key)) return false;
    
                return Add(key, value, expiresSliding, expiressAbsoulte);
            }
            /// <summary>
            /// 修改缓存
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <param name="value">新的缓存Value</param>
            /// <param name="expiresIn">缓存时长</param>
            /// <param name="isSliding">是否滑动过期(如果在过期时间内有操作,则以当前时间点延长过期时间)</param>
            /// <returns></returns>
            public bool Replace(string key, object value, TimeSpan expiresIn, bool isSliding = false)
            {
                if (key == null)
                {
                    throw new ArgumentNullException(nameof(key));
                }
                if (value == null)
                {
                    throw new ArgumentNullException(nameof(value));
                }
                if (Exists(key))
                    if (!Remove(key)) return false;
    
                return Add(key, value, expiresIn, isSliding);
            }
    
            public void Dispose()
            {
                if (_cache != null)
                    _cache.Dispose();
                GC.SuppressFinalize(this);
            }
        }
    }
    View Code

    缓存实现类 RedisCacheService

    using Microsoft.Extensions.Caching.Redis;
    using Newtonsoft.Json;
    using StackExchange.Redis;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using WebAppNetCore.Interface;
    
    namespace WebAppNetCore.Services
    {
        public class RedisCacheService : ICacheService
        {
            protected IDatabase _cache;
    
            private ConnectionMultiplexer _connection;
    
            private readonly string _instance;
    
    
            public RedisCacheService(RedisCacheOptions options, int database = 0)
            {
                _connection = ConnectionMultiplexer.Connect(options.Configuration);
                _cache = _connection.GetDatabase(database);
                _instance = options.InstanceName;
            }
    
            /// <summary>
            /// 验证缓存项是否存在
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <returns></returns>
            public bool Exists(string key)
            {
                if (key == null)
                {
                    throw new ArgumentNullException(nameof(key));
                }
                return _cache.KeyExists(GetKeyForRedis(key));
            }
    
    
            /// <summary>
            /// 添加缓存
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <param name="value">缓存Value</param>
            /// <returns></returns>
            public bool Add(string key, object value)
            {
                if (key == null)
                {
                    throw new ArgumentNullException(nameof(key));
                }
                return _cache.StringSet(GetKeyForRedis(key), Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(value)));
            }
            /// <summary>
            /// 添加缓存
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <param name="value">缓存Value</param>
            /// <param name="expiresSliding">滑动过期时长(如果在过期时间内有操作,则以当前时间点延长过期时间,Redis中无效)</param>
            /// <param name="expiressAbsoulte">绝对过期时长</param>
            /// <returns></returns>
            public bool Add(string key, object value, TimeSpan expiresSliding, TimeSpan expiressAbsoulte)
            {
                if (key == null)
                {
                    throw new ArgumentNullException(nameof(key));
                }
                return _cache.StringSet(GetKeyForRedis(key), Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(value)), expiressAbsoulte);
            }
            /// <summary>
            /// 添加缓存
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <param name="value">缓存Value</param>
            /// <param name="expiresIn">缓存时长</param>
            /// <param name="isSliding">是否滑动过期(如果在过期时间内有操作,则以当前时间点延长过期时间,Redis中无效)</param>
            /// <returns></returns>
            public bool Add(string key, object value, TimeSpan expiresIn, bool isSliding = false)
            {
                if (key == null)
                {
                    throw new ArgumentNullException(nameof(key));
                }
    
    
                return _cache.StringSet(GetKeyForRedis(key), Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(value)), expiresIn);
            }
    
            /// <summary>
            /// 删除缓存
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <returns></returns>
            public bool Remove(string key)
            {
                if (key == null)
                {
                    throw new ArgumentNullException(nameof(key));
                }
                return _cache.KeyDelete(GetKeyForRedis(key));
            }
            /// <summary>
            /// 批量删除缓存
            /// </summary>
            /// <param name="key">缓存Key集合</param>
            /// <returns></returns>
            public void RemoveAll(IEnumerable<string> keys)
            {
                if (keys == null)
                {
                    throw new ArgumentNullException(nameof(keys));
                }
    
                keys.ToList().ForEach(item => Remove(item));
            }
    
            /// <summary>
            /// 获取缓存
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <returns></returns>
            public T Get<T>(string key) where T : class
            {
                if (key == null)
                {
                    throw new ArgumentNullException(nameof(key));
                }
    
                var value = _cache.StringGet(GetKeyForRedis(key));
    
                if (!value.HasValue)
                {
                    return default(T);
                }
    
                return JsonConvert.DeserializeObject<T>(value);
            }
            
            /// <summary>
            /// 获取缓存
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <returns></returns>
            public object Get(string key)
            {
                if (key == null)
                {
                    throw new ArgumentNullException(nameof(key));
                }
    
                var value = _cache.StringGet(GetKeyForRedis(key));
    
                if (!value.HasValue)
                {
                    return null;
                }
                return JsonConvert.DeserializeObject(value);
            }
            /// <summary>
            /// 获取缓存集合
            /// </summary>
            /// <param name="keys">缓存Key集合</param>
            /// <returns></returns>
            public IDictionary<string, object> GetAll(IEnumerable<string> keys)
            {
                if (keys == null)
                {
                    throw new ArgumentNullException(nameof(keys));
                }
                var dict = new Dictionary<string, object>();
    
                keys.ToList().ForEach(item => dict.Add(item, Get(GetKeyForRedis(item))));
    
                return dict;
            }
    
    
    
            /// <summary>
            /// 修改缓存
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <param name="value">新的缓存Value</param>
            /// <returns></returns>
            public bool Replace(string key, object value)
            {
                if (key == null)
                {
                    throw new ArgumentNullException(nameof(key));
                }
    
                if (Exists(key))
                    if (!Remove(key))
                        return false;
    
                return Add(key, value);
    
            }
            /// <summary>
            /// 修改缓存
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <param name="value">新的缓存Value</param>
            /// <param name="expiresSliding">滑动过期时长(如果在过期时间内有操作,则以当前时间点延长过期时间)</param>
            /// <param name="expiressAbsoulte">绝对过期时长</param>
            /// <returns></returns>
            public bool Replace(string key, object value, TimeSpan expiresSliding, TimeSpan expiressAbsoulte)
            {
                if (key == null)
                {
                    throw new ArgumentNullException(nameof(key));
                }
    
                if (Exists(key))
                    if (!Remove(key))
                        return false;
    
                return Add(key, value, expiresSliding, expiressAbsoulte);
            }
            /// <summary>
            /// 修改缓存
            /// </summary>
            /// <param name="key">缓存Key</param>
            /// <param name="value">新的缓存Value</param>
            /// <param name="expiresIn">缓存时长</param>
            /// <param name="isSliding">是否滑动过期(如果在过期时间内有操作,则以当前时间点延长过期时间)</param>
            /// <returns></returns>
            public bool Replace(string key, object value, TimeSpan expiresIn, bool isSliding = false)
            {
                if (key == null)
                {
                    throw new ArgumentNullException(nameof(key));
                }
    
                if (Exists(key))
                    if (!Remove(key)) return false;
    
                return Add(key, value, expiresIn, isSliding);
            }
    
            //这里我们写了个方法,来组合Key值和实例名,就是Key值转为 实例名+Key
            public string GetKeyForRedis(string key)
            {
                return _instance + key;
            }
    
            public void Dispose()
            {
                if (_connection != null)
                    _connection.Dispose();
                GC.SuppressFinalize(this);
            }
    
        }
    }
    View Code

    配置文件字段映射类

        public class AppsettingConfig
        {
            public string RedisConnection { get; set; }
    
            public string InstanceName { get; set; }
    
            public bool IsRedis { get; set; }
        }

    starup:

           public void ConfigureServices(IServiceCollection services)
            {
                //注入redis或者memorycache
                CacheInjection.Initialize(services, _appsettingConfig.RedisConnection,_appsettingConfig.InstanceName,_appsettingConfig.IsRedis); 
    }

    依赖注入

        public class CacheInjection
        {
            public static void Initialize(IServiceCollection services,string redisCon,string instanceName,bool isRedis = false)
            {
                //注册缓存服务
                services.AddMemoryCache();
                if (isRedis)
                {
                    //Use Redis
                    services.AddSingleton(typeof(ICacheService), new RedisCacheService(new RedisCacheOptions
                    {
                        Configuration = redisCon,
                        InstanceName = instanceName
                    }, 0));
                }
                else
                {
                    //Use MemoryCache
                    services.AddSingleton<IMemoryCache>(factory =>
                    {
                        var cache = new MemoryCache(new MemoryCacheOptions());
                        return cache;
                    });
                    services.AddSingleton<ICacheService, MemoryCacheService>();
                }
            }
        }

    参考文章:https://www.cnblogs.com/yuangang/p/5800113.html

  • 相关阅读:
    openldap
    Java实现 洛谷 P1200 [USACO1.1]你的飞碟在这儿Your Ride Is He…
    Java实现 洛谷 P1200 [USACO1.1]你的飞碟在这儿Your Ride Is He…
    Java实现 洛谷 P2141 珠心算测验
    Java实现 洛谷 P2141 珠心算测验
    Java实现 洛谷 P2141 珠心算测验
    Java实现 洛谷 P2141 珠心算测验
    Java实现 洛谷 P2141 珠心算测验
    Java实现 洛谷 P1567 统计天数
    Java实现 洛谷 P1567 统计天数
  • 原文地址:https://www.cnblogs.com/chuankang/p/8798826.html
Copyright © 2011-2022 走看看