標籤:des blog http 使用 資料 os
最近搞redis儲存物件出了點問題,大概說一下背景,項目原有的東東以前存的是redis,儲存的直接是物件模型,沒有問題,這裡儲存物件儲存任何資訊事都沒有問題的。但是現在調整為儲存序列化的json字串,此時擷取對象資訊發生了問題,不是報錯就是有亂碼似的東東,一開始以為是編碼問題,其實不準確,現在來一步步看一看到底是什麼問題(這裡的測試只是為了簡單,命名等都不規範,大家湊活著看瞭解問題就行)
public class test { public string Name { get; set; } public string View { get; set; } public IList<AAA> list { get; set; } } public class AAA { public string Name { get; set; } public string View { get; set; } } public class CartController : Controller { public void Index2() { test tes = new test(); tes.Name = "z中文"; tes.View = null; tes.list = new List<AAA>(); AAA d = new AAA(); d.Name = "123"; d.View = "asd"; AAA b = new AAA(); b.Name = "我是特殊符號~!^?*$#<>\\"; b.View = "我是單引號\""; tes.list.Add(d); tes.list.Add(b); //var aa = Newtonsoft.Json.JsonConvert.SerializeObject(tes); RedisManager.Execute(redis => redis.Set("test", tes)); var ggg = RedisManager.Execute(redis => redis.Get<test>("test")); } }
直接儲存物件是沒有問題的,看看其中的set和get吧,
public bool Set<T>(string key, T value) { byte[] numArray = (object) value as byte[]; if (numArray != null) { base.Set(key, numArray); return true; } else { string str = JsonSerializer.SerializeToString<T>(value); this.SetEntry(key, str); return true; }} public static string SerializeToString<T>(T value) { if ((object) value == null) return (string) null; if (typeof (T) == typeof (object) || typeof (T).IsAbstract || typeof (T).IsInterface) { if (typeof (T).IsAbstract || typeof (T).IsInterface) JsState.IsWritingDynamic = true; string str = JsonSerializer.SerializeToString((object) value, value.GetType()); if (typeof (T).IsAbstract || typeof (T).IsInterface) JsState.IsWritingDynamic = false; return str; } else { StringBuilder sb = new StringBuilder(); using (StringWriter stringWriter = new StringWriter(sb, (IFormatProvider) CultureInfo.InvariantCulture)) { if (typeof (T) == typeof (string)) JsonUtils.WriteString((TextWriter) stringWriter, (object) value as string); else JsonWriter<T>.WriteObject((TextWriter) stringWriter, (object) value); } return ((object) sb).ToString(); } } public void SetEntry(string key, string value) { byte[] numArray = value != null ? ServiceStack.Text.StringExtensions.ToUtf8Bytes(value) : (byte[]) null; this.Set<byte[]>(key, numArray);}
恩恩,看樣子應該是,儲存的時候序列化了,並且使用utf8編碼,那好吧,get肯定也就是utf8編碼還原序列化成對象取出來的,所以泛型的存取資料並沒有跟編碼有什麼關係,那問題出在哪裡呢。
public T Get<T>(string key) { if (!(typeof (T) == typeof (byte[]))) return JsonSerializer.DeserializeFromString<T>(this.GetValue(key)); else return (T) base.Get(key); }public byte[] Get(string key) { return this.GetBytes(key); } public byte[] GetBytes(string key) { if (key == null) throw new ArgumentNullException("key"); return this.SendExpectData(Commands.Get, StringExtensions.ToUtf8Bytes(key)); }
上面代碼解釋了為什麼,對象怎麼儲存都沒有問題,再來看看string類型的資訊。
public void Index2() { test tes = new test(); tes.Name = "z中文"; tes.View = null; tes.list = new List<AAA>(); AAA d = new AAA(); d.Name = "123"; d.View = "asd"; AAA b = new AAA(); //b.Name = "我是特殊符號~!^?*$#<>\\"; b.Name = "2"; //b.View = "\""; b.View = "\\"; tes.list.Add(d); tes.list.Add(b); var aa = Newtonsoft.Json.JsonConvert.SerializeObject(tes); RedisManager.Execute(redis => redis.Set("niutaotao_cart", aa)); var ggg = RedisManager.Execute(redis => redis.Get<byte[]>("niutaotao_cart")); var ii = RedisManager.Execute(redis => redis.Get<string>("niutaotao_cart")); var json="{\"Name\":\"z中文\",\"View\":null,\"list\":[{\"Name\":\"123\",\"View\":\"asd\"},{\"Name\":\"2\",\"View\":\"\\\"}]}"; RedisManager.Execute(redis => redis.Set("ddd", json)); var o = RedisManager.Execute(redis => redis.Get<byte[]>("ddd")); var pp = RedisManager.Execute(redis => redis.Get<string>("ddd")); }
可以看看監視的結果,不多說直接。大概能看出點區別了。
問題來了,1.json編碼後首先轉移符並沒有特殊處理,而是直接寫進了json格式字串中
2. 重現向上看取資料的時候 1.雙引號有問題 ,貌似都變為了轉移符+雙引號
2.轉移符有問題,具體規律也看不大出來,貌似就是之前都加了兩個轉移符
3。中文編碼也有問題(這尼瑪很奇怪啊,從上面代碼來看,應該跟編碼沒關係才對)
我們繼續看代碼,看看set儲存資料的時候有什麼特別的地方。我們可以看到對象和字串的處理是不同的,嗯,估計問題就應該在這裡了,看代碼。
{"Name":"z中文","View":null,"list":[{"Name":"123","View":"asd"},{"Name":"2","View":"\\"}]} public static void WriteString(TextWriter writer, string value) { if (value == null) writer.Write("null"); else if (!JsonUtils.HasAnyEscapeChars(value)) { writer.Write(‘"‘); writer.Write(value); writer.Write(‘"‘); } else { char[] chArray = new char[4]; writer.Write(‘"‘); int length = value.Length; for (int index = 0; index < length; ++index) { switch (value[index]) { case ‘\b‘: writer.Write("\\b"); break; case ‘\t‘: writer.Write("\\t"); break; case ‘\n‘: writer.Write("\\n"); break; case ‘\f‘: writer.Write("\\f"); break; case ‘\r‘: writer.Write("\\r"); break; case ‘"‘: case ‘\\‘: writer.Write(‘\\‘); writer.Write(value[index]); break; default: if ((int) value[index] >= 32 && (int) value[index] <= 126) { writer.Write(value[index]); break; } else if ((int) value[index] < 55296 || (int) value[index] > 57343) { JsonUtils.IntToHex((int) value[index], chArray); writer.Write("\\u"); writer.Write(chArray); break; } else break; } } writer.Write(‘"‘); } }
好了基本上找到原因了,問題就出在方法中列出的”\,””等特殊符號的問題,而且看((int) value[index] < 55296 || (int) value[index] > 57343)句話應該是對這個範圍之外的符號進行了轉碼,具體什麼轉碼方式小弟不清楚,
所以呢,解決辦法就來了,我們是不是可以在儲存之前將這些被視為特殊符號,特殊處理的字元進行處理呢,然後區出來之後再解碼是不是就可以了。
好了試一把。我們就用UrlEncode試一下吧
System.Web.HttpUtility.UrlEncode( ““, Encoding.UTF8);System.Web.HttpUtility. UrlDecode ( ““, Encoding.UTF8); var aa = Newtonsoft.Json.JsonConvert.SerializeObject(tes); var jj = System.Web.HttpUtility.UrlEncode(aa, Encoding.UTF8); RedisManager.Execute(redis => redis.Set("niutaotao_cart", jj)); var ggg = RedisManager.Execute(redis => redis.Get<byte[]>("niutaotao_cart")); var ii = RedisManager.Execute(redis => redis.Get<string>("niutaotao_cart")); var zz = System.Web.HttpUtility.UrlDecode(ii,Encoding.UTF8);
看看結果
呵呵,尼瑪可以了。這裡解決問題就可以了,關於redis的儲存結構和實現,可以學習下下面兩篇文章。
關於redis的儲存結構大家可以看看(http://www.cnblogs.com/shanyou/archive/2012/09/04/2670972.html)
關於redis的實現可以看看(http://www.searchtb.com/2011/05/redis-storage.html)
redis官網(http://www.redis.cn/)