I have participated in a project called “Cloud document Management System” since my internship. To put it bluntly, it is the operation of uploading, downloading, preview, sharing and recycling of files. Uploading, downloading and sharing are Easy, complex on the document preview, pictures, video, audio have ready-made plug-ins can be used, Office documents online preview is relatively complex, at that time is also promising to convert more Office documents into HTML preview, there are also converted into Pdf preview, So what if they all implement the preview. There was a request from the client called “document version change history retention, can add comments”, which was so overwhelming that I didn’t know how to do it. I remember that my supervisor at the time was helpless about this requirement and told me a few days later that he had found a plugin that could only be implemented using the Active X control in Internet Explorer and was extremely cumbersome to debug. I think it was scrapped at the time.

Five years later, I stumbled upon a service for document previewing online, which I won’t go into too much detail here, as you can see in my other blog post, How to Implement Document previewing online. Even so far I have only managed to find a solution for online document preview, but online document editing has always been a problem for me. Since the beginning of 2021, I have been busy with work every day and accumulated my lunch break time. I have written an online document editing system based on cloud service (basic functions have been basically realized). If you need it, you can refer to the steps I introduce below to experience it:

Open developer privileges

We enter theCloud Service Official websiteAppId and appKey, which will be used when calling the interface.

Check method encapsulation

Check method, that is, you call the parameters of the interface for signature, the called party to get your parameters to check, check is valid call.

// </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; }}Copy the code

Remember, this sign is very important, because I started calling appKey as sign and kept getting an error, until I realized it was my signature that sent a lonely message.

 

Interface call

Preparation work is ready, the following is about to start the interface call, API provides a new document, local document upload, file deletion, file version deletion and so on, I am not here a call, only do a few of my projects to list, I do the ugly interface, just so-so.

Local document upload, the result returned after successful document upload is as follows, including the first file version Id and the file Id, so my document is uploaded to the cloud service, we can hold the file version Id, online editing.

If you call it in Postman, you will get a large string of HTML code. Even if you paste it, it is also missing CSS and JS, because the way to open it is not right. Let’s take a look at the effect:

Online editing effect: the overall effect is very good, and can be annotated

The callback function

Online editing is ok, but it’s not finished. Because if you open it again with the previous version ID, nothing has changed. Why is that? Because we modify content is saved as a new version, because I am in the unit test, not published to the server, so I don’t know how much is the saved document version ID, I found, according to the version file names that is starting from 0, in turn, additive, after that I directly on the document ID underlined _1 test, Sure enough, I opened the document I had modified and saved last time. So I went back to the API document and found that the online edit is saved locally in real time. Once you leave the online edit, it will call back to your interface. Here we configure the interface:

So this looks like an extranet address, but it’s actually an Intranet address that I’m mapping to, and I set up an Intranet mapping service so that when I’m debugging from the extranet, I can map it to my PC for debugging, and then I edit the document, and I return, and this is the callback address that works, and it’s worth noting that, Routing addresses must be configured as 3rd/ Edit /callBack from the interface, otherwise your interface will receive nothing.

 

Ok, so we receive the data that the cloud service calls back to us, so we can do data operations based on that data.

Limited ability, can only C# this programming language, for reference only.

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
            });
        }
    }
}
Copy the code

Comrades who are interested can communicate with each other.

Github has made this Demo open source