/// <summary> /// 接收文件、保存记录 /// </summary> /// <param name="p_section"></param> /// <returns></returns> async Task <FileDesc> ReceiveFile(FileMultipartSection p_section) { FileDesc desc = new FileDesc(); desc.ID = Kit.NewID; desc.Name = p_section.FileName; if (long.TryParse(_context.Request.Headers["uid"], out var id)) { desc.Uploader = id; } desc.Info = p_section.Name; // 扩展名 int pt = desc.Name.LastIndexOf('.'); string ext = pt > -1 ? desc.Name.Substring(pt).ToLower() : ""; // 根据文件名获取两级目录 string dir = GetDir(desc.Name); desc.Path = Path.Combine(_volume, dir, desc.ID + ext).Replace('\\', '/'); EnsurePathExist(dir); string fullPath = Path.Combine(Cfg.Root, _volume, dir, desc.ID + ext); _result.Add(desc.Path); try { using (var writeStream = File.Create(fullPath)) { // FileStream 类型为 MultipartReaderStream await p_section.FileStream.CopyToAsync(writeStream, _bufferSize, _context.RequestAborted); desc.Size = writeStream.Length; } await _db.Exec("上传文件", desc); } catch { FileInfo fi = new FileInfo(fullPath); if (fi.Exists) { try { fi.Delete(); } catch { } } throw; } return(desc); }
/************************ Section 结构 ************************ * Content-Length: 60408 * Content-Type:multipart/form-data; boundary=ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC * * --ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC * Content-Disposition: form-data; name="fixedvolume" * * photo * --ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC * Content-Disposition: form-data;name="72 x 72 (.png)"; filename="photo.jpg" * Content-Type: application/octet-stream * Content-Transfer-Encoding: binary * * ... binary data of the jpg ... * --ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC * Content-Disposition: form-data;name="thumbnail"; filename="thumbnail.jpg" * Content-Type: application/octet-stream * Content-Transfer-Encoding: binary * * ... binary data of the jpg ... * --ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC * Content-Disposition: form-data;name="00:00:06 (480 x 288)"; filename="ImageStabilization.wmv" * Content-Type: application/octet-stream * Content-Transfer-Encoding: binary * * ... binary data ... * --ZnGpDtePMx0KrHh_G0X99Yef9r8JZsRJSXC-- ****************************/ public async Task Handle() { // 读取boundary var boundary = _context.Request.GetMultipartBoundary(); if (string.IsNullOrEmpty(boundary)) { // 不支持的媒体类型 _context.Response.StatusCode = 415; Log.Information("无Boundary,上传失败"); return; } SortedSetCache cache = null; _db = new MySqlAccess(false); // 记录已成功接收的文件,以备后续异常时删除这些文件 List <FileDesc> sucFiles = new List <FileDesc>(); try { var reader = new MultipartReader(boundary, _context.Request.Body); var section = await reader.ReadNextSectionAsync(_context.RequestAborted); var fmSection = section.AsFormDataSection(); if (fmSection != null && fmSection.Name == "fixedvolume") { // 选择固定卷 _volume = (await fmSection.GetValueAsync()).ToLower(); if (!Cfg.FixedVolumes.Contains(_volume)) { // 无此固定卷,状态码:未满足前提条件 _context.Response.StatusCode = 412; Log.Information("不存在固定卷 {0},上传失败!", _volume); return; } section = await reader.ReadNextSectionAsync(_context.RequestAborted); } else { // 选择正在使用数最低的卷 cache = new SortedSetCache(Cfg.VolumeKey); _volume = await cache.GetMin(); // 该卷使用数加1 await cache.Increment(_volume); } // Section顺序:固定路径(可无),文件,文件,缩略图,文件 // 缩略图由客户端生成,因提取视频帧耗资源,属于前面的文件 FileDesc lastFile = null; while (section != null) { var fileSection = section.AsFileSection(); if (fileSection != null) { if (fileSection.Name == "thumbnail") { if (lastFile != null) { // 将缩略图复制到同路径 // 命名规则:原文件名添加后缀"-t.jpg",如 143203944767549440.wmv-t.jpg string fullPath = Path.Combine(Cfg.Root, lastFile.Path + Cfg.ThumbPostfix); try { using (var writeStream = File.Create(fullPath)) { await fileSection.FileStream.CopyToAsync(writeStream, _bufferSize, _context.RequestAborted); } } catch { } lastFile = null; } } else { lastFile = await ReceiveFile(fileSection); sucFiles.Add(lastFile); } } section = await reader.ReadNextSectionAsync(_context.RequestAborted); } } catch (Exception ex) { Log.Error(ex, "处理上传文件异常"); _result.Clear(); if (sucFiles.Count > 0) { // 删除已上传成功的文件和记录 StringBuilder sb = new StringBuilder(); foreach (var desc in sucFiles) { FileInfo fi = new FileInfo(Path.Combine(Cfg.Root, desc.Path)); if (fi.Exists) { try { fi.Delete(); } catch { } } sb.Append(','); sb.Append(desc.ID); } try { await _db.Exec($"delete from fsm_file where id in ({sb.ToString().TrimStart(',')})"); } catch { } } } finally { await _db.Close(true); // 卷使用数减1 if (cache != null) { await cache.Decrement(_volume); } } await Response(); }