介绍

本章节先来把上一章漏掉的上传文件处理下,然后实现Tag功能。

上传文件

上传文件其实不含在任何一个聚合中,它属于一个独立的辅助性功能,先把抽象接口定义一下,在Bcvp.Blog.Core.Application.Contracts层Blog内新建File文件夹。

一个是根据文件name获取文件,一个是创建文件,另外BlogWebConsts是对上传文件的约束。

    public interface IFileAppService : IApplicationService
{
Task<RawFileDto> GetAsync(string name); Task<FileUploadOutputDto> CreateAsync(FileUploadInputDto input);
} public class RawFileDto
{
public byte[] Bytes { get; set; } public bool IsFileEmpty => Bytes == null || Bytes.Length == 0; public RawFileDto()
{ } public static RawFileDto EmptyResult()
{
return new RawFileDto() { Bytes = new byte[0] };
}
} public class FileUploadInputDto
{
[Required]
public byte[] Bytes { get; set; } [Required]
public string Name { get; set; }
} public class FileUploadOutputDto
{
public string Name { get; set; } public string WebUrl { get; set; }
} public class BlogWebConsts
{
public class FileUploading
{
/// <summary>
/// Default value: 5242880
/// </summary>
public static int MaxFileSize { get; set; } = 5242880; //5MB public static int MaxFileSizeAsMegabytes => Convert.ToInt32((MaxFileSize / 1024f) / 1024f);
}
}

Bcvp.Blog.Core.Application层实现抽象接口

    public class FileAppService : CoreAppService, IFileAppService
{
// Nuget: Volo.Abp.BlobStoring https://docs.abp.io/en/abp/latest/Blob-Storing
protected IBlobContainer<BloggingFileContainer> BlobContainer { get; } public FileAppService(
IBlobContainer<BloggingFileContainer> blobContainer)
{
BlobContainer = blobContainer;
} public virtual async Task<RawFileDto> GetAsync(string name)
{
Check.NotNullOrWhiteSpace(name, nameof(name)); return new RawFileDto
{
Bytes = await BlobContainer.GetAllBytesAsync(name)
};
} public virtual async Task<FileUploadOutputDto> CreateAsync(FileUploadInputDto input)
{
if (input.Bytes.IsNullOrEmpty())
{
ThrowValidationException("上传文件为空!", "Bytes");
} if (input.Bytes.Length > BlogWebConsts.FileUploading.MaxFileSize)
{
throw new UserFriendlyException($"文件大小超出上限 ({BlogWebConsts.FileUploading.MaxFileSizeAsMegabytes} MB)!");
} if (!ImageFormatHelper.IsValidImage(input.Bytes, FileUploadConsts.AllowedImageUploadFormats))
{
throw new UserFriendlyException("无效的图片格式!");
} var uniqueFileName = GenerateUniqueFileName(Path.GetExtension(input.Name)); await BlobContainer.SaveAsync(uniqueFileName, input.Bytes); return new FileUploadOutputDto
{
Name = uniqueFileName,
WebUrl = "/api/blog/files/www/" + uniqueFileName
};
} private static void ThrowValidationException(string message, string memberName)
{
throw new AbpValidationException(message,
new List<ValidationResult>
{
new ValidationResult(message, new[] {memberName})
});
} protected virtual string GenerateUniqueFileName(string extension, string prefix = null, string postfix = null)
{
return prefix + GuidGenerator.Create().ToString("N") + postfix + extension;
}
} public class FileUploadConsts
{
public static readonly ICollection<ImageFormat> AllowedImageUploadFormats = new Collection<ImageFormat>
{
ImageFormat.Jpeg,
ImageFormat.Png,
ImageFormat.Gif,
ImageFormat.Bmp
}; public static string AllowedImageFormatsJoint => string.Join(",", AllowedImageUploadFormats.Select(x => x.ToString()));
} public class ImageFormatHelper
{
public static ImageFormat GetImageRawFormat(byte[] fileBytes)
{
using (var memoryStream = new MemoryStream(fileBytes))
{
return System.Drawing.Image.FromStream(memoryStream).RawFormat;
}
} public static bool IsValidImage(byte[] fileBytes, ICollection<ImageFormat> validFormats)
{
var imageFormat = GetImageRawFormat(fileBytes);
return validFormats.Contains(imageFormat);
}
}

结构目录如下

思考

这个接口的创建文件和返回都是用的byte这个适用于服务间调用,但是如果我们是前端调用根本没法用,我们传统开发的上传文件都是通过IFormFile来做的这里咋办?

ABP为我们提供Bcvp.Blog.Core.HttpApi远程服务层,用于定义 HTTP APIs,在Controllers文件夹下创建BlogFilesController控制器,简单点理解就是文件创建还是由上面的FileAppService来完成,我们通过BlogFilesController扩展了远程服务传输文件的方式。

    [RemoteService(Name = "blog")] // 远程服务的组名
[Area("blog")]// Mvc里的区域
[Route("api/blog/files")] //Api路由
public class BlogFilesController : AbpController, IFileAppService
{
private readonly IFileAppService _fileAppService; public BlogFilesController(IFileAppService fileAppService)
{
_fileAppService = fileAppService;
} [HttpGet]
[Route("{name}")]
public Task<RawFileDto> GetAsync(string name)
{
return _fileAppService.GetAsync(name);
} [HttpGet]
[Route("www/{name}")]
public async Task<FileResult> GetForWebAsync(string name)
{
var file = await _fileAppService.GetAsync(name);
return File(
file.Bytes,
MimeTypes.GetByExtension(Path.GetExtension(name))
);
} [HttpPost]
public Task<FileUploadOutputDto> CreateAsync(FileUploadInputDto input)
{
return _fileAppService.CreateAsync(input);
} [HttpPost]
[Route("images/upload")]
public async Task<JsonResult> UploadImage(IFormFile file)
{
//TODO: localize exception messages if (file == null)
{
throw new UserFriendlyException("没找到文件");
} if (file.Length <= 0)
{
throw new UserFriendlyException("上传文件为空");
} if (!file.ContentType.Contains("image"))
{
throw new UserFriendlyException("文件不是图片类型");
} var output = await _fileAppService.CreateAsync(
new FileUploadInputDto
{
Bytes = file.GetAllBytes(),
Name = file.FileName
}
); return Json(new FileUploadResult(output.WebUrl));
} } public class FileUploadResult
{
public string FileUrl { get; set; } public FileUploadResult(string fileUrl)
{
FileUrl = fileUrl;
}
}

标签聚合

标签应该是最简单的了,它就一个功能,获取当前博客下的标签列表,在之前我们写文章聚合的时候已经把标签的仓储接口和实现都完成了,这里补一下业务接口。

    public interface ITagAppService : IApplicationService
{
Task<List<TagDto>> GetPopularTags(Guid blogId, GetPopularTagsInput input); } public class GetPopularTagsInput
{
public int ResultCount { get; set; } = 10; public int? MinimumPostCount { get; set; }
} public class TagAppService : CoreAppService, ITagAppService
{
private readonly ITagRepository _tagRepository; public TagAppService(ITagRepository tagRepository)
{
_tagRepository = tagRepository;
} public async Task<List<TagDto>> GetPopularTags(Guid blogId, GetPopularTagsInput input)
{
var postTags = (await _tagRepository.GetListAsync(blogId)).OrderByDescending(t=>t.UsageCount)
.WhereIf(input.MinimumPostCount != null, t=>t.UsageCount >= input.MinimumPostCount)
.Take(input.ResultCount).ToList(); return new List<TagDto>(
ObjectMapper.Map<List<Tag>, List<TagDto>>(postTags));
}
}

查缺补漏

前面写了这么多结果忘了配置实体映射了,我们在AutoMapper的时候是需要配置Dto和实体的映射才是使用的,在Bcvp.Blog.Core.Application层有一个CoreApplicationAutoMapperProfile.cs,把漏掉的映射配置补一下。

        public CoreApplicationAutoMapperProfile()
{
/* You can configure your AutoMapper mapping configuration here.
* Alternatively, you can split your mapping configurations
* into multiple profile classes for a better organization. */ CreateMap<BlogCore.Blogs.Blog, BlogDto>();
CreateMap<IdentityUser, BlogUserDto>(); CreateMap<Post, PostCacheItem>().Ignore(x => x.CommentCount).Ignore(x => x.Tags);
CreateMap<Post, PostWithDetailsDto>().Ignore(x => x.Writer).Ignore(x => x.CommentCount).Ignore(x => x.Tags);
CreateMap<PostCacheItem, PostWithDetailsDto>()
.Ignore(x => x.Writer)
.Ignore(x => x.CommentCount)
.Ignore(x => x.Tags); CreateMap<Tag, TagDto>(); }

结语

本节知识点:

  • 1.远程服务层的使用
  • 2.标签聚合的完成
  • 3.AutoMapper配置

联系作者:加群:867095512 @MrChuJiu

八、Abp vNext 基础篇丨标签聚合功能的更多相关文章

  1. 六、Abp vNext 基础篇丨文章聚合功能上

    介绍 9月开篇讲,前面几章群里已经有几个小伙伴跟着做了一遍了,遇到的问题和疑惑也都在群里反馈和解决好了,9月咱们保持保持更新.争取10月份更新完基础篇. 另外番外篇属于 我在abp群里和日常开发的问题 ...

  2. 七、Abp vNext 基础篇丨文章聚合功能下

    介绍 不好意思这篇文章应该早点更新的,这几天在忙CICD的东西没顾得上,等后面整好了CICD我也发2篇文章讲讲,咱们进入正题,这一章来补全剩下的 2个接口和将文章聚合进行完善. 开工 上一章大部分业务 ...

  3. 九、Abp vNext 基础篇丨评论聚合功能

    介绍 评论本来是要放到标签里面去讲的,但是因为上一章东西有点多了,我就没放进去,这一章单独拿出来,内容不多大家自己写写就可以,也算是对前面讲解的一个小练习吧. 相关注释我也加在代码上面了,大家看看代码 ...

  4. 十一、Abp vNext 基础篇丨测试

    前言 祝大家国庆快乐,本来想国庆之前更新完的,结果没写完,今天把剩下的代码补了一下总算ok了. 本章节也是我们后端日常开发中最重要的一步就是测试,我们经常听到的单元测试.集成测试.UI测试.系统测试, ...

  5. 五、Abp vNext 基础篇丨博客聚合功能

    介绍 业务篇章先从客户端开始写,另外补充一下我给项目起名的时候没多想起的太随意了,结果后面有些地方命名冲突了需要通过手动using不过问题不大. 开工 应用层 根据第三章分层架构里面讲到的现在我们模型 ...

  6. Abp vNext 基础篇丨分层架构

    介绍 本章节对 ABP 框架进行一个简单的介绍,摘自ABP官方,后面会在使用过程中对各个知识点进行细致的讲解. 领域驱动设计 领域驱动设计(简称:DDD)是一种针对复杂需求的软件开发方法.将软件实现与 ...

  7. Abp vNext 基础篇丨领域构建

    介绍 我们将通过例⼦介绍和解释⼀些显式规则.在实现领域驱动设计时,应该遵循这些规则并将其应⽤到解决⽅案中. 领域划分 首先我们先对比下Blog.Core和本次重构设计上的偏差,可以看到多了一个博客管理 ...

  8. 十、Abp vNext 基础篇丨权限

    介绍 本章节来把接口的权限加一下 权限配置和使用 官方地址:https://docs.abp.io/en/abp/latest/Authorization 下面这种代码可能我们日常开发都写过,ASP. ...

  9. iOS系列 基础篇 06 标签和按钮 (Label & Button)

    iOS系列 基础篇 06 标签和按钮 (Label & Button) 目录: 标签控件 按钮控件 小结 标签和按钮是两个常用的控件,下面咱们逐一学习. 1. 标签控件 使用Single Vi ...

随机推荐

  1. docker容器网络bridge

    我们知道docker利用linux内核特性namespace实现了网络的隔离,让每个容器都处于自己的小世界里面,当这个小世界需要与外界(宿主机或其他容器)通信的时候docker的网络就发挥作用了,这篇 ...

  2. CCS box-flex属性

    box-flex==按比例分配父标签的宽度or高度空间 1.非固定分配 eg.一块地总150平方分配给三孩子,按照2:1:1分 #老大 { 房子-分配: 2; } = 75平 #老二 { 房子-分配: ...

  3. Salesforce Integration 概览(五) Remote Call-In(远程操作 外部->salesforce)

    本篇参考:https://resources.docs.salesforce.com/sfdc/pdf/integration_patterns_and_practices.pdf 本篇博客介绍 Re ...

  4. 《Python Cookbook v3.0.0》Chapter2 字符串、文本

    感谢: https://github.com/yidao620c/python3-cookbook 如有侵权,请联系我整改. 本文章节会严格按照原书(以便和原书对照,章节标题可能会略有修改),内容会有 ...

  5. 以命令行方式使用Desktop版Ubuntu

    方法1:安装Ubuntu Server版,好处是默认不安装GUI,且可以自动安装ssh服务和其他很多服务,且消耗系统资源少(约200MB内存,Desktop版启动后需要500MB左右内存),启动和关闭 ...

  6. IDEA Error:java: 无效的源发行版: 11错误

    IDEA Error:java: 无效的源发行版: 11错误 今天在网上下载了一个项目到本地运行报错 Error: Java : 无效的源发行版: 11 ,上网查了很多找到问题所在.项目的 JDK(P ...

  7. 爬虫实践二--豆瓣top250电影

    import requests def get_movies(): headers={ 'user-agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) ...

  8. Linux 开机关机在线求助与指令输入

    由于那本书版本稍稍有点早,我就跳过学习第二章第三章了(分别是主机规划和虚拟机安装)下图为在自己电脑上安装好的redhat7 4.1 我们来登入 其中第一行是Linux发行商和发行版本(欸,我的这个怎么 ...

  9. 【笔记】numpy.array的常用基本运算以及对数据的操作

    numpy.array的基本运算以及对数据的操作 设置一个问题,例如 这种只需要基本的运算就可以实现 类似的 numpy对向量的运算进行了优化,速度是相当快的,这种被称为universal funct ...

  10. 模拟7 T3 寿司题解

    题目要求可以转化成一个01串,让通过最少次数把序列变成中间是0,两端是1: 首先我们可以考虑一些性质: 最优解一定是每次操作都把0和1交换 这个很好理解,如果你交换同一种东西,跟没换一样 这个题卡就卡 ...