基於 abp vNext 和 .NET Core 開發博客項目 - 博客介面實戰篇(一)

来源:https://www.cnblogs.com/meowv/archive/2020/06/02/12987623.html
-Advertisement-
Play Games

系列文章 基於 abp vNext 和 .NET Core 開發博客項目 - 使用 abp cli 搭建項目 基於 abp vNext 和 .NET Core 開發博客項目 - 給項目瘦身,讓它跑起來 基於 abp vNext 和 .NET Core 開發博客項目 - 完善與美化,Swagger登場 ...


系列文章

  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,還是咬牙堅持吧。

準備工作

現在博客資料庫中的數據是比較混亂的,為了看起來像那麼回事,顯得正式一點,我先手動搞點數據進去。

0

搞定了種子數據,就可以去愉快的寫介面了,我這裡將根據我現在的博客頁面去分析所需要介面,感興趣的去點點。

為了讓介面看起來清晰,一目瞭然,刪掉之前在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,最終效果圖如下:

1

文章列表頁

2

分析:列錶帶分頁,以文章發表的年份分組,所需欄位:標題、鏈接、時間、年份。

.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進行查詢參數,編譯運行看效果。

4

已經可以查詢出數據,並且緩存至Redis中。

獲取文章詳情

5

分析:文章詳情頁,文章的標題、作者、發佈時間、所屬分類、標簽列表、文章內容(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.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查詢一條文章詳情數據。

6

成功輸出預期內容,緩存同時也是ok的。

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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前言 使用C#實現一個二叉樹及其基本操作, 配合xunit來做單元測試; 所以數據結構的定義和演算法均使用C#實現; 概念 二叉樹或為空樹, 或是由一個根結點加上兩棵分別稱為左子樹和右子樹的、互不交的二叉樹組成; 二叉樹的遍歷 二叉樹遍歷的遞歸演算法比較簡潔, 思路比較清晰, 但是非遞歸的版本, 個人覺 ...
  • 前言 一般來說需要更改我們API的時候才考慮版本控制,但是我覺得我們不應該等到那時候來實現它,我們應該有一個版本策略從我們應用程式開發時就開始制定好我們的策略,我們一直遵循著這個策略進行開發。 我們其實可以通過多種方式進行實現我們API版本的控制,其實對於版本控制沒有最好的方式,這完全取決於我們面向 ...
  • using System; /*********************************************************************************** * 創建人: * 創建時間: * 功能描述: * * 修改人: * 修改時間: * 功能描述: *** ...
  • C#有兩種類型:值類型和引用類型。值類型的變數直接包含數據,引用類型的變數則包含對變數(對象)的引用。對於引用類型,兩個變數可以引用同一個對象,所以,對一個變數的操作可能會影響另一個變數引用的對象。而對於值類型,每個變數都有自己的數據副本,因此,對一個變數的操作不會影響到另一個變數(ref和out參 ...
  • 函數簽名 假設有一個函數 f,輸入一個 int 值,返回一個 string 值,在 C# 中,該函數可以表示為: Func<int, string> 我們可以使用箭頭(→)符號來表示: f : int → string 箭頭符號是函數式編程社區的標準函數符號,與語言無關。 當沒有輸入或沒有輸出(vo ...
  • Code Snippet功能:敲幾個關鍵字,按兩下Tab鍵,插入設好的代碼片段。我們可以在代碼片段管理里導入自己需要的代碼片段。 敲寫“review”,按兩下Tab鍵,自動出來編寫好的代碼片段。 在編輯->InteliSence->插入片段,可以選擇各種不同的片段插入。 ...
  • Named pipe (more flexible)Allows two-way communication between arbitrary processes on the same computer or different computers across a network.A pipe ...
  • BackWork代碼: using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Utils { publ ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...