上篇文章完成了分類和標籤頁面相關的共6個接口,本篇繼續來寫博客增刪改查API的業務。html
供前端查詢用的接口還剩下一個,這裏先補上。前端
分析:返回標題和對應的連接便可,傳輸對象FriendLinkDto.cs
。git
//FriendLinkDto.cs namespace Meowv.Blog.Application.Contracts.Blog { public class FriendLinkDto { /// <summary> /// 標題 /// </summary> public string Title { get; set; } /// <summary> /// 連接 /// </summary> public string LinkUrl { get; set; } } }
添加查詢友鏈列表接口和緩存接口。github
//IBlogService.FriendLink.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<FriendLinkDto>>> QueryFriendLinksAsync(); } }
//IBlogCacheService.FriendLink.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<FriendLinkDto>>> QueryFriendLinksAsync(Func<Task<ServiceResult<IEnumerable<FriendLinkDto>>>> factory); } }
接下來,實現他們。數據庫
//BlogCacheService.FriendLink.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_QueryFriendLinks = "Blog:FriendLink:QueryFriendLinks"; /// <summary> /// 查詢友鏈列表 /// </summary> /// <param name="factory"></param> /// <returns></returns> public async Task<ServiceResult<IEnumerable<FriendLinkDto>>> QueryFriendLinksAsync(Func<Task<ServiceResult<IEnumerable<FriendLinkDto>>>> factory) { return await Cache.GetOrAddAsync(KEY_QueryFriendLinks, factory, CacheStrategy.ONE_DAY); } } }
//BlogService.FriendLink.cs using Meowv.Blog.Application.Contracts.Blog; using Meowv.Blog.Domain.Blog; using Meowv.Blog.ToolKits.Base; using System.Collections.Generic; using System.Threading.Tasks; namespace Meowv.Blog.Application.Blog.Impl { public partial class BlogService { /// <summary> /// 查詢友鏈列表 /// </summary> /// <returns></returns> public async Task<ServiceResult<IEnumerable<FriendLinkDto>>> QueryFriendLinksAsync() { return await _blogCacheService.QueryFriendLinksAsync(async () => { var result = new ServiceResult<IEnumerable<FriendLinkDto>>(); var friendLinks = await _friendLinksRepository.GetListAsync(); var list = ObjectMapper.Map<IEnumerable<FriendLink>, IEnumerable<FriendLinkDto>>(friendLinks); result.IsSuccess(list); return result; }); } } }
直接查詢全部的友鏈數據,這裏使用前面講到的AutoMapper處理對象映射,將IEnumerable<FriendLink>
轉換爲IEnumerable<FriendLinkDto>
。緩存
在MeowvBlogAutoMapperProfile.cs
中添加一條配置:CreateMap<FriendLink, FriendLinkDto>();
,在BlogController
中添加API。app
/// <summary> /// 查詢友鏈列表 /// </summary> /// <returns></returns> [HttpGet] [Route("friendlinks")] public async Task<ServiceResult<IEnumerable<FriendLinkDto>>> QueryFriendLinksAsync() { return await _blogService.QueryFriendLinksAsync(); }
編譯運行,打開查詢友鏈的API,此時沒數據,手動添加幾條數據進去再試試吧。async
後臺文章管理包含:文章列表、新增、更新、刪除文章,接下來依次完成這些接口。post
這裏的文章列表和前臺的文章列表差很少,就是多了一個Id,以供編輯和刪除使用,因此能夠新建一個模型類QueryPostForAdminDto
繼承QueryPostDto
,添加PostBriefForAdminDto
繼承PostBriefDto
同時新增一個字段主鍵Id。學習
在QueryPostForAdminDto
中隱藏基類成員Posts
,使用新的接收類型:IEnumerable<PostBriefForAdminDto>
。
//PostBriefForAdminDto.cs namespace Meowv.Blog.Application.Contracts.Blog { public class PostBriefForAdminDto : PostBriefDto { /// <summary> /// 主鍵 /// </summary> public int Id { get; set; } } }
//QueryPostForAdminDto.cs using System.Collections.Generic; namespace Meowv.Blog.Application.Contracts.Blog { public class QueryPostForAdminDto : QueryPostDto { /// <summary> /// Posts /// </summary> public new IEnumerable<PostBriefForAdminDto> Posts { get; set; } } }
添加分頁查詢文章列表的接口:QueryPostsForAdminAsync()
,關於後臺的一些接口就不添加緩存了。
//IBlogService.Admin.cs using Meowv.Blog.Application.Contracts; using Meowv.Blog.Application.Contracts.Blog; using Meowv.Blog.ToolKits.Base; using System.Threading.Tasks; namespace Meowv.Blog.Application.Blog { public partial interface IBlogService { /// <summary> /// 分頁查詢文章列表 /// </summary> /// <param name="input"></param> /// <returns></returns> Task<ServiceResult<PagedList<QueryPostForAdminDto>>> QueryPostsForAdminAsync(PagingInput input); } }
而後實現這個接口。
//BlogService.Admin.cs using Meowv.Blog.Application.Contracts; using Meowv.Blog.Application.Contracts.Blog; using Meowv.Blog.ToolKits.Base; using Meowv.Blog.ToolKits.Extensions; using System.Linq; using System.Threading.Tasks; namespace Meowv.Blog.Application.Blog.Impl { public partial class BlogService { /// <summary> /// 分頁查詢文章列表 /// </summary> /// <param name="input"></param> /// <returns></returns> public async Task<ServiceResult<PagedList<QueryPostForAdminDto>>> QueryPostsForAdminAsync(PagingInput input) { var result = new ServiceResult<PagedList<QueryPostForAdminDto>>(); var count = await _postRepository.GetCountAsync(); var list = _postRepository.OrderByDescending(x => x.CreationTime) .PageByIndex(input.Page, input.Limit) .Select(x => new PostBriefForAdminDto { Id = x.Id, Title = x.Title, Url = x.Url, Year = x.CreationTime.Year, CreationTime = x.CreationTime.TryToDateTime() }) .GroupBy(x => x.Year) .Select(x => new QueryPostForAdminDto { Year = x.Key, Posts = x.ToList() }).ToList(); result.IsSuccess(new PagedList<QueryPostForAdminDto>(count.TryToInt(), list)); return result; } } }
實現邏輯也很是簡單和以前同樣,就是在Select
的時候多了一個Id
,添加一個新的Controller:BlogController.Admin.cs
,添加這個接口。
//BlogController.Admin.cs using Meowv.Blog.Application.Contracts; using Meowv.Blog.Application.Contracts.Blog; using Meowv.Blog.ToolKits.Base; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System.Threading.Tasks; using static Meowv.Blog.Domain.Shared.MeowvBlogConsts; namespace Meowv.Blog.HttpApi.Controllers { public partial class BlogController { /// <summary> /// 分頁查詢文章列表 /// </summary> /// <param name="input"></param> /// <returns></returns> [HttpGet] [Authorize] [Route("admin/posts")] [ApiExplorerSettings(GroupName = Grouping.GroupName_v2)] public async Task<ServiceResult<PagedList<QueryPostForAdminDto>>> QueryPostsForAdminAsync([FromQuery] PagingInput input) { return await _blogService.QueryPostsForAdminAsync(input); } } }
由於是後臺的接口,因此加上AuthorizeAttribute
,指定接口組爲GroupName_v2
,參數方式爲[FromQuery]
。
當沒有進行受權的時候,是沒法訪問接口的。
在作新增文章的時候要注意幾點,不是單純的添加文章數據就結束了,要指定文章分類,添加文章的標籤。添加標籤我這裏是從標籤庫中去取得數據,只存標籤Id,因此添加標籤的時候就可能存在添加了標籤庫中已有的標籤。
新建一個新增和更新文章的通用輸出參數模型類,起名:EditPostInput
,繼承PostDto
,而後添加標籤Tags字段,返回類型IEnumerable<string>
。
//EditPostInput.cs using System.Collections.Generic; namespace Meowv.Blog.Application.Contracts.Blog.Params { public class EditPostInput : PostDto { /// <summary> /// 標籤列表 /// </summary> public IEnumerable<string> Tags { get; set; } } }
添加新增文章的接口:InsertPostAsync
。
/// <summary> /// 新增文章 /// </summary> /// <param name="input"></param> /// <returns></returns> Task<ServiceResult> InsertPostAsync(EditPostInput input);
而後去實現這個接口,實現以前,配置AutoMapper實體映射。
CreateMap<EditPostInput, Post>().ForMember(x => x.Id, opt => opt.Ignore());
將EditPostInput
轉換爲Post
,而且忽略Id
字段。
/// <summary> /// 新增文章 /// </summary> /// <param name="input"></param> /// <returns></returns> public async Task<ServiceResult> InsertPostAsync(EditPostInput input) { var result = new ServiceResult(); var post = ObjectMapper.Map<EditPostInput, Post>(input); post.Url = $"{post.CreationTime.ToString(" yyyy MM dd ").Replace(" ", "/")}{post.Url}/"; await _postRepository.InsertAsync(post); var tags = await _tagRepository.GetListAsync(); var newTags = input.Tags .Where(item => !tags.Any(x => x.TagName.Equals(item))) .Select(item => new Tag { TagName = item, DisplayName = item }); await _tagRepository.BulkInsertAsync(newTags); var postTags = input.Tags.Select(item => new PostTag { PostId = post.Id, TagId = _tagRepository.FirstOrDefault(x => x.TagName == item).Id }); await _postTagRepository.BulkInsertAsync(postTags); result.IsSuccess(ResponseText.INSERT_SUCCESS); return result; }
URL字段,根據建立時間按照yyyy/MM/dd/name/
格式拼接。
而後找出是否有新標籤,有的話批量添加至標籤表。
再根據input.Tags
構建PostTag
列表,也進行批量保存,這樣纔算是新增好一篇文章,最後輸出ResponseText.INSERT_SUCCESS
常量,提示成功。
在BlogController.Admin.cs
添加API。
/// <summary> /// 新增文章 /// </summary> /// <param name="input"></param> /// <returns></returns> [HttpPost] [Authorize] [Route("post")] [ApiExplorerSettings(GroupName = Grouping.GroupName_v2)] public async Task<ServiceResult> InsertPostAsync([FromBody] EditPostInput input) { return await _blogService.InsertPostAsync(input); }
更新操做和新增操做輸入參數同樣,只新增一個Id用來標識更新那篇文章,添加UpdatePostAsync
更新文章接口。
/// <summary> /// 更新文章 /// </summary> /// <param name="id"></param> /// <param name="input"></param> /// <returns></returns> Task<ServiceResult> UpdatePostAsync(int id, EditPostInput input);
一樣的實現這個接口。
/// <summary> /// 更新文章 /// </summary> /// <param name="id"></param> /// <param name="input"></param> /// <returns></returns> public async Task<ServiceResult> UpdatePostAsync(int id, EditPostInput input) { var result = new ServiceResult(); var post = await _postRepository.GetAsync(id); post.Title = input.Title; post.Author = input.Author; post.Url = $"{input.CreationTime.ToString(" yyyy MM dd ").Replace(" ", "/")}{input.Url}/"; post.Html = input.Html; post.Markdown = input.Markdown; post.CreationTime = input.CreationTime; post.CategoryId = input.CategoryId; await _postRepository.UpdateAsync(post); var tags = await _tagRepository.GetListAsync(); var oldPostTags = 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 { post_tags.Id, tag.TagName }; var removedIds = oldPostTags.Where(item => !input.Tags.Any(x => x == item.TagName) && tags.Any(t => t.TagName == item.TagName)) .Select(item => item.Id); await _postTagRepository.DeleteAsync(x => removedIds.Contains(x.Id)); var newTags = input.Tags .Where(item => !tags.Any(x => x.TagName == item)) .Select(item => new Tag { TagName = item, DisplayName = item }); await _tagRepository.BulkInsertAsync(newTags); var postTags = input.Tags .Where(item => !oldPostTags.Any(x => x.TagName == item)) .Select(item => new PostTag { PostId = id, TagId = _tagRepository.FirstOrDefault(x => x.TagName == item).Id }); await _postTagRepository.BulkInsertAsync(postTags); result.IsSuccess(ResponseText.UPDATE_SUCCESS); return result; }
ResponseText.UPDATE_SUCCESS
是常量更新成功。
先根據Id查詢到數據庫中的這篇文章數據,而後根據input參數,修改須要修改的數據,最後保存。
注意的是,若是修改的時候修改了標籤,有可能新增也有可能刪除,也許會又有新增又有刪除。
這時候就須要注意,這裏作了一個比較通用的方法,找到數據庫中當前文章Id的全部Tags,而後根據參數input.Tags
能夠找出被刪掉的標籤的PostTags的Id,調用刪除方法刪掉便可,同時也能夠獲取到新增的標籤,批量進行保存。
完成上面操做後,才保存新加標籤與文章對應的數據,最後提示更新成功,在BlogController.Admin
添加API。
/// <summary> /// 更新文章 /// </summary> /// <param name="id"></param> /// <param name="input"></param> /// <returns></returns> [HttpPut] [Authorize] [Route("post")] [ApiExplorerSettings(GroupName = Grouping.GroupName_v2)] public async Task<ServiceResult> UpdatePostAsync([Required] int id, [FromBody] EditPostInput input) { return await _blogService.UpdatePostAsync(id, input); }
[HttpPut]
指定請求方式爲put
請求,通常須要修改用put,添加用post。
[Required]
指定參數id必填且是FromQuery的方式,input爲[FromBody]
。
更新一下上面新增的數據試試。
刪除相對來講就很是簡單了,通常刪除都會作邏輯刪除,就是避免某些手殘刪除了,有找回的餘地,咱們這裏就直接Delete了,也沒什麼重要數據。
添加接口:DeletePostAsync
。
/// <summary> /// 刪除文章 /// </summary> /// <param name="id"></param> /// <returns></returns> Task<ServiceResult> DeletePostAsync(int id);
實現接口。
/// <summary> /// 刪除文章 /// </summary> /// <param name="id"></param> /// <returns></returns> public async Task<ServiceResult> DeletePostAsync(int id) { var result = new ServiceResult(); var post = await _postRepository.GetAsync(id); if (null == post) { result.IsFailed(ResponseText.WHAT_NOT_EXIST.FormatWith("Id", id)); return result; } await _postRepository.DeleteAsync(id); await _postTagRepository.DeleteAsync(x => x.PostId == id); result.IsSuccess(ResponseText.DELETE_SUCCESS); return result; }
刪除的時候一樣去查詢一下數據,來判斷是否存在。
ResponseText.DELETE_SUCCESS
是添加的常量刪除成功,刪除成功同時也要將post_tags表的標籤對應關係也幹掉纔算完整,在BlogController.Admin添加API。
/// <summary> /// 刪除文章 /// </summary> /// <param name="id"></param> /// <returns></returns> [HttpDelete] [Authorize] [Route("post")] [ApiExplorerSettings(GroupName = Grouping.GroupName_v2)] public async Task<ServiceResult> DeletePostAsync([Required] int id) { return await _blogService.DeletePostAsync(id); }
[HttpDelete]
指定請求方式是刪除資源,[Required]
指定參數Id必填。
刪掉上面添加的文章看看效果。
至此,完成了博客文章的增刪改接口,未完待續...
開源地址:https://github.com/Meowv/Blog/tree/blog_tutorial
搭配下方課程學習更佳 ↓ ↓ ↓