使用StackExchange.Redis对redis简单封装(一)
1.0 前言
参考一系列网上学习资料,自己尝试手写了一个有关redis的封装类。因为ServiceStack.Redis从4.0版本以后开始商业化,免费版有限制,故采用StackExchange.Redis对redis进行简单封装。
1.1StackExchange.Redis引用
NuGet包里面搜索StackExchange.Redis,然后点击安装即可。
1.2代码内容
public class RedisHelper
{
private readonly string strConnectionMultiplexer;
private ConnectionMultiplexer RedisConnectionMultiplexer;
private IDatabase database;
private ISubscriber subscriber;
//JsonSerializerSettings jsonConfig = new JsonSerializerSettings() { ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore, NullValueHandling = NullValueHandling.Ignore };
public RedisHelper(string options)
{
this.RedisConnect(ConfigurationOptions.Parse(options));
}
public RedisHelper(string server, int port, string password, int defaultDb = 0)
{
#region 第一种连接方式
//if (!string.IsNullOrEmpty(password))
//{
// strConnectionMultiplexer = string.Format("{0}:{1},DefaultDatabase=0,password={2}", server, port, password, defaultDb);
//}
//else
//{
// strConnectionMultiplexer = string.Format("{0}:{1},DefaultDatabase=0", server, port, password, defaultDb);
//}
//RedisConnectionMultiplexer = ConnectionMultiplexer.Connect(strConnectionMultiplexer);
//database = RedisConnectionMultiplexer.GetDatabase(defaultDb);
#endregion
#region 第二种连接方式
ConfigurationOptions options = new ConfigurationOptions();
options.EndPoints.Add(server, port);
options.Password = password;
options.DefaultDatabase = defaultDb;
this.RedisConnect(options);
//options.CommandMap = CommandMap.Sentinel;//redis主从设定配置,涉及到负载均衡 分布式 redis主从配置 设定不全,仅作记录
//options.AbortOnConnectFail = false;//时候显示通知链接超时
#endregion
}
private void RedisConnect(ConfigurationOptions options)
{
this.RedisConnectionMultiplexer = ConnectionMultiplexer.Connect(options);
this.database = RedisConnectionMultiplexer.GetDatabase();
this.subscriber = RedisConnectionMultiplexer.GetSubscriber();//订阅消息
//RedisConnectionMultiplexer.ConnectionFailed+=方法名; //每当物理连接失败时将被引发
//RedisConnectionMultiplexer.ConnectionRestored += 方法名;//每次建立物理连接时触发
//RedisConnectionMultiplexer.ErrorMessage += 方法名;//一个服务器回复了一条错误消息
//RedisConnectionMultiplexer.ConfigurationChanged += 方法名;//检测到配置更改时引发
//RedisConnectionMultiplexer.HashSlotMoved += 方法名;//重新定位哈希槽时将被引发
//RedisConnectionMultiplexer.InternalError += 方法名;//当内部错误发生时引发(主要用于调试)
}
#region 辅助方法
/// <summary>
/// 判断key是否存在
/// </summary>
public bool Exists(string key)
{
return database.KeyExists(key);
}
/// <summary>
/// 设置key过期时间
/// </summary>
/// <param name="key">键值</param>
/// <param name="cacheTime">过期时间秒</param>
/// <returns></returns>
public bool KeyExpire(string key, int cacheTime)
{
return database.KeyExpire(key, TimeSpan.FromSeconds(cacheTime));
}
/// <summary>
/// 设置key到期时间
/// </summary>
/// <param name="key">键值</param>
/// <param name="cacheTime">到期时间 日期</param>
/// <returns></returns>
public bool KeyExpire(string key, DateTime cacheTime)
{
return database.KeyExpire(key, cacheTime);
}
/// <summary>
/// 删除缓存
/// </summary>
/// <param name="key"></param>
public bool Remove(string key)
{
return database.KeyDelete(key);
}
#endregion
#region Redis 字符串(String)
/// <summary>
/// 获取键值
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public object Get(string key)
{
return Get<object>(key);
}
/// <summary>
/// 获取T缓存
/// </summary>
/// <typeparam name="T">泛型model</typeparam>
/// <param name="key">键值</param>
/// <param name="action">委托方法</param>
/// <param name="cacheTime">到期时间秒</param>
/// <returns></returns>
public T Get<T>(string key, Func<T> action = null)
{
var cacheValue = database.StringGet(key);//获取缓存
var result = default(T);
if (!cacheValue.IsNull)//缓存不为空
{
result = JsonConvert.DeserializeObject<T>(cacheValue);
}
else
{
if (action != null)//如果缓存为null,执行缓存方法
{
result = action();
database.StringSet(key, JsonConvert.SerializeObject(result));
}
}
return result;
}
/// <summary>
/// 获取T缓存
/// </summary>
/// <typeparam name="T">泛型model</typeparam>
/// <param name="key">键值</param>
/// <param name="action">委托方法</param>
/// <param name="cacheTime">到期时间秒</param>
/// <returns></returns>
public T Get<T>(string key,int cacheTime, Func<T> action = null)
{
var cacheValue = database.StringGet(key);//获取缓存
var result = default(T);
if (!cacheValue.IsNull)//缓存不为空
{
result = JsonConvert.DeserializeObject<T>(cacheValue);
}
else
{
if (action != null)//如果缓存为null,执行缓存方法
{
result = action();
database.StringSet(key, JsonConvert.SerializeObject(result), TimeSpan.FromSeconds(cacheTime));
}
}
return result;
}
/// <summary>
/// 获取T缓存
/// </summary>
/// <typeparam name="T">泛型model</typeparam>
/// <param name="key">键值</param>
/// <param name="action">委托方法</param>
/// <param name="cacheTime">过期日期</param>
/// <returns></returns>
public T Get<T>(string key, DateTime cacheTime, Func<T> action = null)
{
var cacheValue = database.StringGet(key);//获取缓存
var result = default(T);
if (!cacheValue.IsNull)//缓存不为空
{
result = JsonConvert.DeserializeObject<T>(cacheValue);
}
else
{
if (action != null)//如果缓存为null,执行缓存方法
{
result = action();
database.StringSet(key, JsonConvert.SerializeObject(result), cacheTime - DateTime.Now);
}
}
return result;
}
/// <summary>
/// 设置缓存
/// </summary>
/// <param name="key">Key值</param>
/// <param name="tValue">value值</param>
public void Set(string key, object tValue)
{
database.StringSet(key, JsonConvert.SerializeObject(tValue));
}
/// <summary>
/// 设置缓存
/// </summary>
/// <param name="key">Key值</param>
/// <param name="tValue">value值</param>
/// <param name="cacheTime">到期时间秒</param>
public void Set(string key, object tValue, int cacheTime)
{
database.StringSet(key, JsonConvert.SerializeObject(tValue), TimeSpan.FromSeconds(cacheTime));
}
/// <summary>
/// 设置缓存
/// </summary>
/// <param name="key">Key值</param>
/// <param name="tValue">value值</param>
/// <param name="cacheTime">过期时间</param>
public void Set(string key, object tValue, DateTime cacheTime)
{
var timeSpan = cacheTime - DateTime.Now;
database.StringSet(key, JsonConvert.SerializeObject(tValue), timeSpan);
}
/// <summary>
/// 设置缓存
/// </summary>
/// <typeparam name="T">model</typeparam>
/// <param name="key">Key值</param>
/// <param name="tValue">value值</param>
public void Set<T>(string key, T tValue)
{
database.StringSet(key, JsonConvert.SerializeObject(tValue));
}
/// <summary>
/// 设置缓存
/// </summary>
/// <typeparam name="T">model</typeparam>
/// <param name="key">Key值</param>
/// <param name="tValue">value值</param>
/// <param name="cacheTime">到期时间秒</param>
public void Set<T>(string key, T tValue, int cacheTime)
{
database.StringSet(key, JsonConvert.SerializeObject(tValue), TimeSpan.FromSeconds(cacheTime));
}
/// <summary>
/// 设置缓存
/// </summary>
/// <typeparam name="T">model</typeparam>
/// <param name="key">Key值</param>
/// <param name="tValue">value值</param>
/// <param name="cacheTime">过期时间</param>
public void Set<T>(string key, T tValue, DateTime cacheTime)
{
var timeSpan = cacheTime - DateTime.Now;
database.StringSet(key, JsonConvert.SerializeObject(tValue), timeSpan);
}
#endregion
#region Redis 列表(List)
/// <summary>
/// 获取List长度
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public long GetListLength(string key)
{
return database.ListLength(key);
}
/// <summary>
/// 获取list 下标从开始到末尾
/// </summary>
/// <param name="key">键值</param>
/// <param name="start">起始位置</param>
/// <param name="stop">结束位置</param>
/// <returns></returns>
public string[] ListRange(string key, int start = 0, int stop = -1)
{
return database.ListRange(key, start, stop).ToStringArray();
}
/// <summary>
/// 根据list下标获取list内容
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="index"></param>
/// <returns></returns>
public string ListGetByIndex(string key, int index=0)
{
return database.ListGetByIndex(key, index);
}
/// <summary>
/// 从左边(起始位置)新插入一条list记录
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key">键值</param>
/// <param name="list">集合</param>
/// <returns></returns>
public long ListLeftPush<T>(string key,List<T> list)
{
return database.ListLeftPush(key, JsonConvert.SerializeObject(list));
}
/// <summary>
/// 从左边(起始位置)新插入一条list记录
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key">键值</param>
/// <param name="list">集合</param>
/// <returns></returns>
public long ListLeftPush<T>(string key, T list)
{
return database.ListLeftPush(key, JsonConvert.SerializeObject(list));
}
/// <summary>
/// 从右边(末尾位置)新插入一条list记录
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key">键值</param>
/// <param name="list">集合</param>
/// <returns></returns>
public long ListRightPush<T>(string key, List<T> list)
{
return database.ListRightPush(key, JsonConvert.SerializeObject(list));
}
/// <summary>
/// 从右边(末尾位置)新插入一条list记录
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key">键值</param>
/// <param name="list">集合</param>
/// <returns></returns>
public long ListRightPush<T>(string key, T list)
{
return database.ListRightPush(key, JsonConvert.SerializeObject(list));
}
/// <summary>
/// 根据下标count替换相对应的值
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key">键值</param>
/// <param name="count">下标</param>
/// <param name="list">集合</param>
public void ListSetByIndex<T>(string key, int count, List<T> list)
{
database.ListSetByIndex(key, count, JsonConvert.SerializeObject(list));
}
/// <summary>
/// 根据下标count替换相对应的值
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key">键值</param>
/// <param name="count">下标</param>
/// <param name="list">集合</param>
public void ListSetByIndex<T>(string key, int count, T list)
{
database.ListSetByIndex(key, count, JsonConvert.SerializeObject(list));
}
/// <summary>
/// 删除list 找到相等的list值,count>0 从头往后查找删除,
/// count<0 从后往前查找删除,count=0删除所有相等的list
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key">键值</param>
/// <param name="list">集合</param>
/// <param name="count">数量</param>
/// <returns></returns>
public long ListRemove<T>(string key,List<T> list,int count=0)
{
return database.ListRemove(key, JsonConvert.SerializeObject(list), count);
}
/// <summary>
/// 删除list 找到相等的list值,count>0 从头往后查找删除,
/// count<0 从后往前查找删除,count=0删除所有等于list的键
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key">键值</param>
/// <param name="list">集合</param>
/// <param name="count">数量</param>
/// <returns></returns>
public long ListRemove<T>(string key, T list, int count = 0)
{
return database.ListRemove(key, JsonConvert.SerializeObject(list), count);
}
#endregion
#region Redis 有序集合(sorted set)
/// <summary>
/// 设置分数
/// </summary>
/// <param name="key">键值</param>
/// <param name="model">缓存内容</param>
/// <param name="dbScore">分数</param>
/// <returns></returns>
public bool SortedSetAdd(string key, string model, double dbScore)
{
return database.SortedSetAdd(key, model, dbScore);
}
/// <summary>
/// 批量设置分数,返回新加的数目,更改或者无新加的返回0
/// </summary>
/// <param name="key">键值</param>
/// <param name="keyValues">字典值</param>
/// <returns></returns>
public long SortedSetAdd(string key, IDictionary<string, double> keyValues)
{
return database.SortedSetAdd(key, ConvertDictionaryToSortedSetEntry(keyValues));
}
/// <summary>
/// 降低相对应键值values分数
/// </summary>
/// <param name="key">键值</param>
/// <param name="model">values</param>
/// <param name="dbScore">减去的分数</param>
/// <returns>新的分数</returns>
public double SortedSetDecrement(string key, string model, double dbScore)
{
return database.SortedSetDecrement(key, model, dbScore);
}
/// <summary>
/// 增加相对应键值values分数
/// </summary>
/// <param name="key">键值</param>
/// <param name="model">values</param>
/// <param name="dbScore">增加的分数</param>
/// <returns>新的分数</returns>
public double SortedSetIncrement(string key, string model, double dbScore)
{
return database.SortedSetIncrement(key, model, dbScore);
}
/// <summary>
/// 查询分数区间内的values总数
/// </summary>
/// <param name="key">键值</param>
/// <param name="min">最小分数</param>
/// <param name="max">最大分数</param>
/// <returns></returns>
public long SortedSetLength(string key, double min = double.NegativeInfinity, double max = double.PositiveInfinity)
{
return database.SortedSetLength(key, min, max);
}
/// <summary>
/// 排序之后根据下标区间取出values
/// </summary>
/// <param name="key">键值</param>
/// <param name="start">开始下标</param>
/// <param name="stop">结束下标</param>
/// <param name="sortType">true倒序排列,false正序排列</param>
/// <returns></returns>
public List<string> SortedSetRangeByRank(string key, long start = 0, long stop = -1,bool sortType=true)
{
if (sortType)
{
return database.SortedSetRangeByRank(key, start, stop,Order.Descending).ToStringArray().ToList<string>();
}
else
{
return database.SortedSetRangeByRank(key, start, stop, Order.Ascending).ToStringArray().ToList<string>();
}
}
/// <summary>
/// 排序之后根据下标区间取出values和分数
/// </summary>
/// <param name="key">键值</param>
/// <param name="start">开始下标</param>
/// <param name="stop">结束下标</param>
/// <param name="sortType">true倒序排列,false正序排列</param>
/// <returns></returns>
public IDictionary<string,double> SortedSetRangeByRankWithScores(string key, long start = 0, long stop = -1, bool sortType = true)
{
if (sortType)
{
return ConvertSortedSetEntryToDictionary(database.SortedSetRangeByRankWithScores(key, start, stop, Order.Descending));
}
else
{
return ConvertSortedSetEntryToDictionary(database.SortedSetRangeByRankWithScores(key, start, stop, Order.Ascending));
}
}
/// <summary>
/// 根据分数区间取出values和分数
/// </summary>
/// <param name="key">键值</param>
/// <param name="start">最小分数</param>
/// <param name="stop">最大分数</param>
/// <returns></returns>
public IDictionary<string,double> SortedSetRangeByScoreWithScores(RedisKey key, double start = double.NegativeInfinity, double stop = double.PositiveInfinity)
{
return ConvertSortedSetEntryToDictionary(database.SortedSetRangeByScoreWithScores(key, start, stop));
}
/// <summary>
/// 批量删除values
/// </summary>
/// <param name="key">键值</param>
/// <param name="list">内容</param>
/// <returns></returns>
public long SortedSetRemove(string key,List<string> list)
{
return database.SortedSetRemove(key, list.Select(e=>new RedisValue(e)).ToArray());
}
/// <summary>
/// 删除相对应的values
/// </summary>
/// <param name="key">键值</param>
/// <param name="model">内容</param>
/// <returns></returns>
public bool SortedSetRemove(string key,string model)
{
return database.SortedSetRemove(key, model);
}
/// <summary>
/// 数据转换
/// </summary>
/// <param name="keyValues"></param>
/// <returns></returns>
public SortedSetEntry[] ConvertDictionaryToSortedSetEntry(IDictionary<string,double> keyValues)
{
List<SortedSetEntry> setEntries = new List<SortedSetEntry>();
foreach (var item in keyValues)
{
setEntries.Add(new SortedSetEntry(item.Key, item.Value));
}
return setEntries.ToArray();
}
/// <summary>
/// 数据转换字典
/// </summary>
/// <param name="keyValues"></param>
/// <returns></returns>
public IDictionary<string, double> ConvertSortedSetEntryToDictionary(SortedSetEntry[] setEntries)
{
IDictionary<string, double> keyValues = new Dictionary<string, double>();
foreach (var item in setEntries)
{
keyValues.Add(item.Element, item.Score);
}
return keyValues;
}
#endregion
#region Redis 哈希(Hash)
/// <summary>
/// 判断当前哈希是否包含当前列
/// </summary>
/// <param name="key">键值</param>
/// <param name="hashKey">列值</param>
/// <returns></returns>
public bool HashExists(string key, string hashKey)
{
return database.HashExists(key, hashKey);
}
/// <summary>
/// 根据键值 哈希键值删除对应的values
/// </summary>
/// <param name="key"></param>
/// <param name="hashKey"></param>
/// <returns></returns>
public bool HashDelete(string key, string hashKey)
{
return database.HashDelete(key, hashKey);
}
/// <summary>
/// 根据键值 哈希键值批量删除删除对应的values
/// </summary>
/// <param name="key"></param>
/// <param name="hashKey"></param>
/// <returns></returns>
public long HashDelete(string key, List<string> hashKey)
{
return database.HashDelete(key, hashKey.Select(e => new RedisValue(e)).ToArray());
}
/// <summary>
/// 获取哈希值
/// </summary>
/// <typeparam name="T">类型</typeparam>
/// <param name="key">键值</param>
/// <param name="hashKey">哈希标志</param>
/// <param name="action"></param>
/// <returns></returns>
public T HashGet<T>(string key, string hashKey, Func<T> action = null)
{
var cacheValue = database.HashGet(key, hashKey);//获取缓存
var result = default(T);
if (!cacheValue.IsNull)//缓存不为空
{
result = JsonConvert.DeserializeObject<T>(cacheValue);
}
else
{
if (action != null)//如果缓存为null,执行缓存方法
{
result = action();
IDictionary<string, T> keyValues = new Dictionary<string, T>();
keyValues.Add(hashKey, result);
HashSet<T>(key, keyValues);
}
}
return result;
}
/// <summary>
/// 获取多个哈希值
/// </summary>
/// <param name="key">键值</param>
/// <param name="hashKey">多个哈希标志</param>
/// <returns>返回相对应的哈希值values</returns>
public List<string> HashGet(string key, List<string> hashKey)
{
return database.HashGet(key, hashKey.Select(e => new RedisValue(e)).ToArray()).ToList().ConvertAll(o=>o.ToString());//获取缓存
}
/// <summary>
/// 根据key值获取相对应所有的哈希键值
/// </summary>
/// <param name="key">键值</param>
/// <returns></returns>
public List<string> HashKeys(string key)
{
return database.HashKeys(key).ToList().ConvertAll(o => o.ToString());
}
/// <summary>
/// 根据键值获取所有值
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key">键值</param>
/// <returns></returns>
public IDictionary<string,T> HashGetAll<T>(string key)
{
return ConvertHashEntryToIDictionary<T>(database.HashGetAll(key));
}
/// <summary>
/// 以递减的方式递减存储在key散列中字段中存储的数字。如果key不存在,则创建一个包含哈希的新key。如果字段不存在、存在或保存不能被解释为整型的字符串,则设置该值为0,然后再执行操作。
/// </summary>
/// <param name="key"></param>
/// <param name="hashKey"></param>
/// <param name="value"></param>
/// <returns></returns>
public long HashDecrement(string key, string hashKey, long value = 1)
{
return database.HashDecrement(key, hashKey, value);
}
/// <summary>
/// 递减存储在键处的哈希的指定字段,表示浮点数点的数量,通过指定的递减。如果该字段不存在,则存在操作前设置为0。
/// </summary>
/// <param name="key"></param>
/// <param name="hashKey"></param>
/// <param name="value"></param>
/// <returns></returns>
public double HashDecrement(string key, string hashKey, double value)
{
return database.HashDecrement(key, hashKey, value);
}
/// <summary>
/// //在key存储的散列中,字段的存储数以递增的方式递增。如果key不存在,则创建一个包含哈希的新key。如果字段不存在、存在或保存不能被解释为整型的字符串,则设置该值为0,然后再执行操作。
/// </summary>
/// <param name="key"></param>
/// <param name="hashKey"></param>
/// <param name="value"></param>
/// <returns></returns>
public long HashIncrement(string key, string hashKey, long value = 1)
{
return database.HashIncrement(key, hashKey, value);
}
/// <summary>
/// 递增存储在key并表示浮点数的散列的指定字段点数,按指定的增量。如果该字段不存在,则存在操作前设置为0。
/// </summary>
/// <param name="key"></param>
/// <param name="hashKey"></param>
/// <param name="value"></param>
/// <returns></returns>
public double HashIncrement(string key, string hashKey, double value)
{
return database.HashIncrement(key, hashKey, value);
}
/// <summary>
/// 设置哈希值
/// </summary>
/// <typeparam name="T">类型</typeparam>
/// <param name="key">键值</param>
/// <param name="keyValues">哈希内容</param>
public void HashSet<T>(string key,IDictionary<string,T> keyValues)
{
database.HashSet(key,keyValues.Select(e=>new HashEntry(e.Key,JsonConvert.SerializeObject(e.Value))).ToArray());
}
/// <summary>
/// 根据键值 哈希值修改相对应的values
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <param name="hashKey"></param>
/// <param name="model"></param>
/// <returns></returns>
public bool HashSet<T>(string key,string hashKey,T model)
{
return database.HashSet(key, hashKey, JsonConvert.SerializeObject(model));
}
/// <summary>
/// 哈希数组转换成字典
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="hashEntries"></param>
/// <returns></returns>
public IDictionary<string, T> ConvertHashEntryToIDictionary<T>(HashEntry[] hashEntries)
{
IDictionary<string, T> keyValues = new Dictionary<string, T>();
foreach (var item in hashEntries)
{
keyValues.Add(item.Name, JsonConvert.DeserializeObject<T>(item.Value));
}
return keyValues;
}
#endregion
#region Redis 发布订阅
/// <summary>
/// 获取所有连接地址
/// </summary>
/// <returns></returns>
public System.Net.EndPoint[] GetEndPoints()
{
return RedisConnectionMultiplexer.GetEndPoints();
}
/// <summary>
/// 获取单个服务的配置
/// </summary>
/// <param name="server"></param>
/// <param name="port"></param>
/// <returns></returns>
public IServer GetServer(string server, int port)
{
return RedisConnectionMultiplexer.GetServer(server, port);
}
/// <summary>
/// 发布消息
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="channel">发送的管道</param>
/// <param name="sendMessage">发送的消息内容</param>
/// <returns></returns>
public long RedisPub<T>(string channel,T sendMessage)
{
return subscriber.Publish(channel, JsonConvert.SerializeObject(sendMessage));
}
/// <summary>
/// 订阅消息
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="channel">管道</param>
/// <param name="action">接收消息之后执行的方法</param>
public void RedisSub<T>(string channel, Action<string> action)
{
subscriber.Subscribe(channel, (redisChannel, redisValue) =>
{
action(redisValue);//接收管道消息,触发事件
});
}
/// <summary>
/// 取消订阅
/// </summary>
/// <param name="channel">订阅通道</param>
public void Unsubscribe(string channel)
{
subscriber.Unsubscribe(channel);
}
/// <summary>
/// 取消所有订阅
/// </summary>
public void UnsubscribeAll()
{
subscriber.UnsubscribeAll();
}
#endregion
}
1.3尾声
本人菜鸟一枚,如有不对,欢迎指正