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

系列文章

  1. 使用 abp cli 搭建项目

  2. 给项目瘦身,让它跑起来

  3. 完善与美化,Swagger登场

  4. 数据访问和代码优先

  5. 自定义仓储之增删改查

  6. 统一规范API,包装返回模型

  7. 再说Swagger,分组、描述、小绿锁

  8. 接入GitHub,用JWT保护你的API

  9. 异常处理和日志记录

  10. 使用Redis缓存数据

  11. 集成Hangfire实现定时任务处理

  12. 用AutoMapper搞定对象映射

  13. 定时任务最佳实战(一)

  14. 定时任务最佳实战(二)

  15. 定时任务最佳实战(三)

准备工作

现在博客数据库中的数据是比较混乱的,为了看起来像那么回事,显得正式一点,我先手动搞点数据进去。

搞定了种子数据,就可以去愉快的写接口了,我这里将根据我现在的博客页面去分析所需要接口,感兴趣的去点点。

为了让接口看起来清晰,一目了然,删掉之前在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.csIBlogService.Category.csIBlogService.Tag.csIBlogService.FriendLink.csIBlogService.Admin.cs

在Blog/Impl文件夹下添加实现类:IBlogService.Post.csBlogService.Category.csBlogService.Tag.csBlogService.FriendLink.csBlogService.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);
}

ServiceResultPagedList是之前添加的统一返回模型,紧接着就去添加一个分页查询文章列表缓存接口,和上面是对应的。

//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; }}
}

同时添加CategoryDtoTagDtoPostForPagedDto

//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.Idwhere 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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/309467.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

MySql轻松入门系列——第一站 从源码角度轻松认识mysql整体框架图

一&#xff1a;背景1. 讲故事最近看各大技术社区&#xff0c;不管是知乎&#xff0c;掘金&#xff0c;博客园&#xff0c;csdn基本上看不到有小伙伴分享sqlserver类的文章&#xff0c;看样子这些年sqlserver没落了&#xff0c;已经后继无人了&#xff0c;再写sqlserver是不可能…

嫌弃俄罗斯的火箭报价太黑!马斯克自己造火箭!SpaceX首次载人发射任务成功!太牛了!...

当你仰望天空&#xff0c;可曾想象到&#xff0c;距地8公里的平流层每分钟有65架飞机在天空穿梭&#xff0c;距地20公里有太阳能激光通信无人机展翅翱翔、高空通信热气球悠闲的漂荡&#xff0c;再往上有世界各大企业的低轨宽带卫星&#xff0c;在往上是各国的若干低轨道、中轨道…

ASP.NET Core 3.x API版本控制

前言一般来说需要更改我们API的时候才考虑版本控制&#xff0c;但是我觉得我们不应该等到那时候来实现它&#xff0c;我们应该有一个版本策略从我们应用程序开发时就开始制定好我们的策略&#xff0c;我们一直遵循着这个策略进行开发。我们其实可以通过多种方式进行实现我们API…

[JavaWeb-HTML]HTML标签_文本标签_练习

案列效果: 文本素材: "中关村黑马程序员训练营"是由传智播客联合中关村软件园、CSDN&#xff0c; 并委托传智播客进行教学实施的软件开发高端培训机构&#xff0c;致力于服务各大软件企业&#xff0c;解决当前软件开发技术飞速发展&#xff0c; 而企业招不到优秀人才…

在 WSL2.0 的 Ubuntu 18 里使用 Docker

近日&#xff0c;随着Windows 10 2004版本的发布&#xff0c;WSL 2经过了近一年的insider测试&#xff0c;现在也正式上线了。Windows 10 2004中引入了一个真实的Linux kernel&#xff0c;使得系统全部的系统调用更加兼容。这也是首次&#xff0c;Linux kernel安装在Windows系统…

基本程序单元Activity—Activity生命周期之数据传递小程序

一、 实验目的 &#xff08;1&#xff09; 掌握Andriod Studio的基本使用方法&#xff1b; &#xff08;2&#xff09; 掌握Andriod Studio中常用的控件及其使用方法&#xff1b; 二、 实验内容 题目&#xff1a; 编写一个数据传递的小程序&#xff0c;要求在第一个界面输入…

Asp.Net Core+Dapper开发直播平台!

现在直播大热&#xff0c;从游戏直播到直播带货&#xff0c;这几年都是最热门的了。教育直播、视频会议、云点播等各种基于直播构建的业务模式&#xff0c;让众多企业也都开始配备自己的直播平台。14年在公司带队做了个游戏直播平台&#xff0c;疫情期间在家重构了下项目&#…

Magicodes.IE 在100万数据量下导入导出性能测试

原文作者&#xff1a;HueiFeng前言目前Magicodes.IE更新到了2.2.3&#xff0c;感谢大家的支持&#xff0c;同时建议大家在使用过程中如果遇到一些问题或者说需要一些额外的功能可以直接提issues&#xff0c;当然更建议大家提PR。‍近期更新2020.05.24 【Nuget】版本更新到2.2.2…

[JavaWeb-HTML]HTML文本标签

文本标签&#xff1a;和文本有关的标签 * 注释&#xff1a;<!-- 注释内容 -->* <h1> to <h6>&#xff1a;标题标签* h1~h6:字体大小逐渐递减* <p>&#xff1a;段落标签* <br>&#xff1a;换行标签* <hr>&#xff1a;展示一条水平线* 属性&…

我的『MVP.Blazor』快速创建与部署

‍最近一直在录Blog.Core相关的操作视频&#xff0c;也没有研究过什么新的东西&#xff0c;公司也各种项目迭代&#xff0c;特别是从Fwk迁移到NetCore&#xff0c;真的是不是一个容易的事&#xff0c;闲的时候&#xff0c;为了歇歇脑子&#xff0c;就抽出时间简单看了看又有哪些…

.NET IDE Rider公布2020.2路线图

跨平台 .NET IDE Rider 近日公布了 2020.2 的路线图&#xff0c;介绍了目前正在开发的一些特性&#xff0c;并表示其中一些可能在接下来的版本中出现。主要包括&#xff1a;Windows 上的 .NET Core 后端&#xff1a;Rider 2020.1 已在 macOS 和 Linux 上的 .NET Core 上运行 Re…

字符串太占内存了,我想了各种奇思淫巧对它进行压缩

一&#xff1a;背景1. 讲故事在我们的一个全内存项目中&#xff0c;需要将一家大品牌店铺小千万的trade灌入到内存中&#xff0c;大家知道trade中一般会有订单来源,省市区 &#xff0c;当把这些字段灌进去后&#xff0c;你会发现他们特别侵蚀内存&#xff0c;因为都是字符串类型…

[JavaWeb-HTML]HTML标签_表格标签

表格标签&#xff1a; * table&#xff1a;定义表格* width&#xff1a;宽度* border&#xff1a;边框* cellpadding&#xff1a;定义内容和单元格的距离* cellspacing&#xff1a;定义单元格之间的距离。如果指定为0&#xff0c;则单元格的线会合为一条、* bgcolor&#xff1a…

玩转二叉树 (25 分) 知中序遍历和前序遍历,求做个镜面反转后的层序遍历

题目&#xff1a; 给定一棵二叉树的中序遍历和前序遍历&#xff0c;请你先将树做个镜面反转&#xff0c;再输出反转后的层序遍历的序列。所谓镜面反转&#xff0c;是指将所有非叶结点的左右孩子对换。这里假设键值都是互不相等的正整数。 输入格式&#xff1a; 输入第一行给…

使用dotnet Cli向nuget发布包

长话短说&#xff0c; 今天分享如何在nuget.org创建并发布.NET Standard package。前置安装勾选.NET Core开发套件的Visual Studio; 安装dotnet Cli从VS2017开始&#xff0c;dotnet Cli已经自动在.NET开发套件中被安装&#xff1b;使用SDK-style format&#xff08;SDK属性&…

STL中vector建立最大堆和最小堆

1.堆的概念&#xff1a; 堆是一种非线性结构&#xff0c;可以把堆看作一个数组&#xff0c;也可以被看作一个完全二叉树&#xff0c;通俗来讲堆其实就是利用完全二叉树的结构来维护的一维数组按照堆的特点可以把堆分为大顶堆和小顶堆 大顶堆&#xff1a;每个结点的值都大于或…

结合 AOP 轻松处理事件发布处理日志

结合 AOP 轻松处理事件发布处理日志Intro前段时间&#xff0c;实现了 EventBus 以及 EventQueue 基于 Event 的事件处理&#xff0c;但是没有做日志&#xff08;EventLog&#xff09;相关的部分&#xff0c;原本想增加两个接口&#xff0c; 处理事件发布日志和事件处理日志&…

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

系列文章使用 abp cli 搭建项目给项目瘦身&#xff0c;让它跑起来完善与美化&#xff0c;Swagger登场数据访问和代码优先自定义仓储之增删改查统一规范API&#xff0c;包装返回模型再说Swagger&#xff0c;分组、描述、小绿锁接入GitHub&#xff0c;用JWT保护你的API异常处理和…

完全卸载软件及电脑软件残留

当我在控制面板的卸载软件里没有发现我要删软件的软件时&#xff0c;只找到了软件的部分安装目录删除&#xff0c;就会发生残留问题&#xff0c;导致软件依旧可以运行。 经过这么多次后&#xff0c;我就找到了我自认为的最优解。首先运行这个软件&#xff1b;之后打开windows任…