系列文章

  1. 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目
  2. 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来
  3. 基于 abp vNext 和 .NET Core 开发博客项目 - 完善与美化,Swagger登场
  4. 基于 abp vNext 和 .NET Core 开发博客项目 - 数据访问和代码优先
  5. 基于 abp vNext 和 .NET Core 开发博客项目 - 自定义仓储之增删改查
  6. 基于 abp vNext 和 .NET Core 开发博客项目 - 统一规范API,包装返回模型
  7. 基于 abp vNext 和 .NET Core 开发博客项目 - 再说Swagger,分组、描述、小绿锁
  8. 基于 abp vNext 和 .NET Core 开发博客项目 - 接入GitHub,用JWT保护你的API
  9. 基于 abp vNext 和 .NET Core 开发博客项目 - 异常处理和日志记录
  10. 基于 abp vNext 和 .NET Core 开发博客项目 - 使用Redis缓存数据
  11. 基于 abp vNext 和 .NET Core 开发博客项目 - 集成Hangfire实现定时任务处理
  12. 基于 abp vNext 和 .NET Core 开发博客项目 - 用AutoMapper搞定对象映射
  13. 基于 abp vNext 和 .NET Core 开发博客项目 - 定时任务最佳实战(一)
  14. 基于 abp vNext 和 .NET Core 开发博客项目 - 定时任务最佳实战(二)
  15. 基于 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的。

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

基于 abp vNext 和 .NET Core 开发博客项目 - 博客接口实战篇(一)的更多相关文章

  1. 基于 abp vNext 和 .NET Core 开发博客项目 - 定时任务最佳实战(三)

    上一篇(https://www.cnblogs.com/meowv/p/12974439.html)完成了全网各大平台的热点新闻数据的抓取,本篇继续围绕抓取完成后的操作做一个提醒.当每次抓取完数据后, ...

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

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

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

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

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

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

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

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

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

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

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

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

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

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

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

    系列文章 基于 abp vNext 和 .NET Core 开发博客项目 - 使用 abp cli 搭建项目 基于 abp vNext 和 .NET Core 开发博客项目 - 给项目瘦身,让它跑起来 ...

随机推荐

  1. 学习vue第七节,filter过滤器如何的使用

    vue 过滤器如何的使用 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> ...

  2. P2422 良好的感觉(两头单调)

    描述:https://www.luogu.com.cn/problem/P2422 kkk做了一个人体感觉分析器.每一天,人都有一个感受值Ai,Ai越大,表示人感觉越舒适.在一段时间[i, j]内,人 ...

  3. react中this.setState的理解

    this.setState作用? 在react中要修改this.state要使用this.setState,因为this.state只是一个对象,单纯的修改state并不会触发ui更新.所以我们需要用 ...

  4. 201771010113 李婷华《面向对象程序设计(Java)》第十二周总结

    一.理论知识部分 1.Java的抽象口工具箱( Abstract WindowToolkit, AWT)包含在java.awt包中,它提供了许多用来设计GUI的组件类和容器类. 2.AWT库处理用户界 ...

  5. 整型和浮点型与QByteArray的转换

    目录 QByteArray 整型 QByteArray 浮点型 QByteArray QByteArray The QByteArray class provides an array of byte ...

  6. Scala 中 Any 类源码

    package scalaabstract class Any { def equals(that: Any): Boolean //值比较 def hashCode(): Int //hash值 d ...

  7. 【OracleDB】 01 概述和基本操作

    实例概念: Oracle有一个特殊的概念 Oracle数据库 = 数据库 + Oracle文件系统 + Oracle实例 实例处理Oracle的请求,调用文件系统 然后返回结果响应给客户端 单实例和多 ...

  8. vtk学习记录(三)——初识vtkRenderer

    目录 前言 vtkRenderer 引入vtk窗口 小结 前言 一场疫情打乱了好多人的节奏,我也一样,一不留神半年都快过去了,这期间虽说一直在鼓捣东西吧,不过确实是没啥实质性的进展,索性就继续把vtk ...

  9. chmod的用法

    指令名称 : chmod 使用权限 : 所有使用者 使用方式 : chmod [-cfvR] [--help] [--version] mode file... 说明 : Linux/Unix 的档案 ...

  10. linux下安装gmp遇到 configure:error:no usable m4 in$path or /user/5bin解决方案

    安装过程中遇到如下报错: 上面的报错是因为你没有安装m4,安装m4就可以了:以下两种命令人选其一: #yum install m4 或 #apt-get install m4 ps:如果遇到权限问题就 ...