基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(二)

1|0系列文章

  1. 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目
  2. 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来
  3. 基于 abp vNext 和 .NET Core 开发博客项目 - 完善与美化,Swagger登场
  4. 基于 abp vNext 和 .NET Core 开发博客项目 - 数据访问和代码优先
  5. 基于 abp vNext 和 .NET Core 开发博客项目 - 自定义仓储之增删改查
  6. 基于 abp vNext 和 .NET Core 开发博客项目 - 统一规范API,包装返回模型
  7. 基于 abp vNext 和 .NET Core 开发博客项目 - 再说Swagger,分组、描述、小绿锁
  8. 基于 abp vNext 和 .NET Core 开发博客项目 - 接入GitHub,用JWT保护你的API
  9. 基于 abp vNext 和 .NET Core 开发博客项目 - 异常处理和日志记录
  10. 基于 abp vNext 和 .NET Core 开发博客项目 - 使用Redis缓存数据
  11. 基于 abp vNext 和 .NET Core 开发博客项目 - 集成Hangfire实现定时任务处理
  12. 基于 abp vNext 和 .NET Core 开发博客项目 - 用AutoMapper搞定对象映射
  13. 基于 abp vNext 和 .NET Core 开发博客项目 - 定时任务最佳实战(一)
  14. 基于 abp vNext 和 .NET Core 开发博客项目 - 定时任务最佳实战(二)
  15. 基于 abp vNext 和 .NET Core 开发博客项目 - 定时任务最佳实战(三)
  16. 基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(一)

上篇文章完成了两个接口:文章列表页、文章详情页,本篇继续。

2|0分类列表

1

分析:这里多了一个统计文章数量的字段,可以直接新建一个模型QueryCategoryDto.cs继承CategoryDto

//QueryCategoryDto.cs namespace Meowv.Blog.Application.Contracts.Blog { public class QueryCategoryDto : CategoryDto { /// <summary> /// 总数 /// </summary> public int Count { get; set; } } }

添加查询分类列表接口和缓存接口。

//IBlogService.Category.cs using Meowv.Blog.Application.Contracts.Blog; using Meowv.Blog.ToolKits.Base; using System.Collections.Generic; using System.Threading.Tasks; namespace Meowv.Blog.Application.Blog { public partial interface IBlogService { /// <summary> /// 查询分类列表 /// </summary> /// <returns></returns> Task<ServiceResult<IEnumerable<QueryCategoryDto>>> QueryCategoriesAsync(); } }
//IBlogCacheService.Category.cs using Meowv.Blog.Application.Contracts.Blog; using Meowv.Blog.ToolKits.Base; using System; using System.Collections.Generic; using System.Threading.Tasks; namespace Meowv.Blog.Application.Caching.Blog { public partial interface IBlogCacheService { /// <summary> /// 查询分类列表 /// </summary> /// <param name="factory"></param> /// <returns></returns> Task<ServiceResult<IEnumerable<QueryCategoryDto>>> QueryCategoriesAsync(Func<Task<ServiceResult<IEnumerable<QueryCategoryDto>>>> factory); } }

分别实现这两个接口。

//BlogCacheService.Category.cs using Meowv.Blog.Application.Contracts.Blog; using Meowv.Blog.ToolKits.Base; using System; using System.Collections.Generic; using System.Threading.Tasks; using static Meowv.Blog.Domain.Shared.MeowvBlogConsts; namespace Meowv.Blog.Application.Caching.Blog.Impl { public partial class BlogCacheService { private const string KEY_QueryCategories = "Blog:Category:QueryCategories"; /// <summary> /// 查询分类列表 /// </summary> /// <param name="factory"></param> /// <returns></returns> public async Task<ServiceResult<IEnumerable<QueryCategoryDto>>> QueryCategoriesAsync(Func<Task<ServiceResult<IEnumerable<QueryCategoryDto>>>> factory) { return await Cache.GetOrAddAsync(KEY_QueryCategories, factory, CacheStrategy.ONE_DAY); } } }
//BlogService.Category.cs using Meowv.Blog.Application.Contracts.Blog; using Meowv.Blog.ToolKits.Base; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace Meowv.Blog.Application.Blog.Impl { public partial class BlogService { /// <summary> /// 查询分类列表 /// </summary> /// <returns></returns> public async Task<ServiceResult<IEnumerable<QueryCategoryDto>>> QueryCategoriesAsync() { return await _blogCacheService.QueryCategoriesAsync(async () => { var result = new ServiceResult<IEnumerable<QueryCategoryDto>>(); var list = from category in await _categoryRepository.GetListAsync() join posts in await _postRepository.GetListAsync() on category.Id equals posts.CategoryId group category by new { category.CategoryName, category.DisplayName } into g select new QueryCategoryDto { CategoryName = g.Key.CategoryName, DisplayName = g.Key.DisplayName, Count = g.Count() }; result.IsSuccess(list); return result; }); } } }

缓存就不说了,查询分类列表,联合查询文章和分类两张表,关联字段为CategoryId,然后分组,计算出对应的数量,在BlogController中添加API。

/// <summary> /// 查询分类列表 /// </summary> /// <returns></returns> [HttpGet] [Route("categories")] public async Task<ServiceResult<IEnumerable<QueryCategoryDto>>> QueryCategoriesAsync() { return await _blogService.QueryCategoriesAsync(); }

2

3|0标签列表

3

分析:和分类列表差不多,新建模型QueryTagDto.cs继承TagDto

//QueryTagDto.cs namespace Meowv.Blog.Application.Contracts.Blog { public class QueryTagDto : TagDto { /// <summary> /// 总数 /// </summary> public int Count { get; set; } } }

添加查询标签列表接口和缓存接口。

//IBlogCacheService.Tag.cs using Meowv.Blog.Application.Contracts.Blog; using Meowv.Blog.ToolKits.Base; using System; using System.Collections.Generic; using System.Threading.Tasks; namespace Meowv.Blog.Application.Caching.Blog { public partial interface IBlogCacheService { /// <summary> /// 查询标签列表 /// </summary> /// <param name="factory"></param> /// <returns></returns> Task<ServiceResult<IEnumerable<QueryTagDto>>> QueryTagsAsync(Func<Task<ServiceResult<IEnumerable<QueryTagDto>>>> factory); } }
//IBlogService.Tag.cs using Meowv.Blog.Application.Contracts.Blog; using Meowv.Blog.ToolKits.Base; using System.Collections.Generic; using System.Threading.Tasks; namespace Meowv.Blog.Application.Blog { public partial interface IBlogService { /// <summary> /// 查询标签列表 /// </summary> /// <returns></returns> Task<ServiceResult<IEnumerable<QueryTagDto>>> QueryTagsAsync(); } }

分别实现这两个接口。

//BlogCacheService.Tag.cs using Meowv.Blog.Application.Contracts.Blog; using Meowv.Blog.ToolKits.Base; using System; using System.Collections.Generic; using System.Threading.Tasks; using static Meowv.Blog.Domain.Shared.MeowvBlogConsts; namespace Meowv.Blog.Application.Caching.Blog.Impl { public partial class BlogCacheService { private const string KEY_QueryTags = "Blog:Tag:QueryTags"; /// <summary> /// 查询标签列表 /// </summary> /// <param name="factory"></param> /// <returns></returns> public async Task<ServiceResult<IEnumerable<QueryTagDto>>> QueryTagsAsync(Func<Task<ServiceResult<IEnumerable<QueryTagDto>>>> factory) { return await Cache.GetOrAddAsync(KEY_QueryTags, factory, CacheStrategy.ONE_DAY); } } }
//BlogService.Tag.cs using Meowv.Blog.Application.Contracts.Blog; using Meowv.Blog.ToolKits.Base; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace Meowv.Blog.Application.Blog.Impl { public partial class BlogService { /// <summary> /// 查询标签列表 /// </summary> /// <returns></returns> public async Task<ServiceResult<IEnumerable<QueryTagDto>>> QueryTagsAsync() { return await _blogCacheService.QueryTagsAsync(async () => { var result = new ServiceResult<IEnumerable<QueryTagDto>>(); var list = from tags in await _tagRepository.GetListAsync() join post_tags in await _postTagRepository.GetListAsync() on tags.Id equals post_tags.TagId group tags by new { tags.TagName, tags.DisplayName } into g select new QueryTagDto { TagName = g.Key.TagName, DisplayName = g.Key.DisplayName, Count = g.Count() }; result.IsSuccess(list); return result; }); } } }

查询标签列表需要联合查询tags和post_tags,根据TagId进行关联,然后分组从而获取标签下文章的总数,在BlogController中添加API。

/// <summary> /// 查询标签列表 /// </summary> /// <returns></returns> [HttpGet] [Route("tags")] public async Task<ServiceResult<IEnumerable<QueryTagDto>>> QueryTagsAsync() { return await _blogService.QueryTagsAsync(); }

4

4|0分类名称&文章列表

5

分析:此页面下包含两个接口,查询分类的名称和当前分类下的文章列表,和文章列表不同的是,它不带分页。分类包含两个字段,分类名称和展示名称,我们要把真正的名称查询出来展示在页面上。

4|1分类名称

不需要给他添加返回模型,直接返回一个string类型即可,同时给一个查询参数name,添加获取分类名称接口和缓存接口。

//IBlogService.Category.cs /// <summary> /// 获取分类名称 /// </summary> /// <param name="name"></param> /// <returns></returns> Task<ServiceResult<string>> GetCategoryAsync(string name);
//IBlogCacheService.Category.cs /// <summary> /// 获取分类名称 /// </summary> /// <param name="name"></param> /// <param name="factory"></param> /// <returns></returns> Task<ServiceResult<string>> GetCategoryAsync(string name, Func<Task<ServiceResult<string>>> factory);

实现这两个接口。

//BlogCacheService.Category.cs ... public partial class BlogCacheService { private const string KEY_GetCategory = "Blog:Category:GetCategory-{0}"; /// <summary> /// 获取分类名称 /// </summary> /// <param name="name"></param> /// <param name="factory"></param> /// <returns></returns> public async Task<ServiceResult<string>> GetCategoryAsync(string name, Func<Task<ServiceResult<string>>> factory) { return await Cache.GetOrAddAsync(KEY_GetCategory.FormatWith(name), factory, CacheStrategy.ONE_DAY); } } ...
//BlogService.Category.cs /// <summary> /// 获取分类名称 /// </summary> /// <param name="name"></param> /// <returns></returns> public async Task<ServiceResult<string>> GetCategoryAsync(string name) { return await _blogCacheService.GetCategoryAsync(name, async () => { var result = new ServiceResult<string>(); var category = await _categoryRepository.FindAsync(x => x.DisplayName.Equals(name)); if (null == category) { result.IsFailed(ResponseText.WHAT_NOT_EXIST.FormatWith("分类", name)); return result; } result.IsSuccess(category.CategoryName); return result; }); }

FormatWith()是扩展方法,ResponseText.WHAT_NOT_EXIST是之前说过的常量,直接查询是否存在当前name的分类,如果不存在给出错误提示,存在的话,则只返回分类名称,在BlogController中添加API。

/// <summary> /// 获取分类名称 /// </summary> /// <param name="name"></param> /// <returns></returns> [HttpGet] [Route("category")] public async Task<ServiceResult<string>> GetCategoryAsync(([Required] string name) { return await _blogService.GetCategoryAsync(name); }

[Required]Attribute 指定参数name必填。

6

7

4|2文章列表

通过分类名称查询文章列表和分页查询文章列表返回模型是一样的,只是不用分页,所以直接返回一个列表就可以了,添加通过分类名称查询文章列表和缓存的接口。

//IBlogService.Post.cs /// <summary> /// 通过分类名称查询文章列表 /// </summary> /// <param name="name"></param> /// <returns></returns> Task<ServiceResult<IEnumerable<QueryPostDto>>> QueryPostsByCategoryAsync(string name);
//IBlogCacheService.Post.cs /// <summary> /// 通过分类名称查询文章列表 /// </summary> /// <param name="name"></param> /// <param name="factory"></param> /// <returns></returns> Task<ServiceResult<IEnumerable<QueryPostDto>>> QueryPostsByCategoryAsync(string name, Func<Task<ServiceResult<IEnumerable<QueryPostDto>>>> factory);

分别实现这两个接口。

//BlogCacheService.Post.cs ... public partial class BlogCacheService { private const string KEY_QueryPostsByCategory = "Blog:Post:QueryPostsByCategory-{0}"; /// <summary> /// 通过分类名称查询文章列表 /// </summary> /// <param name="name"></param> /// <param name="factory"></param> /// <returns></returns> public async Task<ServiceResult<IEnumerable<QueryPostDto>>> QueryPostsByCategoryAsync(string name, Func<Task<ServiceResult<IEnumerable<QueryPostDto>>>> factory) { return await Cache.GetOrAddAsync(KEY_QueryPostsByCategory.FormatWith(name), factory, CacheStrategy.ONE_DAY); } } ...
//BlogService.Post.cs /// <summary> /// 通过分类名称查询文章列表 /// </summary> /// <param name="name"></param> /// <returns></returns> public async Task<ServiceResult<IEnumerable<QueryPostDto>>> QueryPostsByCategoryAsync(string name) { return await _blogCacheService.QueryPostsByCategoryAsync(name, async () => { var result = new ServiceResult<IEnumerable<QueryPostDto>>(); var list = (from posts in await _postRepository.GetListAsync() join categories in await _categoryRepository.GetListAsync() on posts.CategoryId equals categories.Id where categories.DisplayName.Equals(name) orderby posts.CreationTime descending select new PostBriefDto { Title = posts.Title, Url = posts.Url, Year = posts.CreationTime.Year, CreationTime = posts.CreationTime.TryToDateTime() }) .GroupBy(x => x.Year) .Select(x => new QueryPostDto { Year = x.Key, Posts = x.ToList() }); result.IsSuccess(list); return result; }); }

这个逻辑和分页查询文章列表是差不多的,联合查询文章表和分类表,关联字段为CategoryId,指定查询条件categories.DisplayName==name,以CreationTime倒序排序,年份分组,筛选出所需字段返回,在BlogController中添加API。

/// <summary> /// 通过分类名称查询文章列表 /// </summary> /// <param name="name"></param> /// <returns></returns> [HttpGet] [Route("posts/category")] public async Task<ServiceResult<IEnumerable<QueryPostDto>>> QueryPostsByCategoryAsync([Required] string name) { return await _blogService.QueryPostsByCategoryAsync(name); }

8

5|0标签名称&文章列表

9

分析:此页面和分类页一样,包含两个接口,查询标签的名称和当前标签下的文章列表。

5|1标签名称

添加获取标签名称接口和缓存接口,GetTagAsync()

//IBlogService.Tag.cs /// <summary> /// 获取标签名称 /// </summary> /// <param name="name"></param> /// <returns></returns> Task<ServiceResult<string>> GetTagAsync(string name);
//IBlogCacheService.Tag.cs /// <summary> /// 获取标签名称 /// </summary> /// <param name="name"></param> /// <param name="factory"></param> /// <returns></returns> Task<ServiceResult<string>> GetTagAsync(string name, Func<Task<ServiceResult<string>>> factory);

实现这两个接口。

//BlogCacheService.Tag.cs ... public partial class BlogCacheService { private const string KEY_GetTag = "Blog:Tag:GetTag-{0}"; /// <summary> /// 获取标签名称 /// </summary> /// <param name="name"></param> /// <param name="factory"></param> /// <returns></returns> public async Task<ServiceResult<string>> GetTagAsync(string name, Func<Task<ServiceResult<string>>> factory) { return await Cache.GetOrAddAsync(KEY_GetTag.FormatWith(name), factory, CacheStrategy.ONE_DAY); } } ...
//BlogService.Tag.cs /// <summary> /// 获取标签名称 /// </summary> /// <param name="name"></param> /// <returns></returns> public async Task<ServiceResult<string>> GetTagAsync(string name) { return await _blogCacheService.GetTagAsync(name, async () => { var result = new ServiceResult<string>(); var tag = await _tagRepository.FindAsync(x => x.DisplayName.Equals(name)); if (null == tag) { result.IsFailed(ResponseText.WHAT_NOT_EXIST.FormatWith("标签", name)); return result; } result.IsSuccess(tag.TagName); return result; }); }

FormatWith()是扩展方法,ResponseText.WHAT_NOT_EXIST是之前说过的常量,直接查询是否存在当前name的分类,如果不存在给出错误提示,存在的话,则只返回分类名称,在BlogController中添加API。

/// <summary> /// 获取标签名称 /// </summary> /// <param name="name"></param> /// <returns></returns> [HttpGet] [Route("tag")] public async Task<ServiceResult<string>> GetTagAsync(string name) { return await _blogService.GetTagAsync(name); }

[Required]Attribute 指定参数name必填。

10

11

5|2文章列表

和上面一模一样的,添加通过标签名称查询文章列表接口和缓存接口。

//IBlogService.Post.cs /// <summary> /// 通过标签名称查询文章列表 /// </summary> /// <param name="name"></param> /// <returns></returns> Task<ServiceResult<IEnumerable<QueryPostDto>>> QueryPostsByTagAsync(string name);
//IBlogCacheService.Post.cs /// <summary> /// 通过标签名称查询文章列表 /// </summary> /// <param name="name"></param> /// <param name="factory"></param> /// <returns></returns> Task<ServiceResult<IEnumerable<QueryPostDto>>> QueryPostsByTagAsync(string name, Func<Task<ServiceResult<IEnumerable<QueryPostDto>>>> factory);

分别实现这两个接口。

//BlogCacheService.Post.cs ... public partial class BlogCacheService { private const string KEY_QueryPostsByTag = "Blog:Post:QueryPostsByTag-{0}"; /// <summary> /// 通过标签名称查询文章列表 /// </summary> /// <param name="name"></param> /// <param name="factory"></param> /// <returns></returns> public async Task<ServiceResult<IEnumerable<QueryPostDto>>> QueryPostsByTagAsync(string name, Func<Task<ServiceResult<IEnumerable<QueryPostDto>>>> factory) { return await Cache.GetOrAddAsync(KEY_QueryPostsByTag.FormatWith(name), factory, CacheStrategy.ONE_DAY); } } ...
//BlogService.Post.cs /// <summary> /// 通过标签名称查询文章列表 /// </summary> /// <param name="name"></param> /// <returns></returns> public async Task<ServiceResult<IEnumerable<QueryPostDto>>> QueryPostsByTagAsync(string name) { return await _blogCacheService.QueryPostsByTagAsync(name, async () => { var result = new ServiceResult<IEnumerable<QueryPostDto>>(); var list = (from post_tags in await _postTagRepository.GetListAsync() join tags in await _tagRepository.GetListAsync() on post_tags.TagId equals tags.Id join posts in await _postRepository.GetListAsync() on post_tags.PostId equals posts.Id where tags.DisplayName.Equals(name) orderby posts.CreationTime descending select new PostBriefDto { Title = posts.Title, Url = posts.Url, Year = posts.CreationTime.Year, CreationTime = posts.CreationTime.TryToDateTime() }) .GroupBy(x => x.Year) .Select(x => new QueryPostDto { Year = x.Key, Posts = x.ToList() }); result.IsSuccess(list); return result; }); }

这个查询有点特殊,联合查询了3张表,先查post_tags和tags,关联字段TagId,再根据PostId查询posts,指定查询条件tags.DisplayName==name,以CreationTime倒序排序,年份分组,筛选出所需字段返回,在BlogController中添加API。

/// <summary> /// 通过标签名称查询文章列表 /// </summary> /// <param name="name"></param> /// <returns></returns> [HttpGet] [Route("posts/tag")] public async Task<ServiceResult<IEnumerable<QueryPostDto>>> QueryPostsByTagAsync(string name) { return await _blogService.QueryPostsByTagAsync(name); }

12

至此,基本上完成了博客前端所需的所有查询接口,就还剩下友链的查询,大家可以自己完成,后面如果需要什么新的接口再回头来写就好了。

开源地址:https://github.com/Meowv/Blog/tree/blog_tutorial


搭配下方课程学习更佳 ↓ ↓ ↓

http://gk.link/a/10iQ7

7


__EOF__

本文作者阿星Plus
本文链接https://www.cnblogs.com/meowv/p/12994914.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   阿星Plus  阅读(1143)  评论(2编辑  收藏  举报
编辑推荐:
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
点击右上角即可分享
微信分享提示