From e4a283937be0c7d3d9cff70d7fbceea98cb8cae4 Mon Sep 17 00:00:00 2001 From: Tsanie Lily Date: Fri, 26 May 2023 11:14:58 +0800 Subject: [PATCH] resolve photo issue. --- .gitignore | 2 +- Server/Controller/BaseController.cs | 5 +- Server/Controller/BaseController.sqlite.cs | 32 +++++- Server/Controller/EventApiController.cs | 116 +++++++++++++++++++-- 4 files changed, 142 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index 62b24f7..72d2a79 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ ## ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore -flower.db +flower.db* TestCase/ # User-specific files diff --git a/Server/Controller/BaseController.cs b/Server/Controller/BaseController.cs index bcf696d..7c1e268 100644 --- a/Server/Controller/BaseController.cs +++ b/Server/Controller/BaseController.cs @@ -239,7 +239,8 @@ public abstract partial class BaseController : ControllerBase /// 用户唯一 id /// 花草唯一 id /// 文件对象 - protected async Task WriteToFile(int uid, int fid, FileResult file) + /// 取消令牌 + protected async Task WriteToFile(int uid, int fid, FileResult file, CancellationToken token = default) { var directory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "uploads", uid.ToString(), fid.ToString()); if (!Directory.Exists(directory)) @@ -247,7 +248,7 @@ public abstract partial class BaseController : ControllerBase Directory.CreateDirectory(directory); } var path = Path.Combine(directory, file.Path); - await System.IO.File.WriteAllBytesAsync(path, file.Content); + await System.IO.File.WriteAllBytesAsync(path, file.Content, token); } } diff --git a/Server/Controller/BaseController.sqlite.cs b/Server/Controller/BaseController.sqlite.cs index 1c6e257..abdb3b5 100644 --- a/Server/Controller/BaseController.sqlite.cs +++ b/Server/Controller/BaseController.sqlite.cs @@ -1,11 +1,31 @@ using Blahblah.FlowerStory.Server.Data.Model; using Microsoft.EntityFrameworkCore; -using System.Security.Cryptography; namespace Blahblah.FlowerStory.Server.Controller; partial class BaseController { + /// + /// 执行事务 + /// + /// 执行代理 + /// 取消令牌 + /// 执行代理为 null + protected async Task ExecuteTransaction(Func executor, CancellationToken token = default) + { + if (executor == null) throw new ArgumentNullException(nameof(executor)); + using var trans = await database.Database.BeginTransactionAsync(token); + try + { + await executor(token); + await trans.CommitAsync(token); + } + catch + { + await trans.RollbackAsync(token); + throw; + } + } /// /// 根据 uid 获取用户对象 @@ -50,4 +70,14 @@ partial class BaseController { return database.Database.ExecuteSql($"UPDATE \"users\" SET \"avatar\" = NULL WHERE \"uid\" = {uid}"); } + + /// + /// 添加照片项 + /// + /// 照片对象 + /// + protected int AddPhotoItem(PhotoItem item) + { + return database.Database.ExecuteSql($"INSERT INTO \"photos\"(\"fid\",\"rid\",\"filetype\",\"filename\",\"path\",\"dateupload\") VALUES({item.FlowerId},{item.RecordId},{item.FileType},{item.FileName},{item.Path},{item.DateUploadUnixTime})"); + } } diff --git a/Server/Controller/EventApiController.cs b/Server/Controller/EventApiController.cs index d4e546c..64a14bd 100644 --- a/Server/Controller/EventApiController.cs +++ b/Server/Controller/EventApiController.cs @@ -95,10 +95,7 @@ public class EventApiController : BaseController if (includePhoto == true) { - foreach (var r in records) - { - r.Photos = database.Photos.Where(p => p.RecordId == r.Id).ToList(); - } + records = records.Include(r => r.Photos); } return Ok(records.ToArray()); @@ -334,9 +331,9 @@ public class EventApiController : BaseController /// /// 事件唯一 id /// 图片 - /// 修改成功则返回 HTTP 204 - /// 修改成功 - /// 照片格式非法 + /// 添加成功则返回 HTTP 204 + /// 添加成功 + /// 图片格式非法 /// 未找到登录会话或已过期 /// 用户已禁用 /// 未找到关联用户或者关联的事件 @@ -351,7 +348,7 @@ public class EventApiController : BaseController [HttpPost] [Consumes("multipart/form-data")] [RequestSizeLimit(15 * 1024 * 1024)] - public async Task UploadCovers([Required][FromQuery] int id, [Required] IFormFile photo) + public async Task UploadPhoto([Required][FromQuery] int id, [Required] IFormFile photo) { var (result, user) = CheckPermission(); if (result != null) @@ -391,7 +388,7 @@ public class EventApiController : BaseController try { - await WriteToFile(user.Id, id, file); + await WriteToFile(user.Id, record.FlowerId, file); } catch (Exception ex) { @@ -405,6 +402,107 @@ public class EventApiController : BaseController return NoContent(); } + /// + /// 批量添加事件关联照片,总大小限制 75MB + /// + /// + /// 请求示例: + /// + /// POST /api/event/add_photos + /// Authorization: authorization id + /// + /// 参数: + /// + /// id: int + /// photos: IFormFile[] + /// + /// + /// 事件唯一 id + /// 图片集 + /// 添加成功则返回 HTTP 204 + /// 添加成功 + /// 图片参数非法或图片格式非法 + /// 未找到登录会话或已过期 + /// 用户已禁用 + /// 未找到关联用户或者关联的事件 + /// 提交正文过大 + [Route("add_photos", Name = "addEventPhotos")] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status413PayloadTooLarge)] + [HttpPost] + [Consumes("multipart/form-data")] + // 5 photos + [RequestSizeLimit(75 * 1024 * 1024)] + public async Task UploadPhotos([Required][FromQuery] int id, [Required] IFormFile[] photos) + { + var (result, user) = CheckPermission(); + if (result != null) + { + return result; + } + if (user == null) + { + return NotFound(); + } + + if (photos == null || photos.Length == 0) + { + SaveDatabase(); + return BadRequest(); + } + + var record = database.Records.SingleOrDefault(r => r.Id == id && r.OwnerId == user.Id); + if (record == null) + { + SaveDatabase(); + return NotFound(id); + } + + SaveDatabase(); + + try + { + await ExecuteTransaction(async token => + { + foreach (var photo in photos) + { + if (photo.Length > 0) + { + var file = WrapFormFile(photo) ?? throw new BadHttpRequestException(photo?.FileName ?? string.Empty); + + var p = new PhotoItem + { + FlowerId = record.FlowerId, + RecordId = id, + FileType = file.FileType, + FileName = file.Filename, + Path = file.Path, + DateUploadUnixTime = user.ActiveDateUnixTime ?? DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + }; + AddPhotoItem(p); + + await WriteToFile(user.Id, record.FlowerId, file, token); + } + } + }); + } + catch (BadHttpRequestException bex) + { + return BadRequest(bex.Message); + } + catch (Exception ex) + { + return Problem(ex.ToString(), "api/event/add_photos"); + // TODO: Logger + } + + return NoContent(); + } + /// /// 获取事件关联的照片列表 ///