基于.NetCore开发博客项目 StarBlog - (25) 图片接口与文件上传

基于,netcore,开发,博客,项目,starblog,图片,接口,文件,上传 · 浏览次数 : 808

小编点评

**图片接口设计** **优点:** * 清晰易懂,代码易读。 * 支持分页查询、批量导入等复杂操作。 * 采用文件上传的方式,易于处理文件大小和类型。 **缺点:** * 由于代码要处理多种操作,可能导致代码冗余。 * 对于需要进行复杂的筛选或排序的场景,需要进行额外的处理。 * 文件上传过程可能存在速度限制,对于大量文件可能造成性能问题。 **建议:** * 可以考虑使用面向对象的编程模式,将接口模块化。 * 可以使用缓存机制,提高性能。 * 可以优化文件上传逻辑,减少磁盘 I/O 操作。 * 可以使用异步处理机制,处理大量文件请求。

正文

前言

上传文件的接口设计有两种风格,一种是整个项目只设置一个接口用来上传,然后其他需要用到文件的地方,都只存一个引用ID;另一种是每个需要文件的地方单独管理各自的文件。这俩各有优劣吧,本项目中选择的是后者的风格,文章图片和照片模块又要能CRUD又要批量导入,还是各自管理文件比较好。

图片接口

说会正题,先介绍一下图片相关接口。

图片列表

首先CRUD是肯定有的,图片列表的分页查看也是有的,不过因为筛选功能没有做,所以就不定义一个ViewModel作为参数了。

控制器代码 StarBlog.Web/Apis/Blog/PhotoController.cs

[HttpGet]
public ApiResponsePaged<Photo> GetList(int page = 1, int pageSize = 10) {
    var paged = _photoService.GetPagedList(page, pageSize);
    return new ApiResponsePaged<Photo> {
        Pagination = paged.ToPaginationMetadata(),
        Data = paged.ToList()
    };
}

跟博客前台公用一套图片列表逻辑,所以这部分抽出来放在service,代码如下

StarBlog.Web/Services/PhotoService.cs

public IPagedList<Photo> GetPagedList(int page = 1, int pageSize = 10) {
    return _photoRepo.Select.OrderByDescending(a => a.CreateTime)
        .ToList().ToPagedList(page, pageSize);
}

单个图片

获取单个图片,跟获取文章的差不多,传入ID,找不到就返回404,找到就返回图片对象

[HttpGet("{id}")]
public ApiResponse<Photo> Get(string id) {
    var photo = _photoService.GetById(id);
    return photo == null
        ? ApiResponse.NotFound($"图片 {id} 不存在")
        : new ApiResponse<Photo> {Data = photo};
}

图片缩略图

在本系列第20篇中,本项目已经实现了图片显示的优化,详见:基于.NetCore开发博客项目 StarBlog - (20) 图片显示优化

除了 ImageSharp 组件提供的图片缩略图功能外,我这里还写了另一个生成缩略图的方法,这个方法有俩特点

  • 直接在内存中生成返回,不会写入缓存文件
  • 生成的是Progressive JPEG格式,目前 ImageSharp 是不支持的,可以优化前端的加载速度

控制器代码

[HttpGet("{id}/Thumb")]
public async Task<IActionResult> GetThumb(string id, [FromQuery] int width = 300) {
    var data = await _photoService.GetThumb(id, width);
    return new FileContentResult(data, "image/jpeg");
}

service代码

/// <summary>
/// 生成Progressive JPEG缩略图 (使用 MagickImage)
/// </summary>
/// <param name="width">设置为0则不调整大小</param>
public async Task<byte[]> GetThumb(string id, int width = 0) {
    var photo = await _photoRepo.Where(a => a.Id == id).FirstAsync();
    using (var image = new MagickImage(GetPhotoFilePath(photo))) {
        image.Format = MagickFormat.Pjpeg;
        if (width != 0) {
            image.Resize(width, 0);
        }

        return image.ToByteArray();
    }
}

这个 MagickImage 是用 C++ 写的,在不同平台上引用不同 native 库,需要在 csproj 里面写上配置,这样发布的时候才会带上对应的依赖库,而且似乎在 CentOS 系统上会有坑…

<!--  复制 Magick 库  -->
<PropertyGroup>
    <MagickCopyNativeWindows>true</MagickCopyNativeWindows>
    <MagickCopyNativeLinux>true</MagickCopyNativeLinux>
    <MagickCopyNativeMacOS>true</MagickCopyNativeMacOS>
</PropertyGroup>

其他接口

还有一些接口,跟之前介绍的大同小异,再重复一次也意义不大,读者有需要的话可以自行查看源码。

图片文件上传

这个同时也是图片的添加接口

先定义DTO

public class PhotoCreationDto {
    /// <summary>
    /// 作品标题
    /// </summary>
    [Required(ErrorMessage = "作品标题不能为空")]
    public string Title { get; set; }

    /// <summary>
    /// 拍摄地点
    /// </summary>
    [Required(ErrorMessage = "拍摄地点不能为空")]
    public string Location { get; set; }
}

控制器代码

[Authorize]
[HttpPost]
public ApiResponse<Photo> Add([FromForm] PhotoCreationDto dto, IFormFile file) {
    var photo = _photoService.Add(dto, file);

    return !ModelState.IsValid
        ? ApiResponse.BadRequest(ModelState)
        : new ApiResponse<Photo>(photo);
}

因为上传的同时还要附带一些数据,需要使用 FormData 传参,所以这里使用 [FromForm] 特性标记这个 dto 参数

IFormFile 类型的参数可以拿到上传上来的文件

下面是service代码

public Photo Add(PhotoCreationDto dto, IFormFile photoFile) {
    var photoId = GuidUtils.GuidTo16String();
    var photo = new Photo {
        Id = photoId,
        Title = dto.Title,
        CreateTime = DateTime.Now,
        Location = dto.Location,
        FilePath = Path.Combine("photography", $"{photoId}.jpg")
    };

    var savePath = Path.Combine(_environment.WebRootPath, "media", photo.FilePath);
	
    // 如果超出最大允许的大小,则按比例缩小
    const int maxWidth = 2000;
    const int maxHeight = 2000;
    using (var image = Image.Load(photoFile.OpenReadStream())) {
        if (image.Width > maxWidth)
            image.Mutate(a => a.Resize(maxWidth, 0));
        if (image.Height > maxHeight)
            image.Mutate(a => a.Resize(0, maxHeight));
        image.Save(savePath);
    }

    // 保存文件
    using (var fs = new FileStream(savePath, FileMode.Create)) {
        photoFile.CopyTo(fs);
    }

    // 读取图片的尺寸等数据
    photo = BuildPhotoData(photo);

    return _photoRepo.Insert(photo);
}

这里对图片做了一些处理,抛开这些细节,其实对上传的文件,最关键的只有几行保存代码

using (var fs = new FileStream("savePath", FileMode.Create)) {
    photoFile.CopyTo(fs);
}

这样就完成了文件上传接口。

系列文章

与基于.NetCore开发博客项目 StarBlog - (25) 图片接口与文件上传相似的内容:

基于.NetCore开发博客项目 StarBlog - (25) 图片接口与文件上传

## 前言 上传文件的接口设计有两种风格,一种是整个项目只设置一个接口用来上传,然后其他需要用到文件的地方,都只存一个引用ID;另一种是每个需要文件的地方单独管理各自的文件。这俩各有优劣吧,本项目中选择的是后者的风格,文章图片和照片模块又要能CRUD又要批量导入,还是各自管理文件比较好。 ## 图片

基于.NetCore开发博客项目 StarBlog - (19) Markdown渲染方案探索

## 前言 笔者认为,一个博客网站,最核心的是阅读体验。 在开发StarBlog的过程中,最耗时的恰恰也是文章的展示部分功能。 最开始还没研究出来如何很好的使用后端渲染,所以只能先用Editor.md组件做前端渲染,过渡一下。前端渲染我是不满意的,因为性能较差,页面加载出来还会闪一下,有割裂感,影响

基于.NetCore开发博客项目 StarBlog - (20) 图片显示优化

## 前言 我的服务器带宽比较高,博客部署在上面访问的时候几乎没感觉有加载延迟,就没做图片这块的优化,不过最近有小伙伴说博客的图片加载比较慢,那就来把图片优化完善一下吧~ 目前有两个地方需要完善 - 图片瀑布流 - 图片缩略图 ## 图片瀑布流 关于瀑布流之前的文章有介绍: [基于.NetCore开

基于.NetCore开发博客项目 StarBlog - (21) 开始开发RESTFul接口

## 前言 最近电脑坏了,开源项目的进度也受到一些影响 这篇酝酿很久了,作为本系列第二部分(API接口开发)的第一篇,得想一个好的开头,想着想着就鸽了好久,索性不扯那么多了,直接开写吧~ ## 关于RESTFul 网上很多相关的文章都要把RESTFul历史来龙去脉给复制一遍,所以我这就不重复了,现在

基于.NetCore开发博客项目 StarBlog - (22) 开发博客文章相关接口

## 前言 本文介绍博客文章相关接口的开发,作为接口开发介绍的第一篇,会写得比较详细,以抛砖引玉,后面的其他接口就粗略带过了,着重于WebApi开发的周边设施。 涉及到的接口:文章CRUD、置顶文章、推荐文章等。 开始前先介绍下AspNetCore框架的基础概念,MVC模式(前后端不分离)、WebA

基于.NetCore开发博客项目 StarBlog - (23) 文章列表接口分页、过滤、搜索、排序

## 前言 上一篇留的坑,火速补上。 在之前的第6篇中,已经有初步介绍,本文做一些补充,已经搞定这部分的同学可以快速跳过,[基于.NetCore开发博客项目 StarBlog - (6) 页面开发之博客文章列表](https://www.cnblogs.com/deali/p/16286780.ht

基于.NetCore开发博客项目 StarBlog - (24) 统一接口数据返回格式

## 前言 开发接口,是给客户端(Web前端、App)用的,前面说的RESTFul,是接口的规范,有了统一的接口风格,客户端开发人员在访问后端功能的时候能更快找到需要的接口,能写出可维护性更高的代码。 而接口的数据返回格式也是接口规范的重要一环,不然一个接口返回JSON,一个返回纯字符串,客户端对接

基于.NetCore开发博客项目 StarBlog - (26) 集成Swagger接口文档

## 前言 这是StarBlog系列在2023年的第一篇更新😃~ 在之前的文章里,我们已经完成了部分接口的开发,接下来需要使用 curl、Postman 这类工具对这些接口进行测试,但接口一多,每次测试都要一个个填入地址和对应参数会比较麻烦… 我们需要一种直观的方式来汇总项目里的所有接口,并且如果

基于.NetCore开发博客项目 StarBlog - (27) 使用JWT保护接口

## 前言 这是StarBlog系列在2023年的第二篇更新😂 这几个月都在忙,更新变得很不勤快,但是拖着不更新我的心里更慌,很久没写,要开头就变得很难😑 说回正题,之前的文章里,我们已经把博客关键的接口都开发完成了,但还少了一个最关键的「认证授权」,少了这东西,网站就跟筛子一样,谁都可以来添加

基于.NetCore开发博客项目 StarBlog - (28) 开发友情链接相关接口

## 前言 之前介绍的友情链接功能,只实现了友情链接的展示和管理接口。 还缺失友情链接申请、审核管理、通知,现在把这块功能补全。 Model 什么的之前那篇文章都有,本文直接补全逻辑代码~ 详见: [基于.NetCore开发博客项目 StarBlog - (13) 加入友情链接功能](https:/