首页 .Net 八、Abp vNext 基础篇丨标签聚合功能

八、Abp vNext 基础篇丨标签聚合功能

系列文章列表,点击展示/隐藏

系列教程一目录:知识点源码解析

系列教程二目录:Bcvp AbpVnext讲解

系列教程三目录:单个知识点讲解

系列教程四目录:分库分表(日志系统案例讲解)

本文梯子

    正文

    介绍

    本章节先来把上一章漏掉的上传文件处理下,然后实现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 >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 >FileUploadInputDto
        {
            [Required]
            public byte[] Bytes { get; set; }
    
            [Required]
            public string Name { get; set; }
        }
    
    
        public >FileUploadOutputDto
        {
            public string Name { get; set; }
    
            public string WebUrl { get; set; }
        }
    
    
        public >BlogWebConsts
        {
            public >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 >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 >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 >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 >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 >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 >GetPopularTagsInput
        {
            public int ResultCount { get; set; } = 10;
    
            public int? MinimumPostCount { get; set; }
        }
    
    
        public >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()
            {
                
    
                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

    公众号

    特别声明:本站部分内容收集于互联网是出于更直观传递信息的目的。该内容版权归原作者所有,并不代表本站赞同其观点和对其真实性负责。如该内容涉及任何第三方合法权利,请及时与824310991@qq.com联系,我们会及时反馈并处理完毕。