公司动态
722:零侵入;DBG;
1零侵入注入零侵入 不动别人的代码常规做法侵入式 你要加功能得去改 Program.cs、App.xaml.cs → 主程序代码被你改了 零侵入 你写一个独立的库放个实现了 IHostingStartup 的类 → 主程序一行不用动自动发现并加载注入 往里塞东西// 在 Configure 里你想加什么就加什么publicvoidConfigure(IWebHostBuilderbuilder){builder.ConfigureServices(services{services.AddSingletonIMyService,MyService();// 注入新服务});}对比侵入式 零侵入 注入 主程序 App.cs 你的插件.cs ┌──────────────┐ ┌────────────────┐ │ 改这行 │ │ IHostingStartup │ │ 加那行 ←── 你插进去的 │ Configure() │ │ 删第三行 │ │ 往里塞服务 │ └──────────────┘ └────────────────┘ ↓ 自动被主程序发现并执行零侵入 不碰原代码注入 把自己的东西塞进容器。两者配合实现插件式扩展。2DBG刚才所有日志前面的[DBG]Debug调试级别。日志等级[VRB] Verbose ─ 最啰嗦什么都记 [DBG] Debug ─ 调试用启动/初始化信息 [INF] Information ─ 常规信息如 Now listening on... [WRN] Warning ─ 警告可能有问题 [ERR] Error ─ 出错了 [FTL] Fatal ─ 致命错误程序要挂这个项目里注册用的是 Serilog配置了按级别输出[DBG] ≈ 给开发人员看的用户看不见 [INF] ≈ 关键节点信息如启动成功 [ERR] ≈ 需要关注的错误所以之前那些带[DBG]的日志都是调试级别正常运行时可能就不会输出只在调试模式下才记录。不影响功能也不代表有问题。32026-07-21 15:13:25.556 [INF] Hosting environment: Production人话“当前运行环境生产模式”为什么是 ProductionASP.NET Core 默认就是Production除非你显式设置了环境变量ASPNETCORE_ENVIRONMENTDevelopment → 开发模式详细信息、开发者异常页 ASPNETCORE_ENVIRONMENTStaging → 预发布 不设置 → 默认 Production对项目的影响模式影响Development开发者异常页、详细错误信息Production当前不暴露内部错误详情、性能优化跑在Production是合理的。42026-07-21 15:13:25.556 [INF] Application started. Press CtrlC to shut down.52026-07-21 15:13:25.891 [DBG] Creating DbConnection.这是整个数据库操作的第一步——创建连接对象。完整顺序回顾15:13:25.891 Creating DbConnection ← 你这条第1步准备连接对象 15:13:25.897 Created DbConnection (2ms) ← 连接对象创建完毕 15:13:25.903 Creating DbCommand ← 准备 SQL 命令 15:13:25.908 Initialized DbCommand (5ms) ← 命令就绪 15:13:25.917 Opening connection ← 真正打开连接 15:13:25.933 Opened connection ← 连上了Create创建 → Open打开 → Execute执行 ↑你在这里创建连接对象 ≠ 打开连接。就像买了根网线 ≠ 插上去通了。Creating DbConnection是 new 了一个连接对象16ms 后才真正Opening connection连数据库。DbConnectionDbConnection 跟数据库之间的那根线EF Core 用它连 MySQL。类比DbConnection 电话线 Creating DbConnection → 准备话机、扯好线 Opening connection → 拨号 Opened connection → 接通了可以说话 Executing command → 说话发 SQL Close / Dispose → 挂电话代码里你不需要手动管EF Core 和连接池自动处理awaitusingvarcontextawait_factory.CreateDbContextAsync();// DbConnection 由 EF Core 内部创建和打开你感知不到varresultawaitcontext.Components.FindAsync(1);// 内部获取连接 → 打开 → 执行 SQL → 返回结果 → 连接归还连接池不是关闭// await using 结束DbContext 归还池连接池DbConnection 也不是用完就销毁的 ┌──────────────────────┐ │ 连接池 (MySQL) │ │ [空闲] [空闲] [空闲] │ ← 一直保持打开下次直接用 └──────────────────────┘ 比每次都 new open 快得多DbContext 池1024个管理的是 C# 对象DbConnection 的底层连接是 MySQL 的连接池在管两个池各管各的。62026-07-21 15:13:25.897 [DBG] Created DbConnection. (2ms).连接对象创建完成花了 2ms。这 2ms 做了什么new MySqlConnection(Serverlocalhost;Databasexray_v1;...) ↓ 检查连接字符串格式、初始化连接池引用 ↓ 打印Created DbConnection (2ms)此时连接还没打开电话线准备好了但没拨号16ms 后才Opening connection。891ms: Creating → 897ms: Created (2ms) → 917ms: Opening → 933ms: Opened 创建对象 创建完成 拨号 通了就是确认一句连接字符串没问题对象 new 出来了。72026-07-21 15:13:25.903 [DBG] Creating DbCommand for ‘ExecuteScalar’.连接对象好了现在准备 SQL 命令。线准备好了 → 准备要说的话 Creating DbCommand 拼好这句 SQL查一下这些表在不在下一步 908msInitialized DbCommand拼好了。ExecuteScalar前面解释过快速回顾执行一条 SQL只返回一个值。不是返回多行表数据 → 不是 ExecuteReader 不是插入修改删除 → 不是 ExecuteNonQuery 就是一个值 → ExecuteScalar这里查的是表存在吗→ 返回一个数字4 代表有0 代表没有。分开讲1. ExecuteScalar是一种执行方式不是 SQL 内容。ExecuteScalar → 执行查询只返回一个值如数字 4 ExecuteReader → 执行查询返回多行数据 ExecuteNonQuery → 执行更新返回影响行数2. 检查表是否存在的 SQL是查询的内容EF Core 内部自动生成的类似-- EF Core 帮你写的你不用管SELECT表数量FROMMySQL系统表WHERE表名IN(...)3. EF Core 内部指你看不到的 EF Core 源码里的逻辑。拼起来EF Core 内部生成 SQL 查一下那 4 张表在不在 ← 自动的你不用写 ↓ 包装成 DbCommand标记为 ExecuteScalar 类型 ← 执行方式返回一个值 ↓ 发给 MySQL ← 查到结果在/不在ExecuteScalar 返回单个值的执行方式。检查表是否存在的 SQL EF Core 自动生成的查询内容。两回事一个是怎么执行一个是在查什么。82026-07-21 15:13:25.907 [DBG] Created DbCommand for ‘ExecuteScalar’ (1ms).命令对象包好了耗时 1ms。下一步是连接数据库执行。对象DbCommand for ‘ExecuteScalar’ 检查表是否存在的 SQL对理解正确。DbCommand for ExecuteScalar │ ├── 包裹的 SQL 这 4 张表在不在 │ └── 执行方式 ExecuteScalar返回一个数字三者关系DbCommand 对象信封 └── 里面装着检查表是否存在的 SQL信的内容 └── 执行类型标记ExecuteScalar只需要一个返回值92026-07-21 15:13:25.908 [DBG] Initialized DbCommand for ‘ExecuteScalar’ (5ms).初始化完成命令就绪可以执行。从创建 (903ms) 到初始化 (908ms) 共 5ms。对。Created创建好了→Initialized就绪随时可以发→Opening开始连数据库。Initialized就是摩拳擦掌准备上场的那一步。10[Parameters[], CommandType‘Text’, CommandTimeout‘30’]SELECT CASE WHEN COUNT(*) 0 THEN FALSE ELSE TRUE ENDFROM information_schema.tablesWHERE table_type ‘BASE TABLE’ AND table_schema ‘_v1’这条日志显示的是 EF Core 执行 SQL 的完整命令信息[Parameters[], CommandTypeText, CommandTimeout30]分解解释Parameters[]— 参数为空。这条 SQL 不需要外部传参表名_v1是直接写在 SQL 里的硬编码字符串。CommandType‘Text’— 命令类型是文本也就是一条原始 SQL 字符串。EF Core 直接把这串 SELECT 语句发给 MySQL不走存储过程。CommandTimeout‘30’— 超时时间 30 秒。如果这条查询 30 秒内没返回结果就会抛超时异常。下面的 SQL 就是上一轮讲过的EnsureCreatedAsync 在检查_v1库里到底有没有表。COUNT(*) 0→ 没表就返回 TRUE需要建表有表返回 FALSE啥也不干。简单说就是 EF Core 启动时问 MySQL“兄弟_v1 库里有表吗没有的话我帮你建。” 这条日志就是这次询问的完整记录。EnsureCreatedAsync是什么这是 EF Core 提供的一个方法一句话概括“库和表不存在就自动建已经存在就啥也不干。”它怎么跑的AppDbContextExtensions.cs注册了一个后台服务internalclassEnsureDatabaseCreatedHostedService:BackgroundService{protectedoverrideasyncTaskExecuteAsync(CancellationTokenstoppingToken){usingvarcontext_dbContextFactory.CreateDbContext();// ← 从池子拿一个 DbContextawaitcontext.Database.EnsureCreatedAsync(stoppingToken);// ← 核心自动建库建表}}程序启动 → 这个服务自动运行 → 调用EnsureCreatedAsync。它干了什么分步骤步骤做什么对应你看到的日志1打开 MySQL 连接Opening connection to _v1 on localhost2查information_schema.tables看库里有没有表SELECT ... FROM information_schema.tables WHERE table_schema _v13a有表→ 直接结束啥也不做日志就停了3b没表→ 根据你的AppDbContext里定义的 4 个实体类自动生成CREATE TABLE语句并执行你会看到一堆 CREATE TABLE 日志和另一个方法MigrateAsync的区别EnsureCreatedAsyncMigrateAsync适用场景开发/小项目不用迁移文件生产环境用迁移文件管理版本建库建表✅ 自动✅ 通过迁移文件表已存在时啥也不干执行未应用的迁移表结构变了❌不会更新需要删库重建✅ 通过新迁移文件增量更新本项目用哪个✅ 这个❌你们项目注释也写了自动建表EnsureCreated 适用于无迁移文件的场景。简单类比就像你租了个空房子MySQL 服务器进门时先看看卧室厨房有没有家具查information_schema.tables没有 → 帮你搬进来CREATE TABLE已经有了 → 直接合租不动你的东西所以你日志里那条SELECT就是看有没有家具这一步。后续没看到CREATE TABLE说明数据库之前已经建好了。这是筛选条件限定只统计用户真正创建的表拆解WHERE table_type BASE TABLE ← 只要实体表 AND table_schema _v1 ← 只看 _v1 这个库为什么加table_type BASE TABLEMySQL 每个库里不只有你建的表还有五花八门的东西table_type是什么举例BASE TABLE你建的实体表recipes、componentsVIEW视图虚拟表查询结果伪装成的表SYSTEM VIEW系统视图information_schema自己的表不算在内如果不加这个过滤COUNT(*)可能把视图也算进去。举个例子假设你的库是这样的xray_v1/ ├── recipes ← BASE TABLE你建的 ├── components ← BASE TABLE你建的 ├── my_view ← VIEW视图不算实体表不加过滤COUNT(*) 3→TRUE加了过滤COUNT(*) 2→TRUEEF Core 要确认的是有没有实体表需要它管视图不是它建的它不管所以要过滤掉。table_schema _v1锁定只看_v1这一个库别把其他库的表算进来。information_schema.tables存的是整个 MySQL 服务器上所有库的所有表信息。11这是 X-Ray 设备上电启动的标准流程加载轴限位配置 → 读取运动轴的软/硬限位参数轴能跑多远、别撞了 通电 X-Ray 光管 → 给 X 射线源上电核心成像部件类似灯泡点亮 扫图丢弃投影数 写入PLC → 告诉 PLC扫描时开头丢掉几帧图像去除不稳定帧 进出板方向 写入PLC → 告诉 PLCPCB 板从左进右出还是右进左出 Smema模式 写入PLC → 告诉 PLC上下料通讯协议模式SMEMA 是产线设备通讯标准 正在连接复判站 Socket → 连到复判工位检测完人工复核的那个工位整体流程数据库 ✅ → 加载机械配置 → 点亮光管 → 发参数给 PLC → 连复判站PLC 是设备的大脑前面几步都在往 PLC 里写运行参数。光管、轴限位、进出板、SMEMA 这些都是实际物理硬件的初始化说明程序已经从软件准备进入了硬件就绪阶段。122026-07-21 15:13:27.471 [INF] [SupXDriver] 初始化开始2026-07-21 15:13:27.765 [INF] [SupXDriver] 搜索到1个设备使用设备02026-07-21 15:13:27.767 [INF] [SupXDriver] 校准中请稍后…2026-07-21 15:13:33.260 [INF] [SupXDriver] 校准结束2026-07-21 15:13:33.261 [INF] [SupXDriver] 初始化成功光管驱动初始化一次教科书级别的硬件自检流程初始化开始 → 驱动启动 搜索到1个设备使用设备0 → 扫描到 1 个 X-Ray 探测器/光管选第 0 个第一个 校准中请稍后... → 自动校准探测器跟光管对位、参数自整定 校准结束 → 校准完成花了约 5.5 秒 初始化成功 → 驱动就绪关键信息搜索到 1 个设备— 说明硬件连接正常驱动能找到探测器。如果这条日志说搜索到 0 个设备那就是硬件线没插或驱动没装。校准 5.5 秒27.767 → 33.260— 这是探测器在做自动校准比如暗场校正、增益校准之类的确保图像质量正常。为什么硬件初始化比连数据库慢数据库操作是纯 CPU 网络毫秒级。硬件校准是真物理设备在跑秒级5.5 秒非常正常。整体启动时序已经推进到数据库 ✅ → PLC 参数 ✅ → 光管通电 ✅ → 探测器校准 ✅硬件层基本就绪接下来就该加载业务模块了。132026-07-21 15:13:33.264 [INF] 图像服务初始化完成相机读取任务已启动2026-07-21 15:13:33.266 [INF] [APP] 自动清理过期文件…两步收尾动作图像服务初始化完成相机读取任务已启动探测器校准完图像采集服务正式跑起来了。相机读取任务已启动说明后台起了一个持续循环任务不断从探测器拿图像数据有板子进来就采图。[APP] 自动清理过期文件...打扫卫生——把过期的日志、临时图片、缓存文件删掉免得硬盘撑爆。当前启动进度数据库 ✅ → PLC 参数 ✅ → 光管通电 ✅ → 探测器校准 ✅ → 图像采集启动 ✅ → 清理临时文件...到这里硬件和基础服务基本全部就绪设备处于待机状态就等 PCB 板进来触发检测流程了。