.NET Core MongoDB数据仓储和工作单元模式实操

前言

  上一章节我们主要讲解了MongoDB数据仓储和工作单元模式的封装,这一章节主要讲的是MongoDB用户管理相关操作实操。如:获取所有用户信息、获取用户分页数据、通过用户ID获取对应用户信息、添加用户信息、事务添加用户信息、用户信息修改、用户信息删除等实战教程。

MongoDB从入门到实战的相关教程

MongoDB从入门到实战之MongoDB简介👉

MongoDB从入门到实战之MongoDB快速入门👉

MongoDB从入门到实战之Docker快速安装MongoDB👉

MongoDB从入门到实战之MongoDB工作常用操作命令👉

MongoDB从入门到实战之.NET Core使用MongoDB开发ToDoList系统(1)-后端项目框架搭建👉

MongoDB从入门到实战之.NET Core使用MongoDB开发ToDoList系统(2)-Swagger框架集成👉

MongoDB从入门到实战之.NET Core使用MongoDB开发ToDoList系统(3)-系统数据集合设计👉

MongoDB从入门到实战之.NET Core使用MongoDB开发ToDoList系统(4)-MongoDB数据仓储和工作单元模式封装👉

MongoDB从入门到实战之.NET Core使用MongoDB开发ToDoList系统(5)-MongoDB数据仓储和工作单元模式实操👉

YyFlight.ToDoList项目源码地址

欢迎各位看官老爷review,有帮助的别忘了给我个Star哦💖!!!

GitHub地址:GitHub - YSGStudyHards/YyFlight.ToDoList: 【.NET8 MongoDB 待办清单系统】.NET8 MongoDB从入门到实战基础教程,该项目后端使用的是.NET8、前端页面使用Blazor、使用MongoDB存储数据,更多相关内容大家可以看目录中的MongoDB从入门到实战的相关教程。该系列教程可作为.NET Core入门项目进行学习,感兴趣的小伙伴可以关注博主和我一起学习共同进步。

MongoRepository地址:https://github.com/YSGStudyHards/YyFlight.ToDoList/tree/main/Repository/Repository

MongoDB事务使用前提说明

参阅MongoDB的事务👉

说明:

MongoDB单机服务器不支持事务【使用MongoDB事务会报错:Standalone servers do not support transactions】,只有在集群情况下才支持事务,因为博主接下来都是在单机环境下操作,所以无法来演示Mongo事务操作,但是方法都已经是封装好了的,大家可以自己搭建集群实操。

原因:

MongoDB在使用分布式事务时需要进行多节点之间的协调和通信,而单机环境下无法实现这样的分布式协调和通信机制。但是,在MongoDB部署为一个集群(cluster)后,将多个计算机连接为一个整体,通过协调和通信机制实现了分布式事务的正常使用。从数据一致性和可靠性的角度来看,在分布式系统中实现事务处理是至关重要的。而在单机环境下不支持事务,只有在集群情况下才支持事务的设计方式是为了保证数据一致性和可靠性,并且也符合分布式系统的设计思想。

创建EntityBase公共类

一个公共的具有相同特性和行为的基类。

    public class EntityBase{/// <summary>/// 主键Id/// </summary>[BsonId][BsonRepresentation(BsonType.ObjectId)]public string Id { get; set; }/// <summary>/// 创建时间/// </summary>public DateTime CreateDate { get; set; }/// <summary>/// 更新时间/// </summary>public DateTime UpdateDate { get; set; }}

添加UserInfo用户表实体映射模型

    [Table("yyflight_todolist_user")]public class UserInfo : EntityBase{/// <summary>/// 登录账号/// </summary>public string UserName { get; set; }/// <summary>/// 登录密码/// </summary>public string Password { get; set; }/// <summary>/// 用户昵称/// </summary>public string NickName { get; set; }/// <summary>/// 用户头像/// </summary>public string HeadPortrait { get; set; }/// <summary>/// 用户邮箱/// </summary>public string Email { get; set; }/// <summary>/// 用户状态(0冻结,1正常,2注销)/// </summary>public int Status { get; set; }}

在前面类中,Id属性中的特性的作用:

  • 需要用于将通用语言运行时(CLR)对象映射到MongoDB集合。
  • 用[BsonId]进行注释,使该属性成为文档的主键。
  • 用[BsonRepresentation(BsonType.ObjectId)]进行注释,以允许以字符串类型而不是ObjectId结构传递参数。Mongo处理从字符串到ObjectId的转换。没有此特性序列化时会有如下异常提示:

System.FormatException: An error occurred while deserializing the Id property of class Repository.Domain.User.UserInfo: Cannot deserialize a 'String' from BsonType 'ObjectId'.

知识拓展MongoDB ObjectId类型概述:

每次插入一条数据系统都会自动插入一个_id键,键值不可以重复,它可以是任何类型的,也可以手动的插入,默认情况下它的数据类型是ObjectId,由于MongoDB在设计之初就是用作分布式数据库,所以使用ObjectId可以避免不同数据库中_id的重复(如果使用自增的方式在分布式系统中就会出现重复的_id的值)。
ObjectId使用12字节的存储空间,每个字节可以存储两个十六进制数字,所以一共可以存储24个十六进制数字组成的字符串,在这24个字符串中,前8位表示时间戳,接下来6位是一个机器码,接下来4位表示进程id,最后6位表示计数器。

MongoDB 采用 ObjectId 来表示主键的类型,数据库中每个文档都拥有一个_id 字段表示主键,_id 的生成规则如下:

其中包括4-byte Unix 时间戳,3-byte 机器 ID,2-byte 进程 ID,3-byte 计数器(初始化随机)

601e2b6b  a3203c  c89f   2d31aa↑        ↑       ↑       ↑时间戳    机器码   进程ID   随机数 

创建用户Repository

创建用户IUserRepository接口

    public interface IUserRepository : IMongoRepository<UserInfo>{}

创建用户UserRepository类

    public class UserRepository : MongoBaseRepository<UserInfo>, IUserRepository{public UserRepository(IMongoContext context) : base(context){}}

创建用户管理业务代码

创建IUserOperationExampleServices接口

    public interface IUserOperationExampleServices{/// <summary>/// 获取所有用户信息/// </summary>/// <returns></returns>Task<IEnumerable<UserInfo>> GetAllUserInfos();/// <summary>/// 用户分页数据获取/// </summary>/// <param name="userInfoByPageListReq">userInfoByPageListReq</param>/// <returns></returns>Task<IEnumerable<UserInfo>> GetUserInfoByPageList(UserInfoByPageListReq userInfoByPageListReq);/// <summary>/// 通过用户ID获取对应用户信息/// </summary>/// <param name="id">id</param>/// <returns></returns>Task<UserInfo> GetUserInfoById(string id);/// <summary>/// 添加用户信息/// </summary>/// <param name="userInfo">userInfo</param>/// <returns></returns>Task<UserInfo> AddUserInfo(UserInfoReq userInfo);/// <summary>/// 事务添加用户信息/// </summary>/// <param name="userInfo">userInfo</param>/// <returns></returns>Task<UserInfo> AddUserInfoTransactions(UserInfoReq userInfo);/// <summary>/// 用户信息修改/// </summary>/// <param name="id">id</param>/// <param name="userInfo">userInfo</param>/// <returns></returns>Task<UserInfo> UpdateUserInfo(string id, UserInfoReq userInfo);/// <summary>/// 用户信息删除/// </summary>/// <param name="id">id</param>/// <returns></returns>Task<bool> Delete(string id);}

创建UserOperationExampleServices类

    public class UserOperationExampleServices : IUserOperationExampleServices{private readonly IUnitOfWork _unitOfWork;private readonly IUserRepository _userRepository;/// <summary>/// 依赖注入/// </summary>/// <param name="unitOfWork">unitOfWork</param>/// <param name="userRepository">userRepository</param>public UserOperationExampleServices(IUnitOfWork unitOfWork, IUserRepository userRepository){_unitOfWork = unitOfWork;_userRepository = userRepository;}/// <summary>/// 获取所有用户信息/// </summary>/// <returns></returns>public async Task<IEnumerable<UserInfo>> GetAllUserInfos(){var getAllUserInfos = await _userRepository.GetAllAsync();return getAllUserInfos;}/// <summary>/// 用户分页数据获取/// </summary>/// <param name="userInfoByPageListReq">userInfoByPageListReq</param>/// <returns></returns>public async Task<IEnumerable<UserInfo>> GetUserInfoByPageList(UserInfoByPageListReq request){//创建查询条件构造器FilterDefinitionBuilder<UserInfo> buildFilter = Builders<UserInfo>.Filter;FilterDefinition<UserInfo> filter = buildFilter.Empty;SortDefinition<UserInfo> sort = Builders<UserInfo>.Sort.Ascending(m => m.CreateDate);if (!string.IsNullOrEmpty(request.NickName)){filter = buildFilter.Eq(m => m.NickName, request.NickName);}if (!string.IsNullOrEmpty(request.Id)){filter = buildFilter.Eq(m => m.Id, request.Id);}var list = await _userRepository.FindListByPageAsync(filter, request.PageIndex, request.PageSize, Array.Empty<string>(), sort);return list;}/// <summary>/// 通过用户ID获取对应用户信息/// </summary>/// <param name="id">id</param>/// <returns></returns>public async Task<UserInfo> GetUserInfoById(string id){var getUserInfo = await _userRepository.GetByIdAsync(id);return getUserInfo;}/// <summary>/// 添加用户信息/// </summary>/// <param name="userInfo">userInfo</param>/// <returns></returns>public async Task<UserInfo> AddUserInfo(UserInfoReq userInfo){var addUserInfo = new UserInfo(){Id = ObjectId.GenerateNewId().ToString(),UserName = userInfo.UserName,Email = userInfo.Email,NickName = userInfo.NickName,Password = MD5Helper.MDString(userInfo.Password),Status = 1,HeadPortrait = userInfo.HeadPortrait,CreateDate = DateTime.Now,UpdateDate = DateTime.Now,};await _userRepository.AddAsync(addUserInfo);var queryUserInfo = await _userRepository.GetByIdAsync(addUserInfo.Id);return queryUserInfo;}/// <summary>/// 事务添加用户信息/// </summary>/// <param name="userInfo">userInfo</param>/// <returns></returns>public async Task<UserInfo> AddUserInfoTransactions(UserInfoReq userInfo){using var session = await _unitOfWork.InitTransaction();var addUserInfo = new UserInfo(){Id = ObjectId.GenerateNewId().ToString(),UserName = userInfo.UserName,Email = userInfo.Email,NickName = userInfo.NickName,Password = MD5Helper.MDString(userInfo.Password),Status = 1,HeadPortrait = userInfo.HeadPortrait,CreateDate = DateTime.Now,UpdateDate = DateTime.Now,};await _userRepository.AddTransactionsAsync(session, addUserInfo);//查不到任何信息var queryUserInfo = await _userRepository.GetByIdAsync(addUserInfo.Id);//提交新增用户信息操作await _unitOfWork.Commit(session);//UserInfo只有在提交后才会被添加queryUserInfo = await _userRepository.GetByIdAsync(addUserInfo.Id);return queryUserInfo;}/// <summary>/// 用户信息修改/// </summary>/// <param name="id">id</param>/// <param name="userInfo">userInfo</param>/// <returns></returns>public async Task<UserInfo> UpdateUserInfo(string id, UserInfoReq userInfo){#region 指定字段和条件修改//修改条件var list = new List<FilterDefinition<UserInfo>>{Builders<UserInfo>.Filter.Eq("_id", new ObjectId(id))};var filter = Builders<UserInfo>.Filter.And(list);//指定要修改的字段内容//参考文章:https://chsakell.gitbook.io/mongodb-csharp-docs/crud-basics/update-documentsvar updateDefinition = Builders<UserInfo>.Update.Set(u => u.HeadPortrait, userInfo.HeadPortrait).Set(u => u.NickName, userInfo.NickName).Set(u => u.Status, userInfo.Status);await _userRepository.UpdateAsync(filter, updateDefinition);#endregion#region 指定对象异步修改一条数据//var updateUserInfo = new UserInfo//{//    UserName = userInfo.UserName,//    Password = MD5Helper.MDString(userInfo.Password),//    Status = 1,//    HeadPortrait = userInfo.HeadPortrait,//    Email = userInfo.Email,//    NickName = userInfo.NickName,//    UpdateDate = DateTime.Now,//};//await _userRepository.UpdateAsync(updateUserInfo, id);#endregion#region 数据批量修改示例1.批量修改的条件(把创建时间CreateDate为近五日的用户状态更改为0)//var time = DateTime.Now;//var list = new List<FilterDefinition<UserInfo>>();//list.Add(Builders<UserInfo>.Filter.Gt("CreateDate", time));//大于当前时间//list.Add(Builders<UserInfo>.Filter.Lt("CreateDate", time.AddDays(5)));//小于当前时间+5day//var filter = Builders<UserInfo>.Filter.And(list);2.要修改的字段内容//var dic = new Dictionary<string, string>//{//    { "Status", "0" }//};3.批量修改//await _userRepository.UpdateManayAsync(dic, filter);#endregionreturn await _userRepository.GetByIdAsync(id);}/// <summary>/// 用户信息删除/// </summary>/// <param name="id"></param>/// <returns></returns>public async Task<bool> Delete(string id){await _userRepository.DeleteAsync(id);var testUserInfo = await _userRepository.GetByIdAsync(id);return testUserInfo == null;}}

UserOperationExample控制创建

    /// <summary>/// MongoDB用户管理操作示例/// </summary>[ApiController][Produces("application/json")][Route("api/[controller]/[action]")]public class UserOperationExampleController : ControllerBase{private readonly IUserOperationExampleServices _userOperationExampleServices;/// <summary>/// 依赖注入/// </summary>/// <param name="userOperationExampleServices">userOperationExampleServices</param>public UserOperationExampleController(IUserOperationExampleServices userOperationExampleServices){_userOperationExampleServices = userOperationExampleServices;}/// <summary>/// 获取所有用户信息/// </summary>/// <returns></returns>[HttpGet]public async Task<ActionResult<IEnumerable<UserInfo>>> GetAllUserInfos(){var userInfos = await _userOperationExampleServices.GetAllUserInfos();return Ok(userInfos);}/// <summary>/// 获取用户分页数据/// </summary>/// <param name="userInfoByPageListReq">userInfoByPageListReq</param>/// <returns></returns>[HttpPost]public async Task<ActionResult<IEnumerable<UserInfo>>> GetUserInfoByPageList([FromBody] UserInfoByPageListReq userInfoByPageListReq){var getUserInfoByPageList = await _userOperationExampleServices.GetUserInfoByPageList(userInfoByPageListReq);return Ok(getUserInfoByPageList);}/// <summary>/// 通过用户ID获取对应用户信息/// </summary>/// <param name="id">id</param>/// <returns></returns>[HttpGet("{id}")]public async Task<ActionResult<UserInfo>> GetUserInfoById(string id){var userInfo = await _userOperationExampleServices.GetUserInfoById(id);return Ok(userInfo);}/// <summary>/// 添加用户信息/// </summary>/// <param name="userInfo">userInfo</param>/// <returns></returns>[HttpPost]public async Task<ActionResult<UserInfo>> AddUserInfo([FromBody] UserInfoReq userInfo){var addUserInfo = await _userOperationExampleServices.AddUserInfo(userInfo);return Ok(addUserInfo);}/// <summary>/// 事务添加用户信息/// </summary>/// <param name="userInfo">userInfo</param>/// <returns></returns>[HttpPost]public async Task<ActionResult<UserInfo>> AddUserInfoTransactions([FromBody] UserInfoReq userInfo){//TODO:单机服务器不支持事务使用【使用MongoDB事务会报错:Standalone servers do not support transactions】,只有在集群情况下才能用var addUserInfo = await _userOperationExampleServices.AddUserInfoTransactions(userInfo);return Ok(addUserInfo);}/// <summary>/// 用户信息修改/// </summary>/// <param name="id">id</param>/// <param name="userInfo">userInfo</param>/// <returns></returns>[HttpPut("{id}")]public async Task<ActionResult<UserInfo>> UpdateUserInfo(string id, [FromBody] UserInfoReq userInfo){var updateUserInfo = await _userOperationExampleServices.UpdateUserInfo(id, userInfo);return Ok(updateUserInfo);}/// <summary>/// 用户信息删除/// </summary>/// <param name="id">id</param>/// <returns></returns>[HttpDelete("{id}")]public async Task<ActionResult> Delete(string id){var deleteUser = await _userOperationExampleServices.Delete(id);return Ok(deleteUser);}}

注册数据库基础操作和工作单元

//注册数据库基础操作和工作单元
builder.Services.AddSingleton<IMongoConnection, MongoConnection>();
builder.Services.AddScoped<IMongoContext, MongoContext>();
builder.Services.AddScoped<IUnitOfWork, UnitOfWork>();
builder.Services.AddScoped<IUserRepository, UserRepository>();

注册相关应用服务

builder.Services.AddScoped<IUserOperationExampleServices, UserOperationExampleServices>();

Swagger用户管理操作示例展示

添加用户信息

 添加成功,返回添加成功的用户信息:

通过用户ID获取对应用户信息

拿刚才添加成功的用户ID,查询用户信息:

获取所有用户信息

用户分页数据获取

查询第1页,显示10条数据:

查询第1页,显示2条数据:

 

 用户信息修改

指定要修改的字段内容,修改HeadPortrait、NickName、Status
参考文章:Update - MongoDB C# docs

 

 修改成功:

用户信息删除

输入需要删除的用户ID,点击Execute删除:

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

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

相关文章

Matplotlib plt.scatter:从入门到精通,只需一篇文章!

Matplotlib plt.scatter&#xff1a;从入门到精通&#xff0c;只需一篇文章&#xff01;&#x1f680; 利用Matplotlib进行数据可视化示例 &#x1f335;文章目录&#x1f335; 一、plt.scatter入门&#xff1a;轻松迈出第一步 &#x1f463;二、进阶探索&#xff1a;plt.scatt…

Makefile 中的 clean 目标 Target 到底应该怎么写

如下 .PHONY: clean clean: -rm -f *.o a.out test *.so解释&#xff1a; .PHONY&#xff1a;表示伪目标&#xff0c;即&#xff0c;不需要检查依赖的时间戳&#xff0c;每次运行 make clean 都要执行 clean 目标下的命令 负号(-)&#xff1a;表示当这行命令出错时&#xff…

使用Docker Compose搭建Redis主从复制

在Docker中搭建Redis主从架构非常方便&#xff0c;下面是一个示例&#xff0c;演示一下如何使用Docker Compose搭建一个Redis主从复制环境。首先&#xff0c;确保我们本地环境已经安装了Docker和Docker Compose。 我这里使用OrbStack替代了Docker desktop。 1. 创建一个名为r…

机器人能否返回原点

657. 机器人能否返回原点 在二维平面上&#xff0c;有一个机器人从原点 (0, 0) 开始。给出它的移动顺序&#xff0c;判断这个机器人在完成移动后是否在 (0, 0) 处结束。 移动顺序由字符串 moves 表示。字符 move[i] 表示其第 i 次移动。机器人的有效动作有 R&#xff08;右&a…

Ansible file文件模块 设置文件的属性,比如创建文件、创建链接文件、删除文件

目录 语法创建目录创建链接文件删除文件 每个值的属性 语法 创建目录 ansible slave -m file -a path/data/app statedirectory path/data/app # 定义创建路径 statedirectory # 如果目录不存在就创建目录这就是创建目录成功之后的回显 可以看到&#xff0c;已经打印出目录a…

【QT+QGIS跨平台编译】之三十九:【Exiv2+Qt跨平台编译】(一套代码、一套框架,跨平台编译)

文章目录 一、Exiv2介绍二、文件下载三、文件分析四、pro文件4.1 exiv2-xmp4.2 exiv2lib_int4.3 exiv2lib五、编译实践一、Exiv2介绍 Exiv2是一个开源的C++库,用于读取、编辑和写入图片和视频文件的元数据。它可以处理各种类型的元数据,包括EXIF、IPTC、XMP等。 元数据是与…

OLMo 以促进语言模型科学之名 —— OLMo Accelerating the Science of Language Models —— 全文翻译

OLMo: Accelerating the Science of Language Models OLMo 以促进语言模型科学之名 摘要 语言模型在自然语言处理的研究中和商业产品中已经变得无所不在。因为其商业上的重要性激增&#xff0c;所以&#xff0c;其中最强大的模型已经闭源&#xff0c;控制在专有接口之中&#…

MQL语言图表事件详解

MQL语言图表事件函数 OnChartEvent&#xff1a;当发生图表事件时触发的函数。可以通过该事件来处理鼠标点击、图表对象、键盘按键等操作。 OnChartEvent函数的参数&#xff1a; long id&#xff1a;事件的ID&#xff0c;用于区分不同的事件类型。long lparam&#xff1a;事件的…

stl~string

迭代器 typedef char* iterator;typedef const char* const_iterator;iterator begin(){return _str;}iterator end(){return _str _size;}const_iterator begin() const//左值const{return _str;}const_iterator end() const{return _str _size;} for&#xff08;auto e : …

16.1 Spring框架_AOP面向切面编程(❤❤❤❤)

16.1 Spring框架_AOP面向切面编程 1. AOP介绍及相关概念名词1.1 需求分析1.2 简介2. AOP开发与配置流程2.1 入门实战_基于xml配置(❤❤)1. 依赖引入2. spring配置文件:基础格式3. 加载配置文件,启动Spring容器4. 定义切面:获取各层类信息5. 在applicationContext.xml配置切点和…

排序算法---计数排序

原创不易&#xff0c;转载请注明出处。欢迎点赞收藏~ 计数排序&#xff08;Counting Sort&#xff09;是一种线性时间复杂度的排序算法&#xff0c;其核心思想是通过统计待排序元素的个数来确定元素的相对位置&#xff0c;从而实现排序。 具体的计数排序算法步骤如下&#xff…

政安晨:【示例演绎】【Python】【Numpy数据处理】快速入门(一)

简介 NumPy是SciPy家族的成员之一。 SciPy家族是一个专门应用于数学、科学和工程领域的开源Python生态圈&#xff0c;或者说是一个由多个Python库组成的集合&#xff0c;用于解决科学计算中的各种问题。这些库构成了一个功能强大的科学计算工具箱&#xff0c;可以进行数值计算…

QT+OSG/osgEarth编译之八十九:osgdb_ply+Qt编译(一套代码、一套框架,跨平台编译,版本:OSG-3.6.5插件库osgdb_ply)

文章目录 一、osgdb_ply介绍二、文件分析三、pro文件四、编译实践一、osgdb_ply介绍 斯坦福三角形格式(Stanford Triangle Format)是一种用于存储三维模型数据的文件格式,也称为 PLY 格式。它最初由斯坦福大学图形实验室开发,用于存储和共享三维扫描和计算机图形数据。 P…

活用 Composition API 核心函数,打造卓越应用(上)

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

面试指导(面试会遇到的问题准备)

自我介绍 介绍项目流程 项目简介、用途项目架构、技术、模块、我负责的模块 开发过程中遇到的问题 效率问题、并发问题、JVM内存泄漏等问题接口修改文档不同步拖进度问题使用到了没使用过的技术浏览器 JDK linux等环境问题 你的优点是什么 能加班&#xff0c;能出差学习能…

简单试验:用Excel进行爬虫

文章目录 Excel的版本具体操作实例从网站上爬取工商银行的汇率Excel的版本 office 2016,2019,365这几个版本都可以 具体操作 #mermaid-svg-NlIVMivGoJbdyWW0 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-NlIVMi…

Vue2学习第三天

Vue2 学习第三天 1. 计算属性 computed 计算属性实现 定义&#xff1a;要用的属性不存在&#xff0c;要通过已有属性计算得来。 原理&#xff1a;底层借助了Objcet.defineproperty方法提供的getter和setter。 get函数什么时候执行&#xff1f; 初次读取时会执行一次。当依赖…

如何让Obsidian实现电脑端和安卓端同步

Obsidian是一款知名的笔记软件&#xff0c;支持Markdown语法&#xff0c;它允许用户在多个设备之间同步文件。要在安卓设备上实现同步&#xff0c;可以使用remote save插件&#xff0c;以下是具体操作步骤&#xff1a; 首先是安装电脑端的obsidian&#xff0c;然后依次下载obs…

Typora+PicGO+腾讯云COS做图床教程

文章目录 Typora&#xff0b;PicGO&#xff0b;腾讯云COS做图床教程一、为什么使用图床二、Typora、PicGO和腾讯云COS介绍三、下载Typora和PicGOTyporaPicGO 四、配置Typora、PicGO和腾讯云COS腾讯云COS配置PicGO配置Typora配置 Typora&#xff0b;PicGO&#xff0b;腾讯云COS做…

STM32入坑

目录 一、选择合适的开发板 二、安装和配置开发环境 三、学习基础知识 四、编写和调试程序 五、扩展功能和学习进阶知识 六、坚持&#xff0c;坚持&#xff0c;坚持 七、STM32的学习路径 一、选择合适的开发板 首先&#xff0c;你需要选择一款合适的STM32开发板。开发板…