
任务调度后端【免费下载链接】quartznetQuartz Enterprise Scheduler .NET项目地址https://gitcode.com/gh_mirrors/qu/quartznet点击查看免费下载导读Quartz.Tests.Integration.Seeder是 Quartz.NET 仓库中一个特殊的控制台项目它引用已发布的 Quartz 3.20.0 包向一套 3.20 数据库 schema 中写入与真实旧版本完全一致的业务数据从而为 4.0 升级链路提供旧数据来源。本文将以 src/Quartz.Tests.Integration.Seeder/README.md 为主线完整讲解该播种器的设计动机、种子内容、命令行用法与 fixture 再生成流程并深入其源码说明每一步底层实现。读完本文你将理解 Quartz.NET 是如何在新旧程序集身份冲突的前提下用真实版本数据验证数据库迁移脚本与 JSON 兼容性的。为什么需要一个独立的播种进程Quartz 4.0 升级的最大风险不是空 schema 能否迁移而是数据库里已经存在的 3.x 行能否被 4.0 正确读取。仓库中的UpgradeRehearsalTest需要在非空的 3.20 schema 上执行 4.0 迁移LegacyJsonPayloadTest需要读取3.20 进程真实产出的 blob 数据而不是手工誊写的字面量。但这里有一个绕不开的矛盾已发布的Quartz3.20.0 包与本仓库的Quartz具有相同的程序集身份assembly identity因此没有任何一个进程可以同时引用两者。解决方式就是让播种器成为独立的进程——它只编译并运行在 3.20.0 上用 3.20 自己的 ADO job store 写入数据测试进程再以 4.0 读取这些数据。正如 LegacySeeder.cs 的注释所写它用一个已发布的 Quartz 3.20.0 及其自身的 ADO job store向 3.20 schema 填充 4.0 升级必须承载的行。版本锁定也是刻意的。在 Quartz.Tests.Integration.Seeder.csproj 中三个包引用全部使用字面版本号并通过VersionOverride3.20.0固定绝不使用浮动版本原因正如项目文件注释所说——fixture 的全部意义就在于一个具名的已发布版本。同时该程序集刻意不签名SignAssemblyfalse因为它只是临时进程永远不会发布。播种器写入什么数据播种器在同一个调度器名scheduler name和同一个表前缀table prefix下写入以下六类数据见 README1. 五大家族触发器 blob 触发器3.20 的 ADO 存储为五种触发器家族提供了持久化委托persistence delegatesimple、cron、calendar-interval、daily-time-interval、recurrence见 LegacySeeder.cs 的AllFamilies数组。播种器在seed组中各写一个随后又在blob组中再写同样的家族——这组由BlobStorageOverride拒绝提供持久化委托强制它们进入QRTZ_BLOB_TRIGGERS表使该表持有每种形状的真实负载。为什么必须用这种方式构造 blobQRTZ_BLOB_TRIGGERS在正常情况下只保存没有持久化委托的触发器——实践中就是应用自定义的ITrigger实现。但播种器不能用应用自己的触发器blob 会命名本程序集里的类型而读取它的 4.0 进程没有该程序集行只会证明无法解析的 blob 依然无法解析。所以 BlobStorageOverride.cs 选择对blob这一个触发器组拒绝委托让出厂自带的触发器类型走 blob 路径——这样 blob 里的字节就是 3.20 序列化器写出的标准负载正是 4.0 读取器必须理解的格式。实现上它只重写了 3.20 六个已发布 driver delegateSQLite、SqlServer、PostgreSQL、MySQL、Oracle、Firebird各自的一个protected virtual方法FindTriggerPersistenceDelegate当触发器组名为blob时返回null其余逻辑——每一条 SQL、每一个参数绑定——全部是发布版原样。2. 携带 EXECUTION_GROUP 与 PREFERRED_NODE 的触发器3.18 引入了EXECUTION_GROUP3.19 引入了PREFERRED_NODE。两者在 3.20 上都是真实存在的列且必须在升级后原样保留。播种器在 LegacySeeder.cs 中写入一个名为pinned的触发器同时设置.WithExecutionGroup(seeded-execution-group)与.WithPreferredNode(options.InstanceId)。3. 全部六种日历 链式日历播种器写入annual、holiday、monthly、weekly、daily、cron六种日历外加一个链式组合CronCalendar作为HolidayCalendar的基日历见 LegacySeeder.cs。每种日历都附带探测时刻probe instants与 3.20 给出的答案IsTimeIncluded结果记录在 manifest 中。升级后 4.0 会对同样时刻给出同样问题若日历 blob 反序列化后变成了包含一切的形态测试就会失败——这正是探测点设计的意义。为保证探测有效LegacySeeder.cs 还做了一个防御如果某日历的所有探测点答案全部相同要么全包含、要么全不包含就抛异常拒绝记录——因为这样的探测集无法发现日历丢失了排除规则的缺陷。4. 覆盖全部值类型的 JobDataMap3.20 写入的 job data map 必须覆盖4.0 的 JSON 写入门write gate允许的每一种值类型因为这些值才是应用升级后仍能继续存储的值其 3.x 写出的 blob 必须继续可读。SeedValues见 SeedValues.cs用一张声明表同时驱动写入与 manifest 描述共 16 项KeyKind值textstringstagingflagbooltruecountint42biglong9_000_000_000Lratiodouble2.5dsmallfloat1.5fmoneydecimal12.34mlettercharqmomentdateTime2024-07-01T03:30:00ZoffsetMomentdateTimeOffset同一时刻的DateTimeOffsetspantimeSpan42 秒idguid固定 GUIDdaydateOnly2024-07-01timeOfDaytimeOnly03:30:00weekdayenumDayOfWeek.Fridaylabelsdictionary{ alpha: 1, beta: 2 }其中Dictionarystring, string有它自己的意义这就是 issue #3582 的形状——3.x 的 Newtonsoft 写入器会为它装饰$type而 4.0 的写入器不会。此外还有一个门外的值JobKey。4.0 会拒绝写入它但 3.x 数据库完全可能存着一个。播种器把它放在独立的一个 job名为exotic上而不是放在所有触发器指向的那个 worker job 上——否则一个不可读的条目会把整个演练拖垮见 SeedValues.cs 与 LegacySeeder.cs。5. 暂停的触发器组与暂停的 Job 组播种器构造了暂停前存储与暂停后存储两种成员见 LegacySeeder.cs暂停触发器组pausedtriggers先存before触发器再调用PauseTriggers暂停该组然后存after触发器。后者只能因为组已暂停而处于暂停状态——这正是QRTZ_PAUSED_TRIGGER_GRPS表存在的意义。暂停 Job 组pausedjobs3.x 暂停 Job 组时什么都不会记录所以暂停后存入的after触发器并不是暂停的。manifest 特意把这一点记录下来因为这是操作者在升级中最可能感到意外的事实。6. 一条执行中被放弃的 FIRED_TRIGGERS 行最后一种种子数据模拟的是节点崩溃播种器先调度一个孤儿 joborphan启动调度器使其触发LegacyWorkerJob在执行中阻塞数据 map 中的blockForever键随后进程被整体杀掉留下一条仍在飞行中的QRTZ_FIRED_TRIGGERS行——这正是升级后 4.0 需要恢复的行。干净的关机会把这行清理掉所以播种器在成功路径上也是用Environment.Exit(0)结束而非优雅关闭见 Program.cs。LegacyWorkerJob见 LegacyWorkerJob.cs也是所有种子行共用的唯一 job 类型这样 4.0 进程只需要一个类型别名就能运行 3.20 存储的一切。它的类型名会写入JOB_CLASS_NAME而它命名的程序集是演练进程没有的——这正是每个升级时重命名了 job 类型的应用所处的境地。manifest 会原样携带存储拼写供演练用UseTypeLoader(o o.Map(...))映射这也是迁移指南教给升级用户的机制在这里得到了真实数据的检验。运行播种器命令行参数播种器的命令行由 SeedOptions.cs 定义并校验。完整参数如下参数含义默认值--dialect数据库方言必填sqlite|sqlServer|postgres|mysql_innodb|oracle|firebird--connection-string对应数据库的连接字符串必填--serializer序列化器必填jsonNewtonsoft|stjSystem.Text.Json--output写入seed.json的目录必填--table-prefix播种所用的表前缀QRTZU_--scheduler-name调度器实例名Quartz320Upgrade--instance-id调度器实例 idseed-node--schema先运行的 fresh-install 脚本可选仅 SQLite--fixture-output转储 blob 列的目录可选SeedOptions.Parse会强制要求四个必填参数并校验--serializer只能是json或stj参数解析失败时返回退出码 2 并打印用法见 Program.cs。方言名与仓库中database/tables/tables_dialect.sql的拼写保持一致因此调用者传给播种器的词与命名脚本的词是同一个见 LegacyDialect.cs。LegacyDialect负责把方言名翻译成三件事3.20 的DbProvider认识的 provider 名如SQLite-Microsoft、SqlServer、Npgsql、MySqlConnector、OracleODPManaged、Firebird、播种器自己的 driver delegate 类型、以及播种器回读数据用的DbConnection。--schema刻意只支持 SQLite其他方言的 fresh-install 脚本都是为该数据库自带的命令行客户端写的GO、/、SET TERM等批处理分隔符演练测试正是通过那个客户端在容器内原样运行脚本——这是唯一能保证告诉用户运行的脚本就是实际运行的脚本的方式。播种器若在这里自行拆分脚本只会变成一份更糟的副本。而 SQLite 没有这样的客户端也不需要容器SchemaScript.cs 就自己按分号切分语句并专门避开BEGIN … END触发器体——那是脚本中唯一的复合结构。seed.json演练断言的词汇表播种结束时Program.cs 会把 manifest 以UTF-8 无 BOM格式写入seed.json与仓库其他文件一致。SeedManifest见 SeedManifest.cs的定位非常关键它记录的是3.20 实际存储了什么——从 3.20 调度器与数据库表本身读回而不是播种器请求了什么。因为演练要回答的问题是4.0 是否看到 3.20 写的东西而一份基于播种器意图构建的 manifest 可能两边一致却都与数据库不符。manifest 中因此包含从表中读回的TRIGGER_TYPE/TRIGGER_STATE、从列中读回的JOB_CLASS_NAME原始拼写、暂停触发器组行、被放弃的 fired trigger 行、日历探测点及其答案、job data map 的值类型与不变文本形式文本而非原始值因为 JSON 无法区分decimal与double且断言要验证 4.0 的强类型访问器能把存储形态还原成 3.20 收到的类型。SeedManifest.cs还有一个实现细节它被源码编译进Quartz.Tests.Integration而非程序集引用——因为播种器构建在已发布的 3.20 包上任何对它的程序集引用都会撞上程序集身份冲突。因此该文件的一切都是 BCL-only绝不能出现任何 Quartz 类型。当传入--fixture-output时BlobDump.cs 会把四个 blob 列逐字节写出为文件job-data-map.json、trigger-job-data-map.json、七个calendar-*.json、按序列化器各自可用的trigger-*.json。逐字节是硬要求不做任何重排、缩进或再编码列里是什么文件里就是什么。Oracle 的 LOB 类型会被转成字节流其他数据库直接取byte[]。重新生成提交的 fixturessrc/Quartz.Tests.Unit/TestData/Legacy/3.20/由两次 SQLite 运行产出其自身 README 也如此说明分为stj/与newtonsoft/两个子目录。注意它们不是同一份数据的两种编码而是两种序列化器在 3.x 默认设置下真实写出的不同形状System.Text.Json 是带TriggerType判别字段的判别形式NewtonsoftRegisterTriggerConverters保持默认false则是携带$type的普通对象图且Dictionarystring, string值自带$type#3582 形状。从仓库根目录先执行dotnet build src/Quartz.Tests.Integration.Seeder然后运行务必先删除临时数据库文件schema 脚本是 fresh install不会执行第二次dotnet artifacts/bin/Quartz.Tests.Integration.Seeder/debug/Quartz.Tests.Integration.Seeder.dll \ --dialect sqlite --connection-string Data Source/tmp/seed-stj.db; --serializer stj \ --schema src/Quartz.Tests.Integration/SchemaBaselines/3.20/tables_sqlite.sql \ --table-prefix QRTZ_ --output /tmp/seed-stj \ --fixture-output src/Quartz.Tests.Unit/TestData/Legacy/3.20 dotnet artifacts/bin/Quartz.Tests.Integration.Seeder/debug/Quartz.Tests.Integration.Seeder.dll \ --dialect sqlite --connection-string Data Source/tmp/seed-json.db; --serializer json \ --schema src/Quartz.Tests.Integration/SchemaBaselines/3.20/tables_sqlite.sql \ --table-prefix QRTZ_ --output /tmp/seed-json \ --fixture-output src/Quartz.Tests.Unit/TestData/Legacy/3.20fixtures 中的时间戳属于捕获时刻本身重新生成会改变StartTimeUtc与NextFireTimeUtc因此没有任何断言针对它们。一个值得注意的 fixture 缺口newtonsoft/下没有trigger-daily-time-interval.json而且不可能有。原因是一个已发布 3.20 的缺陷在 3.x 默认设置下DailyTimeIntervalTriggerImpl被写成普通对象图其StartTimeOfDay与EndTimeOfDay是Quartz.TimeOfDay对象而TimeOfDay既没有无参构造函数也没有[JsonConstructor]——所以3.20 自己也读不回这个 blobSelectTrigger会抛 Unable to find a constructor to use for type Quartz.TimeOfDay。BlobStorageOverride.Families见 BlobStorageOverride.cs记录了同一事实Newtonsoft 只写四个家族System.Text.Json 由于判别形式可以往返写全部五个。因此也不存在在真实环境中工作过的该行演练无从断言 4.0 读取它。与测试链路的关系播种器是两条测试证据链的源头见 src/Quartz.Tests.Integration/Impl/AdoJobStore/UpgradeRehearsalTest.csUpgradeRehearsalTest在QRTZU_前缀下构建 3.20 schema由播种器填充每种序列化器一次、各用独立调度器名——因为 Quartz 所有表以SCHED_NAME为键两个调度器可以共享一套 schema然后依次运行database/migrations/4.0/下的schema_30_to_40_upgrade_dialect.sql与schema_30_to_40_indexes_…及之后所有迁移最后启动 4.0 调度器把每一行种子数据与播种器写下的 manifest 对照断言。该测试被标记为[NonParallelizable]每次运行都要在与其他迁移 fixture 共用的数据库上重建 schema而 Firebird 会以死锁SQLSTATE 40001拒绝并发的元数据更新。LegacyJsonPayloadTest直接读取src/Quartz.Tests.Unit/TestData/Legacy/3.20/下的字节文件。这些文件的来源3.x 写的从注释升级为可复现的事实——字节是 3.20 进程真实产出的且产出命令被写了下来。小结Quartz.Tests.Integration.Seeder展示了大型开源项目做升级兼容性验证的一种严谨手法用与被测版本程序集身份冲突的旧版本包在一个独立进程中真实写入数据再让新版本读取并对照清单。它同时覆盖了触发器五大家族、blob 存储、日历链、job data map 全类型、暂停组语义、崩溃残留行与新旧序列化差异把升级后数据是否幸存从口号变成了逐行可断言的工程事实。赞分享任务调度后端【免费下载链接】quartznetQuartz Enterprise Scheduler .NET项目地址https://gitcode.com/gh_mirrors/qu/quartznet点击查看免费下载相关推荐wger数据迁移验证编写测试确保健身数据迁移正确wger数据迁移验证编写测试确保健身数据迁移正确 还在为健身数据迁移后的准确性担忧吗wger作为开源健身管理平台提供了完善的测试体系来确保数据迁移的可靠性后端医疗健康fzf 进阶工作流3 种键盘操作搭好全文搜索、列表重载与日志追踪fzf 进阶工作流3 种键盘操作搭好全文搜索、列表重载与日志追踪 fzf 是终端里可编程的交互式过滤器。接 命令 | fzf 只是入门它还能在运行中自己变CLI微信QQ防撤回工具失效怎么办3步快速恢复与版本适配全攻略微信QQ防撤回工具失效怎么办3步快速恢复与版本适配全攻略 在日常的即时通讯使用中你是否遇到过这样的尴尬时刻同事在微信工作群中发送了重要的项目文档链接却在桌面应用即时通讯上一篇Audio Slicer 终极指南如何用智能音频分割工具提升你的音频处理效率400倍下一篇VC运行库修复终极指南一键解决软件启动兼容性问题创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考