as is
This commit is contained in:
64
StopShopping.FileApi/Services/ApiResponse.cs
Normal file
64
StopShopping.FileApi/Services/ApiResponse.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
namespace StopShopping.FileApi.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 强类型返回值
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
public class ApiResponse<T>
|
||||
{
|
||||
public ApiResponse()
|
||||
{ }
|
||||
|
||||
public ApiResponse(T data)
|
||||
{
|
||||
Data = data;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否成功
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public bool IsSucced { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 错误消息
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public string? Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关联数据
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public T? Data { get; set; }
|
||||
|
||||
public ApiResponse<T> Failed(string message)
|
||||
{
|
||||
IsSucced = false;
|
||||
Message = message;
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 强类型返回值,只返回成功与否和消息
|
||||
/// </summary>
|
||||
public class ApiResponse : ApiResponse<object?>
|
||||
{
|
||||
public ApiResponse(bool isSucced = true, string message = "")
|
||||
{
|
||||
IsSucced = isSucced;
|
||||
Message = message;
|
||||
}
|
||||
|
||||
public static ApiResponse Succed(string message = "")
|
||||
{
|
||||
return new ApiResponse(message: message);
|
||||
}
|
||||
|
||||
public static new ApiResponse Failed(string message)
|
||||
{
|
||||
return new ApiResponse(false, message);
|
||||
}
|
||||
}
|
||||
6
StopShopping.FileApi/Services/AppOptions.cs
Normal file
6
StopShopping.FileApi/Services/AppOptions.cs
Normal file
@@ -0,0 +1,6 @@
|
||||
namespace StopShopping.FileApi.Services;
|
||||
|
||||
public class AppOptions
|
||||
{
|
||||
public string Domain { get; set; } = string.Empty;
|
||||
}
|
||||
34
StopShopping.FileApi/Services/Extensions.cs
Normal file
34
StopShopping.FileApi/Services/Extensions.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
|
||||
using FileSignatures;
|
||||
using FileSignatures.Formats;
|
||||
using StopShopping.FileApi.Services.Implementions;
|
||||
|
||||
namespace StopShopping.FileApi.Services;
|
||||
|
||||
public static class Extensions
|
||||
{
|
||||
public static IServiceCollection AddServices(this IServiceCollection services, IConfiguration appOptions)
|
||||
{
|
||||
services.Configure<AppOptions>(appOptions);
|
||||
services.AddValidation();
|
||||
|
||||
var imageFormats = FileFormatLocator.GetFormats().OfType<Image>();
|
||||
var imageInspector = new FileFormatInspector(imageFormats);
|
||||
services.AddSingleton<IFileFormatInspector>(imageInspector);
|
||||
|
||||
services.AddScoped<IFileService, FileService>();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
public static string GetTargetDirectory(this UploadScences uploadScences)
|
||||
{
|
||||
return uploadScences switch
|
||||
{
|
||||
UploadScences.Avatar => "avatar",
|
||||
UploadScences.Product => "product",
|
||||
UploadScences.Category => "category",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(uploadScences))
|
||||
};
|
||||
}
|
||||
}
|
||||
18
StopShopping.FileApi/Services/FileUploadResp.cs
Normal file
18
StopShopping.FileApi/Services/FileUploadResp.cs
Normal file
@@ -0,0 +1,18 @@
|
||||
namespace StopShopping.FileApi.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 文件上传结果
|
||||
/// </summary>
|
||||
public class FileUploadResp
|
||||
{
|
||||
/// <summary>
|
||||
/// 新名,上传后重命名
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public string NewName { get; set; } = string.Empty;
|
||||
/// <summary>
|
||||
/// Url
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public string Url { get; set; } = string.Empty;
|
||||
}
|
||||
14
StopShopping.FileApi/Services/IFileService.cs
Normal file
14
StopShopping.FileApi/Services/IFileService.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace StopShopping.FileApi.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 文件服务
|
||||
/// </summary>
|
||||
public interface IFileService
|
||||
{
|
||||
/// <summary>
|
||||
/// 上传文件
|
||||
/// </summary>
|
||||
/// <param name="payload"></param>
|
||||
/// <returns></returns>
|
||||
Task<ApiResponse<FileUploadResp>> UploadFileAsync(UploadParams payload);
|
||||
}
|
||||
59
StopShopping.FileApi/Services/Implementions/FileService.cs
Normal file
59
StopShopping.FileApi/Services/Implementions/FileService.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace StopShopping.FileApi.Services.Implementions;
|
||||
|
||||
public class FileService : IFileService
|
||||
{
|
||||
public FileService(IOptions<AppOptions> appOptions,
|
||||
IWebHostEnvironment webHostEnvironment)
|
||||
{
|
||||
_appOptions = appOptions.Value;
|
||||
_env = webHostEnvironment;
|
||||
}
|
||||
|
||||
private readonly IWebHostEnvironment _env;
|
||||
private readonly AppOptions _appOptions;
|
||||
|
||||
public async Task<ApiResponse<FileUploadResp>> UploadFileAsync(UploadParams payload)
|
||||
{
|
||||
var newName = Guid.NewGuid().ToString("N").ToLower();
|
||||
var extension = Path.GetExtension(payload.File!.FileName);
|
||||
var newFullName = $"{newName}{extension}";
|
||||
var relativeToRootPath = GetRelativeToRootPath(payload.Scences, newFullName);
|
||||
var targetPath = Path.Combine(_env.WebRootPath, GetRelativeToRootPath(payload.Scences));
|
||||
|
||||
if (!Directory.Exists(targetPath))
|
||||
{
|
||||
Directory.CreateDirectory(targetPath);
|
||||
}
|
||||
|
||||
using var file = new FileStream(
|
||||
Path.Combine(_env.WebRootPath, relativeToRootPath),
|
||||
FileMode.CreateNew,
|
||||
FileAccess.Write);
|
||||
|
||||
await payload.File.CopyToAsync(file);
|
||||
|
||||
FileUploadResp result = new()
|
||||
{
|
||||
NewName = newFullName,
|
||||
Url = GetFileUrl(payload.Scences, newFullName),
|
||||
};
|
||||
|
||||
return new ApiResponse<FileUploadResp>(result);
|
||||
}
|
||||
|
||||
private string GetFileUrl(UploadScences scences, string fileName)
|
||||
{
|
||||
return $"{_appOptions.Domain}/{GetRelativeToRootPath(scences, fileName)
|
||||
.Replace(Path.DirectorySeparatorChar, '/')}";
|
||||
}
|
||||
|
||||
private string GetRelativeToRootPath(UploadScences scences, string fileName = "")
|
||||
{
|
||||
return Path.Combine(
|
||||
"images",
|
||||
scences.GetTargetDirectory(),
|
||||
fileName);
|
||||
}
|
||||
}
|
||||
22
StopShopping.FileApi/Services/NameUrlParams.cs
Normal file
22
StopShopping.FileApi/Services/NameUrlParams.cs
Normal file
@@ -0,0 +1,22 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace StopShopping.FileApi.Services;
|
||||
|
||||
/// <summary>
|
||||
/// url查询参数
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public record NameUrlParams
|
||||
{
|
||||
/// <summary>
|
||||
/// 场景
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public UploadScences Scences { get; set; }
|
||||
/// <summary>
|
||||
/// 文件名
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
[Required]
|
||||
public string[] Names { get; set; } = [];
|
||||
}
|
||||
14
StopShopping.FileApi/Services/NameUrlResp.cs
Normal file
14
StopShopping.FileApi/Services/NameUrlResp.cs
Normal file
@@ -0,0 +1,14 @@
|
||||
namespace StopShopping.FileApi.Services;
|
||||
|
||||
/// <summary>
|
||||
/// url查询响应
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public class NameUrlResp
|
||||
{
|
||||
/// <summary>
|
||||
/// 文件名:文件链接
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public KeyValuePair<string, string>[] NameUrls { get; set; } = [];
|
||||
}
|
||||
23
StopShopping.FileApi/Services/UploadParams.cs
Normal file
23
StopShopping.FileApi/Services/UploadParams.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace StopShopping.FileApi.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 上传
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public record UploadParams
|
||||
{
|
||||
/// <summary>
|
||||
/// 场景
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
public UploadScences Scences { get; set; }
|
||||
/// <summary>
|
||||
/// 文件
|
||||
/// </summary>
|
||||
/// <value></value>
|
||||
[Required]
|
||||
[ImageFileValidation(2 * 1024 * 1024)]
|
||||
public IFormFile? File { get; set; }
|
||||
}
|
||||
20
StopShopping.FileApi/Services/UploadScences.cs
Normal file
20
StopShopping.FileApi/Services/UploadScences.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace StopShopping.FileApi.Services;
|
||||
|
||||
/// <summary>
|
||||
/// 文件上传场景
|
||||
/// </summary>
|
||||
public enum UploadScences
|
||||
{
|
||||
/// <summary>
|
||||
/// 头像
|
||||
/// </summary>
|
||||
Avatar,
|
||||
/// <summary>
|
||||
/// 商品
|
||||
/// </summary>
|
||||
Product,
|
||||
/// <summary>
|
||||
/// 商品分类
|
||||
/// </summary>
|
||||
Category,
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
using FileSignatures;
|
||||
|
||||
namespace System.ComponentModel.DataAnnotations;
|
||||
|
||||
public class ImageFileValidationAttribute : ValidationAttribute
|
||||
{
|
||||
public ImageFileValidationAttribute(long length)
|
||||
{
|
||||
Length = length;
|
||||
}
|
||||
|
||||
public long Length { get; set; }
|
||||
|
||||
protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
|
||||
{
|
||||
var fileInspector = validationContext.GetRequiredService<IFileFormatInspector>();
|
||||
if (null == fileInspector)
|
||||
{
|
||||
return new ValidationResult("未配置文件验证器");
|
||||
}
|
||||
if (value is IFormFile file)
|
||||
{
|
||||
var result = Validate(fileInspector, file);
|
||||
if (null != result)
|
||||
return result;
|
||||
}
|
||||
else if (value is IFormFileCollection files)
|
||||
{
|
||||
foreach (var f in files)
|
||||
{
|
||||
var result = Validate(fileInspector, f);
|
||||
if (null != result)
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return ValidationResult.Success;
|
||||
}
|
||||
|
||||
private ValidationResult? Validate(IFileFormatInspector fileInspector, IFormFile file)
|
||||
{
|
||||
if (file.Length > Length)
|
||||
return new ValidationResult("文件太大");
|
||||
var format = fileInspector.DetermineFileFormat(file.OpenReadStream());
|
||||
if (null == format)
|
||||
return new ValidationResult("文件格式不支持");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user