介紹

不好意思這篇文章應該早點更新的,這幾天在忙CICD的東西沒顧得上,等後面整好了CICD我也發2篇文章講講,咱們進入正題,這一章來補全剩下的 2個介面和將文章聚合進行完善。

開工

上一章大部分業務都完成了,這一章專門講刪除和修改,首先是刪除,文章被刪除評論肯定也要同步被刪掉掉,另外評論因為也會存在子集所以也要同步刪除。

業務介面

首先根據上面的分析建立評論自定義倉儲介面。

    public interface ICommentRepository : IBasicRepository<Comment, Guid>
{
Task DeleteOfPost(Guid id, CancellationToken cancellationToken = default);
} public interface ITagRepository : IBasicRepository<Tag, Guid>
{
Task<List<Tag>> GetListAsync(Guid blogId, CancellationToken cancellationToken = default); Task<Tag> FindByNameAsync(Guid blogId, string name, CancellationToken cancellationToken = default); Task<List<Tag>> GetListAsync(IEnumerable<Guid> ids, CancellationToken cancellationToken = default); // 新加入的
Task DecreaseUsageCountOfTagsAsync(List<Guid> id, CancellationToken cancellationToken = default);
}

完成刪除業務介面


public async Task DeleteAsync(Guid id)
{
// 查詢文章
var post = await _postRepository.GetAsync(id);
// 根據文章獲取Tags
var tags = await GetTagsOfPost(id);
// 減少Tag引用數量
await _tagRepository.DecreaseUsageCountOfTagsAsync(tags.Select(t => t.Id).ToList());
// 刪除評論
await _commentRepository.DeleteOfPost(id);
// 刪除文章
await _postRepository.DeleteAsync(id); }

上面的刪除介面完成後,就剩下修改介面了。

        public async Task<PostWithDetailsDto> UpdateAsync(Guid id, UpdatePostDto input)
{
var post = await _postRepository.GetAsync(id); input.Url = await RenameUrlIfItAlreadyExistAsync(input.BlogId, input.Url, post); post.SetTitle(input.Title);
post.SetUrl(input.Url);
post.Content = input.Content;
post.Description = input.Description;
post.CoverImage = input.CoverImage; post = await _postRepository.UpdateAsync(post); var tagList = SplitTags(input.Tags);
await SaveTags(tagList, post); return ObjectMapper.Map<Post, PostWithDetailsDto>(post);
}

補充

文章整體業務完成了,現在需要把其他使用到的倉儲介面實現補全一下了。

    public class EfCoreBlogRepository : EfCoreRepository<CoreDbContext, Blog, Guid>, IBlogRepository
{
public EfCoreBlogRepository(IDbContextProvider<CoreDbContext> dbContextProvider)
: base(dbContextProvider)
{ } public async Task<Blog> FindByShortNameAsync(string shortName, CancellationToken cancellationToken = default)
{
return await (await GetDbSetAsync()).FirstOrDefaultAsync(p => p.ShortName == shortName, GetCancellationToken(cancellationToken));
}
} public class EfCoreTagRepository : EfCoreRepository<CoreDbContext, Tag, Guid>, ITagRepository
{
public EfCoreTagRepository(IDbContextProvider<CoreDbContext> dbContextProvider) : base(dbContextProvider)
{
} public async Task<List<Tag>> GetListAsync(Guid blogId, CancellationToken cancellationToken = default)
{
return await (await GetDbSetAsync()).Where(t => t.BlogId == blogId).ToListAsync(GetCancellationToken(cancellationToken));
} public async Task<Tag> GetByNameAsync(Guid blogId, string name, CancellationToken cancellationToken = default)
{
return await (await GetDbSetAsync()).FirstAsync(t => t.BlogId == blogId && t.Name == name, GetCancellationToken(cancellationToken));
} public async Task<Tag> FindByNameAsync(Guid blogId, string name, CancellationToken cancellationToken = default)
{
return await (await GetDbSetAsync()).FirstOrDefaultAsync(t => t.BlogId == blogId && t.Name == name, GetCancellationToken(cancellationToken));
} public async Task DecreaseUsageCountOfTagsAsync(List<Guid> ids, CancellationToken cancellationToken = default)
{
var tags = await (await GetDbSetAsync())
.Where(t => ids.Any(id => id == t.Id))
.ToListAsync(GetCancellationToken(cancellationToken)); foreach (var tag in tags)
{
tag.DecreaseUsageCount();
}
}
}

快取與事件

GetTimeOrderedListAsync的文章列表資料用快取處理。

快取用法可以直接參照官方文件:https://docs.abp.io/en/abp/latest/Caching,另外快取如果你開啟了redis就是我們第二章講的那麼資料就會進入redis,如果沒開啟就是記憶體。

ICreationAuditedObject我們會在中級篇進行講解,名字一看就懂建立物件稽核。

    private readonly IDistributedCache<List<PostCacheItem>> _postsCache;

    public async Task<ListResultDto<PostWithDetailsDto>> GetTimeOrderedListAsync(Guid blogId)
{
var postCacheItems = await _postsCache.GetOrAddAsync(
blogId.ToString(),
async () => await GetTimeOrderedPostsAsync(blogId),
() => new DistributedCacheEntryOptions
{
AbsoluteExpiration = DateTimeOffset.Now.AddHours(1)
}
); var postsWithDetails = ObjectMapper.Map<List<PostCacheItem>, List<PostWithDetailsDto>>(postCacheItems); foreach (var post in postsWithDetails)
{
if (post.CreatorId.HasValue)
{
var creatorUser = await UserLookupService.FindByIdAsync(post.CreatorId.Value);
if (creatorUser != null)
{
post.Writer = ObjectMapper.Map<IdentityUser, BlogUserDto>(creatorUser);
}
}
} return new ListResultDto<PostWithDetailsDto>(postsWithDetails); } private async Task<List<PostCacheItem>> GetTimeOrderedPostsAsync(Guid blogId)
{
var posts = await _postRepository.GetOrderedList(blogId); return ObjectMapper.Map<List<Post>, List<PostCacheItem>>(posts);
} [Serializable]
public class PostCacheItem : ICreationAuditedObject
{
public Guid Id { get; set; } public Guid BlogId { get; set; } public string Title { get; set; } public string CoverImage { get; set; } public string Url { get; set; } public string Content { get; set; } public string Description { get; set; } public int ReadCount { get; set; } public int CommentCount { get; set; } public List<Tag> Tags { get; set; } public Guid? CreatorId { get; set; } public DateTime CreationTime { get; set; }
}

我們現在將根據時間排序獲取文章列表介面的文章資料快取了,但是如果文章被刪除或者修改或者建立了新的文章,怎麼辦,這個時候我們快取中的資料是有問題的,我們需要重新整理快取內容。

領域事件使用文件:https://docs.abp.io/en/abp/latest/Local-Event-Bus

引入ILocalEventBus並新增PublishPostChangedEventAsync方法釋出事件,在刪除、新增、修改介面上呼叫釋出事件方法。

PostChangedEvent放在領域層,可以參考第三章的架構講解


private readonly ILocalEventBus _localEventBus; private async Task PublishPostChangedEventAsync(Guid blogId)
{
await _localEventBus.PublishAsync(
new PostChangedEvent
{
BlogId = blogId
});
} public class PostChangedEvent
{
public Guid BlogId { get; set; }
}

事件釋出出去了,誰來處理呢,這個業務是文章的肯定文章自己處理,LocalEvent屬於本地事件和介面屬於同一個事務單元,可以去上面的文件說明。

    public class PostCacheInvalidator : ILocalEventHandler<PostChangedEvent>, ITransientDependency
{
protected IDistributedCache<List<PostCacheItem>> Cache { get; } public PostCacheInvalidator(IDistributedCache<List<PostCacheItem>> cache)
{
Cache = cache;
} public virtual async Task HandleEventAsync(PostChangedEvent post)
{
await Cache.RemoveAsync(post.BlogId.ToString());
}
}

結語

本節知識點:

  • 1.業務開發方式
  • 2.ABP快取的使用
  • 3.領域事件的使用

最麻煩的文章聚合算是講完了,有一個ABP如何做檔案上傳沒講那就是那個文章封面下期再說,還剩下Comment、Tag,另外我說下我這邊寫程式碼暫時不給演示測試效果,大家也是先學ABP用法和DDD理論實踐。

後面單元測試的時候我在把介面一把梭,加油請持續關注。

聯絡作者:加群:867095512 @MrChuJiu