系列文章

基于 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 开发博客项目 - 博客接口实战篇(二)

基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(三)

基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(四)

基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(五)

基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(一)

基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(二)

基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(三)

基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(四)

基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(五)

基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(六)

基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(七)

基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(八)

基于 abp vNext 和 .NET Core 开发博客项目 - Blazor 实战系列(九)

基于 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

{

///

/// 年份

///

public int Year { get; set; }

///

/// Posts

///

public IEnumerable Posts { get; set; }

}

}

模型为一个年份和一个文章列表,文章列表模型:PostBriefDto.cs。

//PostBriefDto.cs

namespace Meowv.Blog.Application.Contracts.Blog

{

public class PostBriefDto

{

///

/// 标题

///

public string Title { get; set; }

///

/// 链接

///

public string Url { get; set; }

///

/// 年份

///

public int Year { get; set; }

///

/// 创建时间

///

public string CreationTime { get; set; }

}

}

搞定,因为返回时间为英文格式,所以CreationTime 给了字符串类型。

在IBlogService.Post.cs中添加接口分页查询文章列表QueryPostsAsync,肯定需要接受俩参数分页页码和分页数量。还是去添加一个公共模型PagingInput吧,在.Application.Contracts下面。

//PagingInput.cs

using System.ComponentModel.DataAnnotations;

namespace Meowv.Blog.Application.Contracts

{

///

/// 分页输入参数

///

public class PagingInput

{

///

/// 页码

///

[Range(1, int.MaxValue)]

public int Page { get; set; } = 1;

///

/// 限制条数

///

[Range(10, 30)]

public int Limit { get; set; } = 10;

}

}

Page设置默认值为1,Limit设置默认值为10,Range Attribute设置参数可输入大小限制,于是这个分页查询文章列表的接口就是这个样子的。

//IBlogService.Post.cs

public partial interface IBlogService

{

///

/// 分页查询文章列表

///

///

///

Task>> 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

{

///

/// 分页查询文章列表

///

///

///

///

Task>> QueryPostsAsync(PagingInput input, Func>>> factory);

}

}

分别实现这两个接口。

//BlogCacheService.Post.cs

public partial class BlogCacheService

{

private const string KEY_QueryPosts = "Blog:Post:QueryPosts-{0}-{1}";

///

/// 分页查询文章列表

///

///

///

///

public async Task>> QueryPostsAsync(PagingInput input, Func>>> factory)

{

return await Cache.GetOrAddAsync(KEY_QueryPosts.FormatWith(input.Page, input.Limit), factory, CacheStrategy.ONE_DAY);

}

}

//BlogService.Post.cs

///

/// 分页查询文章列表

///

///

///

public async Task>> QueryPostsAsync(PagingInput input)

{

return await _blogCacheService.QueryPostsAsync(input, async () =>

{

var result = new ServiceResult>();

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(count.TryToInt(), list));

return result;

});

}

PageByIndex(...)、TryToDateTime()是.ToolKits层添加的扩展方法,先查询总数,然后根据时间倒序,分页,筛选出所需字段,根据年份分组,输出,结束。

在BlogController中添加API。

///

/// 分页查询文章列表

///

///

///

[HttpGet]

[Route("posts")]

public async Task>> 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

{

///

/// 标题

///

public string Title { get; set; }

///

/// 作者

///

public string Author { get; set; }

///

/// 链接

///

public string Url { get; set; }

///

/// HTML

///

public string Html { get; set; }

///

/// Markdown

///

public string Markdown { get; set; }

///

/// 创建时间

///

public string CreationTime { get; set; }

///

/// 分类

///

public CategoryDto Category { get; set; }

///

/// 标签列表

///

public IEnumerable Tags { get; set; }

///

/// 上一篇

///

public PostForPagedDto Previous { get; set; }

///

/// 下一篇

///

public PostForPagedDto Next { get; set; }

}

}

同时添加CategoryDto、TagDto、PostForPagedDto。

//CategoryDto.cs

namespace Meowv.Blog.Application.Contracts.Blog

{

public class CategoryDto

{

///

/// 分类名称

///

public string CategoryName { get; set; }

///

/// 展示名称

///

public string DisplayName { get; set; }

}

}

//TagDto.cs

namespace Meowv.Blog.Application.Contracts.Blog

{

public class TagDto

{

///

/// 标签名称

///

public string TagName { get; set; }

///

/// 展示名称

///

public string DisplayName { get; set; }

}

}

//PostForPagedDto.cs

namespace Meowv.Blog.Application.Contracts.Blog

{

public class PostForPagedDto

{

///

/// 标题

///

public string Title { get; set; }

///

/// 链接

///

public string Url { get; set; }

}

}

添加获取文章详情接口和缓存的接口。

//IBlogService.Post.cs

public partial interface IBlogService

{

///

/// 根据URL获取文章详情

///

///

///

Task> GetPostDetailAsync(string url);

}

//IBlogCacheService.Post.cs

public partial interface IBlogCacheService

{

///

/// 根据URL获取文章详情

///

///

///

Task> GetPostDetailAsync(string url, Func>> factory);

}

分别实现这两个接口。

//BlogCacheService.Post.cs

public partial class BlogCacheService

{

private const string KEY_GetPostDetail = "Blog:Post:GetPostDetail-{0}";

///

/// 根据URL获取文章详情

///

///

///

///

public async Task> GetPostDetailAsync(string url, Func>> factory)

{

return await Cache.GetOrAddAsync(KEY_GetPostDetail.FormatWith(url), factory, CacheStrategy.ONE_DAY);

}

}

//BlogService.Post.cs

///

/// 根据URL获取文章详情

///

///

///

public async Task> GetPostDetailAsync(string url)

{

return await _blogCacheService.GetPostDetailAsync(url, async () =>

{

var result = new ServiceResult();

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。

///

/// 根据URL获取文章详情

///

///

///

[HttpGet]

[Route("post")]

public async Task> GetPostDetailAsync(string url)

{

return await _blogService.GetPostDetailAsync(url);

}

编译运行,然后输入URL查询一条文章详情数据。

成功输出预期内容,缓存同时也是ok的。

开源地址:https://github.com/Meowv/Blog/tree/blog_tutorial

搭配下方课程学习更佳 ↓ ↓ ↓

http://gk.link/a/10iQ7

查看原文