Compare commits

590 Commits

Author SHA1 Message Date
72d70e90f1 更新ota方案 2025-12-02 17:16:48 +08:00
3bede99cc6 ota升级方案修改 2025-12-02 16:40:11 +08:00
95aaf80f3a ota升级方案修改 2025-12-02 16:40:10 +08:00
77ed812901 支持ota升级方案初稿 2025-12-02 16:40:10 +08:00
2e6a0abac3 增加ping指令并获取带版本号的响应 2025-12-02 16:40:10 +08:00
d5056af676 支持ota升级结果相应处理 2025-12-02 16:40:10 +08:00
1e685340f8 增加AreaControllerProperties 2025-12-02 16:40:10 +08:00
7ec9fb3f0b index.md 2025-12-02 16:40:10 +08:00
d430307b48 提供lora公共逻辑 2025-12-02 16:40:10 +08:00
5113e5953a lora处理逻辑统一方案 2025-12-02 16:40:10 +08:00
766fda7292 重构webhook包 2025-12-02 16:40:10 +08:00
1bc49ea249 proto 2025-12-02 16:40:10 +08:00
70fad51f40 Merge pull request 'issue_72' (#73) from issue_72 into main
Reviewed-on: #73
2025-12-02 16:34:47 +08:00
6764684fe7 优化ai初始化逻辑 2025-12-02 16:34:14 +08:00
da2c296c05 去掉无效日志 2025-12-02 16:19:56 +08:00
bdf74652b3 实现ai 2025-12-02 15:51:37 +08:00
70e8627a96 增加ai配置 2025-12-02 13:38:49 +08:00
c2c6577064 格式化 2025-11-29 16:04:12 +08:00
260c7d054c 更新makefile 2025-11-29 15:54:00 +08:00
d25933cf26 Merge pull request 'issue_66' (#70) from issue_66 into main
Reviewed-on: #70
2025-11-29 15:39:49 +08:00
4aa56441ce 归档任务 2025-11-29 15:38:52 +08:00
de68151539 优化日算法 2025-11-28 22:22:39 +08:00
04b46d8025 优化日志 2025-11-28 14:37:53 +08:00
bc4355cad5 修bug 2025-11-27 22:01:37 +08:00
968d996a9b 修bug 2025-11-27 21:47:07 +08:00
d6e5d89768 优化展示 2025-11-27 21:39:09 +08:00
1b5f715dec 实现优先使用库存的配方一键生成 2025-11-27 21:06:15 +08:00
da8e1d0191 优化算法 2025-11-27 20:03:14 +08:00
33cdf7278e 增加最后一次操作类型 2025-11-27 18:32:22 +08:00
3b12802900 实现按原料是否有库存筛选 2025-11-27 17:33:28 +08:00
e6b307b0dc 修bug 2025-11-27 16:42:32 +08:00
b8e0301175 修正数据错误 2025-11-27 16:27:49 +08:00
e2da441a6d 修正数据错误 2025-11-27 15:52:38 +08:00
dca6cc5dd4 原料增加最大添加量限制 2025-11-27 00:39:01 +08:00
5c99ff7475 原料增加最大添加量限制 2025-11-26 22:56:24 +08:00
0283c250e4 重构seeder 2025-11-26 22:51:58 +08:00
5bd52df240 优化算法 2025-11-26 22:41:38 +08:00
5ad403bf86 增加原料添加量限制 2025-11-26 22:35:52 +08:00
ce3844957f 修复逻辑错误 2025-11-26 22:13:51 +08:00
6c0f655d0a 增加删除原料校验 2025-11-26 21:14:32 +08:00
29b820b846 优化展示 2025-11-26 21:08:34 +08:00
34311889e8 实现使用系统中所有可用的原料一键生成配方 2025-11-26 20:44:41 +08:00
ba60ed541c 实现配方生成器 2025-11-26 20:23:29 +08:00
35eae7b3ec 只在第一次启动平台导入预设数据 2025-11-26 15:05:34 +08:00
ca85671a4c 支持预设价格 2025-11-26 14:35:58 +08:00
d7e2777c13 使用枚举 2025-11-25 20:22:38 +08:00
566f2d9a15 注入对象 2025-11-25 20:05:52 +08:00
c01ce6d1e6 原料模型增加参考价 2025-11-25 20:03:36 +08:00
c66671bf5f 原料删除校验 2025-11-25 18:54:11 +08:00
44ff3b19d6 实现库存管理相关逻辑 2025-11-25 18:10:28 +08:00
ae27eb142d agents.md 2025-11-25 15:25:52 +08:00
d7deaa346b 配方增删改查服务层和控制器 2025-11-24 13:25:15 +08:00
1200f36d14 重构配方类服务层 2025-11-23 15:16:45 +08:00
1b2e211bfa 重构配方类控制器 2025-11-23 14:49:55 +08:00
851682d579 配方领域层方法+重构配方领域 2025-11-22 21:29:23 +08:00
b40eb35016 实现修改猪营养需求 2025-11-22 20:52:15 +08:00
3ef2910058 实现修改猪营养需求 2025-11-22 17:55:56 +08:00
0637f5fb6c 实现修改猪营养需求 2025-11-22 17:55:52 +08:00
4405d1f3f1 修bug 2025-11-22 17:29:25 +08:00
9aea487537 实现修改原料营养信息 2025-11-22 16:44:22 +08:00
f81635f997 seeder支持按顺序读取 2025-11-21 18:36:02 +08:00
7829ac9931 拓展接口响应 2025-11-21 17:23:57 +08:00
4224be8567 拓展接口响应 2025-11-21 16:37:09 +08:00
534891309c 实现配方领域的web接口 2025-11-21 16:02:06 +08:00
9996fcfd74 实现配方领域关于猪模型和营养需求的增删改查 2025-11-21 15:03:42 +08:00
a669bfda6c 修复数据bug 2025-11-20 23:01:08 +08:00
aa13239e89 实现从json读取猪营养需求并写入数据库 2025-11-20 22:55:18 +08:00
c4ab53db12 优化PigBatchStatus 2025-11-20 21:00:58 +08:00
d185f334ac 增加Taber实现校验 2025-11-20 18:58:49 +08:00
1313140e45 重构creatingUniqueIndex和createGinIndexes 2025-11-20 17:46:01 +08:00
da934a9bbb 修复软删除和唯一索引同时存在的bug 2025-11-20 17:37:02 +08:00
1f3d3d8a7c 配置continue 2025-11-20 16:47:47 +08:00
6ca101727a 定义猪的模型和营养需求模型 2025-11-20 14:38:36 +08:00
c697e668e3 实现原材料的增删改查和仓库层的原料库存记录表增查 2025-11-20 13:43:09 +08:00
fd0939fe0a 定义配方领域, 实现营养元素的增删改查 2025-11-19 23:23:48 +08:00
365d69e0c6 支持预设原料和营养简介 2025-11-19 19:58:09 +08:00
a74ab4e5e7 迁移配置文件, 实现从json文件中读取原材料营养预设值, 并自动写入数据库 2025-11-19 19:31:51 +08:00
a1be06854f 调整文件位置 2025-11-19 15:06:18 +08:00
e1399be538 删除原有食物逻辑和模型
新增原料和营养价值表和原料库存日志和营养表定义
2025-11-18 22:22:31 +08:00
200a358933 修复bug 2025-11-18 17:21:04 +08:00
32abff626e 修复bug 2025-11-18 15:46:32 +08:00
98317dce1f 优化日志 2025-11-18 14:41:04 +08:00
31e1139cd0 移除冗余代码 2025-11-18 14:29:10 +08:00
a4fbfbd145 修bug 2025-11-16 23:11:47 +08:00
b2d1b10962 增加通知状态刷新任务 2025-11-16 23:03:05 +08:00
b9b067707b 修bug 2025-11-16 22:33:49 +08:00
3daa03eab2 修bug 2025-11-16 21:56:46 +08:00
01b6d1cc14 修bug 2025-11-16 21:49:28 +08:00
aa94b47773 修bug 2025-11-16 21:44:50 +08:00
148fa1f2bb 修bug 2025-11-16 20:18:42 +08:00
bf1600b385 增加两个批量查询接口 2025-11-16 16:30:26 +08:00
bf747e22ce 日志支持不展示调用链 2025-11-16 15:53:07 +08:00
2aabbfd8b9 修bug 2025-11-16 15:47:47 +08:00
9dcf362048 修bug 2025-11-16 14:55:40 +08:00
549bfd074c 修bug 2025-11-16 14:42:26 +08:00
e38c806aee 归档 2025-11-10 22:50:54 +08:00
7124e6934b Merge pull request 'issue_62' (#65) from issue_62 into main
Reviewed-on: #65
2025-11-10 22:48:42 +08:00
ecd2d37c70 uint/uint64全部改为uint32 2025-11-10 22:23:31 +08:00
3e711551e7 float64全部改float32 2025-11-10 21:42:46 +08:00
75306941c2 将所有Regional更改为Area 2025-11-10 21:32:18 +08:00
4f3148eaa2 修复报错 2025-11-10 21:17:28 +08:00
30880f8c30 设备和区域主控删除时清除对应区域阈值告警或设备阈值告警任务 2025-11-10 21:14:36 +08:00
9dc47ec7ad 实现根据区域ID或设备ID清空对应阈值告警任务 2025-11-10 20:37:07 +08:00
2cc4135b28 实现任务11应的八个web接口 2025-11-10 19:18:49 +08:00
cb075c907d DeleteDeviceThresholdAlarm
DeleteAreaThresholdAlarm
2025-11-10 18:22:00 +08:00
f44a94b451 GetDeviceThresholdAlarm
GetAreaThresholdAlarm
2025-11-10 17:50:05 +08:00
f2b0c2987f CreateDeviceThresholdAlarm
UpdateDeviceThresholdAlarm
CreateAreaThresholdAlarm
UpdateAreaThresholdAlarm
2025-11-10 17:35:22 +08:00
d4e8aba1fd 实现区域阈值告警任务 2025-11-10 15:25:33 +08:00
19d55eb09b 系统初始化时健康计划调整(包括增加延时任务) 2025-11-10 14:11:39 +08:00
ca8e5ff867 实现列表查询活跃告警和历史告警 2025-11-10 13:41:54 +08:00
37f515d4a8 实现列表查询活跃告警和历史告警 2025-11-10 13:41:26 +08:00
b94aa6137c 实现忽略告警和取消忽略告警接口及功能 2025-11-09 22:34:05 +08:00
84fe20396b 实现设备阈值检查任务 2025-11-09 21:37:37 +08:00
e54c1bbc97 实现设备阈值检查任务 2025-11-09 21:37:35 +08:00
a35a9a1038 实现DeviceThresholdCheckTask(除Execute外) 2025-11-08 21:12:51 +08:00
26df398392 修正方法位置 2025-11-08 18:43:46 +08:00
2ad1ae4f40 创建PlanNameAlarmNotification 计划 2025-11-08 18:33:07 +08:00
362ed5ad9d 实现 AlarmNotificationTask 2025-11-08 17:35:03 +08:00
f049cbce6c 实现查询满足发送告警消息条件的活跃告警列表的仓库层方法 2025-11-08 01:27:33 +08:00
3a59a2755c 增加告警间隔配置 2025-11-08 00:13:15 +08:00
f46c8aed0c AlarmNotificationTask.parseParameters 2025-11-07 23:57:04 +08:00
e6aa9696a9 gormAlarmRepository.ListActiveAlarms 2025-11-07 23:46:05 +08:00
f23479c4d1 定义AlarmNotificationTask(不含方法) 2025-11-07 23:42:09 +08:00
e4c41d6500 定义AlarmService(不含方法) 2025-11-07 23:21:37 +08:00
637df63953 更新方案 2025-11-07 22:49:43 +08:00
baa62a9c77 更新方案 2025-11-07 22:42:28 +08:00
a90b1cc012 定义仓库层对象 2025-11-07 22:26:16 +08:00
d3207cc2b8 调整models定义 2025-11-07 22:19:55 +08:00
2796d9bad7 重构部分枚举, 让models包不依赖其他项目中的包 2025-11-07 21:39:24 +08:00
375af57afe 定义告警表和告警历史表 2025-11-07 20:56:04 +08:00
42317e4ed2 修正计划 2025-11-07 20:21:57 +08:00
6b6fd411c5 制定初步计划和任务青岛那 2025-11-07 18:54:02 +08:00
04d9c0b515 修复bug 2025-11-07 16:02:16 +08:00
342c9df3ff 生成swag 2025-11-06 16:41:06 +08:00
3761f8a88e Merge pull request 'issue_48' (#60) from issue_48 into main
Reviewed-on: #60
2025-11-06 16:36:15 +08:00
458313af9b 归档 2025-11-06 16:35:18 +08:00
e73a4675f3 修bug 2025-11-06 16:34:50 +08:00
901cdfeb5c 实现健康路由 2025-11-06 16:31:21 +08:00
b203cfcb7f Merge pull request 'issue_55' (#59) from issue_55 into main
Reviewed-on: #59
2025-11-06 15:56:15 +08:00
9f366d55fa 归档 2025-11-06 15:56:49 +08:00
30304da0c8 归档 2025-11-06 15:56:19 +08:00
cb18e27f18 归档 2025-11-05 23:58:50 +08:00
4eeaec45c4 Merge pull request 'issue_56' (#58) from issue_56 into main
Reviewed-on: #58
2025-11-05 23:56:50 +08:00
d377314b40 标记任务完成 2025-11-05 23:57:09 +08:00
5eeeeb5006 修bug 2025-11-05 23:52:48 +08:00
a1f7c09b2a 修复报错 2025-11-05 23:10:51 +08:00
10b123ab93 修改infra.repository包 2025-11-05 23:00:07 +08:00
97aea66f7c 修改infra除repository包 2025-11-05 22:22:46 +08:00
07d8c719ac 修改domain包 2025-11-05 21:40:19 +08:00
203be4307d 修改service包 2025-11-05 19:57:30 +08:00
bd4f5b83e1 修改webhook包 2025-11-05 18:54:24 +08:00
3e2f9e7210 修改middleware包 2025-11-05 18:38:41 +08:00
ef4ca397ce 修改controller包 2025-11-05 17:24:19 +08:00
4cae93ef34 修改api包 2025-11-05 16:10:46 +08:00
aaa2f1b22f 修改程序入口 2025-11-05 16:00:43 +08:00
bee5104661 让调用链信息永远在日志最末尾 2025-11-05 15:43:14 +08:00
349fc3e129 log包改造 2025-11-05 14:58:13 +08:00
09898ec5c2 详细任务清单 2025-11-03 23:17:06 +08:00
d5c336e946 任务清单 2025-11-03 21:11:19 +08:00
b6c2fa58f5 制定方案 2025-11-03 20:39:31 +08:00
d7c7b56b95 归档改动方案 2025-11-03 17:29:23 +08:00
545a53bb68 Merge pull request 'issue_50' (#57) from issue_50 into main
Reviewed-on: #57
2025-11-03 17:27:25 +08:00
9127eeaf31 优化设备服务方法的入参 2025-11-03 17:27:29 +08:00
f0b71b47a0 删除设备模板时检查和删除区域主控时检查 2025-11-03 17:11:51 +08:00
f569876225 删除设备时检查 2025-11-03 16:46:23 +08:00
8669dcd9b0 增加任务增删改查时对设备任务关联表的维护 2025-11-03 16:29:57 +08:00
66554a1376 增加任务设备关联表 2025-11-03 14:24:38 +08:00
b62a3d0e5d 计划逻辑迁移 2025-11-02 23:51:45 +08:00
026dad9374 需求文档 2025-11-02 23:26:16 +08:00
687c2f12ee 让任务可以提供自身使用设备 2025-11-02 20:47:25 +08:00
4b0be88fca 调整文件命名 2025-11-02 19:59:13 +08:00
bb42147974 使用plan service 替换子领域 2025-11-02 19:46:20 +08:00
8d7d9fc485 增加plan service 2025-11-02 19:33:05 +08:00
6cd566bc30 对外暴露接口 2025-11-02 18:27:40 +08:00
408df2f09c 修改方案 2025-11-02 18:20:02 +08:00
011658461e 重构名字 2025-11-02 18:16:44 +08:00
3ab2eb0535 重构计划 2025-11-02 18:11:48 +08:00
a29e15faba 增加文件目录树和生成命令, 方便ai阅读 2025-11-02 15:48:20 +08:00
8e97922012 还原改动, bmad真难用 2025-11-02 15:07:19 +08:00
548d3eae00 bmad 架构师工作 2025-11-01 23:29:42 +08:00
6f7e462589 bmad 分析师工作 2025-11-01 22:43:34 +08:00
cf9e43cdd8 bmad代理支持chrome mcp 2025-11-01 20:58:00 +08:00
426ae41f54 bmad初始化 2025-11-01 19:22:39 +08:00
5b21dc0bd5 更新makefile 2025-11-01 17:21:09 +08:00
67d4fb097d Merge pull request 'issue 52' (#54) from issue_52 into main
Reviewed-on: #54
2025-11-01 16:29:15 +08:00
0008141989 实现 2025-11-01 16:29:18 +08:00
c4ca0175dd 调整报错 2025-10-31 22:01:24 +08:00
193d77b5b7 修复bug 2025-10-31 18:14:12 +08:00
0c88c76417 修复bug 2025-10-31 17:59:48 +08:00
843bd8a814 修正page_size 2025-10-31 17:45:28 +08:00
348220bc7b Merge pull request 'issue_49' (#51) from issue_49 into main
Reviewed-on: #51
2025-10-31 17:05:22 +08:00
d6c18f0774 归档任务 2025-10-31 17:04:58 +08:00
e1c76fd8ec 任务1 and 3 2025-10-31 16:53:40 +08:00
bc6a960451 任务2.5 2025-10-31 16:49:35 +08:00
4e87436cc0 调整任务列表 2025-10-31 16:40:33 +08:00
942ffa29a1 任务2.4 2025-10-31 16:28:26 +08:00
b44e1a0e7c 任务2.3 2025-10-31 16:11:12 +08:00
d22ddac9cd 移除废弃接口 2025-10-31 16:01:49 +08:00
ccab7c98e4 任务2.2.3/2.2.4 2025-10-31 16:00:55 +08:00
3334537663 补充缺失任务 2025-10-31 15:54:17 +08:00
0c35e2ce7d 实现任务2.2 2025-10-31 15:38:10 +08:00
db11438f5c 填充design.md 2025-10-31 15:16:21 +08:00
9f3e800e59 任务2.1 2025-10-31 15:10:09 +08:00
8d8310fd2c 修正tasks.md 2025-10-31 14:39:37 +08:00
12c6dc515f 增加新发现的问题 2025-10-31 14:18:24 +08:00
c2c2383305 提案和任务列表 2025-10-31 14:11:01 +08:00
4a92324774 删掉失效的文件 2025-10-31 14:09:47 +08:00
a4bd19f950 删掉失效的文件 2025-10-30 23:22:45 +08:00
f71d04f8af Merge pull request 'issue_36' (#47) from issue_36 into main
Reviewed-on: #47
2025-10-30 18:25:26 +08:00
4b10efb13c openspec归档 2025-10-30 18:25:25 +08:00
b4c70d4d9c 完成任务6(修bug)和任务7和任务八 2025-10-30 18:07:17 +08:00
f624a8bf5e 部分完成任务6(先提交然后修bug) 2025-10-30 17:44:34 +08:00
8ce553a9e4 完成任务5 2025-10-30 17:39:05 +08:00
5b064b4015 调整openspace方案 2025-10-30 17:34:25 +08:00
6228534155 调整openspace方案 2025-10-30 17:23:07 +08:00
d235130d11 完成任务4 2025-10-30 17:15:14 +08:00
f0982839e0 完成任务3 2025-10-30 16:58:08 +08:00
ff8a8d2b97 完成任务3.1 2025-10-30 16:35:54 +08:00
f2078ea54a 修正任务清单 2025-10-30 16:27:49 +08:00
c463875fba 完成任务2 2025-10-30 16:19:24 +08:00
7c5232e71b 完成任务1 2025-10-30 16:11:59 +08:00
2c9b4777ae 生成openspace任务列表 2025-10-30 16:10:10 +08:00
93f67812ae openspec init 2025-10-30 14:26:48 +08:00
e5b75e3879 优化代码 2025-10-29 19:42:22 +08:00
67575c17bc 修bug 2025-10-29 19:15:52 +08:00
7ac9e49212 调整日志等级 2025-10-29 19:14:26 +08:00
ff45c59946 修bug 2025-10-29 19:07:00 +08:00
8d48576305 修bug 2025-10-29 18:56:05 +08:00
af8689d627 计划监控增加计划名 2025-10-29 17:52:07 +08:00
2910c9186a Merge pull request 'issue_42' (#46) from issue_42 into main
Reviewed-on: #46
2025-10-29 17:21:25 +08:00
b09d32b1d7 修改config.yml 2025-10-29 17:21:23 +08:00
403d46b777 删掉原来的定时采集线程 2025-10-29 17:13:03 +08:00
85bd5254c1 实现全量采集系统计划 2025-10-29 17:10:48 +08:00
5050f76066 增加全量采集任务 2025-10-29 16:37:05 +08:00
1ee3e638f7 controller调整, 增加计划类型 2025-10-29 16:25:39 +08:00
94e8768424 plan增加一个类型字段 2025-10-29 15:48:49 +08:00
675711cdcf 拆分task包 2025-10-29 15:30:16 +08:00
e66ee67cf7 Merge pull request 'issue_42' (#44) from issue_42 into main
Reviewed-on: #44
2025-10-26 15:57:04 +08:00
40eb57ee47 重构core包 2025-10-26 15:48:38 +08:00
6a8e8f1f7d 实现定时采集 2025-10-26 15:10:38 +08:00
5c83c19bce Merge pull request 'issue_22' (#41) from issue_22 into main
Reviewed-on: #41
2025-10-25 15:42:19 +08:00
86c9073da8 修bug 2025-10-25 15:41:49 +08:00
43c1839345 实现controller 2025-10-25 15:04:47 +08:00
f62cc1c4a9 实现service层 2025-10-25 14:36:24 +08:00
f6d2069e1a 发送通知时写入数据库 2025-10-25 14:17:17 +08:00
f33e14f60f 发送通知时写入数据库 2025-10-25 14:15:17 +08:00
d6f275b2d1 定义仓库方法 2025-10-25 13:35:43 +08:00
d8de5a68eb 定义通知model 2025-10-25 13:28:19 +08:00
bd8729d473 中文枚举 2025-10-24 21:38:52 +08:00
3fd97aa43f 日志发送逻辑及测试消息发送接口 2025-10-24 21:24:48 +08:00
9d6876684b 实现飞书/微信/邮件发送通知 2025-10-24 20:33:15 +08:00
47ed819b9d Merge pull request 'issue_39' (#40) from issue_39 into main
Reviewed-on: #40
2025-10-23 16:07:20 +08:00
b1dce77e51 修bug 2025-10-23 16:06:15 +08:00
21607559c4 修bug 2025-10-23 12:00:57 +08:00
af6a00ee47 优化报错 2025-10-23 11:52:08 +08:00
324a533c94 猪群相关接口增加当前总量和当前总存栏量 2025-10-23 11:29:48 +08:00
c1f71050e9 猪栏信息接口增加猪栏当前存栏量 2025-10-23 10:52:40 +08:00
db32c37318 修bug 2025-10-22 17:58:24 +08:00
3d5741f5fd 修bug 2025-10-20 20:58:24 +08:00
c4c9723b7b make dev 2025-10-20 20:50:03 +08:00
a32749cef8 lora mesh 发送即收到 2025-10-20 19:31:19 +08:00
be8275b936 Merge pull request 'air' (#38) from issue_37 into main
Reviewed-on: #38
2025-10-20 15:24:57 +08:00
169b2c79cb air 2025-10-20 15:22:08 +08:00
33ad309eeb 优化代码 2025-10-19 20:51:30 +08:00
ebaaa86f09 swag 2025-10-19 20:41:33 +08:00
71afbf5ff9 调整ListUserHistory 2025-10-19 20:36:10 +08:00
4e046021e3 注册api 2025-10-19 20:24:01 +08:00
4cbb4bb859 ListPigSales 2025-10-19 15:53:19 +08:00
0038f20334 ListPigPurchases 2025-10-19 14:54:13 +08:00
197af0181c ListPigSickLogs 2025-10-19 14:34:22 +08:00
1830fcd43e ListPigTransferLogs 2025-10-19 14:11:18 +08:00
53845422c1 ListWeighingRecords 2025-10-19 13:59:11 +08:00
757d38645e 移动位置 2025-10-19 13:47:37 +08:00
5ee6cbce8f 移动位置 2025-10-19 13:44:13 +08:00
fd39eb6450 ListWeighingBatches 2025-10-19 13:41:29 +08:00
89fbbbb75f ListPigBatchLogs 2025-10-19 12:41:13 +08:00
e150969ee3 ListMedicationLogs 2025-10-18 16:22:59 +08:00
4c6843afb4 ListFeedUsageRecords 2025-10-18 16:08:46 +08:00
eb0786ca27 ListRawMaterialStockLogs 2025-10-18 16:04:54 +08:00
7299c8ebe6 ListRawMaterialPurchases 2025-10-18 15:58:31 +08:00
bcdcaa5631 ListUserActionLogs 2025-10-18 15:47:13 +08:00
fab26ffca4 ListPendingCollections 2025-10-18 15:43:27 +08:00
6a93346e87 ListTaskExecutionLogs 2025-10-18 15:39:47 +08:00
df0dfd62c6 ListPlanExecutionLogs 2025-10-18 15:36:32 +08:00
51a873049e ListDeviceCommandLogs 2025-10-18 15:33:39 +08:00
05820438d0 ListSensorData 2025-10-18 15:31:05 +08:00
3b967aa449 优化日志 2025-10-17 10:44:20 +08:00
fa437b30aa 调整swag 2025-10-13 14:37:57 +08:00
bcec36f7e2 调整swag 2025-10-13 14:15:38 +08:00
8c0dc6c815 ManualControl 2025-10-13 12:16:36 +08:00
9b6548c1b4 ManualControl 2025-10-13 10:39:51 +08:00
b4d31d3133 处理路由冲突 2025-10-10 18:23:06 +08:00
6d8cb7ca4e Merge pull request 'issue_33' (#35) from issue_33 into main
Reviewed-on: #35
2025-10-10 17:19:53 +08:00
503feb1b21 实现 LoRaMeshUartPassthroughTransport 2025-10-10 15:59:58 +08:00
50a843c9ef 实现 LoRaMeshUartPassthroughTransport 2025-10-10 15:58:40 +08:00
8a5f6dc34e 实现 LoRaMeshUartPassthroughTransport 构造函数 2025-10-10 00:01:21 +08:00
38a01f4a6e 改造成支持lora mesh(没实现,只是支持) 2025-10-09 23:43:19 +08:00
ca544d7605 更新配置文件 2025-10-09 23:02:56 +08:00
ac8c8c56a6 优化proto 2025-10-09 13:55:46 +08:00
8a2e889048 采集失败处理 2025-10-08 17:52:30 +08:00
b611f132f1 更新proto 2025-10-07 16:14:47 +08:00
759caadb21 Merge pull request 'issue_29' (#32) from issue_29 into main
Reviewed-on: #32
2025-10-07 13:33:24 +08:00
4250f27e11 增加注释 2025-10-07 13:31:56 +08:00
77ab434d17 简化控制器层重复代码 2025-10-07 00:18:17 +08:00
21661eb748 简化控制器层重复代码 2025-10-06 23:48:31 +08:00
5e84b473f6 移除废弃代码 2025-10-06 23:24:16 +08:00
e142405bb3 猪群领域其他方法映射到api 2025-10-06 23:22:47 +08:00
632bd20e7d 更新猪群对应猪栏接口变更 2025-10-06 23:10:58 +08:00
aac0324616 实现 RemoveEmptyPenFromBatch 2025-10-06 22:47:47 +08:00
18b45b223c 调整swag 2025-10-06 22:26:57 +08:00
035da5293b 实现 RecordCull 和 RecordDeath 2025-10-06 22:22:10 +08:00
1290676fe4 实现 RecordSickPigCull 和 RecordSickPigDeath 2025-10-06 22:08:09 +08:00
73de8ad04f 实现 RecordSickPigRecovery 2025-10-06 21:57:53 +08:00
9a7b765b71 实现 RecordSickPigs 2025-10-06 21:54:55 +08:00
4fb8729a2a 定义病猪方法 2025-10-06 21:50:39 +08:00
84c22e342c 定义病猪方法 2025-10-06 21:27:23 +08:00
691810c591 实现 SickPigManager 2025-10-06 18:50:22 +08:00
67b45d2e05 删批次时释放猪栏 2025-10-06 18:15:47 +08:00
0576a790dd 实现 ReclassifyPenToNewBatch 2025-10-06 18:08:56 +08:00
5e49cd3f95 实现MovePigsIntoPen 2025-10-06 17:56:13 +08:00
efbe7d167c 实现AssignEmptyPensToBatch 2025-10-06 17:44:00 +08:00
51b776f393 跨群调栏没有调整猪的数量 2025-10-06 17:13:30 +08:00
189d532ac9 增加调栏数量检查 2025-10-06 16:31:24 +08:00
3b109d1547 买卖猪要调整猪栏存量 2025-10-06 16:19:48 +08:00
648a790cec 定义病猪子服务 2025-10-06 15:35:20 +08:00
1b026d6106 定义病猪用药两个repo 2025-10-06 15:26:44 +08:00
91e18c432c 提取修改猪群数量逻辑 2025-10-06 15:08:32 +08:00
59b6977367 调整model位置 2025-10-06 14:48:47 +08:00
c49844feea 实现交易管理器主服务入口 2025-10-06 14:32:52 +08:00
448b721af5 调整方法存放位置 2025-10-06 11:58:26 +08:00
759b31bce3 实现交易子服务 2025-10-06 11:42:56 +08:00
c76c976cc8 买卖猪记录表 2025-10-05 22:09:25 +08:00
1652df1533 病猪变化日志 2025-10-05 21:58:06 +08:00
b1e1dcdcad 增加类型 2025-10-05 21:25:13 +08:00
47c72dff3e 记录调猪人 2025-10-05 21:20:22 +08:00
811c6a09c5 清理文件 2025-10-05 18:32:18 +08:00
cb20732205 文件改名 2025-10-05 18:29:08 +08:00
2aa0f09079 创建批次时插入一条记录 2025-10-05 18:28:16 +08:00
9c35372720 修改方法命名 2025-10-05 18:00:50 +08:00
b6e68e861b 调整仓库方法归属 2025-10-05 17:46:03 +08:00
b3933b6d63 调整仓库方法归属 2025-10-05 17:42:27 +08:00
01327eb8d2 猪群管理聚合服务 增加调栏管理 2025-10-05 17:30:39 +08:00
6d080d250d 猪群管理聚合服务 2025-10-05 16:37:12 +08:00
740e14e6cc swag 2025-10-04 01:31:35 +08:00
8d9e4286b0 单独修改猪圈状态 2025-10-04 01:28:05 +08:00
5403be5e7d 增加日志 2025-10-04 01:02:40 +08:00
1bc36f5e10 移动业务逻辑 2025-10-04 00:58:29 +08:00
8bb0a54f18 实现修改批次绑定的猪栏 2025-10-04 00:47:27 +08:00
d03163a189 修改路由 2025-10-03 23:59:25 +08:00
9875994df8 修改路由 2025-10-03 23:58:37 +08:00
c27b5bd708 移动文件位置 2025-10-03 23:52:25 +08:00
4e17ddf638 实现猪批次增删改查 2025-10-03 23:42:14 +08:00
d273932693 重构dto 2025-10-03 23:02:43 +08:00
fadc1e2535 改名 2025-10-03 22:34:24 +08:00
c4fb237604 业务逻辑移动到服务层 2025-10-03 22:33:43 +08:00
645c92978b 增加删除检查 2025-10-03 22:23:31 +08:00
c50366f670 定义model 2025-10-03 22:17:28 +08:00
258e350c35 定义model 2025-10-03 22:00:44 +08:00
aced495cd6 定义model 2025-10-03 20:58:41 +08:00
25e9e07cc8 定义model 2025-10-03 20:46:04 +08:00
6cc6d719e1 定义model 2025-10-03 20:32:34 +08:00
8cbe313c89 实现 猪舍相关路由组 和 猪圈相关路由组 2025-10-03 18:27:53 +08:00
5754a1d94c 定义对应model 2025-10-03 17:23:44 +08:00
609aee2513 定义对应model 2025-10-03 16:56:03 +08:00
829f0a6253 调整目录结构 2025-10-02 00:18:13 +08:00
0b8b37511e 定义兽药模组 2025-10-01 20:40:35 +08:00
3cc88a5248 枚举改成中文 2025-10-01 20:39:59 +08:00
f814e682cf 改bug 2025-10-01 00:40:47 +08:00
981e523440 改bug 2025-09-30 22:42:07 +08:00
95c2c2e0c1 改bug 2025-09-30 22:32:30 +08:00
65a26b1880 允许创建小于六位的密码 2025-09-30 22:27:38 +08:00
077e866915 增加设备模板列表 2025-09-30 22:07:55 +08:00
108d496346 超表改造 2025-09-30 21:44:03 +08:00
5022a2be1f 添加区域主控相关路由 2025-09-30 15:25:07 +08:00
5d6fd315e3 Merge pull request 'issue_25' (#26) from issue_25 into main
Reviewed-on: #26
2025-09-30 00:33:33 +08:00
3722ec8031 注入依赖 2025-09-30 00:32:56 +08:00
441ce2c5ec 调整 ChirpStackListener 2025-09-30 00:30:46 +08:00
ab9842dc10 调整 Controller 2025-09-30 00:18:21 +08:00
56dbb680a7 调整 GeneralDeviceService 2025-09-30 00:07:16 +08:00
3a0e72a5c8 proto 2025-09-30 00:01:51 +08:00
e2be93565d proto 2025-09-29 23:54:09 +08:00
503296e574 调整 device和模板 2025-09-29 23:46:33 +08:00
35c2d03602 调整 device和模板 2025-09-29 23:46:28 +08:00
bc97c6bfed modbus rtu指令生成器 2025-09-29 23:27:40 +08:00
4bed3e51b2 调整 ReleaseFeedWeightTask 2025-09-29 22:26:58 +08:00
8392438dc5 调整 ValueDescriptor 2025-09-29 22:23:35 +08:00
4f730cf58f 调整 ChirpStackListener 2025-09-29 19:17:42 +08:00
ee8039b301 调整GeneralDeviceService 2025-09-29 18:13:19 +08:00
ccea087f6c 优化设备模板, 一个设备只能干一件事 2025-09-29 17:55:43 +08:00
8706d8c913 加强device仓库方法 2025-09-29 17:27:42 +08:00
1df1bf2e75 1. 增加自检
2. 去掉device的gin索引
2025-09-29 17:06:21 +08:00
facbbfe6a1 修改设备模型 2025-09-29 16:58:32 +08:00
f007e3b207 Merge pull request 'issue_20' (#24) from issue_20 into main
Reviewed-on: #24
2025-09-28 01:47:47 +08:00
b483415a9a ListUserHistory 实现 2025-09-28 01:48:15 +08:00
aaca5b1723 增加日志 2025-09-28 01:33:19 +08:00
72d8b45241 调整审计输出 2025-09-28 01:28:54 +08:00
17344cdb89 增加审计输出(初步) 2025-09-28 01:23:50 +08:00
7d527d9a67 加注释 2025-09-28 01:14:35 +08:00
fdbea5b7e9 优化代码位置 2025-09-28 01:10:04 +08:00
d995498199 优化代码 2025-09-28 01:02:29 +08:00
e3c0a972fb 响应状态改成自定义状态 2025-09-28 00:54:19 +08:00
3c8b91ff6a JSON转换放在内置函数里 2025-09-28 00:37:20 +08:00
1c7e13b965 初步实现审计 2025-09-28 00:13:47 +08:00
b177781fa1 支持用手机号等信息代替用户名登录 2025-09-27 23:28:06 +08:00
6d9d4ff91b 增加社交信息model 2025-09-27 23:22:27 +08:00
ea9cc3cbe4 实现登录校验中间件 2025-09-27 23:17:23 +08:00
18c4747de6 增加超表, 超过两天的数据全部压缩, 压缩可以释放索引减少内存使用 2025-09-27 23:05:48 +08:00
4496f2822c make swag 2025-09-27 22:25:49 +08:00
200a45a483 Merge pull request 'issue_18' (#19) from issue_18 into main
Reviewed-on: #19
2025-09-27 01:15:33 +08:00
25474f851e 优化代码 2025-09-27 01:09:20 +08:00
5d1b642cc8 校验device和task的配置的地方全部用内置方法 2025-09-27 01:03:58 +08:00
aed665b6b0 解析device和task的配置全部用内置方法处理 2025-09-27 00:58:22 +08:00
29fa23ba36 GeneralDeviceService 改为全局唯一使用靠注入 2025-09-27 00:30:47 +08:00
23b7f66d74 issue-18 优化代码(只保证编译通过没检查) 2025-09-26 22:50:08 +08:00
d9fe1683d2 issue-18初步实现 2025-09-26 15:26:21 +08:00
3b4de24e1d 增加proto定义 2025-09-25 20:16:07 +08:00
2342f388c0 Merge pull request 'issue_16' (#17) from issue_16 into main
Reviewed-on: #17
2025-09-25 11:28:28 +08:00
5a655ffc9e DelayTask 使用结构体代替map 2025-09-25 11:28:38 +08:00
97c750778a ReleaseFeedWeightTask 使用结构体代替map 2025-09-25 11:27:14 +08:00
50aac8d7e5 实现ReleaseFeedWeightTask 2025-09-25 11:17:13 +08:00
f6941fe002 优化超表分片逻辑, 一天一片 2025-09-25 10:34:35 +08:00
0d6d1db290 定义ReleaseFeedWeightTask并注入依赖 2025-09-25 09:44:32 +08:00
e1a1b29a0f 将GeneralDeviceService改成通用版, 随用随New 2025-09-25 09:27:29 +08:00
fe549fca4a 调整task工厂 2025-09-25 09:07:32 +08:00
0606be4711 更新 README.md 2025-09-25 00:36:08 +08:00
36d3f81d40 Merge pull request '删除计划时停止这个计划的执行,停止计划前判断一下是不是未启用的计划' (#15) from issue_6 into main
Reviewed-on: #15
2025-09-25 00:32:52 +08:00
5e019ecf73 删除计划时停止这个计划的执行
停止计划前判断一下是不是未启用的计划
2025-09-25 00:32:57 +08:00
6c8568f304 Merge pull request 'issue_9' (#14) from issue_9 into main
Reviewed-on: #14
2025-09-25 00:16:50 +08:00
e2e21601f4 记录任务下发历史和接收是否成功 2025-09-25 00:17:01 +08:00
6b931648dc 定义ack表 2025-09-24 23:08:59 +08:00
cf53cdfe28 记录温度湿度称重数据 2025-09-24 22:34:11 +08:00
21fb9c7e57 记录信号强度数据 2025-09-24 21:53:18 +08:00
f764ad8962 完善结构体定义 2025-09-24 20:43:40 +08:00
53dbe41d7b 处理 ChirpStack 的 log 事件 2025-09-24 20:26:26 +08:00
17e2c6471a 定义事件结构体和接收器 2025-09-24 19:13:15 +08:00
3a030f5bca 增加pprof 2025-09-24 18:09:29 +08:00
2070653f2f 优化索引 2025-09-24 16:48:41 +08:00
47b8c5bc65 增加 timescaledb 处理逻辑和gin索引 2025-09-24 16:34:16 +08:00
b668f3fbb5 定义一个配置记录是不是timescaledb 2025-09-24 16:06:05 +08:00
6520f2e9d7 更新计划时清零计数器 2025-09-23 22:38:04 +08:00
68b97a12a1 重启系统时清零计数器 2025-09-23 22:30:39 +08:00
1764ff5598 启动计划时清零计数器 2025-09-23 22:18:59 +08:00
9594d08e40 修正更新任务后待执行任务找不到被删除的任务的问题 2025-09-23 22:08:18 +08:00
6b4ef79c45 Merge pull request 'issue_11' (#13) from issue_11 into main
Reviewed-on: #13
2025-09-23 21:54:39 +08:00
28a43928aa 增加任务描述日志 2025-09-23 21:54:47 +08:00
15b4a9520a 修bug 2025-09-23 21:49:53 +08:00
8f3daef5cb Merge pull request 'issue_10' (#12) from issue_10 into main
Reviewed-on: #12
2025-09-23 21:35:53 +08:00
25d6855b38 修复空计划不会反复执行 2025-09-23 21:26:45 +08:00
08e326d56d 修复空计划不会反复执行 2025-09-23 21:14:37 +08:00
e711db94c0 自动判断content_type 2025-09-23 19:28:43 +08:00
b6a872b3b8 自动判断content_type 2025-09-23 18:11:21 +08:00
9e129a1ac0 调整任务进度跟踪器, 改为从数据库获取执行进度:
修bug
2025-09-23 17:55:33 +08:00
eda5c8dedb 调整任务进度跟踪器, 改为从数据库获取执行进度:
修bug
2025-09-23 17:28:33 +08:00
557a0d5d3e 调整任务进度跟踪器, 改为从数据库获取执行进度:
修bug
2025-09-23 17:21:14 +08:00
06f327518a 调整任务进度跟踪器, 改为从数据库获取执行进度:
调整任务调度器
2025-09-23 17:19:39 +08:00
db42560654 调整任务进度跟踪器, 改为从数据库获取执行进度:
提供仓库层api
2025-09-23 17:11:31 +08:00
83db3b2278 修复程序启动时执行历史没有正确刷新 2025-09-23 11:39:47 +08:00
05e789b707 1. 函数改名
2. 删掉没用文件
2025-09-23 11:08:18 +08:00
f5e862ee86 日志加颜色 2025-09-22 23:15:10 +08:00
fd2ac68a03 修复bug 2025-09-22 18:28:50 +08:00
806f1cf5ef 修复bug 2025-09-22 18:27:14 +08:00
a5d6c81f3a 修bug 2025-09-22 15:20:19 +08:00
4096499d28 实现关闭计划 2025-09-22 15:17:54 +08:00
bd2d1c6b63 实现启动计划 2025-09-22 14:21:08 +08:00
0a8e6793ef 改回cron解析五位表达式 2025-09-22 00:41:57 +08:00
d6eaf23289 完成的TODO 2025-09-21 23:09:25 +08:00
df583ef157 支持6位cron表达式 2025-09-21 17:01:26 +08:00
3b1d1580a1 修复gorm自动创建无用外键的问题 2025-09-21 14:22:48 +08:00
ca2dedb2e2 Merge pull request 'issue-5' (#8) from issue-5 into main
Reviewed-on: #8
2025-09-21 12:43:59 +08:00
769a0432c8 初始化待执行队列 2025-09-20 23:50:27 +08:00
74e42de7aa 重构AnalysisPlanTaskManager 2025-09-20 22:41:03 +08:00
b0eb135f44 重构AnalysisPlanTaskManager 2025-09-20 22:32:39 +08:00
056279bdc2 重构AnalysisPlanTaskManager 2025-09-20 22:32:00 +08:00
6711f55fba 重构AnalysisPlanTaskManager 2025-09-20 21:45:38 +08:00
e85d4f8ec3 重构AnalysisPlanTaskManager 2025-09-20 21:14:58 +08:00
40a892e09d todo 2025-09-20 18:11:48 +08:00
1f2d54d53e 修复bug 2025-09-20 17:11:04 +08:00
cb63437e0e 修复swagger 2025-09-19 23:51:13 +08:00
88e0fbfb64 修复swagger 2025-09-19 15:55:56 +08:00
3af1b4949f 允许创建/更新时指定执行次数 2025-09-19 13:35:41 +08:00
11502cb5f0 修改api接口定义 2025-09-19 13:18:05 +08:00
d94a18779e 修改model 2025-09-19 12:53:58 +08:00
46c7b96fed todo 2025-09-18 22:12:19 +08:00
3d2c99afaa Merge pull request '重构' (#4) from 重构 into main
Reviewed-on: #4
2025-09-18 21:51:16 +08:00
5a6153cacc todo 2025-09-18 21:50:51 +08:00
810049d62e 1. 增加任务调度器配置文件
2. 创建/更新计划会自动处理触发器
2025-09-17 23:01:15 +08:00
f7a5e4737d 1. 提取触发器逻辑
2. 创建/更新计划时自动生成对应触发器
2025-09-17 22:43:35 +08:00
ceba0c280e 任务调度器关于任务执行部分实现(没测没检查, 但应该实现完了) 2025-09-17 20:02:40 +08:00
e6047f6b6e 改注释 2025-09-17 17:04:16 +08:00
dde277c14d 实现task工厂 2025-09-17 16:55:56 +08:00
8b8c539e06 定义task接口和delay_task实现 2025-09-17 16:17:36 +08:00
4a24e1a08d task.go 改名 2025-09-17 15:56:08 +08:00
db75370873 实现意外获取Task后重新放回去 2025-09-17 15:47:58 +08:00
c750ef350d 实现意外获取Task后重新放回去 2025-09-17 15:45:40 +08:00
2402c206dc 实现task调度器同一时间只能运行同一个plan的同一个task 2025-09-17 14:58:41 +08:00
1e949aab69 task调度器该用ants线程池 2025-09-16 23:54:15 +08:00
23343a8558 重写task调度器之简单实现 2025-09-16 23:31:36 +08:00
3271f820d4 定义执行历史和执行队列model, 以及基础的增删改查功能 2025-09-16 23:11:07 +08:00
8980c29b07 移动文件位置 2025-09-16 17:14:17 +08:00
6271dc2e6a 移动文件位置 2025-09-16 17:13:51 +08:00
c9df4fd6f4 实现 GeneralDeviceService 2025-09-16 17:12:27 +08:00
2ac3dcda84 去掉ping功能, 异步状态下设备ping平台, 平台ping设备没用 2025-09-15 23:46:14 +08:00
8ede415443 todo 2025-09-15 23:45:36 +08:00
1b255090b0 生成proto代码 2025-09-15 23:45:15 +08:00
80a35c2f14 TODO 2025-09-15 23:05:00 +08:00
b7c67ea3c2 定义GeneralDeviceService 2025-09-15 22:25:05 +08:00
264bcf9cb7 todo 2025-09-15 22:24:10 +08:00
621c45f9c0 定义上行事件监听器 2025-09-15 22:01:00 +08:00
6131f38232 todo 2025-09-15 22:00:22 +08:00
4980495f90 定义 device service 2025-09-15 21:28:22 +08:00
b183ad660f 实现 chirp stack transport 的Send方法 2025-09-15 21:27:54 +08:00
78e885e4ce 移动文件位置 2025-09-15 21:26:31 +08:00
28cf997f1d 定义平台和区域主控间的消息格式 2025-09-15 21:26:18 +08:00
417e63fbcb 调整文件目录 2025-09-15 21:25:48 +08:00
4105d91741 调整gorm注解 2025-09-15 20:01:09 +08:00
3ecebc0d61 增加待处理事项 2025-09-15 20:00:57 +08:00
9869897349 调整包名 2025-09-15 20:00:41 +08:00
ff5b40bb56 变量改名 2025-09-15 18:57:47 +08:00
dedb429186 定义 ChirpStackTransport 2025-09-15 18:56:11 +08:00
3876162f1d 移动代码位置 2025-09-15 18:02:10 +08:00
d42ca300d3 用chirp_stack swagger文档生成代码 2025-09-15 17:52:51 +08:00
bed87ba9ce 导入chirp_stack配置和swagger文档 2025-09-15 17:33:25 +08:00
6dc6fc427c swag 2025-09-14 21:30:20 +08:00
784b02b571 实现 UpdatePlan 和单测 2025-09-14 17:39:44 +08:00
f11bd35b74 实现 DeletePlan 和单测 2025-09-14 17:02:36 +08:00
164c7a4923 实现ListPlans和单测 2025-09-14 16:52:59 +08:00
a5a4f6cbe2 改变量名 2025-09-14 16:42:54 +08:00
04242ab3d8 实现GetPlan单测 2025-09-14 16:40:28 +08:00
563571d4a5 实现GetPlan 2025-09-14 16:19:09 +08:00
b926f7d6a3 实现CreatePlan单测 2025-09-14 16:12:56 +08:00
389c2f9846 定义静音日志对象 2025-09-14 16:08:39 +08:00
55d32dad5f 实现CreatePlan接口 2025-09-14 15:56:55 +08:00
eb1be3f366 增加类型转换时顺序检测 2025-09-14 15:47:08 +08:00
4ddb2c5448 增加顺序修复 2025-09-14 15:18:35 +08:00
8ceff5c3c7 删除UpdatePlan的递归更新, 因为子计划的内容不应该被更改 2025-09-14 15:06:45 +08:00
352c9d9246 修复createPlan逻辑错误 2025-09-14 14:45:56 +08:00
67707c0d41 实现请求和响应与数据库模型转换 2025-09-14 14:13:40 +08:00
1dbef11a8c 定义plan_controller请求和响应 2025-09-14 14:00:42 +08:00
69bdc50b3e 将状态码改成自定义码 2025-09-14 13:36:41 +08:00
b3322fe367 将状态码改成自定义码 2025-09-14 13:23:16 +08:00
b0ce191aff 1. 删除计划单测
2. 增加makefile 生成swagger
2025-09-13 22:29:42 +08:00
fc0e8a36ce 实现删除计划 2025-09-13 22:21:23 +08:00
9fc9cda08e 1. 增加重复顺序校验
2. 增加测试用例
2025-09-13 21:14:22 +08:00
287c27a5ab 实现创建计划 2025-09-13 20:13:53 +08:00
bd22e452d3 补充测试用例 2025-09-13 19:48:13 +08:00
ec2595a751 补充测试用例 2025-09-13 17:49:43 +08:00
5e8ed88832 实现UpdatePlan单测 2025-09-13 17:38:02 +08:00
2228d8e879 实现UpdatePlan 2025-09-13 17:03:46 +08:00
01b11b6e42 增加TestGetPlanByID单测 2025-09-13 15:42:03 +08:00
cacb9a9b1d 修改变量名, 规避order关键字 2025-09-13 15:30:10 +08:00
16bc3b08bc 修复不同单测会使用同一个sqlite实例的情况 2025-09-13 15:29:36 +08:00
d169f9b9d1 增加获取表列表方法 2025-09-13 15:14:08 +08:00
fcea68c7c7 实现GetPlanByID 2025-09-13 15:00:31 +08:00
2b431377c1 实现GetPlanByID 2025-09-13 14:53:31 +08:00
986bdf15a6 定义接口 2025-09-13 14:09:22 +08:00
32e260ed73 查看计划简单信息和单测 2025-09-13 13:04:19 +08:00
4035172a4b 增加延时Task 2025-09-13 12:25:27 +08:00
2593097989 优化命名 2025-09-13 12:12:38 +08:00
6797db914a 初步创建计划相关model 2025-09-12 21:06:19 +08:00
52669e7055 调整swagger注解位置 2025-09-12 20:32:29 +08:00
0884e229fe 修改单测--目前单测全过 2025-09-12 20:28:57 +08:00
03a92ce3e9 增加测试用例 2025-09-12 19:20:33 +08:00
906d371df6 优化注释 2025-09-12 17:51:09 +08:00
9e9bf7b8a0 实现swagger 2025-09-12 17:43:42 +08:00
fe9b0db985 初步实现device_controller 2025-09-12 17:18:14 +08:00
4a78aa1c20 swagger 2025-09-12 16:58:39 +08:00
63db640abe 填充config.yml 2025-09-12 16:52:00 +08:00
8dfcfbfc18 修复单测 2025-09-12 16:40:36 +08:00
8acefb2dd3 DeviceRepository单测实现 2025-09-12 16:36:44 +08:00
d09fb815bb 简单实现DeviceRepository 2025-09-12 16:28:28 +08:00
fe25800a42 定义device结构体 2025-09-12 16:08:39 +08:00
8f9bbca22b application增加API的初始化 2025-09-12 14:58:07 +08:00
41bbe69442 输出消息改为中文 2025-09-12 14:54:07 +08:00
588c819c3c 重写API 2025-09-12 14:39:44 +08:00
29975350a6 Login增加单测 2025-09-12 14:22:37 +08:00
7f73ea2852 CreateUser增加单测 2025-09-12 13:22:19 +08:00
e07017b3fc user_controller使用通用token逻辑 2025-09-12 13:11:04 +08:00
9f72c30e8b 提取token逻辑, 增加token单测 2025-09-12 12:25:53 +08:00
5edf802593 调整日志目录, gemini code assist会根据.gitignore屏蔽目录, 屏蔽logs/会导致infre/logs目录无法访问 2025-09-12 11:52:18 +08:00
5f4ce26b25 update gitignore 2025-09-11 23:48:50 +08:00
196e63f40d 1. 调整目录结构
2. 实现user_controller
2025-09-11 23:48:06 +08:00
1aab2e850a 1. 调整目录结构
2. go mod tidy
2025-09-11 23:10:02 +08:00
68462960fa main调整为只启动application 2025-09-11 23:01:15 +08:00
9f7dbbd470 1. 重写user model和user_repository
2. 增加对应单测
2025-09-11 23:00:48 +08:00
1a42f2b858 重写application包 2025-09-11 21:04:17 +08:00
a12019248e 优化db包 2025-09-11 20:50:51 +08:00
76453bb7bd 修复task调整导致的单测错误 2025-09-11 20:42:29 +08:00
cf011f18f2 1. 重写logs
2. logs增加单测
3. task更换新的log实例
4. 配置文件增加日志相关配置
2025-09-11 20:37:29 +08:00
8704ec477e task_test 2025-09-11 20:13:09 +08:00
4cd05c1b9b 删掉所有文件 2025-09-11 19:30:04 +08:00
2918 changed files with 149765 additions and 1210774 deletions

View File

@@ -0,0 +1,9 @@
name: New MCP server
version: 0.0.1
schema: v1
mcpServers:
- name: chrome-devtools
command: node
args:
- "C:\\nvm4w\\nodejs\\node_modules\\chrome-devtools-mcp\\build\\src\\index.js"
env: {}

View File

@@ -0,0 +1,26 @@
1. **语言与沟通**
* 优先用 Go 语言提供所有代码示例。
* 使用中文进行回复, 且项目中的注释、报错、枚举、日志等所有文本内容全部使用中文。
* 遇到任何不确定或模糊不清的情况,必须直接询问,绝不进行猜测。
2. **工作流程与方案制定**
* 对于所有需求都必须优先输出详尽的文字方案,只有在获得您的明确许可后,才能进行任何代码或文件的修改。
* 制定方案时严格遵循两步流程:
1. **初步方案**: 基于需求,快速形成一个概要方案。
2. **详尽方案**: 通过搜索和阅读所有涉及的代码文件,将初步方案细化为一个不包含任何模糊信息(如“可能需要”、“我需要先查找”等)的、可直接执行的最终方案。
* 如果项目根目录存在 `project_structure.txt` 文件,必须查阅该文件以全面了解项目结构,确保在制定方案和修改文件时使用准确的文件路径。
3. **文件操作与代码修改**
* 我将不再使用任何 MCP 服务提供的能力(包括但不限于 `write_file`, `create_new_file`, `replace_text_in_file` 等)来直接修改、新增或删除文件。
* 每次提出修改前,我仍会先读取文件的最新内容,并基于最新内容提供修改建议。
* 我可以在不征得同意的情况下读取任何我需要分析的文件。
* 在需要新建文件时,我将提供文件内容和建议的文件路径,由用户手动创建。
4. **注释规范**
* 积极编写有价值的功能注释、参数注释和逻辑注释。
* 绝对禁止添加任何解释性、总结性或礼貌性的“废话”注释(例如:“这段代码修复了问题”,“优化后的代码”,“新增:xxx”“注入:xxx”等
* 不得删除或修改用户已有的任何注释,包括但不限于 TODO、FIXME 或文档注释。
5. **多工具链协同应用策略**
* 我知晓并能主动运用下列独立的 MCP 服务,以最高效、最安全的方式完成任务。
* **Chrome DevTools MCP 服务 (浏览器自动化)**: 用于所有与前端浏览器相关的任务,包括页面导航、模拟用户交互、检查 DOM 和网络状态等。

3
.gitignore vendored
View File

@@ -16,6 +16,7 @@ vendor/
# IDE-specific files
.idea/
.vscode/
*.swp
*.swo
@@ -23,3 +24,5 @@ vendor/
.env
bin/
app_logs/
tmp/

11
AGENTS.md Normal file
View File

@@ -0,0 +1,11 @@
# 资源地址
1. 你可以访问 http://localhost:8080/ 进入我的前端界面, 前端项目是另一个项目, 但接入的是当前项目对应的后端平台, 如果需要登录账号密码都是huang
2. 你可以阅读 config/config.yml 了解我的配置信息, 包括数据库的连接地址和账号密码, 本平台监听的端口等, 后端的swagger界面在 http://localhost:8086/swagger/index.html
3. 项目根目录有project_structure.txt, 你需要先阅读此文件了解项目目录结构
4. 项目中有config/presets-data目录, 里面有一些预设数据, 可以间接看作数据库内的数据, 在测试环境中他们一般会和数据库的数据保持一致
# 权限管理
1. 我授权你执行数据库的所有查询类sql
2. 我授权你操作浏览器访问我的项目swagger文档地址和前端项目, 并允许你进行任何操作

View File

@@ -9,6 +9,15 @@ help:
@echo " run Run the application"
@echo " build Build the application"
@echo " clean Clean generated files"
@echo " test Run all tests"
@echo " swag Generate Swagger docs"
@echo " proto Generate protobuf files"
@echo " lint Lint the code"
@echo " dev Run in development mode with hot-reload"
@echo " mcp-chrome Start the Google Chrome MCP server"
@echo " mcp-pgsql Start the PostgreSQL MCP server"
@echo " tree Generate the project file structure list"
@echo " gemini Start the gemini-cli"
@echo " help Show this help message"
# 运行应用
@@ -25,3 +34,60 @@ build:
.PHONY: clean
clean:
rm -f bin/pig-farm-controller
# 运行所有测试
.PHONY: test
test:
go test --count=1 ./...
# 生成swagger文档
.PHONY: swag
swag:
if exist docs rmdir /s /q docs
swag init -g internal/app/api/api.go --parseInternal --parseDependency
# 生成protobuf文件
.PHONY: proto
proto:
protoc --go_out=internal/infra/transport/proto --go_opt=paths=source_relative --go-grpc_out=internal/infra/transport/proto --go-grpc_opt=paths=source_relative -Iinternal/infra/transport/proto internal/infra/transport/proto/device.proto
# 运行代码检查
.PHONY: lint
lint:
golangci-lint run ./... -c ./config/.golangci.yml
# 测试模式(改动文件自动重编译重启)
.PHONY: dev
dev:
air -c ./config/.air.toml
# 启用谷歌浏览器MCP服务器
.PHONY: mcp-chrome
mcp-chrome:
node "C:\nvm4w\nodejs\node_modules\chrome-devtools-mcp\build\src\index.js"
# 启用PostgreSQL MCP服务器
.PHONY: mcp-pgsql
mcp-pgsql:
npx mcp-postgres-server "postgresql://pig-farm-controller:pig-farm-controller@192.168.5.16:5431/pig-farm-controller"
# 生成文件目录树
.PHONY: tree
# 定义要额外排除的生成代码目录
EXCLUDE_CONTEXT_PREFIX = internal/infra/transport/lora/chirp_stack_proto/
# 最终的文件清单会保存在这里
OUTPUT_FILE = project_structure.txt
# 使用 PowerShell 脚本块执行 Git 命令和二次过滤
tree:
@powershell -Command "git ls-files --exclude-standard | Select-String -NotMatch '$(EXCLUDE_CONTEXT_PREFIX)' | Out-File -Encoding UTF8 $(OUTPUT_FILE)"
@powershell -Command "Add-Content -Path $(OUTPUT_FILE) -Value '$(EXCLUDE_CONTEXT_PREFIX)' -Encoding UTF8"
@echo "The project file list has been generated to project_structure.txt"
# 启用gemini-cli
.PHONY: gemini
gemini:
gemini -m "gemini-2.5-flash"

View File

@@ -1,11 +1,17 @@
# 猪场管理系统
## 安装说明
### 推荐使用 TimescaleDB
TimescaleDB 是基于 PostgreSQL 的开源数据库, 专门为处理时序数据而设计的。可以应对后续传海量传感器数据
## 功能介绍
### 一. 猪舍控制
- [ ] 通过猪舍主控操作舍内所有设备(下料口, 风机, 水帘等)
- [ ] 通过猪舍主控采集舍内环境数据(温度, 湿度, 氨气浓度等)
- [x] 通过猪舍主控采集舍内环境数据(温度, 湿度, 氨气浓度等)
- [ ] 监测猪舍主控和舍内设备运行状态
- [ ] 根据监测数据自动调整舍内环境
- [ ] 环境异常自动报警(微信, 邮件, 短信)
@@ -56,4 +62,4 @@
### 九. RESTful API
- [ ] 提供RESTful API接口, 方便其他系统对接
- [x] 提供RESTful API接口, 方便其他系统对接

View File

@@ -1,6 +0,0 @@
// TODO 列表
1. websocket不是安全的wss
2. 添加设备时应该激活一下设备状态采集
3. 设备Model缺少硬件地址
4. 如果同时有两条请求发给同一个设备, 会不会导致接收到错误的回复

16
TODO-List.txt Normal file
View File

@@ -0,0 +1,16 @@
// TODO 列表
// TODO 可以实现的问题
1. plan执行到一半时如果用户删掉里面的task, 后续调度器执行task时可能会找不到这个任务的细节
2. 目前调度器把所有任务都当成定时任务了, 手动和限制次数的没做(增加了model对应字段)
3. 系统启动时应该检查一遍执行历史库, 将所有显示为执行中的任务都修正为执行失败并报错
// TODO 暂时实现不了的问题
1. 目前设备都只对应一个地址, 但实际上如电磁两位五通阀等设备是需要用两个开关控制的
2. Task调度器目前只能一个任务一个任务执行, 但实际上有些任务需要并发执行, 如开启下料口时需要不断从料筒称重传感器读取数据
3. ListenHandler 的实现遇到问题只能panic, 没有处理错误
4. 暂时不考虑和区域主控间的同步消息, 假设所有消息都是异步的, 这可能导致无法知道指令是否执行成功
5. 如果系统停机时间很长, 待执行任务表中的任务过期了怎么办, 目前没有任务过期机制
6. 可以用TimescaleDB代替PGSQL, 优化传感器数据存储性能
已执行次数在停止后需要重置吗

View File

@@ -1,37 +0,0 @@
# 应用配置文件
server:
# Web服务器监听IP
host: "0.0.0.0"
# Web服务器监听端口
port: 8086
# 服务器超时配置(秒)
read_timeout: 30
write_timeout: 30
idle_timeout: 120
# PostgreSQL数据库配置
database:
host: "huangwc.com"
port: 5432
username: "pig-farm-controller"
password: "pig-farm-controller"
dbname: "pig-farm-controller"
sslmode: "disable"
# 连接池配置
max_open_conns: 5
max_idle_conns: 5
conn_max_lifetime: 300 # 5分钟
# WebSocket配置
websocket:
# WebSocket请求超时时间(秒)
timeout: 5
# 心跳检测间隔(秒), 如果超过这个时间没有消息往来系统会自动发送一个心跳包维持长链接
heartbeat_interval: 54
# 心跳配置
heartbeat:
# 心跳间隔(秒)
interval: 30
# 请求并发数
concurrency: 5

52
config/.air.toml Normal file
View File

@@ -0,0 +1,52 @@
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"
[build]
args_bin = []
bin = "tmp\\main.exe"
cmd = "go build -o ./tmp/main.exe ."
delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"
poll = false
poll_interval = 0
post_cmd = []
pre_cmd = []
rerun = false
rerun_delay = 500
send_interrupt = false
stop_on_error = false
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
main_only = false
silent = false
time = false
[misc]
clean_on_exit = false
[proxy]
app_port = 0
enabled = false
proxy_port = 0
[screen]
clear_on_rebuild = false
keep_scroll = true

52
config/.golangci.yml Normal file
View File

@@ -0,0 +1,52 @@
# .golangci.yml - 为你的项目量身定制的 linter 配置
linters-settings:
# 这里可以对特定的 linter 进行微调
errcheck:
# 检查未处理的错误,但可以排除一些常见的、我们确认无需处理的函数
exclude-functions:
- io/ioutil.ReadFile
- io.Copy
- io.WriteString
- os.Create
linters:
# 明确我们想要禁用的 linter
disable:
# --- 暂时禁用的“干扰项” ---
- godox # 禁用对 TODO, FIXME 注释的检查,让我们能专注于代码
# --- 暂时禁用的“风格/复杂度”检查器 ---
- gocyclo # 暂时不检查圈复杂度
- funlen # 暂时不检查函数长度
- lll # 暂时不检查行长度
- wsl # 检查多余的空格和换行,可以后期再处理
- gocritic # 这个检查器包含很多子项,有些可能过于严格,可以先禁用,或在下面精细配置
# 排除路径:分析这些文件但不报告问题(使用 regex 匹配)
exclusions:
paths:
# 排除 docs/ 目录(匹配路径以 docs/ 开头)
- '^docs/'
# 精细排除规则:用于特定文件/文本的 linter 排除
rules:
# 排除对 main.go 中 log.Fatalf 的抱怨(仅针对 goconst linter
- path: '^main\.go$'
text: "log.Fatalf"
linters:
- goconst
# 你也可以明确启用你认为最重要的检查器,形成一个“白名单”
# enable:
# - govet
# - errcheck
# - staticcheck
# - unused
# - gosimple
# - ineffassign
# - typecheck
run:
# 完全跳过测试文件分析(不解析、不报告任何问题)
tests: false

135
config/config.example.yml Normal file
View File

@@ -0,0 +1,135 @@
# 应用基础配置
app:
name: "PigFarmController" # 应用名称
version: "1.0.0" # 应用版本
jwt_secret: "your_jwt_secret_key_here" # JWT 签名密钥,请务必修改为强密码
# 服务器配置
server:
port: 8080 # 服务器监听端口
mode: "debug" # 服务运行模式: debug, release, test
# 日志配置
log:
level: "info" # 日志级别: debug, info, warn, error, dpanic, panic, fatal
format: "console" # 日志输出格式: console, json
enable_file: true # 是否同时输出到文件
file_path: "app_logs/pig_farm_controller.log" # 日志文件路径
max_size: 100 # 单个日志文件最大大小 (MB)
max_backups: 7 # 最多保留的旧日志文件数量
max_age: 7 # 最多保留的旧日志文件天数
compress: true # 是否压缩旧日志文件
enable_trace: false # 是否启用调用链追踪
# 数据库配置
database:
host: "localhost" # 数据库主机地址
port: 5432 # 数据库端口
username: "postgres" # 数据库用户名
password: "your_db_password" # 数据库密码
dbname: "pig_farm_controller_db" # 数据库名称
sslmode: "disable" # SSL模式: disable, require, verify-ca, verify-full
is_timescaledb: false # 是否为 TimescaleDB
max_open_conns: 100 # 最大开放连接数
max_idle_conns: 10 # 最大空闲连接数
conn_max_lifetime: 300 # 连接最大生命周期 (秒)
# WebSocket配置
websocket:
timeout: 60 # WebSocket请求超时时间 (秒)
heartbeat_interval: 30 # 心跳检测间隔 (秒)
# 心跳配置
heartbeat:
interval: 10 # 心跳间隔 (秒)
concurrency: 5 # 请求并发数
# ChirpStack API 配置
chirp_stack:
api_host: "http://localhost:8080" # ChirpStack API 主机地址
api_token: "your_chirpstack_api_token" # ChirpStack API Token
fport: 10 # ChirpStack FPort
api_timeout: 10 # ChirpStack API请求超时时间(秒)
# 等待设备上行响应的超时时间(秒)。
# 对于LoRaWAN这种延迟较高的网络建议设置为5分钟 (300秒) 或更长。
collection_request_timeout: 300
# 任务调度配置
task:
interval: 5 # 任务调度间隔 (秒)
num_workers: 5 # 任务执行器并发工作数量
# Lora 配置
lora:
mode: "lora_mesh" # Lora 运行模式: lora_wan, lora_mesh
# Lora Mesh 配置
lora_mesh:
# 主节点串口
uart_port: "COM7"
# LoRa模块的通信波特率
baud_rate: 9600
# 等待LoRa模块AT指令响应的超时时间(ms)
timeout: 50
# LoRa Mesh 模块发送模式(EC: 透传; ED: 完整数据包)
# e.g.
# EC: 接收端只会接收到消息, 不会接收到请求头
# e.g. 发送: EC 05 02 01 48 65 6c 6c 6f
# (EC + 05(消息长度) + 0201(地址) + "Hello"(消息本体))
# 接收: 48 65 6c 6c 6f ("Hello")
# ED: 接收端会接收完整数据包,包含自定义协议头和地址信息。
# e.g. 发送: ED 05 12 34 01 00 01 02 03
# (ED(帧头) + 05(Length, 即 1(总包数)+1(当前包序号)+3(数据块)) + 12 34(目标地址) + 01(总包数) + 00(当前包序号) + 01 02 03(数据块))
# 接收: ED 05 12 34 01 00 01 02 03 56 78(56 78 是发送方地址,会自动拼接到消息末尾)
lora_mesh_mode: "ED"
# 单包最大用户数据数据长度, 模块限制240, 去掉两位自定义包头, 还剩238
max_chunk_size: 238
#分片重组超时时间(秒)。如果在一个分片到达后,超过这个时间
# 还没收到完整的包,则认为接收失败。
reassembly_timeout: 30
# 通知服务配置
notify:
primary: "日志" # 首选通知渠道: "邮件", "企业微信", "飞书", "日志" (如果其他渠道未启用,"日志" 会自动成为首选)
failureThreshold: 2 # 连续失败多少次后触发广播模式
smtp:
enabled: false # 是否启用 SMTP 邮件通知
host: "smtp.example.com" # SMTP 服务器地址
port: 587 # SMTP 服务器端口
username: "your_email@example.com" # 发件人邮箱地址
password: "your_email_password" # 发件人邮箱授权码或密码
sender: "PigFarm Alarm <no-reply@example.com>" # 发件人名称和地址
wechat:
enabled: false # 是否启用企业微信通知
corpID: "wwxxxxxxxxxxxx" # 企业ID (CorpID)
agentID: "1000001" # 应用ID (AgentID)
secret: "your_wechat_app_secret" # 应用密钥 (Secret)
lark:
enabled: false # 是否启用飞书通知
appID: "cli_xxxxxxxxxx" # 应用 ID
appSecret: "your_lark_app_secret" # 应用密钥
# 定时采集配置
collection:
interval: 1 # 采集间隔 (分钟)
# 告警通知配置
alarm_notification:
notification_intervals: # 告警通知间隔(分钟)
debug: 1
info: 1
warn: 1
error: 1
dpanic: 1
panic: 1
fatal: 1
# AI 服务配置
ai:
model: "gemini" # 不指定就是不用AI
gemini:
api_key: "YOUR_GEMINI_API_KEY" # 替换为你的 Gemini API Key
model_name: "gemini-2.5-flash" # Gemini 模型名称,例如 "gemini-pro"
timeout: 30 # AI 请求超时时间 (秒)

113
config/config.yml Normal file
View File

@@ -0,0 +1,113 @@
# 应用基础配置
app:
name: "pig-farm-controller"
version: "1.0.0"
# JWT 密钥,用于签发和验证 token。请在生产环境中替换为更复杂的密钥
jwt_secret: "pig-farm-controller"
# HTTP 服务配置
server:
port: 8086
mode: "release" # 服务运行模式: "debug", "release", "test"
# 日志配置
log:
level: "info" # 日志级别: "debug", "info", "warn", "error", "dpanic", "panic", "fatal"
format: "console" # 日志格式: "console" 或 "json"
enable_file: true # 是否启用文件日志
file_path: "./app_logs/app.log" # 日志文件路径
max_size: 10 # 每个日志文件的最大尺寸 (MB)
max_backups: 5 # 保留的旧日志文件的最大数量
max_age: 30 # 保留的旧日志文件的最大天数
compress: false # 是否压缩/归档旧日志文件
enable_trace: true # 是否启用调用链追踪
# 数据库配置 (PostgreSQL)
database:
host: "192.168.5.16"
port: 5431
username: "pig-farm-controller"
password: "pig-farm-controller"
dbname: "pig-farm-controller"
sslmode: "disable" # 在生产环境中建议使用 "require"
is_timescaledb: true
max_open_conns: 25 # 最大开放连接数
max_idle_conns: 10 # 最大空闲连接数
conn_max_lifetime: 600 # 连接最大生命周期(秒)
# WebSocket 配置 (如果使用)
websocket:
timeout: 60 # WebSocket请求超时时间(秒)
heartbeat_interval: 30 # 心跳检测间隔(秒)
# 心跳/定时任务配置
heartbeat:
interval: 600 # 任务调度或心跳检查的默认间隔(秒)
concurrency: 2 # 执行任务的并发协程数
# chirp_stack 配置文件
chirp_stack:
api_host: "http://192.168.5.16:8090" # ChirpStack API服务器地址
api_token: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJjaGlycHN0YWNrIiwiaXNzIjoiY2hpcnBzdGFjayIsInN1YiI6IjU2ZWRhNWQ3LTM4NzgtNDAwMC05MWMzLWYwZDk3M2YwODhjNiIsInR5cCI6ImtleSJ9.NxBxTrhPAnezKMqAYZR_Uq2mGQjJRlmVzg1ZDFCyaHQ" # ChirpStack API密钥, 请求头中需要设置 Grpc-Metadata-Authorization: Bearer <YOUR_API_TOKEN>
fport: 1
api_timeout: 10 # ChirpStack API请求超时时间(秒)
# 等待设备上行响应的超时时间(秒)。
# 对于LoRaWAN这种延迟较高的网络建议设置为5分钟 (300秒) 或更长。
collection_request_timeout: 300
# 任务调度器配置
task:
interval: 3
num_workers: 5
# Lora 配置
lora:
mode: "lora_mesh" # "lora_wan" or "lora_mesh"
lora_mesh:
# 主节点串口
uart_port: "COM7"
# LoRa模块的通信波特率
baud_rate: 9600
# 等待LoRa模块AT指令响应的超时时间(ms)
timeout: 50
# LoRa Mesh 模块发送模式(EC: 透传; ED: 完整数据包)
# e.g.
# EC: 接收端只会接收到消息, 不会接收到请求头
# e.g. 发送: EC 05 02 01 48 65 6c 6c 6f
# (EC + 05(消息长度) + 0201(地址) + "Hello"(消息本体))
# 接收: 48 65 6c 6c 6f ("Hello")
# ED: 接收端会接收完整数据包,包含自定义协议头和地址信息。
# e.g. 发送: ED 05 12 34 01 00 01 02 03
# (ED(帧头) + 05(Length, 即 1(总包数)+1(当前包序号)+3(数据块)) + 12 34(目标地址) + 01(总包数) + 00(当前包序号) + 01 02 03(数据块))
# 接收: ED 05 12 34 01 00 01 02 03 56 78(56 78 是发送方地址,会自动拼接到消息末尾)
lora_mesh_mode: "ED"
# 单包最大用户数据数据长度, 模块限制240, 去掉两位自定义包头, 还剩238
max_chunk_size: 238
#分片重组超时时间(秒)。如果在一个分片到达后,超过这个时间
# 还没收到完整的包,则认为接收失败。
reassembly_timeout: 30
# 定时采集配置
collection:
interval: 1 # 采集间隔 (分钟)
# 告警通知配置
alarm_notification:
notification_intervals: # 告警通知间隔 (分钟)
debug: 1
info: 1
warn: 1
error: 1
dpanic: 1
panic: 1
fatal: 1
# AI 服务配置
ai:
model: Gemini
gemini:
api_key: "AIzaSyAJdXUmoN07LIswDac6YxPeRnvXlR73OO8" # 替换为你的 Gemini API Key
model_name: "gemini-2.0-flash" # Gemini 模型名称,例如 "gemini-pro"
timeout: 30 # AI 请求超时时间 (秒)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,914 @@
{
"type": "pig_nutrient_requirements",
"data": {
"杜长大 (DLY)": {
"保育期": {
"可消化赖氨酸 (SID %)": {
"min_requirement": 1.2,
"max_requirement": 1.5
},
"蛋+胱氨酸 (%)": {
"min_requirement": 0.72,
"max_requirement": 1.05
},
"可消化苏氨酸 (SID %)": {
"min_requirement": 0.78,
"max_requirement": 1.08
},
"可消化色氨酸 (SID %)": {
"min_requirement": 0.22,
"max_requirement": 0.30
},
"粗蛋白 (%)": {
"min_requirement": 18.0,
"max_requirement": 22.0
},
"粗脂肪 (%)": {
"min_requirement": 3.0,
"max_requirement": 6.0
},
"粗纤维 (%)": {
"min_requirement": 2.0,
"max_requirement": 6.0
},
"钙 (%)": {
"min_requirement": 0.9,
"max_requirement": 1.2
},
"总磷 (%)": {
"min_requirement": 0.6,
"max_requirement": 0.8
},
"有效磷 (%)": {
"min_requirement": 0.2,
"max_requirement": 0.45
},
"代谢能 (kcal/kg)": {
"min_requirement": 3226.5,
"max_requirement": 3585.0
},
"钠 (%)": {
"min_requirement": 0.15,
"max_requirement": 0.25
},
"氯 (%)": {
"min_requirement": 0.25,
"max_requirement": 0.45
},
"黄曲霉毒素B1 (μg/kg)": {
"max_requirement": 10
},
"呕吐毒素DON (μg/kg)": {
"max_requirement": 1
},
"玉米赤霉烯酮ZEN (μg/kg)": {
"max_requirement": 0.15
}
},
"育肥前期": {
"可消化赖氨酸 (SID %)": {
"min_requirement": 0.94,
"max_requirement": 1.10
},
"蛋+胱氨酸 (%)": {
"min_requirement": 0.55,
"max_requirement": 0.73
},
"可消化苏氨酸 (SID %)": {
"min_requirement": 0.58,
"max_requirement": 0.77
},
"可消化色氨酸 (SID %)": {
"min_requirement": 0.16,
"max_requirement": 0.22
},
"粗蛋白 (%)": {
"min_requirement": 16.0,
"max_requirement": 18.0
},
"粗脂肪 (%)": {
"min_requirement": 3.0,
"max_requirement": 6.0
},
"粗纤维 (%)": {
"min_requirement": 2.0,
"max_requirement": 6.0
},
"钙 (%)": {
"min_requirement": 0.7,
"max_requirement": 0.9
},
"总磷 (%)": {
"min_requirement": 0.5,
"max_requirement": 0.7
},
"有效磷 (%)": {
"min_requirement": 0.2,
"max_requirement": 0.40
},
"代谢能 (kcal/kg)": {
"min_requirement": 3107.0,
"max_requirement": 3346.0
},
"钠 (%)": {
"min_requirement": 0.15,
"max_requirement": 0.25
},
"氯 (%)": {
"min_requirement": 0.25,
"max_requirement": 0.45
},
"黄曲霉毒素B1 (μg/kg)": {
"max_requirement": 10
},
"呕吐毒素DON (μg/kg)": {
"max_requirement": 1
},
"玉米赤霉烯酮ZEN (μg/kg)": {
"max_requirement": 0.15
}
},
"育肥后期": {
"可消化赖氨酸 (SID %)": {
"min_requirement": 0.81,
"max_requirement": 0.90
},
"蛋+胱氨酸 (%)": {
"min_requirement": 0.45,
"max_requirement": 0.58
},
"可消化苏氨酸 (SID %)": {
"min_requirement": 0.48,
"max_requirement": 0.61
},
"可消化色氨酸 (SID %)": {
"min_requirement": 0.13,
"max_requirement": 0.18
},
"粗蛋白 (%)": {
"min_requirement": 14.0,
"max_requirement": 16.0
},
"粗脂肪 (%)": {
"min_requirement": 3.0,
"max_requirement": 6.0
},
"粗纤维 (%)": {
"min_requirement": 2.0,
"max_requirement": 6.0
},
"钙 (%)": {
"min_requirement": 0.6,
"max_requirement": 0.8
},
"总磷 (%)": {
"min_requirement": 0.45,
"max_requirement": 0.6
},
"有效磷 (%)": {
"min_requirement": 0.18,
"max_requirement": 0.35
},
"代谢能 (kcal/kg)": {
"min_requirement": 2987.5,
"max_requirement": 3226.5
},
"钠 (%)": {
"min_requirement": 0.15,
"max_requirement": 0.25
},
"氯 (%)": {
"min_requirement": 0.25,
"max_requirement": 0.45
},
"黄曲霉毒素B1 (μg/kg)": {
"max_requirement": 10
},
"呕吐毒素DON (μg/kg)": {
"max_requirement": 1
},
"玉米赤霉烯酮ZEN (μg/kg)": {
"max_requirement": 0.15
}
},
"二次育肥期": {
"可消化赖氨酸 (SID %)": {
"min_requirement": 0.53,
"max_requirement": 0.65
},
"蛋+胱氨酸 (%)": {
"min_requirement": 0.30,
"max_requirement": 0.41
},
"可消化苏氨酸 (SID %)": {
"min_requirement": 0.31,
"max_requirement": 0.43
},
"可消化色氨酸 (SID %)": {
"min_requirement": 0.10,
"max_requirement": 0.13
},
"粗蛋白 (%)": {
"min_requirement": 12.0,
"max_requirement": 14.0
},
"粗脂肪 (%)": {
"min_requirement": 3.0,
"max_requirement": 6.0
},
"粗纤维 (%)": {
"min_requirement": 2.0,
"max_requirement": 6.0
},
"钙 (%)": {
"min_requirement": 0.5,
"max_requirement": 0.7
},
"总磷 (%)": {
"min_requirement": 0.4,
"max_requirement": 0.55
},
"有效磷 (%)": {
"min_requirement": 0.15,
"max_requirement": 0.30
},
"代谢能 (kcal/kg)": {
"min_requirement": 2868.0,
"max_requirement": 3107.0
},
"钠 (%)": {
"min_requirement": 0.15,
"max_requirement": 0.25
},
"氯 (%)": {
"min_requirement": 0.25,
"max_requirement": 0.45
},
"黄曲霉毒素B1 (μg/kg)": {
"max_requirement": 10
},
"呕吐毒素DON (μg/kg)": {
"max_requirement": 1
},
"玉米赤霉烯酮ZEN (μg/kg)": {
"max_requirement": 0.15
}
}
},
"杜大长 (DYL)": {
"保育期": {
"可消化赖氨酸 (SID %)": {
"min_requirement": 1.2,
"max_requirement": 1.5
},
"蛋+胱氨酸 (%)": {
"min_requirement": 0.72,
"max_requirement": 1.05
},
"可消化苏氨酸 (SID %)": {
"min_requirement": 0.78,
"max_requirement": 1.08
},
"可消化色氨酸 (SID %)": {
"min_requirement": 0.22,
"max_requirement": 0.30
},
"粗蛋白 (%)": {
"min_requirement": 18.0,
"max_requirement": 22.0
},
"粗脂肪 (%)": {
"min_requirement": 3.0,
"max_requirement": 6.0
},
"粗纤维 (%)": {
"min_requirement": 2.0,
"max_requirement": 6.0
},
"钙 (%)": {
"min_requirement": 0.9,
"max_requirement": 1.2
},
"总磷 (%)": {
"min_requirement": 0.6,
"max_requirement": 0.8
},
"有效磷 (%)": {
"min_requirement": 0.2,
"max_requirement": 0.45
},
"代谢能 (kcal/kg)": {
"min_requirement": 3226.5,
"max_requirement": 3585.0
},
"钠 (%)": {
"min_requirement": 0.15,
"max_requirement": 0.25
},
"氯 (%)": {
"min_requirement": 0.25,
"max_requirement": 0.45
},
"黄曲霉毒素B1 (μg/kg)": {
"max_requirement": 10
},
"呕吐毒素DON (μg/kg)": {
"max_requirement": 1
},
"玉米赤霉烯酮ZEN (μg/kg)": {
"max_requirement": 0.15
}
},
"育肥前期": {
"可消化赖氨酸 (SID %)": {
"min_requirement": 0.94,
"max_requirement": 1.10
},
"蛋+胱氨酸 (%)": {
"min_requirement": 0.55,
"max_requirement": 0.73
},
"可消化苏氨酸 (SID %)": {
"min_requirement": 0.58,
"max_requirement": 0.77
},
"可消化色氨酸 (SID %)": {
"min_requirement": 0.16,
"max_requirement": 0.22
},
"粗蛋白 (%)": {
"min_requirement": 16.0,
"max_requirement": 18.0
},
"粗脂肪 (%)": {
"min_requirement": 3.0,
"max_requirement": 6.0
},
"粗纤维 (%)": {
"min_requirement": 2.0,
"max_requirement": 6.0
},
"钙 (%)": {
"min_requirement": 0.7,
"max_requirement": 0.9
},
"总磷 (%)": {
"min_requirement": 0.5,
"max_requirement": 0.7
},
"有效磷 (%)": {
"min_requirement": 0.2,
"max_requirement": 0.40
},
"代谢能 (kcal/kg)": {
"min_requirement": 3107.0,
"max_requirement": 3346.0
},
"钠 (%)": {
"min_requirement": 0.15,
"max_requirement": 0.25
},
"氯 (%)": {
"min_requirement": 0.25,
"max_requirement": 0.45
},
"黄曲霉毒素B1 (μg/kg)": {
"max_requirement": 10
},
"呕吐毒素DON (μg/kg)": {
"max_requirement": 1
},
"玉米赤霉烯酮ZEN (μg/kg)": {
"max_requirement": 0.15
}
},
"育肥后期": {
"可消化赖氨酸 (SID %)": {
"min_requirement": 0.81,
"max_requirement": 0.90
},
"蛋+胱氨酸 (%)": {
"min_requirement": 0.45,
"max_requirement": 0.58
},
"可消化苏氨酸 (SID %)": {
"min_requirement": 0.48,
"max_requirement": 0.61
},
"可消化色氨酸 (SID %)": {
"min_requirement": 0.13,
"max_requirement": 0.18
},
"粗蛋白 (%)": {
"min_requirement": 14.0,
"max_requirement": 16.0
},
"粗脂肪 (%)": {
"min_requirement": 3.0,
"max_requirement": 6.0
},
"粗纤维 (%)": {
"min_requirement": 2.0,
"max_requirement": 6.0
},
"钙 (%)": {
"min_requirement": 0.6,
"max_requirement": 0.8
},
"总磷 (%)": {
"min_requirement": 0.45,
"max_requirement": 0.6
},
"有效磷 (%)": {
"min_requirement": 0.18,
"max_requirement": 0.35
},
"代谢能 (kcal/kg)": {
"min_requirement": 2987.5,
"max_requirement": 3226.5
},
"钠 (%)": {
"min_requirement": 0.15,
"max_requirement": 0.25
},
"氯 (%)": {
"min_requirement": 0.25,
"max_requirement": 0.45
},
"黄曲霉毒素B1 (μg/kg)": {
"max_requirement": 10
},
"呕吐毒素DON (μg/kg)": {
"max_requirement": 1
},
"玉米赤霉烯酮ZEN (μg/kg)": {
"max_requirement": 0.15
}
},
"二次育肥期": {
"可消化赖氨酸 (SID %)": {
"min_requirement": 0.53,
"max_requirement": 0.65
},
"蛋+胱氨酸 (%)": {
"min_requirement": 0.30,
"max_requirement": 0.41
},
"可消化苏氨酸 (SID %)": {
"min_requirement": 0.31,
"max_requirement": 0.43
},
"可消化色氨酸 (SID %)": {
"min_requirement": 0.10,
"max_requirement": 0.13
},
"粗蛋白 (%)": {
"min_requirement": 12.0,
"max_requirement": 14.0
},
"粗脂肪 (%)": {
"min_requirement": 3.0,
"max_requirement": 6.0
},
"粗纤维 (%)": {
"min_requirement": 2.0,
"max_requirement": 6.0
},
"钙 (%)": {
"min_requirement": 0.5,
"max_requirement": 0.7
},
"总磷 (%)": {
"min_requirement": 0.4,
"max_requirement": 0.55
},
"有效磷 (%)": {
"min_requirement": 0.15,
"max_requirement": 0.30
},
"代谢能 (kcal/kg)": {
"min_requirement": 2868.0,
"max_requirement": 3107.0
},
"钠 (%)": {
"min_requirement": 0.15,
"max_requirement": 0.25
},
"氯 (%)": {
"min_requirement": 0.25,
"max_requirement": 0.45
},
"黄曲霉毒素B1 (μg/kg)": {
"max_requirement": 10
},
"呕吐毒素DON (μg/kg)": {
"max_requirement": 1
},
"玉米赤霉烯酮ZEN (μg/kg)": {
"max_requirement": 0.15
}
}
},
"皮长大 (PLY)": {
"保育期": {
"可消化赖氨酸 (SID %)": {
"min_requirement": 1.2,
"max_requirement": 1.5
},
"蛋+胱氨酸 (%)": {
"min_requirement": 0.72,
"max_requirement": 1.05
},
"可消化苏氨酸 (SID %)": {
"min_requirement": 0.78,
"max_requirement": 1.08
},
"可消化色氨酸 (SID %)": {
"min_requirement": 0.22,
"max_requirement": 0.30
},
"粗蛋白 (%)": {
"min_requirement": 18.0,
"max_requirement": 22.0
},
"粗脂肪 (%)": {
"min_requirement": 3.0,
"max_requirement": 6.0
},
"粗纤维 (%)": {
"min_requirement": 2.0,
"max_requirement": 6.0
},
"钙 (%)": {
"min_requirement": 0.9,
"max_requirement": 1.2
},
"总磷 (%)": {
"min_requirement": 0.6,
"max_requirement": 0.8
},
"有效磷 (%)": {
"min_requirement": 0.2,
"max_requirement": 0.45
},
"代谢能 (kcal/kg)": {
"min_requirement": 3226.5,
"max_requirement": 3585.0
},
"钠 (%)": {
"min_requirement": 0.15,
"max_requirement": 0.25
},
"氯 (%)": {
"min_requirement": 0.25,
"max_requirement": 0.45
},
"黄曲霉毒素B1 (μg/kg)": {
"max_requirement": 10
},
"呕吐毒素DON (μg/kg)": {
"max_requirement": 1
},
"玉米赤霉烯酮ZEN (μg/kg)": {
"max_requirement": 0.15
}
},
"育肥前期": {
"可消化赖氨酸 (SID %)": {
"min_requirement": 0.94,
"max_requirement": 1.10
},
"蛋+胱氨酸 (%)": {
"min_requirement": 0.55,
"max_requirement": 0.73
},
"可消化苏氨酸 (SID %)": {
"min_requirement": 0.58,
"max_requirement": 0.77
},
"可消化色氨酸 (SID %)": {
"min_requirement": 0.16,
"max_requirement": 0.22
},
"粗蛋白 (%)": {
"min_requirement": 16.0,
"max_requirement": 18.0
},
"粗脂肪 (%)": {
"min_requirement": 3.0,
"max_requirement": 6.0
},
"粗纤维 (%)": {
"min_requirement": 2.0,
"max_requirement": 6.0
},
"钙 (%)": {
"min_requirement": 0.7,
"max_requirement": 0.9
},
"总磷 (%)": {
"min_requirement": 0.5,
"max_requirement": 0.7
},
"有效磷 (%)": {
"min_requirement": 0.2,
"max_requirement": 0.40
},
"代谢能 (kcal/kg)": {
"min_requirement": 3107.0,
"max_requirement": 3346.0
},
"钠 (%)": {
"min_requirement": 0.15,
"max_requirement": 0.25
},
"氯 (%)": {
"min_requirement": 0.25,
"max_requirement": 0.45
},
"黄曲霉毒素B1 (μg/kg)": {
"max_requirement": 10
},
"呕吐毒素DON (μg/kg)": {
"max_requirement": 1
},
"玉米赤霉烯酮ZEN (μg/kg)": {
"max_requirement": 0.15
}
},
"育肥后期": {
"可消化赖氨酸 (SID %)": {
"min_requirement": 0.81,
"max_requirement": 0.90
},
"蛋+胱氨酸 (%)": {
"min_requirement": 0.45,
"max_requirement": 0.58
},
"可消化苏氨酸 (SID %)": {
"min_requirement": 0.48,
"max_requirement": 0.61
},
"可消化色氨酸 (SID %)": {
"min_requirement": 0.13,
"max_requirement": 0.18
},
"粗蛋白 (%)": {
"min_requirement": 14.0,
"max_requirement": 16.0
},
"粗脂肪 (%)": {
"min_requirement": 3.0,
"max_requirement": 6.0
},
"粗纤维 (%)": {
"min_requirement": 2.0,
"max_requirement": 6.0
},
"钙 (%)": {
"min_requirement": 0.6,
"max_requirement": 0.8
},
"总磷 (%)": {
"min_requirement": 0.45,
"max_requirement": 0.6
},
"有效磷 (%)": {
"min_requirement": 0.18,
"max_requirement": 0.35
},
"代谢能 (kcal/kg)": {
"min_requirement": 2987.5,
"max_requirement": 3226.5
},
"钠 (%)": {
"min_requirement": 0.15,
"max_requirement": 0.25
},
"氯 (%)": {
"min_requirement": 0.25,
"max_requirement": 0.45
},
"黄曲霉毒素B1 (μg/kg)": {
"max_requirement": 10
},
"呕吐毒素DON (μg/kg)": {
"max_requirement": 1
},
"玉米赤霉烯酮ZEN (μg/kg)": {
"max_requirement": 0.15
}
},
"二次育肥期": {
"可消化赖氨酸 (SID %)": {
"min_requirement": 0.53,
"max_requirement": 0.65
},
"蛋+胱氨酸 (%)": {
"min_requirement": 0.30,
"max_requirement": 0.41
},
"可消化苏氨酸 (SID %)": {
"min_requirement": 0.31,
"max_requirement": 0.43
},
"可消化色氨酸 (SID %)": {
"min_requirement": 0.10,
"max_requirement": 0.13
},
"粗蛋白 (%)": {
"min_requirement": 12.0,
"max_requirement": 14.0
},
"粗脂肪 (%)": {
"min_requirement": 3.0,
"max_requirement": 6.0
},
"粗纤维 (%)": {
"min_requirement": 2.0,
"max_requirement": 6.0
},
"钙 (%)": {
"min_requirement": 0.5,
"max_requirement": 0.7
},
"总磷 (%)": {
"min_requirement": 0.4,
"max_requirement": 0.55
},
"有效磷 (%)": {
"min_requirement": 0.15,
"max_requirement": 0.30
},
"代谢能 (kcal/kg)": {
"min_requirement": 2868.0,
"max_requirement": 3107.0
},
"钠 (%)": {
"min_requirement": 0.15,
"max_requirement": 0.25
},
"氯 (%)": {
"min_requirement": 0.25,
"max_requirement": 0.45
},
"黄曲霉毒素B1 (μg/kg)": {
"max_requirement": 10
},
"呕吐毒素DON (μg/kg)": {
"max_requirement": 1
},
"玉米赤霉烯酮ZEN (μg/kg)": {
"max_requirement": 0.15
}
}
}
},
"descriptions": {
"pig_breeds": {
"杜长大 (DLY)": {
"description": "杜长大是中国市场占有率最高的商品肉猪。通过利用杜洛克、长白、大约克三品种的杂种优势,实现高效生长和高瘦肉率。",
"parent_info": "终端父本:杜洛克 (D);二元母本:长白 (L) × 大约克 (Y)。",
"appearance_features": "全身白色,体型健壮、体躯较长,肌肉发达,背腰平直。",
"breed_advantages": "生长速度快、日增重极高、饲料转化率最优、瘦肉率稳定在60%以上、适应性良好、出栏时间最短。",
"breed_disadvantages": "抗应激能力中等,对疫病和环境变化相对敏感;无法作为种猪进行自繁。"
},
"杜大长 (DYL)": {
"description": "杜大长是另一种重要的外三元猪,与杜长大体系相似,但在母本的搭配上有所区别,同样追求高生长速度和高瘦肉率。",
"parent_info": "终端父本:杜洛克 (D);二元母本:大约克 (Y) × 长白 (L)。",
"appearance_features": "全身白色,体型比杜长大略微魁梧,肌肉丰满度高。",
"breed_advantages": "生长性能和瘦肉率与杜长大相当,同时遗传了大约克母本的良好体型和生长潜力,综合性能优秀。",
"breed_disadvantages": "与杜长大相似,无法留作种用,需要依赖稳定的种源体系;对饲养管理要求高。"
},
"皮长大 (PLY)": {
"description": "皮长大是以皮特兰作为终端父本的杂交体系,专注于生产超高瘦肉率的商品肉猪。",
"parent_info": "终端父本:皮特兰 (P);二元母本:长白 (L) × 大约克 (Y)。",
"appearance_features": "多数为白色或带有黑色斑点,肌肉极其发达,后臀饱满,体型呈方形。",
"breed_advantages": "瘦肉率极高能达到65%以上),胴体丰满,背膘薄。",
"breed_disadvantages": "生长速度和日增重逊于杜长大应激敏感性极高易发生P.S.S.管理难度大肉质易出现PSE苍白、软、渗水现象影响口感和加工性能。"
}
},
"pig_age_stages": {
"保育期": "从断奶到转入生长舍。主要目标是适应固体饲料建立肠道菌群确保健康稳定过渡体重约5kg~30kg。",
"育肥前期": "小猪转入育肥舍后到体重达到约60kg的阶段。以骨骼和肌肉生长为主是高效增重期。",
"育肥后期": "体重从约60kg到达到出栏体重约110-120kg的阶段。脂肪沉积速度开始加快是出栏前的冲刺期。",
"二次育肥期": "指收购达到常规出栏体重约100-120kg的商品猪继续饲养至更大体重140-180kg+)的阶段。其存在主要受市场价格波动驱动,目的是提高单体出肉量。"
},
"pig_breed_age_stages": {
"杜长大 (DLY)": {
"保育期": {
"description": "遗传自杜洛克的生长优势在断奶后开始显现,需精细化管理以避免断奶应激和腹泻。",
"daily_feed_intake": 400.0,
"daily_gain_weight": 350.0,
"min_days": 21,
"max_days": 70,
"min_weight": 5000.0,
"max_weight": 30000.0
},
"育肥前期": {
"description": "生长速度和饲料转化率表现良好,是瘦肉沉积效率高的阶段。",
"daily_feed_intake": 1800.0,
"daily_gain_weight": 700.0,
"min_days": 71,
"max_days": 120,
"min_weight": 30000.0,
"max_weight": 60000.0
},
"育肥后期": {
"description": "继续实现较高日增重,脂肪沉积开始加快,管理目标为达到目标出栏体重并保持料肉比。",
"daily_feed_intake": 2800.0,
"daily_gain_weight": 800.0,
"min_days": 121,
"max_days": 180,
"min_weight": 60000.0,
"max_weight": 100000.0
},
"二次育肥期": {
"description": "增重效率下降,料肉比恶化,增重多为脂肪沉积,注意热应激与蹄部问题。",
"daily_feed_intake": 3500.0,
"daily_gain_weight": 600.0,
"min_days": 181,
"max_days": 240,
"min_weight": 100000.0,
"max_weight": 140000.0
}
},
"杜大长 (DYL)": {
"保育期": {
"description": "与杜长大相近,生长潜力强,管理重点为断奶适应与稳定采食。",
"daily_feed_intake": 400.0,
"daily_gain_weight": 330.0,
"min_days": 21,
"max_days": 70,
"min_weight": 5000.0,
"max_weight": 30000.0
},
"育肥前期": {
"description": "生长期增重与料肉比接近杜长大,肌肉发展迅速。",
"daily_feed_intake": 1750.0,
"daily_gain_weight": 680.0,
"min_days": 71,
"max_days": 120,
"min_weight": 30000.0,
"max_weight": 60000.0
},
"育肥后期": {
"description": "保持较高增重速度,脂肪沉积稍快于部分 DLY 群体,需配方微调以控制背膘。",
"daily_feed_intake": 2700.0,
"daily_gain_weight": 770.0,
"min_days": 121,
"max_days": 180,
"min_weight": 60000.0,
"max_weight": 100000.0
},
"二次育肥期": {
"description": "与杜长大相似,采食量高但增重多为脂肪,注意健康与福利管理。",
"daily_feed_intake": 3500.0,
"daily_gain_weight": 580.0,
"min_days": 181,
"max_days": 240,
"min_weight": 100000.0,
"max_weight": 140000.0
}
},
"皮长大 (PLY)": {
"保育期": {
"description": "个体应激敏感性可能更高,需要稳定环境与逐步换料以减少应激性下降重。",
"daily_feed_intake": 350.0,
"daily_gain_weight": 300.0,
"min_days": 21,
"max_days": 70,
"min_weight": 5000.0,
"max_weight": 30000.0
},
"育肥前期": {
"description": "增重速度一般,但瘦肉率高;需注意高应激个体的管理以避免肉质问题。",
"daily_feed_intake": 1600.0,
"daily_gain_weight": 600.0,
"min_days": 71,
"max_days": 120,
"min_weight": 30000.0,
"max_weight": 60000.0
},
"育肥后期": {
"description": "瘦肉率高且脂肪沉积较慢,但应激易导致肉质问题,育肥管理需谨慎。",
"daily_feed_intake": 2400.0,
"daily_gain_weight": 650.0,
"min_days": 121,
"max_days": 180,
"min_weight": 60000.0,
"max_weight": 100000.0
},
"二次育肥期": {
"description": "超重育肥带来的应激和死亡风险增加,通常不推荐长期二次育肥。",
"daily_feed_intake": 3200.0,
"daily_gain_weight": 450.0,
"min_days": 181,
"max_days": 240,
"min_weight": 100000.0,
"max_weight": 140000.0
}
}
}
}
}

View File

@@ -0,0 +1,34 @@
# 任务接口增加获取关联设备ID方法设计
## 1. 需求
为了在设备删除前进行验证需要为任务接口增加一个方法该方法能够直接返回指定任务配置中所有关联的设备ID列表。所有实现 `task` 接口的对象都必须实现此方法。
## 2. 新接口定义:`TaskDeviceIDResolver`
```go
// TaskDeviceIDResolver 定义了从任务配置中解析设备ID的方法
type TaskDeviceIDResolver interface {
// ResolveDeviceIDs 从任务配置中解析并返回所有关联的设备ID列表
// 返回值: uint数组每个字符串代表一个设备ID
ResolveDeviceIDs() ([]uint, error)
}
```
## 3. `task` 接口更新
`task` 接口将嵌入 `TaskDeviceIDResolver` 接口。
```go
// Task 接口(示例,具体结构可能不同)
type Task interface {
// ... 其他现有方法 ...
// 嵌入 TaskDeviceIDResolver 接口
TaskDeviceIDResolver
}
```
## 4. 实现要求
所有当前及未来实现 `Task` 接口的类型,都必须实现 `TaskDeviceIDResolver` 接口中定义的所有方法,即 `ResolveDeviceIDs` 方法。

View File

@@ -0,0 +1,41 @@
# 方案:删除设备前的使用校验
## 1. 目标
在删除设备前,检查该设备是否被任何任务关联。如果设备正在被使用,则禁止删除,并向用户返回明确的错误提示。
## 2. 核心思路
我们将遵循您项目清晰的分层架构,将“检查设备是否被任务使用”这一业务规则放在 **应用层** (`internal/app/service/`)
中进行协调。当上层请求删除设备时,应用服务会先调用仓库层查询 `device_tasks` 关联表,如果发现设备仍被任务关联,则会拒绝删除并返回一个明确的业务错误。
## 3. 实施步骤
### 3.1. 仓库层 (`DeviceRepository`)
- **动作**: 在 `internal/infra/repository/device_repository.go``DeviceRepository` 接口中,增加一个新方法
`IsDeviceInUse(deviceID uint) (bool, error)`
- **实现**: 在 `gormDeviceRepository` 中实现此方法。该方法将通过对 `models.DeviceTask` 模型执行 `Count`
操作来高效地判断是否存在 `device_id` 匹配的记录。这比查询完整记录性能更好。
### 3.2. 应用层 (`DeviceService`)
- **动作**:
1.`internal/app/service/device_service.go` 文件顶部定义一个新的错误变量 `ErrDeviceInUse`,例如
`var ErrDeviceInUse = errors.New("设备正在被一个或多个任务使用,无法删除")`
2. 修改该文件中的 `DeleteDevice` 方法。
- **实现**: 在 `DeleteDevice` 方法中,在调用 `s.deviceRepo.Delete()` 之前,先调用我们刚刚创建的
`s.deviceRepo.IsDeviceInUse()` 方法。如果返回 `true`,则立即返回 `ErrDeviceInUse` 错误,中断删除流程。
### 3.3. 表现层 (`DeviceController`)
- **动作**: 修改 `internal/app/controller/device/device_controller.go` 中的 `DeleteDevice` 方法。
- **实现**: 在错误处理逻辑中,增加一个 `case` 来专门捕获从服务层返回的 `service.ErrDeviceInUse`
错误。当捕获到此错误时,返回一个带有明确提示信息(如“设备正在被任务使用,无法删除”)和合适 HTTP 状态码(例如 `409 Conflict`)的错误响应。
## 4. 方案优势
- **职责清晰**: 业务流程的编排和校验逻辑被正确地放置在应用层,符合您项目清晰的分层架构。
- **高效查询**: 通过 `COUNT` 查询代替 `Find`,避免了不必要的数据加载,性能更佳。
- **代码内聚**: 与设备相关的数据库操作都统一封装在 `DeviceRepository` 中。
- **用户友好**: 通过在控制器层处理特定业务错误,可以给前端返回明确、可操作的错误信息。

View File

@@ -0,0 +1,111 @@
# 方案:维护设备与任务的关联关系
## 1. 目标
在对计划Plan及其包含的任务Task进行创建、更新、删除CRUD操作时同步维护 `device_tasks` 这张多对多关联表。
这是实现“删除设备前检查其是否被任务使用”这一需求的基础。
## 2. 核心挑战
1. **参数结构异构性**:不同类型的任务(`TaskType`),其设备 ID 存储在 `Parameters` (JSON) 字段中的 `key` 和数据结构(单个 ID
或 ID 数组)各不相同。
2. **分层架构原则**:解析 `Parameters` 以提取设备 ID 的逻辑属于 **业务规则**,需要找到一个合适的位置来封装它,以维持各层职责的清晰。
## 3. 方案设计
本方案旨在最大化地复用现有领域模型和逻辑,通过扩展 `TaskFactory` 来实现设备ID的解析从而保持了各领域模块的高内聚和低耦合。
### 3.1. 核心思路:复用领域对象与工厂
我们不移动任何结构体,也不在 `plan` 包中引入任何具体任务的实现细节。取而代之,我们利用现有的 `TaskFactory`
和各个任务领域对象自身的能力来解析参数。
每个具体的任务领域对象(如 `ReleaseFeedWeightTask`)最了解如何解析自己的 `Parameters`。因此我们将解析设备ID的责任完全交还给它们。
### 3.2. 扩展 `TaskFactory`
- **动作**:在 `plan.TaskFactory` 接口中增加一个新方法 `CreateTaskFromModel(*models.Task) (TaskDeviceIDResolver, error)`
- **目的**:此方法允许我们在非任务执行的场景下(例如,在增删改查计划时),仅根据数据库模型 `models.Task` 来创建一个临时的、轻量级的任务领域对象。
- **实现**:在 `internal/domain/task/task.go``taskFactory` 中实现此方法。它会根据传入的 `taskModel.Type``switch-case`
来调用相应的构造函数(如 `NewReleaseFeedWeightTask`)创建实例。
- **实现**
- **优势**
- **高内聚,低耦合**`plan` 包保持通用,无需了解任何具体任务的参数细节。参数定义和解析逻辑都保留在各自的 `task` 包内。
- **逻辑复用**:完美复用了您已在 `ReleaseFeedWeightTask` 中实现的 `ResolveDeviceIDs` 方法,避免了重复代码。
### 3.3. 调整领域服务层 (`PlanService`)
`PlanService` 将作为此业务用例的核心编排者。借助 `UnitOfWork` 模式,它可以在单个事务中协调多个仓库,完成数据准备和持久化。
- **职责**:在创建或更新计划的业务流程中,负责解析任务参数、准备设备关联数据,并调用仓库层完成持久化。
- **实现**
-`planServiceImpl` 注入 `repository.UnitOfWork``plan.TaskFactory`
-`CreatePlan``UpdatePlan` 方法中,使用 `unitOfWork.ExecuteInTransaction` 来包裹整个操作。
- 在事务闭包内,遍历计划中的所有任务 (`models.Task`)
1. 调用 `taskFactory.CreateTaskFromModel(taskModel)` 创建一个临时的任务领域对象。
2. 调用该领域对象的 `ResolveDeviceIDs()` 方法获取设备ID列表。
3. 使用事务性的 `DeviceRepository` 查询出设备实体。
4. 将查询到的设备实体列表填充到 `taskModel.Devices` 字段中。
- 最后,将填充好关联数据的 `plan` 对象传递给事务性的 `PlanRepository` 进行创建或更新。
- **优势**
- **职责清晰**`PlanService` 完整地拥有了“创建/更新计划”的业务逻辑,而仓库层则回归到纯粹的数据访问职责。
- **数据一致性**`UnitOfWork` 确保了从准备数据(查询设备)到最终持久化(创建计划和关联)的所有数据库操作都在一个原子事务中完成。
### 3.4. 调整仓库层 (`PlanRepository`)
仓库层被简化,回归其作为数据持久化网关的纯粹角色。
- **职责**:负责 `Plan` 及其直接子对象(`Task`, `SubPlan`)的 CRUD 操作。
- **实现**
- `CreatePlan``UpdatePlanMetadataAndStructure` 方法将被简化。它们不再需要任何特殊的关联处理逻辑(如 `Association().Replace()`)。
- 只需接收一个由 `PlanService` 准备好的、`task.Devices` 字段已被填充的 `plan` 对象。
-`CreatePlan` 中,调用 `tx.Create(plan)`GORM 会自动级联创建 `Plan``Task` 以及 `device_tasks` 中的关联记录。
-`UpdatePlanMetadataAndStructure``reconcileTasks` 逻辑中对于新创建的任务GORM 的 `tx.Create(task)` 同样会自动处理其设备关联。
### 3.5. 整体流程
**创建计划** 为例:
1. `PlanController` 调用 `PlanService.CreatePlan(plan)`
2. `PlanService` 调用 `unitOfWork.ExecuteInTransaction` 启动一个数据库事务。
3. 在事务闭包内,`PlanService` 遍历 `plan` 对象中的所有 `task`
4. 对于每一个 `task` 模型,调用 `taskFactory.CreateTaskFromModel(task)` 创建一个临时的领域对象。
5. 调用该领域对象的 `ResolveDeviceIDs()` 方法,获取其使用的设备 ID 列表。
6. 如果返回了设备 ID 列表,则使用事务性的 `DeviceRepository` 查询出 `[]models.Device` 实体。
7. 所有 `task` 的关联数据准备好后,调用事务性的 `PlanRepository.CreatePlan(plan)`。GORM 在创建 `plan``task` 的同时,会自动创建
`device_tasks` 表中的关联记录。
8. `UnitOfWork` 提交事务。
**更新计划** 的流程与创建类似,在 `UpdatePlanMetadataAndStructure` 方法中,由于会先删除旧任务再创建新任务,因此在创建新任务后执行相同的设备关联步骤。
**删除计划** 时,由于 `Task` 模型上配置了 `OnDelete:CASCADE`GORM 会自动删除关联的 `Task` 记录。同时GORM 的多对多删除逻辑会自动清理
`device_tasks` 表中与被删除任务相关的记录。因此 `DeletePlan` 方法无需修改。
## 4. 实施步骤
1. **扩展 `TaskFactory` 接口**
-`internal/domain/plan/task.go` 文件中,为 `TaskFactory` 接口添加
`CreateTaskFromModel(*models.Task) (TaskDeviceIDResolver, error)` 方法。
2. **实现 `TaskFactory` 新方法**
-`internal/domain/task/task.go` 文件中,为 `taskFactory` 结构体实现 `CreateTaskFromModel` 方法。
3. **修改 `PlanService`**
-`internal/domain/plan/plan_service.go` 中:
- 修改 `planServiceImpl` 结构体,增加 `unitOfWork repository.UnitOfWork``taskFactory TaskFactory` 字段。
- 修改 `NewPlanService` 构造函数,接收并注入这些新依赖。
- 重构 `CreatePlan``UpdatePlan` 方法,使用 `UnitOfWork` 包裹事务,并在其中实现数据准备和关联逻辑。
4. **修改 `PlanRepository`**
-`internal/infra/repository/plan_repository.go` 中:
- **简化 `CreatePlan` 和 `UpdatePlanMetadataAndStructure` 方法**。移除所有手动处理设备关联的代码(例如,如果之前有 `Association("Devices").Replace()` 等调用,则应删除)。
- 确保这两个方法的核心逻辑就是调用 GORM 的 `Create``Updates`,信任 GORM 会根据传入模型中已填充的 `Devices` 字段来自动维护多对多关联。
5. **修改依赖注入**
-`internal/core/component_initializers.go` (或类似的依赖注入入口文件) 中:
-`unitOfWork``taskFactory` 实例传递给 `plan.NewPlanService` 的构造函数。
## 5. 结论
此方案通过复用现有的领域对象和工厂模式,优雅地解决了设备关联维护的问题。它保持了清晰的架构分层和模块职责,在实现功能的同时,为项目未来的扩展和维护奠定了坚实、可扩展的基础。

View File

@@ -0,0 +1,103 @@
# 设备与任务多对多关联模型设计
## 需求背景
用户需要为系统中的“设备”和“任务”增加多对多关联,即一个设备可以执行多个任务,一个任务可以被多个设备执行。
## 现有模型分析
### `internal/infra/models/device.go`
`Device` 模型定义:
```go
type Device struct {
gorm.Model
Name string `gorm:"not null" json:"name"`
DeviceTemplateID uint `gorm:"not null;index" json:"device_template_id"`
DeviceTemplate DeviceTemplate `json:"device_template"`
AreaControllerID uint `gorm:"not null;index" json:"area_controller_id"`
AreaController AreaController `json:"area_controller"`
Location string `gorm:"index" json:"location"`
Properties datatypes.JSON `json:"properties"`
}
```
### `internal/infra/models/plan.go`
`Task` 模型定义:
```go
type Task struct {
ID int `gorm:"primarykey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
PlanID uint `gorm:"not null;index" json:"plan_id"`
Name string `gorm:"not null" json:"name"`
Description string `json:"description"`
ExecutionOrder int `gorm:"not null" json:"execution_order"`
Type TaskType `gorm:"not null" json:"type"`
Parameters datatypes.JSON `json:"parameters"`
}
```
## 方案设计
为了实现设备和任务的多对多关系,我们将引入一个中间关联模型 `DeviceTask`。考虑到 `Task` 模型定义在 `plan.go` 中,为了保持相关模型的内聚性,我们将 `DeviceTask` 模型也定义在 `internal/infra/models/plan.go` 文件中。
### 1. 在 `internal/infra/models/plan.go` 中新增 `DeviceTask` 关联模型
`DeviceTask` 模型将包含 `DeviceID``TaskID` 作为外键,以及 GORM 的标准模型字段。
```go
// DeviceTask 是设备和任务之间的关联模型,表示一个设备可以执行多个任务,一个任务可以被多个设备执行。
type DeviceTask struct {
gorm.Model
DeviceID uint `gorm:"not null;index"` // 设备ID
TaskID uint `gorm:"not null;index"` // 任务ID
// 可选:如果需要存储关联的额外信息,可以在这里添加字段,例如:
// Configuration datatypes.JSON `json:"configuration"` // 任务在特定设备上的配置
}
// TableName 自定义 GORM 使用的数据库表名
func (DeviceTask) TableName() string {
return "device_tasks"
}
```
### 2. 修改 `internal/infra/models/device.go`
`Device` 结构体中添加 `Tasks` 字段,通过 `gorm:"many2many:device_tasks;"` 标签声明与 `Task` 的多对多关系,并指定中间表名为 `device_tasks`
```go
// Device 代表系统中的所有普通设备
type Device struct {
gorm.Model
// ... 其他现有字段 ...
// Tasks 是与此设备关联的任务列表,通过 DeviceTask 关联表实现多对多关系
Tasks []Task `gorm:"many2many:device_tasks;" json:"tasks"`
}
```
### 3. 修改 `internal/infra/models/plan.go`
`Task` 结构体中添加 `Devices` 字段,通过 `gorm:"many2many:device_tasks;"` 标签声明与 `Device` 的多对多关系,并指定中间表名为 `device_tasks`
```go
// Task 代表计划中的一个任务,具有执行顺序
type Task struct {
// ... 其他现有字段 ...
// Devices 是与此任务关联的设备列表,通过 DeviceTask 关联表实现多对多关系
Devices []Device `gorm:"many2many:device_tasks;" json:"devices"`
}
```
## 总结
通过上述修改,我们将在数据库中创建一个名为 `device_tasks` 的中间表,用于存储 `Device``Task` 之间的关联关系。在 Go 代码层面,`Device``Task` 模型将能够直接通过 `Tasks``Devices` 字段进行多对多关系的查询和操作。

View File

@@ -0,0 +1,24 @@
# 需求
删除设备/设备模板/区域主控前进行校验
## issue
http://git.huangwc.com/pig/pig-farm-controller/issues/50
## 需求描述
1. 删除设备时检测是否被任务使用
2. 删除设备模板时检测是否被设备使用
3. 删除区域主控时检测是否被设备使用
# 实现
1. [重构计划领域](./plan_service_refactor.md)
2. [让任务可以提供自身使用设备](./add_get_device_id_configs_to_task.md)
3. [现有计划管理逻辑迁移](./plan_service_refactor_to_domain.md)
4. [增加设备任务关联表](./device_task_many_to_many_design.md)
5. [增加任务增删改查时对设备任务关联表的维护](./device_task_association_maintenance.md)
6. [删除设备时检查](./check_before_device_deletion.md)
7. [删除设备模板时检查和删除区域主控时检查](./refactor_deletion_check.md)
8. [优化设备服务方法的入参](./refactor_id_conversion.md)

View File

@@ -0,0 +1,83 @@
# 计划服务重构设计方案
## 1. 目标
`internal/domain/scheduler` 包重构为 `internal/domain/plan`,并创建一个新的 `Service` 对象,将原 `scheduler`
包中的核心调度逻辑集成到 `Service` 中作为一个子服务,统一由 `Service`
对外提供服务。此重构旨在提高代码的模块化、可维护性和可测试性,并为后续的“设备删除前校验”功能奠定基础。
## 2. 方案详情
### 2.1. 包重命名
*`internal/domain/scheduler` 目录重命名为 `internal/domain/plan`
* 修改 `internal/domain/plan` 目录下所有 Go 文件中的 `package scheduler``package plan`
* 更新 `internal/domain/plan` 目录下所有 Go 文件中所有引用
`git.huangwc.com/pig/pig-farm-controller/internal/domain/scheduler` 的导入路径为
`git.huangwc.com/pig/pig-farm-controller/internal/domain/plan`
### 2.2. `internal/domain/plan` 包内部结构调整
* **`internal/domain/plan/task.go`**:
* 保持不变。它定义了任务的接口和工厂,是领域内的核心抽象。
* **`internal/domain/plan/plan_execution_manager.go`**:
*`Scheduler` 结构体更名为 `ExecutionManagerImpl`。这个名称更准确地反映了它作为计划任务执行的协调者和管理者的具体实现。
*`NewScheduler` 构造函数更名为 `NewExecutionManagerImpl`
* 文件内部所有对 `Scheduler` 的引用都将更新为 `ExecutionManagerImpl`
* **`internal/domain/plan/analysis_plan_task_manager.go`**:
*`AnalysisPlanTaskManager` 结构体更名为 `AnalysisPlanTaskManagerImpl`
*`NewAnalysisPlanTaskManager` 构造函数更名为 `NewAnalysisPlanTaskManagerImpl`
* 文件内部所有对 `AnalysisPlanTaskManager` 的引用都将更新为 `AnalysisPlanTaskManagerImpl`
* **定义领域层接口**:
*`internal/domain/plan` 包中定义 `ExecutionManager` 接口,包含 `ExecutionManagerImpl` 对外暴露的所有公共方法。
*`internal/domain/plan` 包中定义 `AnalysisPlanTaskManager` 接口,包含 `AnalysisPlanTaskManagerImpl` 对外暴露的所有公共方法。
* `ExecutionManagerImpl``AnalysisPlanTaskManagerImpl` 将分别实现对应的接口。
### 2.3. 创建 `internal/domain/plan/plan_service.go`
* 创建新文件 `internal/domain/plan/plan_service.go`
* **定义领域服务接口**:
*`internal/domain/plan` 包中定义 `Service` 接口,该接口将聚合 `ExecutionManager``AnalysisPlanTaskManager`
的所有公共方法,并由 `planServiceImpl` 实现这些方法的委托。
* **实现领域服务**:
* 该文件将定义 `planServiceImpl` 结构体,并包含 `ExecutionManager` 接口和 `AnalysisPlanTaskManager` 接口的实例作为其依赖。
* 实现 `NewService` 构造函数,负责接收 `ExecutionManager` 接口和 `AnalysisPlanTaskManager` 接口的实例,并将其注入到
`planServiceImpl` 中。
* `planServiceImpl` 将对外提供高层次的 API这些 API 会协调调用其依赖的接口方法。例如:
* `Service.Start()` 方法会调用 `ExecutionManager` 接口的 `Start()` 方法。
* `Service.Stop()` 方法会调用 `ExecutionManager` 接口的 `Stop()` 方法。
* `Service.RefreshPlanTriggers()` 方法会调用 `AnalysisPlanTaskManager` 接口的 `Refresh()` 方法。
* `Service.CreateOrUpdateTrigger()` 方法会调用 `AnalysisPlanTaskManager` 接口的 `CreateOrUpdateTrigger()` 方法。
* `Service.EnsureAnalysisTaskDefinition()` 方法会调用 `AnalysisPlanTaskManager` 接口的
`EnsureAnalysisTaskDefinition()` 方法。
* 未来所有与计划相关的领域操作,都将通过 `Service` 接口进行。
### 2.4. 调整依赖注入和引用
* **查找并替换导入路径:** 使用 `grep` 命令查找整个项目中所有引用
`git.huangwc.com/pig/pig-farm-controller/internal/domain/scheduler` 的地方,并将其替换为
`git.huangwc.com/pig/pig-farm-controller/internal/domain/plan`
* **更新 `internal/core/component_initializers.go`**:
* 在初始化阶段,我们将创建 `plan.ExecutionManagerImpl``plan.AnalysisPlanTaskManagerImpl` 的具体实例。
* 然后,将这些具体实例作为 `plan.ExecutionManager` 接口和 `plan.AnalysisPlanTaskManager` 接口类型传递给
`plan.NewService` 构造函数,创建 `planServiceImpl` 实例。
* 最终,`plan.NewService` 返回 `plan.Service` 接口类型。
* 应用程序的其他部分将通过 `plan.Service` 接口来访问计划相关的逻辑,而不是直接访问底层的管理器或其具体实现。
## 3. 优势
* **职责分离清晰:** `internal/domain/plan` 包专注于计划领域的核心逻辑和管理,并提供统一的 `Service` 接口作为领域服务的入口。
* **符合领域驱动设计:** 领域层包含核心业务逻辑和管理器,应用层(如果需要)作为领域层的协调者。
* **与现有项目风格一致:** 借鉴 `domain/pig` 包的模式,提高了项目内部的一致性。
* **可测试性增强:** `Service` 可以更容易地进行单元测试,因为其依赖的接口可以被模拟。
* **可维护性提高:** 当计划相关的业务逻辑发生变化时,可以更精确地定位到需要修改的组件。
* **松耦合:** `Service` 不依赖于具体的实现,而是依赖于接口,提高了系统的灵活性和可扩展性。
## 4. 验证和测试
在完成所有修改后,需要运行项目并进行测试,确保调度器功能正常,没有引入新的错误。

View File

@@ -0,0 +1,179 @@
# 重构方案:将 `app/service/plan_service.go` 的核心逻辑迁移到 `domain/plan/plan_service.go`
## 目标:
* `app/service/plan_service.go` (应用服务层): 仅负责接收 DTO、将 DTO 转换为领域实体、调用 `domain/plan/plan_service` 的领域方法,并将领域方法返回的领域实体转换为 DTO 返回。
* `domain/plan/plan_service.go` (领域层): 封装所有与计划相关的业务逻辑、验证规则、状态管理以及对领域实体的查询操作。
## 详细步骤:
### 第一步:修改 `domain/plan/plan_service.go` (领域层)
1. **引入必要的依赖**:
* `repository.PlanRepository`:用于与计划数据存储交互。
* `repository.DeviceRepository`:如果计划逻辑中需要设备信息。
* `models.Plan`:领域实体。
* `errors``gorm.ErrRecordNotFound`:用于错误处理。
* `models.PlanTypeSystem`, `models.PlanStatusEnabled`, `models.PlanContentTypeSubPlans`, `models.PlanContentTypeTasks` 等常量。
* `git.huangwc.com/pig/pig-farm-controller/internal/infra/models`
* `git.huangwc.com/pig/pig-farm-controller/internal/infra/repository`
* `errors`
* `gorm.io/gorm`
2. **定义领域层错误**: 将 `app/service/plan_service.go` 中定义的错误(`ErrPlanNotFound`, `ErrPlanCannotBeModified` 等)迁移到 `domain/plan/plan_service.go`,并根据领域层的语义进行调整。
```go
var (
// ErrPlanNotFound 表示未找到计划
ErrPlanNotFound = errors.New("计划不存在")
// ErrPlanCannotBeModified 表示计划不允许修改
ErrPlanCannotBeModified = errors.New("系统计划不允许修改")
// ErrPlanCannotBeDeleted 表示计划不允许删除
ErrPlanCannotBeDeleted = errors.New("系统计划不允许删除")
// ErrPlanCannotBeStarted 表示计划不允许手动启动
ErrPlanCannotBeStarted = errors.New("系统计划不允许手动启动")
// ErrPlanAlreadyEnabled 表示计划已处于启动状态
ErrPlanAlreadyEnabled = errors.New("计划已处于启动状态,无需重复操作")
// ErrPlanNotEnabled 表示计划未处于启动状态
ErrPlanNotEnabled = errors.New("计划当前不是启用状态")
// ErrPlanCannotBeStopped 表示计划不允许停止
ErrPlanCannotBeStopped = errors.New("系统计划不允许停止")
)
```
3. **扩展 `plan.Service` 接口**:
* 将 `app/service/plan_service.go` 中 `PlanService` 接口的所有方法(`CreatePlan`, `GetPlanByID`, `ListPlans`, `UpdatePlan`, `DeletePlan`, `StartPlan`, `StopPlan`)添加到 `domain/plan/Service` 接口中。
* 这些方法的参数和返回值将直接使用领域实体(`*models.Plan`)或基本类型,而不是 DTO。例如
* `CreatePlan(plan *models.Plan) (*models.Plan, error)`
* `GetPlanByID(id uint) (*models.Plan, error)`
* `ListPlans(opts repository.ListPlansOptions, page, pageSize int) ([]models.Plan, int64, error)`
* `UpdatePlan(plan *models.Plan) (*models.Plan, error)`
* `DeletePlan(id uint) error`
* `StartPlan(id uint) error`
* `StopPlan(id uint) error`
4. **修改 `planServiceImpl` 结构体**:
* 添加 `planRepo repository.PlanRepository` 字段。
* 添加 `deviceRepo repository.DeviceRepository` 字段 (如果需要)。
* `analysisPlanTaskManager plan.AnalysisPlanTaskManager` 字段保持不变。
```go
type planServiceImpl struct {
executionManager ExecutionManager
taskManager AnalysisPlanTaskManager
planRepo repository.PlanRepository // 新增
// deviceRepo repository.DeviceRepository // 如果需要,新增
logger *logs.Logger
}
```
5. **实现 `plan.Service` 接口中的新方法**:
* 将 `app/service/plan_service.go` 中 `planService` 的所有业务逻辑方法(`CreatePlan`, `GetPlanByID`, `ListPlans`, `UpdatePlan`, `DeletePlan`, `StartPlan`, `StopPlan`)的实现,迁移到 `domain/plan/planServiceImpl` 中。
* **关键修改点**:
* **参数和返回值**: 确保这些方法现在接收和返回的是 `*models.Plan` 或其他领域实体,而不是 DTO。
* **业务逻辑**: 保留所有的业务规则、验证和状态管理逻辑。
* **依赖**: 这些方法将直接调用 `planRepo` 和 `analysisPlanTaskManager`。
* **日志**: 日志记录保持不变,但可能需要调整日志信息以反映领域层的上下文。
* **错误处理**: 错误处理逻辑保持不变,但现在将返回领域层定义的错误。
* **ContentType 自动判断**: `CreatePlan` 和 `UpdatePlan` 中的 `ContentType` 自动判断逻辑应该保留在领域层。
* **执行计数器重置**: `UpdatePlan` 和 `StartPlan` 中的执行计数器重置逻辑应该保留在领域层。
* **系统计划限制**: 对系统计划的修改、删除、启动、停止限制逻辑应该保留在领域层。
* **验证和重排顺序**: `models.Plan` 的 `ValidateExecutionOrder()` 和 `ReorderSteps()` 方法的调用应该在 `CreatePlan` 和 `UpdatePlan` 方法的领域层实现中进行,而不是在 DTO 转换函数中。
6. **修改 `NewPlanService` 函数**: 接收 `repository.PlanRepository` 和 `repository.DeviceRepository` (如果需要) 作为参数,并注入到 `planServiceImpl` 中。
```go
func NewPlanService(
executionManager ExecutionManager,
taskManager AnalysisPlanTaskManager,
planRepo repository.PlanRepository, // 新增
// deviceRepo repository.DeviceRepository, // 如果需要,新增
logger *logs.Logger,
) Service {
return &planServiceImpl{
executionManager: executionManager,
taskManager: taskManager,
planRepo: planRepo, // 注入
// deviceRepo: deviceRepo, // 注入
logger: logger,
}
}
```
### 第二步:修改 `app/service/plan_service.go` (应用服务层)
1. **修改 `planService` 结构体**:
* 移除 `planRepo repository.PlanRepository` 字段。
* 将 `analysisPlanTaskManager plan.AnalysisPlanTaskManager` 字段替换为 `domainPlanService plan.Service`。
```go
type planService struct {
logger *logs.Logger
// planRepo repository.PlanRepository // 移除
domainPlanService plan.Service // 替换为领域层的服务接口
// analysisPlanTaskManager plan.AnalysisPlanTaskManager // 移除,由 domainPlanService 内部持有
}
```
2. **修改 `NewPlanService` 函数**:
* 接收 `domainPlanService plan.Service` 作为参数。
* 将 `planRepo` 和 `analysisPlanTaskManager` 的注入替换为 `domainPlanService`。
```go
func NewPlanService(
logger *logs.Logger,
// planRepo repository.PlanRepository, // 移除
domainPlanService plan.Service, // 接收领域层服务
// analysisPlanTaskManager plan.AnalysisPlanTaskManager, // 移除
) PlanService {
return &planService{
logger: logger,
domainPlanService: domainPlanService, // 注入领域层服务
}
}
```
3. **修改 `PlanService` 接口**:
* 接口定义保持不变,仍然接收和返回 DTO。
4. **修改 `planService` 接口实现**:
* **`CreatePlan`**:
* 接收 `dto.CreatePlanRequest`。
* 使用 `dto.NewPlanFromCreateRequest` 将 DTO 转换为 `*models.Plan`。**注意:此时 `NewPlanFromCreateRequest` 不再包含 `ValidateExecutionOrder()` 和 `ReorderSteps()` 的调用。**
* 调用 `s.domainPlanService.CreatePlan(*models.Plan)`。
* 将返回的 `*models.Plan` 转换为 `dto.PlanResponse`。
* **`GetPlanByID`**:
* 调用 `s.domainPlanService.GetPlanByID(id)`。
* 将返回的 `*models.Plan` 转换为 `dto.PlanResponse`。
* **`ListPlans`**:
* 将 `dto.ListPlansQuery` 转换为 `repository.ListPlansOptions`。
* 调用 `s.domainPlanService.ListPlans(...)`。
* 将返回的 `[]models.Plan` 转换为 `[]dto.PlanResponse`。
* **`UpdatePlan`**:
* 使用 `dto.NewPlanFromUpdateRequest` 将 `dto.UpdatePlanRequest` 转换为 `*models.Plan`。**注意:此时 `NewPlanFromUpdateRequest` 不再包含 `ValidateExecutionOrder()` 和 `ReorderSteps()` 的调用。**
* 设置 `plan.ID = id`。
* 调用 `s.domainPlanService.UpdatePlan(*models.Plan)`。
* 将返回的 `*models.Plan` 转换为 `dto.PlanResponse`。
* **`DeletePlan`**:
* 调用 `s.domainPlanService.DeletePlan(id)`。
* **`StartPlan`**:
* 调用 `s.domainPlanService.StartPlan(id)`。
* **`StopPlan`**:
* 调用 `s.domainPlanService.StopPlan(id)`。
* **错误处理**: 应用服务层将捕获领域层返回的错误,并可能将其转换为更适合应用服务层或表示层的错误信息(例如,将领域层的 `ErrPlanNotFound` 转换为 `app/service` 层定义的 `ErrPlanNotFound`)。
### 第三步:修改 `internal/app/dto/plan_converter.go`
1. **移除 `NewPlanFromCreateRequest` 和 `NewPlanFromUpdateRequest` 中的领域逻辑**:
* 从 `NewPlanFromCreateRequest` 和 `NewPlanFromUpdateRequest` 函数中移除 `plan.ValidateExecutionOrder()` 和 `plan.ReorderSteps()` 的调用。这些逻辑应该由领域服务来处理。
### 第四步:更新 `main.go` 或其他依赖注入点
* 调整 `NewPlanService` 的调用,确保正确注入 `domain/plan/Service` 的实现。
## 风险与注意事项:
* **事务管理**: 如果领域层的方法需要事务,确保事务在领域层内部或由应用服务层协调。
* **错误映射**: 仔细处理领域层错误到应用服务层错误的映射,确保对外暴露的错误信息是恰当的。
* **循环依赖**: 确保 `domain` 层不依赖 `app` 层,`app` 层可以依赖 `domain` 层。
* **测试**: 重构后需要对所有相关功能进行全面的单元测试和集成测试。

View File

@@ -0,0 +1,196 @@
# 重构方案:将删除前关联检查逻辑迁移至 Service 层
## 1. 目标
将删除**区域主控 (AreaController)** 和**设备模板 (DeviceTemplate)** 时的关联设备检查逻辑,从 Repository数据访问层重构至 Service业务逻辑层。
## 2. 动机
当前实现中,关联检查逻辑位于 Repository 层的 `Delete` 方法内。这违反了分层架构的最佳实践。Repository 层应仅负责单纯的数据持久化操作(增删改查),而不应包含业务规则。
通过本次重构,我们将实现:
- **职责分离**: Service 层负责编排业务逻辑如“删除前必须检查关联”Repository 层回归其数据访问的单一职责。
- **代码清晰**: 业务流程在 Service 层一目了然,便于理解和维护。
- **可测试性增强**: 可以独立测试 Service 层的业务规则,而无需依赖数据库的事务或约束。
## 3. 详细实施步骤
### 第 1 步:在 Service 层定义业务错误
`internal/app/service/device_service.go` 文件中,导出两个新的错误变量,用于清晰地表达业务约束。
```go
// ErrAreaControllerInUse 表示区域主控正在被设备使用,无法删除
var ErrAreaControllerInUse = errors.New("区域主控正在被一个或多个设备使用,无法删除")
// ErrDeviceTemplateInUse 表示设备模板正在被设备使用,无法删除
var ErrDeviceTemplateInUse = errors.New("设备模板正在被一个或多个设备使用,无法删除")
```
### 第 2 步:调整 Repository 接口与实现
#### 2.1 `device_repository.go`
`DeviceRepository` 接口中增加一个方法,用于检查区域主控是否被使用,并在 `gormDeviceRepository` 中实现它。
```go
// internal/infra/repository/device_repository.go
type DeviceRepository interface {
// ... 其他方法
IsAreaControllerInUse(areaControllerID uint) (bool, error)
}
func (r *gormDeviceRepository) IsAreaControllerInUse(areaControllerID uint) (bool, error) {
var count int64
if err := r.db.Model(&models.Device{}).Where("area_controller_id = ?", areaControllerID).Count(&count).Error; err != nil {
return false, fmt.Errorf("检查区域主控使用情况失败: %w", err)
}
return count > 0, nil
}
```
#### 2.2 `area_controller_repository.go`
简化 `Delete` 方法,移除所有业务逻辑,使其成为一个纯粹的数据库删除操作。
```go
// internal/infra/repository/area_controller_repository.go
func (r *gormAreaControllerRepository) Delete(id uint) error {
// 移除原有的事务和关联检查
if err := r.db.Delete(&models.AreaController{}, id).Error; err != nil {
return fmt.Errorf("删除区域主控失败: %w", err)
}
return nil
}
```
#### 2.3 `device_template_repository.go`
同样,简化 `Delete` 方法。`IsInUse` 方法保持不变,因为它仍然是一个有用的查询。
```go
// internal/infra/repository/device_template_repository.go
func (r *gormDeviceTemplateRepository) Delete(id uint) error {
// 移除原有的关联检查逻辑
if err := r.db.Delete(&models.DeviceTemplate{}, id).Error; err != nil {
return fmt.Errorf("删除设备模板失败: %w", err)
}
return nil
}
```
### 第 3 步:在 Service 层实现业务逻辑
#### 3.1 `device_service.go` - 删除区域主控
修改 `DeleteAreaController` 方法,加入关联检查的业务逻辑。
```go
// internal/app/service/device_service.go
func (s *deviceService) DeleteAreaController(id string) error {
idUint, err := strconv.ParseUint(id, 10, 64)
if err != nil {
return fmt.Errorf("无效的ID格式: %w", err)
}
acID := uint(idUint)
// 1. 检查是否存在
_, err = s.areaControllerRepo.FindByID(acID)
if err != nil {
return err // 如果未找到gorm会返回 ErrRecordNotFound
}
// 2. 检查是否被使用(业务逻辑)
inUse, err := s.deviceRepo.IsAreaControllerInUse(acID)
if err != nil {
return err // 返回数据库检查错误
}
if inUse {
return ErrAreaControllerInUse // 返回业务错误
}
// 3. 执行删除
return s.areaControllerRepo.Delete(acID)
}
```
#### 3.2 `device_service.go` - 删除设备模板
修改 `DeleteDeviceTemplate` 方法,加入关联检查的业务逻辑。
```go
// internal/app/service/device_service.go
func (s *deviceService) DeleteDeviceTemplate(id string) error {
idUint, err := strconv.ParseUint(id, 10, 64)
if err != nil {
return fmt.Errorf("无效的ID格式: %w", err)
}
dtID := uint(idUint)
// 1. 检查是否存在
_, err = s.deviceTemplateRepo.FindByID(dtID)
if err != nil {
return err
}
// 2. 检查是否被使用(业务逻辑)
inUse, err := s.deviceTemplateRepo.IsInUse(dtID)
if err != nil {
return err
}
if inUse {
return ErrDeviceTemplateInUse // 返回业务错误
}
// 3. 执行删除
return s.deviceTemplateRepo.Delete(dtID)
}
```
### 第 4 步:在 Controller 层处理新的业务错误
#### 4.1 `device_controller.go` - 删除区域主控
`DeleteAreaController` 的错误处理中,增加对 `ErrAreaControllerInUse` 的捕获,并返回 `409 Conflict` 状态码。
```go
// internal/app/controller/device/device_controller.go
func (c *Controller) DeleteAreaController(ctx echo.Context) error {
// ...
if err := c.deviceService.DeleteAreaController(acID); err != nil {
switch {
case errors.Is(err, gorm.ErrRecordNotFound):
// ...
case errors.Is(err, service.ErrAreaControllerInUse): // 新增
c.logger.Warnf("%s: 尝试删除正在被使用的主控, ID: %s", actionType, acID)
return controller.SendErrorWithAudit(ctx, controller.CodeConflict, err.Error(), actionType, "主控正在被使用", acID)
default:
// ...
}
}
// ...
}
```
#### 4.2 `device_controller.go` - 删除设备模板
`DeleteDeviceTemplate` 的错误处理中,增加对 `ErrDeviceTemplateInUse` 的捕获,并返回 `409 Conflict` 状态码。
```go
// internal/app/controller/device/device_controller.go
func (c *Controller) DeleteDeviceTemplate(ctx echo.Context) error {
// ...
if err := c.deviceService.DeleteDeviceTemplate(dtID); err != nil {
switch {
case errors.Is(err, gorm.ErrRecordNotFound):
// ...
case errors.Is(err, service.ErrDeviceTemplateInUse): // 新增
c.logger.Warnf("%s: 尝试删除正在被使用的模板, ID: %s", actionType, dtID)
return controller.SendErrorWithAudit(ctx, controller.CodeConflict, err.Error(), actionType, "模板正在被使用", dtID)
default:
// ...
}
}
// ...
}
```

View File

@@ -0,0 +1,106 @@
# 重构方案:将 ID 类型转换逻辑迁移至 Controller 层
## 1. 目标
将所有通过 URL 路径传入的 `id``string` 类型),其到 `uint` 类型的转换和验证逻辑,从 Service业务逻辑层统一迁移至 Controller控制器层。
## 2. 动机
当前实现中Controller 将从 URL 获取的 `string` 类型的 ID 直接传递给 Service 层,由 Service 层负责使用 `strconv.ParseUint` 进行类型转换。
这种模式存在以下问题:
- **职责不清**Service 层被迫处理了本应属于输入验证和转换的逻辑,而这部分工作更贴近 Controller 的职责。
- **Service 不纯粹**:业务核心逻辑与原始输入(字符串)耦合,降低了 Service 的可复用性。理想情况下Service 的接口应该只处理内部定义的、类型安全的数据。
- **延迟的错误处理**:对于一个无效的 ID如 "abc"),请求会穿透到 Service 层才会失败,而这种格式错误在 Controller 层就应该被拦截。
通过本次重构,我们将实现:
- **职责分离**Controller 负责处理 HTTP 请求的原始数据验证、转换Service 负责处理核心业务。
- **接口清晰**Service 层的所有方法将只接受类型安全的 `uint` 作为 ID使其意图更加明确。
- **快速失败**:无效的 ID 将在 Controller 层被立即拒绝,并返回 `400 Bad Request`,提高了系统的健壮性。
## 3. 详细实施步骤
### 第 1 步:修改 `device_service.go`
#### 3.1 修改 `DeviceService` 接口
所有接收 `id string` 参数的方法签名,全部修改为接收 `id uint`
**受影响的方法列表:**
- `GetDevice(id string)` -> `GetDevice(id uint)`
- `UpdateDevice(id string, ...)` -> `UpdateDevice(id uint, ...)`
- `DeleteDevice(id string)` -> `DeleteDevice(id uint)`
- `ManualControl(id string, ...)` -> `ManualControl(id uint, ...)`
- `GetAreaController(id string)` -> `GetAreaController(id uint)`
- `UpdateAreaController(id string, ...)` -> `UpdateAreaController(id uint, ...)`
- `DeleteAreaController(id string)` -> `DeleteAreaController(id uint)`
- `GetDeviceTemplate(id string)` -> `GetDeviceTemplate(id uint)`
- `UpdateDeviceTemplate(id string, ...)` -> `UpdateDeviceTemplate(id uint, ...)`
- `DeleteDeviceTemplate(id string)` -> `DeleteDeviceTemplate(id uint)`
#### 3.2 修改 `deviceService` 实现
`deviceService` 的方法实现中,移除所有 `strconv.ParseUint` 的调用,直接使用传入的 `uint` 类型的 ID。
**示例 (`DeleteDeviceTemplate`):**
**修改前:**
```go
func (s *deviceService) DeleteDeviceTemplate(id string) error {
idUint, err := strconv.ParseUint(id, 10, 64)
if err != nil {
return fmt.Errorf("无效的ID格式: %w", err)
}
dtID := uint(idUint)
// ... 业务逻辑
}
```
**修改后:**
```go
func (s *deviceService) DeleteDeviceTemplate(id uint) error {
// 直接使用 id
// ... 业务逻辑
}
```
### 第 2 步:修改 `device_controller.go`
在所有调用受影响 Service 方法的 Controller 方法中,增加 ID 的转换和错误处理逻辑。
**示例 (`DeleteDeviceTemplate`):**
**修改前:**
```go
func (c *Controller) DeleteDeviceTemplate(ctx echo.Context) error {
const actionType = "删除设备模板"
dtID := ctx.Param("id") // dtID is a string
if err := c.deviceService.DeleteDeviceTemplate(dtID); err != nil {
// ... 错误处理
}
// ... 成功处理
}
```
**修改后:**
```go
func (c *Controller) DeleteDeviceTemplate(ctx echo.Context) error {
const actionType = "删除设备模板"
idStr := ctx.Param("id")
// 在 Controller 层进行转换和验证
idUint, err := strconv.ParseUint(idStr, 10, 64)
if err != nil {
c.logger.Warnf("%s: 无效的ID格式: %s", actionType, idStr)
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的ID格式", actionType, "ID格式错误", idStr)
}
dtID := uint(idUint)
// 调用 Service传入 uint 类型的 ID
if err := c.deviceService.DeleteDeviceTemplate(dtID); err != nil {
// ... 错误处理 (保持不变)
}
// ... 成功处理
}
```
此模式将应用于所有受影响的 Controller 方法。

View File

@@ -0,0 +1,89 @@
# 实现方案:纯 Context 驱动的调用链追踪
本方案旨在提供一个绝对安全、符合 Go 语言习惯且对业务代码侵入性最小的调用链追踪方案。其核心思想是:**调用链信息完全由标准的 `context.Context` 承载,并通过 `logs` 包提供的一系列无状态包级函数进行原子化、安全的操作。**
### 1. 核心设计原则
- **`Context` 是唯一载体**: 所有的调用链信息,包括组件名(对象名)和函数名(方法名),都只存储在标准的 `context.Context` 中。我们不再定义任何自定义的 `Context` 接口,以保证最大的兼容性。
- **纯粹的包级函数**: `logs` 包将提供一系列纯粹的、无状态的包级函数,作为与 `Context` 交互的唯一 API。这些函数负责向 `Context` 中添加信息,或从 `Context` 中生成 `Logger`
- **无状态的 `Logger`**: `Logger` 对象本身不再携带任何调用链信息。它在被 `logs.GetLogger(ctx)` 生成时,才被一次性地赋予包含完整调用链的配置。
### 2. 实现细节
#### a. Context 中存储的数据
`context.Context` 将通过 `context.WithValue` 在幕后存储两种核心信息,这两种信息都使用 `logs` 包内部的私有 `key` 类型,以避免与其他包的键冲突。
- **组件名 (`compNameKey`)**: 用于存储一个字符串,表示当前上下文环境属于哪个组件(例如 `\"组件1\"`)。
- **调用链 (`chainKey`)**: 用于存储一个字符串切片 (`[]string`),记录了从请求开始到当前位置的完整调用路径(例如 `[\"组件2.Create\", \"组件1.Create\"]`)。
#### b. `logs` 包提供的核心 API
`logs` 包需要对外提供以下四个核心的包级函数,以提供不同粒度的灵活性:
1. **`logs.AddCompName(ctx, compName) context.Context`**
- **职责**: 将一个组件名(对象名)存入 `Context`,并返回一个包含该信息的新 `Context`。这通常在依赖注入时完成,用于创建组件的“身份名牌” `selfCtx`
- **实现细节**: 该函数接收一个 `context.Context` 和一个 `compName` 字符串。它内部使用 `context.WithValue`,以私有的 `compNameKey` 为键,将 `compName` 字符串存入 `Context`,然后返回这个全新的 `Context`
2. **`logs.AddFuncName(upstreamCtx, selfCtx, funcName) context.Context`**
- **职责**: 这是构建调用链的核心原子操作。它智能地合并上游的调用链和当前组件的信息,生成并返回一个包含更新后调用链和当前组件名的 **新 `Context`**。此函数用于只需要传递调用链而不需要立即打印日志的场景。
- **实现细节**:
1. 函数接收 `upstreamCtx`, `selfCtx`, `funcName`
2. **获取上游调用链**: 从 `upstreamCtx` 中,通过 `chainKey` 读取出已经存在的调用链(`oldChain []string`)。
3. **获取当前组件名**: 从 `selfCtx` 中,通过 `compNameKey` 读取出当前组件的名称(`compName string`)。
4. **构建新节点**: 将 `compName``funcName` 拼接成一个新节点(例如 `\"组件2.Create\"`)。
5. **生成新调用链**: 将这个新节点追加到 `oldChain` 的末尾,形成 `newChain []string`
6. **创建新的 Context**:
- 使用 `context.WithValue`,以 `chainKey` 为键,将 `newChain` 存入一个新的 `Context`,我们称之为 `tmpCtx`
- 接着,**(关键修正)** 基于 `tmpCtx`,再次调用 `context.WithValue`,以 `compNameKey` 为键,将从 `selfCtx` 中获取的 `compName` 存入,得到最终的 `newCtx`。这确保了传向下游的 `Context` 正确地标识了当前组件。
7. **返回**: 返回 `newCtx`
3. **`logs.GetLogger(ctx) *Logger`**
- **职责**: 从 `Context` 中生成最终的、可用于打印的 `Logger` 实例。
- **实现细节**:
1. 函数接收一个 `context.Context`
2. 它从 `ctx` 中,通过 `chainKey` 读取出完整的调用链 `[]string`
3. 如果调用链不存在或为空,它就生成一个不带 `trace` 字段的普通 `Logger` 实例并返回。
4. 如果调用链存在,它就用 `->` 符号将切片中的所有节点拼接成一个完整的 `trace` 字符串。
5. 最后,它创建一个**一次性**的 `Logger` 实例,将这个 `trace` 字符串和底层的 `zap` 配置传给它,然后返回这个准备就绪的 `Logger`
4. **`logs.Trace(upstreamCtx, selfCtx, funcName) (context.Context, *Logger)`**
- **职责**: 作为 `AddFuncName``GetLogger` 的便捷封装,一步到位地完成调用链构建和 `Logger` 生成。用于需要立即打印日志的场景。
- **实现细节**:
1. 内部调用 `newCtx := logs.AddFuncName(upstreamCtx, selfCtx, funcName)`
2. 内部调用 `logger := logs.GetLogger(newCtx)`
3. 返回 `newCtx``logger`
### 3. 最终使用模式
#### a. 依赖注入阶段
(保持不变)在应用启动和组装依赖时,我们不再注入 `Logger` 对象,而是为每个组件创建一个包含其自身名称的专属 `Context`,并将这个 `Context` 注入到组件实例中。
- **流程**:
1. 在依赖注入的根源处,创建一个全局的、初始的 `context.Background()`
2. 对于需要被追踪的组件(例如 `组件1`),调用 `logs.AddCompName(ctx, \"组件1\")` 来创建一个 `ctxForC1`
3. 在创建 `组件1` 的实例时,将这个 `ctxForC1` 作为其成员变量(例如 `selfCtx`)保存起来。这个 `selfCtx` 就成了 `组件1` 的“身份名牌”。
#### b. 请求处理阶段
开发者可以根据需求灵活选择 API。
- **场景一:需要立即打印日志 (推荐)**:
1. 在方法入口处,立即调用 `ctx, logger := logs.Trace(upstreamCtx, z.selfCtx, \"Create\")`
2. 使用这个 `logger` 打印日志:`logger.Info(\"创建组件2\")`
3. 当需要调用下游方法时,将返回的 **新 `Context` (`ctx`)** 传递下去。
- **场景二:只需要传递调用链**:
1. 在方法入口处,调用 `ctx := logs.AddFuncName(upstreamCtx, z.selfCtx, \"Create\")`
2. 这个方法本身不打印日志,但在调用下游方法时,将这个包含了更新后调用链的 **新 `Context` (`ctx`)** 传递下去。
- **场景三:在方法中间打印日志**:
1. 一个方法可能在执行了一部分逻辑后才需要打印日志。
2. `ctx := logs.AddFuncName(upstreamCtx, z.selfCtx, \"Create\")` // 先在入口更新调用链
3. // ... 执行一些业务逻辑 ...
4. `logger := logs.GetLogger(ctx)` // 在需要时,基于更新后的 ctx 获取 logger
5. `logger.Info(\"业务逻辑执行到一半\")`
6. // ... 继续执行并传递 ctx ...

View File

@@ -0,0 +1,21 @@
# 需求
为日志系统提供一种机制,能够记录和追踪跨越不同对象和方法的调用链,以增强日志的可读性和可观测性。
## issue
http://git.huangwc.com/pig/pig-farm-controller/issues/56
## 描述
### 初始问题
在复杂的业务流程中,一个请求可能会流经多个服务或控制器。为了追踪一个完整的操作流程,开发者不得不在每个方法的日志中手动添加上下文信息(如 `actionType`),这非常繁琐且容易出错。
### 期望的演进
1. **初步设想**: 提供一个 `logger.With()` 方法,调用后返回一个携带预设信息的日志记录器副本,后续日志自动附带此信息。
2. **解决上下文覆盖**: 简单的 `With("action", "some_action")` 会导致同名字段被覆盖。因此,期望能将调用链信息存储在数组中,并在打印时拼接,如 `action: "action1·action2"`
3. **最终目标 (可观测性)**: 进一步区分“对象”和“方法”,构建出更具表现力的调用链。例如,当 `userController``Create` 方法调用 `userService``Create` 方法时,日志应能清晰地展示 `trace: "userController.Create->userService.Create"` 这样的调用关系。
## 实现方案
[实现方案](./implementation.md)

View File

@@ -0,0 +1,9 @@
- **`internal/app/api/api.go` (`API`)**:
- [x] 修改 `NewAPI` 函数,移除 `logger` 参数,改为接收 `ctx context.Context`
- [x] 移除 `API` 结构体中的 `logger` 成员,改为保存 `Ctx context.Context`
- [x] 为 `API` 组件本身创建 `Ctx``Ctx := logs.AddCompName(ctx, 'API')`,并传递给所有 `Controller`
的构造函数。
- [x] 改造 `Start` 方法,从 `a.Ctx` 获取 `logger` 实例进行日志记录。
- [x] 改造 `Stop` 方法,从 `a.Ctx` 获取 `logger` 实例进行日志记录。
- **`internal/app/api/router.go`
- [x] 改造 `setupRoutes` 方法,从 `a.Ctx` 获取 `logger` 实例进行日志记录。

View File

@@ -0,0 +1,113 @@
- **`internal/app/controller/user/user_controller.go` (`user.Controller`)**
- **结构体改造**:
- [x] 移除 `Controller` 结构体中的 `logger *logs.Logger` 成员。
- [x] 新增 `Ctx context.Context` 成员。
- **构造函数改造 (`NewController`)**:
- [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- [x] 将这个 `Ctx` 赋值给 `Controller` 结构体的 `Ctx` 成员。
- **公共方法改造 (`CreateUser`, `Login`, `SendTestNotification`)**:
- [x] 在每个方法入口处,从 `echo.Context` 中提取 `request.Context()` 作为 `upstreamCtx`
- [x] 使用 `newCtx, logger := logs.Trace(upstreamCtx, c.Ctx, "MethodName")` 获取新的 `context.Context`
`logger` 实例。
- [x] 将所有对 `c.logger.Errorf``c.logger.Infof` 的调用替换为 `logger.Errorf``logger.Infof`
- [x] 确保所有对 `c.userService` 的调用都将 `newCtx` 作为第一个参数传递。
- **`internal/app/controller/device/device_controller.go` (`device.Controller`)**
- **结构体改造**:
- [x] 移除 `Controller` 结构体中的 `logger *logs.Logger` 成员。
- [x] 新增 `Ctx context.Context` 成员。
- **构造函数改造 (`NewController`)**:
- [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- [x] 将这个 `Ctx` 赋值给 `Controller` 结构体的 `Ctx` 成员。
- **公共方法改造 (`CreateDevice`, `GetDevice`, `ListDevices`, `UpdateDevice`, `DeleteDevice`, `ManualControl`,
`CreateAreaController`, `GetAreaController`, `ListAreaControllers`, `UpdateAreaController`,
`DeleteAreaController`, `CreateDeviceTemplate`, `GetDeviceTemplate`, `ListDeviceTemplates`,
`UpdateDeviceTemplate`, `DeleteDeviceTemplate`)**:
- [x] 在每个方法入口处,从 `echo.Context` 中提取 `request.Context()` 作为 `upstreamCtx`
- [x] 使用 `newCtx, logger := logs.Trace(upstreamCtx, c.Ctx, "MethodName")` 获取新的 `context.Context`
`logger` 实例。
- [x] 将所有对 `c.logger.Errorf`, `c.logger.Warnf`, `c.logger.Infof` 的调用替换为 `logger.Errorf`,
`logger.Warnf`, `logger.Infof`
- [x] 确保所有对 `c.deviceService` 的调用都将 `newCtx` 作为第一个参数传递。
- **`internal/app/controller/plan/plan_controller.go` (`plan.Controller`)**
- **结构体改造**:
- [x] 移除 `Controller` 结构体中的 `logger *logs.Logger` 成员。
- [x] 新增 `Ctx context.Context` 成员。
- **构造函数改造 (`NewController`)**:
- [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- [x] 将这个 `Ctx` 赋值给 `Controller` 结构体的 `Ctx` 成员。
- **公共方法改造 (`CreatePlan`, `GetPlan`, `ListPlans`, `UpdatePlan`, `DeletePlan`, `StartPlan`, `StopPlan`)**:
- [x] 在每个方法入口处,从 `echo.Context` 中提取 `request.Context()` 作为 `upstreamCtx`
- [x] 使用 `newCtx, logger := logs.Trace(upstreamCtx, c.Ctx, "MethodName")` 获取新的 `context.Context`
`logger` 实例。
- [x] 将所有对 `c.logger.Errorf``c.logger.Infof` 的调用替换为 `logger.Errorf``logger.Infof`
- [x] 确保所有对 `c.planService` 的调用都将 `newCtx` 作为第一个参数传递。
- **`internal/app/controller/management/pig_farm_controller.go` (`management.PigFarmController`)**
- **结构体改造**:
- [x] 移除 `PigFarmController` 结构体中的 `logger *logs.Logger` 成员。
- [x] 新增 `Ctx context.Context` 成员。
- **构造函数改造 (`NewPigFarmController`)**:
- [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- [x] 将这个 `Ctx` 赋值给 `PigFarmController` 结构体的 `Ctx` 成员。
- **公共方法改造 (`CreatePigHouse`, `GetPigHouse`, `ListPigHouses`, `UpdatePigHouse`, `DeletePigHouse`, `CreatePen`,
`GetPen`, `ListPens`, `UpdatePen`, `DeletePen`, `UpdatePenStatus`)**:
- [x] 在每个方法入口处,从 `echo.Context` 中提取 `request.Context()` 作为 `upstreamCtx`
- [x] 使用 `newCtx, logger := logs.Trace(upstreamCtx, c.Ctx, "MethodName")` 获取新的 `context.Context`
`logger` 实例。
- [x] 将所有对 `c.logger.Errorf``c.logger.Infof` 的调用替换为 `logger.Errorf``logger.Infof`
- [x] 确保所有对 `c.service` 的调用都将 `newCtx` 作为第一个参数传递。
- **`internal/app/controller/management/pig_batch_controller.go` (`management.PigBatchController`)**
- **结构体改造**:
- [x] 移除 `PigBatchController` 结构体中的 `logger *logs.Logger` 成员。
- [x] 新增 `Ctx context.Context` 成员。
- **构造函数改造 (`NewPigBatchController`)**:
- [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- [x] 将这个 `Ctx` 赋值给 `PigBatchController` 结构体的 `Ctx` 成员。
- **公共方法改造 (
所有方法,例如 `CreatePigBatch`, `GetPigBatch`, `UpdatePigBatch`, `DeletePigBatch`, `ListPigBatches`,
`AssignEmptyPensToBatch`, `ReclassifyPenToNewBatch`, `RemoveEmptyPenFromBatch`, `MovePigsIntoPen`)**:
- [x] 这些方法通过调用 `controller_helpers.go` 中的 `handleAPIRequest...` 系列函数来处理请求。
- [x] 确保传递给 `handleAPIRequest...` 系列函数的 `serviceExecutor` 匿名函数,其签名与 `controller_helpers.go`
改造后期望的签名一致(即接收 `context.Context` 作为第一个参数)。
- [x] 在 `serviceExecutor` 匿名函数内部,将接收到的 `context.Context` 传递给 `c.service` 的相应方法。
- **`internal/app/controller/management/controller_helpers.go`**
- [x] 修改 `mapAndSendError` 函数中的 `c.logger.Errorf` 调用,改为从 `echo.Context` 中提取 `context.Context`,并使用
`logs.Trace``logs.GetLogger` 获取 `logger` 实例进行日志记录。
- [x] 检查其他函数是否直接或间接依赖 `*PigBatchController``logger` 成员,并进行相应改造。
- **`internal/app/controller/management/pig_batch_trade_controller.go` (`management.PigBatchController`)**
- [x] 修改 `NewController` 函数,移除 `logger` 参数,改为接收 `Ctx context.Context`
- [x] 移除 `Controller` 结构体中的 `logger` 成员,改为保存 `Ctx`
- [x] **所有公共方法**: 接收 `echo.Context` 的方法,需要从中提取 `request.Context()` 作为 `upstreamCtx`
- **`internal/app/controller/management/pig_batch_health_controller.go` (`management.PigBatchController`)**
- [x] 修改 `NewController` 函数,移除 `logger` 参数,改为接收 `Ctx context.Context`
- [x] 移除 `Controller` 结构体中的 `logger` 成员,改为保存 `Ctx`
- [x] **所有公共方法**: 接收 `echo.Context` 的方法,需要从中提取 `request.Context()` 作为 `upstreamCtx`
- **`internal/app/controller/management/pig_batch_transfer_controller.go` (`management.PigBatchController`)**
- [x] 修改 `NewController` 函数,移除 `logger` 参数,改为接收 `Ctx context.Context`
- [x] 移除 `Controller` 结构体中的 `logger` 成员,改为保存 `Ctx`
- [x] **所有公共方法**: 接收 `echo.Context` 的方法,需要从中提取 `request.Context()` 作为 `upstreamCtx`
- **`internal/app/controller/monitor/monitor_controller.go` (`monitor.Controller`)**
- **结构体改造**:
- [x] 移除 `Controller` 结构体中的 `logger *logs.Logger` 成员。
- [x] 新增 `Ctx context.Context` 成员。
- **构造函数改造 (`NewController`)**:
- [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- [x] 将这个 `Ctx` 赋值给 `Controller` 结构体的 `Ctx` 成员。
- **公共方法改造 (
所有方法,例如 `ListSensorData`, `ListDeviceCommandLogs`, `ListPlanExecutionLogs`, `ListTaskExecutionLogs`,
`ListPendingCollections`, `ListUserActionLogs`, `ListRawMaterialPurchases`, `ListRawMaterialStockLogs`,
`ListFeedUsageRecords`, `ListMedicationLogs`, `ListPigBatchLogs`, `ListWeighingBatches`, `ListWeighingRecords`,
`ListPigTransferLogs`, `ListPigSickLogs`, `ListPigPurchases`, `ListPigSales`, `ListNotifications`)**:
- [x] 在每个方法入口处,从 `echo.Context` 中提取 `request.Context()` 作为 `upstreamCtx`
- [x] 使用 `newCtx, logger := logs.Trace(upstreamCtx, c.Ctx, actionType)` 获取新的 `context.Context`
`logger` 实例(`actionType` 为方法内部定义的常量)。
- [x] 将所有对 `c.logger.Errorf`, `c.logger.Warnf`, `c.logger.Infof` 的调用替换为 `logger.Errorf`,
`logger.Warnf`, `logger.Infof`
- [x] 确保所有对 `c.monitorService` 的调用都将 `newCtx` 作为第一个参数传递。
- **`internal/app/controller/response.go`**
- [x] 修改 `SendSuccessWithAudit``SendErrorWithAudit` 函数签名,使其接收 `ctx context.Context` 作为第一个参数。
- [x] 在 `setAuditDetails` 函数中,如果需要,从传入的 `context.Context` 中提取调用链信息,并将其添加到审计信息中。
- **`internal/app/controller/auth_utils.go`**
- [x] 检查 `GetOperatorIDFromContext``GetOperatorFromContext` 函数,确保它们能够正确地从 `echo.Context` 中获取
`request.Context()`,并从中提取用户信息。

View File

@@ -0,0 +1,272 @@
- **`internal/domain/audit/service.go` (`audit.Service`)**
- **结构体改造**:
- [x] 移除 `service` 结构体中的 `logger *logs.Logger` 成员。
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewService`)**:
- [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- [x] 在函数内部,为 `audit.Service` 创建其专属的 `selfCtx``selfCtx := logs.AddCompName(ctx, "AuditService")`
- [x] 将这个 `selfCtx` 赋值给 `service` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`LogAction`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "LogAction")` 获取新的 `context.Context`
`logger` 实例。
- [x] 将所有对 `s.logger.Warnw``s.logger.Errorw` 的调用替换为 `logger.Warnw``logger.Errorw`
- [x] 确保所有对 `s.userActionLogRepository.Create` 的调用都将 `newCtx` 作为第一个参数传递。
- **`internal/domain/device/general_device_service.go` (`device.Service`)**
- **结构体改造**:
- [x] 移除 `GeneralDeviceService` 结构体中的 `logger *logs.Logger` 成员。
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewGeneralDeviceService`)**:
- [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- [x] 在函数内部,为 `GeneralDeviceService` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "GeneralDeviceService")`
- [x] 将这个 `selfCtx` 赋值给 `GeneralDeviceService` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`Switch`, `Collect`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, g.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。
- [x] 将所有对 `g.logger.Errorf`, `g.logger.Infof`, `g.logger.Warnf`, `g.logger.Debugf`, `g.logger.DPanicf`
的调用替换为 `logger.Errorf`, `logger.Infof`, `logger.Warnf`, `logger.Debugf`, `logger.DPanicf`
- [x] 确保所有对 `g.deviceRepo`, `g.deviceCommandLogRepo`, `g.pendingCollectionRepo`, `g.comm` 等依赖的调用都将
`newCtx` 作为第一个参数传递。
- **`internal/domain/notify/notify.go` (`domain_notify.Service` - `failoverService` 实现)**
- **结构体改造**:
- [x] 移除 `failoverService` 结构体中的 `log *logs.Logger` 成员。
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewFailoverService`)**:
- [x] 修改函数签名,移除 `log *logs.Logger` 参数,改为接收 `ctx context.Context`
- [x] 在函数内部,为 `failoverService` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "FailoverService")`
- [x] 将这个 `selfCtx` 赋值给 `failoverService` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`SendBatchAlarm`, `BroadcastAlarm`, `SendTestMessage`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。
- [x] 将所有对 `s.log.Infow`, `s.log.Errorw`, `s.log.Warnw` 的调用替换为 `logger.Infow`, `logger.Errorw`,
`logger.Warnw`
- [x] 确保所有对 `s.userRepo`, `s.primaryNotifier.Send`, `s.notifiers`, `s.notificationRepo` 等依赖的调用都将
`newCtx` 作为第一个参数传递。
- **内部辅助方法改造 (`sendAlarmToUser`, `recordNotificationAttempt`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。
- [x] 将所有对 `s.log.Errorw`, `s.log.Infow`, `s.log.Warnw` 的调用替换为 `logger.Errorw`, `logger.Infow`,
`logger.Warnw`
- [x] 确保所有对 `s.userRepo`, `s.primaryNotifier.Send`, `s.notifiers`, `s.notificationRepo` 等依赖的调用都将
`newCtx` 作为第一个参数传递。
- **`internal/domain/pig/pen_transfer_manager.go` (`pig.PigPenTransferManager`)**
- **结构体改造**:
- [x] 移除 `pigPenTransferManager` 结构体中可能存在的 `logger *logs.Logger` 成员(如果未来添加)。
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewPigPenTransferManager`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `pigPenTransferManager` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "PigPenTransferManager")`
- [x] 将这个 `selfCtx` 赋值给 `pigPenTransferManager` 结构体的 `selfCtx` 成员。
- **公共方法改造 (
所有方法,例如 `LogTransfer`, `GetPenByID`, `GetPensByBatchID`, `UpdatePenFields`, `GetCurrentPigsInPen`,
`GetTotalPigsInPensForBatchTx`, `ReleasePen`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数(在 `tx *gorm.DB` 之前)。
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。
- [x] 将所有对 `s.logger.Errorf``s.logger.Infof` 的调用替换为 `logger.Errorf``logger.Infof`
- [x] 确保所有对 `s.penRepo`, `s.logRepo`, `s.pigBatchRepo` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
- **`internal/domain/pig/pig_trade_manager.go` (`pig.PigTradeManager`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewPigTradeManager`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `pigTradeManager` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "PigTradeManager")`
- [x] 将这个 `selfCtx` 赋值给 `pigTradeManager` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`SellPig`, `BuyPig`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数(在 `tx *gorm.DB` 之前)。
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。
- [x] 确保所有对 `s.tradeRepo` 的调用都将 `newCtx` 作为第一个参数传递。
- **`internal/domain/pig/pig_sick_manager.go` (`pig.SickPigManager`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewSickPigManager`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `sickPigManager` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "SickPigManager")`
- [x] 将这个 `selfCtx` 赋值给 `sickPigManager` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`ProcessSickPigLog`, `GetCurrentSickPigCount`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数(在 `tx *gorm.DB` 之前)。
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。
- [x] 确保所有对 `s.sickLogRepo`, `s.medicationLogRepo` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
- **`internal/domain/pig/pig_batch_service.go` (`pig.PigBatchService`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewPigBatchService`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `pigBatchService` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "PigBatchService")`
- [x] 将这个 `selfCtx` 赋值给 `pigBatchService` 结构体的 `selfCtx` 成员。
- **公共方法改造 (
所有方法,例如 `CreatePigBatch`, `GetPigBatch`, `UpdatePigBatch`, `DeletePigBatch`, `ListPigBatches`,
`AssignEmptyPensToBatch`, `MovePigsIntoPen`, `ReclassifyPenToNewBatch`, `RemoveEmptyPenFromBatch`,
`GetCurrentPigQuantity`, `GetCurrentPigsInPen`, `GetTotalPigsInPensForBatch`, `UpdatePigBatchQuantity`,
`SellPigs`, `BuyPigs`, `TransferPigsAcrossBatches`, `TransferPigsWithinBatch`, `RecordSickPigs`,
`RecordSickPigRecovery`, `RecordSickPigDeath`, `RecordSickPigCull`, `RecordDeath`, `RecordCull`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。
- [x] 确保所有对 `s.pigBatchRepo`, `s.pigBatchLogRepo`, `s.uow`, `s.transferSvc`, `s.tradeSvc`, `s.sickSvc`
等依赖的调用都将 `newCtx` 作为第一个参数传递。
- **`internal/domain/plan/analysis_plan_task_manager.go` (`plan.AnalysisPlanTaskManager`)**
- **结构体改造**:
- [x] 移除 `analysisPlanTaskManagerImpl` 结构体中的 `logger *logs.Logger` 成员。
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewAnalysisPlanTaskManager`)**:
- [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- [x] 在函数内部,为 `analysisPlanTaskManagerImpl` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "AnalysisPlanTaskManager")`
- [x] 将这个 `selfCtx` 赋值给 `analysisPlanTaskManagerImpl` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`Refresh`, `CreateOrUpdateTrigger`, `EnsureAnalysisTaskDefinition`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, m.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。
- [x] 将所有对 `m.logger.Info`, `m.logger.Errorf`, `m.logger.Warnf` 的调用替换为 `logger.Info`,
`logger.Errorf`, `logger.Warnf`
- [x] 确保所有对 `m.planRepo`, `m.pendingTaskRepo`, `m.executionLogRepo` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
- **内部辅助方法改造 (`getRefreshData`, `cleanupInvalidTasks`, `addOrUpdateTriggers`, `createTriggerTask`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, m.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。
- [x] 将所有对 `m.logger.Info`, `m.logger.Errorf`, `m.logger.Warnf` 的调用替换为 `logger.Info`,
`logger.Errorf`, `logger.Warnf`
- [x] 确保所有对 `m.planRepo`, `m.pendingTaskRepo`, `m.executionLogRepo` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
- **`internal/domain/plan/plan_execution_manager.go` (`plan.ExecutionManager`)**
- **结构体改造**:
- [x] 移除 `planExecutionManagerImpl` 结构体中的 `logger *logs.Logger` 成员。
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewPlanExecutionManager`)**:
- [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- [x] 在函数内部,为 `planExecutionManagerImpl` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "PlanExecutionManager")`
- [x] 将这个 `selfCtx` 赋值给 `planExecutionManagerImpl` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`Start`, `Stop`)**:
- [x] 在 `Start` 方法中,使用 `newCtx, logger := logs.Trace(context.Background(), s.selfCtx, "Start")` 获取
`logger` 实例进行日志记录。
- [x] 在 `Stop` 方法中,使用 `newCtx, logger := logs.Trace(context.Background(), s.selfCtx, "Stop")` 获取
`logger` 实例进行日志记录。
- **内部辅助方法改造 (`run`, `claimAndSubmit`, `handleRequeue`, `processTask`, `runTask`, `analysisPlan`,
`updateTaskExecutionLogStatus`, `handlePlanTermination`, `handlePlanCompletion`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数(如果方法内部需要传递上下文)。
- [x] 在每个方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。
- [x] 将所有对 `s.logger.Errorf`, `s.logger.Warnf`, `s.logger.Infof`, `s.logger.DPanicf` 的调用替换为
`logger.Errorf`, `logger.Warnf`, `logger.Infof`, `logger.DPanicf`
- [x] 确保所有对 `s.pendingTaskRepo`, `s.executionLogRepo`, `s.deviceRepo`, `s.sensorDataRepo`, `s.planRepo`,
`s.analysisPlanTaskManager`, `s.taskFactory`, `s.deviceService` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
- **`internal/domain/plan/plan_service.go` (`plan.Service`)**
- **结构体改造**:
- [x] 移除 `planServiceImpl` 结构体中的 `logger *logs.Logger` 成员。
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewPlanService`)**:
- [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- [x] 在函数内部,为 `planServiceImpl` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "PlanService")`
- [x] 将这个 `selfCtx` 赋值给 `planServiceImpl` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`Start`, `Stop`, `RefreshPlanTriggers`, `CreatePlan`, `GetPlanByID`, `ListPlans`, `UpdatePlan`,
`DeletePlan`, `StartPlan`, `StopPlan`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。
- [x] 将所有对 `s.logger.Errorf`, `s.logger.Infof`, `s.logger.Warnf` 的调用替换为 `logger.Errorf`,
`logger.Infof`, `logger.Warnf`
- [x] 确保所有对 `s.executionManager`, `s.taskManager`, `s.planRepo`, `s.deviceRepo`, `s.unitOfWork`,
`s.taskFactory` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
- **`internal/domain/task/task.go` (`task.TaskFactory`)**
- **结构体改造**:
- [x] 移除 `taskFactory` 结构体中的 `logger *logs.Logger` 成员。
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewTaskFactory`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `taskFactory` 创建其专属的 `selfCtx``selfCtx := logs.AddCompName(ctx, "TaskFactory")`
- [x] 将这个 `selfCtx` 赋值给 `taskFactory` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`Production`, `CreateTaskFromModel`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法内部,使用 `newCtx, logger := logs.Trace(ctx, t.selfCtx, "MethodName")` 获取新的 `context.Context`
`logger` 实例。
- [x] 将所有对 `t.logger.Panicf` 的调用替换为 `logger.Panicf`
- [x] 将所有对 `NewDelayTask`, `NewReleaseFeedWeightTask`, `NewFullCollectionTask` 的调用,传递 `newCtx`
- **`internal/domain/task/release_feed_weight_task.go`**
- **结构体改造**:
- [x] 移除 `ReleaseFeedWeightTask` 结构体中的 `logger *logs.Logger` 成员。
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewReleaseFeedWeightTask`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `ReleaseFeedWeightTask` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "ReleaseFeedWeightTask")`
- [x] 将这个 `selfCtx` 赋值给 `ReleaseFeedWeightTask` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`Execute`, `OnFailure`, `ResolveDeviceIDs`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, r.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。
- [x] 将所有对 `r.logger.Infof`, `r.logger.Errorf`, `r.logger.Warnf`, `r.logger.Debugf` 的调用替换为
`logger.Infof`, `logger.Errorf`, `logger.Warnf`, `logger.Debugf`
- [x] 确保所有对 `r.deviceRepo`, `r.sensorDataRepo`, `r.feedPort` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
- **内部辅助方法改造 (`getNowWeight`, `parseParameters`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, r.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。
- [x] 将所有对 `r.logger.Errorf`, `r.logger.Warnf` 的调用替换为 `logger.Errorf`, `logger.Warnf`
- [x] 确保所有对 `r.sensorDataRepo`, `r.deviceRepo` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
- **`internal/domain/task/full_collection_task.go`**
- **结构体改造**:
- [x] 移除 `FullCollectionTask` 结构体中的 `logger *logs.Logger` 成员。
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewFullCollectionTask`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `FullCollectionTask` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "FullCollectionTask")`
- [x] 将这个 `selfCtx` 赋值给 `FullCollectionTask` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`Execute`, `OnFailure`, `ResolveDeviceIDs`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, t.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。
- [x] 将所有对 `t.logger.Infow`, `t.logger.Errorw` 的调用替换为 `logger.Infow`, `logger.Errorw`
- [x] 确保所有对 `t.deviceRepo`, `t.deviceService` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
- **`internal/domain/task/delay_task.go`**
- **结构体改造**:
- [x] 移除 `DelayTask` 结构体中的 `logger *logs.Logger` 成员。
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewDelayTask`)**:
- [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- [x] 在函数内部,为 `DelayTask` 创建其专属的 `selfCtx``selfCtx := logs.AddCompName(ctx, "DelayTask")`
- [x] 将这个 `selfCtx` 赋值给 `DelayTask` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`Execute`, `OnFailure`, `ResolveDeviceIDs`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, d.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。
- [x] 将所有对 `d.logger.Infof``d.logger.Errorf` 的调用替换为 `logger.Infof``logger.Errorf`
- **内部辅助方法改造 (`parseParameters`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, d.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。
- [x] 将所有对 `d.logger.Errorf` 的调用替换为 `logger.Errorf`
- **`internal/domain/token/token_service.go` (`token.Service`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewTokenService`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `tokenService` 创建其专属的 `selfCtx``selfCtx := logs.AddCompName(ctx, "TokenService")`
- [x] 将这个 `selfCtx` 赋值给 `tokenService` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`GenerateToken`, `ParseToken`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。
- [x] 将所有对 `s.logger.*` 的调用替换为 `logger.*` (如果存在)。

View File

@@ -0,0 +1,163 @@
- **`internal/infra/database/storage.go` (`database.Storage`)**
- **工厂函数改造 (`NewStorage`)**:
- [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- [x] 在函数内部,将接收到的 `ctx` 作为第一个参数传递给 `NewPostgresStorage` 函数。
- **`internal/infra/database/postgres.go`**
- **结构体改造**:
- [x] 移除 `PostgresStorage` 结构体中的 `logger *logs.Logger` 成员。
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewPostgresStorage`)**:
- [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- [x] 在函数内部,为 `PostgresStorage` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "PostgresStorage")`
- [x] 将这个 `selfCtx` 赋值给 `PostgresStorage` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`Connect`, `Disconnect`, `Migrate`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, ps.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。
- [x] 将所有对 `ps.logger.Info`, `ps.logger.Errorw`, `ps.logger.Debugw` 的调用替换为 `logger.Info`,
`logger.Errorw`, `logger.Debugw`
- [x] 在 `Connect` 方法中,调用 `logs.NewGormLogger` 时,将 `newCtx` 传递给它。
- [x] 在 `Connect` 方法中,调用 `gorm.Open` 时,使用
`ps.db, err = gorm.Open(postgres.Open(ps.connectionString), &gorm.Config{Logger: logs.NewGormLogger(newCtx)})`
- [x] 在 `Migrate` 方法中,确保所有对 `ps.db.AutoMigrate``ps.db.Exec` 的调用都使用 `newCtx`
- **内部辅助方法改造 (`setupTimescaleDB`, `creatingHyperTable`, `applyCompressionPolicies`, `creatingIndex`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在每个方法入口处,使用 `newCtx, logger := logs.Trace(ctx, ps.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。
- [x] 将所有对 `ps.logger.Info`, `ps.logger.Errorw`, `ps.logger.Debugw`, `ps.logger.Warnw`, `ps.logger.Debug`
的调用替换为 `logger.Info`, `logger.Errorw`, `logger.Debugw`, `logger.Warnw`, `logger.Debug`
- [x] 确保所有对 `ps.db.Exec` 的调用都使用 `newCtx`
- **`internal/infra/notify/log_notifier.go` (`notify.LogNotifier`)**
- **结构体改造**:
- [x] 移除 `logNotifier` 结构体中的 `logger *logs.Logger` 成员。
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewLogNotifier`)**:
- [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- [x] 在函数内部,为 `logNotifier` 创建其专属的 `selfCtx``selfCtx := logs.AddCompName(ctx, "LogNotifier")`
- [x] 将这个 `selfCtx` 赋值给 `logNotifier` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`Send`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, l.selfCtx, "Send")` 获取新的 `context.Context`
`logger` 实例。
- [x] 将所有对 `l.logger.Infow` 的调用替换为 `logger.Infow`
- **公共方法 (`Type`)**:
- [x] 此方法不涉及日志或上下文传递,无需改造。
- **`internal/infra/notify/lark.go` (`notify.LarkNotifier`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewLarkNotifier`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `larkNotifier` 创建其专属的 `selfCtx``selfCtx := logs.AddCompName(ctx, "LarkNotifier")`
- [x] 将这个 `selfCtx` 赋值给 `larkNotifier` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`Send`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, l.selfCtx, "Send")` 获取新的 `context.Context`
`logger` 实例。
- [x] 将所有内部的错误日志(例如 `fmt.Errorf` 返回的错误,以及 `getAccessToken` 中的错误)通过 `logger.Errorf`
记录。
- **内部辅助方法改造 (`getAccessToken`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, l.selfCtx, "getAccessToken")` 获取新的
`context.Context``logger` 实例。
- [x] 将所有内部的错误日志通过 `logger.Errorf` 记录。
- **`internal/infra/notify/smtp.go` (`notify.SMTPNotifier`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewSMTPNotifier`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `smtpNotifier` 创建其专属的 `selfCtx``selfCtx := logs.AddCompName(ctx, "SMTPNotifier")`
- [x] 将这个 `selfCtx` 赋值给 `smtpNotifier` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`Send`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "Send")` 获取新的 `context.Context`
`logger` 实例。
- [x] 将所有内部的错误日志(例如 `fmt.Errorf` 返回的错误)通过 `logger.Errorf` 记录。
- **`internal/infra/notify/wechat.go` (`notify.WechatNotifier`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewWechatNotifier`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `wechatNotifier` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "WechatNotifier")`
- [x] 将这个 `selfCtx` 赋值给 `wechatNotifier` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`Send`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, w.selfCtx, "Send")` 获取新的 `context.Context`
`logger` 实例。
- [x] 将所有内部的错误日志(例如 `fmt.Errorf` 返回的错误,以及 `getAccessToken` 中的错误)通过 `logger.Errorf`
记录。
- **内部辅助方法改造 (`getAccessToken`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, w.selfCtx, "getAccessToken")` 获取新的
`context.Context``logger` 实例。
- [x] 将所有内部的错误日志通过 `logger.Errorf` 记录。
- **`internal/infra/transport/lora/chirp_stack.go` (`lora.ChirpStackTransport`)**
- **结构体改造**:
- [x] 移除 `ChirpStackTransport` 结构体中的 `logger *logs.Logger` 成员。
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewChirpStackTransport`)**:
- [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- [x] 在函数内部,为 `ChirpStackTransport` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "ChirpStackTransport")`
- [x] 将这个 `selfCtx` 赋值给 `ChirpStackTransport` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`Send`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, c.selfCtx, "Send")` 获取新的 `context.Context`
`logger` 实例。
- [x] 将所有对 `c.logger.Errorf``c.logger.Infof` 的调用替换为 `logger.Errorf``logger.Infof`
- [x] 在调用 `c.client.DeviceService.DeviceServiceEnqueue` 时,确保将 `newCtx` 传递给
`params.WithContext(newCtx)`,以便 ChirpStack 客户端内部的 HTTP 请求也能携带上下文。
- **`internal/infra/transport/lora/lora_mesh_uart_passthrough_transport.go` (`lora.LoRaMeshUartPassthroughTransport`)**
- **结构体改造**:
- [x] 移除 `LoRaMeshUartPassthroughTransport` 结构体中的 `logger *logs.Logger` 成员。
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewLoRaMeshUartPassthroughTransport`)**:
- [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- [x] 在函数内部,为 `LoRaMeshUartPassthroughTransport` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "LoRaMeshUartPassthroughTransport")`
- [x] 将这个 `selfCtx` 赋值给 `LoRaMeshUartPassthroughTransport` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`Listen`, `Send`, `Stop`)**:
- [x] 修改 `Listen` 方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 修改 `Send` 方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 修改 `Stop` 方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, t.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。
- [x] 将所有对 `t.logger.Info`, `t.logger.Errorf` 的调用替换为 `logger.Info`, `logger.Errorf`
- [x] 在 `Send` 方法中,确保 `t.sendChan <- req` 传递的 `req` 包含了 `newCtx`
- **内部辅助方法改造 (`workerLoop`, `runIdleState`, `runReceivingState`, `executeSend`, `handleFrame`,
`handleUpstreamMessage`, `recordSensorData`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在每个方法入口处,使用 `newCtx, logger := logs.Trace(ctx, t.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。
- [x] 将所有对 `t.logger.Info`, `t.logger.Errorf`, `t.logger.Warnf`, `t.logger.Infof`, `t.logger.Debugf`
的调用替换为 `logger.Info`, `logger.Errorf`, `logger.Warnf`, `logger.Infof`, `logger.Debugf`
- [x] 确保所有对 `t.port`, `t.areaControllerRepo`, `t.pendingCollectionRepo`, `t.deviceRepo`,
`t.sensorDataRepo` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
- **`internal/infra/transport/lora/placeholder_transport.go` (`lora.PlaceholderTransport`)**
- **结构体改造**:
- [x] 移除 `PlaceholderTransport` 结构体中的 `logger *logs.Logger` 成员。
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewPlaceholderTransport`)**:
- [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- [x] 在函数内部,为 `PlaceholderTransport` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "PlaceholderTransport")`
- [x] 将这个 `selfCtx` 赋值给 `PlaceholderTransport` 结构体的 `selfCtx` 成员。
- [x] 使用 `newCtx, logger := logs.Trace(ctx, selfCtx, "NewPlaceholderTransport")` 获取 `logger` 实例,并替换
`logger.Info` 调用。
- **公共方法改造 (`Listen`, `Stop`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, p.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。
- [x] 将所有对 `p.logger.Warnf` 的调用替换为 `logger.Warnf`

View File

@@ -0,0 +1,82 @@
### 重构任务清单:纯 Context 驱动的调用链追踪
---
#### 1. 核心改造 (`logs` 包)
- **`internal/infra/logs/logs.go` (`logs.Logger` - 内部实现,非注入)**
- **核心包级函数实现 (根据 `implementation.md` 描述)**:
- [x] 实现 `AddCompName(ctx context.Context, compName string) context.Context`
- [x] 实现
`AddFuncName(upstreamCtx context.Context, selfCtx context.Context, funcName string) context.Context`
- [x] 实现 `GetLogger(ctx context.Context) *Logger`
- [x] 实现
`Trace(upstreamCtx context.Context, selfCtx context.Context, funcName string) (context.Context, *Logger)`
- **`Logger` 结构体改造**:
- [x] 无
- **`GormLogger` 改造**:
- [x] 修改 `GormLogger.Info` 方法,从传入的 `ctx` 中获取 `logger` 实例,并使用该实例进行日志记录。
- [x] 修改 `GormLogger.Warn` 方法,从传入的 `ctx` 中获取 `logger` 实例,并使用该实例进行日志记录。
- [x] 修改 `GormLogger.Error` 方法,从传入的 `ctx` 中获取 `logger` 实例,并使用该实例进行日志记录。
- [x] 修改 `GormLogger.Trace` 方法,从传入的 `ctx` 中获取 `logger` 实例,并使用该实例进行日志记录。特别是
`With(fields...)` 的调用需要调整。
---
#### 2. 依赖注入与结构体改造
- **`internal/core/application.go`**:
- [x] 移除 `Application` 结构体中的 `Logger *logs.Logger` 成员。
- [x] 修改 `NewApplication` 函数,使其不再创建 `logger`,而是创建根 `context.Background()`
- [x] 调整 `NewApplication`,将根 `context` 传递给 `initInfrastructure`, `initDomainServices`, `initAppServices`
`api.NewAPI`
- [x] 移除 `Application` 结构体中所有对 `app.Logger` 的直接调用,改为通过 `context` 获取 `Logger`
- **`internal/core/component_initializers.go`**:
- [x] **修改所有组件结构体定义**: 遍历所有相关组件Controllers, Services, Repositories 等),将其结构体中的
`logger *logs.Logger` 成员变量替换为 `selfCtx context.Context`
- [x] **重构所有 `init...` 函数**:
- 移除所有 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- 在每个 `init...` 函数内部,为即将创建的组件生成其专属的 `selfCtx`。例如:
`selfCtx := logs.AddCompName(ctx, 'ComponentName')`
- 将这个 `selfCtx` 注入到组件的构造函数中,并由组件保存为 `selfCtx` 成员。
---
#### 3. 全局方法签名改造:传递 `context.Context`
**以下所有列出的组件,其所有公共方法都需要进行签名改造,将 `ctx context.Context` 作为第一个参数。**
##### 3.1. API 层
[api](./task-api.md)
[controller](./task-controller.md)
[middleware](./task-middleware.md)
[webhook](./task-webhook.md)
##### 3.2. 应用层 (Application Services)
[service](./task-service.md)
##### 3.3. 领域层 (Domain Services)
[domain](./task-domain.md)
##### 3.4. 基础设施层 (Infrastructure)
[repository](./task-repository.md)
[infra-other](./task-infra.md)
---
#### 4. 日志调用点及方法内部逻辑改造
- [x] **遍历所有业务方法** (针对上述所有列出的组件的公共方法):
- [x] **定位旧日志**: 搜索所有对旧 `z.logger.*` 成员的调用。
- [x] **改造方法入口** (对于非 Controller 方法):
1. 在方法开始处,使用作为参数传入的 `ctx` (作为 `upstreamCtx`) 和组件自身持有的 `z.selfCtx`,调用 `logs.Trace`
- `newCtx, logger := logs.Trace(ctx, z.selfCtx, 'MethodName')`
2. 将所有旧的 `z.logger.*(...)` 调用,替换为使用新获取的 `logger.*(...)`
- [x] **改造下游调用**:
1. 在方法内部,当需要调用其他组件的方法时(如下游服务),**必须传递 `newCtx`**。
- `err := z.downstreamService.DoSomething(newCtx, data)`

View File

@@ -0,0 +1,25 @@
- **`internal/app/middleware/auth.go`**
- **中间件函数改造 (`AuthMiddleware`)**:
- [x] 在 `AuthMiddleware` 返回的 `echo.HandlerFunc` 内部,获取 `echo.Context``request.Context()` 作为
`upstreamCtx`
- [x] 使用 `newCtx, logger := logs.Trace(upstreamCtx, context.Background(), "AuthMiddleware")` 来创建 `newCtx`
`logger` 实例。
- [x] 使用 `c.SetRequest(c.Request().WithContext(newCtx))` 将更新后的 `newCtx` 写入 `echo.Context`,以便后续处理链使用。
- [x] 将所有对 `controller.SendErrorWithStatus` 的调用替换为 `controller.SendErrorWithAudit`
- [x] 确保 `controller.SendErrorWithAudit` 接收 `newCtx` 作为第一个参数,并提供适当的 `actionType`,
`description`, `targetResource`
- 例如,对于“请求未包含授权标头”的错误,`actionType` 可以是“认证失败”,`description` 是“请求未包含授权标头”,
`targetResource``nil`
- 对于“无效的Token”错误`actionType` 可以是“认证失败”,`description` 是“无效的Token”`targetResource`
`tokenString`
- [x] 在 `AuthMiddleware` 内部,如果需要日志记录(例如 `tokenService.ParseToken` 失败或 `userRepo.FindByID`
失败),使用新创建的 `logger` 实例进行日志输出。
- [x] **关键**: 使用 `c.SetRequest(c.Request().WithContext(newCtx))` 将更新后的 `Context` 写回 `echo.Context`
,以便传递给后续的中间件和 `Controller`
- **`internal/app/middleware/audit.go`**
- **改造动作**:
- [x] 检查并重构所有日志记录中间件。
- [x] 中间件应该从请求的 `c.Request().Context()` 中提取 `upstreamCtx`
- [x] 使用 `logs.Trace``logs.AddFuncName` 创建新的 `Context``Logger`
- [x] **关键**: 使用 `c.SetRequest(c.Request().WithContext(newCtx))` 将更新后的 `Context` 写回 `echo.Context`
,以便传递给后续的中间件和 `Controller`

View File

@@ -0,0 +1,396 @@
- **`internal/infra/repository/unit_of_work.go` (`repository.UnitOfWork` - `gormUnitOfWork` 实现)**
- **接口改造 (`UnitOfWork`)**:
- [x] 修改 `UnitOfWork` 接口中的 `ExecuteInTransaction` 方法签名,使其接收 `ctx context.Context` 作为第一个参数:
`ExecuteInTransaction(ctx context.Context, fn func(tx *gorm.DB) error) error`
- **结构体改造 (`gormUnitOfWork`)**:
- [x] 移除 `gormUnitOfWork` 结构体中的 `logger *logs.Logger` 成员。
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewGormUnitOfWork`)**:
- [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- [x] 在函数内部,为 `gormUnitOfWork` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "GormUnitOfWork")`
- [x] 将这个 `selfCtx` 赋值给 `gormUnitOfWork` 结构体的 `selfCtx` 成员。
- **方法改造 (`ExecuteInTransaction`)**:
- [x] 修改方法签名,使其接收 `ctx context.Context` 作为第一个参数:
`(u *gormUnitOfWork) ExecuteInTransaction(ctx context.Context, fn func(tx *gorm.DB) error) error`
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, u.selfCtx, "ExecuteInTransaction")` 获取新的
`context.Context``logger` 实例。
- [x] 将所有对 `u.logger.Errorf` 的调用替换为 `logger.Errorf`
- [x] 在开启事务时,使用 `tx := u.db.WithContext(newCtx).Begin()`,确保事务 `tx` 携带了正确的上下文。
- **`internal/infra/repository/plan_repository.go` (`repository.PlanRepository`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewGormPlanRepository`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `gormPlanRepository` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "PlanRepository")`
- [x] 将这个 `selfCtx` 赋值给 `gormPlanRepository` 结构体的 `selfCtx` 成员。
- **公共方法改造 (所有公共方法)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`
- [x] 确保所有对 `r.db``tx` 的调用都使用 `r.db.WithContext(newCtx)``tx.WithContext(newCtx)`
- **`internal/infra/repository/user_repository.go` (`repository.UserRepository`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewGormUserRepository`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `gormUserRepository` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "UserRepository")`
- [x] 将这个 `selfCtx` 赋值给 `gormUserRepository` 结构体的 `selfCtx` 成员。
- **公共方法改造 (所有公共方法)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`
- [x] 确保所有对 `r.db``tx` 的调用都使用 `r.db.WithContext(newCtx)``tx.WithContext(newCtx)`
- **`internal/infra/repository/device_repository.go` (`repository.DeviceRepository`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewGormDeviceRepository`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `gormDeviceRepository` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "DeviceRepository")`
- [x] 将这个 `selfCtx` 赋值给 `gormDeviceRepository` 结构体的 `selfCtx` 成员。
- **公共方法改造 (所有公共方法)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`
- [x] 确保所有对 `r.db``tx` 的调用都使用 `r.db.WithContext(newCtx)``tx.WithContext(newCtx)`
- **`internal/infra/repository/pig_pen_repository.go` (`repository.PigPenRepository`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewGormPigPenRepository`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `gormPigPenRepository` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "PigPenRepository")`
- [x] 将这个 `selfCtx` 赋值给 `gormPigPenRepository` 结构体的 `selfCtx` 成员。
- **公共方法改造 (所有公共方法)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`
- [x] 确保所有对 `r.db``tx` 的调用都使用 `r.db.WithContext(newCtx)``tx.WithContext(newCtx)`
- **`internal/infra/repository/pig_farm_repository.go` (`repository.PigFarmRepository`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewGormPigFarmRepository`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `gormPigFarmRepository` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "PigFarmRepository")`
- [x] 将这个 `selfCtx` 赋值给 `gormPigFarmRepository` 结构体的 `selfCtx` 成员。
- **公共方法改造 (所有公共方法)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`
- [x] 确保所有对 `r.db``tx` 的调用都使用 `r.db.WithContext(newCtx)``tx.WithContext(newCtx)`
- **`internal/infra/repository/pig_sick_repository.go` (`repository.PigSickLogRepository`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewGormPigSickLogRepository`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `gormPigSickLogRepository` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "PigSickLogRepository")`
- [x] 将这个 `selfCtx` 赋值给 `gormPigSickLogRepository` 结构体的 `selfCtx` 成员。
- **公共方法改造 (所有公共方法)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`
- [x] 确保所有对 `r.db``tx` 的调用都使用 `r.db.WithContext(newCtx)``tx.WithContext(newCtx)`
- **`internal/infra/repository/pig_batch_repository.go` (`repository.PigBatchRepository`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewGormPigBatchRepository`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `gormPigBatchRepository` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "PigBatchRepository")`
- [x] 将这个 `selfCtx` 赋值给 `gormPigBatchRepository` 结构体的 `selfCtx` 成员。
- **公共方法改造 (所有公共方法)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`
- [x] 确保所有对 `r.db``tx` 的调用都使用 `r.db.WithContext(newCtx)``tx.WithContext(newCtx)`
- **`internal/infra/repository/pig_trade_repository.go` (`repository.PigTradeRepository`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewGormPigTradeRepository`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `gormPigTradeRepository` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "PigTradeRepository")`
- [x] 将这个 `selfCtx` 赋值给 `gormPigTradeRepository` 结构体的 `selfCtx` 成员。
- **公共方法改造 (所有公共方法)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`
- [x] 确保所有对 `r.db``tx` 的调用都使用 `r.db.WithContext(newCtx)``tx.WithContext(newCtx)`
- **`internal/infra/repository/sensor_data_repository.go` (`repository.SensorDataRepository`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewGormSensorDataRepository`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `gormSensorDataRepository` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "SensorDataRepository")`
- [x] 将这个 `selfCtx` 赋值给 `gormSensorDataRepository` 结构体的 `selfCtx` 成员。
- **公共方法改造 (所有公共方法)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`
- [x] 确保所有对 `r.db``tx` 的调用都使用 `r.db.WithContext(newCtx)``tx.WithContext(newCtx)`
- **`internal/infra/repository/notification_repository.go` (`repository.NotificationRepository`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewGormNotificationRepository`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `gormNotificationRepository` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "NotificationRepository")`
- [x] 将这个 `selfCtx` 赋值给 `gormNotificationRepository` 结构体的 `selfCtx` 成员。
- **公共方法改造 (所有公共方法)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`
- [x] 确保所有对 `r.db``tx` 的调用都使用 `r.db.WithContext(newCtx)``tx.WithContext(newCtx)`
- **`internal/infra/repository/pending_task_repository.go` (`repository.PendingTaskRepository`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewGormPendingTaskRepository`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `gormPendingTaskRepository` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "PendingTaskRepository")`
- [x] 将这个 `selfCtx` 赋值给 `gormPendingTaskRepository` 结构体的 `selfCtx` 成员。
- **公共方法改造 (所有公共方法)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`
- [x] 确保所有对 `r.db``tx` 的调用都使用 `r.db.WithContext(newCtx)``tx.WithContext(newCtx)`
- **`internal/infra/repository/raw_material_repository.go` (`repository.RawMaterialRepository`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewGormRawMaterialRepository`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `gormRawMaterialRepository` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "RawMaterialRepository")`
- [x] 将这个 `selfCtx` 赋值给 `gormRawMaterialRepository` 结构体的 `selfCtx` 成员。
- **公共方法改造 (所有公共方法)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`
- [x] 确保所有对 `r.db``tx` 的调用都使用 `r.db.WithContext(newCtx)``tx.WithContext(newCtx)`
- **`internal/infra/repository/execution_log_repository.go` (`repository.ExecutionLogRepository`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewGormExecutionLogRepository`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `gormExecutionLogRepository` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "ExecutionLogRepository")`
- [x] 将这个 `selfCtx` 赋值给 `gormExecutionLogRepository` 结构体的 `selfCtx` 成员。
- **公共方法改造 (所有公共方法)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`
- [x] 确保所有对 `r.db``tx` 的调用都使用 `r.db.WithContext(newCtx)``tx.WithContext(newCtx)`
- **`internal/infra/repository/pig_batch_log_repository.go` (`repository.PigBatchLogRepository`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewGormPigBatchLogRepository`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `gormPigBatchLogRepository` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "PigBatchLogRepository")`
- [x] 将这个 `selfCtx` 赋值给 `gormPigBatchLogRepository` 结构体的 `selfCtx` 成员。
- **公共方法改造 (所有公共方法)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`
- [x] 确保所有对 `r.db``tx` 的调用都使用 `r.db.WithContext(newCtx)``tx.WithContext(newCtx)`
- **`internal/infra/repository/medication_log_repository.go` (`repository.MedicationLogRepository`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewGormMedicationLogRepository`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `gormMedicationLogRepository` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "MedicationLogRepository")`
- [x] 将这个 `selfCtx` 赋值给 `gormMedicationLogRepository` 结构体的 `selfCtx` 成员。
- **公共方法改造 (所有公共方法)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`
- [x] 确保所有对 `r.db``tx` 的调用都使用 `r.db.WithContext(newCtx)``tx.WithContext(newCtx)`
- **`internal/infra/repository/area_controller_repository.go` (`repository.AreaControllerRepository`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewGormAreaControllerRepository`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `gormAreaControllerRepository` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "AreaControllerRepository")`
- [x] 将这个 `selfCtx` 赋值给 `gormAreaControllerRepository` 结构体的 `selfCtx` 成员。
- **公共方法改造 (所有公共方法)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`
- [x] 确保所有对 `r.db``tx` 的调用都使用 `r.db.WithContext(newCtx)``tx.WithContext(newCtx)`
- **`internal/infra/repository/device_template_repository.go` (`repository.DeviceTemplateRepository`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewGormDeviceTemplateRepository`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `gormDeviceTemplateRepository` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "DeviceTemplateRepository")`
- [x] 将这个 `selfCtx` 赋值给 `gormDeviceTemplateRepository` 结构体的 `selfCtx` 成员。
- **公共方法改造 (所有公共方法)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`
- [x] 确保所有对 `r.db``tx` 的调用都使用 `r.db.WithContext(newCtx)``tx.WithContext(newCtx)`
- **`internal/infra/repository/user_action_log_repository.go` (`repository.UserActionLogRepository`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewGormUserActionLogRepository`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `gormUserActionLogRepository` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "UserActionLogRepository")`
- [x] 将这个 `selfCtx` 赋值给 `gormUserActionLogRepository` 结构体的 `selfCtx` 成员。
- **公共方法改造 (所有公共方法)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`
- [x] 确保所有对 `r.db``tx` 的调用都使用 `r.db.WithContext(newCtx)``tx.WithContext(newCtx)`
- **`internal/infra/repository/pig_transfer_log_repository.go` (`repository.PigTransferLogRepository`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewGormPigTransferLogRepository`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `gormPigTransferLogRepository` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "PigTransferLogRepository")`
- [x] 将这个 `selfCtx` 赋值给 `gormPigTransferLogRepository` 结构体的 `selfCtx` 成员。
- **公共方法改造 (所有公共方法)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`
- [x] 确保所有对 `r.db``tx` 的调用都使用 `r.db.WithContext(newCtx)``tx.WithContext(newCtx)`
- **`internal/infra/repository/device_command_log_repository.go` (`repository.DeviceCommandLogRepository`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewGormDeviceCommandLogRepository`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `gormDeviceCommandLogRepository` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "DeviceCommandLogRepository")`
- [x] 将这个 `selfCtx` 赋值给 `gormDeviceCommandLogRepository` 结构体的 `selfCtx` 成员。
- **公共方法改造 (所有公共方法)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`
- [x] 确保所有对 `r.db``tx` 的调用都使用 `r.db.WithContext(newCtx)``tx.WithContext(newCtx)`
- **`internal/infra/repository/pending_collection_repository.go` (`repository.PendingCollectionRepository`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewGormPendingCollectionRepository`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `gormPendingCollectionRepository` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "PendingCollectionRepository")`
- [x] 将这个 `selfCtx` 赋值给 `gormPendingCollectionRepository` 结构体的 `selfCtx` 成员。
- **公共方法改造 (所有公共方法)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`
- [x] 确保所有对 `r.db``tx` 的调用都使用 `r.db.WithContext(newCtx)``tx.WithContext(newCtx)`
- **`internal/infra/repository/raw_material_repository.go` (`repository.RawMaterialRepository`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewGormRawMaterialRepository`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `gormRawMaterialRepository` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "RawMaterialRepository")`
- [x] 将这个 `selfCtx` 赋值给 `gormRawMaterialRepository` 结构体的 `selfCtx` 成员。
- **公共方法改造 (所有公共方法)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`
- [x] 确保所有对 `r.db``tx` 的调用都使用 `r.db.WithContext(newCtx)``tx.WithContext(newCtx)`
- **`internal/infra/repository/execution_log_repository.go` (`repository.ExecutionLogRepository`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewGormExecutionLogRepository`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `gormExecutionLogRepository` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "ExecutionLogRepository")`
- [x] 将这个 `selfCtx` 赋值给 `gormExecutionLogRepository` 结构体的 `selfCtx` 成员。
- **公共方法改造 (所有公共方法)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`
- [x] 确保所有对 `r.db``tx` 的调用都使用 `r.db.WithContext(newCtx)``tx.WithContext(newCtx)`
- **`internal/infra/repository/pig_batch_log_repository.go` (`repository.PigBatchLogRepository`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewGormPigBatchLogRepository`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `gormPigBatchLogRepository` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "PigBatchLogRepository")`
- [x] 将这个 `selfCtx` 赋值给 `gormPigBatchLogRepository` 结构体的 `selfCtx` 成员。
- **公共方法改造 (所有公共方法)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`
- [x] 确保所有对 `r.db``tx` 的调用都使用 `r.db.WithContext(newCtx)``tx.WithContext(newCtx)`
- **`internal/infra/repository/medication_log_repository.go` (`repository.MedicationLogRepository`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewGormMedicationLogRepository`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `gormMedicationLogRepository` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "MedicationLogRepository")`
- [x] 将这个 `selfCtx` 赋值给 `gormMedicationLogRepository` 结构体的 `selfCtx` 成员。
- **公共方法改造 (所有公共方法)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`
- [x] 确保所有对 `r.db``tx` 的调用都使用 `r.db.WithContext(newCtx)``tx.WithContext(newCtx)`
- **`internal/infra/repository/area_controller_repository.go` (`repository.AreaControllerRepository`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewGormAreaControllerRepository`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `gormAreaControllerRepository` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "AreaControllerRepository")`
- [x] 将这个 `selfCtx` 赋值给 `gormAreaControllerRepository` 结构体的 `selfCtx` 成员。
- **公共方法改造 (所有公共方法)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`
- [x] 确保所有对 `r.db``tx` 的调用都使用 `r.db.WithContext(newCtx)``tx.WithContext(newCtx)`
- **`internal/infra/repository/device_template_repository.go` (`repository.DeviceTemplateRepository`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewGormDeviceTemplateRepository`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `gormDeviceTemplateRepository` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "DeviceTemplateRepository")`
- [x] 将这个 `selfCtx` 赋值给 `gormDeviceTemplateRepository` 结构体的 `selfCtx` 成员。
- **公共方法改造 (所有公共方法)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`
- [x] 确保所有对 `r.db``tx` 的调用都使用 `r.db.WithContext(newCtx)``tx.WithContext(newCtx)`
- **`internal/infra/repository/user_action_log_repository.go` (`repository.UserActionLogRepository`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewGormUserActionLogRepository`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `gormUserActionLogRepository` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "UserActionLogRepository")`
- [x] 将这个 `selfCtx` 赋值给 `gormUserActionLogRepository` 结构体的 `selfCtx` 成员。
- **公共方法改造 (所有公共方法)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`
- [x] 确保所有对 `r.db``tx` 的调用都使用 `r.db.WithContext(newCtx)``tx.WithContext(newCtx)`
- **`internal/infra/repository/pig_transfer_log_repository.go` (`repository.PigTransferLogRepository`)**
- **结构体改造**:
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewGormPigTransferLogRepository`)**:
- [x] 修改函数签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在函数内部,为 `gormPigTransferLogRepository` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "PigTransferLogRepository")`
- [x] 将这个 `selfCtx` 赋值给 `gormPigTransferLogRepository` 结构体的 `selfCtx` 成员。
- **公共方法改造 (所有公共方法)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx := logs.AddFuncName(ctx, r.selfCtx, "MethodName")`
- [x] 确保所有对 `r.db``tx` 的调用都使用 `r.db.WithContext(newCtx)` 或 `tx.WithContext(new

View File

@@ -0,0 +1,113 @@
- **`internal/app/service/pig_farm_service.go` (`service.PigFarmService`)**
- **结构体改造**:
- [x] 移除 `PigFarmService` 结构体中的 `logger *logs.Logger` 成员。
- [x] 新增 `selfCtx context.Context` 成员。
- [x] 移除 `PigFarmService` 结构体中的 `repo repository.PigFarmRepository` 成员,改为
`repo repository.PigFarmRepository`
- **构造函数改造 (`NewPigFarmService`)**:
- [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- [x] 在函数内部,为 `PigFarmService` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "PigFarmService")`
- [x] 将这个 `selfCtx` 赋值给 `PigFarmService` 结构体的 `selfCtx` 成员。
- **公共方法改造 (
所有方法,例如 `CreatePigHouse`, `GetPigHouseByID`, `ListPigHouses`, `UpdatePigHouse`, `DeletePigHouse`,
`CreatePen`, `GetPenByID`, `ListPens`, `UpdatePen`, `DeletePen`, `UpdatePenStatus`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。
- [x] 将所有对 `s.logger.Errorf``s.logger.Infof` 的调用替换为 `logger.Errorf``logger.Infof`
- [x] 确保所有对 `s.repo` 的调用都将 `newCtx` 作为第一个参数传递。
- **`internal/app/service/pig_batch_service.go` (`service.PigBatchService`)**
- **结构体改造**:
- [x] 移除 `PigBatchService` 结构体中的 `logger *logs.Logger` 成员。
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewPigBatchService`)**:
- [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- [x] 在函数内部,为 `PigBatchService` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "PigBatchService")`
- [x] 将这个 `selfCtx` 赋值给 `PigBatchService` 结构体的 `selfCtx` 成员。
- **公共方法改造 (
所有方法,例如 `CreatePigBatch`, `GetPigBatch`, `UpdatePigBatch`, `DeletePigBatch`, `ListPigBatches`,
`AssignEmptyPensToBatch`, `ReclassifyPenToNewBatch`, `RemoveEmptyPenFromBatch`, `MovePigsIntoPen`, `SellPigs`,
`BuyPigs`, `RecordSickPigs`, `RecordSickPigRecovery`, `RecordSickPigDeath`, `RecordSickPigCull`, `RecordDeath`,
`RecordCull`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。
- [x] 将所有对 `s.logger.Errorf``s.logger.Infof` 的调用替换为 `logger.Errorf``logger.Infof`
- [x] 确保所有对 `s.pigBatchRepo`, `s.pigBatchLogRepo`, `s.uow`, `s.transferSvc`, `s.tradeSvc`, `s.sickSvc`
等依赖的调用都将 `newCtx` 作为第一个参数传递。
- **`internal/app/service/monitor_service.go` (`service.MonitorService`)**
- **结构体改造**:
- [x] 移除 `MonitorService` 结构体中的 `logger *logs.Logger` 成员。
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewMonitorService`)**:
- [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- [x] 在函数内部,为 `MonitorService` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "MonitorService")`
- [x] 将这个 `selfCtx` 赋值给 `MonitorService` 结构体的 `selfCtx` 成员。
- **公共方法改造 (
所有方法,例如 `ListSensorData`, `ListDeviceCommandLogs`, `ListPlanExecutionLogs`, `ListTaskExecutionLogs`,
`ListPendingCollections`, `ListUserActionLogs`, `ListRawMaterialPurchases`, `ListRawMaterialStockLogs`,
`ListFeedUsageRecords`, `ListMedicationLogs`, `ListPigBatchLogs`, `ListWeighingBatches`, `ListWeighingRecords`,
`ListPigTransferLogs`, `ListPigSickLogs`, `ListPigPurchases`, `ListPigSales`, `ListNotifications`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。
- [x] 将所有对 `s.logger.Errorf``s.logger.Infof` 的调用替换为 `logger.Errorf``logger.Infof`
- [x] 确保所有对 `s.repo` 的调用都将 `newCtx` 作为第一个参数传递。
- **`internal/app/service/device_service.go` (`service.DeviceService`)**
- **结构体改造**:
- [x] 移除 `DeviceService` 结构体中的 `logger *logs.Logger` 成员。
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewDeviceService`)**:
- [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- [x] 在函数内部,为 `DeviceService` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "DeviceService")`
- [x] 将这个 `selfCtx` 赋值给 `DeviceService` 结构体的 `selfCtx` 成员。
- **公共方法改造 (
所有方法,例如 `CreateDevice`, `GetDevice`, `ListDevices`, `UpdateDevice`, `DeleteDevice`, `ManualControl`,
`CreateAreaController`, `GetAreaController`, `ListAreaControllers`, `UpdateAreaController`,
`DeleteAreaController`, `CreateDeviceTemplate`, `GetDeviceTemplate`, `ListDeviceTemplates`,
`UpdateDeviceTemplate`, `DeleteDeviceTemplate`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。
- [x] 将所有对 `s.logger.Errorf`, `s.logger.Warnf`, `s.logger.Infof` 的调用替换为 `logger.Errorf`,
`logger.Warnf`, `logger.Infof`
- [x] 确保所有对 `s.repo`, `s.generalDeviceService` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
- **`internal/app/service/plan_service.go` (`service.PlanService`)**
- **结构体改造**:
- [x] 移除 `PlanService` 结构体中的 `logger *logs.Logger` 成员。
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewPlanService`)**:
- [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- [x] 在函数内部,为 `PlanService` 创建其专属的 `selfCtx``selfCtx := logs.AddCompName(ctx, "PlanService")`
- [x] 将这个 `selfCtx` 赋值给 `PlanService` 结构体的 `selfCtx` 成员。
- **公共方法改造 (
所有方法,例如 `CreatePlan`, `GetPlanByID`, `ListPlans`, `UpdatePlan`, `DeletePlan`, `StartPlan`, `StopPlan`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。
- [x] 将所有对 `s.logger.Errorf``s.logger.Infof` 的调用替换为 `logger.Errorf``logger.Infof`
- [x] 确保所有对 `s.repo`, `s.planExecutionManager`, `s.analysisPlanTaskManager` 等依赖的调用都将 `newCtx`
作为第一个参数传递。
- **`internal/app/service/user_service.go` (`service.UserService`)**
- **结构体改造**:
- [x] 移除 `UserService` 结构体中的 `logger *logs.Logger` 成员。
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewUserService`)**:
- [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- [x] 在函数内部,为 `UserService` 创建其专属的 `selfCtx``selfCtx := logs.AddCompName(ctx, "UserService")`
- [x] 将这个 `selfCtx` 赋值给 `UserService` 结构体的 `selfCtx` 成员。
- **公共方法改造 (所有方法,例如 `CreateUser`, `Login`, `SendTestNotification`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在方法入口处,使用 `newCtx, logger := logs.Trace(ctx, s.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。
- [x] 将所有对 `s.logger.Errorf``s.logger.Infof` 的调用替换为 `logger.Errorf``logger.Infof`
- [x] 确保所有对 `s.repo`, `s.tokenService`, `s.notifier` 等依赖的调用都将 `newCtx` 作为第一个参数传递。

View File

@@ -0,0 +1,39 @@
- **`internal/app/webhook/chirp_stack.go` (`webhook.ChirpStackListener`)**
- **结构体改造**:
- [x] 移除 `ChirpStackListener` 结构体中的 `logger *logs.Logger` 成员。
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewChirpStackListener`)**:
- [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- [x] 在函数内部,为 `ChirpStackListener` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "ChirpStackListener")`
- [x] 将这个 `selfCtx` 赋值给 `ChirpStackListener` 结构体的 `selfCtx` 成员。
- **公共方法改造 (`Handler`)**:
- [x] `Handler` 方法返回 `http.HandlerFunc`。在返回的 `http.HandlerFunc` 内部,获取 `r.Context()` 作为
`upstreamCtx`
- [x] 在 `go c.handler(b, event)` 调用之前,将 `upstreamCtx` 传递给 `c.handler` 方法,即
`go c.handler(upstreamCtx, b, event)`
- [x] 将所有对 `c.logger.Errorf` 的调用替换为 `logger.Errorf` (在 `handler` 方法中处理)。
- **内部辅助方法改造 (`handler`, `handleUpEvent`, `handleStatusEvent`, `handleAckEvent`, `handleLogEvent`,
`handleJoinEvent`, `handleTxAckEvent`, `handleLocationEvent`, `handleIntegrationEvent`, `recordSensorData`)**:
- [x] 修改方法签名,添加 `ctx context.Context` 作为第一个参数。
- [x] 在每个方法入口处,使用 `newCtx, logger := logs.Trace(ctx, c.selfCtx, "MethodName")` 获取新的
`context.Context``logger` 实例。
- [x] 将所有对 `c.logger.Errorf`, `c.logger.Infof`, `c.logger.Warnf` 的调用替换为 `logger.Errorf`,
`logger.Infof`, `logger.Warnf`
- [x] 确保所有对 `c.sensorDataRepo`, `c.deviceRepo`, `c.areaControllerRepo`, `c.deviceCommandLogRepo`,
`c.pendingCollectionRepo` 等依赖的调用都将 `newCtx` 作为第一个参数传递。
- **`internal/app/webhook/placeholder_listener.go` (`webhook.PlaceholderListener`)**
- **结构体改造**:
- [x] 移除 `PlaceholderListener` 结构体中的 `logger *logs.Logger` 成员。
- [x] 新增 `selfCtx context.Context` 成员。
- **构造函数改造 (`NewPlaceholderListener`)**:
- [x] 修改函数签名,移除 `logger *logs.Logger` 参数,改为接收 `ctx context.Context`
- [x] 在函数内部,为 `PlaceholderListener` 创建其专属的 `selfCtx`
`selfCtx := logs.AddCompName(ctx, "PlaceholderListener")`
- [x] 将这个 `selfCtx` 赋值给 `PlaceholderListener` 结构体的 `selfCtx` 成员。
- [x] 使用 `newCtx, logger := logs.Trace(ctx, selfCtx, "NewPlaceholderListener")` 获取 `logger` 实例,并替换
`logger.Info` 调用。
- **公共方法改造 (`Handler`)**:
- [x] 在 `Handler` 方法返回的 `http.HandlerFunc` 内部,获取 `r.Context()` 作为 `upstreamCtx`
- [x] 使用 `newCtx, logger := logs.Trace(upstreamCtx, p.selfCtx, "Handler")` 获取 `logger` 实例。
- [x] 将所有对 `p.logger.Warn` 的调用替换为 `logger.Warn`

View File

@@ -0,0 +1,15 @@
# 问题
增加健康检查路由, 供docker/k8s健康探测服务使用
## issue
http://git.huangwc.com/pig/pig-farm-controller/issues/48
# 方案
增加两个对应路由
# 其他修复
修复系统启动时系统计划更新和触发器刷新冲突的问题

View File

@@ -0,0 +1,21 @@
# 问题
每次全量传感器采集都会连着下发五条采集指令
## issue
http://git.huangwc.com/pig/pig-farm-controller/issues/55
# 解决方案
## 问题描述
系统启动时没有删掉旧的任务但会在更新时把新的任务插入, 导致计划中的任务越来越多
## 修复方案
系统启动更新系统计划时删掉旧的任务
## 其他修复
删除任务时对于设备任务关联表是软删除, 但这张表不支持软删除, 遂改为硬删除

View File

@@ -0,0 +1,149 @@
# 需求
实现采集数据超过阈值报警
## issue
[实现采集数据超过阈值报警](http://git.huangwc.com/pig/pig-farm-controller/issues/62)
# 方案
1. **架构核心**: 新增一个 **告警领域服务**,作为告警系统的核心大脑,负责告警事件的生命周期管理。
2. **任务分离**:
* 新增 **阈值告警任务** (分为区域主控和普通设备两种),仅负责检测数据并将结果报告给领域服务。
* 新增 **告警通知发送任务**,作为一个独立的、系统预定义的定时任务,负责调用领域服务,获取并发送所有待处理的通知。
3. **计划调度**:
* 修改现有 "定时全量数据采集" 计划, 更名为 "周期性系统健康检查"。此计划包含固定的 **全量采集任务** 和由用户动态配置的
**阈值告警任务**
* 新增一个独立的 "告警通知发送" 计划,用于定时执行固定的 **告警通知发送任务**
4. **数据与接口**:
* 新增独立的告警记录表(建议采用“活跃告警表 + 历史告警超表”的模式)。
* 新增相应的告警配置管理接口。
## 方案细节
### 架构与职责划分
1. **告警领域服务 (`internal/domain/alarm/`) - 管理器**
* **职责**: 作为告警系统的核心大脑,负责处理告警事件的完整生命周期。
* **功能**:
* 接收来自检测任务的状态报告包含设备ID、传感器类型、当前是否异常等信息
* 根据报告和数据库中的告警记录,决策是创建新告警、更新为已解决、还是因被忽略而跳过。
* 管理“手动忽略” (`Ignored`) 状态和忽略到期时间 (`ignored_until`)。
* 实现可配置的“重复通知”策略(`re_notification_interval`),决定何时对持续存在的告警再次发送通知。
* 提供接口供 `告警通知发送任务` 调用,以获取所有待处理的通知。
2. **阈值告警任务 (`internal/domain/task/`) - 检测器**
* **职责**: 职责纯粹,仅负责执行检测并将结果报告给告警领域服务。
* **逻辑**: 从传感器数据表读取最新数据 -> 与自身配置的阈值进行比对 -> 无论结果如何,都调用 `告警领域服务.ReportStatus()`
报告当前状态(正常或异常)。
* **无状态**: 任务本身不关心告警是否已存在或被忽略,它只负责“状态同步”。
3. **告警通知发送任务 (`internal/domain/task/`) - 发送器**
* **职责**: 作为一个独立的定时任务,解耦通知发送与告警检测。
* **逻辑**: 调用 `告警领域服务.GetAndProcessPendingNotifications()` -> 获取待发送通知列表 -> 调用 `通知领域服务`
逐一发送。
* **优势**: 统一管理定时任务,实现资源控制,提高系统稳定性和可扩展性。
### 计划与任务调度
1. **"周期性系统健康检查" 计划**
* **任务构成**:
* **全量数据采集任务 (ExecutionOrder: 1)**: 系统预定义,必须是第一个执行的任务,为后续的告警检测提供最新的数据基础。
* **阈值告警任务 (ExecutionOrder: 2, 3...)**: 由用户通过API动态配置和管理`告警配置服务` 负责将其增删改到此计划中。
2. **"告警通知发送" 计划**
* **任务构成**: 包含一个系统预定义的 `告警通知发送任务`
* **调度**: 可配置独立的执行频率(如每分钟一次),与健康检查计划解耦。
3. **系统初始化 (`data_initializer.go`)**
* **职责**: 只负责创建和维护系统预定义的、固定的计划和任务。
* **操作**:
* 确保 "周期性系统健康检查" 计划存在,并包含 `全量数据采集任务`
* 确保 "告警通知发送" 计划存在,并包含 `告警通知发送任务`
* **注意**: 初始化逻辑 **不会****不应该** 触及用户动态配置的阈值告警任务。
### 阈值告警任务 (用户可配置的任务类型)
1. **任务类型**: 提供两种可供用户配置的阈值告警任务类型,分别对应 **区域主控****普通设备** 告警。
2. **参数结构**:
* **通用参数**: 任务参数将包含 `Thresholds` (阈值) 和 `Operator` (操作符,如 `>``<`) 字段。
* **普通设备任务**: 配置包含 `DeviceID`
* **区域主控任务**: 配置包含 `AreaControllerID`, `SensorType`, 以及一个 `ExcludeDeviceIDs` (需要排除的设备ID列表)。
### 告警事件与生命周期
1. **告警事件定义**:
* 区分 **告警规则** (配置的策略) 和 **告警事件** (规则被具体设备触发的实例)。
* 区域主控下不同设备触发的告警,即使基于同一规则,也应视为独立的 **告警事件**,以便于精确追溯和独立操作。
2. **生命周期管理**:
* **自动闭环**: 当阈值告警任务报告数据恢复正常时,告警领域服务会自动将对应的 `Active` 告警事件状态更新为 `Resolved`
* **手动忽略 (Snooze)**: 用户可通过接口将告警事件状态置为 `Ignored` 并设置 `ignored_until`
。在此期间,即使数据持续异常,也不会发送通知。忽略到期后若问题仍存在,告警将重新变为 `Active` 并发送通知。
* **持续告警与重复通知**: 对持续未解决的 `Active` 告警,只保留一条记录。告警领域服务会根据 `re_notification_interval`
配置的重复通知间隔,决定是否需要再次发送通知。
### 数据库设计考量
1. **冷热分离方案 (推荐)**:
* **`active_alarms` (活跃告警表)**:
* **类型**: 标准 PostgreSQL 表。
* **内容**: 只存放 `Active``Ignored` 状态的告警。
* **优势**: 保证高频读写的性能,避免在被压缩的数据上执行更新操作。
* **`historical_alarms` (历史告警表)**:
* **类型**: 改造为 **TimescaleDB 超表**
* **内容**: 存放 `Resolved` 状态的告警。当告警在 `active_alarms` 中被解决后,记录将移至此表。
* **优势**: 适合存储海量历史数据,便于分析、统计,并可利用 TimescaleDB 的压缩和数据生命周期管理功能。
2. **表结构字段**:
* `status`: 枚举类型,包含 `Active`, `Resolved`, `Ignored`
* `ignored_until`: `timestamp` 类型,记录忽略截止时间。
* `last_notified_at`: `timestamp` 类型,记录上次发送通知的时间。
### 阈值告警服务 (领域层)
1. **服务职责**:
* 负责管理阈值告警 **任务配置** 的增删改查。这些任务配置包含了具体的阈值规则。
* 负责将用户创建的阈值告警任务动态更新到 "周期性系统健康检查" 计划中。
* **任务配置引用检查**: 提供自检方法,用于在删除设备或设备模板前,检查它们是否被任何阈值告警任务配置所引用,以防止产生悬空引用。
2. **排除列表计算与联动**:
* **删除独立任务配置后归属**: 当一个普通设备的独立告警任务配置被删除时,它将自动从其所属区域主控的 `ExcludeDeviceIDs`
列表中移除,从而回归到区域统一告警策略的管理之下。
* **设备生命周期管理**: 在对设备进行修改(特别是更换区域主控)或删除时,以及在删除区域主控时,必须同步更新相关的
`ExcludeDeviceIDs` 列表,同时解决相关告警(当删除时), 以保证数据一致性。
* **实现**: `DeviceService` 中负责处理设备更新和删除的方法,需要调用本服务提供的“任务配置引用检查”和刷新接口。
### 阈值告警控制器
1. **独立接口**: 提供两组独立的 Web 接口,分别用于管理区域主控和普通设备的阈值告警配置。
* 区域主控告警配置接口: `/api/v1/alarm/region-config`
* 普通设备告警配置接口: `/api/v1/alarm/device-config`
2. **接口职责**: 接口负责接收前端请求,调用应用服务层的阈值告警服务来完成实际的业务逻辑。
### TODO
1. 是否要加一个延时操作, 因为采集是异步的, 采集任务结束时不一定能拿到最新数据, 所以需要一个延时操作等待区域主控上传
2. 统一一下区域主控的命名, 目前有AreaController和RegionalController, 不排除还有别的
3. 将数据类型转为float32, 节约空间, float64精度有些浪费, float32小数点后6-7位足够了
# 实现记录
1. 定义告警表和告警历史表
2. 重构部分枚举, 让models包不依赖其他项目中的包
3. 创建仓库层对象(不包含方法)
4. 实现告警发送任务
5. 实现告警通知发送计划/全量采集计划改名
6. 实现设备阈值检查任务
7. 实现忽略告警和取消忽略告警接口及功能
8. 实现列表查询活跃告警和历史告警
9. 系统初始化时健康计划调整(包括增加延时任务)
10. 实现区域阈值告警任务
11. 实现区域阈值告警和设备阈值告警的增删改查
12. 实现任务11应的八个web接口
13. 实现根据区域ID或设备ID清空对应阈值告警任务
14. 设备和区域主控删除时清除对应区域阈值告警或设备阈值告警任务
15. 将所有Regional更改为Area
16. float64全部改float32
17. uint/uint64全部改为uint32

View File

@@ -0,0 +1,82 @@
# 需求
饲料配方管理及自动生成配方
## issue
http://git.huangwc.com/pig/pig-farm-controller/issues/66
# 开发计划
1. 原料营养价值管理
- 增删改查
- 内置60+条常用原料玉米、豆粕43、豆粕46、发酵豆粕、麸皮、次粉、DDGS、乳清粉、鱼粉、膨化大豆、各种氨基酸、预混料、油脂等
- 每种原料固定营养值消化能、粗蛋白、赖氨酸、钙、磷等15项左右
2. 饲料库存管理(代替批次)
- 字段:饲料名、当前原料种类、当前剩余量(吨)、上次入料日期、保质期剩余天数(手动填)、是否发酵料(勾选)
- 发酵料塔额外字段:
- 发酵状态(未发酵 / 正在发酵 / 已发酵可用)
- 发酵开始日期
- 发酵几天默认37天
- 水分增加比例(默认+1020%
- 营养折损系数(可调,粗蛋白-5%、能量-3%之类)
3. 猪只阶段营养需求管理
- 预设10个常用阶段教槽、仔猪、小猪、中猪、大猪、后备、怀孕前中后、哺乳
- 每个阶段维护营养需求上下限消化能、粗蛋白、赖氨酸、钙、有效磷等12项
4. 配方管理
- 按阶段建配方
- 支持增删改查 + 复制上个配方快速新建
- 配方明细:原料 + 配比(%
5. 自动生成配方(核心功能)
- 选择阶段 → 点击“自动计算最低成本配方”
- 自动读取当前所有料塔的:
- 剩余量(不够的原料自动降配比)
- 保质期剩余天数越快过期的优先用权重×1.5
- 发酵料塔如果状态是“已发酵可用”则按发酵后营养值参与计算
- 输出:总成本、营养达标情况、发酵料占比、即将过期原料使用提示
6. 配方下发与记录
- 一键下发到喂料站/料线(生成下料曲线)
- 自动记录今天用了哪个配方
7. 简单查看功能
- 两个配方对比页面(营养+成本对比)
# 实现总结
## 实现内容
实现库存和原料和营养和猪营养需求的管理, 支持根据库存和已录入原料和猪营养需求生成配方
## TODO
1. 发酵料管理考虑到发酵目前没有自动化流程, 不好追踪, 遂暂时不做
2. 目前的价格是根据原料的参考价设置的, 后续应当实现一个在服务供平台采集参考价, 以及使用原料采购价计算
3. 原料应该加上膨润土等, 比如膨润土的黄曲霉素含量应该是负数以表示减少饲料里的含量
4. 饲料保质期考虑到批次间管理暂时不方便, 等可以实现同一原料先进先出后再实现
5. 暂时不支持指定原料列表然后自动生成, 也不支持告诉用户当前生成不出是为什么, 等以后再做
# 完成事项
1. 定义原料表, 营养表, 原料营养表, 原料库存变更表
2. 迁移配置文件, 实现从json文件中读取原材料营养预设值, 并自动写入数据库
3. 定义配方领域, 实现营养元素的增删改查
4. 实现原材料的增删改查和仓库层的原料库存记录表增查
5. 定义猪的模型和营养需求模型
6. 实现从json读取猪营养需求并写入数据库
7. 实现配方领域关于猪模型和营养需求的增删改查
8. 实现配方领域的web接口
9. 实现修改原料营养信息
10. 实现修改猪营养需求
11. 配方模型定义和仓库层增删改查方法
12. 配方领域层方法
13. 重构配方领域
14. 配方增删改查服务层和控制器
15. 实现库存管理相关逻辑
16. 实现配方生成器
17. 实现使用系统中所有可用的原料一键生成配方
18. 实现优先使用库存的配方一键生成

View File

@@ -0,0 +1,20 @@
# 需求
支持主控设备ota升级和远程查看日志
## issue
http://git.huangwc.com/pig/pig-farm-controller/issues/71
# 开发计划
## OTA 升级
- [x] 增加一个proto对象, 用于封装ota升级包
- [x] 区域主控增加版本号
- [x] 增加ping指令并获取带版本号的响应
- [ ] [实现ota升级逻辑](design/ota-upgrade-and-log-monitoring/ota_upgrade_solution.md)
## Lora 监听逻辑重构
- [x] [Lora逻辑重构](design/ota-upgrade-and-log-monitoring/lora_refactoring_plan.md)

View File

@@ -0,0 +1,199 @@
# LoRa 通信层统一重构方案
## 1. 目标
统一项目当前并存的两种 LoRa 通信模式(基于 ChirpStack API 和基于串口透传),使其在架构层面遵循相同的接口和设计模式。最终实现:
- **业务逻辑统一**:所有上行业务处理逻辑集中在一个地方,与具体的通信方式无关。
- **发送接口统一**:上层服务使用同一个接口发送下行指令,无需关心底层实现。
- **架构清晰**:明确划分基础设施层(负责传输)和应用层(负责业务)的职责,并确保正确的依赖方向 (`app` -> `infra`)。
- **高扩展性**:未来支持新的通信方式时,只需添加新的“适配器”,而无需改动核心业务代码。
## 2. 背景与问题分析
### 2.1. 当前存在两种 LoRa 通信模式
1. **ChirpStack 模式**: 通过 `internal/infra/transport/lora/chirp_stack.go` 实现发送,通过 `internal/app/listener/chirp_stack/chirp_stack.go` 监听并处理 ChirpStack Webhook 推送的数据。
2. **串口透传模式**: 通过 `internal/infra/transport/lora/lora_mesh_uart_passthrough_transport.go` 实现发送和接收处理。
### 2.2. 核心差异
| 特性 | ChirpStack 模式 | 串口透传模式 |
| :--- | :--- | :--- |
| **通信模型** | 双向、有状态、异步API调用 | 单向、无状态、直接串口读写 |
| **接收机制** | Webhook (HTTP POST) 推送 | 主动从串口读取字节流 |
| **数据格式** | JSON 包装 + Base64 编码 | 自定义二进制物理帧 |
| **寻址方式**| `DevEui` | 自定义 16 位网络地址 |
| **核心职责** | LNS管理会话、ACK、队列 | 纯粹的“无线串口” |
### 2.3. 问题
- **业务逻辑分散**:处理 `CollectResult` 的业务逻辑在 `chirp_stack.go``lora_mesh_uart_passthrough_transport.go` 中都存在,造成代码重复和维护困难。
- **职责不清**`lora_mesh_uart_passthrough_transport.go` 同时承担了基础设施(串口读写)和应用(处理业务)两种职责。
- **依赖关系混乱**:为了让 `infra` 层的串口模块能调用业务逻辑,可能会导致 `infra` 层反向依赖 `app` 层,破坏了项目的核心架构原则。
## 3. 统一架构设计方案
### 3.1. 核心思想
采用 **端口与适配器模式 (Ports and Adapters Pattern)**,严格遵守 **依赖倒置原则**
- **端口 (Port)**:在 `infra` 层定义一个 `UpstreamHandler` 接口。这个接口是 `infra` 层向上层暴露的“端口”,它规定了上行业务处理器必须满足的协约。
- **适配器 (Adapter)**:在 `app` 层创建一个 `LoRaListener` 作为“适配器”,它实现 `infra` 层定义的 `UpstreamHandler` 接口,并封装所有核心业务处理逻辑。
- **依赖注入**:在系统启动时,将 `app` 层的 `LoRaListener` 实例注入到需要它的 `infra` 层组件中。
### 3.2. 统一接口定义
#### 3.2.1. 发送接口 (已存在,无需修改)
```go
// file: internal/infra/transport/transport.go
package transport
type Communicator interface {
Send(ctx context.Context, address string, payload []byte) (*SendResult, error)
}
```
#### 3.2.2. 接收处理接口 (端口定义)
此接口定义了 `infra` 层对上行业务处理器的期望,是 `infra` 层向上层暴露的“端口”。
```go
// file: internal/infra/transport/transport.go
package transport
import (
"context"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/transport/proto"
)
// UpstreamHandler 定义了处理所有来源的上行数据的统一协约。
// 任何实现了上行消息监听的基础设施如串口、MQTT客户端都应该在收到消息后调用此接口的实现者。
// 这样,基础设施层只负责“接收和解析”,而将“业务处理”的控制权交给了上层。
type UpstreamHandler interface {
// HandleInstruction 处理来自设备的、已解析为Instruction的业务指令。
HandleInstruction(ctx context.Context, sourceAddr string, instruction *proto.Instruction) error
// HandleStatus 处理非业务指令的设备状态更新,例如信号强度、电量等。
HandleStatus(ctx context.Context, sourceAddr string, status map[string]interface{}) error
}
```
### 3.3. 组件职责划分 (重构后)
#### 3.3.1. 统一业务处理器 (应用层适配器)
- **文件**: `internal/app/listener/lora_listener.go` (新)
- **职责**:
- 实现 `transport.UpstreamHandler` 接口。
- 包含所有处理业务所需的依赖(如领域服务、仓储等)。
- 实现 `HandleInstruction` 方法,通过 `switch-case` 编排所有核心业务。
- 实现 `HandleStatus` 方法,处理设备状态更新。
- **这是项目中唯一处理 LoRa 上行业务的地方。**
#### 3.3.2. 基础设施层 (Infra Layer)
- **文件 1**: `internal/app/listener/chirp_stack/chirp_stack.go` (重构)
- **职责**: 纯粹的 Webhook 适配器。
- 移除所有业务逻辑和数据库依赖。
- 依赖 `transport.UpstreamHandler` 接口。
- 功能:接收 Webhook -> 解析 JSON -> 调用 `handler.HandleInstruction``handler.HandleStatus`
- **文件 2**: `internal/infra/transport/lora/lora_mesh_uart_passthrough_transport.go` (重构)
- **职责**: 纯粹的串口传输工具。
- 移除所有业务逻辑和数据库依赖。
- 依赖 `transport.UpstreamHandler` 接口。
- 功能:管理串口 -> 读字节流 -> 重组分片 -> 解析 `proto.Instruction` -> 调用 `handler.HandleInstruction`
### 3.4. 架构图 (重构后)
```
+--------------------------------+
| Upper-Level Services |
| (e.g., DeviceService) |
+--------------------------------+
|
v (uses)
+--------------------------------+
| transport.Communicator (I) | <-- Infra Layer (Send Port)
+--------------------------------+
^ ^
| | (implements)
+------------------+------------------+
| ChirpStackSender | UartSender | <-- Infra Layer (Senders)
+------------------+------------------+
+--------------------------------+
| listener.LoRaListener | <-- App Layer (Adapter)
| (Implements UpstreamHandler) |
+--------------------------------+
^
| (dependency, via interface)
+--------------------------------+
| transport.UpstreamHandler (I) | <-- Infra Layer (Receive Port)
+--------------------------------+
^ ^
| | (calls)
+------------------+------------------+
| ChirpStackWebhook| UartPassthrough | <-- Infra Layer (Receivers)
+------------------+------------------+
^ ^
| | (receives from)
+------------------+------------------+
| HTTP Webhook | Serial Port |
+------------------+------------------+
```
### 3.5. 依赖注入与组装示例
```go
// file: internal/core/component_initializers.go
// 1. 创建统一的业务处理器 (App层适配器)
// 它实现了 infra 层的 transport.UpstreamHandler 接口
loraListener := listener.NewLoRaListener(logger, dbRepo1, dbRepo2)
// 2. 初始化 ChirpStack 模式
// 2a. 创建 ChirpStack 的发送器 (infra)
chirpStackCommunicator := chirp_stack.NewChirpStackTransport(...)
// 2b. 创建 ChirpStack 的监听器 (infra),并注入 App 层的业务处理器
chirpStackListener := chirp_stack.NewChirpStackListener(loraListener)
// 2c. 注册 Webhook 路由
api.RegisterWebhook("/chirpstack", chirpStackListener.Handler())
// 3. 初始化串口透传模式
// 3a. 创建串口的传输工具 (infra),并注入 App 层的业务处理器
uartTransport := lora.NewLoRaMeshUartPassthroughTransport(port, loraListener)
// 3b. 启动串口监听
uartTransport.Listen()
// 4. 向上层业务提供统一的发送器
var finalCommunicator transport.Communicator
if config.UseChirpStack {
finalCommunicator = chirpStackCommunicator
} else {
finalCommunicator = uartTransport
}
// 将 finalCommunicator 注入到需要发送指令的服务中...
```
## 4. 实施步骤
1. **定义端口**: 在 `internal/infra/transport/transport.go` 中定义 `UpstreamHandler` 接口。
2. **创建适配器**: 创建 `internal/app/listener/lora_listener.go`,定义 `LoRaListener` 结构体,并实现 `transport.UpstreamHandler` 接口。
3. **迁移业务逻辑**: 将 `chirp_stack.go``lora_mesh_uart_passthrough_transport.go` 中的业务逻辑(查库、存数据等)逐步迁移到 `lora_listener.go` 的对应方法中。
4. **重构基础设施**:
- 清理 `chirp_stack.go`,移除 Repo 依赖,改为依赖 `transport.UpstreamHandler` 接口,并调用其方法。
- 清理 `lora_mesh_uart_passthrough_transport.go`,做同样的操作。
5. **更新依赖注入**: 修改 `component_initializers.go`,按照 `3.5` 中的示例完成组件的创建和注入。
6. **测试与验证**: 对两种模式分别进行完整的上下行通信测试。
## 5. 收益
- **消除代码重复**:业务逻辑仅存在于一处。
- **职责清晰**:基础设施层只管传输,应用层只管业务。
- **正确的依赖关系**:确保了 `app` -> `infra` 的单向依赖,核心架构更加稳固。
- **可维护性**:修改业务逻辑只需改一个文件,修改传输细节不影响业务。
- **可测试性**:可以轻松地对 `LoRaListener` 进行单元测试,无需真实的硬件或网络。

View File

@@ -0,0 +1,306 @@
# 区域主控 MicroPython OTA 升级方案
## 1. 概述
### 1.1. 目标
实现区域主控 (ESP32-S3-N16R8, MicroPython 固件) 的安全、可靠的远程固件升级 (OTA)。
### 1.2. 核心思想
* **AB 分区模式**: 区域主控采用 AB 分区模式,允许在设备运行时更新非活动分区,升级失败时可回滚到上一个已知的工作版本。
* **平台主导**: 升级过程由平台完全控制,包括固件准备、文件分发和升级指令下发。
* **LoRa 传输层自动分片**: 充分利用 LoRa 传输层自动分片和重组的能力,简化应用层协议设计。
* **逐文件校验**: 设备在接收每个文件后立即进行 MD5 校验,确保文件完整性,并处理重试。
* **清单文件**: 使用清单文件管理所有待更新文件的元数据和校验信息。
* **设备自驱动**: 设备主动请求清单文件和固件文件,并在所有文件校验成功后自行激活新固件并重启。
* **平台记录升级任务**: 平台将记录 OTA 升级任务的创建、进度和最终状态。
* **配置文件独立管理**: OTA 升级过程将不涉及配置文件的更新,配置文件由平台提供独立的远程修改功能。
### 1.3. 涉及组件
* **平台**: 负责固件包管理、清单文件生成、数字签名(未来)、文件分发、指令下发、状态接收和**升级任务记录**。
* **LoRa 传输层**: 负责应用层数据的分片、传输和重组。
* **区域主控 (ESP32-S3-N16R8)**: 负责接收文件、存储到非活动分区、文件校验、分区切换、新固件启动验证和状态上报。
## 2. 固件包结构与准备
### 2.1. 原始固件包 (由开发者提供给平台)
* 一个标准的压缩包(例如 `.zip`),其中包含所有 MicroPython `.py` 文件、资源文件等。
* 压缩包内的文件结构应与期望在设备上部署的路径结构一致。
### 2.2. 平台处理流程
1. **接收**: 平台接收开发者上传的 MicroPython 项目压缩包。
2. **解压**: 平台将该压缩包解压到内部的一个临时目录。
3. **分析与生成清单**: 平台遍历解压后的所有文件,为每个文件计算:
* 在设备上的目标路径 (`path`)
* MD5 校验和 (`md5`)
* 文件大小 (`size`)
* **排除配置文件**: 平台会识别配置文件(例如通过文件名约定,如 `/config/` 目录下的所有文件),并**排除**
这些文件,不将其包含在清单文件中,也不通过 OTA 传输。
4. **生成清单文件**: 平台根据上述信息,生成一个 JSON 格式的清单文件。
5. **数字签名 (未来扩展)**: 平台使用其私钥对**清单文件**的内容进行数字签名,并将签名添加到清单文件中。
### 2.3. 清单文件 (Manifest File) 结构
清单文件是一个 JSON 对象,包含新固件的元数据和所有文件的详细信息。
```json
{
"version": "1.0.1",
// 新固件版本号
"signature": "...",
// 清单文件内容的数字签名 (未来扩展)
"files": [
{
"path": "/manifest.json",
// 清单文件本身也作为文件列表的一部分
"md5": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6",
"size": 1024
},
{
"path": "/main.py",
"md5": "b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6a1",
"size": 10240
},
{
"path": "/lib/sensor.py",
"md5": "c3d4e5f6a7b8c9d0e1f2a3b4c5d6a1b2",
"size": 5120
}
// ... 更多文件 (不包含配置文件)
]
}
```
## 3. 通信协议定义 (Protobuf Messages)
以下是 OTA 过程中平台与区域主控之间通信所需的 Protobuf 消息定义。
```protobuf
// OTA 升级指令和状态消息
// PrepareUpdateReq: 平台发送给设备,通知设备准备开始 OTA 升级
message PrepareUpdateReq {
string version = 1; // 新固件版本号
string task_id = 2; // 升级任务唯一ID
string manifest_md5 = 3; // 清单文件的 MD5 校验和,用于设备初步校验清单文件完整性
}
// RequestFile: 设备向平台请求特定文件 (包括清单文件和固件文件)
message RequestFile {
string task_id = 1; // 升级任务ID
string filepath = 2; // 请求的文件路径 (例如 "/manifest.json" 或 "/main.py")
uint32 retry_count = 3; // 设备请求该文件的重试次数
}
// FileResponse: 平台响应设备请求,发送单个文件的完整内容
// LoRa 传输层会自动处理分片和重组,因此应用层可以直接发送完整的单个文件内容
message FileResponse {
string task_id = 1; // 升级任务ID
string filepath = 2; // 设备上的目标路径 (例如 "/manifest.json" 或 "/main.py")
bytes content = 3; // 文件的完整内容
}
// UpdateStatusReport: 设备向平台报告升级状态
message UpdateStatusReport {
string device_id = 1; // 设备ID
string task_id = 2; // 升级任务ID
string current_version = 3; // 操作完成后的当前版本
enum Status {
STATUS_UNKNOWN = 0;
// --- 设备主动上报的状态 ---
SUCCESS = 1; // 升级成功,新固件已运行 (由设备在自检成功后主动上报)
SUCCESS_ALREADY_UP_TO_DATE = 2; // 版本已是最新,未执行升级 (由设备在版本检查后主动上报)
FAILED_PRE_CHECK = 3; // 升级前检查失败 (例如拒绝降级、准备分区失败等,由设备主动上报)
FAILED_DOWNLOAD = 4; // 文件下载或校验失败 (由设备在下载过程中主动上报)
// --- 平台推断的状态 (数据库记录用) ---
FAILED_TIMEOUT = 5; // 平台在超时后仍未收到SUCCESS报告将任务标记为此状态
}
Status status = 4; // 升级的最终状态
string error_message = 6; // 人类可读的详细错误信息
string failed_file = 7; // 失败时关联的文件路径 (可选)
}
```
## 4. 平台侧操作流程
### 4.1. 准备升级任务
1. 接收开发者提供的 MicroPython 项目压缩包。
2. 解压压缩包。
3. 遍历解压后的文件,计算每个文件的 MD5、大小并确定目标路径。
4. **排除配置文件**: 平台会识别配置文件(例如通过文件名约定,如 `/config/` 目录下的所有文件),并**排除**这些文件。
5. 生成清单文件 (Manifest File)。**注意:清单文件本身也应作为 OTA 的一部分,其元数据应包含在清单文件自身的 `files`
列表中。Manifest文件生成后将被放在解压后的文件夹的根目录下, 方便后续主控设备获取**
6. (未来扩展)对清单文件进行数字签名。
7. 将清单文件和所有固件文件存储在平台内部,等待分发。
8. **记录 OTA 升级任务**: 在数据库中创建一条新的 OTA 升级任务记录(模型名为 `OTATask`,位于 `internal/infra/models/ota.go`
),包含任务 ID、目标设备、新固件版本、状态例如“待开始”
### 4.2. 发送“准备更新”指令
1. 平台向目标区域主控发送 `PrepareUpdateReq` 消息。
2. **更新任务记录**: 平台发送指令后,更新 OTA 任务记录的状态为“进行中”。
### 4.3. 响应设备文件请求
1. 平台接收区域主控发送的 `RequestFile` 消息。
2. 平台根据 `task_id``filepath` 在内部存储中找到对应的文件内容。
3. 平台构建 `FileResponse` 消息,将文件的完整内容和路径放入其中。
4. 平台通过 LoRa 传输层发送 `FileResponse` 消息。
### 4.4. 处理设备状态上报
1. 平台接收区域主控发送的 `UpdateStatusReport` 消息。
2. 根据报告的 `status` (`SUCCESS``FAILED`),更新 OTA 任务记录的最终状态,并记录 `error_code``error_message`
3. 如果状态为 `SUCCESS`,平台应更新该设备在系统中的固件版本记录。
4. **总超时管理**: 平台为每个 OTA 任务设置一个总的超时时间(例如 2 小时)。如果在总超时时间内未能收到设备的最终状态报告,平台应自动将该任务标记为
`FAILED``error_code` 设为 `ERR_TIMEOUT`
5. **处理重复报告**: 平台在收到最终状态报告后,即使后续再次收到相同的报告,也只需更新一次任务记录,无需重复处理。
## 5. 区域主控侧操作流程 (MicroPython)
### 5.1. 接收“准备更新”指令与版本检查
1. 区域主控接收 `PrepareUpdateReq` 消息。
2. **版本检查**: 设备将 `PrepareUpdateReq` 中的 `version` 与自身当前运行的固件版本进行比较。
* **降级场景**: 如果 `新版本 < 当前版本`,设备立即中止升级,并向平台发送 `UpdateStatusReport` (status: `FAILED`,
error_code: `ERR_VERSION_ROLLBACK`, error_message: "拒绝版本回滚,目标版本低于当前版本")。
* **同版本场景**: 如果 `新版本 == 当前版本`,设备立即中止升级,并向平台发送 `UpdateStatusReport` (status: `SUCCESS`,
error_code: `SUCCESS_ALREADY_UP_TO_DATE`, error_message: "版本已是最新,无需升级")。
* **正常升级场景**: 如果 `新版本 > 当前版本`,继续执行下一步。
3. **清空非活动分区**: 使用 MicroPython 的文件系统操作(例如 `os.remove()``os.rmdir()`),递归删除非活动 OTA 分区(例如
`/ota_b`)下的所有文件和目录。
* **错误处理**: 如果清空分区失败,设备应立即中止,并向平台发送 `UpdateStatusReport` (status: `FAILED`, error_code:
`ERR_PREPARE`, error_message: "清空非活动分区失败: [具体错误]").
4. 设备准备就绪后,将直接开始请求清单文件。
### 5.2. 请求并验证清单文件
1. 设备完成准备后,向平台发送 `RequestFile` 消息,请求清单文件 (`filepath: "/manifest.json"`)。
2. 设备接收平台响应的 `FileResponse` 消息,并将其写入非活动分区(例如 `/ota_b/manifest.json`)。
3. **MD5 校验**: 计算写入的清单文件的 MD5并与 `PrepareUpdateReq` 消息中提供的 `manifest_md5` 进行比对。
4. **解析 JSON**: 解析清单文件内容。
5. **数字签名验证 (未来扩展)**: 使用预置的平台公钥,验证清单文件的数字签名。
6. 如果上述任何步骤失败,设备应向平台发送 `UpdateStatusReport` (status: `FAILED`, error_code: `ERR_MANIFEST_VERIFY`,
error_message: "[具体失败原因]"), 然后中止升级。
### 5.3. 请求与存储固件文件 (逐文件校验)
1. 设备成功接收并验证清单文件后,根据清单文件中的文件列表,**逐个文件**地向平台发送 `RequestFile` 消息。
2. 对于每个请求的文件:
* **请求、接收与写入**: 设备请求文件,接收响应,并根据 `filepath` 将内容写入到非活动 OTA 分区。需要确保目标目录存在,如果不存在则创建。
* **MD5 校验**: 在文件写入完成后,计算该文件的 MD5 校验和,并与清单文件中记录的 MD5 进行比对。
* **错误处理与重试**:
* 如果文件下载超时、写入失败或 MD5 校验失败,设备将进行重试(例如最多 3 次)。
* 如果达到最大重试次数仍失败,设备应立即中止整个 OTA 任务,并向平台发送 `UpdateStatusReport` (status: `FAILED`,
error_code: `ERR_DOWNLOAD``ERR_VERIFY`, error_message: "[具体失败原因]", failed_file: "[失败的文件路径]")。
### 5.4. 自激活与重启
1. **所有文件接收并校验成功后**,设备将自行执行以下操作:
* **配置 OTA 分区**: 使用 MicroPython 提供的 ESP-IDF OTA API设置下一个启动分区为刚刚写入新固件的非活动分区。
* **自触发重启**: 在成功配置 OTA 分区后,区域主控自行触发重启。
### 5.5. 新版本启动与验证
1. 设备重启后,启动加载器会从新的 OTA 分区加载 MicroPython 固件。
2. **自检**: 新固件启动后,应执行必要的自检(如 LoRa 初始化、网络连接等)。
3. **标记有效**: 只有当所有自检项都成功通过后,新固件才必须调用相应的 API例如 `esp.ota_mark_app_valid_cancel_rollback()`
)来标记自身为有效。
4. **看门狗与回滚**:
* 如果新固件在一定次数的尝试后仍未标记自身为有效,启动加载器会自动回滚到上一个有效固件。
* 在 MicroPython 应用层,如果自检失败,**绝不能**标记自身为有效,并应等待底层机制自动触发回滚。
### 5.6. 报告最终状态
1. **成功场景**: 新固件自检成功并标记有效后,向平台发送 `UpdateStatusReport` (status: `SUCCESS`, current_version:
新版本号)。
2. **回滚场景**: 设备回滚到旧版本后,向平台发送 `UpdateStatusReport` (status: `FAILED`, error_code: `ERR_ROLLED_BACK`,
error_message: "新固件启动失败,已自动回滚", current_version: 旧版本号)。
3. **重复发送**: 为了提高在单向 LoRa 通信中的可靠性,设备在发送最终状态报告时,应在短时间内重复发送多次(例如 3-5 次)。
## 6. 关键技术点与注意事项
### 6.1. LoRa 传输层
* 确保 `internal/infra/transport/lora/lora_mesh_uart_passthrough_transport.go` 能稳定处理大尺寸 Protobuf 消息的分片和重组。
### 6.2. 平台侧的请求处理
* `internal/app/listener/lora_listener.go` 在接收 `RequestFile` 消息时,需要高效处理,避免阻塞监听器。
### 6.3. 文件系统操作 (MicroPython)
* 确保文件系统操作(创建目录、写入文件、删除文件)的正确性和鲁棒性,并对错误进行捕获和报告。
### 6.4. MD5 校验 (MicroPython)
* MicroPython 的 `hashlib` 模块提供 MD5 算法。确保计算的效率和准确性。
### 6.5. OTA 分区管理 (MicroPython)
* 熟悉 ESP-IDF 的 OTA 机制在 MicroPython 中的绑定和使用方法。正确调用 API 来设置启动分区和标记应用有效。
### 6.6. 回滚机制
* 完全依赖 ESP-IDF 提供的 OTA 回滚机制。新固件必须在启动后标记自身为有效,否则会自动回滚。
### 6.7. 错误处理与重试
* **设备侧**: 实现文件级别的下载和校验重试。对于无法恢复的错误,立即上报 `FAILED` 状态并中止任务。
* **平台侧**: 实现任务级别的总超时管理。这是处理设备意外断电、失联等情况的关键机制。设备重启后无需保留升级状态,简化了设备端逻辑。
### 6.8. 安全性
* **数字签名**: 强烈建议尽快实现清单文件的数字签名。**没有数字签名OTA 过程将面临严重的安全风险(如中间人攻击)**
,攻击者可能下发恶意固件。平台的公钥需要被硬编码到设备固件中,作为信任的根基。
* **LoRaWAN 安全**: 确保 LoRaWAN 的网络层和应用层密钥管理得当, 防止未经授权的设备加入网络或窃听数据。
---
## 7. 固件 OTA 升级流程描述
### 阶段一:任务准备与下发
1. **上传与准备 (Developer -> Platform)**: 开发者上传固件包平台解压、计算MD5、生成清单文件、创建升级任务。
2. **下发更新通知 (Platform -> Device)**: 平台向设备发送 `PrepareUpdateReq`
### 阶段二:设备版本检查与准备
1. **版本检查 (Device)**:
* **失败分支 (降级/同版本)**: 设备拒绝升级,上报 `FAILED` (ERR_VERSION_ROLLBACK) 或 `SUCCESS` (
SUCCESS_ALREADY_UP_TO_DATE),流程结束。
* **成功分支**: 版本检查通过,设备继续。
2. **设备准备 (Device)**:
* 设备清空非活动分区。
* **失败分支**: 上报 `FAILED` (ERR_PREPARE),流程结束。
* **成功分支**: 设备发送 `RequestFile` 请求清单文件。
### 阶段三:文件循环下载和校验
1. **清单文件传输与校验 (Platform <-> Device)**:
* 平台发送清单文件,设备接收并校验。
* **失败分支**: 上报 `FAILED` (ERR_MANIFEST_VERIFY),流程结束。
2. **固件文件循环 (Device <-> Platform)**:
* 设备逐个请求、下载、校验清单中的所有文件。
* **失败分支 (重试耗尽)**: 上报 `FAILED` (ERR_DOWNLOAD / ERR_VERIFY),流程结束。
### 阶段四:激活与最终状态
1. **激活重启 (Device)**: 所有文件成功下载后,设备配置启动分区并重启。
2. **新固件自检 (Device)**:
* **成功分支**:
* 设备标记自身为有效。
* 设备上报 `SUCCESS`
* 平台更新任务状态为 `SUCCESS`
* **失败分支 (自检失败/未标记)**:
* 设备等待底层机制自动回滚。
* 设备回滚后,上报 `FAILED` (ERR_ROLLED_BACK)。
* 平台更新任务状态为 `FAILED`
3. **总超时检查 (Platform)**: 如果在规定时间内未收到任何最终报告,平台将任务标记为 `FAILED` (ERR_TIMEOUT)。

10824
docs/docs.go Normal file

File diff suppressed because it is too large Load Diff

10798
docs/swagger.json Normal file

File diff suppressed because it is too large Load Diff

6873
docs/swagger.yaml Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1,16 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>猪场管理系统</title>
<script type="module" crossorigin src="/assets/index.cb9d3828.js"></script>
<link rel="stylesheet" href="/assets/index.bcc76856.css">
</head>
<body>
<div id="app"></div>
<!-- Vue应用将挂载到这个div上 -->
</body>
</html>
```

View File

@@ -1,14 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>猪场管理系统</title>
</head>
<body>
<div id="app"></div>
<!-- Vue应用将挂载到这个div上 -->
<script type="module" src="/src/main.js"></script>
</body>
</html>
```

12
frontend/node_modules/.bin/esbuild generated vendored
View File

@@ -1,12 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../esbuild/bin/esbuild" "$@"
else
exec node "$basedir/../esbuild/bin/esbuild" "$@"
fi

View File

@@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\esbuild\bin\esbuild" %*

View File

@@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../esbuild/bin/esbuild" $args
} else {
& "$basedir/node$exe" "$basedir/../esbuild/bin/esbuild" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../esbuild/bin/esbuild" $args
} else {
& "node$exe" "$basedir/../esbuild/bin/esbuild" $args
}
$ret=$LASTEXITCODE
}
exit $ret

12
frontend/node_modules/.bin/nanoid generated vendored
View File

@@ -1,12 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../nanoid/bin/nanoid.cjs" "$@"
else
exec node "$basedir/../nanoid/bin/nanoid.cjs" "$@"
fi

View File

@@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\nanoid\bin\nanoid.cjs" %*

View File

@@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../nanoid/bin/nanoid.cjs" $args
} else {
& "$basedir/node$exe" "$basedir/../nanoid/bin/nanoid.cjs" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../nanoid/bin/nanoid.cjs" $args
} else {
& "node$exe" "$basedir/../nanoid/bin/nanoid.cjs" $args
}
$ret=$LASTEXITCODE
}
exit $ret

12
frontend/node_modules/.bin/parser generated vendored
View File

@@ -1,12 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../@babel/parser/bin/babel-parser.js" "$@"
else
exec node "$basedir/../@babel/parser/bin/babel-parser.js" "$@"
fi

View File

@@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\@babel\parser\bin\babel-parser.js" %*

View File

@@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../@babel/parser/bin/babel-parser.js" $args
} else {
& "$basedir/node$exe" "$basedir/../@babel/parser/bin/babel-parser.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../@babel/parser/bin/babel-parser.js" $args
} else {
& "node$exe" "$basedir/../@babel/parser/bin/babel-parser.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

12
frontend/node_modules/.bin/resolve generated vendored
View File

@@ -1,12 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../resolve/bin/resolve" "$@"
else
exec node "$basedir/../resolve/bin/resolve" "$@"
fi

View File

@@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\resolve\bin\resolve" %*

View File

@@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../resolve/bin/resolve" $args
} else {
& "$basedir/node$exe" "$basedir/../resolve/bin/resolve" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../resolve/bin/resolve" $args
} else {
& "node$exe" "$basedir/../resolve/bin/resolve" $args
}
$ret=$LASTEXITCODE
}
exit $ret

12
frontend/node_modules/.bin/rollup generated vendored
View File

@@ -1,12 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../rollup/dist/bin/rollup" "$@"
else
exec node "$basedir/../rollup/dist/bin/rollup" "$@"
fi

View File

@@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\rollup\dist\bin\rollup" %*

View File

@@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../rollup/dist/bin/rollup" $args
} else {
& "$basedir/node$exe" "$basedir/../rollup/dist/bin/rollup" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../rollup/dist/bin/rollup" $args
} else {
& "node$exe" "$basedir/../rollup/dist/bin/rollup" $args
}
$ret=$LASTEXITCODE
}
exit $ret

12
frontend/node_modules/.bin/vite generated vendored
View File

@@ -1,12 +0,0 @@
#!/bin/sh
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
case `uname` in
*CYGWIN*|*MINGW*|*MSYS*) basedir=`cygpath -w "$basedir"`;;
esac
if [ -x "$basedir/node" ]; then
exec "$basedir/node" "$basedir/../vite/bin/vite.js" "$@"
else
exec node "$basedir/../vite/bin/vite.js" "$@"
fi

17
frontend/node_modules/.bin/vite.cmd generated vendored
View File

@@ -1,17 +0,0 @@
@ECHO off
GOTO start
:find_dp0
SET dp0=%~dp0
EXIT /b
:start
SETLOCAL
CALL :find_dp0
IF EXIST "%dp0%\node.exe" (
SET "_prog=%dp0%\node.exe"
) ELSE (
SET "_prog=node"
SET PATHEXT=%PATHEXT:;.JS;=;%
)
endLocal & goto #_undefined_# 2>NUL || title %COMSPEC% & "%_prog%" "%dp0%\..\vite\bin\vite.js" %*

28
frontend/node_modules/.bin/vite.ps1 generated vendored
View File

@@ -1,28 +0,0 @@
#!/usr/bin/env pwsh
$basedir=Split-Path $MyInvocation.MyCommand.Definition -Parent
$exe=""
if ($PSVersionTable.PSVersion -lt "6.0" -or $IsWindows) {
# Fix case when both the Windows and Linux builds of Node
# are installed in the same directory
$exe=".exe"
}
$ret=0
if (Test-Path "$basedir/node$exe") {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "$basedir/node$exe" "$basedir/../vite/bin/vite.js" $args
} else {
& "$basedir/node$exe" "$basedir/../vite/bin/vite.js" $args
}
$ret=$LASTEXITCODE
} else {
# Support pipeline input
if ($MyInvocation.ExpectingInput) {
$input | & "node$exe" "$basedir/../vite/bin/vite.js" $args
} else {
& "node$exe" "$basedir/../vite/bin/vite.js" $args
}
$ret=$LASTEXITCODE
}
exit $ret

View File

@@ -1,475 +0,0 @@
{
"name": "pig-farm-controller-frontend",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"node_modules/@babel/helper-string-parser": {
"version": "7.27.1",
"resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.27.1",
"resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz",
"integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
"version": "7.28.4",
"resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.28.4.tgz",
"integrity": "sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==",
"dependencies": {
"@babel/types": "^7.28.4"
},
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@babel/types": {
"version": "7.28.4",
"resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.28.4.tgz",
"integrity": "sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5",
"resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="
},
"node_modules/@vitejs/plugin-vue": {
"version": "3.2.0",
"resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-3.2.0.tgz",
"integrity": "sha512-E0tnaL4fr+qkdCNxJ+Xd0yM31UwMkQje76fsDVBBUCoGOUPexu2VDUYHL8P4CwV+zMvWw6nlRw19OnRKmYAJpw==",
"dev": true,
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"peerDependencies": {
"vite": "^3.0.0",
"vue": "^3.2.25"
}
},
"node_modules/@vue/compiler-core": {
"version": "3.5.21",
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.21.tgz",
"integrity": "sha512-8i+LZ0vf6ZgII5Z9XmUvrCyEzocvWT+TeR2VBUVlzIH6Tyv57E20mPZ1bCS+tbejgUgmjrEh7q/0F0bibskAmw==",
"dependencies": {
"@babel/parser": "^7.28.3",
"@vue/shared": "3.5.21",
"entities": "^4.5.0",
"estree-walker": "^2.0.2",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-dom": {
"version": "3.5.21",
"resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.21.tgz",
"integrity": "sha512-jNtbu/u97wiyEBJlJ9kmdw7tAr5Vy0Aj5CgQmo+6pxWNQhXZDPsRr1UWPN4v3Zf82s2H3kF51IbzZ4jMWAgPlQ==",
"dependencies": {
"@vue/compiler-core": "3.5.21",
"@vue/shared": "3.5.21"
}
},
"node_modules/@vue/compiler-sfc": {
"version": "3.5.21",
"resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.21.tgz",
"integrity": "sha512-SXlyk6I5eUGBd2v8Ie7tF6ADHE9kCR6mBEuPyH1nUZ0h6Xx6nZI29i12sJKQmzbDyr2tUHMhhTt51Z6blbkTTQ==",
"dependencies": {
"@babel/parser": "^7.28.3",
"@vue/compiler-core": "3.5.21",
"@vue/compiler-dom": "3.5.21",
"@vue/compiler-ssr": "3.5.21",
"@vue/shared": "3.5.21",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.18",
"postcss": "^8.5.6",
"source-map-js": "^1.2.1"
}
},
"node_modules/@vue/compiler-ssr": {
"version": "3.5.21",
"resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.21.tgz",
"integrity": "sha512-vKQ5olH5edFZdf5ZrlEgSO1j1DMA4u23TVK5XR1uMhvwnYvVdDF0nHXJUblL/GvzlShQbjhZZ2uvYmDlAbgo9w==",
"dependencies": {
"@vue/compiler-dom": "3.5.21",
"@vue/shared": "3.5.21"
}
},
"node_modules/@vue/devtools-api": {
"version": "6.6.4",
"resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
"integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="
},
"node_modules/@vue/reactivity": {
"version": "3.5.21",
"resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.21.tgz",
"integrity": "sha512-3ah7sa+Cwr9iiYEERt9JfZKPw4A2UlbY8RbbnH2mGCE8NwHkhmlZt2VsH0oDA3P08X3jJd29ohBDtX+TbD9AsA==",
"dependencies": {
"@vue/shared": "3.5.21"
}
},
"node_modules/@vue/runtime-core": {
"version": "3.5.21",
"resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.21.tgz",
"integrity": "sha512-+DplQlRS4MXfIf9gfD1BOJpk5RSyGgGXD/R+cumhe8jdjUcq/qlxDawQlSI8hCKupBlvM+3eS1se5xW+SuNAwA==",
"dependencies": {
"@vue/reactivity": "3.5.21",
"@vue/shared": "3.5.21"
}
},
"node_modules/@vue/runtime-dom": {
"version": "3.5.21",
"resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.21.tgz",
"integrity": "sha512-3M2DZsOFwM5qI15wrMmNF5RJe1+ARijt2HM3TbzBbPSuBHOQpoidE+Pa+XEaVN+czbHf81ETRoG1ltztP2em8w==",
"dependencies": {
"@vue/reactivity": "3.5.21",
"@vue/runtime-core": "3.5.21",
"@vue/shared": "3.5.21",
"csstype": "^3.1.3"
}
},
"node_modules/@vue/server-renderer": {
"version": "3.5.21",
"resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.21.tgz",
"integrity": "sha512-qr8AqgD3DJPJcGvLcJKQo2tAc8OnXRcfxhOJCPF+fcfn5bBGz7VCcO7t+qETOPxpWK1mgysXvVT/j+xWaHeMWA==",
"dependencies": {
"@vue/compiler-ssr": "3.5.21",
"@vue/shared": "3.5.21"
},
"peerDependencies": {
"vue": "3.5.21"
}
},
"node_modules/@vue/shared": {
"version": "3.5.21",
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.21.tgz",
"integrity": "sha512-+2k1EQpnYuVuu3N7atWyG3/xoFWIVJZq4Mz8XNOdScFI0etES75fbny/oU4lKWk/577P1zmg0ioYvpGEDZ3DLw=="
},
"node_modules/csstype": {
"version": "3.1.3",
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/esbuild": {
"version": "0.15.18",
"resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.15.18.tgz",
"integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==",
"dev": true,
"hasInstallScript": true,
"bin": {
"esbuild": "bin/esbuild"
},
"engines": {
"node": ">=12"
},
"optionalDependencies": {
"@esbuild/android-arm": "0.15.18",
"@esbuild/linux-loong64": "0.15.18",
"esbuild-android-64": "0.15.18",
"esbuild-android-arm64": "0.15.18",
"esbuild-darwin-64": "0.15.18",
"esbuild-darwin-arm64": "0.15.18",
"esbuild-freebsd-64": "0.15.18",
"esbuild-freebsd-arm64": "0.15.18",
"esbuild-linux-32": "0.15.18",
"esbuild-linux-64": "0.15.18",
"esbuild-linux-arm": "0.15.18",
"esbuild-linux-arm64": "0.15.18",
"esbuild-linux-mips64le": "0.15.18",
"esbuild-linux-ppc64le": "0.15.18",
"esbuild-linux-riscv64": "0.15.18",
"esbuild-linux-s390x": "0.15.18",
"esbuild-netbsd-64": "0.15.18",
"esbuild-openbsd-64": "0.15.18",
"esbuild-sunos-64": "0.15.18",
"esbuild-windows-32": "0.15.18",
"esbuild-windows-64": "0.15.18",
"esbuild-windows-arm64": "0.15.18"
}
},
"node_modules/esbuild-windows-64": {
"version": "0.15.18",
"resolved": "https://registry.npmmirror.com/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz",
"integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==",
"cpu": [
"x64"
],
"dev": true,
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">=12"
}
},
"node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"dev": true,
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"dev": true,
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/is-core-module": {
"version": "2.16.1",
"resolved": "https://registry.npmmirror.com/is-core-module/-/is-core-module-2.16.1.tgz",
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
"dev": true,
"dependencies": {
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/magic-string": {
"version": "0.30.18",
"resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.18.tgz",
"integrity": "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmmirror.com/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
},
"node_modules/postcss": {
"version": "8.5.6",
"resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz",
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/resolve": {
"version": "1.22.10",
"resolved": "https://registry.npmmirror.com/resolve/-/resolve-1.22.10.tgz",
"integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==",
"dev": true,
"dependencies": {
"is-core-module": "^2.16.0",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
"bin": {
"resolve": "bin/resolve"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/rollup": {
"version": "2.79.2",
"resolved": "https://registry.npmmirror.com/rollup/-/rollup-2.79.2.tgz",
"integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==",
"dev": true,
"bin": {
"rollup": "dist/bin/rollup"
},
"engines": {
"node": ">=10.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"dev": true,
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/vite": {
"version": "3.2.11",
"resolved": "https://registry.npmmirror.com/vite/-/vite-3.2.11.tgz",
"integrity": "sha512-K/jGKL/PgbIgKCiJo5QbASQhFiV02X9Jh+Qq0AKCRCRKZtOTVi4t6wh75FDpGf2N9rYOnzH87OEFQNaFy6pdxQ==",
"dev": true,
"dependencies": {
"esbuild": "^0.15.9",
"postcss": "^8.4.18",
"resolve": "^1.22.1",
"rollup": "^2.79.1"
},
"bin": {
"vite": "bin/vite.js"
},
"engines": {
"node": "^14.18.0 || >=16.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
},
"peerDependencies": {
"@types/node": ">= 14",
"less": "*",
"sass": "*",
"stylus": "*",
"sugarss": "*",
"terser": "^5.4.0"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
},
"less": {
"optional": true
},
"sass": {
"optional": true
},
"stylus": {
"optional": true
},
"sugarss": {
"optional": true
},
"terser": {
"optional": true
}
}
},
"node_modules/vue": {
"version": "3.5.21",
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.21.tgz",
"integrity": "sha512-xxf9rum9KtOdwdRkiApWL+9hZEMWE90FHh8yS1+KJAiWYh+iGWV1FquPjoO9VUHQ+VIhsCXNNyZ5Sf4++RVZBA==",
"dependencies": {
"@vue/compiler-dom": "3.5.21",
"@vue/compiler-sfc": "3.5.21",
"@vue/runtime-dom": "3.5.21",
"@vue/server-renderer": "3.5.21",
"@vue/shared": "3.5.21"
},
"peerDependencies": {
"typescript": "*"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/vue-router": {
"version": "4.5.1",
"resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-4.5.1.tgz",
"integrity": "sha512-ogAF3P97NPm8fJsE4by9dwSYtDwXIY1nFY9T6DyQnGHd1E2Da94w9JIolpe42LJGIl0DwOHBi8TcRPlPGwbTtw==",
"dependencies": {
"@vue/devtools-api": "^6.6.4"
},
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
"vue": "^3.2.0"
}
}
}
}

View File

@@ -1,23 +0,0 @@
{
"hash": "dd8a32db",
"browserHash": "91f1c2e1",
"optimized": {
"vue": {
"src": "../../vue/dist/vue.runtime.esm-bundler.js",
"file": "vue.js",
"fileHash": "6d85718e",
"needsInterop": false
},
"vue-router": {
"src": "../../vue-router/dist/vue-router.mjs",
"file": "vue-router.js",
"fileHash": "1e430545",
"needsInterop": false
}
},
"chunks": {
"chunk-DB3RJHEA": {
"file": "chunk-DB3RJHEA.js"
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
{"type":"module"}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,343 +0,0 @@
import {
BaseTransition,
BaseTransitionPropsValidators,
Comment,
DeprecationTypes,
EffectScope,
ErrorCodes,
ErrorTypeStrings,
Fragment,
KeepAlive,
ReactiveEffect,
Static,
Suspense,
Teleport,
Text,
TrackOpTypes,
Transition,
TransitionGroup,
TriggerOpTypes,
VueElement,
assertNumber,
callWithAsyncErrorHandling,
callWithErrorHandling,
camelize,
capitalize,
cloneVNode,
compatUtils,
compile,
computed,
createApp,
createBaseVNode,
createBlock,
createCommentVNode,
createElementBlock,
createHydrationRenderer,
createPropsRestProxy,
createRenderer,
createSSRApp,
createSlots,
createStaticVNode,
createTextVNode,
createVNode,
customRef,
defineAsyncComponent,
defineComponent,
defineCustomElement,
defineEmits,
defineExpose,
defineModel,
defineOptions,
defineProps,
defineSSRCustomElement,
defineSlots,
devtools,
effect,
effectScope,
getCurrentInstance,
getCurrentScope,
getCurrentWatcher,
getTransitionRawChildren,
guardReactiveProps,
h,
handleError,
hasInjectionContext,
hydrate,
hydrateOnIdle,
hydrateOnInteraction,
hydrateOnMediaQuery,
hydrateOnVisible,
initCustomFormatter,
initDirectivesForSSR,
inject,
isMemoSame,
isProxy,
isReactive,
isReadonly,
isRef,
isRuntimeOnly,
isShallow,
isVNode,
markRaw,
mergeDefaults,
mergeModels,
mergeProps,
nextTick,
normalizeClass,
normalizeProps,
normalizeStyle,
onActivated,
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
onDeactivated,
onErrorCaptured,
onMounted,
onRenderTracked,
onRenderTriggered,
onScopeDispose,
onServerPrefetch,
onUnmounted,
onUpdated,
onWatcherCleanup,
openBlock,
popScopeId,
provide,
proxyRefs,
pushScopeId,
queuePostFlushCb,
reactive,
readonly,
ref,
registerRuntimeCompiler,
render,
renderList,
renderSlot,
resolveComponent,
resolveDirective,
resolveDynamicComponent,
resolveFilter,
resolveTransitionHooks,
setBlockTracking,
setDevtoolsHook,
setTransitionHooks,
shallowReactive,
shallowReadonly,
shallowRef,
ssrContextKey,
ssrUtils,
stop,
toDisplayString,
toHandlerKey,
toHandlers,
toRaw,
toRef,
toRefs,
toValue,
transformVNodeArgs,
triggerRef,
unref,
useAttrs,
useCssModule,
useCssVars,
useHost,
useId,
useModel,
useSSRContext,
useShadowRoot,
useSlots,
useTemplateRef,
useTransitionState,
vModelCheckbox,
vModelDynamic,
vModelRadio,
vModelSelect,
vModelText,
vShow,
version,
warn,
watch,
watchEffect,
watchPostEffect,
watchSyncEffect,
withAsyncContext,
withCtx,
withDefaults,
withDirectives,
withKeys,
withMemo,
withModifiers,
withScopeId
} from "./chunk-DB3RJHEA.js";
export {
BaseTransition,
BaseTransitionPropsValidators,
Comment,
DeprecationTypes,
EffectScope,
ErrorCodes,
ErrorTypeStrings,
Fragment,
KeepAlive,
ReactiveEffect,
Static,
Suspense,
Teleport,
Text,
TrackOpTypes,
Transition,
TransitionGroup,
TriggerOpTypes,
VueElement,
assertNumber,
callWithAsyncErrorHandling,
callWithErrorHandling,
camelize,
capitalize,
cloneVNode,
compatUtils,
compile,
computed,
createApp,
createBlock,
createCommentVNode,
createElementBlock,
createBaseVNode as createElementVNode,
createHydrationRenderer,
createPropsRestProxy,
createRenderer,
createSSRApp,
createSlots,
createStaticVNode,
createTextVNode,
createVNode,
customRef,
defineAsyncComponent,
defineComponent,
defineCustomElement,
defineEmits,
defineExpose,
defineModel,
defineOptions,
defineProps,
defineSSRCustomElement,
defineSlots,
devtools,
effect,
effectScope,
getCurrentInstance,
getCurrentScope,
getCurrentWatcher,
getTransitionRawChildren,
guardReactiveProps,
h,
handleError,
hasInjectionContext,
hydrate,
hydrateOnIdle,
hydrateOnInteraction,
hydrateOnMediaQuery,
hydrateOnVisible,
initCustomFormatter,
initDirectivesForSSR,
inject,
isMemoSame,
isProxy,
isReactive,
isReadonly,
isRef,
isRuntimeOnly,
isShallow,
isVNode,
markRaw,
mergeDefaults,
mergeModels,
mergeProps,
nextTick,
normalizeClass,
normalizeProps,
normalizeStyle,
onActivated,
onBeforeMount,
onBeforeUnmount,
onBeforeUpdate,
onDeactivated,
onErrorCaptured,
onMounted,
onRenderTracked,
onRenderTriggered,
onScopeDispose,
onServerPrefetch,
onUnmounted,
onUpdated,
onWatcherCleanup,
openBlock,
popScopeId,
provide,
proxyRefs,
pushScopeId,
queuePostFlushCb,
reactive,
readonly,
ref,
registerRuntimeCompiler,
render,
renderList,
renderSlot,
resolveComponent,
resolveDirective,
resolveDynamicComponent,
resolveFilter,
resolveTransitionHooks,
setBlockTracking,
setDevtoolsHook,
setTransitionHooks,
shallowReactive,
shallowReadonly,
shallowRef,
ssrContextKey,
ssrUtils,
stop,
toDisplayString,
toHandlerKey,
toHandlers,
toRaw,
toRef,
toRefs,
toValue,
transformVNodeArgs,
triggerRef,
unref,
useAttrs,
useCssModule,
useCssVars,
useHost,
useId,
useModel,
useSSRContext,
useShadowRoot,
useSlots,
useTemplateRef,
useTransitionState,
vModelCheckbox,
vModelDynamic,
vModelRadio,
vModelSelect,
vModelText,
vShow,
version,
warn,
watch,
watchEffect,
watchPostEffect,
watchSyncEffect,
withAsyncContext,
withCtx,
withDefaults,
withDirectives,
withKeys,
withMemo,
withModifiers,
withScopeId
};
//# sourceMappingURL=vue.js.map

View File

@@ -1,7 +0,0 @@
{
"version": 3,
"sources": [],
"sourcesContent": [],
"mappings": "",
"names": []
}

View File

@@ -1,22 +0,0 @@
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,19 +0,0 @@
# @babel/helper-string-parser
> A utility package to parse strings
See our website [@babel/helper-string-parser](https://babeljs.io/docs/babel-helper-string-parser) for more information.
## Install
Using npm:
```sh
npm install --save @babel/helper-string-parser
```
or using yarn:
```sh
yarn add @babel/helper-string-parser
```

View File

@@ -1,295 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.readCodePoint = readCodePoint;
exports.readInt = readInt;
exports.readStringContents = readStringContents;
var _isDigit = function isDigit(code) {
return code >= 48 && code <= 57;
};
const forbiddenNumericSeparatorSiblings = {
decBinOct: new Set([46, 66, 69, 79, 95, 98, 101, 111]),
hex: new Set([46, 88, 95, 120])
};
const isAllowedNumericSeparatorSibling = {
bin: ch => ch === 48 || ch === 49,
oct: ch => ch >= 48 && ch <= 55,
dec: ch => ch >= 48 && ch <= 57,
hex: ch => ch >= 48 && ch <= 57 || ch >= 65 && ch <= 70 || ch >= 97 && ch <= 102
};
function readStringContents(type, input, pos, lineStart, curLine, errors) {
const initialPos = pos;
const initialLineStart = lineStart;
const initialCurLine = curLine;
let out = "";
let firstInvalidLoc = null;
let chunkStart = pos;
const {
length
} = input;
for (;;) {
if (pos >= length) {
errors.unterminated(initialPos, initialLineStart, initialCurLine);
out += input.slice(chunkStart, pos);
break;
}
const ch = input.charCodeAt(pos);
if (isStringEnd(type, ch, input, pos)) {
out += input.slice(chunkStart, pos);
break;
}
if (ch === 92) {
out += input.slice(chunkStart, pos);
const res = readEscapedChar(input, pos, lineStart, curLine, type === "template", errors);
if (res.ch === null && !firstInvalidLoc) {
firstInvalidLoc = {
pos,
lineStart,
curLine
};
} else {
out += res.ch;
}
({
pos,
lineStart,
curLine
} = res);
chunkStart = pos;
} else if (ch === 8232 || ch === 8233) {
++pos;
++curLine;
lineStart = pos;
} else if (ch === 10 || ch === 13) {
if (type === "template") {
out += input.slice(chunkStart, pos) + "\n";
++pos;
if (ch === 13 && input.charCodeAt(pos) === 10) {
++pos;
}
++curLine;
chunkStart = lineStart = pos;
} else {
errors.unterminated(initialPos, initialLineStart, initialCurLine);
}
} else {
++pos;
}
}
return {
pos,
str: out,
firstInvalidLoc,
lineStart,
curLine,
containsInvalid: !!firstInvalidLoc
};
}
function isStringEnd(type, ch, input, pos) {
if (type === "template") {
return ch === 96 || ch === 36 && input.charCodeAt(pos + 1) === 123;
}
return ch === (type === "double" ? 34 : 39);
}
function readEscapedChar(input, pos, lineStart, curLine, inTemplate, errors) {
const throwOnInvalid = !inTemplate;
pos++;
const res = ch => ({
pos,
ch,
lineStart,
curLine
});
const ch = input.charCodeAt(pos++);
switch (ch) {
case 110:
return res("\n");
case 114:
return res("\r");
case 120:
{
let code;
({
code,
pos
} = readHexChar(input, pos, lineStart, curLine, 2, false, throwOnInvalid, errors));
return res(code === null ? null : String.fromCharCode(code));
}
case 117:
{
let code;
({
code,
pos
} = readCodePoint(input, pos, lineStart, curLine, throwOnInvalid, errors));
return res(code === null ? null : String.fromCodePoint(code));
}
case 116:
return res("\t");
case 98:
return res("\b");
case 118:
return res("\u000b");
case 102:
return res("\f");
case 13:
if (input.charCodeAt(pos) === 10) {
++pos;
}
case 10:
lineStart = pos;
++curLine;
case 8232:
case 8233:
return res("");
case 56:
case 57:
if (inTemplate) {
return res(null);
} else {
errors.strictNumericEscape(pos - 1, lineStart, curLine);
}
default:
if (ch >= 48 && ch <= 55) {
const startPos = pos - 1;
const match = /^[0-7]+/.exec(input.slice(startPos, pos + 2));
let octalStr = match[0];
let octal = parseInt(octalStr, 8);
if (octal > 255) {
octalStr = octalStr.slice(0, -1);
octal = parseInt(octalStr, 8);
}
pos += octalStr.length - 1;
const next = input.charCodeAt(pos);
if (octalStr !== "0" || next === 56 || next === 57) {
if (inTemplate) {
return res(null);
} else {
errors.strictNumericEscape(startPos, lineStart, curLine);
}
}
return res(String.fromCharCode(octal));
}
return res(String.fromCharCode(ch));
}
}
function readHexChar(input, pos, lineStart, curLine, len, forceLen, throwOnInvalid, errors) {
const initialPos = pos;
let n;
({
n,
pos
} = readInt(input, pos, lineStart, curLine, 16, len, forceLen, false, errors, !throwOnInvalid));
if (n === null) {
if (throwOnInvalid) {
errors.invalidEscapeSequence(initialPos, lineStart, curLine);
} else {
pos = initialPos - 1;
}
}
return {
code: n,
pos
};
}
function readInt(input, pos, lineStart, curLine, radix, len, forceLen, allowNumSeparator, errors, bailOnError) {
const start = pos;
const forbiddenSiblings = radix === 16 ? forbiddenNumericSeparatorSiblings.hex : forbiddenNumericSeparatorSiblings.decBinOct;
const isAllowedSibling = radix === 16 ? isAllowedNumericSeparatorSibling.hex : radix === 10 ? isAllowedNumericSeparatorSibling.dec : radix === 8 ? isAllowedNumericSeparatorSibling.oct : isAllowedNumericSeparatorSibling.bin;
let invalid = false;
let total = 0;
for (let i = 0, e = len == null ? Infinity : len; i < e; ++i) {
const code = input.charCodeAt(pos);
let val;
if (code === 95 && allowNumSeparator !== "bail") {
const prev = input.charCodeAt(pos - 1);
const next = input.charCodeAt(pos + 1);
if (!allowNumSeparator) {
if (bailOnError) return {
n: null,
pos
};
errors.numericSeparatorInEscapeSequence(pos, lineStart, curLine);
} else if (Number.isNaN(next) || !isAllowedSibling(next) || forbiddenSiblings.has(prev) || forbiddenSiblings.has(next)) {
if (bailOnError) return {
n: null,
pos
};
errors.unexpectedNumericSeparator(pos, lineStart, curLine);
}
++pos;
continue;
}
if (code >= 97) {
val = code - 97 + 10;
} else if (code >= 65) {
val = code - 65 + 10;
} else if (_isDigit(code)) {
val = code - 48;
} else {
val = Infinity;
}
if (val >= radix) {
if (val <= 9 && bailOnError) {
return {
n: null,
pos
};
} else if (val <= 9 && errors.invalidDigit(pos, lineStart, curLine, radix)) {
val = 0;
} else if (forceLen) {
val = 0;
invalid = true;
} else {
break;
}
}
++pos;
total = total * radix + val;
}
if (pos === start || len != null && pos - start !== len || invalid) {
return {
n: null,
pos
};
}
return {
n: total,
pos
};
}
function readCodePoint(input, pos, lineStart, curLine, throwOnInvalid, errors) {
const ch = input.charCodeAt(pos);
let code;
if (ch === 123) {
++pos;
({
code,
pos
} = readHexChar(input, pos, lineStart, curLine, input.indexOf("}", pos) - pos, true, throwOnInvalid, errors));
++pos;
if (code !== null && code > 0x10ffff) {
if (throwOnInvalid) {
errors.invalidCodePoint(pos, lineStart, curLine);
} else {
return {
code: null,
pos
};
}
}
} else {
({
code,
pos
} = readHexChar(input, pos, lineStart, curLine, 4, false, throwOnInvalid, errors));
}
return {
code,
pos
};
}
//# sourceMappingURL=index.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,31 +0,0 @@
{
"name": "@babel/helper-string-parser",
"version": "7.27.1",
"description": "A utility package to parse strings",
"repository": {
"type": "git",
"url": "https://github.com/babel/babel.git",
"directory": "packages/babel-helper-string-parser"
},
"homepage": "https://babel.dev/docs/en/next/babel-helper-string-parser",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"main": "./lib/index.js",
"devDependencies": {
"charcodes": "^0.2.0"
},
"engines": {
"node": ">=6.9.0"
},
"author": "The Babel Team (https://babel.dev/team)",
"exports": {
".": {
"types": "./lib/index.d.ts",
"default": "./lib/index.js"
},
"./package.json": "./package.json"
},
"type": "commonjs"
}

View File

@@ -1,22 +0,0 @@
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,19 +0,0 @@
# @babel/helper-validator-identifier
> Validate identifier/keywords name
See our website [@babel/helper-validator-identifier](https://babeljs.io/docs/babel-helper-validator-identifier) for more information.
## Install
Using npm:
```sh
npm install --save @babel/helper-validator-identifier
```
or using yarn:
```sh
yarn add @babel/helper-validator-identifier
```

View File

@@ -1,70 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.isIdentifierChar = isIdentifierChar;
exports.isIdentifierName = isIdentifierName;
exports.isIdentifierStart = isIdentifierStart;
let nonASCIIidentifierStartChars = "\xaa\xb5\xba\xc0-\xd6\xd8-\xf6\xf8-\u02c1\u02c6-\u02d1\u02e0-\u02e4\u02ec\u02ee\u0370-\u0374\u0376\u0377\u037a-\u037d\u037f\u0386\u0388-\u038a\u038c\u038e-\u03a1\u03a3-\u03f5\u03f7-\u0481\u048a-\u052f\u0531-\u0556\u0559\u0560-\u0588\u05d0-\u05ea\u05ef-\u05f2\u0620-\u064a\u066e\u066f\u0671-\u06d3\u06d5\u06e5\u06e6\u06ee\u06ef\u06fa-\u06fc\u06ff\u0710\u0712-\u072f\u074d-\u07a5\u07b1\u07ca-\u07ea\u07f4\u07f5\u07fa\u0800-\u0815\u081a\u0824\u0828\u0840-\u0858\u0860-\u086a\u0870-\u0887\u0889-\u088e\u08a0-\u08c9\u0904-\u0939\u093d\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098c\u098f\u0990\u0993-\u09a8\u09aa-\u09b0\u09b2\u09b6-\u09b9\u09bd\u09ce\u09dc\u09dd\u09df-\u09e1\u09f0\u09f1\u09fc\u0a05-\u0a0a\u0a0f\u0a10\u0a13-\u0a28\u0a2a-\u0a30\u0a32\u0a33\u0a35\u0a36\u0a38\u0a39\u0a59-\u0a5c\u0a5e\u0a72-\u0a74\u0a85-\u0a8d\u0a8f-\u0a91\u0a93-\u0aa8\u0aaa-\u0ab0\u0ab2\u0ab3\u0ab5-\u0ab9\u0abd\u0ad0\u0ae0\u0ae1\u0af9\u0b05-\u0b0c\u0b0f\u0b10\u0b13-\u0b28\u0b2a-\u0b30\u0b32\u0b33\u0b35-\u0b39\u0b3d\u0b5c\u0b5d\u0b5f-\u0b61\u0b71\u0b83\u0b85-\u0b8a\u0b8e-\u0b90\u0b92-\u0b95\u0b99\u0b9a\u0b9c\u0b9e\u0b9f\u0ba3\u0ba4\u0ba8-\u0baa\u0bae-\u0bb9\u0bd0\u0c05-\u0c0c\u0c0e-\u0c10\u0c12-\u0c28\u0c2a-\u0c39\u0c3d\u0c58-\u0c5a\u0c5d\u0c60\u0c61\u0c80\u0c85-\u0c8c\u0c8e-\u0c90\u0c92-\u0ca8\u0caa-\u0cb3\u0cb5-\u0cb9\u0cbd\u0cdd\u0cde\u0ce0\u0ce1\u0cf1\u0cf2\u0d04-\u0d0c\u0d0e-\u0d10\u0d12-\u0d3a\u0d3d\u0d4e\u0d54-\u0d56\u0d5f-\u0d61\u0d7a-\u0d7f\u0d85-\u0d96\u0d9a-\u0db1\u0db3-\u0dbb\u0dbd\u0dc0-\u0dc6\u0e01-\u0e30\u0e32\u0e33\u0e40-\u0e46\u0e81\u0e82\u0e84\u0e86-\u0e8a\u0e8c-\u0ea3\u0ea5\u0ea7-\u0eb0\u0eb2\u0eb3\u0ebd\u0ec0-\u0ec4\u0ec6\u0edc-\u0edf\u0f00\u0f40-\u0f47\u0f49-\u0f6c\u0f88-\u0f8c\u1000-\u102a\u103f\u1050-\u1055\u105a-\u105d\u1061\u1065\u1066\u106e-\u1070\u1075-\u1081\u108e\u10a0-\u10c5\u10c7\u10cd\u10d0-\u10fa\u10fc-\u1248\u124a-\u124d\u1250-\u1256\u1258\u125a-\u125d\u1260-\u1288\u128a-\u128d\u1290-\u12b0\u12b2-\u12b5\u12b8-\u12be\u12c0\u12c2-\u12c5\u12c8-\u12d6\u12d8-\u1310\u1312-\u1315\u1318-\u135a\u1380-\u138f\u13a0-\u13f5\u13f8-\u13fd\u1401-\u166c\u166f-\u167f\u1681-\u169a\u16a0-\u16ea\u16ee-\u16f8\u1700-\u1711\u171f-\u1731\u1740-\u1751\u1760-\u176c\u176e-\u1770\u1780-\u17b3\u17d7\u17dc\u1820-\u1878\u1880-\u18a8\u18aa\u18b0-\u18f5\u1900-\u191e\u1950-\u196d\u1970-\u1974\u1980-\u19ab\u19b0-\u19c9\u1a00-\u1a16\u1a20-\u1a54\u1aa7\u1b05-\u1b33\u1b45-\u1b4c\u1b83-\u1ba0\u1bae\u1baf\u1bba-\u1be5\u1c00-\u1c23\u1c4d-\u1c4f\u1c5a-\u1c7d\u1c80-\u1c8a\u1c90-\u1cba\u1cbd-\u1cbf\u1ce9-\u1cec\u1cee-\u1cf3\u1cf5\u1cf6\u1cfa\u1d00-\u1dbf\u1e00-\u1f15\u1f18-\u1f1d\u1f20-\u1f45\u1f48-\u1f4d\u1f50-\u1f57\u1f59\u1f5b\u1f5d\u1f5f-\u1f7d\u1f80-\u1fb4\u1fb6-\u1fbc\u1fbe\u1fc2-\u1fc4\u1fc6-\u1fcc\u1fd0-\u1fd3\u1fd6-\u1fdb\u1fe0-\u1fec\u1ff2-\u1ff4\u1ff6-\u1ffc\u2071\u207f\u2090-\u209c\u2102\u2107\u210a-\u2113\u2115\u2118-\u211d\u2124\u2126\u2128\u212a-\u2139\u213c-\u213f\u2145-\u2149\u214e\u2160-\u2188\u2c00-\u2ce4\u2ceb-\u2cee\u2cf2\u2cf3\u2d00-\u2d25\u2d27\u2d2d\u2d30-\u2d67\u2d6f\u2d80-\u2d96\u2da0-\u2da6\u2da8-\u2dae\u2db0-\u2db6\u2db8-\u2dbe\u2dc0-\u2dc6\u2dc8-\u2dce\u2dd0-\u2dd6\u2dd8-\u2dde\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303c\u3041-\u3096\u309b-\u309f\u30a1-\u30fa\u30fc-\u30ff\u3105-\u312f\u3131-\u318e\u31a0-\u31bf\u31f0-\u31ff\u3400-\u4dbf\u4e00-\ua48c\ua4d0-\ua4fd\ua500-\ua60c\ua610-\ua61f\ua62a\ua62b\ua640-\ua66e\ua67f-\ua69d\ua6a0-\ua6ef\ua717-\ua71f\ua722-\ua788\ua78b-\ua7cd\ua7d0\ua7d1\ua7d3\ua7d5-\ua7dc\ua7f2-\ua801\ua803-\ua805\ua807-\ua80a\ua80c-\ua822\ua840-\ua873\ua882-\ua8b3\ua8f2-\ua8f7\ua8fb\ua8fd\ua8fe\ua90a-\ua925\ua930-\ua946\ua960-\ua97c\ua984-\ua9b2\ua9cf\ua9e0-\ua9e4\ua9e6-\ua9ef\ua9fa-\ua9fe\uaa00-\uaa28\uaa40-\uaa42\uaa44-\uaa4b\uaa60-\uaa76\uaa7a\uaa7e-\uaaaf\uaab1\uaab5\uaab6\uaab9-\uaabd\uaac0\uaac2\uaadb-\uaadd\uaae0-\uaaea\uaaf2-\uaaf4\uab01-\uab06\uab09-\uab0e\uab11-\uab16\uab20-\uab26\uab28-\uab2e\uab30-\uab5a\uab5c-\uab69\uab70-\uabe2\uac00-\ud7a3\ud7b0-\ud7c6\ud7cb-\ud7fb\uf900-\ufa6d\ufa70-\ufad9\ufb00-\ufb06\ufb13-\ufb17\ufb1d\ufb1f-\ufb28\ufb2a-\ufb36\ufb38-\ufb3c\ufb3e\ufb40\ufb41\ufb43\ufb44\ufb46-\ufbb1\ufbd3-\ufd3d\ufd50-\ufd8f\ufd92-\ufdc7\ufdf0-\ufdfb\ufe70-\ufe74\ufe76-\ufefc\uff21-\uff3a\uff41-\uff5a\uff66-\uffbe\uffc2-\uffc7\uffca-\uffcf\uffd2-\uffd7\uffda-\uffdc";
let nonASCIIidentifierChars = "\xb7\u0300-\u036f\u0387\u0483-\u0487\u0591-\u05bd\u05bf\u05c1\u05c2\u05c4\u05c5\u05c7\u0610-\u061a\u064b-\u0669\u0670\u06d6-\u06dc\u06df-\u06e4\u06e7\u06e8\u06ea-\u06ed\u06f0-\u06f9\u0711\u0730-\u074a\u07a6-\u07b0\u07c0-\u07c9\u07eb-\u07f3\u07fd\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0859-\u085b\u0897-\u089f\u08ca-\u08e1\u08e3-\u0903\u093a-\u093c\u093e-\u094f\u0951-\u0957\u0962\u0963\u0966-\u096f\u0981-\u0983\u09bc\u09be-\u09c4\u09c7\u09c8\u09cb-\u09cd\u09d7\u09e2\u09e3\u09e6-\u09ef\u09fe\u0a01-\u0a03\u0a3c\u0a3e-\u0a42\u0a47\u0a48\u0a4b-\u0a4d\u0a51\u0a66-\u0a71\u0a75\u0a81-\u0a83\u0abc\u0abe-\u0ac5\u0ac7-\u0ac9\u0acb-\u0acd\u0ae2\u0ae3\u0ae6-\u0aef\u0afa-\u0aff\u0b01-\u0b03\u0b3c\u0b3e-\u0b44\u0b47\u0b48\u0b4b-\u0b4d\u0b55-\u0b57\u0b62\u0b63\u0b66-\u0b6f\u0b82\u0bbe-\u0bc2\u0bc6-\u0bc8\u0bca-\u0bcd\u0bd7\u0be6-\u0bef\u0c00-\u0c04\u0c3c\u0c3e-\u0c44\u0c46-\u0c48\u0c4a-\u0c4d\u0c55\u0c56\u0c62\u0c63\u0c66-\u0c6f\u0c81-\u0c83\u0cbc\u0cbe-\u0cc4\u0cc6-\u0cc8\u0cca-\u0ccd\u0cd5\u0cd6\u0ce2\u0ce3\u0ce6-\u0cef\u0cf3\u0d00-\u0d03\u0d3b\u0d3c\u0d3e-\u0d44\u0d46-\u0d48\u0d4a-\u0d4d\u0d57\u0d62\u0d63\u0d66-\u0d6f\u0d81-\u0d83\u0dca\u0dcf-\u0dd4\u0dd6\u0dd8-\u0ddf\u0de6-\u0def\u0df2\u0df3\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0e50-\u0e59\u0eb1\u0eb4-\u0ebc\u0ec8-\u0ece\u0ed0-\u0ed9\u0f18\u0f19\u0f20-\u0f29\u0f35\u0f37\u0f39\u0f3e\u0f3f\u0f71-\u0f84\u0f86\u0f87\u0f8d-\u0f97\u0f99-\u0fbc\u0fc6\u102b-\u103e\u1040-\u1049\u1056-\u1059\u105e-\u1060\u1062-\u1064\u1067-\u106d\u1071-\u1074\u1082-\u108d\u108f-\u109d\u135d-\u135f\u1369-\u1371\u1712-\u1715\u1732-\u1734\u1752\u1753\u1772\u1773\u17b4-\u17d3\u17dd\u17e0-\u17e9\u180b-\u180d\u180f-\u1819\u18a9\u1920-\u192b\u1930-\u193b\u1946-\u194f\u19d0-\u19da\u1a17-\u1a1b\u1a55-\u1a5e\u1a60-\u1a7c\u1a7f-\u1a89\u1a90-\u1a99\u1ab0-\u1abd\u1abf-\u1ace\u1b00-\u1b04\u1b34-\u1b44\u1b50-\u1b59\u1b6b-\u1b73\u1b80-\u1b82\u1ba1-\u1bad\u1bb0-\u1bb9\u1be6-\u1bf3\u1c24-\u1c37\u1c40-\u1c49\u1c50-\u1c59\u1cd0-\u1cd2\u1cd4-\u1ce8\u1ced\u1cf4\u1cf7-\u1cf9\u1dc0-\u1dff\u200c\u200d\u203f\u2040\u2054\u20d0-\u20dc\u20e1\u20e5-\u20f0\u2cef-\u2cf1\u2d7f\u2de0-\u2dff\u302a-\u302f\u3099\u309a\u30fb\ua620-\ua629\ua66f\ua674-\ua67d\ua69e\ua69f\ua6f0\ua6f1\ua802\ua806\ua80b\ua823-\ua827\ua82c\ua880\ua881\ua8b4-\ua8c5\ua8d0-\ua8d9\ua8e0-\ua8f1\ua8ff-\ua909\ua926-\ua92d\ua947-\ua953\ua980-\ua983\ua9b3-\ua9c0\ua9d0-\ua9d9\ua9e5\ua9f0-\ua9f9\uaa29-\uaa36\uaa43\uaa4c\uaa4d\uaa50-\uaa59\uaa7b-\uaa7d\uaab0\uaab2-\uaab4\uaab7\uaab8\uaabe\uaabf\uaac1\uaaeb-\uaaef\uaaf5\uaaf6\uabe3-\uabea\uabec\uabed\uabf0-\uabf9\ufb1e\ufe00-\ufe0f\ufe20-\ufe2f\ufe33\ufe34\ufe4d-\ufe4f\uff10-\uff19\uff3f\uff65";
const nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]");
const nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]");
nonASCIIidentifierStartChars = nonASCIIidentifierChars = null;
const astralIdentifierStartCodes = [0, 11, 2, 25, 2, 18, 2, 1, 2, 14, 3, 13, 35, 122, 70, 52, 268, 28, 4, 48, 48, 31, 14, 29, 6, 37, 11, 29, 3, 35, 5, 7, 2, 4, 43, 157, 19, 35, 5, 35, 5, 39, 9, 51, 13, 10, 2, 14, 2, 6, 2, 1, 2, 10, 2, 14, 2, 6, 2, 1, 4, 51, 13, 310, 10, 21, 11, 7, 25, 5, 2, 41, 2, 8, 70, 5, 3, 0, 2, 43, 2, 1, 4, 0, 3, 22, 11, 22, 10, 30, 66, 18, 2, 1, 11, 21, 11, 25, 71, 55, 7, 1, 65, 0, 16, 3, 2, 2, 2, 28, 43, 28, 4, 28, 36, 7, 2, 27, 28, 53, 11, 21, 11, 18, 14, 17, 111, 72, 56, 50, 14, 50, 14, 35, 39, 27, 10, 22, 251, 41, 7, 1, 17, 2, 60, 28, 11, 0, 9, 21, 43, 17, 47, 20, 28, 22, 13, 52, 58, 1, 3, 0, 14, 44, 33, 24, 27, 35, 30, 0, 3, 0, 9, 34, 4, 0, 13, 47, 15, 3, 22, 0, 2, 0, 36, 17, 2, 24, 20, 1, 64, 6, 2, 0, 2, 3, 2, 14, 2, 9, 8, 46, 39, 7, 3, 1, 3, 21, 2, 6, 2, 1, 2, 4, 4, 0, 19, 0, 13, 4, 31, 9, 2, 0, 3, 0, 2, 37, 2, 0, 26, 0, 2, 0, 45, 52, 19, 3, 21, 2, 31, 47, 21, 1, 2, 0, 185, 46, 42, 3, 37, 47, 21, 0, 60, 42, 14, 0, 72, 26, 38, 6, 186, 43, 117, 63, 32, 7, 3, 0, 3, 7, 2, 1, 2, 23, 16, 0, 2, 0, 95, 7, 3, 38, 17, 0, 2, 0, 29, 0, 11, 39, 8, 0, 22, 0, 12, 45, 20, 0, 19, 72, 200, 32, 32, 8, 2, 36, 18, 0, 50, 29, 113, 6, 2, 1, 2, 37, 22, 0, 26, 5, 2, 1, 2, 31, 15, 0, 328, 18, 16, 0, 2, 12, 2, 33, 125, 0, 80, 921, 103, 110, 18, 195, 2637, 96, 16, 1071, 18, 5, 26, 3994, 6, 582, 6842, 29, 1763, 568, 8, 30, 18, 78, 18, 29, 19, 47, 17, 3, 32, 20, 6, 18, 433, 44, 212, 63, 129, 74, 6, 0, 67, 12, 65, 1, 2, 0, 29, 6135, 9, 1237, 42, 9, 8936, 3, 2, 6, 2, 1, 2, 290, 16, 0, 30, 2, 3, 0, 15, 3, 9, 395, 2309, 106, 6, 12, 4, 8, 8, 9, 5991, 84, 2, 70, 2, 1, 3, 0, 3, 1, 3, 3, 2, 11, 2, 0, 2, 6, 2, 64, 2, 3, 3, 7, 2, 6, 2, 27, 2, 3, 2, 4, 2, 0, 4, 6, 2, 339, 3, 24, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 7, 1845, 30, 7, 5, 262, 61, 147, 44, 11, 6, 17, 0, 322, 29, 19, 43, 485, 27, 229, 29, 3, 0, 496, 6, 2, 3, 2, 1, 2, 14, 2, 196, 60, 67, 8, 0, 1205, 3, 2, 26, 2, 1, 2, 0, 3, 0, 2, 9, 2, 3, 2, 0, 2, 0, 7, 0, 5, 0, 2, 0, 2, 0, 2, 2, 2, 1, 2, 0, 3, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 1, 2, 0, 3, 3, 2, 6, 2, 3, 2, 3, 2, 0, 2, 9, 2, 16, 6, 2, 2, 4, 2, 16, 4421, 42719, 33, 4153, 7, 221, 3, 5761, 15, 7472, 16, 621, 2467, 541, 1507, 4938, 6, 4191];
const astralIdentifierCodes = [509, 0, 227, 0, 150, 4, 294, 9, 1368, 2, 2, 1, 6, 3, 41, 2, 5, 0, 166, 1, 574, 3, 9, 9, 7, 9, 32, 4, 318, 1, 80, 3, 71, 10, 50, 3, 123, 2, 54, 14, 32, 10, 3, 1, 11, 3, 46, 10, 8, 0, 46, 9, 7, 2, 37, 13, 2, 9, 6, 1, 45, 0, 13, 2, 49, 13, 9, 3, 2, 11, 83, 11, 7, 0, 3, 0, 158, 11, 6, 9, 7, 3, 56, 1, 2, 6, 3, 1, 3, 2, 10, 0, 11, 1, 3, 6, 4, 4, 68, 8, 2, 0, 3, 0, 2, 3, 2, 4, 2, 0, 15, 1, 83, 17, 10, 9, 5, 0, 82, 19, 13, 9, 214, 6, 3, 8, 28, 1, 83, 16, 16, 9, 82, 12, 9, 9, 7, 19, 58, 14, 5, 9, 243, 14, 166, 9, 71, 5, 2, 1, 3, 3, 2, 0, 2, 1, 13, 9, 120, 6, 3, 6, 4, 0, 29, 9, 41, 6, 2, 3, 9, 0, 10, 10, 47, 15, 343, 9, 54, 7, 2, 7, 17, 9, 57, 21, 2, 13, 123, 5, 4, 0, 2, 1, 2, 6, 2, 0, 9, 9, 49, 4, 2, 1, 2, 4, 9, 9, 330, 3, 10, 1, 2, 0, 49, 6, 4, 4, 14, 10, 5350, 0, 7, 14, 11465, 27, 2343, 9, 87, 9, 39, 4, 60, 6, 26, 9, 535, 9, 470, 0, 2, 54, 8, 3, 82, 0, 12, 1, 19628, 1, 4178, 9, 519, 45, 3, 22, 543, 4, 4, 5, 9, 7, 3, 6, 31, 3, 149, 2, 1418, 49, 513, 54, 5, 49, 9, 0, 15, 0, 23, 4, 2, 14, 1361, 6, 2, 16, 3, 6, 2, 1, 2, 4, 101, 0, 161, 6, 10, 9, 357, 0, 62, 13, 499, 13, 245, 1, 2, 9, 726, 6, 110, 6, 6, 9, 4759, 9, 787719, 239];
function isInAstralSet(code, set) {
let pos = 0x10000;
for (let i = 0, length = set.length; i < length; i += 2) {
pos += set[i];
if (pos > code) return false;
pos += set[i + 1];
if (pos >= code) return true;
}
return false;
}
function isIdentifierStart(code) {
if (code < 65) return code === 36;
if (code <= 90) return true;
if (code < 97) return code === 95;
if (code <= 122) return true;
if (code <= 0xffff) {
return code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code));
}
return isInAstralSet(code, astralIdentifierStartCodes);
}
function isIdentifierChar(code) {
if (code < 48) return code === 36;
if (code < 58) return true;
if (code < 65) return false;
if (code <= 90) return true;
if (code < 97) return code === 95;
if (code <= 122) return true;
if (code <= 0xffff) {
return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code));
}
return isInAstralSet(code, astralIdentifierStartCodes) || isInAstralSet(code, astralIdentifierCodes);
}
function isIdentifierName(name) {
let isFirst = true;
for (let i = 0; i < name.length; i++) {
let cp = name.charCodeAt(i);
if ((cp & 0xfc00) === 0xd800 && i + 1 < name.length) {
const trail = name.charCodeAt(++i);
if ((trail & 0xfc00) === 0xdc00) {
cp = 0x10000 + ((cp & 0x3ff) << 10) + (trail & 0x3ff);
}
}
if (isFirst) {
isFirst = false;
if (!isIdentifierStart(cp)) {
return false;
}
} else if (!isIdentifierChar(cp)) {
return false;
}
}
return !isFirst;
}
//# sourceMappingURL=identifier.js.map

File diff suppressed because one or more lines are too long

View File

@@ -1,57 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
Object.defineProperty(exports, "isIdentifierChar", {
enumerable: true,
get: function () {
return _identifier.isIdentifierChar;
}
});
Object.defineProperty(exports, "isIdentifierName", {
enumerable: true,
get: function () {
return _identifier.isIdentifierName;
}
});
Object.defineProperty(exports, "isIdentifierStart", {
enumerable: true,
get: function () {
return _identifier.isIdentifierStart;
}
});
Object.defineProperty(exports, "isKeyword", {
enumerable: true,
get: function () {
return _keyword.isKeyword;
}
});
Object.defineProperty(exports, "isReservedWord", {
enumerable: true,
get: function () {
return _keyword.isReservedWord;
}
});
Object.defineProperty(exports, "isStrictBindOnlyReservedWord", {
enumerable: true,
get: function () {
return _keyword.isStrictBindOnlyReservedWord;
}
});
Object.defineProperty(exports, "isStrictBindReservedWord", {
enumerable: true,
get: function () {
return _keyword.isStrictBindReservedWord;
}
});
Object.defineProperty(exports, "isStrictReservedWord", {
enumerable: true,
get: function () {
return _keyword.isStrictReservedWord;
}
});
var _identifier = require("./identifier.js");
var _keyword = require("./keyword.js");
//# sourceMappingURL=index.js.map

View File

@@ -1 +0,0 @@
{"version":3,"names":["_identifier","require","_keyword"],"sources":["../src/index.ts"],"sourcesContent":["export {\n isIdentifierName,\n isIdentifierChar,\n isIdentifierStart,\n} from \"./identifier.ts\";\nexport {\n isReservedWord,\n isStrictBindOnlyReservedWord,\n isStrictBindReservedWord,\n isStrictReservedWord,\n isKeyword,\n} from \"./keyword.ts\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,IAAAA,WAAA,GAAAC,OAAA;AAKA,IAAAC,QAAA,GAAAD,OAAA","ignoreList":[]}

View File

@@ -1,35 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.isKeyword = isKeyword;
exports.isReservedWord = isReservedWord;
exports.isStrictBindOnlyReservedWord = isStrictBindOnlyReservedWord;
exports.isStrictBindReservedWord = isStrictBindReservedWord;
exports.isStrictReservedWord = isStrictReservedWord;
const reservedWords = {
keyword: ["break", "case", "catch", "continue", "debugger", "default", "do", "else", "finally", "for", "function", "if", "return", "switch", "throw", "try", "var", "const", "while", "with", "new", "this", "super", "class", "extends", "export", "import", "null", "true", "false", "in", "instanceof", "typeof", "void", "delete"],
strict: ["implements", "interface", "let", "package", "private", "protected", "public", "static", "yield"],
strictBind: ["eval", "arguments"]
};
const keywords = new Set(reservedWords.keyword);
const reservedWordsStrictSet = new Set(reservedWords.strict);
const reservedWordsStrictBindSet = new Set(reservedWords.strictBind);
function isReservedWord(word, inModule) {
return inModule && word === "await" || word === "enum";
}
function isStrictReservedWord(word, inModule) {
return isReservedWord(word, inModule) || reservedWordsStrictSet.has(word);
}
function isStrictBindOnlyReservedWord(word) {
return reservedWordsStrictBindSet.has(word);
}
function isStrictBindReservedWord(word, inModule) {
return isStrictReservedWord(word, inModule) || isStrictBindOnlyReservedWord(word);
}
function isKeyword(word) {
return keywords.has(word);
}
//# sourceMappingURL=keyword.js.map

View File

@@ -1 +0,0 @@
{"version":3,"names":["reservedWords","keyword","strict","strictBind","keywords","Set","reservedWordsStrictSet","reservedWordsStrictBindSet","isReservedWord","word","inModule","isStrictReservedWord","has","isStrictBindOnlyReservedWord","isStrictBindReservedWord","isKeyword"],"sources":["../src/keyword.ts"],"sourcesContent":["const reservedWords = {\n keyword: [\n \"break\",\n \"case\",\n \"catch\",\n \"continue\",\n \"debugger\",\n \"default\",\n \"do\",\n \"else\",\n \"finally\",\n \"for\",\n \"function\",\n \"if\",\n \"return\",\n \"switch\",\n \"throw\",\n \"try\",\n \"var\",\n \"const\",\n \"while\",\n \"with\",\n \"new\",\n \"this\",\n \"super\",\n \"class\",\n \"extends\",\n \"export\",\n \"import\",\n \"null\",\n \"true\",\n \"false\",\n \"in\",\n \"instanceof\",\n \"typeof\",\n \"void\",\n \"delete\",\n ],\n strict: [\n \"implements\",\n \"interface\",\n \"let\",\n \"package\",\n \"private\",\n \"protected\",\n \"public\",\n \"static\",\n \"yield\",\n ],\n strictBind: [\"eval\", \"arguments\"],\n};\nconst keywords = new Set(reservedWords.keyword);\nconst reservedWordsStrictSet = new Set(reservedWords.strict);\nconst reservedWordsStrictBindSet = new Set(reservedWords.strictBind);\n\n/**\n * Checks if word is a reserved word in non-strict mode\n */\nexport function isReservedWord(word: string, inModule: boolean): boolean {\n return (inModule && word === \"await\") || word === \"enum\";\n}\n\n/**\n * Checks if word is a reserved word in non-binding strict mode\n *\n * Includes non-strict reserved words\n */\nexport function isStrictReservedWord(word: string, inModule: boolean): boolean {\n return isReservedWord(word, inModule) || reservedWordsStrictSet.has(word);\n}\n\n/**\n * Checks if word is a reserved word in binding strict mode, but it is allowed as\n * a normal identifier.\n */\nexport function isStrictBindOnlyReservedWord(word: string): boolean {\n return reservedWordsStrictBindSet.has(word);\n}\n\n/**\n * Checks if word is a reserved word in binding strict mode\n *\n * Includes non-strict reserved words and non-binding strict reserved words\n */\nexport function isStrictBindReservedWord(\n word: string,\n inModule: boolean,\n): boolean {\n return (\n isStrictReservedWord(word, inModule) || isStrictBindOnlyReservedWord(word)\n );\n}\n\nexport function isKeyword(word: string): boolean {\n return keywords.has(word);\n}\n"],"mappings":";;;;;;;;;;AAAA,MAAMA,aAAa,GAAG;EACpBC,OAAO,EAAE,CACP,OAAO,EACP,MAAM,EACN,OAAO,EACP,UAAU,EACV,UAAU,EACV,SAAS,EACT,IAAI,EACJ,MAAM,EACN,SAAS,EACT,KAAK,EACL,UAAU,EACV,IAAI,EACJ,QAAQ,EACR,QAAQ,EACR,OAAO,EACP,KAAK,EACL,KAAK,EACL,OAAO,EACP,OAAO,EACP,MAAM,EACN,KAAK,EACL,MAAM,EACN,OAAO,EACP,OAAO,EACP,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,MAAM,EACN,MAAM,EACN,OAAO,EACP,IAAI,EACJ,YAAY,EACZ,QAAQ,EACR,MAAM,EACN,QAAQ,CACT;EACDC,MAAM,EAAE,CACN,YAAY,EACZ,WAAW,EACX,KAAK,EACL,SAAS,EACT,SAAS,EACT,WAAW,EACX,QAAQ,EACR,QAAQ,EACR,OAAO,CACR;EACDC,UAAU,EAAE,CAAC,MAAM,EAAE,WAAW;AAClC,CAAC;AACD,MAAMC,QAAQ,GAAG,IAAIC,GAAG,CAACL,aAAa,CAACC,OAAO,CAAC;AAC/C,MAAMK,sBAAsB,GAAG,IAAID,GAAG,CAACL,aAAa,CAACE,MAAM,CAAC;AAC5D,MAAMK,0BAA0B,GAAG,IAAIF,GAAG,CAACL,aAAa,CAACG,UAAU,CAAC;AAK7D,SAASK,cAAcA,CAACC,IAAY,EAAEC,QAAiB,EAAW;EACvE,OAAQA,QAAQ,IAAID,IAAI,KAAK,OAAO,IAAKA,IAAI,KAAK,MAAM;AAC1D;AAOO,SAASE,oBAAoBA,CAACF,IAAY,EAAEC,QAAiB,EAAW;EAC7E,OAAOF,cAAc,CAACC,IAAI,EAAEC,QAAQ,CAAC,IAAIJ,sBAAsB,CAACM,GAAG,CAACH,IAAI,CAAC;AAC3E;AAMO,SAASI,4BAA4BA,CAACJ,IAAY,EAAW;EAClE,OAAOF,0BAA0B,CAACK,GAAG,CAACH,IAAI,CAAC;AAC7C;AAOO,SAASK,wBAAwBA,CACtCL,IAAY,EACZC,QAAiB,EACR;EACT,OACEC,oBAAoB,CAACF,IAAI,EAAEC,QAAQ,CAAC,IAAIG,4BAA4B,CAACJ,IAAI,CAAC;AAE9E;AAEO,SAASM,SAASA,CAACN,IAAY,EAAW;EAC/C,OAAOL,QAAQ,CAACQ,GAAG,CAACH,IAAI,CAAC;AAC3B","ignoreList":[]}

View File

@@ -1,31 +0,0 @@
{
"name": "@babel/helper-validator-identifier",
"version": "7.27.1",
"description": "Validate identifier/keywords name",
"repository": {
"type": "git",
"url": "https://github.com/babel/babel.git",
"directory": "packages/babel-helper-validator-identifier"
},
"license": "MIT",
"publishConfig": {
"access": "public"
},
"main": "./lib/index.js",
"exports": {
".": {
"types": "./lib/index.d.ts",
"default": "./lib/index.js"
},
"./package.json": "./package.json"
},
"devDependencies": {
"@unicode/unicode-16.0.0": "^1.0.0",
"charcodes": "^0.2.0"
},
"engines": {
"node": ">=6.9.0"
},
"author": "The Babel Team (https://babel.dev/team)",
"type": "commonjs"
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,19 +0,0 @@
Copyright (C) 2012-2014 by various contributors (see AUTHORS)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,19 +0,0 @@
# @babel/parser
> A JavaScript parser
See our website [@babel/parser](https://babeljs.io/docs/babel-parser) for more information or the [issues](https://github.com/babel/babel/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3A%22pkg%3A%20parser%22+is%3Aopen) associated with this package.
## Install
Using npm:
```sh
npm install --save-dev @babel/parser
```
or using yarn:
```sh
yarn add @babel/parser --dev
```

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -1,50 +0,0 @@
{
"name": "@babel/parser",
"version": "7.28.4",
"description": "A JavaScript parser",
"author": "The Babel Team (https://babel.dev/team)",
"homepage": "https://babel.dev/docs/en/next/babel-parser",
"bugs": "https://github.com/babel/babel/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3A%22pkg%3A+parser+%28babylon%29%22+is%3Aopen",
"license": "MIT",
"publishConfig": {
"access": "public"
},
"keywords": [
"babel",
"javascript",
"parser",
"tc39",
"ecmascript",
"@babel/parser"
],
"repository": {
"type": "git",
"url": "https://github.com/babel/babel.git",
"directory": "packages/babel-parser"
},
"main": "./lib/index.js",
"types": "./typings/babel-parser.d.ts",
"files": [
"bin",
"lib",
"typings/babel-parser.d.ts",
"index.cjs"
],
"engines": {
"node": ">=6.0.0"
},
"# dependencies": "This package doesn't actually have runtime dependencies. @babel/types is only needed for type definitions.",
"dependencies": {
"@babel/types": "^7.28.4"
},
"devDependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/helper-check-duplicate-nodes": "^7.27.1",
"@babel/helper-fixtures": "^7.28.0",
"@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.27.1",
"charcodes": "^0.2.0"
},
"bin": "./bin/babel-parser.js",
"type": "commonjs"
}

View File

@@ -1,239 +0,0 @@
// This file is auto-generated! Do not modify it directly.
// Run `yarn gulp bundle-dts` to re-generate it.
/* eslint-disable @typescript-eslint/consistent-type-imports, @typescript-eslint/no-redundant-type-constituents */
import { File, Expression } from '@babel/types';
type BABEL_8_BREAKING = false;
type IF_BABEL_7<V> = false extends BABEL_8_BREAKING ? V : never;
type Plugin$1 =
| "asyncDoExpressions"
| IF_BABEL_7<"asyncGenerators">
| IF_BABEL_7<"bigInt">
| IF_BABEL_7<"classPrivateMethods">
| IF_BABEL_7<"classPrivateProperties">
| IF_BABEL_7<"classProperties">
| IF_BABEL_7<"classStaticBlock">
| IF_BABEL_7<"decimal">
| "decorators-legacy"
| "deferredImportEvaluation"
| "decoratorAutoAccessors"
| "destructuringPrivate"
| "deprecatedImportAssert"
| "doExpressions"
| IF_BABEL_7<"dynamicImport">
| IF_BABEL_7<"explicitResourceManagement">
| "exportDefaultFrom"
| IF_BABEL_7<"exportNamespaceFrom">
| "flow"
| "flowComments"
| "functionBind"
| "functionSent"
| "importMeta"
| "jsx"
| IF_BABEL_7<"jsonStrings">
| IF_BABEL_7<"logicalAssignment">
| IF_BABEL_7<"importAssertions">
| IF_BABEL_7<"importReflection">
| "moduleBlocks"
| IF_BABEL_7<"moduleStringNames">
| IF_BABEL_7<"nullishCoalescingOperator">
| IF_BABEL_7<"numericSeparator">
| IF_BABEL_7<"objectRestSpread">
| IF_BABEL_7<"optionalCatchBinding">
| IF_BABEL_7<"optionalChaining">
| "partialApplication"
| "placeholders"
| IF_BABEL_7<"privateIn">
| IF_BABEL_7<"regexpUnicodeSets">
| "sourcePhaseImports"
| "throwExpressions"
| IF_BABEL_7<"topLevelAwait">
| "v8intrinsic"
| ParserPluginWithOptions[0];
type ParserPluginWithOptions =
| ["decorators", DecoratorsPluginOptions]
| ["discardBinding", { syntaxType: "void" }]
| ["estree", { classFeatures?: boolean }]
| IF_BABEL_7<["importAttributes", { deprecatedAssertSyntax: boolean }]>
| IF_BABEL_7<["moduleAttributes", { version: "may-2020" }]>
| ["optionalChainingAssign", { version: "2023-07" }]
| ["pipelineOperator", PipelineOperatorPluginOptions]
| ["recordAndTuple", RecordAndTuplePluginOptions]
| ["flow", FlowPluginOptions]
| ["typescript", TypeScriptPluginOptions];
type PluginConfig = Plugin$1 | ParserPluginWithOptions;
interface DecoratorsPluginOptions {
decoratorsBeforeExport?: boolean;
allowCallParenthesized?: boolean;
}
interface PipelineOperatorPluginOptions {
proposal: BABEL_8_BREAKING extends false
? "minimal" | "fsharp" | "hack" | "smart"
: "fsharp" | "hack";
topicToken?: "%" | "#" | "@@" | "^^" | "^";
}
interface RecordAndTuplePluginOptions {
syntaxType: "bar" | "hash";
}
type FlowPluginOptions = BABEL_8_BREAKING extends true
? {
all?: boolean;
enums?: boolean;
}
: {
all?: boolean;
};
interface TypeScriptPluginOptions {
dts?: boolean;
disallowAmbiguousJSXLike?: boolean;
}
type Plugin = PluginConfig;
type SourceType = "script" | "commonjs" | "module" | "unambiguous";
interface Options {
/**
* By default, import and export declarations can only appear at a program's top level.
* Setting this option to true allows them anywhere where a statement is allowed.
*/
allowImportExportEverywhere?: boolean;
/**
* By default, await use is not allowed outside of an async function.
* Set this to true to accept such code.
*/
allowAwaitOutsideFunction?: boolean;
/**
* By default, a return statement at the top level raises an error.
* Set this to true to accept such code.
*/
allowReturnOutsideFunction?: boolean;
/**
* By default, new.target use is not allowed outside of a function or class.
* Set this to true to accept such code.
*/
allowNewTargetOutsideFunction?: boolean;
allowSuperOutsideMethod?: boolean;
/**
* By default, exported identifiers must refer to a declared variable.
* Set this to true to allow export statements to reference undeclared variables.
*/
allowUndeclaredExports?: boolean;
/**
* By default, yield use is not allowed outside of a generator function.
* Set this to true to accept such code.
*/
allowYieldOutsideFunction?: boolean;
/**
* By default, Babel parser JavaScript code according to Annex B syntax.
* Set this to `false` to disable such behavior.
*/
annexB?: boolean;
/**
* By default, Babel attaches comments to adjacent AST nodes.
* When this option is set to false, comments are not attached.
* It can provide up to 30% performance improvement when the input code has many comments.
* @babel/eslint-parser will set it for you.
* It is not recommended to use attachComment: false with Babel transform,
* as doing so removes all the comments in output code, and renders annotations such as
* /* istanbul ignore next *\/ nonfunctional.
*/
attachComment?: boolean;
/**
* By default, Babel always throws an error when it finds some invalid code.
* When this option is set to true, it will store the parsing error and
* try to continue parsing the invalid input file.
*/
errorRecovery?: boolean;
/**
* Indicate the mode the code should be parsed in.
* Can be one of "script", "commonjs", "module", or "unambiguous". Defaults to "script".
* "unambiguous" will make @babel/parser attempt to guess, based on the presence
* of ES6 import or export statements.
* Files with ES6 imports and exports are considered "module" and are otherwise "script".
*
* Use "commonjs" to parse code that is intended to be run in a CommonJS environment such as Node.js.
*/
sourceType?: SourceType;
/**
* Correlate output AST nodes with their source filename.
* Useful when generating code and source maps from the ASTs of multiple input files.
*/
sourceFilename?: string;
/**
* By default, all source indexes start from 0.
* You can provide a start index to alternatively start with.
* Useful for integration with other source tools.
*/
startIndex?: number;
/**
* By default, the first line of code parsed is treated as line 1.
* You can provide a line number to alternatively start with.
* Useful for integration with other source tools.
*/
startLine?: number;
/**
* By default, the parsed code is treated as if it starts from line 1, column 0.
* You can provide a column number to alternatively start with.
* Useful for integration with other source tools.
*/
startColumn?: number;
/**
* Array containing the plugins that you want to enable.
*/
plugins?: Plugin[];
/**
* Should the parser work in strict mode.
* Defaults to true if sourceType === 'module'. Otherwise, false.
*/
strictMode?: boolean;
/**
* Adds a ranges property to each node: [node.start, node.end]
*/
ranges?: boolean;
/**
* Adds all parsed tokens to a tokens property on the File node.
*/
tokens?: boolean;
/**
* By default, the parser adds information about parentheses by setting
* `extra.parenthesized` to `true` as needed.
* When this option is `true` the parser creates `ParenthesizedExpression`
* AST nodes instead of using the `extra` property.
*/
createParenthesizedExpressions?: boolean;
/**
* The default is false in Babel 7 and true in Babel 8
* Set this to true to parse it as an `ImportExpression` node.
* Otherwise `import(foo)` is parsed as `CallExpression(Import, [Identifier(foo)])`.
*/
createImportExpressions?: boolean;
}
type ParserOptions = Partial<Options>;
interface ParseError {
code: string;
reasonCode: string;
}
type ParseResult<Result extends File | Expression = File> = Result & {
errors: null | ParseError[];
};
/**
* Parse the provided code as an entire ECMAScript program.
*/
declare function parse(input: string, options?: ParserOptions): ParseResult<File>;
declare function parseExpression(input: string, options?: ParserOptions): ParseResult<Expression>;
declare const tokTypes: {
// todo(flow->ts) real token type
[name: string]: any;
};
export { DecoratorsPluginOptions, FlowPluginOptions, ParseError, ParseResult, ParserOptions, PluginConfig as ParserPlugin, ParserPluginWithOptions, PipelineOperatorPluginOptions, RecordAndTuplePluginOptions, TypeScriptPluginOptions, parse, parseExpression, tokTypes };

View File

@@ -1,22 +0,0 @@
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,19 +0,0 @@
# @babel/types
> Babel Types is a Lodash-esque utility library for AST nodes
See our website [@babel/types](https://babeljs.io/docs/babel-types) for more information or the [issues](https://github.com/babel/babel/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3A%22pkg%3A%20types%22+is%3Aopen) associated with this package.
## Install
Using npm:
```sh
npm install --save-dev @babel/types
```
or using yarn:
```sh
yarn add @babel/types --dev
```

Some files were not shown because too many files have changed in this diff Show More