16. 【.NET 8 实战--孢子记账--从单体到微服务】--汇率获取定时器
这篇文章我们将一起编写这个系列专栏中第一个和外部系统交互的功能:获取每日汇率。下面我们一起来编写代码吧。
一、需求
根据文章标题可知,在这片文章中我们只进行汇率的获取和写入数据库。
编号 | 需求 | 说明 |
---|---|---|
1 | 获取每日汇率 | 1. 从第三方汇率API中获取汇率信息并存入数据库中;2. 每天凌晨1点获取汇率;3. 目前仅支持人民币、日元、欧元、韩元、美元、港币、澳门元、英镑、新台币之间的汇率 |
二、功能编写
网上有很多获取汇率的API,有收费的也有免费的,由于我们开发的项目只是用于实战练习,而不会真正的发布出去让别人使用,因此我们选择免费的API。但是免费的API有好有坏,有的API只提供有限的免费请求额度,有的API只能免费使用很短的几天。所以即使是使用免费的API我们也要谨慎选择,以免耽误我们的练习。
但是,大家不用担心,我已经为大家选好了一个不错的获取汇率的API:Exchange Rate API 。它对免费用户提供每月1500次的接口调用,并且可以一直使用免费的接口,对于我们的项目来说已经足够了。
2.1 编写数据库映射类
我们的需求是获取每天的汇率,因此我们的数据库映射类应该包含汇率日期字段Data。并且我们还需要保存币种之间的汇率,因此还需要币种转换关系字段ConvertCurrency,以及币种汇率字段ExchangeRate。
以下的代码就是根据前面分析所编写的数据库映射类ExchangeRateRecord
,再次强调的是ExchangeRateRecord
编写完后别忘了迁移数据库:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using SporeAccounting.BaseModels;namespace SporeAccounting.Models;/// <summary>
/// 汇率记录表
/// </summary>
[Table(name: "ExchangeRate")]
public class ExchangeRateRecord : BaseModel
{/// <summary>/// 汇率/// </summary>[Column(TypeName = "decimal(10,2)")][Required]public decimal ExchangeRate { get; set; }/// <summary>/// 币种转换/// </summary>[Column(TypeName = "nvarchar(20)")][Required]public string ConvertCurrency { get; set; }/// <summary>/// 汇率日期/// </summary>[Column(TypeName = "date")][Required]public DateTime Date { get; set; }
}
2.2 编写定时器
需求上说需要在每天凌晨1点获取当天的汇率,因此需要编写一个定时器来定时执行获取汇率的工作。我们可以选择的定时器很多,有.NET 原生的也有第三方的。在这里我们选择 Quartz.NET,Quartz.NET 是 Quartz 的.NET版,它在.NET领域中使用的最多。下面我们使用Quartz.NET一起来编写定时器。
- 安装 Quartz.NET
我们不直接使用Quartz.NET,而是使用Quartz.NET的ASP.NET Core集成版。在Nuget中搜索Quartz.AspNetCore,选择支持.NET8的最新版,点击安装即可。 - 编写Job
在Quartz中,一个定时器就是一个Job。新建Job类ExchangeRateTimer
,它继承Quartz的IJob
接口,并实现Execute
方法。在Execute
方法中我们要实现向Exchange Rate API获取汇率的接口发送请求并获取汇率信息的功能,以及将汇率信息存入数据库的功能。这里不多说直接上代码:
这段代码看着很复杂,其实是完全按照前面的需求实现的功能,因此这里就不多讲解的。唯一需要注意的是的我们在using System.Text.Json; using Quartz; using SporeAccounting.Models; using SporeAccounting.Server.Interface; using SporeAccounting.Task.Timer.Model;namespace SporeAccounting.Task.Timer;/// <summary> /// 获取汇率定时器 /// </summary> public class ExchangeRateTimer : IJob {private readonly IHttpClientFactory _httpClientFactory;private readonly IConfiguration _configuration;private readonly IServiceScopeFactory _serviceScopeFactory;private readonly ICurrencyService _currencyService;public ExchangeRateTimer(IHttpClientFactory httpClientFactory,IConfiguration configuration, IServiceScopeFactory serviceScopeFactory,ICurrencyService currencyService){_httpClientFactory = httpClientFactory;_configuration = configuration;_serviceScopeFactory = serviceScopeFactory;_currencyService = currencyService;}public System.Threading.Tasks.Task Execute(IJobExecutionContext context){string exchangeRateUrl = _configuration["ExchangeRate"];//获取全部币种List<Currency> currencies = _currencyService.Query().ToList();//获取对每种币种的汇率foreach (var currency in currencies){_httpClientFactory.CreateClient().GetAsync($"{exchangeRateUrl}{currency.Abbreviation}").ContinueWith(response =>{using var scope = _serviceScopeFactory.CreateScope();var exchangeRateRecordService =scope.ServiceProvider.GetRequiredService<IExchangeRateRecordService>();List<ExchangeRateRecord> exchangeRateRecords = new();if (response.Result.IsSuccessStatusCode){var result = response.Result.Content.ReadAsStringAsync().Result;var resultModel = JsonSerializer.Deserialize<ExchangeRateApiData>(result);if (resultModel?.Result == "success"){foreach (var rate in resultModel.ConversionRates){//只获取人民币、日元、欧元、韩元、美元、港币、澳门元、英镑、新台币之间的汇率//其他币种的汇率直接跳过if (currencies.All(c => c.Abbreviation != rate.Key)){continue;}exchangeRateRecords.Add(new ExchangeRateRecord{Id = Guid.NewGuid().ToString(),ExchangeRate = rate.Value,//汇率记录的币种代码是基础币种代码和目标币种代码的组合ConvertCurrency = $"{resultModel.BaseCode}_{rate.Key}",Date = DateTime.Now,CreateDateTime = DateTime.Now,CreateUserId = "System",IsDeleted = false});}//存入数据库exchangeRateRecordService.Add(exchangeRateRecords);}}});}return System.Threading.Tasks.Task.CompletedTask;} }
ContinueWith
方法的回调函数中使用了如下代码来获取IExchangeRateRecordService
的实例 :
为什么要这么做呢,为什么不在构造函数中通过注入的方式获取呢?这是因为我们使用异步的方式来获取汇率数据的,因此using var scope = _serviceScopeFactory.CreateScope(); var exchangeRateRecordService = scope.ServiceProvider.GetRequiredService<IExchangeRateRecordService>
ContinueWith
内的方法是在另一个线程上运行的,如果通过注入的方式在构造函数中获取IExchangeRateRecordService
实例的话,在Execute
方法执行完毕后实例就被释放回收了,因此这时如果在ContinueWith
内的方法中使用IExchangeRateRecordService
的实例就会出发链接已被关闭的异常。
2.3 配置定时器
Job的逻辑已经编写完了,那么最后要做的就是配置定时器,让它在凌晨一点是定时获取汇率信息。在Program
类中加入如下代码:
// 添加定时任务
builder.Services.AddQuartz(q =>
{var exchangeRateTimerJobKey=new JobKey("ExchangeRateTimer");q.AddJob<ExchangeRateTimer>(opts=>opts.WithIdentity(exchangeRateTimerJobKey));q.AddTrigger(opts=>opts.ForJob(exchangeRateTimerJobKey).WithIdentity("ExchangeRateTimerTrigger").StartNow().WithCronSchedule("0 0 1 * * ?"));
});
builder.Services.AddQuartzHostedService(options =>
{//启用 Quartz 的托管服务,`WaitForJobsToComplete = true` 表示在应用程序停止时等待任务完成后再关闭。options.WaitForJobsToComplete = true;
});
这段代码通过 AddQuartz
方法注册了 Quartz 服务容器,在配置定时Job和触发器时,我们先创建了一个唯一标识Job的Job Key exchangeRateTimerJobKey
,用于区分不同Job,并通过AddJob
方法注册了刚才我们编写的Job类 ExchangeRateTimer
,并将其与 exchangeRateTimerJobKey
绑定。接着通过AddTrigger
方法创建一个触发器,并将触发器绑定到指定Job上,然后使用StartNow
方法将Job注册为立即开始,最后使用WithCronSchedule
方法通过Cron 表达式设置每天凌晨 1 点触发Job。
Tip:这篇文章我们只展示出了核心的类、方法以及配置,还有一个方法、接口以及接口的实现类没有展示。一方面因为这些代码和前面文章中的代码类似,另一方面就是我一直再强调的我希望大家能自己动手写写代码。
三、总结
我们一起编写了获取每日汇率的定时器,掌握了 Quartz.NET 的使用,我们的项目也距离完成越来越近了。
相关文章:
16. 【.NET 8 实战--孢子记账--从单体到微服务】--汇率获取定时器
这篇文章我们将一起编写这个系列专栏中第一个和外部系统交互的功能:获取每日汇率。下面我们一起来编写代码吧。 一、需求 根据文章标题可知,在这片文章中我们只进行汇率的获取和写入数据库。 编号需求说明1获取每日汇率1. 从第三方汇率API中获取汇率信…...
移动充储机器人“小奥”的多场景应用(上)
一、高速公路服务区应用 在高速公路服务区,新能源汽车的充电需求得到“小奥”机器人的及时响应。该机器人配备有储能电池和自动驾驶技术,能够迅速定位至指定充电点,为待充电的新能源汽车提供服务。得益于“小奥”的机动性,其服务…...
【Android】Service使用方法:本地服务 / 可通信服务 / 前台服务 / 远程服务(AIDL)
1 本地Service 这是最普通、最常用的后台服务Service。 1.1 使用步骤 步骤1:新建子类继承Service类:需重写父类的onCreate()、onStartCommand()、onDestroy()和onBind()方法步骤2:构建用于启动Service的Intent对象步骤3:调用st…...
Qt文件目录操作
文件目录操作相关类 Qt 为文件和目录操作提供了一些类,利用这些类可以方便地实现一些操作。Qt 提供的与文件和目录操作相关的类包括以下几个: QCoreApplication:用于提取应用程序路径,程序名等文件信息;QFile&#x…...
刷题-1122
1. 蛇形矩阵是由1开始的自然数依次排列成的一个矩阵上三角形。 例如,当输入5时,应该输出的三角形为: 1 3 6 10 15 2 5 9 14 4 8 13 7 12 11 import sys def generate_snake_matrix(n):matrix [[0]*n for _ in range(n)]curent_num 1…...
【通俗理解】Jensen不等式与变分分布q(z)在积分计算中的应用
【通俗理解】Jensen不等式与变分分布q(z)在积分计算中的应用 关键词提炼 #Jensen不等式 #变分分布 #积分计算 #期望 #凸函数 #优化问题 #下界估计 #机器学习 第一节:Jensen不等式与变分分布的类比与核心概念【尽可能通俗】 Jensen不等式就像是一个“积分计算器”…...
微信小程序2-地图显示和地图标记
一、index修改页面,让页面能够显示地图和一个添加标记的按钮。 index.wxml <scroll-view class"scrollarea" scroll-y type"list"><view class"index_container"><map id"map" style"width: 100%; h…...
webpack配置和打包性能优化
文章目录 webpack基础配置loaderpluginloader 和 plugin 的区别devServer打包性能优化1、按需引入组件2、externals 属性3、给定文件匹配范围4、noParse 属性5、cacheDirectory 缓存属性6、happyPack 多个子进程并行 webpack基础配置 mode:development:设置webpack…...
iframe嵌入踩坑记录
iframe嵌入父子页面token问题 背景介绍 最近在做在平台A中嵌入平台B某个页面的需求,我负责的是平台B这边,使这个页面被嵌入后能正常使用。两个平台都实现了单点登录。 其实这是第二次做这个功能了,原本以为会很顺利,但没想到折腾…...
FreeRTOS——消息队列
目录 一、概念及其作用 1.1概念 1.2特点 1.3工作原理 二、相关API 2.1创建队列 2.2任务中写队列 2.3任务中读队列 2.4中断中写队列 2.5中断中读队列 三、实现原理 3.1消息队列控制块 3.2消息队列的创建 3.3消息的发送 3.3.1任务中发送 3.3.2中断中发送 3.4消息的…...
c++11的动态类型
c17引入了any 和 variant,可以将任意数据类型统一用any或variant类型表示,在开发中还是能够带来很多便利的。在c11版本中,可以用下面这个例子,仿照实现一个Any类型。 #include <iostream> #include <stdexcept> #inc…...
大语言模型---Llama模型文件介绍;文件组成
文章目录 1. 概要2. 文件组成 1. 概要 在使用 LLaMA(Large Language Model Meta AI)权重时,通常会涉及到与模型权重存储和加载相关的文件。这些文件通常是以二进制格式存储的,具有特定的结构来支持高效的模型操作。以下以Llama-7…...
常见网络厂商设备默认用户名/密码大全
常见网络厂商的默认用户名/密码 01 思科 (Cisco) 设备类型:路由器、交换机、防火墙、无线控制器 默认用户名:cisco 默认密码:cisco 设备类型:网管型交换机 默认用户名:admin 默认密码:admin 02 华…...
【大数据分析机器学习】分布式机器学习
【作者主页】Francek Chen 【专栏介绍】 ⌈ ⌈ ⌈智能大数据分析 ⌋ ⌋ ⌋ 智能大数据分析是指利用先进的技术和算法对大规模数据进行深入分析和挖掘,以提取有价值的信息和洞察。它结合了大数据技术、人工智能(AI)、机器学习(ML&a…...
Go 语言已立足主流,编程语言排行榜24 年 11 月
Go语言概述 Go语言,简称Golang,是由Google的Robert Griesemer、Rob Pike和Ken Thompson在2007年设计,并于2009年11月正式宣布推出的静态类型、编译型开源编程语言。Go语言以其提高编程效率、软件构建速度和运行时性能的设计目标,…...
数字反向输出
数字反向输出 C语言代码C 代码Java代码Python代码 💐The Begin💐点点关注,收藏不迷路💐 小明听到广播里的数字后,总喜欢反着念给妈妈听。请聪明的你将小明听到的数字反向输出。 输入 输入为一个整型的四位数n 输出 …...
c++ std::stack总结
概念 std::stack 是 C 标准库中的一个容器适配器(Container Adapter)。它通常是基于其他容器(如 std::deque 或 std::vector)实现的,提供了一个后进先出(LIFO,Last In First Out)的…...
【C++习题】10.反转字符串中的单词 lll
题目: 链接🔗:557.反转字符串中的单词 lll 题目: 代码: class Solution { public:void Reverse(string &s, int start, int end){char tmp;while(start < end){tmp s[start];s[start] s[end];s[end] tmp;…...
【pyspark学习从入门到精通14】MLlib_1
目录 包的概览 加载和转换数据 在前文中,我们学习了如何为建模准备数据。在本文中,我们将实际使用这些知识,使用 PySpark 的 MLlib 包构建一个分类模型。 MLlib 代表机器学习库。尽管 MLlib 现在处于维护模式,即它不再积极开发…...
transformer.js(四): 模型接口介绍
前面的文章底层架构及性能优化指南介绍了transformer.js的架构和优化策略,在本文中,将详细介绍 transformer.js 的模型接口,帮助你了解如何在 JavaScript 环境中使用这些强大的工具。 推荐阅读 ansformer.js(二)&…...
java集合练习题
简答题:分析HashSet和treeSet分别如何去重的? TreeSet的去重机制: 如果你传入了一个Comparator匿名对象,就使用实现的compare去重,如果方法返回0,就认为是相同的元素/数据,就不添加,如果你没有传入一个Comp…...
微知-plantuml常用语法和要点以及模板?(note over、create、box,endbox、alt,else,end, autonumber)
文章目录 常见语法常用 线条类实线虚线斜箭头或奇数箭头 A ->(10) B: B->(10) A分割线:newpage 颜色类给箭头指定颜色 -[#red]->给某个note加颜色: note over Alice, Bob #FFAAAA: xxx给分组信息着色 alt#red 分组类alt xxx; else xxx; else xx…...
gitHub常用操作
gitHub常用操作 1、把项目拉下来2、添加上游仓库3、进入分支4、从上游仓库拉取更新 1、把项目拉下来 在对应项目的右上角点击fork,fork下来:将远程仓库复制到个人仓库 在创建好的分支文件夹下使用 git clone自己远程仓库下的http地址(fork…...
Mybatis Plus动态指定数据源
Java开发中一个项目连接多个数据源时,可能会有需要动态指定一个方法所使用的数据源的场景。例如不同的用户查询不同的数据源。 我遇到的需求是这样的:设计一个公共的数据字典组件,该组件需要连接数据源,使用方引入该组件后可以直…...
Python 爬虫 (1)基础 | 基础操作
一、基础操作 1、快速构建一个爬虫 ConvertCurl: https://curlconverter.com/选择URL,点击右键,选择 Copy >> Copy as cURL(bash) 安装JS环境:https://www.jb51.net/python/307069k7q.htm...
C++
目录 C 的发展总结:编辑 1. C 的早期发展(1979-1985) 2. C 标准化过程(1985-1998) 3. C 标准演化(2003-2011) 4. C11(2011年) 5. C14(2014年…...
Infineon(英飞凌) TLE985xQX 芯片电机工作电流、电压AD采样
其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 单片机芯片合集 文章目录 其他系列文章导航 文章目录 前言 一、选取合适的端口 1.通过 OP1、OP2 电流采集运放输入端口进行H桥驱动的电流采集。 2.通过 O_D_VBAT_AD_EN、I_A_VBAT_A…...
三极管工作原理,以及小电流,如何驱动大电流
因为研究【自动下载电路实现】,涉及到三极管内容,之前看过,现在回看之前的笔记,一点印象都没了,于是,想了个办法,记住它 个人联想,不喜绕道,只是帮助个人记忆的 标题也是…...
Haystack 的开源开发 LLM 应用设计框架
每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…...
彻底理解消息队列的作用及如何选择
一.为什么要使用消息队列? 使用消息队列,其实是需要根据实际业务场景来的,一般都是实际开发中,遇到了某种技术挑战,如果不使用MQ的话,业务实现起来比较麻烦,但是通过MQ就可以更快捷高效的实现业…...
【Java】二叉树:数据海洋中灯塔式结构探秘(上)
个人主页 🌹:喜欢做梦 二叉树中有一个树,我们可以猜到他和树有关,那我们先了解一下什么是树,在来了解一下二叉树 一🍝、树型结构 1🍨.什么是树型结构? 树是一种非线性的数据结构&…...
海洋通信船舶组网工业4G路由器应用
船舶是浩瀚海洋中探索与贸易的载体,更是船员们生活与工作的家园。为了在广阔的水域中搭建起稳定、高效的网络桥梁,工业4G路由器以卓越的通信组网能力,为船舶组网提供网络支持。 工业4G路由器以其强大的信号发射能力,确保船舶内部…...
字符串-07-判断两个IP是否属于同一子网
文章目录 1. 题目描述2. 思路3. 代码 1. 题目描述 IP地址是由4个0-255之间的整数构成的,用"."符号相连。 二进制的IP地址格式有32位,例如:10000011,01101011,00000011,00011000;每八…...
Django启用国际化支持(2)—实现界面内切换语言:activate()
文章目录 ⭐注意⭐1. 配置项目全局设置:启用国际化2. 编写视图函数3. 配置路由4. 界面演示5、扩展自动识别并切换到当前语言设置语言并保存到Session设置语言并保存到 Cookie ⭐注意⭐ 以下操作依赖于 Django 项目的国际化支持。如果你不清楚如何启用国际化功能&am…...
自定义协议
1. 问题引入 问题:TCP是面向字节流的(TCP不关心发送的数据是消息、文件还是其他任何类型的数据。它简单地将所有数据视为一个字节序列,即字节流。这意味着TCP不会对发送的数据进行任何特定的边界划分,它只是确保数据的顺序和完整…...
PHP Date 函数:日期和时间处理的全指南
PHP Date 函数:日期和时间处理的全指南 PHP Date 函数是 PHP 编程语言中用于处理日期和时间的核心函数之一。它提供了强大的功能,允许开发者轻松地格式化、计算和操作日期和时间值。本文将详细介绍 PHP Date 函数的用法,包括基本格式化、时间戳处理、时区设置以及一些高级特…...
C++设计模式:抽象工厂模式(风格切换案例)
抽象工厂模式(Abstract Factory)是一种创建型设计模式,其核心思想是:为一组相关或相互依赖的对象提供一个创建接口,而无需指定它们具体的类。简单来说,就是一个工厂可以生产一系列相关的对象。 我们接下来…...
社交媒体营销新趋势:如何通过海外平台提升品牌曝光度?
社交媒体不仅是简单的信息传播工具,更是连接用户与品牌之间的重要纽带。每天,有数以亿计的全球用户在不同平台上活跃,潜藏着巨大的市场潜力。对于企业来说,关键在于制定清晰的营销策略,精准把握不同社交平台的特性&…...
嵌入式C/C++编译常见问题与分析
#1 kcx.c:112:89: error: format specifies type unsigned int but the argument has type u32 * (aka unsigned int *) [-Werror,-Wformat] 报错原因: int kcx(u32 *trigger) { ERR_MSG("%s:failed. attr%d, trigger%u\n", __func__, attr_enable, trig…...
laravel 5.5 增加宏指令 joinSub, 省去->toSql() 和 addBinding($bindings);
laravel 5.5 增加宏指令 joinSub, 省去->toSql() 和 addBinding($bindings); 1. 在laravel5使用join 子查询时 $sub_query DB::table(table1)->select([table1.id, cate_id])->join(table2, table1.id, , table2.id)->where(table1.cate_id, 2)->orderBy(tabl…...
知识库搭建:大健康产业数字化转型的新引擎
随着数字经济的蓬勃发展,大健康产业正步入一个崭新的发展篇章。消费者对于健康的追求日益增长,促使大健康企业积极探索数字化路径,以提升供应链效率、控制成本,并在激烈的市场竞争中脱颖而出。在此过程中,一系列数字化…...
创建可重用React组件的实用指南
尽管React是全球最受欢迎和使用最广泛的前端框架之一,但许多开发者在重构代码以提高可复用性时仍然感到困难。如果你发现自己在React应用中不断重复相同的代码片段,那你来对地方了。 在本教程中,将向你介绍三个最常见的特征,表明是…...
蓝桥杯每日真题 - 第18天
题目:(出差) 题目描述(13届 C&C B组E题) 解题思路: 问题分析 问题实质是一个带权图的最短路径问题,但路径的权重包含两个部分: 从当前城市到下一个城市的路程时间。 当前城市的…...
MySQL中索引全详解
第一部分:什么是索引 索引在数据库中就像书的目录,能够快速定位数据位置,从而提升查询效率。没有索引时,数据库查询需要从头到尾扫描整个表(称为全表扫描),这在数据量大时非常耗时。有了索引后&…...
探索复合物TPP-PEG-Heparin的特性;磷酸三苯酯-聚乙二醇-肝素的线粒体靶向性
TPP-PEG-Heparin,即磷酸三苯酯(TPP)、聚乙二醇(PEG)和肝素(Heparin)的复合物,其特性融合了这三种成分的性质。 一、线粒体靶向性 TPP部分:具有线粒体靶向功能…...
ubuntu 配置 多个 git 客户端 账户
Git配置两个或多个账户 https://blog.csdn.net/mainking2003/article/details/134711865 git 提交 不用输入用户名、密码的方法(GIT免密提交) https://blog.csdn.net/wowocpp/article/details/125797263 git config 用法 https://blog.csdn.net/blueb…...
Web3与智能合约:区块链技术下的数字信任体系
随着互联网的不断发展,Web3代表着我们迈入了一个去中心化、更加安全和智能的网络时代。作为Web3的核心组成部分,区块链技术为智能合约的出现和发展提供了强有力的基础。智能合约不仅仅是自动化的代码,它们正逐步成为重塑数字世界信任体系的关…...
RocketMQ文件刷盘机制深度解析与Java模拟实现
引言 在现代分布式系统中,消息队列(Message Queue, MQ)作为一种重要的中间件,扮演着连接不同服务、实现异步通信和消息解耦的关键角色。Apache RocketMQ作为一款高性能的分布式消息中间件,广泛应用于实时数据流处理、…...
高级编程之结构化代码
背景:以下没结构化代码之前,定时器同步店铺信息的代码。 结构化的思想:SRP(单一职责),一个方法是做一件事,层次是相关的,逻辑和数据操作进行拆分,数据操作从业务流程上定…...
学习编程,学习中间件,学习源码的思路
01 看的多,内化不足 最近想复习一下编程相关的知识,在复习前我翻开了之前的一些笔记,这些笔记基本都是从书本、视频、博客等摘取记录的,看着这些笔记心里总结:看的多,内化不足。 02 整理大纲 为了解决这个…...