系列文章
- 基于 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 開發博客專案 - 定時任務最佳實戰(三)
- 基于 abp vNext 和 .NET Core 開發博客專案 - 博客介面實戰篇(一)
- 基于 abp vNext 和 .NET Core 開發博客專案 - 博客介面實戰篇(二)
上篇文章完成了分類和標簽頁面相關的共6個介面,本篇繼續來寫博客增刪改查API的業務,
供前端查詢用的介面還剩下一個,這里先補上,
友鏈串列

分析:回傳標題和對應的鏈接即可,傳輸物件FriendLinkDto.cs,
//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; }
}
}
添加查詢友鏈串列介面和快取介面,
//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,
/// <summary>
/// 查詢友鏈串列
/// </summary>
/// <returns></returns>
[HttpGet]
[Route("friendlinks")]
public async Task<ServiceResult<IEnumerable<FriendLinkDto>>> QueryFriendLinksAsync()
{
return await _blogService.QueryFriendLinksAsync();
}
編譯運行,打開查詢友鏈的API,此時沒資料,手動添加幾條資料進去再試試吧,

文章管理

后臺文章管理包含:文章串列、新增、更新、洗掉文章,接下來依次完成這些介面,
文章串列
這里的文章串列和前臺的文章串列差不多,就是多了一個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
搭配下方課程學習更佳 ↓ ↓ ↓
http://gk.link/a/10iQ7

轉載請註明出處,本文鏈接:https://www.uj5u.com/net/17098.html
標籤:.NET Core
