在现代Web应用程序中,图片上传功能是常见的需求之一。无论是用户头像、产品图片还是文档附件,确保文件上传的安全性和效率至关重要。本文将详细介绍如何使用ASP.NET Core构建一个安全且高效的图片上传接口,并介绍如何利用SHA256哈希算法避免重复文件存储。
我们的目标是创建一个图片上传接口,支持以下特性:
首先,我们定义了一个ImageUploadController
类来处理图片上传请求。下面是完整的控制器代码及其详细注释。
using MES.Entity;
using MES.Entity.Dtos.SystemDto.Response.UploadImage;
using Microsoft.AspNetCore.Mvc;
using System.Security.Cryptography;
using System.IO;
namespace MES.API.Controllers.SystemControllers
{
///
/// 图片上传控制器
///
[Route("api/[controller]")]
[ApiController]
public class ImageUploadController : ControllerBase
{
///
/// 日志记录器
///
private readonly ILogger<ImageUploadController> _logger;
///
/// 允许上传的文件类型
///
private readonly string[] sourceArray = new[] { "image/jpeg", "image/png", "image/gif" };
///
/// 静态文件根目录
///
private readonly string StaticFileRoot = "wwwroot";
///
/// 构造函数注入ILogger
///
/// 日志记录器
public ImageUploadController(ILogger<ImageUploadController> logger)
{
this._logger = logger;
}
///
/// 上传图片方法
///
/// 图片文件
/// 上传结果
[HttpPost]
public async Task<IActionResult> UploadImageAsync(IFormFile file)
{
// 返回数据对象
ApiResult<UploadImageResponseDto> apiResult = new();
try
{
// 检查文件类型是否合法
if (!sourceArray.Contains(file.ContentType))
{
apiResult.Message = "图片格式不正确,请上传 jpg、png、gif 格式的图片!";
return Ok(apiResult);
}
// 检查文件大小是否超过限制 (2MB)
if (file.Length > 2 * 1024 * 1024)
{
apiResult.Message = "文件大小超过限制,请上传小于 2M 的图片!";
return Ok(apiResult);
}
if (file.Length > 0)
{
// 获取文件名
string fileName = Path.GetFileName(file.FileName);
// 构造文件路径,按年月日分层存储
string fileUrlWithoutFileName = $"InvoiceStaticFile/{DateTime.Now.Year}/{DateTime.Now.Month}/{DateTime.Now.Day}";
string directoryPath = Path.Combine(StaticFileRoot, fileUrlWithoutFileName);
// 创建文件夹,如果文件夹已存在,则什么也不做
Directory.CreateDirectory(directoryPath);
// 使用SHA256生成文件的唯一标识符
using SHA256 hash = SHA256.Create();
byte[] hashByte = await hash.ComputeHashAsync(file.OpenReadStream());
string hashedFileName = BitConverter.ToString(hashByte).Replace("-", "");
// 重新获得一个文件名
string newFileName = hashedFileName + "." + fileName.Split('.').Last();
string filePath = Path.Combine(directoryPath, newFileName);
// 将文件写入指定路径
await using FileStream fileStream = new(filePath, FileMode.Create);
await file.CopyToAsync(fileStream);
// 构造完整的URL以便前端使用
string fullUrl = $"{Request.Scheme}://{Request.Host}/{fileUrlWithoutFileName}/{newFileName}";
// 设置返回的数据
apiResult.Data = new UploadImageResponseDto()
{
FilePath = fileUrlWithoutFileName,
FileName = newFileName,
FullPathName = Path.Combine(fileUrlWithoutFileName, newFileName)
};
apiResult.Message = "上传成功!";
return Ok(apiResult);
}
apiResult.Message = "文件为空!请重新上传!";
}
catch (Exception ex)
{
// 记录错误日志
_logger.LogError("UploadImageAsync,上传图片失败,原因:{ErrorMessage}", ex.Message);
apiResult.Code = ResponseCode.Code999;
apiResult.Message = "一般性错误,请联系管理员!";
}
return Ok(apiResult);
}
}
}
我们首先检查上传文件的ContentType
是否在允许的范围内(JPEG、PNG、GIF)。如果不在,则返回相应的错误信息。
if (!sourceArray.Contains(file.ContentType))
{
apiResult.Message = "图片格式不正确,请上传 jpg、png、gif 格式的图片!";
return Ok(apiResult);
}
为了防止大文件占用过多服务器资源,我们限制了上传文件的最大大小(2MB)。
if (file.Length > 2 * 1024 * 1024) // 限制文件大小不超过 2M
{
apiResult.Message = "文件大小超过限制,请上传小于 2M 的图片!";
return Ok(apiResult);
}
为了避免重复存储相同的文件,我们使用SHA256哈希算法生成唯一的文件名。
using SHA256 hash = SHA256.Create();
byte[] hashByte = await hash.ComputeHashAsync(file.OpenReadStream());
string hashedFileName = BitConverter.ToString(hashByte).Replace("-", "");
string newFileName = hashedFileName + "." + fileName.Split('.').Last();
我们将文件保存到指定路径,并构造完整的URL以便前端使用。
string filePath = Path.Combine(directoryPath, newFileName);
await using FileStream fileStream = new(filePath, FileMode.Create);
await file.CopyToAsync(fileStream);
string fullUrl = $"{Request.Scheme}://{Request.Host}/{fileUrlWithoutFileName}/{newFileName}";
在发生异常时,我们使用ILogger
记录错误信息,并返回通用的错误消息给客户端。
catch (Exception ex)
{
_logger.LogError("UploadImageAsync,上传图片失败,原因:{ErrorMessage}", ex.Message);
apiResult.Code = ResponseCode.Code999;
apiResult.Message = "一般性错误,请联系管理员!";
}
通过上述步骤,我们实现了一个高效且安全的图片上传接口。该接口不仅能够验证文件类型和大小,还能够避免重复存储相同的文件,提升了系统的性能和用户体验。希望这篇文章对你有所帮助!
如果你有任何问题或建议,请在评论区留言,我会尽力解答。
希望这篇更新后的博客文章对你有帮助!你可以根据实际需求进一步调整和完善内容。如果你有更多具体的需求或者想要添加的内容,随时告诉我!