Fast.Framework ORM 于中秋节后 正式开源
Fast Framework
作者 Mr-zhong
开源项目地址 https://github.com/China-Mr-zhong/Fast.Framework
QQ交流群 954866406 欢迎小伙伴加入交流探讨技术
一、前言
Fast Framework 是一个基于NET6.0 封装的轻量级 ORM 框架 支持多种数据库 SqlServer Oracle MySql PostgreSql Sqlite
优点: 体积小、可动态切换不同实现类库、原生支持微软特性、流畅API、使用简单、性能高、模型数据绑定采用 委托+缓存、强大的表达式解析、子查询的原生支持、复杂表达式含成员变量解析,解析性能是目前常见框架中 No1 主要是有缓存的支持 、源代码可读性强。
缺点:目前仅支持Db Frist Code Frist 暂时不考虑 主要是需要花费大量时间和精力。
二、项目明细
名称 | 说明 |
---|---|
Fast.Framework | 接口实现类库(框架核心接口实现) |
Fast.Framework.Aop | Aop类库(基于微软DispatchProxy抽象类封装) |
Fast.Framework.Extensions | 扩展类库(主要扩展框架核心方法,方便使用) |
Fast.Framework.Interfaces | 接口类库(框架核心接口定义) |
Fast.Framework.Logging | 日志类库(主要实现自定义文件日志) |
Fast.Framework.Models | 模型 框架所用到的实体类 |
Fast.Framework.Utils | 工具类库 |
Fast.Framework.Test | 控制台终端测试项目 |
Fast.Framework.UnitTest | 单元测试项目 |
Fast.Framework.Web.Test | Web测试项目 |
三、核心对象
- Ado 原生Ado对象
IAdo ado = new AdoProvider(new DbOptions()
{
DbId = "1",
DbType = DbType.MySQL,
ProviderName = "MySqlConnector",
FactoryName = "MySqlConnector.MySqlConnectorFactory,MySqlConnector",
ConnectionStrings = "server=localhost;database=Test;user=root;pwd=123456789;port=3306;min pool size=3;max pool size=100;connect timeout=30;"
});
- DbContext 支持多租户 支持切换不同Ado实现类库 设置 ProviderName和FactoryName 即可
IDbContext db = new DbContext(new List<DbOptions>() {
new DbOptions()
{
DbId = "1",
DbType = DbType.MySQL,
ProviderName = "MySqlConnector",
FactoryName = "MySqlConnector.MySqlConnectorFactory,MySqlConnector",
ConnectionStrings = "server=localhost;database=Test;user=root;pwd=123456789;port=3306;min pool size=3;max pool size=100;connect timeout=30;"
}});
依赖注入
// 注册服务
builder.Services.AddScoped<IDbContext, DbContext>();
// 数据库选项支持Options接口注入 不是很理解的可以看代码实现
builder.Services.Configure<List<DbOptions>>(configuration.GetSection("DbConfig"));
// 产品服务类 通过构造方法注入
public class ProductService
{
/// <summary>
/// 数据库
/// </summary>
private readonly IDbContext db;
/// <summary>
/// 构造方法
/// </summary>
/// <param name="db">数据库</param>
public ProductService(IDbContext db)
{
this.db = db;
}
}
四、插入
- 实体对象插入
var product = new Product()
{
ProductCode = "1001",
ProductName = "测试商品1"
};
var result = await db.Insert(product).ExceuteAsync();
Console.WriteLine($"实体对象插入 受影响行数 {result}");
- 实体对象插入并返回自增ID 仅支持 SQLServer MySQL SQLite
var product = new Product()
{
ProductCode = "1001",
ProductName = "测试产品1"
};
var result = await db.Insert(product).ExceuteReturnIdentityAsync();
Console.WriteLine($"实体对象插入 返回自增ID {result}");
- 实体对象列表插入
var list = new List<Product>();
for (int i = 0; i < 2100; i++)
{
list.Add(new Product()
{
ProductCode = $"编号{i + 1}",
ProductName = $"名称{i + 1}"
});
}
var result = await db.Insert(list).ExceuteAsync();
Console.WriteLine($"实体对象列表插入 受影响行数 {result}");
- 匿名对象插入
var obj = new
{
ProductCode = "1001",
ProductName = "测试商品1"
};
//注意:需要使用As方法显示指定表名称
var result = await db.Insert(obj).As("product").ExceuteAsync();
Console.WriteLine($"匿名对象插入 受影响行数 {result}");
- 匿名对象列表插入
var list = new List<object>();
for (int i = 0; i < 2100; i++)
{
list.Add(new
{
ProductCode = $"编号{i + 1}",
ProductName = $"名称{i + 1}"
});
}
//注意:需要使用As方法显示指定表名称
var result = await db.Insert(list).As("Product").ExceuteAsync();
Console.WriteLine($"匿名对象列表插入 受影响行数 {result}");
- 字典插入
var product = new Dictionary<string, object>()
{
{"ProductCode","1001"},
{ "ProductName","测试商品1"}
};
//注意:需要显示指定类型否则无法重载到正确的方法,如果没有实体类型可用object类型并配合As方法显示指定表名称.
var result = await db.Insert<Product>(product).ExceuteAsync();
Console.WriteLine($"字典插入 受影响行数 {result}");
- 字典列表插入
var list = new List<Dictionary<string, object>>();
for (int i = 0; i < 2100; i++)
{
list.Add(new Dictionary<string, object>()
{
{"ProductCode","1001"},
{ "ProductName","测试商品1"}
});
}
//注意:需要显示指定泛型类型否则无法重载到正确的方法,如果没有实体可用object类型并配合As方法显示指定表名称.
var result = await db.Insert<Product>(list).ExceuteAsync();
Console.WriteLine($"字典列表插入 受影响行数 {result}");
五、删除
- 实体对象删除
var product = new Product()
{
ProductId = 1,
ProductCode = "1001",
ProductName = "测试商品1"
};
//注意:必须标记KeyAuttribute特性 否则将抛出异常
var result = await db.Delete(product).ExceuteAsync();
Console.WriteLine($"实体删除 受影响行数 {result}");
- 无条件删除
var result = await db.Delete<Product>().ExceuteAsync();
Console.WriteLine($"无条件删除 受影响行数 {result}");
- 表达式删除
var result = await db.Delete<Product>().Where(w => w.ProductId == 1).ExceuteAsync();
Console.WriteLine($"条件删除 受影响行数 {result}");
- 特殊删除
//特殊用法 如需单个条件或多个可搭配 WhereColumn或WhereColumns方法
var result = await db.Delete<object>().As("Product").ExceuteAsync();
Console.WriteLine($"无实体删除 受影响行数 {result}");
六、更新
- 实体对象更新
var product = new Product()
{
ProductId = 1,
ProductCode = "1001",
ProductName = "测试商品1"
};
//注意:标记KeyAuttribute特性属性或使用Where条件,为了安全起见全表更新将必须使用Where方法
var result = await db.Update(product).ExceuteAsync();
Console.WriteLine($"对象更新 受影响行数 {result}");
- 指定列更新
var result = await db.Update<Product>(new Product() { ProductCode = "1001", ProductName = "1002" })
.Columns("ProductCode", "ProductName").ExceuteAsync();
// 字段很多的话可以直接new List<string>(){"列1","列2"}
- 忽略列更新
var result = await db.Update<Product>(new Product() { ProductCode = "1001", ProductName = "1002" })
.IgnoreColumns("Custom1").ExceuteAsync();
// 同上使用方法一样
- 实体对象列表更新
var list = new List<Product>();
for (int i = 0; i < 2022; i++)
{
list.Add(new Product()
{
ProductCode = $"编号{i + 1}",
ProductName = $"名称{i + 1}"
});
}
//注意:标记KeyAuttribute特性属性或使用WhereColumns方法指定更新条件列
var result = await db.Update(list).ExceuteAsync();
Console.WriteLine($"对象列表更新 受影响行数 {result}");
- 匿名对象更新
var obj = new
{
ProductId = 1,
ProductCode = "1001",
ProductName = "测试商品1"
};
//注意:需要显示指定表名称 以及更新条件 使用 Where或者WhereColumns方法均可
var result = await db.Update(obj).As("product").WhereColumns("ProductId").ExceuteAsync();
Console.WriteLine($"匿名对象更新 受影响行数 {result}");
- 匿名对象列表更新
var list = new List<object>();
for (int i = 0; i < 2022; i++)
{
list.Add(new
{
ProductId = i + 1,
ProductCode = $"编号{i + 1}",
ProductName = $"名称{i + 1}"
});
}
//由于是匿名对象需要显示指定表名称,使用WhereColumns方法指定更新条件列
var result = await db.Update(list).As("Product").WhereColumns("ProductId").ExceuteAsync();
Console.WriteLine($"匿名对象列表更新 受影响行数 {result}");
- 字典更新
var product = new Dictionary<string, object>()
{
{ "ProductId",1},
{"ProductCode","1001"},
{ "ProductName","测试商品1"}
};
//注意:需要显示指定泛型类型否则无法重载到正确的方法并且使用WhereColumns方法指定条件列
var result = await db.Update<Product>(product).WhereColumns("ProductId").ExceuteAsync();
Console.WriteLine($"字典更新 受影响行数 {result}");
- 字典列表更新
var list = new List<Dictionary<string, object>>();
for (int i = 0; i < 2022; i++)
{
list.Add(new Dictionary<string, object>()
{
{ "ProductId",i+1},
{"ProductCode",$"更新编号:{i+1}"},
{ "ProductName",$"更新商品:{i + 1}"}
});
}
//注意:需要显示指定泛型类型否则无法重载到正确的方法并且使用WhereColumns方法执行条件列
var result = await db.Update<Product>(list).WhereColumns("ProductId").ExceuteAsync();
Console.WriteLine($"字典列表更新 受影响行数 {result}");
- 表达式更新
var product = new Product()
{
ProductId = 1,
ProductCode = "1001",
ProductName = "测试商品1"
};
var result = await db.Update(product).Where(p => p.ProductId == 100).ExceuteAsync();
Console.WriteLine($"表达式更新 受影响行数 {result}");
七、查询
- 单一查询
var data = await db.Query<Product>().FristAsync();
- 列表查询
var data = await db.Query<Product>().ToListAsync();
- 返回单个字典
var data = await db.Query<Product>().ToDictionaryAsync();
- 返回字典列表
var data = await db.Query<Product>().ToDictionaryListAsync();
- 分页查询
var page = new Pagination() { Page = 1, PageSize = 100 };
var data = await db.Query<Product>().ToPageListAsync(page);
- 计数查询
var data = await db.Query<Product>().CountAsync();
- 任何查询
var data = await db.Query<Product>().AnyAsync();
- 条件查询
var data = await db.Query<Product>().Where(w => w.ProductId == 1);
//需要调用返回数据结果的方法 例如:ToListAsync
- Like 查询
var data = await db.Query<Product>().Where(w => w.ProductName.StartsWith("左模糊") || w.ProductName.EndsWith("右模糊") || w.ProductName.Contains("全模糊"));
- Not Like查询
var data = await db.Query<Product>().Where(w => !w.ProductName.StartsWith("左模糊") || !w.ProductName.EndsWith("右模糊") || !w.ProductName.Contains("全模糊"));
//由于没有专门去扩展 Not Like 方法,可以用取反或使用比较变通实现 例如 w.ProductName.StartsWith("左模糊")==false
//Mysql举例 最终解析后的结果为 `ProductName` Like '%左模糊' = 0 这种用法数据库是支持的 相当于 Not Like
- Select查询 (选择字段)
var data = await db.Query<Product>().Select(s => new
{
s.ProductId,
s.ProductName
}).ToListAsync();
- 分组查询
var data = await db.Query<Product>().GroupBy(s => new
{
s.ProductId,
s.ProductName
}).ToListAsync();
- 分组聚合查询
var sql = db.Query<Order>().InnerJoin<OrderDetail>((a, b) => a.OrderId == b.OrderId).GroupBy((a, b) => new
{
a.OrderCode
}).Select((a, b) => new
{
a.OrderCode,
Sum_Qty = SqlFunc.Sum(b.Qty)//支持嵌套
}).ToListAsync();
- 排序查询
var data = await db.Query<Product>().OrderBy(s => new
{
s.CreateTime
}).ToListAsync();
//这是多个字段排序使用方法 还有其它重载方法
- Having查询
var data = await db.Query<Product>().GroupBy(s => new
{
s.ProductId,
s.ProductName
}).Having(s => SqlFunc.Count(s.ProductId) > 1).ToListAsync();
//必须先使用GroupBy方法 懂得都懂
- 联表查询
var data = await db.Query<Product>().
LeftJoin<Class1>((a, b) => a.ProductId == b.ProductId).ToListAsync();
// 右连接对应的是 RightJoin 内连接对应 InnerJoin
- 联合查询
var query1 = db.Query<Product>();
var query2 = db.Query<Product>();
db.Union(query1, query2);//联合
db.UnionAll(query1, query2);//全联合
//执行查询调用Toxx方法
- 查询并插入 仅支持同实例的数据库 跨库 个人还是建议 用事务分开写查询和插入
//方式1
var result1 = await db.Query<Product>().Where(w => w.ProductId == 1489087).Select(s => new
{
s.ProductCode,
s.ProductName
}).Insert<Product>(p => new
{
p.ProductCode,
p.ProductName
});
//方式2 需要注意的是 显示指定不带 列标识符 例如 `列名称1` 如有字段冲突 可自行加上标识符
var result2 = await db.Query<Product>().Where(w => w.ProductId == 1489087).Select(s => new
{
s.ProductCode,
s.ProductName
}).Insert("表名称 同实例不同库 可以使用 db.数据库名称.表名称 ", "列名称1", "列名称2", "`带标识的列名称3`");
//方式3 需要注意同方式2 一样
var result3 = await db.Query<Product>().Where(w => w.ProductId == 1489087).Select(s => new
{
s.ProductCode,
s.ProductName
}).Insert("表名称 同实例不同库 可以使用 db.数据库名称.表名称 ", new List<string>() { "列名称1" });
- In查询
// 方式1
var data1 = await db.Query<Product>().Where(w => SqlFunc.In(w.ProductCode, "1001", "1002")).ToListAsync();
// 方式2
var data2 = await db.Query<Product>().Where(w => SqlFunc.In(w.ProductCode, new List<string>() { "123", "456" })).ToListAsync();
// 方式3 需要动态更新IN值 使用这种
var list = new List<string>() { "123", "456" };
var data3 = await db.Query<Product>().Where(w => SqlFunc.In(w.ProductCode, list)).ToListAsync();
// 方法4 参数同上一样 单独分离IN和NotIN 是为了兼容匿名查询
var data4 = await db.Query<Product>().In("字段名称", "1001", "1002").ToListAsync();
- 子查询
var subQuery = db.Query<Product>().Where(w => w.ProductId == 1).Select(s => s.ProductName);
var sql1 = db.Query<Product>().Select(s => new Product()
{
Custom1 = db.SubQuery<string>(subQuery)// SubQuery 的泛型是根据你左边赋值的属性类型来定义
}).ToListAsync();
// 这种没有使用new 的 泛型可随意定义 实际作用就是避免 对象属性赋值类型冲突的问题
var sql2 = db.Query<Product>().Select(s => db.SubQuery<string>(subQuery)).ToListAsync();
八、Lambda表达式
- 高性能表达式动态缓存的支持
var list = new List<string>() { "1001" };
Expression<Func<Product, bool>> ex = p => SqlFunc.In(p.ProductCode, list);
for (int i = 1; i <= 3; i++)
{
list.Add($"动态添加参数{i}");
var stopwatch1 = new Stopwatch();
stopwatch1.Start();
var result = ex.ResolveSql(new ResolveSqlOptions()
{
DbType = Models.DbType.MySQL,
ResolveSqlType = ResolveSqlType.Where
});
stopwatch1.Stop();
Console.WriteLine($"解析耗时:{stopwatch1.ElapsedMilliseconds}ms {stopwatch1.ElapsedMilliseconds / 1000.00}s 解析Sql字符串:{result.SqlString}");
}
- 解析结果
解析耗时:14ms 0.014s 解析Sql字符串:p.`ProductCode` IN ( @2dac7a1c4aa64036aeee858b86fbd3a4_0,@2dac7a1c4aa64036aeee858b86fbd3a4_1 )
解析耗时:0ms 0s 解析Sql字符串:p.`ProductCode` IN ( @3b6b8fcb2f674cf490d44f97525c3c2b_0,@3b6b8fcb2f674cf490d44f97525c3c2b_1,@3b6b8fcb2f674cf490d44f97525c3c2b_2 )
解析耗时:0ms 0s 解析Sql字符串:p.`ProductCode` IN ( @4447c5d65e8a49c9b04549b7aac868b2_0,@4447c5d65e8a49c9b04549b7aac868b2_1,@4447c5d65e8a49c9b04549b7aac868b2_2,@4447c5d65e8a49c9b04549b7aac868b2_3 )
- 动态表达式
var ex = DynamicWhereExp.Create<Product>().AndIF(1 == 1, a => a.DeleteMark == true).Build();
var data =await db.Query<Product>().Where(ex).ToListAsync();
九、数据库日志
db.Aop.DbLog = (sql, dp) =>
{
Console.WriteLine($"执行Sql:{sql}");
if (dp != null)
{
foreach (var item in dp)
{
Console.WriteLine($"参数名称:{item.ParameterName} 参数值:{item.Value}");
}
}
};
十、事务
- 普通事务
try
{
await db.Ado.BeginTranAsync();//开启事务
// 执行 CRUD
await db.Ado.CommitTranAsync();//提交事务
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
await db.Ado.RollbackTranAsync();//回滚事务
}
- 更大范围的事务 使用微软 TransactionScope 对象
using (var tran = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
// 执行你的增删改查
// 可使用原生Ado或DbContext对象的CURD方法
tran.Complete();//提交事务
}
十一、多租户
- 改变数据库
//数据库配置可从Json配置文件加载
IDbContext db = new DbContext(new List<DbOptions>() {
new DbOptions()
{
DbId = "0",
DbType = Models.DbType.SQLServer,
ProviderName = "System.Data.SqlClient",
FactoryName = "System.Data.SqlClient.SqlClientFactory,System.Data",
ConnectionStrings = "server=localhost;database=Test;user=sa;pwd=123456789;min pool size=3;max pool size=100;connect timeout=30;"
},
new DbOptions()
{
DbId = "1",
DbType = Models.DbType.MySQL,
ProviderName = "MySqlConnector",
FactoryName = "MySqlConnector.MySqlConnectorFactory,MySqlConnector",
ConnectionStrings = "server=localhost;database=Test;user=root;pwd=123456789;port=3306;min pool size=3;max pool size=100;connect timeout=30;"
}});
db.ChangeDb("1");//切换到MySQL
十二、原生特性支持
/// <summary>
/// 产品
/// </summary>
[Table("ProductMain")]
public class Product
{
/// <summary>
/// 产品ID
/// </summary>
[Key]
public int ProductId { get; set; }
/// <summary>
/// 产品编号
/// </summary>
[Column("ProductCode")]//不标记默认取当前属性名称
public string ProductCode { get; set; }
/// <summary>
/// 自定义1
/// </summary>
[NotMapped]
public string Custom1 { get; set; }
}
十三、原生Ado使用
// 原始起步
// var conn = db.Ado.DbProviderFactory.CreateConnection();
// var cmd = conn.CreateCommand();
// 封装的方法分别以Execute和Create开头以及预处理 PrepareCommand 方法
// 该方法可以自动帮你处理执行的预操作,主要作用是代码复用。
// 当有非常复杂的查询 ORM不能满足需求的时候可以使用原生Ado满足业务需求
// 构建数据集核心扩展方法 分别有 FristBuildAsync ListBuildAsync DictionaryBuildAsync DictionaryListBuildAsync
var data = await db.Ado.ExecuteReaderAsync(CommandType.Text, "select * from product", null).ListBuildAsync<Product>();
Fast.Framework ORM 于中秋节后 正式开源的更多相关文章
- Fast.Framework ORM 试用
简介 Fast.Framework 是一款基于 .NET 6 封装的轻量级ORM框架,支持多种数据库(SQL Server.Oracle.MySQL.PostgreSQL.SQLite). 优点 性能 ...
- Restful.Data v1.0 - 轻量级数据持久层组件, 正式开源发布了
经过几个星期的优化调整,今天 Restful.Data 正式开源发布. 源码地址:https://github.com/linli8/Restful 今天不写那么多废话了,还是重新介绍一下 Restf ...
- 基于NET 6.0 封装的 Fast.Framework
Fast Framework 项目地址 https://gitee.com/China-Mr-zhong/Fast.Framework Author Mr-zhong Wechat 850856667 ...
- 如约而至:微信自用的移动端IM网络层跨平台组件库Mars已正式开源
1.前言 关于微信内部正在使用的网络层封装库Mars开源的消息,1个多月前就已满天飞(参见<微信Mars:微信内部正在使用的网络层封装库,即将开源>),不过微信团队没有失约,微信Mars ...
- 【转】如约而至:微信自用的移动端IM网络层跨平台组件库Mars已正式开源
网上看到关于微信官方的跨平台跨业务的终端基础组件Mars的介绍文章,转载这这里.源代码: https://github.com/Tencent/mars作者:男人链接:https://zhuanlan ...
- 中文编程语言Z语言开源正式开源!!!
(Z语言基于.NET环境,源码中有很多高技术的代码,让更多的人知道对大家有会有很好的帮助,请管理员一点要批准放在首页) 本人实现的中文编程语言Z语言现在正式开源,采用LGPL协议. 编译器核心的网址为 ...
- fir.im Log Guru 正式开源,快速找到 iOS 应用无法安装的原因
很开心的宣布 Log Guru 正式开源! Log Guru,是 fir.im 开发团队创造的小轮子,用在 Mac 电脑上的日志获取,Github 地址:FIRHQ/LogGuru. Log Guru ...
- Swift 正式开源, 包括 Swift 核心库和包管理器
Swift 正式开源!Swift 团队很高兴宣布 Swift 开始开源新篇章.自从苹果发布 Swfit 编程语言,就成为了历史上发展最快的编程语言之一.Swift 通过设计使得软件编写更加快速更加安全 ...
- 微软.NET Framework 4.5.2 RTM正式版
今天,微软.NET开发团队发布.NET Framework 4.5.2 RTM正式版.新版框架继续高度兼容现有的.NET Framework 4.4.5.4.5.1等版本,该版本框架与旧版的.NET ...
随机推荐
- raid划分及创建
RAID 的划分 RAID 0 - RAID 0是最早出现的,是数据分条技术.组建磁盘阵列中最简单的一种形式,可以提高整个磁盘的性能和吞吐量,利用率100%,缺点:一但磁盘损坏,raid0将失效,数据 ...
- WPF 视频硬解码渲染播放(无空域)(支持4K、8K、高帧率视频)
MediaWPF 基于 .NET 6 实现视频硬解码渲染Demo(无空域问题) 代码实现仅供学习参考 本项目视频渲染通过显卡进行视频解码,CPU几乎不参与工作,并且不存在令人烦躁的空域问题. 在播放摄 ...
- arcgis创建postgre企业级数据库
什么是企业级地理数据库? 企业级地理数据库(ArcSD Enterprise,sde)是和 arcGIS 套件集成程度最高的地理数据库:创建时需要用到安装 arcGIS Server 时的 [ecp ...
- 训练一个图像分类器demo in PyTorch【学习笔记】
[学习源]Tutorials > Deep Learning with PyTorch: A 60 Minute Blitz > Training a Classifier 本文相当于 ...
- PaddleOCR系列(一)--环境搭建
官方建议使用他们的docker镜像,所以我们按照他们建议的来. 环境搭建其实很简单,其实不需要在宿主机上配置cuda及cudnn,只需要保证宿主机上的cuda大于docker镜像中的就可以了. 所以我 ...
- Kafka启动遇到ERROR Exiting Kafka due to fatal exception (kafka.Kafka$)
------------恢复内容开始------------ Kafka启动遇到ERROR Exiting Kafka due to fatal exception (kafka.Kafka$) 解决 ...
- 基于ABP实现DDD--DDD相关概念
什么是DDD呢?领域驱动设计[DDD]是一种针对复杂需求的软件开发方法.将软件实现与不断发展的模型联系起来,专注于核心领域逻辑,而不是基础设施细节.DDD适用于复杂领域和大规模应用,而不是简单的C ...
- 2022-07-21 第四组 java之继承
目录 一.继承 1.概念 2.语法 3.父类成员访问 3.1 子类访问父类的成员变量 3.1.1 子类和父类中不存在同名的成员变量 3.1.2 子类和父类中不存在同名的成员变量 3.2 子类中访问父类 ...
- esp8266模拟输入(ADC)检测问题
今天使用esp12f读取A0数据时一直出现错误; Serial.println(analogRead(A0));读取值一直为1024 因为前段时间一直用的是开发板,读取电压值正常 而从昨天换为了esp ...
- lombok的常用注解
出处: https://blog.csdn.net/sunnyzyq/article/details/119992746 1. @Accessors 源码 我们打开 @Accessors 的源码可以看 ...