1. 程式人生 > >.Net使用HttpClient以multipart/form-data形式post上傳檔案及其相關引數

.Net使用HttpClient以multipart/form-data形式post上傳檔案及其相關引數

  目錄導航:
  
  前言:
  
  什麼是multipart/form-data請求:
  
  Html上傳圖片按鈕:
  
  使用ajax將圖片檔案流和相關引數傳遞到後端進行拼接:
  
  後端接收圖片和引數,並將圖片檔案流轉化為圖片位元組型別資料:
  
  重點,HttpClient拼接multipart/form-data形式引數post提交資料:
  
  使用Fiddler 4 抓包檢視請求的引數:
  
  總結:
  
  文章正文:
  
  回到頂部
  
  前言:
  
  本次要講的是使用.Net HttpClient拼接multipark/form-data形式post上傳檔案和相關引數,並接收到上傳檔案成功後返回過來的結果(圖片地址,和是否成功)。可能有很多人會說用ajax不是就可以輕鬆的實現嗎?的確是在不存在跨域問題的前提下使用ajax上傳檔案,接收返回結果是最佳的選擇。無奈的是我們對接的是第三方的一個上傳圖片的介面,而且對方並沒有對我們的域名設定允許跨域,為了能夠解決這一問題我們只能夠通過後端請求避免跨域問題。
  
  回到頂部
  
  什麼是multipart/form-data請求:
  
  關於multipart/form-data詳情檢視: https://www.cnblogs.com/tylerdonet/p/5722858.html
  
  回到頂部
  
  Html上傳圖片按鈕:
  
  <div class="cover-hd">
  
  <a href="javascript:;" class="a-uploadCustom">
  
  <input type="file" id="Logoimg" onchange="OnchangeImage(this)" /></a>
  
  </div>
  
  回到頂部
  
  使用ajax將圖片檔案流和相關引數傳遞到後端進行拼接:
  
  注意:因為我這裡呼叫第三方介面需要傳遞(appid應用程式唯一標識,random隨機數,和sign簽名)
  
  複製程式碼
  
  <script type="text/javascript">
  
  //後端圖片上傳
  
  function OnchangeImage(obj) {
  
  var formData = new FormData();
  
  var files = $(obj).prop('files'); //獲取到檔案列表
  
  console.log(files[0]);
  
  formData.append("imgType", 1);
  
  formData.append("appId","你需要傳遞的引數");
  
  formData.append("random", "你需要傳遞的引數");
  
  formData.append("file", files[0]);//圖片檔案流
  
  formData.append("sign", "你需要傳遞的引數");
  
  console.log(formData);
  
  jQuery.support.cors = true;
  
  $.ajax({
  
  async: true,
  
  contentType: false, //頭部請求內容格式
  
  dataType: 'json',
  
  type: 'post',
  
  data:formData,
  
  // 告訴jQuery不要去處理髮送的資料
  
  processData: false,
  
  url: "@Url.Action("ImageUpload", "MtVirtualStore")",//後端接收圖片介面
  
  success: function(data) {
  
  //後端Httpclient請求成功後返回過來的結果
  
  console.log(data);
  
  }
  
  });
  
  }
  
  </script>
  
  複製程式碼
  
  現在幾乎大部分的App都支援使用多個第三方賬號進行登入,如:微信、QQ、微博等,我們把此稱為多賬號統一登陸。而這些賬號的表設計,流程設計至關重要,不然後續擴充套件性賊差。本文不提供任何程式碼實操,但是梳理一下博主根據我司賬號模組的設計,提供思路,僅供參考。
  
  一、 自建的登陸體系
  
  1.1 手機號登陸註冊
  
  該設計的思路是每個手機號對應一個使用者,手機號為必填項。
  
  流程:
  
  首先輸入手機號,然後傳送到服務端。先判斷該手機號是否存在賬號,如果沒有,就會生成隨機驗證碼,將手機號和驗證碼繫結到Redis中,並設定一定的過期時間(過期時間一般是5分鐘,這就是我們一般手機驗證碼的有效期),最後將驗證碼通過簡訊傳送給使用者。
  
  使用者接收到驗證碼後,在介面填寫驗證碼以及密碼等基礎資訊,然後將這些資料傳送服務端。服務端收到後,先判斷在Redis裡面這個手機號對應的驗證碼是否一致,,失敗就返回錯誤碼,成功就給使用者建立一個賬號和儲存密碼。
  
  註冊成功後,使用者即可通過自己的手機號+密碼進行登陸。
  
  問題:
  
  使用者體驗差,需要完成獲取驗證碼,填寫驗證碼/密碼/使用者名稱等諸多的資訊完成註冊,然後才能使用;
  
  容易遺忘密碼,遺忘後,只能通過忘記密碼來重新設定密碼。
  
  1.2 優化註冊登陸
  
  該方案的思路是弱化密碼的必填性,即無論使用者是否註冊過,可通過手機號 + 驗證碼 直接進行登陸(保留手機號 + 密碼登入的方式)。
  
  流程:
  
  輸入手機號,然後傳送到服務端。服務端生成隨機驗證碼,將手機號和驗證碼繫結到Redis中,並設定一定的過期時間(過期時間一般是5分鐘,這就是我們一般手機驗證碼的有效期),最後將驗證碼通過簡訊傳送給使用者。
  
  使用者接收到驗證碼後,在介面只需填寫收到的驗證碼,提交到服務端。服務端收到後,先判斷在Redis裡面這個手機號對應的驗證碼是否一致,失敗就返回錯誤碼,成功就直接登入。如果是老使用者,直接拉取使用者資訊;如果是新使用者,則提示他可以完善使用者資訊(不強制)。
  
  使用者通過手機號 + 驗證碼登入後,也可選擇設定密碼,然後就可以通過手機號 + 密碼的方式登入,即:密碼是非必填項。
  
  使用者表設計:
  
  id    user_name    user_password    user_mobile    state    more
  
  使用者id    使用者名稱    使用者密碼    手機號碼    賬號狀態    其他資訊
  
  1.3 引入第三方賬戶方案
  
  1.3.1 微博登入
  
  進入 Web2.0 時代 ,微博開放了第三方網站登入, 產品說, 這個我們得要, 加個用微博帳號就能登入我們的App吧,而且得和我們自己的使用者表關聯。
  
  流程:
  
  客戶端呼叫微博登入的介面,進行輸入使用者名稱、密碼,登入成功後,會返回access_token,通過access_token調取API介面獲取使用者資訊。
  
  服務端通過使用者資訊在我們使用者表建立一個賬號,以後,該第三方賬號即可通過該微博賬號直接進行登陸。
  
  微博使用者資訊表設計:
  
  id    user_id    uid    access_token
  
  主鍵id    使用者id    微博唯一id    授權碼
  
  1.3.2 噩夢來臨
  
  緊接著, QQ又開放使用者登入了, 微信開放使用者登入了,網易開發使用者登入了。。。。。。一下子要接入好多家第三方登入了, 只能按照 “微博使用者資訊表” 新建一個表,重寫一套各個第三方登入。
  
  二、 優化賬號體系
  
  2.1 原賬號體系分析
  
  自建登陸體系:無論 手機號 + 密碼 , 還是 手機號 + 驗證碼 , 都是一種 使用者資訊+密碼 的驗證形式;
  
  第三方登入:也是使用者資訊+密碼 的形式, 使用者資訊即第三方系統中的 ID(第三方系統中的唯一標識), 密碼即 access_token, 只不過是一種有使用時效定期修改的密碼。
  
  回到頂部
  
  後端接收圖片和引數,並將圖片檔案流轉化為圖片位元組型別資料:
  
  複製程式碼
  
  //接收前端圖片檔案資訊
  
  [HttpPost]
  
  public JsonResult ImageUpload(FormContext context)
  
  {
  
  HttpPostedFileBase fileData = Request.Files[0];
  
  string appId=Request["appId"];
  
  string random=Request["random"];
  
  string sign=Request["sign"];
  
  string imgType=Request[www.jintianxuesha.com"imgType"];
  
  if (fileData != null)
  
  {
  
  try{
  
  string fileName = Path.GetFileName(fileData.FileName);//原始檔名稱
  
  byte[] byteFileData = ReadFileBytes(fileData);//檔案流轉為位元組流
  
  var resultContext =HttpClientPostUpload(byteFileData,appId,random,sign,imgType, fileName);
  
  return Json(new { code = 1, list = resultContext,msg="上傳成功~"});
  
  }
  
  catch (Exception ex)
  
  {
  
  return Json(new { code = 0, msg = ex.Message });
  
  }
  
  }
  
  else
  
  {
  
  return Json(new { code = 0, msg = "圖片上傳失敗,請稍後再試~" });
  
  }
  
  }
  
  //檔案流轉化為位元組
  
  /// <summary>
  
  /// 檔案流型別轉化位元組型別
  
  /// </summary>
  
  /// <param name="fileData"www.seocelve.com>檔案流資料</param>
  
  /// <returns></returns>
  
  private byte[] ReadFileBytes(www.haishenyul.com HttpPostedFileBase fileData)
  
  {
  
  byte[] data;
  
  using (Stream inputStream = fileData.InputStream)
  
  複製程式碼
  
  回到頂部
  
  重點,HttpClient拼接multipart/form-data形式引數post提交資料:
  
  複製程式碼
  
  /// <summary>
  
  /// 向目標地址提交圖片檔案引數資料
  
  /// </summary>
  
  /// <param name="bmpBytes">圖片位元組流</param>
  
  /// <param name="appId">appid</param>
  
  /// <param name="random">隨機數</param>
  
  /// <param name="sign">簽名</param>
  
  /// <param name="imgType">上傳圖片型別</param>
  
  /// <param name="fileName">圖片名稱</param>
  
  /// <returns></returns>
  
  public string HttpClientPostUpload(byte [www.pingguoyul.cn bmpBytes, string appId, string random,,string sign,string imgType,string fileName)
  
  {
  
  using (var client = new HttpClient())
  
  {
  
  List<ByteArrayContent>www.yuanhuapt.cn  list = new List<ByteArrayContent>();
  
  var dataContent = new ByteArrayContent(Encoding.UTF8.GetBytes(appId));
  
  dataContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data")//內容處置標頭
  
  {
  
  Name = "appId"
  
  };
  
  list.Add(dataContent);
  
  var dataContent2 = new ByteArrayContent(Encoding.UTF8.GetBytes(imgType));
  
  dataContent2.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data")
  
  {
  
  Name = "imgType"
  
  };
  
  list.Add(dataContent2)www.yasenyuLee.cn;
  
  var dataContent3 www.jiuhaoyulept.com= new ByteArrayContent(Encoding.UTF8.GetBytes(random));
  
  dataContent3.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data")
  
  {
  
  Name = "random"
  
  };
  
  list.Add(dataContent3);
  
  var dataContent4 = new ByteArrayContent(Encoding.UTF8.GetBytes(sign));
  
  dataContent4.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data")
  
  {
  
  Name = "sign"
  
  };
  
  list.Add(dataContent4);
  
  List<ByteArrayContent>www.yasenyuLe.com list2 = new List<ByteArrayContent>();
  
  var fileContent = new ByteArrayContent(bmpBytes);//填充圖片位元組
  
  fileContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data")
  
  {
  
  Name="file",
  
  FileName=fileName
  
  list.Add(fileContent)
  
  using (var content =new MultipartFormDataContent())
  
  Action<List<ByteArrayContent>> act =www.tongyayuLe.cn (dataContents) =>
  
  {//宣告一個委託,該委託的作用就是將ByteArrayContent集合加入到MultipartFormDataContent中
  
  foreach (var byteArrayContent in dataContents)
  
  act(list);//執行act
  
  try
  
  複製程式碼
  
  回到頂部
  
  使用Fiddler 4 抓包檢視請求的引數:
  
  因為我們沒有辦法看到我們所拼接成功後的multipark/form-data形式的資料,想要看到對應拼接的請求引數可以使用 Fiddler 4 抓包工具檢視:
  
  關於Fiddler 4抓包工具的使用可以閱讀該篇部落格:https://www.zongxyuLe.com  /p/55f7be58a7e4
  
  抓包獲取到的multipark/form-data形式的請求引數如下圖:
  
  回到頂部
  
  總結:
  
  寫到最後才發現,原本只需要一個簡單的請求就可以解決的問題因為跨域把這個問題變得如此繁瑣,搞得真叫人蛋痛。這裡我試過了很多種方式拼接multipark/form-data形式的請求引數,最後在堅持不懈的嘗試