<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      Loading

      基于.NetCore開發博客項目 StarBlog - (22) 開發博客文章相關接口

      前言

      本文介紹博客文章相關接口的開發,作為接口開發介紹的第一篇,會寫得比較詳細,以拋磚引玉,后面的其他接口就粗略帶過了,著重于WebApi開發的周邊設施。

      涉及到的接口:文章CRUD、置頂文章、推薦文章等。

      開始前先介紹下AspNetCore框架的基礎概念,MVC模式(前后端不分離)、WebApi模式(前后端分離),都是有Controller的。

      區別在前者的Controller集成自 Controller 類,后者繼承自 ControllerBase 類。

      無論博客前臺,還是接口,大部分邏輯都是通用的,因此我把這些邏輯封裝在 service 中,以減少冗余代碼。

      文章CRUD

      在之前的文章里,已經實現了文章列表、文章詳情的功能,等于是CRUD里的 R (Retrieve) “查”功能已經實現。

      相關代碼在 StarBlog.Web/Services/PostService.cs 文件中。

      PS:根據RESTFul規范,CRUD不同的操作對應不同的HTTP方法

      在AspNetCore中,可以通過在 Action 上加上 [HttpPost][HttpDelete("{id}")] 這樣的特性來標記接口使用的HTTP方法和URL。

      現在需要實現“增刪改”的功能。

      增和改 (Create/Update)

      因為這倆功能差不多,所以放在一起實現,很多ORM也是把 InsertUpdate 合在一起,即 InsertOrUpdate

      DTO

      在計算機編程中,數據傳輸對象 (data transfer object,DTO)是在2個進程中攜帶數據的對象。因為進程間通信通常用于遠程接口(如web服務)的昂貴操作。成本的主體是客戶和服務器之間的來回通信時間。為降低這種調用次數,使用DTO聚合本來需要多次通信傳輸的數據。

      DAO與業務對象或數據訪問對象的區別是:DTO的數據的變異子與訪問子(mutator和accessor)、語法分析(parser)、序列化(serializer)時不會有任何存儲、獲取、序列化和反序列化的異常。即DTO是簡單對象,不含任何業務邏輯,但可包含序列化和反序列化以用于傳輸數據。

      by Wikipedia

      添加文章只需要 Post 模型的其中幾個屬性就行,不適合把整個 Post 模型作為參數,所以,首先要定義一個DTO作為添加文章的參數。

      文件路徑 StarBlog.Web/ViewModels/Blog/PostCreationDto.cs

      public class PostCreationDto {
          /// <summary>
          /// 標題
          /// </summary>
          public string? Title { get; set; }
      
          /// <summary>
          /// 梗概
          /// </summary>
          public string? Summary { get; set; }
      
          /// <summary>
          /// 內容(markdown格式)
          /// </summary>
          public string? Content { get; set; }
          
          /// <summary>
          /// 分類ID
          /// </summary>
          public int CategoryId { get; set; }
      }
      

      AutoMapper

      有了DTO作為參數,在保存文章的時候,我們需要手動把DTO對象里面的屬性,一個個賦值到 Post 對象上,像這樣:

      var post = new Post {
          Id = Guid.NewGuid(),
      	Title = dto.Title,
          Summary = dto.Summary,
          Content = dto.Content,
          CategoryId = dto.CategoryId
      };
      

      一個倆個還好,接口多了的話,大量重復的代碼會很煩人,而且也容易出錯。

      還好我們可以用AutoMapper組件來實現對象自動映射。

      通過nuget安裝 AutoMapper.Extensions.Microsoft.DependencyInjection 這個包

      注冊服務:

      builder.Services.AddAutoMapper(typeof(Program));
      

      然后再創建對應的Profile(配置),如果沒有特殊配置其實也可以不添加這個配置文件,執行默認的映射行為即可。

      作為例子,本文簡單介紹一下,創建 StarBlog.Web/Properties/AutoMapper/PostProfile.cs 文件

      public class PostProfile : Profile {
          public PostProfile() {
              CreateMap<PostUpdateDto, Post>();
              CreateMap<PostCreationDto, Post>();
          }
      }
      

      在構造方法里執行 CreateMap 配置從左到右的映射關系。

      上面的代碼配置了從 PostUpdateDto / PostCreationDto 這兩個對象到 Post 對象的映射關系。

      如果有些字段不要映射的,可以這樣寫:

      public class PostProfile : Profile {
          private readonly List<string> _unmapped = new List<string> {
              "Categories",
          };
          public PostProfile() {
              CreateMap<PostUpdateDto, Post>();
              CreateMap<PostCreationDto, Post>();
              ShouldMapProperty = property => !_unmapped.Contains(property.Name);
          }
      }
      

      其他代碼不變,修改 _unmapped 這個字段就行。

      接著在 Controller 里注入 IMapper 對象

      private readonly IMapper _mapper;
      

      使用方法很簡單

      var post = _mapper.Map<Post>(dto);
      

      傳入一個 PostCreationDto 類型的 dto,可以得到 Post 對象。

      Controller

      先上Controller的代碼

      [Authorize]
      [ApiController]
      [Route("Api/[controller]")]
      [ApiExplorerSettings(GroupName = "blog")]
      public class BlogPostController : ControllerBase {
          private readonly IMapper _mapper;
          private readonly PostService _postService;
          private readonly BlogService _blogService;
          
          public BlogPostController(PostService postService, BlogService blogService, IMapper mapper) {
              _postService = postService;
              _blogService = blogService;
              _mapper = mapper;
          }
      }
      

      加在Controller上面的四個特性,挨個介紹

      • Authorize 表示這個controller下面的所有接口需要登錄才能訪問
      • ApiController 表示這是個WebApi Controller
      • Route 指定了這個Controller的路由模板,即下面的接口全是以 Api/BlogPostController 開頭
      • ApiExplorerSettings 接口分組,在swagger文檔里看會更清晰

      接下來,添加和修改是倆接口,分開說。

      添加

      很容易,直接上代碼了

      [HttpPost]
      public async Task<ApiResponse<Post>> Add(PostCreationDto dto, [FromServices] CategoryService categoryService) {
          // 使用 AutoMapper,前面介紹過的
          var post = _mapper.Map<Post>(dto);
          // 獲取文章分類,如果不存在就返回報錯信息
          var category = categoryService.GetById(dto.CategoryId);
          if (category == null) return ApiResponse.BadRequest($"分類 {dto.CategoryId} 不存在!");
      
          // 生成文章的ID、創建、更新時間
          post.Id = GuidUtils.GuidTo16String();
          post.CreationTime = DateTime.Now;
          post.LastUpdateTime = DateTime.Now;
          // 設置文章狀態為已發布
          post.IsPublish = true;
      
          // 獲取分類的層級結構
          post.Categories = categoryService.GetCategoryBreadcrumb(category);
      
          return new ApiResponse<Post>(await _postService.InsertOrUpdateAsync(post));
      }
      

      就是這個 Add 方法

      目前 CategoryService 只需要在這個添加的接口里用到,所以不用整個Controller注入,在 Add 方法里使用 [FromServices] 特性注入。

      后面有個獲取分類的層級結構,因為StarBlog的設計是支持多級分類,為了在前臺展示文章分類層級的時候減少運算量,所以我把文章的分類層級結構(形式是分類ID用逗號分隔開,如:1,3,5,7,9)直接存入數據庫,空間換時間。

      最后,執行 PostService 里的 InsertOrUpdateAsync 方法,解析處理文章內容,并將文章存入數據庫。

      PS:本項目的接口返回值已經做統一包裝處理,可以看到大量使用 ApiResponse 作為返回值,這個后續文章會介紹。

      修改

      噢,還有 修改文章(Update) 的接口,修改使用 PUT 方法

      [HttpPut("{id}")]
      public async Task<ApiResponse<Post>> Update(string id, PostUpdateDto dto) {
          // 先獲取文章對象
          var post = _postService.GetById(id);
          if (post == null) return ApiResponse.NotFound($"博客 {id} 不存在");
      
      	// 在已有對象的基礎上進行映射
          post = _mapper.Map(dto, post);
          // 更新修改時間
          post.LastUpdateTime = DateTime.Now;
          
          return new ApiResponse<Post>(await _postService.InsertOrUpdateAsync(post));
      }
      

      依然很簡單,里面注釋寫得很清楚了

      AutoMapper可以對已有對象的基礎上進行映射

      • mapper.Map(source) 得到一個全新的對象
      • mapper.Map(source, dest) 在 dest 對象的基礎上修改

      搞定。

      Service

      作為一個多層架構項目,核心邏輯依然放在 Service 里

      并且這里是添加和修改二合一,優雅~

      public async Task<Post> InsertOrUpdateAsync(Post post) {
          var postId = post.Id;
          // 是新文章的話,先保存到數據庫
          if (await _postRepo.Where(a => a.Id == postId).CountAsync() == 0) {
              post = await _postRepo.InsertAsync(post);
          }
      
          // 檢查文章中的外部圖片,下載并進行替換
          // todo 將外部圖片下載放到異步任務中執行,以免保存文章的時候太慢
          post.Content = await MdExternalUrlDownloadAsync(post);
          // 修改文章時,將markdown中的圖片地址替換成相對路徑再保存
          post.Content = MdImageLinkConvert(post, false);
      
          // 處理完內容再更新一次
          await _postRepo.UpdateAsync(post);
          return post;
      }
      

      另外,這部分代碼在之前的markdown渲染和自動下載外部圖片的相關文章里已經介紹過了,本文不再重復。詳情可以看本系列的第17篇文章。

      刪 (Delete)

      沒什么好說的,直接上代碼

      StarBlog.Web/Services/PostService.cs

      public int Delete(string id) {
          return _postRepo.Delete(a => a.Id == id);
      }
      

      StarBlog.Web/Apis/Blog/BlogPostController.cs

      [HttpDelete("{id}")]
      public ApiResponse Delete(string id) {
          var post = _postService.GetById(id);
          if (post == null) return ApiResponse.NotFound($"博客 {id} 不存在");
          var rows = _postService.Delete(id);
          return ApiResponse.Ok($"刪除了 {rows} 篇博客");
      }
      

      查 (Retrieve)

      查,分成兩種,一種是列表,一種是單個。

      單個

      先說單個的,比較容易。

      StarBlog.Web/Services/PostService.cs

      public Post? GetById(string id) {
          // 獲取文章的時候對markdown中的圖片地址解析,加上完整地址返回給前端
          var post = _postRepo.Where(a => a.Id == id).Include(a => a.Category).First();
          if (post != null) post.Content = MdImageLinkConvert(post, true);
      
          return post;
      }
      

      StarBlog.Web/Apis/Blog/BlogPostController.cs

      [AllowAnonymous]
      [HttpGet("{id}")]
      public ApiResponse<Post> Get(string id) {
          var post = _postService.GetById(id);
          return post == null ? ApiResponse.NotFound() : new ApiResponse<Post>(post);
      }
      

      這里接口加了個 [AllowAnonymous],表示這接口不用登錄也能訪問。

      列表

      最簡單的就是直接返回全部文章列表。

      [HttpGet]
      public List<Post> GetAll() {
          return _postService.GetAll();
      }
      

      完整功能比較復雜,需要過濾篩選、排序、分頁等功能,這些功能在之前第6篇已經介紹過了。詳見:基于.NetCore開發博客項目 StarBlog - (6) 頁面開發之博客文章列表

      文章的相關操作

      單純的CRUD是無法滿足功能需求的

      所以要在RESTFul接口的接觸上,配合一些RPC風格接口,實現我們需要的功能。

      設置推薦文章

      有一個模型專門管理推薦文章,名為 FeaturedPost

      要設置推薦文章,直接往里面添加數據就行了。反之,取消就是刪除對應的記錄。

      上代碼

      StarBlog.Web/Services/PostService.cs

      public FeaturedPost AddFeaturedPost(Post post) {
          var item = _fPostRepo.Where(a => a.PostId == post.Id).First();
          if (item != null) return item;
          item = new FeaturedPost {PostId = post.Id};
          _fPostRepo.Insert(item);
          return item;
      }
      

      StarBlog.Web/Apis/Blog/BlogPostController.cs

      [HttpPost("{id}/[action]")]
      public ApiResponse<FeaturedPost> SetFeatured(string id) {
          var post = _postService.GetById(id);
          return post == null
              ? ApiResponse.NotFound()
              : new ApiResponse<FeaturedPost>(_blogService.AddFeaturedPost(post));
      }
      

      配置完URL就是:Api/BlogPost/{id}/SetFeatured

      取消推薦文章

      上面那個推薦的逆向操作

      service這樣寫

      public int DeleteFeaturedPost(Post post) {
          var item = _fPostRepo.Where(a => a.PostId == post.Id).First();
          return item == null ? 0 : _fPostRepo.Delete(item);
      }
      

      controller醬子

      [HttpPost("{id}/[action]")]
      public ApiResponse CancelFeatured(string id) {
          var post = _postService.GetById(id);
          if (post == null) return ApiResponse.NotFound($"博客 {id} 不存在");
          var rows = _blogService.DeleteFeaturedPost(post);
          return ApiResponse.Ok($"delete {rows} rows.");
      }
      

      設置置頂

      StarBlog設計為只允許一篇置頂文章

      設置新的置頂文章,會把原有的頂掉

      service代碼

      /// <returns>返回 <see cref="TopPost"/> 對象和刪除原有置頂博客的行數</returns>
      public (TopPost, int) SetTopPost(Post post) {
          var rows = _topPostRepo.Select.ToDelete().ExecuteAffrows();
          var item = new TopPost {PostId = post.Id};
          _topPostRepo.Insert(item);
          return (item, rows);
      }
      

      先刪除已有置頂文章,再添加新的進去。返回值用了元組語法。

      controller代碼

      [HttpPost("{id}/[action]")]
      public ApiResponse<TopPost> SetTop(string id) {
          var post = _postService.GetById(id);
          if (post == null) return ApiResponse.NotFound($"博客 {id} 不存在");
          var (data, rows) = _blogService.SetTopPost(post);
          return new ApiResponse<TopPost> {Data = data, Message = $"ok. deleted {rows} old topPosts."};
      }
      

      就這樣,簡簡單單。

      上傳圖片

      場景:在后臺編輯文章,會插入一些圖片。

      這個接口因為要上傳文件,所以使用FormData接收參數,前端發起請求需要注意。

      這是controller代碼:

      [HttpPost("{id}/[action]")]
      public ApiResponse UploadImage(string id, IFormFile file) {
          var post = _postService.GetById(id);
          if (post == null) return ApiResponse.NotFound($"博客 {id} 不存在");
          var imgUrl = _postService.UploadImage(post, file);
          return ApiResponse.Ok(new {
              imgUrl,
              imgName = Path.GetFileNameWithoutExtension(imgUrl)
          });
      }
      

      后面的 PostService.UploadImage() 方法,本文(囿于篇幅關系)先不介紹了,留個坑,放在后面圖片管理接口里一起介紹哈~

      博客的相關操作

      剛才基本是在對文章做CRUD,別忘了還有個 BlogController 呢~??

      功能就是獲取推薦、獲取置頂、博客文章總覽、打包上傳之類的。

      這里也大概介紹一下。

      獲取推薦、置頂的service代碼:

      public List<Post> GetFeaturedPosts() {
          return _fPostRepo.Select.Include(a => a.Post.Category)
              .ToList(a => a.Post);
      }
      public Post? GetTopOnePost() {
          return _topPostRepo.Select.Include(a => a.Post.Category).First()?.Post;
      }
      

      controller太簡單,就不寫了。

      總覽信息

      這里沒封裝到service里,感覺其他地方不會用到,拒絕過度封裝。

      直接從ORM讀取,文章、分類、圖片、推薦等的數量。

      PS:要做展示大屏的話,這些應該還是不夠的,后續再增加(flag立下了)

      public BlogOverview Overview() {
          return new BlogOverview {
              PostsCount = _postRepo.Select.Count(),
              CategoriesCount = _categoryRepo.Select.Count(),
              PhotosCount = _photoRepo.Select.Count(),
              FeaturedPostsCount = _fPostRepo.Select.Count(),
              FeaturedCategoriesCount = _fCategoryRepo.Select.Count(),
              FeaturedPhotosCount = _fPhotoRepo.Select.Count()
          };
      }
      

      打包上傳

      這個功能是:把本地寫完的markdown文件連同圖片等資源一起打包zip上傳,StarBlog解析markdown并將圖片附件處理后存入數據庫,實現很方便的本地寫文章,博客發表功能。

      具體實現已經在之前的文章里介紹過了,這里就不重復啦,詳情可以查看本系列的第18篇文章。基于.NetCore開發博客項目 StarBlog - (18) 實現本地Typora文章打包上傳

      小結

      AspNetCore WebApi的開發有很多東西可以寫的,在開發過程中我也在不斷學習,有很多好玩的新功能、騷操作是在后面才加入StarBlog項目的,但為了保證本系列文章閱讀的連貫性,即使某功能在文章撰寫時已經實現,也可能不會加入介紹。這些我會在后面單獨寫一篇文章來介紹(絕不是在水哦),以提升讀者的閱讀體驗。

      還有,作為新手向教程,我會盡量寫得比較詳細(廢話比較多),導致篇幅較長,但但仍無法面面俱到介紹AspNetCore的全部細節,建議邊看邊學的讀者搭配AspNetCore官方文檔或教材閱讀~

      系列文章

      posted @ 2022-12-18 23:27  程序設計實驗室  閱讀(631)  評論(0)    收藏  舉報
      主站蜘蛛池模板: bt天堂新版中文在线| 精品国产一区二区三区大| 灯塔市| 久久精品一本到东京热| 午夜国产理论大片高清| 人妻无码av中文系列久| 办公室强奷漂亮少妇视频| 中文字幕亚洲综合久久| 午夜免费国产体验区免费的| 揭东县| 中文字幕亚洲精品乱码| 国产精品大全中文字幕| 白嫩少妇激情无码| 中文字幕色偷偷人妻久久| 少妇又爽又刺激视频| 久久久久国色av免费观看性色 | 天天燥日日燥| 永久免费av网站可以直接看的| 人妻中文字幕精品系列| 国产中文字幕一区二区| 口爆少妇在线视频免费观看| 午夜夫妻试看120国产| 免费午夜无码片在线观看影院| 综合久青草视频在线观看| 开心久久综合激情五月天| 少妇宾馆粉嫩10p| 国产亚洲精品aaaa片app| 日韩本精品一区二区三区| 亚洲精品自拍在线视频| 国产精品露脸视频观看| 国产蜜臀av在线一区二区| 日本不卡三区| 久久亚洲av成人无码软件| 熟女精品国产一区二区三区| 久久无码高潮喷水| 国产乱色国产精品免费视频| 精品一区二区三区蜜桃麻豆| 国产老熟女国语免费视频| 亚洲国产日韩一区三区| 亚洲国产精品线观看不卡| 欧美一区二区三区欧美日韩亚洲|