基于 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. 定时任务最佳实战(三)

  16. 博客接口实战篇(一)

  17. 博客接口实战篇(二)


上篇文章完成了分类和标签页面相关的共6个接口,本篇继续来写博客增删改查API的业务。

供前端查询用的接口还剩下一个,这里先补上。

友链列表

分析:返回标题和对应的链接即可,传输对象FriendLinkDto.cs

//FriendLinkDto.cs
namespace Meowv.Blog.Application.Contracts.Blog
{public class FriendLinkDto{/// <summary>/// 标题/// </summary>public string Title { get; set; }/// <summary>/// 链接/// </summary>public string LinkUrl { get; set; }}
}

添加查询友链列表接口和缓存接口。

//IBlogService.FriendLink.cs
using Meowv.Blog.Application.Contracts.Blog;
using Meowv.Blog.ToolKits.Base;
using System.Collections.Generic;
using System.Threading.Tasks;namespace Meowv.Blog.Application.Blog
{public partial interface IBlogService{/// <summary>/// 查询友链列表/// </summary>/// <returns></returns>Task<ServiceResult<IEnumerable<FriendLinkDto>>> QueryFriendLinksAsync();}
}
//IBlogCacheService.FriendLink.cs
using Meowv.Blog.Application.Contracts.Blog;
using Meowv.Blog.ToolKits.Base;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;namespace Meowv.Blog.Application.Caching.Blog
{public partial interface IBlogCacheService{/// <summary>/// 查询友链列表/// </summary>/// <param name="factory"></param>/// <returns></returns>Task<ServiceResult<IEnumerable<FriendLinkDto>>> QueryFriendLinksAsync(Func<Task<ServiceResult<IEnumerable<FriendLinkDto>>>> factory);}
}

接下来,实现他们。

//BlogCacheService.FriendLink.cs
using Meowv.Blog.Application.Contracts.Blog;
using Meowv.Blog.ToolKits.Base;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using static Meowv.Blog.Domain.Shared.MeowvBlogConsts;namespace Meowv.Blog.Application.Caching.Blog.Impl
{public partial class BlogCacheService{private const string KEY_QueryFriendLinks = "Blog:FriendLink:QueryFriendLinks";/// <summary>/// 查询友链列表/// </summary>/// <param name="factory"></param>/// <returns></returns>public async Task<ServiceResult<IEnumerable<FriendLinkDto>>> QueryFriendLinksAsync(Func<Task<ServiceResult<IEnumerable<FriendLinkDto>>>> factory){return await Cache.GetOrAddAsync(KEY_QueryFriendLinks, factory, CacheStrategy.ONE_DAY);}}
}
//BlogService.FriendLink.cs
using Meowv.Blog.Application.Contracts.Blog;
using Meowv.Blog.Domain.Blog;
using Meowv.Blog.ToolKits.Base;
using System.Collections.Generic;
using System.Threading.Tasks;namespace Meowv.Blog.Application.Blog.Impl
{public partial class BlogService{/// <summary>/// 查询友链列表/// </summary>/// <returns></returns>public async Task<ServiceResult<IEnumerable<FriendLinkDto>>> QueryFriendLinksAsync(){return await _blogCacheService.QueryFriendLinksAsync(async () =>{var result = new ServiceResult<IEnumerable<FriendLinkDto>>();var friendLinks = await _friendLinksRepository.GetListAsync();var list = ObjectMapper.Map<IEnumerable<FriendLink>, IEnumerable<FriendLinkDto>>(friendLinks);result.IsSuccess(list);return result;});}}
}

直接查询所有的友链数据,这里使用前面讲到的AutoMapper处理对象映射,将IEnumerable<FriendLink>转换为IEnumerable<FriendLinkDto>

MeowvBlogAutoMapperProfile.cs中添加一条配置:CreateMap<FriendLink, FriendLinkDto>();,在BlogController中添加API。

/// <summary>
/// 查询友链列表
/// </summary>
/// <returns></returns>
[HttpGet]
[Route("friendlinks")]
public async Task<ServiceResult<IEnumerable<FriendLinkDto>>> QueryFriendLinksAsync()
{return await _blogService.QueryFriendLinksAsync();
}

编译运行,打开查询友链的API,此时没数据,手动添加几条数据进去再试试吧。

文章管理

后台文章管理包含:文章列表、新增、更新、删除文章,接下来依次完成这些接口。

文章列表

这里的文章列表和前台的文章列表差不多,就是多了一个Id,以供编辑和删除使用,所以可以新建一个模型类QueryPostForAdminDto继承QueryPostDto,添加PostBriefForAdminDto继承PostBriefDto同时新增一个字段主键Id。

QueryPostForAdminDto中隐藏基类成员Posts,使用新的接收类型:IEnumerable<PostBriefForAdminDto>

//PostBriefForAdminDto.cs
namespace Meowv.Blog.Application.Contracts.Blog
{public class PostBriefForAdminDto : PostBriefDto{/// <summary>/// 主键/// </summary>public int Id { get; set; }}
}
//QueryPostForAdminDto.cs
using System.Collections.Generic;namespace Meowv.Blog.Application.Contracts.Blog
{public class QueryPostForAdminDto : QueryPostDto{/// <summary>/// Posts/// </summary>public new IEnumerable<PostBriefForAdminDto> Posts { get; set; }}
}

添加分页查询文章列表的接口:QueryPostsForAdminAsync(),关于后台的一些接口就不添加缓存了。

//IBlogService.Admin.cs
using Meowv.Blog.Application.Contracts;
using Meowv.Blog.Application.Contracts.Blog;
using Meowv.Blog.ToolKits.Base;
using System.Threading.Tasks;namespace Meowv.Blog.Application.Blog
{public partial interface IBlogService{/// <summary>/// 分页查询文章列表/// </summary>/// <param name="input"></param>/// <returns></returns>Task<ServiceResult<PagedList<QueryPostForAdminDto>>> QueryPostsForAdminAsync(PagingInput input);}
}

然后实现这个接口。

//BlogService.Admin.cs
using Meowv.Blog.Application.Contracts;
using Meowv.Blog.Application.Contracts.Blog;
using Meowv.Blog.ToolKits.Base;
using Meowv.Blog.ToolKits.Extensions;
using System.Linq;
using System.Threading.Tasks;namespace Meowv.Blog.Application.Blog.Impl
{public partial class BlogService{/// <summary>/// 分页查询文章列表/// </summary>/// <param name="input"></param>/// <returns></returns>public async Task<ServiceResult<PagedList<QueryPostForAdminDto>>> QueryPostsForAdminAsync(PagingInput input){var result = new ServiceResult<PagedList<QueryPostForAdminDto>>();var count = await _postRepository.GetCountAsync();var list = _postRepository.OrderByDescending(x => x.CreationTime).PageByIndex(input.Page, input.Limit).Select(x => new PostBriefForAdminDto{Id = x.Id,Title = x.Title,Url = x.Url,Year = x.CreationTime.Year,CreationTime = x.CreationTime.TryToDateTime()}).GroupBy(x => x.Year).Select(x => new QueryPostForAdminDto{Year = x.Key,Posts = x.ToList()}).ToList();result.IsSuccess(new PagedList<QueryPostForAdminDto>(count.TryToInt(), list));return result;}}
}

实现逻辑也非常简单和之前一样,就是在Select的时候多了一个Id,添加一个新的Controller:BlogController.Admin.cs,添加这个接口。

//BlogController.Admin.cs
using Meowv.Blog.Application.Contracts;
using Meowv.Blog.Application.Contracts.Blog;
using Meowv.Blog.ToolKits.Base;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
using static Meowv.Blog.Domain.Shared.MeowvBlogConsts;namespace Meowv.Blog.HttpApi.Controllers
{public partial class BlogController{/// <summary>/// 分页查询文章列表/// </summary>/// <param name="input"></param>/// <returns></returns>[HttpGet][Authorize][Route("admin/posts")][ApiExplorerSettings(GroupName = Grouping.GroupName_v2)]public async Task<ServiceResult<PagedList<QueryPostForAdminDto>>> QueryPostsForAdminAsync([FromQuery] PagingInput input){return await _blogService.QueryPostsForAdminAsync(input);}}
}

因为是后台的接口,所以加上AuthorizeAttribute,指定接口组为GroupName_v2,参数方式为[FromQuery]

当没有进行授权的时候,是无法访问接口的。

新增文章

在做新增文章的时候要注意几点,不是单纯的添加文章数据就结束了,要指定文章分类,添加文章的标签。添加标签我这里是从标签库中去取得数据,只存标签Id,所以添加标签的时候就可能存在添加了标签库中已有的标签。

新建一个新增和更新文章的通用输出参数模型类,起名:EditPostInput,继承PostDto,然后添加标签Tags字段,返回类型IEnumerable<string>

//EditPostInput.cs
using System.Collections.Generic;namespace Meowv.Blog.Application.Contracts.Blog.Params
{public class EditPostInput : PostDto{/// <summary>/// 标签列表/// </summary>public IEnumerable<string> Tags { get; set; }}
}

添加新增文章的接口:InsertPostAsync

/// <summary>
/// 新增文章
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
Task<ServiceResult> InsertPostAsync(EditPostInput input);

然后去实现这个接口,实现之前,配置AutoMapper实体映射。

CreateMap<EditPostInput, Post>().ForMember(x => x.Id, opt => opt.Ignore());

EditPostInput转换为Post,并且忽略Id字段。

/// <summary>
/// 新增文章
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
public async Task<ServiceResult> InsertPostAsync(EditPostInput input)
{var result = new ServiceResult();var post = ObjectMapper.Map<EditPostInput, Post>(input);post.Url = $"{post.CreationTime.ToString(" yyyy MM dd ").Replace(" ", "/")}{post.Url}/";await _postRepository.InsertAsync(post);var tags = await _tagRepository.GetListAsync();var newTags = input.Tags.Where(item => !tags.Any(x => x.TagName.Equals(item))).Select(item => new Tag{TagName = item,DisplayName = item});await _tagRepository.BulkInsertAsync(newTags);var postTags = input.Tags.Select(item => new PostTag{PostId = post.Id,TagId = _tagRepository.FirstOrDefault(x => x.TagName == item).Id});await _postTagRepository.BulkInsertAsync(postTags);result.IsSuccess(ResponseText.INSERT_SUCCESS);return result;
}

URL字段,根据创建时间按照yyyy/MM/dd/name/格式拼接。

然后找出是否有新标签,有的话批量添加至标签表。

再根据input.Tags构建PostTag列表,也进行批量保存,这样才算是新增好一篇文章,最后输出ResponseText.INSERT_SUCCESS常量,提示成功。

BlogController.Admin.cs添加API。

/// <summary>
/// 新增文章
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[HttpPost]
[Authorize]
[Route("post")]
[ApiExplorerSettings(GroupName = Grouping.GroupName_v2)]
public async Task<ServiceResult> InsertPostAsync([FromBody] EditPostInput input)
{return await _blogService.InsertPostAsync(input);
}

更新文章

更新操作和新增操作输入参数一样,只新增一个Id用来标识更新那篇文章,添加UpdatePostAsync更新文章接口。

/// <summary>
/// 更新文章
/// </summary>
/// <param name="id"></param>
/// <param name="input"></param>
/// <returns></returns>
Task<ServiceResult> UpdatePostAsync(int id, EditPostInput input);

同样的实现这个接口。

/// <summary>
/// 更新文章
/// </summary>
/// <param name="id"></param>
/// <param name="input"></param>
/// <returns></returns>
public async Task<ServiceResult> UpdatePostAsync(int id, EditPostInput input)
{var result = new ServiceResult();var post = await _postRepository.GetAsync(id);post.Title = input.Title;post.Author = input.Author;post.Url = $"{input.CreationTime.ToString(" yyyy MM dd ").Replace(" ", "/")}{input.Url}/";post.Html = input.Html;post.Markdown = input.Markdown;post.CreationTime = input.CreationTime;post.CategoryId = input.CategoryId;await _postRepository.UpdateAsync(post);var tags = await _tagRepository.GetListAsync();var oldPostTags = 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{post_tags.Id,tag.TagName};var removedIds = oldPostTags.Where(item => !input.Tags.Any(x => x == item.TagName) &&tags.Any(t => t.TagName == item.TagName)).Select(item => item.Id);await _postTagRepository.DeleteAsync(x => removedIds.Contains(x.Id));var newTags = input.Tags.Where(item => !tags.Any(x => x.TagName == item)).Select(item => new Tag{TagName = item,DisplayName = item});await _tagRepository.BulkInsertAsync(newTags);var postTags = input.Tags.Where(item => !oldPostTags.Any(x => x.TagName == item)).Select(item => new PostTag{PostId = id,TagId = _tagRepository.FirstOrDefault(x => x.TagName == item).Id});await _postTagRepository.BulkInsertAsync(postTags);result.IsSuccess(ResponseText.UPDATE_SUCCESS);return result;
}

ResponseText.UPDATE_SUCCESS是常量更新成功。

先根据Id查询到数据库中的这篇文章数据,然后根据input参数,修改需要修改的数据,最后保存。

注意的是,如果修改的时候修改了标签,有可能新增也有可能删除,也许会又有新增又有删除。

这时候就需要注意,这里做了一个比较通用的方法,找到数据库中当前文章Id的所有Tags,然后根据参数input.Tags可以找出被删掉的标签的PostTags的Id,调用删除方法删掉即可,同时也可以获取到新增的标签,批量进行保存。

完成上面操作后,才保存新加标签与文章对应的数据,最后提示更新成功,在BlogController.Admin添加API。

/// <summary>
/// 更新文章
/// </summary>
/// <param name="id"></param>
/// <param name="input"></param>
/// <returns></returns>
[HttpPut]
[Authorize]
[Route("post")]
[ApiExplorerSettings(GroupName = Grouping.GroupName_v2)]
public async Task<ServiceResult> UpdatePostAsync([Required] int id, [FromBody] EditPostInput input)
{return await _blogService.UpdatePostAsync(id, input);
}

[HttpPut]指定请求方式为put请求,一般需要修改用put,添加用post。

[Required]指定参数id必填且是FromQuery的方式,input为[FromBody]

更新一下上面新增的数据试试。

删除文章

删除相对来说就非常简单了,一般删除都会做逻辑删除,就是避免某些手残删除了,有找回的余地,我们这里就直接Delete了,也没什么重要数据。


添加接口:DeletePostAsync

/// <summary>
/// 删除文章
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
Task<ServiceResult> DeletePostAsync(int id);

实现接口。

/// <summary>
/// 删除文章
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public async Task<ServiceResult> DeletePostAsync(int id)
{var result = new ServiceResult();var post = await _postRepository.GetAsync(id);if (null == post){result.IsFailed(ResponseText.WHAT_NOT_EXIST.FormatWith("Id", id));return result;}await _postRepository.DeleteAsync(id);await _postTagRepository.DeleteAsync(x => x.PostId == id);result.IsSuccess(ResponseText.DELETE_SUCCESS);return result;
}

删除的时候同样去查询一下数据,来判断是否存在。

ResponseText.DELETE_SUCCESS是添加的常量删除成功,删除成功同时也要将post_tags表的标签对应关系也干掉才算完整,在BlogController.Admin添加API。

/// <summary>
/// 删除文章
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpDelete]
[Authorize]
[Route("post")]
[ApiExplorerSettings(GroupName = Grouping.GroupName_v2)]
public async Task<ServiceResult> DeletePostAsync([Required] int id)
{return await _blogService.DeletePostAsync(id);
}

[HttpDelete]指定请求方式是删除资源,[Required]指定参数Id必填。

删掉上面添加的文章看看效果。

至此,完成了博客文章的增删改接口,未完待续...

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


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

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

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

相关文章

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

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

真的是计划赶不上变化吗?

「做事容易半途而废&#xff0c;缺乏毅力」&#xff0c;我想100个人里面有99个是这样。更加好玩的是&#xff0c;这99个人里面可能还有不少会劝说别人要坚持……所以&#xff0c;其实我们每个人心里都清楚&#xff0c;一件事情不会“自动完成”&#xff0c;只有靠自己坚持做下去…

关于解决Path被大改,无法直接编辑恢复的问题

为了给eclipse改版&#xff0c;不用更改API&#xff0c;我动了环境变量&#xff0c;因为我的环境变量名称是path&#xff0c;所以当给tomcatTomCAT安装以及使用详细解释配置环境时&#xff0c;我直接新建的Path&#xff0c;把原来的path覆盖掉了&#xff0c;而且在注册表里无法…

Sql Server之旅——第十二站 对锁的初步认识

作为一个开发人员&#xff0c;锁机制也是我们程序员必须掌握的东西&#xff0c;很久之前在学习锁的时候&#xff0c;都是教科书上怎么说&#xff0c;然后我怎么背&#xff0c;缺少一个工具让我们眼见为实。。。如果这样的话&#xff0c;学习一个东西就很容易忘记。。。因为这些…

算法基础

目录枚举例题应用&#xff1a;模拟技巧递归$分治递归分治算法贪心常见题型与动态规划的区别例题&#xff1a;应用排序选择排序冒泡排序插入排序计数排序基数排序二分最大值最小化STL 的二分查找三分法最大化平均值&#xff08;01分数规划&#xff09;枚举 枚举&#xff08;英语…

ABP框架 v2.9发布!

ABP框架和ABP商业版2.9已经发布,这是3.0之前的最后一个版本! 这篇文章将涵盖本次发布中的新增内容.ABP框架2.9有哪些新增内容&#xff1f;你可以中GitHub的发行说明中看到所有的变更.这篇文章将只包括重要特征/变更.预编译Razor Pages在之前的版本, 预构建的页面(应用模块)和视…

最终选型 Blazor.Server:又快又稳!

书接上文&#xff0c;昨天我们快速的走了一遍wasm的开发流程&#xff08;我的『MVP.Blazor』快速创建与部署&#xff09;&#xff0c;总体来说还是很不错的&#xff0c;无论是从技术上&#xff0c;还是从开发上&#xff0c;重点是用C#来开启前端时代&#xff0c;可以开发SPA单页…

博客系统知多少:揭秘那些不为人知的学问(三)

点击上方关注“汪宇杰博客”上篇《博客系统知多少&#xff1a;揭秘那些不为人知的学问&#xff08;二&#xff09;》介绍了博客的基本功能设计要点&#xff0c;本篇介绍博客的协议或标准。1.“博客”的前世今生2.我的博客故事3.谁是博客的受众&#xff1f;4. 博客基本功能设计要…

Sql Server之旅——第十三站 深入的探讨锁机制

上一篇我只是做了一个堆表让大家初步的认识到锁的痉挛状态&#xff0c;但是在现实世界上并没有这么简单的事情&#xff0c;起码我的表不会没有索引对吧&#xff0c;还有就是我的表一定会有很多的连接过来&#xff0c;10:1的读写&#xff0c;很多码农可能都会遇到类似神乎其神的…

后端程序员转行前端,强烈推荐这6个前端UI框架,第二款小程序UI框架颜值最高!...

昨天有个同事问我有没有可以直接上手的前端UI框架。那今天就给大家推荐6个简单、视觉体验好的前端框架没吃过猪肉&#xff0c;肯定见过猪跑&#xff01;Jquery Mobiledemo地址&#xff1a;https://demos.jquerymobile.com/1.1.0/教程地址:https://www.codesocang.com/jquerymob…

Kubernetes引发“军备赛”,K8s真是企业生存的关键吗

导语与许多其他国家一样&#xff0c;英国对云基础架构的需求空前增长。随着学校&#xff0c;办公室&#xff0c;商店和饭店的关闭以应对疫情&#xff0c;云驱动的应用程序应运而生&#xff0c;解决挑战并提供安慰。随着各个年龄段人群的屏幕时间激增&#xff0c;数据中心正努力…

【新插件发布】AzureAD运维Excel版插件,增删改查快10倍c以上!

在笔者的BI项目开发中&#xff0c;用到了Azure的AzureSQL和AzureAS分析服务&#xff0c;此两大服务&#xff0c;可和AzureAD帐号体系打通。而AzureAD帐号&#xff0c;在其免费功能基础功能上&#xff0c;是免费使用的&#xff0c;随着项目开发上线进入运维阶段&#xff0c;Azur…

数论相关

目录符号整除/同余理论常见符号数论函数常见符号其他常见符号位运算与、或、异或取反左移和右移复合赋值位运算符关于优先级位运算的应用有关 2 的幂的应用取绝对值取两个数的最大/最小值操作一个数的二进制位模拟集合操作快速幂模意义下大整数乘法快速乘高精度快速幂欧拉降幂求…

博客系统知多少:揭秘那些不为人知的学问(二)

点击上方关注“汪宇杰博客”上篇《博客系统知多少&#xff1a;揭秘那些不为人知的学问&#xff08;一&#xff09;》介绍了博客的历史、我的博客故事及博客的受众来源。本篇精彩继续&#xff0c;介绍博客基本功能设计要点。1.“博客”的前世今生2.我的博客故事3.谁是博客的受众…

二分+01分数规划+最大化平均值 Dropping tests POJ - 2976

题意&#xff1a; 给你若n个分数&#xff0c;分子a[i]a[i]a[i],分母b[i]b[i]b[i],使满足公式100⋅∑i1nai∑i1nbi100\cdot\tfrac{\sum_{i1}^{n} a_{i}}{\sum_{i1}^{n} b_{i}}100⋅∑i1n​bi​∑i1n​ai​​&#xff0c;求任意去掉k个分数后&#xff0c;公式结果最大值。 题目…

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

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

莫比乌斯反演/容斥 +2020ICPC 江西省大学生程序设计竞赛 A Simple Math Problem

题目描述 输入描述: 输出描述: 示例1 输入 3 输出 5 分析&#xff1a; 1.这个题其实考的是一个莫比乌斯反演题&#xff0c;但是由于我知识储备不够&#xff0c;没有看出来&#xff0c;题目给的范围可以瞎搞一下&#xff0c;所以下面容斥可以过。 2.转换一下就是一道经典的…

猎鹰与龙飞船基于Linux,采用C++、Chromium与JS开发

最近两天科技界最重大的事件莫过于马斯克的 SpaceX 成功实现了猎鹰 9 号&#xff08;Falcon 9&#xff09;带着龙飞船&#xff08;Crew Dragon&#xff09;成功发射&#xff0c;并使飞船与国际空间站对接&#xff0c;将 NASA 两名宇航员送上了轨道前哨。背后关于 Falcon 9 与 C…

博客系统知多少:揭秘那些不为人知的学问(四)

点击上方关注“汪宇杰博客” ^_^上篇《博客系统知多少&#xff1a;揭秘那些不为人知的学问&#xff08;三&#xff09;》介绍了博客协议或标准。本篇终章介绍设计博客系统有哪些知识点。1.“博客”的前世今生2.我的博客故事3.谁是博客的受众&#xff1f;4. 博客基本功能设计要点…

Azure 国际版与中国版服务列表对(2020年6月版)

点击上方关注“汪宇杰博客” ^_^对于选择Azure平台的用户来说&#xff0c;会面临选择国内还是国际版的问题。由于一些原因&#xff0c;由世纪互联运营的中国大陆版Azure无法落地所有的国际版服务。相比几年前&#xff0c;情况已经有了一定的改善。本文列出了国际版和国内版Azur…