從本篇就開始博客頁面的接口開發了,其實這些接口我是不想用文字來描述的,太枯燥太無趣了。全是CRUD,誰還不會啊,用得着我來說嗎?想一想爲了避免半途而廢,爲了以前立的Flag,仍是咬牙堅持吧。html
如今博客數據庫中的數據是比較混亂的,爲了看起來像那麼回事,顯得正式一點,我先手動搞點數據進去。git
搞定了種子數據,就能夠去愉快的寫接口了,我這裏將根據我如今的博客頁面去分析所須要接口,感興趣的去點點。github
爲了讓接口看起來清晰,一目瞭然,刪掉以前在IBlogService
中添加的全部接口,將5個自定義倉儲所有添加至BlogService
中,而後用partial
修飾。數據庫
//IBlogService.cs public partial interface IBlogService { } //BlogService.cs using Meowv.Blog.Application.Caching.Blog; using Meowv.Blog.Domain.Blog.Repositories; namespace Meowv.Blog.Application.Blog.Impl { public partial class BlogService : ServiceBase, IBlogService { private readonly IBlogCacheService _blogCacheService; private readonly IPostRepository _postRepository; private readonly ICategoryRepository _categoryRepository; private readonly ITagRepository _tagRepository; private readonly IPostTagRepository _postTagRepository; private readonly IFriendLinkRepository _friendLinksRepository; public BlogService(IBlogCacheService blogCacheService, IPostRepository postRepository, ICategoryRepository categoryRepository, ITagRepository tagRepository, IPostTagRepository postTagRepository, IFriendLinkRepository friendLinksRepository) { _blogCacheService = blogCacheService; _postRepository = postRepository; _categoryRepository = categoryRepository; _tagRepository = tagRepository; _postTagRepository = postTagRepository; _friendLinksRepository = friendLinksRepository; } } }
在Blog文件夾下依次添加接口:IBlogService.Post.cs
、IBlogService.Category.cs
、IBlogService.Tag.cs
、IBlogService.FriendLink.cs
、IBlogService.Admin.cs
。緩存
在Blog/Impl文件夾下添加實現類:IBlogService.Post.cs
、BlogService.Category.cs
、BlogService.Tag.cs
、BlogService.FriendLink.cs
、BlogService.Admin.cs
。app
同上,.Application.Caching
層也按照這個樣子添加。async
注意都須要添加partial修飾爲局部的接口和實現類,全部文章相關的接口放在IBlogService.Post.cs
中,分類放在IBlogService.Category.cs
,標籤放在IBlogService.Tag.cs
,友鏈放在IBlogService.FriendLink.cs
,後臺增刪改全部接口放在IBlogService.Admin.cs
,最終效果圖以下:post
分析:列表帶分頁,以文章發表的年份分組,所需字段:標題、連接、時間、年份。學習
在.Application.Contracts
層Blog文件夾下添加返回的模型:QueryPostDto.cs
。url
//QueryPostDto.cs using System.Collections.Generic; namespace Meowv.Blog.Application.Contracts.Blog { public class QueryPostDto { /// <summary> /// 年份 /// </summary> public int Year { get; set; } /// <summary> /// Posts /// </summary> public IEnumerable<PostBriefDto> Posts { get; set; } } }
模型爲一個年份和一個文章列表,文章列表模型:PostBriefDto.cs
。
//PostBriefDto.cs namespace Meowv.Blog.Application.Contracts.Blog { public class PostBriefDto { /// <summary> /// 標題 /// </summary> public string Title { get; set; } /// <summary> /// 連接 /// </summary> public string Url { get; set; } /// <summary> /// 年份 /// </summary> public int Year { get; set; } /// <summary> /// 建立時間 /// </summary> public string CreationTime { get; set; } } }
搞定,由於返回時間爲英文格式,因此CreationTime
給了字符串類型。
在IBlogService.Post.cs
中添加接口分頁查詢文章列表QueryPostsAsync
,確定須要接受倆參數分頁頁碼和分頁數量。仍是去添加一個公共模型PagingInput
吧,在.Application.Contracts
下面。
//PagingInput.cs using System.ComponentModel.DataAnnotations; namespace Meowv.Blog.Application.Contracts { /// <summary> /// 分頁輸入參數 /// </summary> public class PagingInput { /// <summary> /// 頁碼 /// </summary> [Range(1, int.MaxValue)] public int Page { get; set; } = 1; /// <summary> /// 限制條數 /// </summary> [Range(10, 30)] public int Limit { get; set; } = 10; } }
Page
設置默認值爲1,Limit
設置默認值爲10,Range Attribute
設置參數可輸入大小限制,因而這個分頁查詢文章列表的接口就是這個樣子的。
//IBlogService.Post.cs public partial interface IBlogService { /// <summary> /// 分頁查詢文章列表 /// </summary> /// <param name="input"></param> /// <returns></returns> Task<ServiceResult<PagedList<QueryPostDto>>> QueryPostsAsync(PagingInput input); }
ServiceResult
和PagedList
是以前添加的統一返回模型,緊接着就去添加一個分頁查詢文章列表緩存接口,和上面是對應的。
//IBlogCacheService.Post.cs using Meowv.Blog.Application.Contracts; using Meowv.Blog.Application.Contracts.Blog; using Meowv.Blog.ToolKits.Base; using System; using System.Threading.Tasks; namespace Meowv.Blog.Application.Caching.Blog { public partial interface IBlogCacheService { /// <summary> /// 分頁查詢文章列表 /// </summary> /// <param name="input"></param> /// <param name="factory"></param> /// <returns></returns> Task<ServiceResult<PagedList<QueryPostDto>>> QueryPostsAsync(PagingInput input, Func<Task<ServiceResult<PagedList<QueryPostDto>>>> factory); } }
分別實現這兩個接口。
//BlogCacheService.Post.cs public partial class BlogCacheService { private const string KEY_QueryPosts = "Blog:Post:QueryPosts-{0}-{1}"; /// <summary> /// 分頁查詢文章列表 /// </summary> /// <param name="input"></param> /// <param name="factory"></param> /// <returns></returns> public async Task<ServiceResult<PagedList<QueryPostDto>>> QueryPostsAsync(PagingInput input, Func<Task<ServiceResult<PagedList<QueryPostDto>>>> factory) { return await Cache.GetOrAddAsync(KEY_QueryPosts.FormatWith(input.Page, input.Limit), factory, CacheStrategy.ONE_DAY); } }
//BlogService.Post.cs /// <summary> /// 分頁查詢文章列表 /// </summary> /// <param name="input"></param> /// <returns></returns> public async Task<ServiceResult<PagedList<QueryPostDto>>> QueryPostsAsync(PagingInput input) { return await _blogCacheService.QueryPostsAsync(input, async () => { var result = new ServiceResult<PagedList<QueryPostDto>>(); var count = await _postRepository.GetCountAsync(); var list = _postRepository.OrderByDescending(x => x.CreationTime) .PageByIndex(input.Page, input.Limit) .Select(x => new PostBriefDto { Title = x.Title, Url = x.Url, Year = x.CreationTime.Year, CreationTime = x.CreationTime.TryToDateTime() }).GroupBy(x => x.Year) .Select(x => new QueryPostDto { Year = x.Key, Posts = x.ToList() }).ToList(); result.IsSuccess(new PagedList<QueryPostDto>(count.TryToInt(), list)); return result; }); }
PageByIndex(...)
、TryToDateTime()
是.ToolKits
層添加的擴展方法,先查詢總數,而後根據時間倒序,分頁,篩選出所需字段,根據年份分組,輸出,結束。
在BlogController
中添加API。
/// <summary> /// 分頁查詢文章列表 /// </summary> /// <param name="input"></param> /// <returns></returns> [HttpGet] [Route("posts")] public async Task<ServiceResult<PagedList<QueryPostDto>>> QueryPostsAsync([FromQuery] PagingInput input) { return await _blogService.QueryPostsAsync(input); }
[FromQuery]
設置input爲從URL進行查詢參數,編譯運行看效果。
已經能夠查詢出數據,而且緩存至Redis中。
分析:文章詳情頁,文章的標題、做者、發佈時間、所屬分類、標籤列表、文章內容(HTML和MarkDown)、連接、上下篇的標題和連接。
建立返回模型:PostDetailDto.cs
//PostDetailDto.cs using System.Collections.Generic; namespace Meowv.Blog.Application.Contracts.Blog { public class PostDetailDto { /// <summary> /// 標題 /// </summary> public string Title { get; set; } /// <summary> /// 做者 /// </summary> public string Author { get; set; } /// <summary> /// 連接 /// </summary> public string Url { get; set; } /// <summary> /// HTML /// </summary> public string Html { get; set; } /// <summary> /// Markdown /// </summary> public string Markdown { get; set; } /// <summary> /// 建立時間 /// </summary> public string CreationTime { get; set; } /// <summary> /// 分類 /// </summary> public CategoryDto Category { get; set; } /// <summary> /// 標籤列表 /// </summary> public IEnumerable<TagDto> Tags { get; set; } /// <summary> /// 上一篇 /// </summary> public PostForPagedDto Previous { get; set; } /// <summary> /// 下一篇 /// </summary> public PostForPagedDto Next { get; set; } } }
同時添加CategoryDto
、TagDto
、PostForPagedDto
。
//CategoryDto.cs namespace Meowv.Blog.Application.Contracts.Blog { public class CategoryDto { /// <summary> /// 分類名稱 /// </summary> public string CategoryName { get; set; } /// <summary> /// 展現名稱 /// </summary> public string DisplayName { get; set; } } } //TagDto.cs namespace Meowv.Blog.Application.Contracts.Blog { public class TagDto { /// <summary> /// 標籤名稱 /// </summary> public string TagName { get; set; } /// <summary> /// 展現名稱 /// </summary> public string DisplayName { get; set; } } } //PostForPagedDto.cs namespace Meowv.Blog.Application.Contracts.Blog { public class PostForPagedDto { /// <summary> /// 標題 /// </summary> public string Title { get; set; } /// <summary> /// 連接 /// </summary> public string Url { get; set; } } }
添加獲取文章詳情接口和緩存的接口。
//IBlogService.Post.cs public partial interface IBlogService { /// <summary> /// 根據URL獲取文章詳情 /// </summary> /// <param name="url"></param> /// <returns></returns> Task<ServiceResult<PostDetailDto>> GetPostDetailAsync(string url); }
//IBlogCacheService.Post.cs public partial interface IBlogCacheService { /// <summary> /// 根據URL獲取文章詳情 /// </summary> /// <param name="url"></param> /// <returns></returns> Task<ServiceResult<PostDetailDto>> GetPostDetailAsync(string url, Func<Task<ServiceResult<PostDetailDto>>> factory); }
分別實現這兩個接口。
//BlogCacheService.Post.cs public partial class BlogCacheService { private const string KEY_GetPostDetail = "Blog:Post:GetPostDetail-{0}"; /// <summary> /// 根據URL獲取文章詳情 /// </summary> /// <param name="url"></param> /// <param name="factory"></param> /// <returns></returns> public async Task<ServiceResult<PostDetailDto>> GetPostDetailAsync(string url, Func<Task<ServiceResult<PostDetailDto>>> factory) { return await Cache.GetOrAddAsync(KEY_GetPostDetail.FormatWith(url), factory, CacheStrategy.ONE_DAY); } }
//BlogService.Post.cs /// <summary> /// 根據URL獲取文章詳情 /// </summary> /// <param name="url"></param> /// <returns></returns> public async Task<ServiceResult<PostDetailDto>> GetPostDetailAsync(string url) { return await _blogCacheService.GetPostDetailAsync(url, async () => { var result = new ServiceResult<PostDetailDto>(); var post = await _postRepository.FindAsync(x => x.Url.Equals(url)); if (null == post) { result.IsFailed(ResponseText.WHAT_NOT_EXIST.FormatWith("URL", url)); return result; } var category = await _categoryRepository.GetAsync(post.CategoryId); var tags = from post_tags in await _postTagRepository.GetListAsync() join tag in await _tagRepository.GetListAsync() on post_tags.TagId equals tag.Id where post_tags.PostId.Equals(post.Id) select new TagDto { TagName = tag.TagName, DisplayName = tag.DisplayName }; var previous = _postRepository.Where(x => x.CreationTime > post.CreationTime).Take(1).FirstOrDefault(); var next = _postRepository.Where(x => x.CreationTime < post.CreationTime).OrderByDescending(x => x.CreationTime).Take(1).FirstOrDefault(); var postDetail = new PostDetailDto { Title = post.Title, Author = post.Author, Url = post.Url, Html = post.Html, Markdown = post.Markdown, CreationTime = post.CreationTime.TryToDateTime(), Category = new CategoryDto { CategoryName = category.CategoryName, DisplayName = category.DisplayName }, Tags = tags, Previous = previous == null ? null : new PostForPagedDto { Title = previous.Title, Url = previous.Url }, Next = next == null ? null : new PostForPagedDto { Title = next.Title, Url = next.Url } }; result.IsSuccess(postDetail); return result; }); }
ResponseText.WHAT_NOT_EXIST
是定義在MeowvBlogConsts.cs
的常量。
TryToDateTime()
和列表查詢中的擴展方法同樣,轉換時間爲想要的格式。
簡單說一下查詢邏輯,先根據參數url,查詢是否存在數據,若是文章不存在則返回錯誤消息。
而後根據 post.CategoryId
就能夠查詢到當前文章的分類名稱。
聯合查詢post_tags和tag兩張表,指定查詢條件post.Id,查詢當前文章的全部標籤。
最後上下篇的邏輯也很簡單,上一篇取大於當前文章發佈時間的第一篇,下一篇取時間倒序排序而且小於當前文章發佈時間的第一篇文章。
最後將全部查詢到的數據賦值給輸出對象,返回,結束。
在BlogController
中添加API。
/// <summary> /// 根據URL獲取文章詳情 /// </summary> /// <param name="url"></param> /// <returns></returns> [HttpGet] [Route("post")] public async Task<ServiceResult<PostDetailDto>> GetPostDetailAsync(string url) { return await _blogService.GetPostDetailAsync(url); }
編譯運行,而後輸入URL查詢一條文章詳情數據。
成功輸出預期內容,緩存同時也是ok的。
開源地址:https://github.com/Meowv/Blog/tree/blog_tutorial
搭配下方課程學習更佳 ↓ ↓ ↓