從工作實習的時候我就參與了一個專案叫做“雲文件管理系統”,說白了就是檔案的上傳、下載、預覽、分享、回收站等等一些操作。上傳下載以及分享都很Easy,複雜的就在文件預覽上,圖片、視訊、音訊都有現成的外掛可以使用,Office文件的線上預覽相對來說還是比較複雜的,當時也是看好多把Office文件轉換成html進行預覽的,也有轉換成Pdf預覽的,即使都實現預覽效果又怎樣。客戶提出一個需求叫做“文件版本修改歷史留存、可增加批註”,當時這個需求簡直讓人頭大,我不知道如何下手。我記得我當時的主管對這個需求也是很無助啊,過了幾天他就告訴我他找到一個外掛,不過只能在IE瀏覽器上使用Active X控制元件才能實現,而且除錯起來超級麻煩。我記得當時這個功能還是廢棄了。
時隔五年,我偶然間發現了一個文件線上預覽的服務,大家可以參考我的另一篇部落格《如何實現文件線上預覽》,這裡我就不再過多贅述了。即使到目前我也只是把文件線上預覽功能找到了解決方案,可是文件線上編輯一直是我的一個心結。2021年開年到現在,每天工作都很繁忙,午休的時間累積在一起我寫了一個基於雲服務的文件線上編輯系統(基礎功能基本已經實現),如果有需要的小夥伴可以參照我下面介紹的步驟來體驗一下:
- 開通開發者許可權
我們進入雲服務官網,申請加入開發者,跟著導航一步一步走就OK了,等待稽核通過,你會得到appId和appKey,這倆引數在呼叫介面時候會用到。
- 驗籤方法封裝
驗籤方法,就是對你呼叫介面的引數進行簽名,被呼叫方拿到你的引數要進行校驗,校驗通過才算是有效呼叫。
/// <summary> /// 生成驗籤資料 sign /// </summary> public class Signclient { public static string generateSign(string secret, Dictionary<string, string[]> paramMap) { string fullParamStr = uniqSortParams(paramMap); return HmacSHA256(fullParamStr, secret); } public static string uniqSortParams(Dictionary<string, string[]> paramMap) { paramMap.Remove("sign"); paramMap = paramMap.OrderBy(o => o.Key).ToDictionary(o => o.Key.ToString(), p => p.Value); StringBuilder strB = new StringBuilder(); foreach (KeyValuePair<string, string[]> kvp in paramMap) { string key = kvp.Key; string[] value = kvp.Value; if (value.Length > 0) { Array.Sort(value); foreach (string temp in value) { strB.Append(key).Append("=").Append(temp); } } else { strB.Append(key).Append("="); } } return strB.ToString(); } public static string HmacSHA256(string data, string key) { string signRet = string.Empty; using (HMACSHA256 mac = new HMACSHA256(Encoding.UTF8.GetBytes(key))) { byte[] hash = mac.ComputeHash(Encoding.UTF8.GetBytes(data)); signRet = ToHexString(hash); ; } return signRet; } public static string ToHexString(byte[] bytes) { string hexString = string.Empty; if (bytes != null) { StringBuilder strB = new StringBuilder(); foreach (byte b in bytes) { strB.AppendFormat("{0:X2}", b); } hexString = strB.ToString(); } return hexString; } }
記住,這個sign很重要,因為我剛開始把appKey當做sign傳入引數進行呼叫,總是報錯,後來才知道是我簽名傳了個寂寞。
- 介面呼叫
準備工作已經準備完畢了,下面就要開始介面呼叫了,API提供了新建文件、本地文件上傳、檔案刪除、檔案版本刪除等等,我這裡不一一呼叫了,只做了幾個我專案中用到的來羅列一下,我介面做的比較醜,湊合看。。
本地文件上傳,文件上傳成功之後返回的結果如下,包含第一個檔案版本Id和檔案的Id,這樣我的文件就上傳到雲服務了,我們拿著檔案版本ID,就可以進行線上編輯了。
我們拿過來剛才的檔案版本ID,進行線上編輯功能測試,我這裡直接做了個跳轉,跳轉多的頁面就是線上編輯頁面,如果你在Postman呼叫的話,會得到一大串HTML程式碼,就算你貼上過來,也是缺少css和js的,因為開啟的方式就不對。我們來看一下效果:
線上編輯效果:整體效果非常好,而且可以進行批註。
- 回撥函式
線上編輯是可以了,但是還沒完。因為你用之前的版本ID再次開啟會發現,什麼也沒更改,這是為什麼呢?因為我們修改的內容已經作為新版本進行儲存了,因為我是在本機進行測試,沒有釋出到伺服器,所以我也不知道儲存後的文件版本ID是多少,我根據檔案版本名字發現了規律,那就是從0開始依次累加,那我直接在檔案ID後加下劃線 _1進行測試,果然開啟了我上次修改並儲存的那個文件。於是我又進入API文件發現,這個線上編輯是實時本地儲存的,一旦你離開線上編輯,它就會回撥給你的介面,這裡我們先配置一下介面:
這裡乍一看是個外網地址,其實是我對映內網的地址,我搭建了內網對映服務,這樣我就可以在外網除錯的時候,對映到我本機電腦進行除錯了,於是我編輯完文件,並返回,這是回撥地址就起了作用了,值得注意的是,路由地址要按照介面給出的3rd/edit/callBack進行配置,否則你的介面接收不到任何東西。
好了,我們接收到了雲服務給我們回撥的資料,這樣我們就可以根據這些資料進行資料操作了。
能力有限,只會C#這一程式語言,僅供參考。
namespace WebApplication.Controllers { /// <summary> /// 基於WebUploader外掛的圖片上傳例項 /// </summary> public class UploadController : Controller { public static readonly string appId = "yozojqut3Leq7916"; public static readonly string appKey = "5f83670ada246fc8e0d1********"; #region 檔案上傳 /// <summary> /// 檔案上傳 /// </summary> /// <returns></returns> public ActionResult FileUpload() { return View(); } /// <summary> /// 上傳檔案方法 /// </summary> /// <param name="form"></param> /// <param name="file"></param> /// <returns></returns> [HttpPost] public ActionResult UploadFile(FormCollection form, HttpPostedFileBase file) { Dictionary<string, string[]> dic = new Dictionary<string, string[]>(); dic.Add("appId", new string[] { appId }); string sign = Signclient.generateSign(appKey, dic); try { if (Request.Files.Count == 0) { throw new Exception("請選擇上傳檔案!"); } using (HttpClient client = new HttpClient()) { var postContent = new MultipartFormDataContent(); HttpContent fileStreamContent = new StreamContent(file.InputStream); postContent.Add(fileStreamContent, "file", file.FileName); var requestUri = "http://dmc.yozocloud.cn/api/file/upload?appId=" + appId + "&sign=" + sign + ""; var response = client.PostAsync(requestUri, postContent).Result; Task<string> t = response.Content.ReadAsStringAsync(); return Json(new { Status = response.StatusCode.GetHashCode(), Message = response.StatusCode.GetHashCode() == 200 ? "上傳檔案成功" : "上傳檔案失敗", Data = t.Result }); } } catch (Exception ex) { //扔出異常 throw; } } #endregion #region 檔案刪除 /// <summary> /// 刪除檔案 /// </summary> /// <returns></returns> public ActionResult DelFile() { return View(); } /// <summary> /// 刪除檔案版本 /// </summary> /// <returns></returns> public ActionResult DelFileVersion() { return View(); } [HttpGet] public ActionResult FileDelete(string fileId) { Dictionary<string, string[]> dic = new Dictionary<string, string[]>(); dic.Add("fileId", new string[] { fileId }); dic.Add("appId", new string[] { appId }); string sign = Signclient.generateSign(appKey, dic); using (HttpClient client = new HttpClient()) { var requestUri = "http://dmc.yozocloud.cn/api/file/delete/file?fileId=" + fileId + "&appId=" + appId + "&sign=" + sign + ""; var response = client.GetAsync(requestUri).Result; Task<string> t = response.Content.ReadAsStringAsync(); return Json(new { Status = response.StatusCode.GetHashCode(), Message = response.StatusCode.GetHashCode() == 200 ? "請求成功" : "請求失敗", Data = t.Result },JsonRequestBehavior.AllowGet); } } [HttpGet] public ActionResult FileVersionDelete(string fileVersionId) { Dictionary<string, string[]> dic = new Dictionary<string, string[]>(); dic.Add("fileVersionId", new string[] { fileVersionId }); dic.Add("appId", new string[] { appId }); string sign = Signclient.generateSign(appKey, dic); using (HttpClient client = new HttpClient()) { var requestUri = "http://dmc.yozocloud.cn/api/file/delete/version?fileVersionId=" + fileVersionId + "&appId=" + appId + "&sign=" + sign + ""; var response = client.GetAsync(requestUri).Result; Task<string> t = response.Content.ReadAsStringAsync(); return Json(new { Status = response.StatusCode.GetHashCode(), Message = response.StatusCode.GetHashCode() == 200 ? "請求成功" : "請求失敗", Data = t.Result }, JsonRequestBehavior.AllowGet); } } #endregion #region 新建文件 /// <summary> /// 文件型別,檔名 /// </summary> /// <param name="templateType"></param> /// <param name="fileName"></param> /// <returns></returns> public ActionResult NewDoc(string templateType, string fileName) { Dictionary<string, string[]> dic = new Dictionary<string, string[]>(); dic.Add("templateType", new string[] { templateType }); dic.Add("fileName", new string[] { fileName }); dic.Add("appId", new string[] { appId }); string sign = Signclient.generateSign(appKey, dic); using (HttpClient client = new HttpClient()) { var requestUri = "http://dmc.yozocloud.cn/api/file/template?templateType=" + templateType + "&fileName=" + fileName + "&appId=" + appId + "&sign=" + sign + ""; var response = client.GetAsync(requestUri).Result; Task<string> t = response.Content.ReadAsStringAsync(); return Json(new { Status = response.StatusCode.GetHashCode(), Message = response.StatusCode.GetHashCode() == 200 ? "刪除檔案版本成功" : "刪除檔案版本失敗", Data = t.Result }); } } #endregion /// <summary> /// 線上編輯 /// </summary> /// <returns></returns> public ActionResult FileEdit() { return View(); } [HttpGet] public ActionResult GetFileEdit(string fileversionId) { Dictionary<string, string[]> dic = new Dictionary<string, string[]>(); dic.Add("fileVersionId", new string[] { fileversionId }); dic.Add("appId", new string[] { appId }); string sign = Signclient.generateSign(appKey, dic); string ret = "http://eic.yozocloud.cn/api/edit/file?fileVersionId=" + fileversionId + "&appId=" + appId + "&sign=" + sign + ""; return Redirect(ret); } [HttpPost] [Route("3rd/edit/callBack")] public ActionResult EditCallBack(string oldFileId, string newFileId, string message, int errorCode) { //檔案ID //575716913322135553 //檔案版本 依次累加 0 1 2 3 4 //575716913322135553_0 、 7 return Json(new { oldFileId = oldFileId, newFileId = newFileId, message = message, errorCode = errorCode }); } } }
有興趣的同志可以一起交流。