纵有疾风起
人生不言弃

Redis 小白指南(一)- 简介、安装、GUI 和 C# 驱动介绍

Redis 小白指南(一)- 简介、安装、GUI 和 C# 驱动介绍

目录

  • 简介
  • 安装
  • 入门指令
  • GUI 工具
  • C# 驱动介绍

 

简介

  ANSI C 编写,开源,基于内存,可持久化,一个键值对的数据库,用法简单。

  支持的类型:字符串、散列、列表、集合和有序集合。

  因为 Redis 默认将所有数据都存储到内存中,并且内存的读写速度远远高于硬盘,因此,比其他基于硬盘存储的数据库在性能上体现的优势非常明显。不过这样也引发了一个数据安全性的问题,程序异常或退出后数据会出现丢失的情形,现在新的版本已经提供了数据持久化(RDB + AOF)的支持,即可以将内存中的数据异步写入到硬盘上,同时不会影响其它功能的运行。

  redis 可以为每个键设置生存时间,到期自动删除,也就是说可以作为缓存系统(这也是企业主要的运用场景)进行使用。

  相对于 Memcached,简单的说:Redis 单线程模型,Memcached 支持多线程,但 Redis 支持的功能和数据类型更多,更简单易用,并且 redis 的性能在绝大部分场合下都不会成为系统瓶颈,不过在多核服务器上使用的情况下,理论上 Memcached 比 redis 性能更高。所以,在新项目中,建议使用 redis 代替 Memcached。

  Redis 还可以限定数据占用的最大内存空间,在数据达到空间限制后按一定规则自动淘汰不需要的键;也支持构建高性能的队列(不过很多企业会选择第三方的 MQ,如:RabbitMQ)。

Redis 小白指南(一)- 简介、安装、GUI 和 C# 驱动介绍插图

 

安装

  它的约定次版本号(即第一个小数点后的数字)为偶数的版本是稳定版(如 v2.8,v3.0)。

  为了减少学习成本,我们直接使用 windows 版本的就可以,想学习 Linux 部署的,先搜搜别人的文章吧。

  redis-windows-3.0(搜索了一下,最新的正式版是 3.2):下载地址

Redis 小白指南(一)- 简介、安装、GUI 和 C# 驱动介绍插图1

 

  文件简单说明:

Redis 小白指南(一)- 简介、安装、GUI 和 C# 驱动介绍插图2

 

入门指令

  1.启动 CMD:

$ redis-server$ redis-server --port 6380 //自定义端口

Redis 小白指南(一)- 简介、安装、GUI 和 C# 驱动介绍插图3

 

  2.停止:

$ redis-cli SHUTDOWN

 

  3.PING 命令:

  测试与 redis 的连接是否正常,正常返回 PONG

$ redis-cli PING   

Redis 小白指南(一)- 简介、安装、GUI 和 C# 驱动介绍插图4

 

GUI 工具

  Redis Client:一个基于Java SWT 和 Jedis 编写的 redis 客户端 GUI 工具。可从 https://github.com/caoxinyu/RedisClient 下载。

Redis 小白指南(一)- 简介、安装、GUI 和 C# 驱动介绍插图5

 

  从图可知,redis 包含了 16 个数据库。上面的每个数据库默认从 0 开始的递增数字命名。

 

  因为该程序打包后的压缩包 >10 M,无法上传到 cnblogs,如有需要的童鞋请加群在群文件中下载压缩包。

Redis 小白指南(一)- 简介、安装、GUI 和 C# 驱动介绍插图6

 

  补充一下园友(心态要好)推荐的其它 GUI 工具 RedisDeskTopManager:https://redisdesktop.com/download

 

C# 驱动

  之前在 《使用 StackExchange.Redis 封装属于自己的 RedisHelper》 曾经发布了一篇使用 StackExchange.Redis 进行了简单封装的 RedisHelper,你可以选择查看之前的文章,从中借鉴一些思想或者给出一些建议。

  这里是更新后的 Helper 代码(直接展开即可),代码的后续更新在 GitHub 上。

   1 #region   2    3 using System;   4 using System.Collections.Generic;   5 using System.Configuration;   6 using System.IO;   7 using System.Linq;   8 using System.Runtime.Serialization.Formatters.Binary;   9 using System.Threading.Tasks;  10 using StackExchange.Redis;  11   12 #endregion  13   14 namespace Wen.Helpers.Common.Redis  15 {  16     /// <summary>  17     /// Redis 助手  18     /// </summary>  19     public class RedisHelper  20     {  21         /// <summary>  22         /// 获取 Redis 连接对象  23         /// </summary>  24         /// <returns></returns>  25         public IConnectionMultiplexer GetConnectionRedisMultiplexer()  26         {  27             if (_connMultiplexer == null || !_connMultiplexer.IsConnected)  28                 lock (Locker)  29                 {  30                     if (_connMultiplexer == null || !_connMultiplexer.IsConnected)  31                         _connMultiplexer = ConnectionMultiplexer.Connect(ConnectionString);  32                 }  33   34             return _connMultiplexer;  35         }  36   37         #region 其它  38   39         public ITransaction GetTransaction()  40         {  41             return _db.CreateTransaction();  42         }  43   44         #endregion 其它  45   46         #region private field  47   48         /// <summary>  49         /// 连接字符串  50         /// </summary>  51         private static readonly string ConnectionString;  52   53         /// <summary>  54         /// redis 连接对象  55         /// </summary>  56         private static IConnectionMultiplexer _connMultiplexer;  57   58         /// <summary>  59         /// 默认的 Key 值(用来当作 RedisKey 的前缀)  60         /// </summary>  61         private static readonly string DefaultKey;  62   63         /// <summary>  64         ///  65         /// </summary>  66         private static readonly object Locker = new object();  67   68         /// <summary>  69         /// 数据库  70         /// </summary>  71         private readonly IDatabase _db;  72   73         #endregion private field  74   75         #region 构造函数  76   77         static RedisHelper()  78         {  79             ConnectionString = ConfigurationManager.ConnectionStrings["RedisConnectionString"].ConnectionString;  80             _connMultiplexer = ConnectionMultiplexer.Connect(ConnectionString);  81             DefaultKey = ConfigurationManager.AppSettings["Redis.DefaultKey"];  82             AddRegisterEvent();  83         }  84   85         public RedisHelper(int db = 0)  86         {  87             _db = _connMultiplexer.GetDatabase(db);  88         }  89   90         #endregion 构造函数  91   92         #region String 操作  93   94         /// <summary>  95         /// 设置 key 并保存字符串(如果 key 已存在,则覆盖值)  96         /// </summary>  97         /// <param name="key"></param>  98         /// <param name="value"></param>  99         /// <param name="expiry"></param> 100         /// <returns></returns> 101         public bool StringSet(string key, string value, TimeSpan? expiry = null) 102         { 103             key = AddKeyPrefix(key); 104             return _db.StringSet(key, value, expiry); 105         } 106  107         /// <summary> 108         /// 保存多个 Key-value 109         /// </summary> 110         /// <param name="keyValuePairs"></param> 111         /// <returns></returns> 112         public bool StringSet(IEnumerable<KeyValuePair<string, string>> keyValuePairs) 113         { 114             var pairs = keyValuePairs.Select(x => new KeyValuePair<RedisKey, RedisValue>(AddKeyPrefix(x.Key), x.Value)); 115             return _db.StringSet(pairs.ToArray()); 116         } 117  118         /// <summary> 119         /// 获取字符串 120         /// </summary> 121         /// <param name="redisKey"></param> 122         /// <param name="expiry"></param> 123         /// <returns></returns> 124         public string StringGet(string redisKey) 125         { 126             redisKey = AddKeyPrefix(redisKey); 127             return _db.StringGet(redisKey); 128         } 129  130         /// <summary> 131         /// 存储一个对象(该对象会被序列化保存) 132         /// </summary> 133         /// <param name="key"></param> 134         /// <param name="value"></param> 135         /// <param name="expiry"></param> 136         /// <returns></returns> 137         public bool StringSet<T>(string key, T value, TimeSpan? expiry = null) 138         { 139             key = AddKeyPrefix(key); 140             var json = Serialize(value); 141  142             return _db.StringSet(key, json, expiry); 143         } 144  145         /// <summary> 146         /// 获取一个对象(会进行反序列化) 147         /// </summary> 148         /// <param name="key"></param> 149         /// <param name="expiry"></param> 150         /// <returns></returns> 151         public T StringGet<T>(string key, TimeSpan? expiry = null) 152         { 153             key = AddKeyPrefix(key); 154             return Deserialize<T>(_db.StringGet(key)); 155         } 156  157         /// <summary> 158         /// 在指定 key 处实现增量的递增,如果该键不存在,则在执行前将其设置为 0 159         /// </summary> 160         /// <param name="key"></param> 161         /// <param name="value"></param> 162         /// <returns></returns> 163         public double StringIncrement(string key, double value = 1) 164         { 165             key = AddKeyPrefix(key); 166             return _db.StringIncrement(key, value); 167         } 168  169         /// <summary> 170         /// 在指定 key 处实现增量的递减,如果该键不存在,则在执行前将其设置为 0 171         /// </summary> 172         /// <param name="key"></param> 173         /// <param name="value"></param> 174         /// <returns></returns> 175         public double StringDecrement(string key, double value = 1) 176         { 177             key = AddKeyPrefix(key); 178             return _db.StringDecrement(key, value); 179         } 180  181         #region async 182  183         /// <summary> 184         /// 保存一个字符串值 185         /// </summary> 186         /// <param name="key"></param> 187         /// <param name="value"></param> 188         /// <param name="expiry"></param> 189         /// <returns></returns> 190         public async Task<bool> StringSetAsync(string key, string value, TimeSpan? expiry = null) 191         { 192             key = AddKeyPrefix(key); 193             return await _db.StringSetAsync(key, value, expiry); 194         } 195  196         /// <summary> 197         /// 保存一组字符串值 198         /// </summary> 199         /// <param name="keyValuePairs"></param> 200         /// <returns></returns> 201         public async Task<bool> StringSetAsync(IEnumerable<KeyValuePair<string, string>> keyValuePairs) 202         { 203             var pairs = keyValuePairs.Select(x => new KeyValuePair<RedisKey, RedisValue>(AddKeyPrefix(x.Key), x.Value)); 204             return await _db.StringSetAsync(pairs.ToArray()); 205         } 206  207         /// <summary> 208         /// 获取单个值 209         /// </summary> 210         /// <param name="key"></param> 211         /// <param name="value"></param> 212         /// <param name="expiry"></param> 213         /// <returns></returns> 214         public async Task<string> StringGetAsync(string key, string value, TimeSpan? expiry = null) 215         { 216             key = AddKeyPrefix(key); 217             return await _db.StringGetAsync(key); 218         } 219  220         /// <summary> 221         /// 存储一个对象(该对象会被序列化保存) 222         /// </summary> 223         /// <param name="key"></param> 224         /// <param name="value"></param> 225         /// <param name="expiry"></param> 226         /// <returns></returns> 227         public async Task<bool> StringSetAsync<T>(string key, T value, TimeSpan? expiry = null) 228         { 229             key = AddKeyPrefix(key); 230             var json = Serialize(value); 231             return await _db.StringSetAsync(key, json, expiry); 232         } 233  234         /// <summary> 235         /// 获取一个对象(会进行反序列化) 236         /// </summary> 237         /// <param name="key"></param> 238         /// <param name="expiry"></param> 239         /// <returns></returns> 240         public async Task<T> StringGetAsync<T>(string key, TimeSpan? expiry = null) 241         { 242             key = AddKeyPrefix(key); 243             return Deserialize<T>(await _db.StringGetAsync(key)); 244         } 245  246         /// <summary> 247         /// 在指定 key 处实现增量的递增,如果该键不存在,则在执行前将其设置为 0 248         /// </summary> 249         /// <param name="key"></param> 250         /// <param name="value"></param> 251         /// <returns></returns> 252         public async Task<double> StringIncrementAsync(string key, double value = 1) 253         { 254             key = AddKeyPrefix(key); 255             return await _db.StringIncrementAsync(key, value); 256         } 257  258         /// <summary> 259         /// 在指定 key 处实现增量的递减,如果该键不存在,则在执行前将其设置为 0 260         /// </summary> 261         /// <param name="key"></param> 262         /// <param name="value"></param> 263         /// <returns></returns> 264         public async Task<double> StringDecrementAsync(string key, double value = 1) 265         { 266             key = AddKeyPrefix(key); 267             return await _db.StringDecrementAsync(key, value); 268         } 269  270         #endregion async 271  272         #endregion String 操作 273  274         #region Hash 操作 275  276         /// <summary> 277         /// 判断该字段是否存在 hash 中 278         /// </summary> 279         /// <param name="key"></param> 280         /// <param name="hashField"></param> 281         /// <returns></returns> 282         public bool HashExists(string key, string hashField) 283         { 284             key = AddKeyPrefix(key); 285             return _db.HashExists(key, hashField); 286         } 287  288         /// <summary> 289         /// 从 hash 中移除指定字段 290         /// </summary> 291         /// <param name="key"></param> 292         /// <param name="hashField"></param> 293         /// <returns></returns> 294         public bool HashDelete(string key, string hashField) 295         { 296             key = AddKeyPrefix(key); 297             return _db.HashDelete(key, hashField); 298         } 299  300         /// <summary> 301         /// 从 hash 中移除指定字段 302         /// </summary> 303         /// <param name="key"></param> 304         /// <param name="hashFields"></param> 305         /// <returns></returns> 306         public long HashDelete(string key, IEnumerable<string> hashFields) 307         { 308             key = AddKeyPrefix(key); 309             var fields = hashFields.Select(x => (RedisValue) x); 310  311             return _db.HashDelete(key, fields.ToArray()); 312         } 313  314         /// <summary> 315         /// 在 hash 设定值 316         /// </summary> 317         /// <param name="key"></param> 318         /// <param name="hashField"></param> 319         /// <param name="value"></param> 320         /// <returns></returns> 321         public bool HashSet(string key, string hashField, string value) 322         { 323             key = AddKeyPrefix(key); 324             return _db.HashSet(key, hashField, value); 325         } 326  327         /// <summary> 328         /// 在 hash 中设定值 329         /// </summary> 330         /// <param name="key"></param> 331         /// <param name="hashFields"></param> 332         public void HashSet(string key, IEnumerable<KeyValuePair<string, string>> hashFields) 333         { 334             key = AddKeyPrefix(key); 335             var entries = hashFields.Select(x => new HashEntry(x.Key, x.Value)); 336  337             _db.HashSet(key, entries.ToArray()); 338         } 339  340         /// <summary> 341         /// 在 hash 中获取值 342         /// </summary> 343         /// <param name="key"></param> 344         /// <param name="hashField"></param> 345         /// <returns></returns> 346         public string HashGet(string key, string hashField) 347         { 348             key = AddKeyPrefix(key); 349             return _db.HashGet(key, hashField); 350         } 351  352         /// <summary> 353         /// 在 hash 中获取值 354         /// </summary> 355         /// <param name="key"></param> 356         /// <param name="hashFields"></param> 357         /// <returns></returns> 358         public IEnumerable<string> HashGet(string key, IEnumerable<string> hashFields) 359         { 360             key = AddKeyPrefix(key); 361             var fields = hashFields.Select(x => (RedisValue) x); 362  363             return ConvertStrings(_db.HashGet(key, fields.ToArray())); 364         } 365  366         /// <summary> 367         /// 从 hash 返回所有的字段值 368         /// </summary> 369         /// <param name="key"></param> 370         /// <returns></returns> 371         public IEnumerable<string> HashKeys(string key) 372         { 373             key = AddKeyPrefix(key); 374             return ConvertStrings(_db.HashKeys(key)); 375         } 376  377         /// <summary> 378         /// 返回 hash 中的所有值 379         /// </summary> 380         /// <param name="key"></param> 381         /// <returns></returns> 382         public IEnumerable<string> HashValues(string key) 383         { 384             key = AddKeyPrefix(key); 385             return ConvertStrings(_db.HashValues(key)); 386         } 387  388         /// <summary> 389         /// 在 hash 设定值(序列化) 390         /// </summary> 391         /// <param name="key"></param> 392         /// <param name="hashField"></param> 393         /// <param name="redisValue"></param> 394         /// <returns></returns> 395         public bool HashSet<T>(string key, string hashField, T redisValue) 396         { 397             key = AddKeyPrefix(key); 398             var json = Serialize(redisValue); 399  400             return _db.HashSet(key, hashField, json); 401         } 402  403         /// <summary> 404         /// 在 hash 中获取值(反序列化) 405         /// </summary> 406         /// <param name="key"></param> 407         /// <param name="hashField"></param> 408         /// <returns></returns> 409         public T HashGet<T>(string key, string hashField) 410         { 411             key = AddKeyPrefix(key); 412             return Deserialize<T>(_db.HashGet(key, hashField)); 413         } 414  415         /// <summary> 416         /// 指定键递增 417         /// </summary> 418         /// <param name="key"></param> 419         /// <param name="hashField"></param> 420         /// <param name="value"></param> 421         /// <returns></returns> 422         public double HashIncrement(string key, string hashField, double value = 1) 423         { 424             key = AddKeyPrefix(key); 425             return _db.HashIncrement(key, hashField, value); 426         } 427  428         /// <summary> 429         /// 指定键递减 430         /// </summary> 431         /// <param name="key"></param> 432         /// <param name="hashField"></param> 433         /// <param name="value"></param> 434         /// <returns></returns> 435         public double HashDecrement(string key, string hashField, double value = 1) 436         { 437             key = AddKeyPrefix(key); 438             return _db.HashDecrement(key, hashField, value); 439         } 440  441         #region async 442  443         /// <summary> 444         /// 判断该字段是否存在 hash 中 445         /// </summary> 446         /// <param name="redisKey"></param> 447         /// <param name="hashField"></param> 448         /// <returns></returns> 449         public async Task<bool> HashExistsAsync(string redisKey, string hashField) 450         { 451             redisKey = AddKeyPrefix(redisKey); 452             return await _db.HashExistsAsync(redisKey, hashField); 453         } 454  455         /// <summary> 456         /// 从 hash 中移除指定字段 457         /// </summary> 458         /// <param name="redisKey"></param> 459         /// <param name="hashField"></param> 460         /// <returns></returns> 461         public async Task<bool> HashDeleteAsync(string redisKey, string hashField) 462         { 463             redisKey = AddKeyPrefix(redisKey); 464             return await _db.HashDeleteAsync(redisKey, hashField); 465         } 466  467         /// <summary> 468         /// 从 hash 中移除指定字段 469         /// </summary> 470         /// <param name="redisKey"></param> 471         /// <param name="hashFields"></param> 472         /// <returns></returns> 473         public async Task<long> HashDeleteAsync(string redisKey, IEnumerable<string> hashFields) 474         { 475             redisKey = AddKeyPrefix(redisKey); 476             var fields = hashFields.Select(x => (RedisValue) x); 477  478             return await _db.HashDeleteAsync(redisKey, fields.ToArray()); 479         } 480  481         /// <summary> 482         /// 在 hash 设定值 483         /// </summary> 484         /// <param name="redisKey"></param> 485         /// <param name="hashField"></param> 486         /// <param name="value"></param> 487         /// <returns></returns> 488         public async Task<bool> HashSetAsync(string redisKey, string hashField, string value) 489         { 490             redisKey = AddKeyPrefix(redisKey); 491             return await _db.HashSetAsync(redisKey, hashField, value); 492         } 493  494         /// <summary> 495         /// 在 hash 中设定值 496         /// </summary> 497         /// <param name="redisKey"></param> 498         /// <param name="hashFields"></param> 499         public async Task HashSetAsync(string redisKey, IEnumerable<KeyValuePair<string, string>> hashFields) 500         { 501             redisKey = AddKeyPrefix(redisKey); 502             var entries = hashFields.Select(x => new HashEntry(AddKeyPrefix(x.Key), x.Value)); 503             await _db.HashSetAsync(redisKey, entries.ToArray()); 504         } 505  506         /// <summary> 507         /// 在 hash 中获取值 508         /// </summary> 509         /// <param name="redisKey"></param> 510         /// <param name="hashField"></param> 511         /// <returns></returns> 512         public async Task<string> HashGetAsync(string redisKey, string hashField) 513         { 514             redisKey = AddKeyPrefix(redisKey); 515             return await _db.HashGetAsync(redisKey, hashField); 516         } 517  518         /// <summary> 519         /// 在 hash 中获取值 520         /// </summary> 521         /// <param name="redisKey"></param> 522         /// <param name="hashFields"></param> 523         /// <param name="value"></param> 524         /// <returns></returns> 525         public async Task<IEnumerable<string>> HashGetAsync(string redisKey, IEnumerable<string> hashFields, 526             string value) 527         { 528             redisKey = AddKeyPrefix(redisKey); 529             var fields = hashFields.Select(x => (RedisValue) x); 530  531             return ConvertStrings(await _db.HashGetAsync(redisKey, fields.ToArray())); 532         } 533  534         /// <summary> 535         /// 从 hash 返回所有的字段值 536         /// </summary> 537         /// <param name="redisKey"></param> 538         /// <returns></returns> 539         public async Task<IEnumerable<string>> HashKeysAsync(string redisKey) 540         { 541             redisKey = AddKeyPrefix(redisKey); 542             return ConvertStrings(await _db.HashKeysAsync(redisKey)); 543         } 544  545         /// <summary> 546         /// 返回 hash 中的所有值 547         /// </summary> 548         /// <param name="redisKey"></param> 549         /// <returns></returns> 550         public async Task<IEnumerable<string>> HashValuesAsync(string redisKey) 551         { 552             redisKey = AddKeyPrefix(redisKey); 553             return ConvertStrings(await _db.HashValuesAsync(redisKey)); 554         } 555  556         /// <summary> 557         /// 在 hash 设定值(序列化) 558         /// </summary> 559         /// <param name="redisKey"></param> 560         /// <param name="hashField"></param> 561         /// <param name="value"></param> 562         /// <returns></returns> 563         public async Task<bool> HashSetAsync<T>(string redisKey, string hashField, T value) 564         { 565             redisKey = AddKeyPrefix(redisKey); 566             var json = Serialize(value); 567             return await _db.HashSetAsync(redisKey, hashField, json); 568         } 569  570         /// <summary> 571         /// 在 hash 中获取值(反序列化) 572         /// </summary> 573         /// <param name="redisKey"></param> 574         /// <param name="hashField"></param> 575         /// <returns></returns> 576         public async Task<T> HashGetAsync<T>(string redisKey, string hashField) 577         { 578             redisKey = AddKeyPrefix(redisKey); 579             return Deserialize<T>(await _db.HashGetAsync(redisKey, hashField)); 580         } 581  582         /// <summary> 583         /// 指定键递增 584         /// </summary> 585         /// <param name="key"></param> 586         /// <param name="hashField"></param> 587         /// <param name="value"></param> 588         /// <returns></returns> 589         public async Task<double> HashIncrementAsync(string key, string hashField, double value = 1) 590         { 591             key = AddKeyPrefix(key); 592             return await _db.HashIncrementAsync(key, hashField, value); 593         } 594  595         /// <summary> 596         /// 指定键递减 597         /// </summary> 598         /// <param name="key"></param> 599         /// <param name="hashField"></param> 600         /// <param name="value"></param> 601         /// <returns></returns> 602         public async Task<double> HashDecrementAsync(string key, string hashField, double value = 1) 603         { 604             key = AddKeyPrefix(key); 605             return await _db.HashDecrementAsync(key, hashField, value); 606         } 607  608         #endregion async 609  610         #endregion Hash 操作 611  612         #region List 操作 613  614         /// <summary> 615         /// 移除并返回存储在该键列表的第一个元素 616         /// </summary> 617         /// <param name="key"></param> 618         /// <returns></returns> 619         public string ListLeftPop(string key) 620         { 621             key = AddKeyPrefix(key); 622             return _db.ListLeftPop(key); 623         } 624  625         /// <summary> 626         /// 出列,移除并返回存储在该键列表的最后一个元素 627         /// </summary> 628         /// <param name="key"></param> 629         /// <returns></returns> 630         public string ListRightPop(string key) 631         { 632             key = AddKeyPrefix(key); 633             return _db.ListRightPop(key); 634         } 635  636         /// <summary> 637         /// 移除列表指定键上与该值相同的元素 638         /// </summary> 639         /// <param name="key"></param> 640         /// <param name="value"></param> 641         /// <returns></returns> 642         public long ListRemove(string key, string value) 643         { 644             key = AddKeyPrefix(key); 645             return _db.ListRemove(key, value); 646         } 647  648         /// <summary> 649         /// 入列,在列表尾部插入值。如果键不存在,先创建再插入值 650         /// </summary> 651         /// <param name="key"></param> 652         /// <param name="value"></param> 653         /// <returns></returns> 654         public long ListRightPush(string key, string value) 655         { 656             key = AddKeyPrefix(key); 657             return _db.ListRightPush(key, value); 658         } 659  660         /// <summary> 661         /// 在列表头部插入值。如果键不存在,先创建再插入值 662         /// </summary> 663         /// <param name="key"></param> 664         /// <param name="value"></param> 665         /// <returns></returns> 666         public long ListLeftPush(string key, string value) 667         { 668             key = AddKeyPrefix(key); 669             return _db.ListLeftPush(key, value); 670         } 671  672         /// <summary> 673         /// 返回列表上该键的长度,如果不存在,返回 0 674         /// </summary> 675         /// <param name="key"></param> 676         /// <returns></returns> 677         public long ListLength(string key) 678         { 679             key = AddKeyPrefix(key); 680             return _db.ListLength(key); 681         } 682  683         /// <summary> 684         /// 返回在该列表上键所对应的元素 685         /// </summary> 686         /// <param name="key"></param> 687         /// <param name="start"></param> 688         /// <param name="stop"></param> 689         /// <returns></returns> 690         public IEnumerable<string> ListRange(string key, long start = 0L, long stop = -1L) 691         { 692             key = AddKeyPrefix(key); 693             return ConvertStrings(_db.ListRange(key, start, stop)); 694         } 695  696         /// <summary> 697         /// 移除并返回存储在该键列表的第一个元素 698         /// </summary> 699         /// <param name="key"></param> 700         /// <returns></returns> 701         public T ListLeftPop<T>(string key) 702         { 703             key = AddKeyPrefix(key); 704             return Deserialize<T>(_db.ListLeftPop(key)); 705         } 706  707         /// <summary> 708         /// 出队,移除并返回存储在该键列表的最后一个元素 709         /// </summary> 710         /// <param name="key"></param> 711         /// <returns></returns> 712         public T ListRightPop<T>(string key) 713         { 714             key = AddKeyPrefix(key); 715             return Deserialize<T>(_db.ListRightPop(key)); 716         } 717  718         /// <summary> 719         /// 入队,在列表尾部插入值。如果键不存在,先创建再插入值 720         /// </summary> 721         /// <param name="key"></param> 722         /// <param name="value"></param> 723         /// <returns></returns> 724         public long ListRightPush<T>(string key, T value) 725         { 726             key = AddKeyPrefix(key); 727             return _db.ListRightPush(key, Serialize(value)); 728         } 729  730         /// <summary> 731         /// 在列表头部插入值。如果键不存在,先创建再插入值 732         /// </summary> 733         /// <param name="key"></param> 734         /// <param name="value"></param> 735         /// <returns></returns> 736         public long ListLeftPush<T>(string key, T value) 737         { 738             key = AddKeyPrefix(key); 739             return _db.ListLeftPush(key, Serialize(value)); 740         } 741  742         #region List-async 743  744         /// <summary> 745         /// 移除并返回存储在该键列表的第一个元素 746         /// </summary> 747         /// <param name="key"></param> 748         /// <returns></returns> 749         public async Task<string> ListLeftPopAsync(string key) 750         { 751             key = AddKeyPrefix(key); 752             return await _db.ListLeftPopAsync(key); 753         } 754  755         /// <summary> 756         /// 移除并返回存储在该键列表的最后一个元素 757         /// </summary> 758         /// <param name="key"></param> 759         /// <returns></returns> 760         public async Task<string> ListRightPopAsync(string key) 761         { 762             key = AddKeyPrefix(key); 763             return await _db.ListRightPopAsync(key); 764         } 765  766         /// <summary> 767         /// 移除列表指定键上与该值相同的元素 768         /// </summary> 769         /// <param name="key"></param> 770         /// <param name="value"></param> 771         /// <returns></returns> 772         public async Task<long> ListRemoveAsync(string key, string value) 773         { 774             key = AddKeyPrefix(key); 775             return await _db.ListRemoveAsync(key, value); 776         } 777  778         /// <summary> 779         /// 在列表尾部插入值。如果键不存在,先创建再插入值 780         /// </summary> 781         /// <param name="key"></param> 782         /// <param name="value"></param> 783         /// <returns></returns> 784         public async Task<long> ListRightPushAsync(string key, string value) 785         { 786             key = AddKeyPrefix(key); 787             return await _db.ListRightPushAsync(key, value); 788         } 789  790         /// <summary> 791         /// 在列表头部插入值。如果键不存在,先创建再插入值 792         /// </summary> 793         /// <param name="key"></param> 794         /// <param name="value"></param> 795         /// <returns></returns> 796         public async Task<long> ListLeftPushAsync(string key, string value) 797         { 798             key = AddKeyPrefix(key); 799             return await _db.ListLeftPushAsync(key, value); 800         } 801  802         /// <summary> 803         /// 返回列表上该键的长度,如果不存在,返回 0 804         /// </summary> 805         /// <param name="key"></param> 806         /// <returns></returns> 807         public async Task<long> ListLengthAsync(string key) 808         { 809             key = AddKeyPrefix(key); 810             return await _db.ListLengthAsync(key); 811         } 812  813         /// <summary> 814         /// 返回在该列表上键所对应的元素 815         /// </summary> 816         /// <param name="key"></param> 817         /// <param name="start"></param> 818         /// <param name="stop"></param> 819         /// <returns></returns> 820         public async Task<IEnumerable<string>> ListRangeAsync(string key, long start = 0L, long stop = -1L) 821         { 822             key = AddKeyPrefix(key); 823             var query = await _db.ListRangeAsync(key, start, stop); 824             return query.Select(x => x.ToString()); 825         } 826  827         /// <summary> 828         /// 移除并返回存储在该键列表的第一个元素 829         /// </summary> 830         /// <param name="key"></param> 831         /// <returns></returns> 832         public async Task<T> ListLeftPopAsync<T>(string key) 833         { 834             key = AddKeyPrefix(key); 835             return Deserialize<T>(await _db.ListLeftPopAsync(key)); 836         } 837  838         /// <summary> 839         /// 移除并返回存储在该键列表的最后一个元素 840         /// </summary> 841         /// <param name="key"></param> 842         /// <returns></returns> 843         public async Task<T> ListRightPopAsync<T>(string key) 844         { 845             key = AddKeyPrefix(key); 846             return Deserialize<T>(await _db.ListRightPopAsync(key)); 847         } 848  849         /// <summary> 850         /// 在列表尾部插入值。如果键不存在,先创建再插入值 851         /// </summary> 852         /// <param name="key"></param> 853         /// <param name="value"></param> 854         /// <returns></returns> 855         public async Task<long> ListRightPushAsync<T>(string key, T value) 856         { 857             key = AddKeyPrefix(key); 858             return await _db.ListRightPushAsync(key, Serialize(value)); 859         } 860  861         /// <summary> 862         /// 在列表头部插入值。如果键不存在,先创建再插入值 863         /// </summary> 864         /// <param name="key"></param> 865         /// <param name="value"></param> 866         /// <returns></returns> 867         public async Task<long> ListLeftPushAsync<T>(string key, T value) 868         { 869             key = AddKeyPrefix(key); 870             return await _db.ListLeftPushAsync(key, Serialize(value)); 871         } 872  873         #endregion List-async 874  875         #endregion List 操作 876  877         #region SortedSet 操作 878  879         /// <summary> 880         /// SortedSet 新增 881         /// </summary> 882         /// <param name="key"></param> 883         /// <param name="member"></param> 884         /// <param name="score"></param> 885         /// <returns></returns> 886         public bool SortedSetAdd(string key, string member, double score) 887         { 888             key = AddKeyPrefix(key); 889             return _db.SortedSetAdd(key, member, score); 890         } 891  892         /// <summary> 893         /// 在有序集合中返回指定范围的元素,默认情况下从低到高。 894         /// </summary> 895         /// <param name="key"></param> 896         /// <param name="start"></param> 897         /// <param name="stop"></param> 898         /// <param name="order"></param> 899         /// <returns></returns> 900         public IEnumerable<string> SortedSetRangeByRank(string key, long start = 0L, long stop = -1L, 901             OrderType order = OrderType.Ascending) 902         { 903             key = AddKeyPrefix(key); 904             return _db.SortedSetRangeByRank(key, start, stop, (Order) order).Select(x => x.ToString()); 905         } 906  907         /// <summary> 908         /// 返回有序集合的元素个数 909         /// </summary> 910         /// <param name="key"></param> 911         /// <returns></returns> 912         public long SortedSetLength(string key) 913         { 914             key = AddKeyPrefix(key); 915             return _db.SortedSetLength(key); 916         } 917  918         /// <summary> 919         /// 返回有序集合的元素个数 920         /// </summary> 921         /// <param name="key"></param> 922         /// <param name="memebr"></param> 923         /// <returns></returns> 924         public bool SortedSetRemove(string key, string memebr) 925         { 926             key = AddKeyPrefix(key); 927             return _db.SortedSetRemove(key, memebr); 928         } 929  930         /// <summary> 931         /// SortedSet 新增 932         /// </summary> 933         /// <param name="key"></param> 934         /// <param name="member"></param> 935         /// <param name="score"></param> 936         /// <returns></returns> 937         public bool SortedSetAdd<T>(string key, T member, double score) 938         { 939             key = AddKeyPrefix(key); 940             var json = Serialize(member); 941  942             return _db.SortedSetAdd(key, json, score); 943         } 944  945         /// <summary> 946         /// 增量的得分排序的集合中的成员存储键值键按增量 947         /// </summary> 948         /// <param name="key"></param> 949         /// <param name="member"></param> 950         /// <param name="value"></param> 951         /// <returns></returns> 952         public double SortedSetIncrement(string key, string member, double value = 1) 953         { 954             key = AddKeyPrefix(key); 955             return _db.SortedSetIncrement(key, member, value); 956         } 957  958         #region SortedSet-Async 959  960         /// <summary> 961         /// SortedSet 新增 962         /// </summary> 963         /// <param name="key"></param> 964         /// <param name="member"></param> 965         /// <param name="score"></param> 966         /// <returns></returns> 967         public async Task<bool> SortedSetAddAsync(string key, string member, double score) 968         { 969             key = AddKeyPrefix(key); 970             return await _db.SortedSetAddAsync(key, member, score); 971         } 972  973         /// <summary> 974         /// 在有序集合中返回指定范围的元素,默认情况下从低到高。 975         /// </summary> 976         /// <param name="key"></param> 977         /// <returns></returns> 978         public async Task<IEnumerable<string>> SortedSetRangeByRankAsync(string key) 979         { 980             key = AddKeyPrefix(key); 981             return ConvertStrings(await _db.SortedSetRangeByRankAsync(key)); 982         } 983  984         /// <summary> 985         /// 返回有序集合的元素个数 986         /// </summary> 987         /// <param name="key"></param> 988         /// <returns></returns> 989         public async Task<long> SortedSetLengthAsync(string key) 990         { 991             key = AddKeyPrefix(key); 992             return await _db.SortedSetLengthAsync(key); 993         } 994  995         /// <summary> 996         /// 返回有序集合的元素个数 997         /// </summary> 998         /// <param name="key"></param> 999         /// <param name="memebr"></param>1000         /// <returns></returns>1001         public async Task<bool> SortedSetRemoveAsync(string key, string memebr)1002         {1003             key = AddKeyPrefix(key);1004             return await _db.SortedSetRemoveAsync(key, memebr);1005         }1006 1007         /// <summary>1008         /// SortedSet 新增1009         /// </summary>1010         /// <param name="key"></param>1011         /// <param name="member"></param>1012         /// <param name="score"></param>1013         /// <returns></returns>1014         public async Task<bool> SortedSetAddAsync<T>(string key, T member, double score)1015         {1016             key = AddKeyPrefix(key);1017             var json = Serialize(member);1018 1019             return await _db.SortedSetAddAsync(key, json, score);1020         }1021 1022         /// <summary>1023         /// 增量的得分排序的集合中的成员存储键值键按增量1024         /// </summary>1025         /// <param name="key"></param>1026         /// <param name="member"></param>1027         /// <param name="value"></param>1028         /// <returns></returns>1029         public Task<double> SortedSetIncrementAsync(string key, string member, double value = 1)1030         {1031             key = AddKeyPrefix(key);1032             return _db.SortedSetIncrementAsync(key, member, value);1033         }1034 1035         #endregion SortedSet-Async1036 1037         #endregion SortedSet 操作1038 1039         #region key 操作1040 1041         /// <summary>1042         /// 移除指定 Key1043         /// </summary>1044         /// <param name="key"></param>1045         /// <returns></returns>1046         public bool KeyDelete(string key)1047         {1048             key = AddKeyPrefix(key);1049             return _db.KeyDelete(key);1050         }1051 1052         /// <summary>1053         /// 移除指定 Key1054         /// </summary>1055         /// <param name="keys"></param>1056         /// <returns></returns>1057         public long KeyDelete(IEnumerable<string> keys)1058         {1059             var redisKeys = keys.Select(x => (RedisKey) AddKeyPrefix(x));1060             return _db.KeyDelete(redisKeys.ToArray());1061         }1062 1063         /// <summary>1064         /// 校验 Key 是否存在1065         /// </summary>1066         /// <param name="key"></param>1067         /// <returns></returns>1068         public bool KeyExists(string key)1069         {1070             key = AddKeyPrefix(key);1071             return _db.KeyExists(key);1072         }1073 1074         /// <summary>1075         /// 重命名 Key1076         /// </summary>1077         /// <param name="key"></param>1078         /// <param name="newKey"></param>1079         /// <returns></returns>1080         public bool KeyRename(string key, string newKey)1081         {1082             key = AddKeyPrefix(key);1083             return _db.KeyRename(key, newKey);1084         }1085 1086         /// <summary>1087         /// 设置 Key 的时间1088         /// </summary>1089         /// <param name="key"></param>1090         /// <param name="expiry"></param>1091         /// <returns></returns>1092         public bool KeyExpire(string key, TimeSpan? expiry)1093         {1094             key = AddKeyPrefix(key);1095             return _db.KeyExpire(key, expiry);1096         }1097 1098         #region key-async1099 1100         /// <summary>1101         /// 移除指定 Key1102         /// </summary>1103         /// <param name="key"></param>1104         /// <returns></returns>1105         public async Task<bool> KeyDeleteAsync(string key)1106         {1107             key = AddKeyPrefix(key);1108             return await _db.KeyDeleteAsync(key);1109         }1110 1111         /// <summary>1112         /// 移除指定 Key1113         /// </summary>1114         /// <param name="keys"></param>1115         /// <returns></returns>1116         public async Task<long> KeyDeleteAsync(IEnumerable<string> keys)1117         {1118             var redisKeys = keys.Select(x => (RedisKey) AddKeyPrefix(x));1119             return await _db.KeyDeleteAsync(redisKeys.ToArray());1120         }1121 1122         /// <summary>1123         /// 校验 Key 是否存在1124         /// </summary>1125         /// <param name="key"></param>1126         /// <returns></returns>1127         public async Task<bool> KeyExistsAsync(string key)1128         {1129             key = AddKeyPrefix(key);1130             return await _db.KeyExistsAsync(key);1131         }1132 1133         /// <summary>1134         /// 重命名 Key1135         /// </summary>1136         /// <param name="key"></param>1137         /// <param name="newKey"></param>1138         /// <returns></returns>1139         public async Task<bool> KeyRenameAsync(string key, string newKey)1140         {1141             key = AddKeyPrefix(key);1142             return await _db.KeyRenameAsync(key, newKey);1143         }1144 1145         /// <summary>1146         /// 设置 Key 的时间1147         /// </summary>1148         /// <param name="key"></param>1149         /// <param name="expiry"></param>1150         /// <returns></returns>1151         public async Task<bool> KeyExpireAsync(string key, TimeSpan? expiry)1152         {1153             key = AddKeyPrefix(key);1154             return await _db.KeyExpireAsync(key, expiry);1155         }1156 1157         #endregion key-async1158 1159         #endregion key 操作1160 1161         #region 发布订阅1162 1163         /// <summary>1164         /// 订阅1165         /// </summary>1166         /// <param name="channel"></param>1167         /// <param name="handle"></param>1168         public void Subscribe(RedisChannel channel, Action<RedisChannel, RedisValue> handle)1169         {1170             var sub = _connMultiplexer.GetSubscriber();1171             sub.Subscribe(channel, handle);1172         }1173 1174         /// <summary>1175         /// 发布1176         /// </summary>1177         /// <param name="channel"></param>1178         /// <param name="message"></param>1179         /// <returns></returns>1180         public long Publish(RedisChannel channel, RedisValue message)1181         {1182             var sub = _connMultiplexer.GetSubscriber();1183             return sub.Publish(channel, message);1184         }1185 1186         /// <summary>1187         /// 发布(使用序列化)1188         /// </summary>1189         /// <typeparam name="T"></typeparam>1190         /// <param name="channel"></param>1191         /// <param name="message"></param>1192         /// <returns></returns>1193         public long Publish<T>(RedisChannel channel, T message)1194         {1195             var sub = _connMultiplexer.GetSubscriber();1196             return sub.Publish(channel, Serialize(message));1197         }1198 1199         #region 发布订阅-async1200 1201         /// <summary>1202         /// 订阅1203         /// </summary>1204         /// <param name="channel"></param>1205         /// <param name="handle"></param>1206         public async Task SubscribeAsync(RedisChannel channel, Action<RedisChannel, RedisValue> handle)1207         {1208             var sub = _connMultiplexer.GetSubscriber();1209             await sub.SubscribeAsync(channel, handle);1210         }1211 1212         /// <summary>1213         /// 发布1214         /// </summary>1215         /// <param name="channel"></param>1216         /// <param name="message"></param>1217         /// <returns></returns>1218         public async Task<long> PublishAsync(RedisChannel channel, RedisValue message)1219         {1220             var sub = _connMultiplexer.GetSubscriber();1221             return await sub.PublishAsync(channel, message);1222         }1223 1224         /// <summary>1225         /// 发布(使用序列化)1226         /// </summary>1227         /// <typeparam name="T"></typeparam>1228         /// <param name="channel"></param>1229         /// <param name="message"></param>1230         /// <returns></returns>1231         public async Task<long> PublishAsync<T>(RedisChannel channel, T message)1232         {1233             var sub = _connMultiplexer.GetSubscriber();1234             return await sub.PublishAsync(channel, Serialize(message));1235         }1236 1237         #endregion 发布订阅-async1238 1239         #endregion 发布订阅1240 1241         #region private method1242 1243         /// <summary>1244         /// 添加 Key 的前缀1245         /// </summary>1246         /// <param name="key"></param>1247         /// <returns></returns>1248         private static string AddKeyPrefix(string key)1249         {1250             return $"{DefaultKey}:{key}";1251         }1252 1253         /// <summary>1254         /// 转换为字符串1255         /// </summary>1256         /// <typeparam name="T"></typeparam>1257         /// <param name="list"></param>1258         /// <returns></returns>1259         private static IEnumerable<string> ConvertStrings<T>(IEnumerable<T> list) where T : struct1260         {1261             if (list == null) throw new ArgumentNullException(nameof(list));1262             return list.Select(x => x.ToString());1263         }1264 1265         #region 注册事件1266 1267         /// <summary>1268         /// 添加注册事件1269         /// </summary>1270         private static void AddRegisterEvent()1271         {1272             _connMultiplexer.ConnectionRestored += ConnMultiplexer_ConnectionRestored;1273             _connMultiplexer.ConnectionFailed += ConnMultiplexer_ConnectionFailed;1274             _connMultiplexer.ErrorMessage += ConnMultiplexer_ErrorMessage;1275             _connMultiplexer.ConfigurationChanged += ConnMultiplexer_ConfigurationChanged;1276             _connMultiplexer.HashSlotMoved += ConnMultiplexer_HashSlotMoved;1277             _connMultiplexer.InternalError += ConnMultiplexer_InternalError;1278             _connMultiplexer.ConfigurationChangedBroadcast += ConnMultiplexer_ConfigurationChangedBroadcast;1279         }1280 1281         /// <summary>1282         /// 重新配置广播时(通常意味着主从同步更改)1283         /// </summary>1284         /// <param name="sender"></param>1285         /// <param name="e"></param>1286         private static void ConnMultiplexer_ConfigurationChangedBroadcast(object sender, EndPointEventArgs e)1287         {1288             Console.WriteLine($"{nameof(ConnMultiplexer_ConfigurationChangedBroadcast)}: {e.EndPoint}");1289         }1290 1291         /// <summary>1292         /// 发生内部错误时(主要用于调试)1293         /// </summary>1294         /// <param name="sender"></param>1295         /// <param name="e"></param>1296         private static void ConnMultiplexer_InternalError(object sender, InternalErrorEventArgs e)1297         {1298             Console.WriteLine($"{nameof(ConnMultiplexer_InternalError)}: {e.Exception}");1299         }1300 1301         /// <summary>1302         /// 更改集群时1303         /// </summary>1304         /// <param name="sender"></param>1305         /// <param name="e"></param>1306         private static void ConnMultiplexer_HashSlotMoved(object sender, HashSlotMovedEventArgs e)1307         {1308             Console.WriteLine(1309                 $"{nameof(ConnMultiplexer_HashSlotMoved)}: {nameof(e.OldEndPoint)}-{e.OldEndPoint} To {nameof(e.NewEndPoint)}-{e.NewEndPoint}, ");1310         }1311 1312         /// <summary>1313         /// 配置更改时1314         /// </summary>1315         /// <param name="sender"></param>1316         /// <param name="e"></param>1317         private static void ConnMultiplexer_ConfigurationChanged(object sender, EndPointEventArgs e)1318         {1319             Console.WriteLine($"{nameof(ConnMultiplexer_ConfigurationChanged)}: {e.EndPoint}");1320         }1321 1322         /// <summary>1323         /// 发生错误时1324         /// </summary>1325         /// <param name="sender"></param>1326         /// <param name="e"></param>1327         private static void ConnMultiplexer_ErrorMessage(object sender, RedisErrorEventArgs e)1328         {1329             Console.WriteLine($"{nameof(ConnMultiplexer_ErrorMessage)}: {e.Message}");1330         }1331 1332         /// <summary>1333         /// 物理连接失败时1334         /// </summary>1335         /// <param name="sender"></param>1336         /// <param name="e"></param>1337         private static void ConnMultiplexer_ConnectionFailed(object sender, ConnectionFailedEventArgs e)1338         {1339             Console.WriteLine($"{nameof(ConnMultiplexer_ConnectionFailed)}: {e.Exception}");1340         }1341 1342         /// <summary>1343         /// 建立物理连接时1344         /// </summary>1345         /// <param name="sender"></param>1346         /// <param name="e"></param>1347         private static void ConnMultiplexer_ConnectionRestored(object sender, ConnectionFailedEventArgs e)1348         {1349             Console.WriteLine($"{nameof(ConnMultiplexer_ConnectionRestored)}: {e.Exception}");1350         }1351 1352         #endregion 注册事件1353 1354         /// <summary>1355         /// 序列化1356         /// </summary>1357         /// <param name="obj"></param>1358         /// <returns></returns>1359         private static byte[] Serialize(object obj)1360         {1361             if (obj == null)1362                 return null;1363 1364             var binaryFormatter = new BinaryFormatter();1365             using (var memoryStream = new MemoryStream())1366             {1367                 binaryFormatter.Serialize(memoryStream, obj);1368                 var data = memoryStream.ToArray();1369                 return data;1370             }1371         }1372 1373         /// <summary>1374         /// 反序列化1375         /// </summary>1376         /// <typeparam name="T"></typeparam>1377         /// <param name="data"></param>1378         /// <returns></returns>1379         private static T Deserialize<T>(byte[] data)1380         {1381             if (data == null)1382                 return default(T);1383 1384             var binaryFormatter = new BinaryFormatter();1385             using (var memoryStream = new MemoryStream(data))1386             {1387                 var result = (T) binaryFormatter.Deserialize(memoryStream);1388                 return result;1389             }1390         }1391 1392         #endregion private method1393     }1394 }

RedisHelper.cs

Redis 小白指南(一)- 简介、安装、GUI 和 C# 驱动介绍插图8

 

  下期将分析 redis 的五大类型,及介绍他们常用的指令。   

 

系列

  《Redis 小白指南(一)- 简介、安装、GUI 和 C# 驱动介绍

  《Redis 小白指南(二)- 聊聊五大类型:字符串、散列、列表、集合和有序集合

  《Redis 小白指南(三)- 事务、过期、消息通知、管道、优化内存空间》

  《Redis 小白指南(四)- 数据的持久化保存

 

 


【博主】反骨仔

【原文】http://www.cnblogs.com/liqingwen/p/6917426.html 

【GitHub】https://github.com/liqingwen2015/Wen.Helpers/blob/master/Wen.Helpers.Common/Redis/RedisHelper.cs

【参考】《redis 入门指南》

 

文章转载于:https://www.cnblogs.com/liqingwen/p/6917426.html

原著是一个有趣的人,若有侵权,请通知删除

未经允许不得转载:起风网 » Redis 小白指南(一)- 简介、安装、GUI 和 C# 驱动介绍
分享到: 生成海报

评论 抢沙发

评论前必须登录!

立即登录