首先聲明 ,我是編程菜鳥,剛做編程不久。歡迎拍磚
要實現的功能:
我們一旅游網站(南北游),可以發表游記。富文本,寫攻略,上傳圖片(我們會對圖片進行剪切分成大中小等各種圖片,方便不同地方調用)。
但有一種情況是用戶會把自己發表在其他網站的文章復制粘貼到我們網站,結果是我們只存下了圖片的鏈接地址,而沒有實際保存圖片。在其他地方用到該圖片的時候就會出現圖片大小不合適或者顯示掛圖的現象。
解決的辦法就是根據用戶粘貼的地址,將圖片下載到我們的服務器上。然后將文本框中其他網站地址換成自己網站的地址,保存。要實現這個功能可算是費了一番周章。
第一次嘗試的辦法:
在保存之前 使用jquery對富文本框中的圖片進行遍歷,發現不是我們網站的圖片就將其地址發到后台處理,異步提交使用$.post。
大概的一個代碼
$("#kecontent>img").each({ var src = $(this).attr("src"); if(src.indexOf("nanbeiyou.com")<=0){ $.post("http://file.nanbeiyou.com/******&remotePath="+encodeURI(src), success:function(){ //do some thing }) ; } });
后台根據傳過來的地址,模擬瀏覽器將圖片先保存到內容,然后進行切割保存到服務器上。(很快就實現了,發現要下載的圖片保存到服務器上,着實高興了一下,以為快完工了呢)
改正1
很快發現問題,程序在www.nanbeiyou.com域下,而下載下來的圖片在file.nanbeiyou.com資源域下,$.post不能跨域獲得返回來的圖片名字。負責人說用iframe跨域獲得吧。
我想起jquery 的jsonp方式可以跨域,網上查了查:http://www.cnblogs.com/know/archive/2011/10/09/2204005.html ,然后改用$.ajax jsonp進行異步請求。
調試發現使用jsonp確實可以把圖片的名字返回來(加上前邊固定的地址就ok了),但是當發了多張圖片之后,怎么將圖片的名字和它原來的地址對應上,替換原來的其他網站地址呢?於是在發送圖片原地址之前,給該圖片標簽屬性加上一個唯一標識數(使用index正合適,不用再使用隨機數),然后將圖片源地址和index數一起發送到后台,后台處理了圖片之后,將圖片名稱和 這個index再返回來,根據返回的index匹配頁面中的img,將其中的地址替換。這想法不錯,實現之。
前台頁面代碼:
$("#kecon>img").each(function(index){ var src = $(this).attr("src"); $(this).attr("random",index); if(src.indexOf("nanbeiyou.com")<=0){ $.ajax({ type:"get", async:true, url:"http://file.nanbeiyou.com/*****&remotePath="+encodeURI(src)+"&random="+index, dataType:"jsonp", jsonp: "callbackparam",//傳遞給請求處理程序或頁面的,用以獲得jsonp回調函數名的參數名(默認為:callback) jsonpCallback:"success_jsonpCallback",//自定義的jsonp回調函數名稱,默認為jQuery自動生成的隨機函數名 success : function(json){ $("#kecon>img[random='"+json[0].random+"']").attr("src")=json[0].name; }, error:function(){ alert('fail'); } }); } });
給<img>加上屬性名叫random,后來發現可能這是個jquery保留字,隨之將random改成rands。
愚蠢低級的錯誤總是有:
$("#kecon>img[rands='"+json[0].rands+"']").attr("src")=json[0].name; 應改成
$("#kecon>img[rands='"+json[0].rands+"']").attr("src",json[0].name);
后台實現:這是從網上找的,同事給我的,我也不知道出處。
/// <summary> /// 根據輸入的圖片網址,將圖片下載到內存中 /// </summary> /// <param name="downAddress"></param> /// <returns></returns> private static MemoryStream DownLoad(string downAddress) { MemoryStream ms = new MemoryStream(); HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(downAddress); request.AllowAutoRedirect = false; request.Method = "GET"; request.UserAgent = "Opera/9.25 (Windows NT 6.0; U; en)"; HttpWebResponse response = (HttpWebResponse)request.GetResponse(); Stream responseStream = response.GetResponseStream(); int i; byte[] buffer = new byte[2048]; //delSetProcess pro = new delSetProcess(SetProcess); do { i = responseStream.Read(buffer, 0, 2048); ms.Write(buffer, 0, i); //this.Invoke(pro); } while (i > 0); response.Close(); responseStream.Close(); responseStream.Dispose(); response = null; request = null; responseStream = null; return ms; }
后台保存圖片,調用download。
if (context.Request.Params["pictype"] == "Travel" && context.Request.Params["remotePath"] != null) { string oldPath = context.Request.Params["remotePath"]; string oldName = oldPath.Substring(oldPath.LastIndexOf('/')+1); string oldExt = oldPath.Substring(oldPath.LastIndexOf('.')); int index = int.Parse(context.Request.Params["random"]); MemoryStream ms = DownLoad(oldPath); //給保存在本地的圖片設定一個隨機數名字 string newName = PhotoInfo.GeneratID(); using (PhotoInfoChain pi = ThumbFactory.getInstancePhotoinfoChain("Travel")) { pi.Stream = ms; //根據名字,將圖片切成大中小等圖片保存到本地服務器 pi.Save(newName);
newExt = pi.InfoList[0].ImgFormat; context.Response.ContentType = "text/plain"; string callbackFunName = context.Request["callbackparam"]; context.Response.Write(callbackFunName + "([{name:\""+newName + newExt+"\",rands:\""+index+"\"}])"); }; }
改正2
使用上邊的jquery方法,當往文本框粘貼了兩張圖片時,打開瀏覽器調試查看網絡信息,是往后台發送了兩個jsonp異步請求,每個請求都成功返回了獲得了圖片新名稱和index (0和1),但是$.ajax()中的success方法中得到的json,能得到兩次,但是兩次的json[0].rands都是1,或者都是0.而不是兩個都獲得。也不知道是什么原因。哪位高手遇到過,給解釋解釋。
還有就是使用異步,將多個請求都發出去了,不知道什么時間圖片都下載完成,地址替換完成,什么時間將整個游記表單提交保存。所以放棄一個圖片地址一個jsonp請求。而是首先對頁面文本框進行遍歷,將需要替換的所有圖片地址放到一個string變量中,用逗號分隔。一次異步請求所有的圖片地址發送到后台代碼,然后后台代碼對發送的來的圖片地址string對象解析分隔,對每個地址進行圖片下載,然后將圖片新名字和圖片原地址(跟前台頁面中的地址匹配,以便付新值替換)存入json中返回。
改進3
試着以上方法粘貼了10張左右圖片,可以成功保存游記,這個過程有點慢,但是能成功,所以在點擊保存游記的同時彈出一個小提示“游記正在保存,請稍后。。”原理很簡單,有個隱藏div,點擊保存之后,該提示div顯示出來。
改進4
使用改進3之后,多張圖片也保存了,也給用戶一個提醒了,原以為這樣就ok了。沒想到又發現問題了,當粘貼的圖片很多時比如50張圖片,保存不成功了,一直提示正在保存,也不報錯,發出去的jsonp請求沒有了回音,沒有查看瀏覽器網絡信息,就以為是發送時間之后時間太長,超時了,所以不成功。查查百度、谷歌。$.ajax確實有個timeout參數,更認為是timeout原因。所以給$.ajax設置上timeout參數之后,還是保存失敗,也沒有任何提醒。 心想保存失敗就失敗吧,怎么也要給個提示告訴我失敗了,別讓我等着了,設定的error函數怎么不觸發呢?
原來對於jsonp格式的$.ajax請求,對於jquery1.5版本之前,error方法不能觸發。我們就用的jquery1.4,它怎么就這么寸呢。還好看有個插件jquery.jsonp解決了這個問題,好吧再使用jquery.jsonp方法再重寫一遍吧。 jquery.jsonp api :https://github.com/jaubourg/jquery-jsonp/blob/master/doc/API.md.
這種的默認回調函數名為:“_jqjsp”。改寫好之后,設置timeout短一些之后,還真能檢測到timeout錯誤。心想這總成了吧,把timeout稍微設置長一點,一次粘貼40多張圖片,點擊保存。報錯error錯誤,不是timeout錯誤。這是什么原因呢?查看一些瀏覽器 網絡監測。http 400錯誤,url過長。當初使用$.ajax時就保存失敗也不報錯,是不是也是url過長的原因呢。url過長能不能用post提交呢,嘗試了一把 $.ajax jsonp格式的提交只能用“get”,不可以用"post"提交。完了做的工作都白做了,一切都歸零了。
改用iframe提交吧,iframe可以使用post提交,這樣提交的個數就沒有限制了。
后台圖片保存程序改用多線程保存。對需要下載的圖片放到一個dictionary{picpath,‘等待’}中。用for循環遍歷,下載每一個圖片。由於使用多線程,所以在一個線程判斷dictionary中該圖片是否下載時,進行鎖定。修改該圖片的狀態“等待”到‘正在下載’。然后放開鎖。其他線程就可以判斷了,如果狀態是“正在下載”,就continue,循環判斷下個圖片是否下載了。對於用於圖片保存分隔的類pi,由於多線程共用,會導致一個圖片還沒下載完保存,就有新圖片加入,導致下載下來的圖片不完整。所以把這種共享類放到每個線程中,給每個線程單獨分(實例化)一個。在子線程保存的時候根據相對路徑判斷物理路徑是使用了:httpcontent.current.server.mappath()方法,而在子線程中httpcontent.current為null無法判斷。后來在傳遞參數的時候直接傳物理地址,而是相對地址。這個轉換在主線程中可以完成。 如果用戶往富文本中粘貼兩邊重復的圖片,會導致dictionary中鍵值重復,隨在前天頁面判斷是否有重復的圖片地址,如果存在則只在source中存入一份傳到后台。當傳遞了兩張重復的圖片地址,在返回來替換的時候,如果只是replace,則只會替換一個,第二個符合條件的不進行替換。使用正則表達式則可以解決這個問題。
后台下載保存可能會出現異常,捕獲異常,然后返回圖片名字為“nanbeiyou”,其他接到反饋進行判斷,如果是“nanbeiyou”就知道該圖片下載失敗,不替換該圖片地址。
前天往后台傳遞用post沒有了圖片數量限制,但是在后台跳轉的時候url長度還是有的限制,隨在前台對富文本中圖片的數量進行判斷,每90張往后台傳一次,返回回調,再回調中判斷未處理的圖片個數,如果為0,就提交整個表單,如果大於90張,則傳90張,在0到90之間,則是吧剩余的都傳完。
后發現有些網站上的圖片url很特殊:以qq空間為例:http://user.qzone.qq.com/307722709/blog/1341022967#!app=2&via=QZ.HashRefresh&pos=1340365654中的一個圖片地址:
http://b206.photo.store.qq.com/http_imgload.cgi?/rurl4_b=809f8ca26fba5d947e8642f699ec5571421e5e9c7e38a6cbfdf982c0696537766a8ab54a8f9302ed2743d23408c344b9e9dfeea2928c4f3e0b25cb74c9ebb0da168db30206c32443959287af61818a8321d7dbf5&a=206&b=206
1、該圖片地址中沒有圖片后綴名,通過url分析得不到圖片類型。所以在通過reque請求之后,得到
HttpWebResponse response = (HttpWebResponse)request.GetResponse()
之后,可以通過response.contentType獲得圖片的類型,從而得到后綴名。
2.地址中&粘貼到富文本框中之后,對應的隱藏域Tcontent中時&所以需要對該url進行正則匹配將所以的&換成&
3、有的圖片地址下載時需要跳轉類似於mvc這種,所以將download()中的
request.AllowAutoRedirect = false;這一句去掉。
4、url中存在一個?,在正則中這是一個正則匹配符合,所以需要將該url中?換成\? 在才能正則匹配到正確的url
4、
// $.ajax({
// type:"get",
// timeout:10000,
// async:true,
// url:"@(filePath)/upload.upx?random="+Math.random()+"&pictype=Travel&remotePath="+encodeURI(sources),
// dataType:"jsonp",
// jsonp: "callbackparam",//傳遞給請求處理程序或頁面的,用以獲得jsonp回調函數名的參數名(默認為:callback)
// jsonpCallback:"success_jsonpCallback",//自定義的jsonp回調函數名稱,默認為jQuery自動生成的隨機函數名
// success : function(json){
// $("#Cover").val(json[0].newPath);
// for(var i=0;i<json.length;i++){
// //$("#kecontext>img[src='"+json[i].oldPath+"']").attr("src",$("#imgpath").val()+json[i].newPath);
// if ($("#imgTitle").val() == "") {
// $("#imgTitle").val(json[i].newPath);
// }
// else {
// $("#imgTitle").val($("#imgTitle").val() + ";" + json[i].newPath);
// }
// $("#Tcontent").val($("#Tcontent").val().replace(json[i].oldPath,$("#imgpath").val()+json[i].newPath));
//
// }
//
// $("#form").submit();
//
// },
// error:function(x,t,m){
// if(t==="timeout")
// {
// alert("您上傳的圖片太多,保存失敗!");
// }else{
// alert("t="+t);
// alert("m="+m);
// alert("x="+x);
// }
//
// }
// });
// $.jsonp({
// type:"post",
// url:"@(filePath)/upload.upx?random="+Math.random()+"&pictype=Travel&remotePath="+encodeURI(sources)+"&callback=?",
// timeout:120000,
// context:sources,
// //callbackParameter:"callbackparam=success_jsonpCallback",
// success:function(json,textStatus){
// $("#Cover").val(json[0].newPath);
// for(var i=0;i<json.length;i++){
//
// if ($("#imgTitle").val() == "") {
// $("#imgTitle").val(json[i].newPath);
// }
// else {
// $("#imgTitle").val($("#imgTitle").val() + ";" + json[i].newPath);
// }
// $("#Tcontent").val($("#Tcontent").val().replace(json[i].oldPath,$("#imgpath").val()+json[i].newPath));
//
// }
// $("#form").submit();
//
// },
// error:function(xOptions, textStatus){
// if(textStatus==="timeout"){
// alert("游記保存超時");
// }
// else{
// alert("發生錯誤了!");
// }
//
// }
//
// });
string returnVal = "[";
string returnVal = "";
foreach (string path in arr)
{
string oldPath = path;
string oldName = oldPath.Substring(oldPath.LastIndexOf('/') + 1);
string oldExt = oldPath.Substring(oldPath.LastIndexOf('.'));
MemoryStream ms = DownLoad(oldPath);
string newName = PhotoInfo.GeneratID();
string newExt = "";
Func<string, string> _func = x => x;
using (PhotoInfoChain pi = ThumbFactory.getInstancePhotoinfoChain("Travel"))
{
pi.Func = _func;
pi.Filename = oldName;
pi.Stream = ms;
pi.Save(newName);
ms.Dispose();
newExt = pi.InfoList[0].ImgFormat;
//returnVal +="{oldPath:\""+ oldPath +"\"" +","+ "newPath:" +"\""+ newName + newExt+"\"},";
returnVal += newName + newExt + ":";
}
}
returnVal = returnVal.Substring(0, returnVal.Length - 1);
returnVal += "]";
string callbackFunName = context.Request["callbackparam"];
context.Response.Write(callbackFunName + "(" + returnVal + ")");
context.Response.Write("_jqjsp" + "(" + returnVal + ")");