系列文章
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 使用 abp cli 搭建專案
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 給專案瘦身,讓它跑起來
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 完善與美化,Swagger登場
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 資料訪問和程式碼優先
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 自定義倉儲之增刪改查
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 統一規範API,包裝返回模型
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 再說Swagger,分組、描述、小綠鎖
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 接入GitHub,用JWT保護你的API
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 異常處理和日誌記錄
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 使用Redis快取資料
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 整合Hangfire實現定時任務處理
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 用AutoMapper搞定物件對映
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 定時任務最佳實戰(一)
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 定時任務最佳實戰(二)
- 基於 abp vNext 和 .NET Core 開發部落格專案 - 定時任務最佳實戰(三)
從本篇就開始部落格頁面的介面開發了,其實這些介面我是不想用文字來描述的,太枯燥太無趣了。全是CRUD,誰還不會啊,用得著我來講嗎?想想為了不半途而廢,為了之前立的Flag,還是咬牙堅持吧。
準備工作
現在部落格資料庫中的資料是比較混亂的,為了看起來像那麼回事,顯得正式一點,我先手動搞點資料進去。
搞定了種子資料,就可以去愉快的寫介面了,我這裡將根據我現在的部落格頁面去分析所需要介面,感興趣的去點點。
為了讓介面看起來清晰,一目瞭然,刪掉之前在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
。
同上,.Application.Caching
層也按照這個樣子新增。
注意都需要新增partial修飾為區域性的介面和實現類,所有文章相關的介面放在IBlogService.Post.cs
中,分類放在IBlogService.Category.cs
,標籤放在IBlogService.Tag.cs
,友鏈放在IBlogService.FriendLink.cs
,後臺增刪改所有介面放在IBlogService.Admin.cs
,最終效果圖如下:
文章列表頁
分析:列表帶分頁,以文章發表的年份分組,所需欄位:標題、連結、時間、年份。
在.Application.Contracts
層Blog資料夾下新增返回的模型:QueryPostDto.cs
。
//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的。