文件上传属于常见业务,很多地方都用的到(比如图片上传);
确切的说,这里的“本地”是指的项目所在的服务端,只是在项目服务端再次请求另外一个服务端进行文件二次上传。
比如:我们上传图片的时候,请求项目的服务器需要上传一份,同时还要上传一份到cdn服务器。
示例使用控件: el-upload 作为演示
1 <style> 2 .uploader { 3 border: 1px dashed #d9d9d9; 4 border-radius: 6px; 5 cursor: pointer; 6 position: relative; 7 overflow: hidden; 8 } 9 10 .uploader:hover { 11 border-color: #409EFF; 12 } 13 14 .avatar-uploader-icon { 15 font-size: 28px; 16 color: #8c939d; 17 text-align: center; 18 } 19 20 .banners-size { 21 width: 280px; 22 height: 120px; 23 line-height: 120px; 24 } 25 26 .avatar-banners { 27 width: 280px; 28 height: 120px; 29 background-size: cover; 30 } 31 32 .el-upload__tip { 33 color: red; 34 } 35 </style>
1 <el-upload class="upload-demo" 2 ref="banners" 3 :limit="1" 4 action="https://jsonplaceholder.typicode.com/posts/" 5 accept="image/jpeg,image/png" 6 name="bannersImg" 7 :http-request="uploadFile" 8 :auto-upload="false"> 9 <el-button slot="trigger" size="small" type="primary" v-on:click="selectUpload('bannersImg')">选取文件</el-button> 10 <el-button style="margin-left: 10px;" size="small" type="success" v-on:click="submitUpload('bannersImg')">确定上传</el-button> 11 <div slot="tip" class="el-upload__tip">只能上传jpg文件,且不超过200kb,尺寸:440x240</div> 12 </el-upload> 13 <div class="uploader banners-size"> 14 <img v-if="banners_imageUrl" :src="banners_imageUrl" class="avatar-banners"> 15 </div>
前端核心代码:
1 //选取文件 2 selectUpload: function (obj) { 3 this.clearFiles(obj); 4 }, 5 //确定上传 6 submitUpload: function (obj) { 7 this.$refs.banners.submit(); 8 }, 9 //清除文件状态 10 clearFiles: function (filename) { 11 this.$refs.banners.clearFiles(); 12 }, 13 14 //文件上传 15 uploadFile: function (params) { 16 //console.log(params); 17 var _filename = params.filename; 18 19 //判断图片格式 20 const isJPG = params.file.type === 'image/jpeg'; 21 if (!isJPG) { 22 this.$message.error('上传图片只能是 JPG 格式!'); 23 this.clearFiles(_filename); 24 return isJPG; 25 } 26 this.BannersUpload(params.file); 27 }, 28 //图片上传 29 BannersUpload: function (file) { 30 const isLtSize = file.size / 1024 / 1024 / 1024 < 20; //不能超过xx kb 31 if (!isLtSize) { 32 this.$message.error('上传图片大小不能超过 20kb!'); 33 this.clearFiles(file.filename); 34 return isLtSize; 35 } 36 37 //执行上传操作 38 this.FileUpload(file) 39 .then(res => { //返回结果 40 var getResult = JSON.parse(res.data); 41 //成功 42 if (getResult.Code == 0) { 43 this.$message.success('上传成功'); 44 this.banners_imageUrl = getResult.Data; 45 this.addForm.BannersImg = getResult.Data; 46 } 47 //错误 48 if (getResult.Code == 1) { 49 if (getResult.Message) { 50 this.$message.error(getResult.Message); 51 } 52 } 53 }).catch(function (res) { 54 this.$message.success('请求异常'); 55 }); 56 }, 57 //文件上传 58 FileUpload: function (file) { 59 var fd = new FormData(); 60 fd.append("filedata", file); 61 //异步获取 62 return axios.request({ 63 method: 'post', 64 baseURL: this.apiUrl, 65 url: '/WxLive/UploadImg', 66 params: { "filename": "filedata" }, //是即将与请求一起发送的 URL 参数 67 data: fd, //浏览器专属:FormData, File, Blob 必须是以下类型之一:string, plain object, ArrayBuffer, ArrayBufferView, URLSearchParams 68 headers: { "Content-Type": "multipart/form-data;charset=UTF-8" }, 69 }); 70 }
本地服务器上传接口:
1 /// <summary> 2 /// 本地上传图片 3 /// </summary> 4 /// <returns></returns> 5 [HttpPost] 6 public JsonResult UploadImg(string filename) 7 { 8 var result = new Common.CommonResult(1, "网络请求错误"); 9 try 10 { 11 string basePath = ConfigurationManager.AppSettings["basePath"] ?? "\Images"; //服务器上传文件夹 12 string imgpath = string.Empty; 13 14 HttpPostedFileBase file = Request.Files.Get(filename); //从browser传过来的的文件 15 //HttpPostedFile file = System.Web.HttpContext.Current.Request.Files.Get(filename); //从browser传过来的的文件 16 17 if (file != null && file.ContentLength > 0) 18 { 19 //文件后缀名 20 string fileExtension = Path.GetExtension(file.FileName).ToLower(); 21 22 //获取文件名 23 string fileName = DateTime.Now.ToString("yyyy_MM_dd_HH_mm_ss_ffff"); 24 fileName += fileExtension; 25 26 Stream postStream = file.InputStream; 27 byte[] bytes = new byte[postStream.Length]; 28 postStream.Read(bytes, 0, file.ContentLength); 29 postStream.Seek(0, SeekOrigin.Begin); //设置当前流的位置为流的开始 30 postStream.Close(); 31 32 //写入本地 33 var repath = UploadHelper.UploadFile(bytes, basePath, fileName); 34 35 result.Code = 0; 36 result.Message = "OK"; 37 result.Data = repath; 38 } 39 return Json(JsonConvert.SerializeObject(result)); 40 } 41 catch (Exception) 42 { 43 return Json(JsonConvert.SerializeObject(result)); 44 } 45 }
上传本地文件处理方法:UploadHelper.UploadFile()
1 /// <summary> 2 /// 上传文件到本地 3 /// </summary> 4 /// <param name="filename"></param> 5 /// <param name="basePath"></param> 6 /// <param name="bytes"></param> 7 /// <returns></returns> 8 public static string UploadFile(byte[] bytes,string basePath, string filename) 9 { 10 string result = ""; 11 try 12 { 13 if (bytes != null) 14 { 15 //保存文件路径,比如:\Images\2020-01-01\,这里的basePath相当于\Images 16 string upfilePath = basePath + "\" + DateTime.Now.ToString("yyyy-MM-dd") + "\"; //根路径 17 //上传到本地 ~/表示上级目录,如果不加则表示同级目录 18 string filePath = System.Web.HttpContext.Current.Server.MapPath("~/" + upfilePath); 19 if (!Directory.Exists(filePath)) 20 { 21 Directory.CreateDirectory(filePath); 22 } 23 //完整路径:项目目录\Images\2020-01-01\abc.jpg 24 string fileSavePath = Path.Combine(filePath, filename); //合并成完整的文件路径 25 26 //写入本地 27 using (FileStream fs = new FileStream(fileSavePath, FileMode.Create)) 28 { 29 fs.Write(bytes, 0, bytes.Length); 30 fs.Close(); 31 fs.Dispose(); 32 } 33 //返回路径:/Images/2020-01-01/abc.jpg 34 result = (upfilePath + filename).Replace("\", "/"); 35 } 36 else 37 { 38 result = "上传的文件信息不存在!"; 39 } 40 } 41 catch (Exception ex) 42 { 43 return ""; 44 } 45 return result; 46 }
一般向本地服务器上传的同时,还要向cdn服务器上传一份,请求cdn服务器接口代码如下:
1 /// <summary> 2 /// 上传文件到远程服务器 3 /// </summary> 4 /// <param name="bytes"></param> 5 /// <param name="basepath"></param> 6 /// <param name="filename"></param> 7 /// <returns></returns> 8 public static string PostUploadFile(byte[] bytes, string basepath, string filename) 9 { 10 string repath = ""; 11 string hosturl = ConfigurationManager.AppSettings["apihost"] ?? "https://cache.xxxxxx.com:8080/"; //远程服务器路径 12 try 13 { 14 var apiurl = hosturl + "api/UpLoad/ReceiveFile"; //远程请求接口 相当于:https://cache.xxxxxx.com:8080/api/UpLoad/ReceiveFile 15 HttpClient httpClient = HttpClientFactory.GetHttpClient(); 16 string upfilePath = basepath + "\" + DateTime.Now.ToString("yyyy-MM-dd") + "\"; //保存路径,比如:\Images\2020-01-01\,basepath相当于\Images 17 using (var multipartFormDataContent = new MultipartFormDataContent()) //MultipartFormDataContent相当于 "Content-Type": "multipart/form-data" 18 { 19 //二进制流传输,远程服务器可以使用: Request.Files.Get("filedata")接收 20 multipartFormDataContent.Add(new ByteArrayContent(bytes, 0, bytes.Length), "filedata", filename); 21 //远程服务器可以使用: Request["filePath"]接收 22 multipartFormDataContent.Add(new StringContent(upfilePath, Encoding.UTF8, "application/x-www-form-urlencoded"), "filePath"); 23 24 //post请求 25 var response = httpClient.PostAsync(apiurl, multipartFormDataContent).Result; 26 if (response.StatusCode == System.Net.HttpStatusCode.OK) 27 { 28 var result = response.Content.ReadAsStringAsync().Result; 29 if ((int)response.StatusCode == 200) 30 { 31 repath = (upfilePath + filename).Replace("\", "/"); 32 } 33 } 34 } 35 return repath; 36 } 37 catch (Exception ex) 38 { 39 return repath; 40 } 41 }
接下来看看在远程cdn服务器上如何接收本地服务器上传过来的文件,实际上跟本地服务上传没多大区别。
创建一个文件上传服务,比如创建一个WebApI服务,创建一个文件上传接口:/api/UpLoad/ReceiveFile,然后将该服务发布到IIS上即可
然后客户端请求路径:https://cache.xxxxxx.com:8080/api/UpLoad/ReceiveFile
1 /// <summary> 2 /// 文件上传服务 3 /// </summary> 4 public class UpLoadController : ApiController 5 { 6 /// <summary> 7 /// 文件接收接口 8 /// </summary> 9 /// <returns></returns> 10 [HttpPost] 11 public HttpResponseMessage ReceiveFile() 12 { 13 HttpResponseMessage response = null; 14 try 15 { 16 HttpPostedFile file = HttpContext.Current.Request.Files.Get("filedata"); //获取文件流 17 string getfilepath = HttpContext.Current.Request["filePath"] ?? "\Images\"; //获取保存路径(不包含文件名),默认:\Images\ 18 var filename = file.FileName; //获取文件名 19 20 //保存到本地的路径,~/表示指向上级根目录 21 string savePath = HttpContext.Current.Server.MapPath("~/" + getfilepath); 22 23 //创建文件夹 24 if (!Directory.Exists(savePath)) 25 { 26 Directory.CreateDirectory(savePath); 27 } 28 29 //保存文件到指定路径下 30 string saveFilePath = Path.Combine(savePath, filename); 31 Stream postStream = file.InputStream; 32 using (FileStream fs = new FileStream(saveFilePath, FileMode.Create)) 33 { 34 byte[] new_b = new byte[postStream.Length]; 35 while (postStream.Read(new_b, 0, new_b.Length) != 0) 36 { 37 fs.Write(new_b, 0, new_b.Length); 38 } 39 postStream.Close(); 40 fs.Close(); 41 fs.Dispose(); 42 } 43 response = Request.CreateResponse(HttpStatusCode.OK); //成功返回200 44 } 45 catch (Exception) 46 { 47 response = Request.CreateResponse(HttpStatusCode.BadRequest); //返回400 48 } 49 return response; 50 } 51 52 }