前言

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

图片接口

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

图片列表

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

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

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

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

StarBlog.Web/Services/PhotoService.cs

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

单个图片

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

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

图片缩略图

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

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

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

控制器代码

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

service代码

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

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

  1. <!-- 复制 Magick 库 -->
  2. <PropertyGroup>
  3. <MagickCopyNativeWindows>true</MagickCopyNativeWindows>
  4. <MagickCopyNativeLinux>true</MagickCopyNativeLinux>
  5. <MagickCopyNativeMacOS>true</MagickCopyNativeMacOS>
  6. </PropertyGroup>

其他接口

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

图片文件上传

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

先定义DTO

  1. public class PhotoCreationDto {
  2. /// <summary>
  3. /// 作品标题
  4. /// </summary>
  5. [Required(ErrorMessage = "作品标题不能为空")]
  6. public string Title { get; set; }
  7. /// <summary>
  8. /// 拍摄地点
  9. /// </summary>
  10. [Required(ErrorMessage = "拍摄地点不能为空")]
  11. public string Location { get; set; }
  12. }

控制器代码

  1. [Authorize]
  2. [HttpPost]
  3. public ApiResponse<Photo> Add([FromForm] PhotoCreationDto dto, IFormFile file) {
  4. var photo = _photoService.Add(dto, file);
  5. return !ModelState.IsValid
  6. ? ApiResponse.BadRequest(ModelState)
  7. : new ApiResponse<Photo>(photo);
  8. }

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

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

下面是service代码

  1. public Photo Add(PhotoCreationDto dto, IFormFile photoFile) {
  2. var photoId = GuidUtils.GuidTo16String();
  3. var photo = new Photo {
  4. Id = photoId,
  5. Title = dto.Title,
  6. CreateTime = DateTime.Now,
  7. Location = dto.Location,
  8. FilePath = Path.Combine("photography", $"{photoId}.jpg")
  9. };
  10. var savePath = Path.Combine(_environment.WebRootPath, "media", photo.FilePath);
  11. // 如果超出最大允许的大小,则按比例缩小
  12. const int maxWidth = 2000;
  13. const int maxHeight = 2000;
  14. using (var image = Image.Load(photoFile.OpenReadStream())) {
  15. if (image.Width > maxWidth)
  16. image.Mutate(a => a.Resize(maxWidth, 0));
  17. if (image.Height > maxHeight)
  18. image.Mutate(a => a.Resize(0, maxHeight));
  19. image.Save(savePath);
  20. }
  21. // 保存文件
  22. using (var fs = new FileStream(savePath, FileMode.Create)) {
  23. photoFile.CopyTo(fs);
  24. }
  25. // 读取图片的尺寸等数据
  26. photo = BuildPhotoData(photo);
  27. return _photoRepo.Insert(photo);
  28. }

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

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

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

系列文章

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

  1. 基于.NetCore开发博客项目 StarBlog - (10) 图片瀑布流

    系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...

  2. 基于.NetCore开发博客项目 StarBlog - (9) 图片批量导入

    系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...

  3. 基于.NetCore开发博客项目 StarBlog - (11) 实现访问统计

    系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...

  4. 基于.NetCore开发博客项目 StarBlog - (12) Razor页面动态编译

    系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...

  5. 基于.NetCore开发博客项目 StarBlog - (13) 加入友情链接功能

    系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...

  6. 基于.NetCore开发博客项目 StarBlog - (14) 实现主题切换功能

    系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...

  7. 基于.NetCore开发博客项目 StarBlog - (15) 生成随机尺寸图片

    系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...

  8. 基于.NetCore开发博客项目 StarBlog - (16) 一些新功能 (监控/统计/配置/初始化)

    系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...

  9. 基于.NetCore开发博客项目 StarBlog - (17) 自动下载文章里的外部图片

    系列文章 基于.NetCore开发博客项目 StarBlog - (1) 为什么需要自己写一个博客? 基于.NetCore开发博客项目 StarBlog - (2) 环境准备和创建项目 基于.NetC ...

  10. 基于.NetCore开发博客项目 StarBlog - (18) 实现本地Typora文章打包上传

    前言 九月太忙,只更新了三篇文章,本来这个功能是从九月初就开始做的,结果一直拖到现在国庆假期才有时间完善并且写文章~ 之前我更新了几篇关于 Python 的文章,有朋友留言问是不是不更新 .Net 了 ...

随机推荐

  1. 洛谷P1253 [yLOI2018] 扶苏的问题 (线段树)

    一道用来练习打标记的好题. 对于区间加和区间赋值两个操作分别用两个标记,分析如何打标记并下传标记(还是比较好分析的). 坑点:查询操作时,我一开始把ans设为-0x3f3f3f3f(调试了好久才发现) ...

  2. 计算机保研,maybe this is all you need(普通双非学子上岸浙大工程师数据科学项目)

    写在前面 9.28接收了拟录取通知,也终究是尘埃落定了,我人生的又一个阶段也终于结束.面对最终录取结果,或多或少会有所遗憾,但也还是基本达到了预期的目标了. 作为在今年严峻的保研形势下幸存的我,一直想 ...

  3. 创建线程的方式三:实现Callable接口。 --- JDK 5.0新增

    如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大? call()可以有返回值的.call()可以抛出异常,被外面的操作捕获,获取异常的信息Callable是 ...

  4. Paxos分布式系统共识算法?我愿称其为点歌算法…

    原创:微信公众号 码农参上,欢迎分享,转载请保留出处. 哈喽大家好啊,我是Hydra. 分布式系统共识算法Paxos相信大家都不陌生,它被称为最难理解的算法不是没有道理的,首先,它的发表之路就充满了坎 ...

  5. 19.-哈希算法&注册登录

    一.哈希算法 哈希: 给定明文-计算出一段定长的-不可逆的值 定长输出:不管明文输入多少,哈希都是定长的 不可逆:无法反向计算出对应的明文 雪崩效应:输入改变,输出必然变 md5:32位16进制   ...

  6. [苹果APP上架]ios App Store上架详细教程-一条龙顺滑上架-适合小白

    如何在 2022 年将您的应用提交到 App Store 您正在启动您的第一个应用程序,或者距离上次已经有一段时间了.作者纸飞机@cheng716051来给你讲讲将应用程序提交到 App Store ...

  7. KatalonRecorder系列(一):基本使用+XPath元素定位

    一.简介 Katalon Recorder是基于selenium的浏览器插件,支持火狐和chrome.可以录制web上的操作并回放,还能导入导出脚本. 二.安装 可在谷歌商店或者火狐附件组件中搜索并选 ...

  8. 使用gitee创建个人的图床

    使用gitee创建个人的图床 1.如果还没有gitee(码云)账号,可以注册一个,注册后登陆进入个人中心 2.点击新建仓库 3.进入创建页面 创建成功 5.在本地电脑创建一个文件夹,专门用来放置要上传 ...

  9. Unity之"诡异"的协程

    为什么说是诡异的协程呢?首先从一个案例说起吧,示例如下: 游戏目标:让小车进入到对应颜色屋子里,即可获得一分.(转弯的道路可控)   为了让小车能够平滑转弯,小车的前进方向需要和车子的位置与圆心组成的 ...

  10. 从 洛谷P5309 Ynoi2011 初始化 看卡常

    一般情况下,程序运行消耗时间主要与时间复杂度有关,超时与否取决于算法是否正确. 但对于某些题目,时间复杂度正确的程序也无法通过,这时我们就需要卡常数,即通过优化一些操作的常数因子减少时间消耗. 比如这 ...