Compare commits
	
		
			21 Commits
		
	
	
		
			3b967aa449
			...
			71afbf5ff9
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 71afbf5ff9 | |||
| 4e046021e3 | |||
| 4cbb4bb859 | |||
| 0038f20334 | |||
| 197af0181c | |||
| 1830fcd43e | |||
| 53845422c1 | |||
| 757d38645e | |||
| 5ee6cbce8f | |||
| fd39eb6450 | |||
| 89fbbbb75f | |||
| e150969ee3 | |||
| 4c6843afb4 | |||
| eb0786ca27 | |||
| 7299c8ebe6 | |||
| bcdcaa5631 | |||
| fab26ffca4 | |||
| 6a93346e87 | |||
| df0dfd62c6 | |||
| 51a873049e | |||
| 05820438d0 | 
							
								
								
									
										2402
									
								
								docs/docs.go
									
									
									
									
									
								
							
							
						
						
									
										2402
									
								
								docs/docs.go
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										2402
									
								
								docs/swagger.json
									
									
									
									
									
								
							
							
						
						
									
										2402
									
								
								docs/swagger.json
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										1490
									
								
								docs/swagger.yaml
									
									
									
									
									
								
							
							
						
						
									
										1490
									
								
								docs/swagger.yaml
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -16,15 +16,14 @@ import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"net/http" | ||||
| 	"net/http/pprof" | ||||
| 	"time" | ||||
|  | ||||
| 	_ "git.huangwc.com/pig/pig-farm-controller/docs" // 引入 swag 生成的 docs | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/app/controller/device" | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/app/controller/management" | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/app/controller/monitor" | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/app/controller/plan" | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/app/controller/user" | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/app/middleware" | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/app/service" | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/app/webhook" | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/domain/audit" | ||||
| @@ -36,8 +35,6 @@ import ( | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" | ||||
|  | ||||
| 	"github.com/gin-gonic/gin" | ||||
| 	swaggerFiles "github.com/swaggo/files" | ||||
| 	ginSwagger "github.com/swaggo/gin-swagger" | ||||
| ) | ||||
|  | ||||
| // API 结构体定义了 HTTP 服务器及其依赖 | ||||
| @@ -54,6 +51,7 @@ type API struct { | ||||
| 	planController      *plan.Controller               // 计划控制器实例 | ||||
| 	pigFarmController   *management.PigFarmController  // 猪场管理控制器实例 | ||||
| 	pigBatchController  *management.PigBatchController // 猪群控制器实例 | ||||
| 	monitorController   *monitor.Controller            // 数据监控控制器实例 | ||||
| 	listenHandler       webhook.ListenHandler          // 设备上行事件监听器 | ||||
| 	analysisTaskManager *task.AnalysisPlanTaskManager  // 计划触发器管理器实例 | ||||
| } | ||||
| @@ -69,6 +67,7 @@ func NewAPI(cfg config.ServerConfig, | ||||
| 	planRepository repository.PlanRepository, | ||||
| 	pigFarmService service.PigFarmService, | ||||
| 	pigBatchService service.PigBatchService, | ||||
| 	monitorService service.MonitorService, | ||||
| 	userActionLogRepository repository.UserActionLogRepository, | ||||
| 	tokenService token.TokenService, | ||||
| 	auditService audit.Service, | ||||
| @@ -97,7 +96,7 @@ func NewAPI(cfg config.ServerConfig, | ||||
| 		config:        cfg, | ||||
| 		listenHandler: listenHandler, | ||||
| 		// 在 NewAPI 中初始化用户控制器,并将其作为 API 结构体的成员 | ||||
| 		userController: user.NewController(userRepo, userActionLogRepository, logger, tokenService), | ||||
| 		userController: user.NewController(userRepo, monitorService, logger, tokenService), | ||||
| 		// 在 NewAPI 中初始化设备控制器,并将其作为 API 结构体的成员 | ||||
| 		deviceController: device.NewController(deviceRepository, areaControllerRepository, deviceTemplateRepository, deviceService, logger), | ||||
| 		// 在 NewAPI 中初始化计划控制器,并将其作为 API 结构体的成员 | ||||
| @@ -106,161 +105,14 @@ func NewAPI(cfg config.ServerConfig, | ||||
| 		pigFarmController: management.NewPigFarmController(logger, pigFarmService), | ||||
| 		// 在 NewAPI 中初始化猪群控制器 | ||||
| 		pigBatchController: management.NewPigBatchController(logger, pigBatchService), | ||||
| 		// 在 NewAPI 中初始化数据监控控制器 | ||||
| 		monitorController: monitor.NewController(monitorService, logger), | ||||
| 	} | ||||
|  | ||||
| 	api.setupRoutes() // 设置所有路由 | ||||
| 	return api | ||||
| } | ||||
|  | ||||
| // setupRoutes 设置所有 API 路由 | ||||
| // 在此方法中,使用已初始化的控制器实例将其路由注册到 Gin 引擎中。 | ||||
| func (a *API) setupRoutes() { | ||||
|  | ||||
| 	// --- Public Routes --- | ||||
| 	// 这些路由不需要身份验证 | ||||
|  | ||||
| 	// 用户注册和登录 | ||||
| 	a.engine.POST("/api/v1/users", a.userController.CreateUser)  // 注册新用户 | ||||
| 	a.engine.POST("/api/v1/users/login", a.userController.Login) // 用户登录 | ||||
| 	a.logger.Info("公开接口注册成功:用户注册、登录") | ||||
|  | ||||
| 	// 注册 pprof 路由 | ||||
| 	pprofGroup := a.engine.Group("/debug/pprof") | ||||
| 	{ | ||||
| 		pprofGroup.GET("/", gin.WrapF(pprof.Index))                   // pprof 索引页 | ||||
| 		pprofGroup.GET("/cmdline", gin.WrapF(pprof.Cmdline))          // pprof 命令行参数 | ||||
| 		pprofGroup.GET("/profile", gin.WrapF(pprof.Profile))          // pprof CPU profile | ||||
| 		pprofGroup.POST("/symbol", gin.WrapF(pprof.Symbol))           // pprof 符号查找 (POST) | ||||
| 		pprofGroup.GET("/symbol", gin.WrapF(pprof.Symbol))            // pprof 符号查找 (GET) | ||||
| 		pprofGroup.GET("/trace", gin.WrapF(pprof.Trace))              // pprof 跟踪 | ||||
| 		pprofGroup.GET("/allocs", gin.WrapH(pprof.Handler("allocs"))) // pprof 内存分配 | ||||
| 		pprofGroup.GET("/block", gin.WrapH(pprof.Handler("block")))   // pprof 阻塞 | ||||
| 		pprofGroup.GET("/goroutine", gin.WrapH(pprof.Handler("goroutine"))) | ||||
| 		pprofGroup.GET("/heap", gin.WrapH(pprof.Handler("heap")))   // pprof 堆内存 | ||||
| 		pprofGroup.GET("/mutex", gin.WrapH(pprof.Handler("mutex"))) // pprof 互斥锁 | ||||
| 		pprofGroup.GET("/threadcreate", gin.WrapH(pprof.Handler("threadcreate"))) | ||||
| 	} | ||||
| 	a.logger.Info("pprof 接口注册成功") | ||||
|  | ||||
| 	// 上行事件监听路由 | ||||
| 	a.engine.POST("/upstream", gin.WrapH(a.listenHandler.Handler())) // 处理设备上行事件 | ||||
| 	a.logger.Info("上行事件监听接口注册成功") | ||||
|  | ||||
| 	// 添加 Swagger UI 路由, Swagger UI可在 /swagger/index.html 上找到 | ||||
| 	a.engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) // Swagger UI 接口 | ||||
| 	a.logger.Info("Swagger UI 接口注册成功") | ||||
|  | ||||
| 	// --- Authenticated Routes --- | ||||
| 	// 所有在此注册的路由都需要通过 JWT 身份验证 | ||||
| 	authGroup := a.engine.Group("/api/v1") | ||||
| 	authGroup.Use(middleware.AuthMiddleware(a.tokenService, a.userRepo)) // 1. 身份认证中间件 | ||||
| 	authGroup.Use(middleware.AuditLogMiddleware(a.auditService))         // 2. 审计日志中间件 | ||||
| 	{ | ||||
| 		// 用户相关路由组 | ||||
| 		userGroup := authGroup.Group("/users") | ||||
| 		{ | ||||
| 			userGroup.GET("/:id/history", a.userController.ListUserHistory) // 获取用户操作历史 | ||||
| 		} | ||||
| 		a.logger.Info("用户相关接口注册成功 (需要认证和审计)") | ||||
|  | ||||
| 		// 设备相关路由组 | ||||
| 		deviceGroup := authGroup.Group("/devices") | ||||
| 		{ | ||||
| 			deviceGroup.POST("", a.deviceController.CreateDevice)                     // 创建设备 | ||||
| 			deviceGroup.GET("", a.deviceController.ListDevices)                       // 获取设备列表 | ||||
| 			deviceGroup.GET("/:id", a.deviceController.GetDevice)                     // 获取单个设备 | ||||
| 			deviceGroup.PUT("/:id", a.deviceController.UpdateDevice)                  // 更新设备 | ||||
| 			deviceGroup.DELETE("/:id", a.deviceController.DeleteDevice)               // 删除设备 | ||||
| 			deviceGroup.POST("/manual-control/:id", a.deviceController.ManualControl) // 手动控制设备 | ||||
| 		} | ||||
| 		a.logger.Info("设备相关接口注册成功 (需要认证和审计)") | ||||
|  | ||||
| 		// 区域主控相关路由组 | ||||
| 		areaControllerGroup := authGroup.Group("/area-controllers") | ||||
| 		{ | ||||
| 			areaControllerGroup.POST("", a.deviceController.CreateAreaController)       // 创建区域主控 | ||||
| 			areaControllerGroup.GET("", a.deviceController.ListAreaControllers)         // 获取区域主控列表 | ||||
| 			areaControllerGroup.GET("/:id", a.deviceController.GetAreaController)       // 获取单个区域主控 | ||||
| 			areaControllerGroup.PUT("/:id", a.deviceController.UpdateAreaController)    // 更新区域主控 | ||||
| 			areaControllerGroup.DELETE("/:id", a.deviceController.DeleteAreaController) // 删除区域主控 | ||||
| 		} | ||||
| 		a.logger.Info("区域主控相关接口注册成功 (需要认证和审计)") | ||||
|  | ||||
| 		// 设备模板相关路由组 | ||||
| 		deviceTemplateGroup := authGroup.Group("/device-templates") | ||||
| 		{ | ||||
| 			deviceTemplateGroup.POST("", a.deviceController.CreateDeviceTemplate)       // 创建设备模板 | ||||
| 			deviceTemplateGroup.GET("", a.deviceController.ListDeviceTemplates)         // 获取设备模板列表 | ||||
| 			deviceTemplateGroup.GET("/:id", a.deviceController.GetDeviceTemplate)       // 获取单个设备模板 | ||||
| 			deviceTemplateGroup.PUT("/:id", a.deviceController.UpdateDeviceTemplate)    // 更新设备模板 | ||||
| 			deviceTemplateGroup.DELETE("/:id", a.deviceController.DeleteDeviceTemplate) // 删除设备模板 | ||||
| 		} | ||||
| 		a.logger.Info("设备模板相关接口注册成功 (需要认证和审计)") | ||||
|  | ||||
| 		// 计划相关路由组 | ||||
| 		planGroup := authGroup.Group("/plans") | ||||
| 		{ | ||||
| 			planGroup.POST("", a.planController.CreatePlan)          // 创建计划 | ||||
| 			planGroup.GET("", a.planController.ListPlans)            // 获取计划列表 | ||||
| 			planGroup.GET("/:id", a.planController.GetPlan)          // 获取单个计划 | ||||
| 			planGroup.PUT("/:id", a.planController.UpdatePlan)       // 更新计划 | ||||
| 			planGroup.DELETE("/:id", a.planController.DeletePlan)    // 删除计划 | ||||
| 			planGroup.POST("/:id/start", a.planController.StartPlan) // 启动计划 | ||||
| 			planGroup.POST("/:id/stop", a.planController.StopPlan)   // 停止计划 | ||||
| 		} | ||||
| 		a.logger.Info("计划相关接口注册成功 (需要认证和审计)") | ||||
|  | ||||
| 		// 猪舍相关路由组 | ||||
| 		pigHouseGroup := authGroup.Group("/pig-houses") | ||||
| 		{ | ||||
| 			pigHouseGroup.POST("", a.pigFarmController.CreatePigHouse)       // 创建猪舍 | ||||
| 			pigHouseGroup.GET("", a.pigFarmController.ListPigHouses)         // 获取猪舍列表 | ||||
| 			pigHouseGroup.GET("/:id", a.pigFarmController.GetPigHouse)       // 获取单个猪舍 | ||||
| 			pigHouseGroup.PUT("/:id", a.pigFarmController.UpdatePigHouse)    // 更新猪舍 | ||||
| 			pigHouseGroup.DELETE("/:id", a.pigFarmController.DeletePigHouse) // 删除猪舍 | ||||
| 		} | ||||
| 		a.logger.Info("猪舍相关接口注册成功 (需要认证和审计)") | ||||
|  | ||||
| 		// 猪圈相关路由组 | ||||
| 		penGroup := authGroup.Group("/pens") | ||||
| 		{ | ||||
| 			penGroup.POST("", a.pigFarmController.CreatePen)                 // 创建猪圈 | ||||
| 			penGroup.GET("", a.pigFarmController.ListPens)                   // 获取猪圈列表 | ||||
| 			penGroup.GET("/:id", a.pigFarmController.GetPen)                 // 获取单个猪圈 | ||||
| 			penGroup.PUT("/:id", a.pigFarmController.UpdatePen)              // 更新猪圈 | ||||
| 			penGroup.DELETE("/:id", a.pigFarmController.DeletePen)           // 删除猪圈 | ||||
| 			penGroup.PUT("/:id/status", a.pigFarmController.UpdatePenStatus) // 更新猪圈状态 | ||||
| 		} | ||||
| 		a.logger.Info("猪圈相关接口注册成功 (需要认证和审计)") | ||||
|  | ||||
| 		// 猪群相关路由组 | ||||
| 		pigBatchGroup := authGroup.Group("/pig-batches") | ||||
| 		{ | ||||
| 			pigBatchGroup.POST("", a.pigBatchController.CreatePigBatch)                                                   // 创建猪群 | ||||
| 			pigBatchGroup.GET("", a.pigBatchController.ListPigBatches)                                                    // 获取猪群列表 | ||||
| 			pigBatchGroup.GET("/:id", a.pigBatchController.GetPigBatch)                                                   // 获取单个猪群 | ||||
| 			pigBatchGroup.PUT("/:id", a.pigBatchController.UpdatePigBatch)                                                // 更新猪群 | ||||
| 			pigBatchGroup.DELETE("/:id", a.pigBatchController.DeletePigBatch)                                             // 删除猪群 | ||||
| 			pigBatchGroup.POST("/assign-pens/:id", a.pigBatchController.AssignEmptyPensToBatch)                           // 为猪群分配空栏 | ||||
| 			pigBatchGroup.POST("/reclassify-pen/:fromBatchID", a.pigBatchController.ReclassifyPenToNewBatch)              // 将猪栏划拨到新群 | ||||
| 			penGroup.DELETE("/remove-pen/:penID/:batchID", a.pigBatchController.RemoveEmptyPenFromBatch)                  // 从猪群移除空栏 | ||||
| 			pigBatchGroup.POST("/move-pigs-into-pen/:id", a.pigBatchController.MovePigsIntoPen)                           // 将猪只从“虚拟库存”移入指定猪栏 | ||||
| 			pigBatchGroup.POST("/sell-pigs/:id", a.pigBatchController.SellPigs)                                           // 处理卖猪业务 | ||||
| 			pigBatchGroup.POST("/buy-pigs/:id", a.pigBatchController.BuyPigs)                                             // 处理买猪业务 | ||||
| 			pigBatchGroup.POST("/transfer-across-batches/:sourceBatchID", a.pigBatchController.TransferPigsAcrossBatches) // 跨猪群调栏 | ||||
| 			pigBatchGroup.POST("/transfer-within-batch/:id", a.pigBatchController.TransferPigsWithinBatch)                // 群内调栏 | ||||
| 			pigBatchGroup.POST("/record-sick-pigs/:id", a.pigBatchController.RecordSickPigs)                              // 记录新增病猪事件 | ||||
| 			pigBatchGroup.POST("/record-sick-pig-recovery/:id", a.pigBatchController.RecordSickPigRecovery)               // 记录病猪康复事件 | ||||
| 			pigBatchGroup.POST("/record-sick-pig-death/:id", a.pigBatchController.RecordSickPigDeath)                     // 记录病猪死亡事件 | ||||
| 			pigBatchGroup.POST("/record-sick-pig-cull/:id", a.pigBatchController.RecordSickPigCull)                       // 记录病猪淘汰事件 | ||||
| 			pigBatchGroup.POST("/record-death/:id", a.pigBatchController.RecordDeath)                                     // 记录正常猪只死亡事件 | ||||
| 			pigBatchGroup.POST("/record-cull/:id", a.pigBatchController.RecordCull)                                       // 记录正常猪只淘汰事件 | ||||
| 		} | ||||
| 		a.logger.Info("猪群相关接口注册成功 (需要认证和审计)") | ||||
|  | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Start 启动 HTTP 服务器 | ||||
| // 接收一个地址字符串 (例如 ":8080"),并在一个新的 goroutine 中启动服务器, | ||||
| // 以便主线程可以继续执行其他任务(例如监听操作系统信号)。 | ||||
|   | ||||
							
								
								
									
										181
									
								
								internal/app/api/router.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								internal/app/api/router.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,181 @@ | ||||
| package api | ||||
|  | ||||
| import ( | ||||
| 	"net/http/pprof" | ||||
|  | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/app/middleware" | ||||
| 	"github.com/gin-gonic/gin" | ||||
| 	swaggerFiles "github.com/swaggo/files" | ||||
| 	ginSwagger "github.com/swaggo/gin-swagger" | ||||
| ) | ||||
|  | ||||
| // setupRoutes 设置所有 API 路由 | ||||
| // 在此方法中,使用已初始化的控制器实例将其路由注册到 Gin 引擎中。 | ||||
| func (a *API) setupRoutes() { | ||||
|  | ||||
| 	// --- Public Routes --- | ||||
| 	// 这些路由不需要身份验证 | ||||
|  | ||||
| 	// 用户注册和登录 | ||||
| 	a.engine.POST("/api/v1/users", a.userController.CreateUser)  // 注册新用户 | ||||
| 	a.engine.POST("/api/v1/users/login", a.userController.Login) // 用户登录 | ||||
| 	a.logger.Info("公开接口注册成功:用户注册、登录") | ||||
|  | ||||
| 	// 注册 pprof 路由 | ||||
| 	pprofGroup := a.engine.Group("/debug/pprof") | ||||
| 	{ | ||||
| 		pprofGroup.GET("/", gin.WrapF(pprof.Index))                   // pprof 索引页 | ||||
| 		pprofGroup.GET("/cmdline", gin.WrapF(pprof.Cmdline))          // pprof 命令行参数 | ||||
| 		pprofGroup.GET("/profile", gin.WrapF(pprof.Profile))          // pprof CPU profile | ||||
| 		pprofGroup.POST("/symbol", gin.WrapF(pprof.Symbol))           // pprof 符号查找 (POST) | ||||
| 		pprofGroup.GET("/symbol", gin.WrapF(pprof.Symbol))            // pprof 符号查找 (GET) | ||||
| 		pprofGroup.GET("/trace", gin.WrapF(pprof.Trace))              // pprof 跟踪 | ||||
| 		pprofGroup.GET("/allocs", gin.WrapH(pprof.Handler("allocs"))) // pprof 内存分配 | ||||
| 		pprofGroup.GET("/block", gin.WrapH(pprof.Handler("block")))   // pprof 阻塞 | ||||
| 		pprofGroup.GET("/goroutine", gin.WrapH(pprof.Handler("goroutine"))) | ||||
| 		pprofGroup.GET("/heap", gin.WrapH(pprof.Handler("heap")))   // pprof 堆内存 | ||||
| 		pprofGroup.GET("/mutex", gin.WrapH(pprof.Handler("mutex"))) // pprof 互斥锁 | ||||
| 		pprofGroup.GET("/threadcreate", gin.WrapH(pprof.Handler("threadcreate"))) | ||||
| 	} | ||||
| 	a.logger.Info("pprof 接口注册成功") | ||||
|  | ||||
| 	// 上行事件监听路由 | ||||
| 	a.engine.POST("/upstream", gin.WrapH(a.listenHandler.Handler())) // 处理设备上行事件 | ||||
| 	a.logger.Info("上行事件监听接口注册成功") | ||||
|  | ||||
| 	// 添加 Swagger UI 路由, Swagger UI可在 /swagger/index.html 上找到 | ||||
| 	a.engine.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) // Swagger UI 接口 | ||||
| 	a.logger.Info("Swagger UI 接口注册成功") | ||||
|  | ||||
| 	// --- Authenticated Routes --- | ||||
| 	// 所有在此注册的路由都需要通过 JWT 身份验证 | ||||
| 	authGroup := a.engine.Group("/api/v1") | ||||
| 	authGroup.Use(middleware.AuthMiddleware(a.tokenService, a.userRepo)) // 1. 身份认证中间件 | ||||
| 	authGroup.Use(middleware.AuditLogMiddleware(a.auditService))         // 2. 审计日志中间件 | ||||
| 	{ | ||||
| 		// 用户相关路由组 | ||||
| 		userGroup := authGroup.Group("/users") | ||||
| 		{ | ||||
| 			userGroup.GET("/:id/history", a.userController.ListUserHistory) // 获取用户操作历史 | ||||
| 		} | ||||
| 		a.logger.Info("用户相关接口注册成功 (需要认证和审计)") | ||||
|  | ||||
| 		// 设备相关路由组 | ||||
| 		deviceGroup := authGroup.Group("/devices") | ||||
| 		{ | ||||
| 			deviceGroup.POST("", a.deviceController.CreateDevice)                     // 创建设备 | ||||
| 			deviceGroup.GET("", a.deviceController.ListDevices)                       // 获取设备列表 | ||||
| 			deviceGroup.GET("/:id", a.deviceController.GetDevice)                     // 获取单个设备 | ||||
| 			deviceGroup.PUT("/:id", a.deviceController.UpdateDevice)                  // 更新设备 | ||||
| 			deviceGroup.DELETE("/:id", a.deviceController.DeleteDevice)               // 删除设备 | ||||
| 			deviceGroup.POST("/manual-control/:id", a.deviceController.ManualControl) // 手动控制设备 | ||||
| 		} | ||||
| 		a.logger.Info("设备相关接口注册成功 (需要认证和审计)") | ||||
|  | ||||
| 		// 区域主控相关路由组 | ||||
| 		areaControllerGroup := authGroup.Group("/area-controllers") | ||||
| 		{ | ||||
| 			areaControllerGroup.POST("", a.deviceController.CreateAreaController)       // 创建区域主控 | ||||
| 			areaControllerGroup.GET("", a.deviceController.ListAreaControllers)         // 获取区域主控列表 | ||||
| 			areaControllerGroup.GET("/:id", a.deviceController.GetAreaController)       // 获取单个区域主控 | ||||
| 			areaControllerGroup.PUT("/:id", a.deviceController.UpdateAreaController)    // 更新区域主控 | ||||
| 			areaControllerGroup.DELETE("/:id", a.deviceController.DeleteAreaController) // 删除区域主控 | ||||
| 		} | ||||
| 		a.logger.Info("区域主控相关接口注册成功 (需要认证和审计)") | ||||
|  | ||||
| 		// 设备模板相关路由组 | ||||
| 		deviceTemplateGroup := authGroup.Group("/device-templates") | ||||
| 		{ | ||||
| 			deviceTemplateGroup.POST("", a.deviceController.CreateDeviceTemplate)       // 创建设备模板 | ||||
| 			deviceTemplateGroup.GET("", a.deviceController.ListDeviceTemplates)         // 获取设备模板列表 | ||||
| 			deviceTemplateGroup.GET("/:id", a.deviceController.GetDeviceTemplate)       // 获取单个设备模板 | ||||
| 			deviceTemplateGroup.PUT("/:id", a.deviceController.UpdateDeviceTemplate)    // 更新设备模板 | ||||
| 			deviceTemplateGroup.DELETE("/:id", a.deviceController.DeleteDeviceTemplate) // 删除设备模板 | ||||
| 		} | ||||
| 		a.logger.Info("设备模板相关接口注册成功 (需要认证和审计)") | ||||
|  | ||||
| 		// 计划相关路由组 | ||||
| 		planGroup := authGroup.Group("/plans") | ||||
| 		{ | ||||
| 			planGroup.POST("", a.planController.CreatePlan)          // 创建计划 | ||||
| 			planGroup.GET("", a.planController.ListPlans)            // 获取计划列表 | ||||
| 			planGroup.GET("/:id", a.planController.GetPlan)          // 获取单个计划 | ||||
| 			planGroup.PUT("/:id", a.planController.UpdatePlan)       // 更新计划 | ||||
| 			planGroup.DELETE("/:id", a.planController.DeletePlan)    // 删除计划 | ||||
| 			planGroup.POST("/:id/start", a.planController.StartPlan) // 启动计划 | ||||
| 			planGroup.POST("/:id/stop", a.planController.StopPlan)   // 停止计划 | ||||
| 		} | ||||
| 		a.logger.Info("计划相关接口注册成功 (需要认证和审计)") | ||||
|  | ||||
| 		// 猪舍相关路由组 | ||||
| 		pigHouseGroup := authGroup.Group("/pig-houses") | ||||
| 		{ | ||||
| 			pigHouseGroup.POST("", a.pigFarmController.CreatePigHouse)       // 创建猪舍 | ||||
| 			pigHouseGroup.GET("", a.pigFarmController.ListPigHouses)         // 获取猪舍列表 | ||||
| 			pigHouseGroup.GET("/:id", a.pigFarmController.GetPigHouse)       // 获取单个猪舍 | ||||
| 			pigHouseGroup.PUT("/:id", a.pigFarmController.UpdatePigHouse)    // 更新猪舍 | ||||
| 			pigHouseGroup.DELETE("/:id", a.pigFarmController.DeletePigHouse) // 删除猪舍 | ||||
| 		} | ||||
| 		a.logger.Info("猪舍相关接口注册成功 (需要认证和审计)") | ||||
|  | ||||
| 		// 猪圈相关路由组 | ||||
| 		penGroup := authGroup.Group("/pens") | ||||
| 		{ | ||||
| 			penGroup.POST("", a.pigFarmController.CreatePen)                 // 创建猪圈 | ||||
| 			penGroup.GET("", a.pigFarmController.ListPens)                   // 获取猪圈列表 | ||||
| 			penGroup.GET("/:id", a.pigFarmController.GetPen)                 // 获取单个猪圈 | ||||
| 			penGroup.PUT("/:id", a.pigFarmController.UpdatePen)              // 更新猪圈 | ||||
| 			penGroup.DELETE("/:id", a.pigFarmController.DeletePen)           // 删除猪圈 | ||||
| 			penGroup.PUT("/:id/status", a.pigFarmController.UpdatePenStatus) // 更新猪圈状态 | ||||
| 		} | ||||
| 		a.logger.Info("猪圈相关接口注册成功 (需要认证和审计)") | ||||
|  | ||||
| 		// 猪群相关路由组 | ||||
| 		pigBatchGroup := authGroup.Group("/pig-batches") | ||||
| 		{ | ||||
| 			pigBatchGroup.POST("", a.pigBatchController.CreatePigBatch)                                                   // 创建猪群 | ||||
| 			pigBatchGroup.GET("", a.pigBatchController.ListPigBatches)                                                    // 获取猪群列表 | ||||
| 			pigBatchGroup.GET("/:id", a.pigBatchController.GetPigBatch)                                                   // 获取单个猪群 | ||||
| 			pigBatchGroup.PUT("/:id", a.pigBatchController.UpdatePigBatch)                                                // 更新猪群 | ||||
| 			pigBatchGroup.DELETE("/:id", a.pigBatchController.DeletePigBatch)                                             // 删除猪群 | ||||
| 			pigBatchGroup.POST("/assign-pens/:id", a.pigBatchController.AssignEmptyPensToBatch)                           // 为猪群分配空栏 | ||||
| 			pigBatchGroup.POST("/reclassify-pen/:fromBatchID", a.pigBatchController.ReclassifyPenToNewBatch)              // 将猪栏划拨到新群 | ||||
| 			penGroup.DELETE("/remove-pen/:penID/:batchID", a.pigBatchController.RemoveEmptyPenFromBatch)                  // 从猪群移除空栏 | ||||
| 			pigBatchGroup.POST("/move-pigs-into-pen/:id", a.pigBatchController.MovePigsIntoPen)                           // 将猪只从“虚拟库存”移入指定猪栏 | ||||
| 			pigBatchGroup.POST("/sell-pigs/:id", a.pigBatchController.SellPigs)                                           // 处理卖猪业务 | ||||
| 			pigBatchGroup.POST("/buy-pigs/:id", a.pigBatchController.BuyPigs)                                             // 处理买猪业务 | ||||
| 			pigBatchGroup.POST("/transfer-across-batches/:sourceBatchID", a.pigBatchController.TransferPigsAcrossBatches) // 跨猪群调栏 | ||||
| 			pigBatchGroup.POST("/transfer-within-batch/:id", a.pigBatchController.TransferPigsWithinBatch)                // 群内调栏 | ||||
| 			pigBatchGroup.POST("/record-sick-pigs/:id", a.pigBatchController.RecordSickPigs)                              // 记录新增病猪事件 | ||||
| 			pigBatchGroup.POST("/record-sick-pig-recovery/:id", a.pigBatchController.RecordSickPigRecovery)               // 记录病猪康复事件 | ||||
| 			pigBatchGroup.POST("/record-sick-pig-death/:id", a.pigBatchController.RecordSickPigDeath)                     // 记录病猪死亡事件 | ||||
| 			pigBatchGroup.POST("/record-sick-pig-cull/:id", a.pigBatchController.RecordSickPigCull)                       // 记录病猪淘汰事件 | ||||
| 			pigBatchGroup.POST("/record-death/:id", a.pigBatchController.RecordDeath)                                     // 记录正常猪只死亡事件 | ||||
| 			pigBatchGroup.POST("/record-cull/:id", a.pigBatchController.RecordCull)                                       // 记录正常猪只淘汰事件 | ||||
| 		} | ||||
| 		a.logger.Info("猪群相关接口注册成功 (需要认证和审计)") | ||||
|  | ||||
| 		// 数据监控相关路由组 | ||||
| 		monitorGroup := authGroup.Group("/monitor") | ||||
| 		{ | ||||
| 			monitorGroup.GET("/sensor-data", a.monitorController.ListSensorData) | ||||
| 			monitorGroup.GET("/device-command-logs", a.monitorController.ListDeviceCommandLogs) | ||||
| 			monitorGroup.GET("/plan-execution-logs", a.monitorController.ListPlanExecutionLogs) | ||||
| 			monitorGroup.GET("/task-execution-logs", a.monitorController.ListTaskExecutionLogs) | ||||
| 			monitorGroup.GET("/pending-collections", a.monitorController.ListPendingCollections) | ||||
| 			monitorGroup.GET("/user-action-logs", a.monitorController.ListUserActionLogs) | ||||
| 			monitorGroup.GET("/raw-material-purchases", a.monitorController.ListRawMaterialPurchases) | ||||
| 			monitorGroup.GET("/raw-material-stock-logs", a.monitorController.ListRawMaterialStockLogs) | ||||
| 			monitorGroup.GET("/feed-usage-records", a.monitorController.ListFeedUsageRecords) | ||||
| 			monitorGroup.GET("/medication-logs", a.monitorController.ListMedicationLogs) | ||||
| 			monitorGroup.GET("/pig-batch-logs", a.monitorController.ListPigBatchLogs) | ||||
| 			monitorGroup.GET("/weighing-batches", a.monitorController.ListWeighingBatches) | ||||
| 			monitorGroup.GET("/weighing-records", a.monitorController.ListWeighingRecords) | ||||
| 			monitorGroup.GET("/pig-transfer-logs", a.monitorController.ListPigTransferLogs) | ||||
| 			monitorGroup.GET("/pig-sick-logs", a.monitorController.ListPigSickLogs) | ||||
| 			monitorGroup.GET("/pig-purchases", a.monitorController.ListPigPurchases) | ||||
| 			monitorGroup.GET("/pig-sales", a.monitorController.ListPigSales) | ||||
| 		} | ||||
| 		a.logger.Info("数据监控相关接口注册成功 (需要认证和审计)") | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										841
									
								
								internal/app/controller/monitor/monitor_controller.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										841
									
								
								internal/app/controller/monitor/monitor_controller.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,841 @@ | ||||
| package monitor | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
|  | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/app/controller" | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/app/dto" | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/app/service" | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/infra/models" | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" | ||||
| 	"github.com/gin-gonic/gin" | ||||
| ) | ||||
|  | ||||
| // Controller 监控控制器,封装了所有与数据监控相关的业务逻辑 | ||||
| type Controller struct { | ||||
| 	monitorService service.MonitorService | ||||
| 	logger         *logs.Logger | ||||
| } | ||||
|  | ||||
| // NewController 创建一个新的监控控制器实例 | ||||
| func NewController(monitorService service.MonitorService, logger *logs.Logger) *Controller { | ||||
| 	return &Controller{ | ||||
| 		monitorService: monitorService, | ||||
| 		logger:         logger, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // ListSensorData godoc | ||||
| // @Summary      获取传感器数据列表 | ||||
| // @Description  根据提供的过滤条件,分页获取传感器数据 | ||||
| // @Tags         数据监控 | ||||
| // @Security     BearerAuth | ||||
| // @Produce      json | ||||
| // @Param        query query dto.ListSensorDataRequest true "查询参数" | ||||
| // @Success      200 {object} controller.Response{data=dto.ListSensorDataResponse} | ||||
| // @Router       /api/v1/monitor/sensor-data [get] | ||||
| func (c *Controller) ListSensorData(ctx *gin.Context) { | ||||
| 	const actionType = "获取传感器数据列表" | ||||
|  | ||||
| 	var req dto.ListSensorDataRequest | ||||
| 	if err := ctx.ShouldBindQuery(&req); err != nil { | ||||
| 		c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	opts := repository.SensorDataListOptions{ | ||||
| 		DeviceID:  req.DeviceID, | ||||
| 		OrderBy:   req.OrderBy, | ||||
| 		StartTime: req.StartTime, | ||||
| 		EndTime:   req.EndTime, | ||||
| 	} | ||||
| 	if req.SensorType != nil { | ||||
| 		sensorType := models.SensorType(*req.SensorType) | ||||
| 		opts.SensorType = &sensorType | ||||
| 	} | ||||
|  | ||||
| 	data, total, err := c.monitorService.ListSensorData(opts, req.Page, req.PageSize) | ||||
| 	if err != nil { | ||||
| 		if errors.Is(err, repository.ErrInvalidPagination) { | ||||
| 			c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) | ||||
| 			controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取传感器数据失败: "+err.Error(), actionType, "服务层查询失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	resp := dto.NewListSensorDataResponse(data, total, req.Page, req.PageSize) | ||||
| 	c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total) | ||||
| 	controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取传感器数据成功", resp, actionType, "获取传感器数据成功", req) | ||||
| } | ||||
|  | ||||
| // ListDeviceCommandLogs godoc | ||||
| // @Summary      获取设备命令日志列表 | ||||
| // @Description  根据提供的过滤条件,分页获取设备命令日志 | ||||
| // @Tags         数据监控 | ||||
| // @Security     BearerAuth | ||||
| // @Produce      json | ||||
| // @Param        query query dto.ListDeviceCommandLogRequest true "查询参数" | ||||
| // @Success      200 {object} controller.Response{data=dto.ListDeviceCommandLogResponse} | ||||
| // @Router       /api/v1/monitor/device-command-logs [get] | ||||
| func (c *Controller) ListDeviceCommandLogs(ctx *gin.Context) { | ||||
| 	const actionType = "获取设备命令日志列表" | ||||
|  | ||||
| 	var req dto.ListDeviceCommandLogRequest | ||||
| 	if err := ctx.ShouldBindQuery(&req); err != nil { | ||||
| 		c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	opts := repository.DeviceCommandLogListOptions{ | ||||
| 		DeviceID:        req.DeviceID, | ||||
| 		ReceivedSuccess: req.ReceivedSuccess, | ||||
| 		OrderBy:         req.OrderBy, | ||||
| 		StartTime:       req.StartTime, | ||||
| 		EndTime:         req.EndTime, | ||||
| 	} | ||||
|  | ||||
| 	data, total, err := c.monitorService.ListDeviceCommandLogs(opts, req.Page, req.PageSize) | ||||
| 	if err != nil { | ||||
| 		if errors.Is(err, repository.ErrInvalidPagination) { | ||||
| 			c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) | ||||
| 			controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取设备命令日志失败: "+err.Error(), actionType, "服务层查询失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	resp := dto.NewListDeviceCommandLogResponse(data, total, req.Page, req.PageSize) | ||||
| 	c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total) | ||||
| 	controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取设备命令日志成功", resp, actionType, "获取设备命令日志成功", req) | ||||
| } | ||||
|  | ||||
| // ListPlanExecutionLogs godoc | ||||
| // @Summary      获取计划执行日志列表 | ||||
| // @Description  根据提供的过滤条件,分页获取计划执行日志 | ||||
| // @Tags         数据监控 | ||||
| // @Security     BearerAuth | ||||
| // @Produce      json | ||||
| // @Param        query query dto.ListPlanExecutionLogRequest true "查询参数" | ||||
| // @Success      200 {object} controller.Response{data=dto.ListPlanExecutionLogResponse} | ||||
| // @Router       /api/v1/monitor/plan-execution-logs [get] | ||||
| func (c *Controller) ListPlanExecutionLogs(ctx *gin.Context) { | ||||
| 	const actionType = "获取计划执行日志列表" | ||||
|  | ||||
| 	var req dto.ListPlanExecutionLogRequest | ||||
| 	if err := ctx.ShouldBindQuery(&req); err != nil { | ||||
| 		c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	opts := repository.PlanExecutionLogListOptions{ | ||||
| 		PlanID:    req.PlanID, | ||||
| 		OrderBy:   req.OrderBy, | ||||
| 		StartTime: req.StartTime, | ||||
| 		EndTime:   req.EndTime, | ||||
| 	} | ||||
| 	if req.Status != nil { | ||||
| 		status := models.ExecutionStatus(*req.Status) | ||||
| 		opts.Status = &status | ||||
| 	} | ||||
|  | ||||
| 	data, total, err := c.monitorService.ListPlanExecutionLogs(opts, req.Page, req.PageSize) | ||||
| 	if err != nil { | ||||
| 		if errors.Is(err, repository.ErrInvalidPagination) { | ||||
| 			c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) | ||||
| 			controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取计划执行日志失败: "+err.Error(), actionType, "服务层查询失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	resp := dto.NewListPlanExecutionLogResponse(data, total, req.Page, req.PageSize) | ||||
| 	c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total) | ||||
| 	controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取计划执行日志成功", resp, actionType, "获取计划执行日志成功", req) | ||||
| } | ||||
|  | ||||
| // ListTaskExecutionLogs godoc | ||||
| // @Summary      获取任务执行日志列表 | ||||
| // @Description  根据提供的过滤条件,分页获取任务执行日志 | ||||
| // @Tags         数据监控 | ||||
| // @Security     BearerAuth | ||||
| // @Produce      json | ||||
| // @Param        query query dto.ListTaskExecutionLogRequest true "查询参数" | ||||
| // @Success      200 {object} controller.Response{data=dto.ListTaskExecutionLogResponse} | ||||
| // @Router       /api/v1/monitor/task-execution-logs [get] | ||||
| func (c *Controller) ListTaskExecutionLogs(ctx *gin.Context) { | ||||
| 	const actionType = "获取任务执行日志列表" | ||||
|  | ||||
| 	var req dto.ListTaskExecutionLogRequest | ||||
| 	if err := ctx.ShouldBindQuery(&req); err != nil { | ||||
| 		c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	opts := repository.TaskExecutionLogListOptions{ | ||||
| 		PlanExecutionLogID: req.PlanExecutionLogID, | ||||
| 		TaskID:             req.TaskID, | ||||
| 		OrderBy:            req.OrderBy, | ||||
| 		StartTime:          req.StartTime, | ||||
| 		EndTime:            req.EndTime, | ||||
| 	} | ||||
| 	if req.Status != nil { | ||||
| 		status := models.ExecutionStatus(*req.Status) | ||||
| 		opts.Status = &status | ||||
| 	} | ||||
|  | ||||
| 	data, total, err := c.monitorService.ListTaskExecutionLogs(opts, req.Page, req.PageSize) | ||||
| 	if err != nil { | ||||
| 		if errors.Is(err, repository.ErrInvalidPagination) { | ||||
| 			c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) | ||||
| 			controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取任务执行日志失败: "+err.Error(), actionType, "服务层查询失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	resp := dto.NewListTaskExecutionLogResponse(data, total, req.Page, req.PageSize) | ||||
| 	c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total) | ||||
| 	controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取任务执行日志成功", resp, actionType, "获取任务执行日志成功", req) | ||||
| } | ||||
|  | ||||
| // ListPendingCollections godoc | ||||
| // @Summary      获取待采集请求列表 | ||||
| // @Description  根据提供的过滤条件,分页获取待采集请求 | ||||
| // @Tags         数据监控 | ||||
| // @Security     BearerAuth | ||||
| // @Produce      json | ||||
| // @Param        query query dto.ListPendingCollectionRequest true "查询参数" | ||||
| // @Success      200 {object} controller.Response{data=dto.ListPendingCollectionResponse} | ||||
| // @Router       /api/v1/monitor/pending-collections [get] | ||||
| func (c *Controller) ListPendingCollections(ctx *gin.Context) { | ||||
| 	const actionType = "获取待采集请求列表" | ||||
|  | ||||
| 	var req dto.ListPendingCollectionRequest | ||||
| 	if err := ctx.ShouldBindQuery(&req); err != nil { | ||||
| 		c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	opts := repository.PendingCollectionListOptions{ | ||||
| 		DeviceID:  req.DeviceID, | ||||
| 		OrderBy:   req.OrderBy, | ||||
| 		StartTime: req.StartTime, | ||||
| 		EndTime:   req.EndTime, | ||||
| 	} | ||||
| 	if req.Status != nil { | ||||
| 		status := models.PendingCollectionStatus(*req.Status) | ||||
| 		opts.Status = &status | ||||
| 	} | ||||
|  | ||||
| 	data, total, err := c.monitorService.ListPendingCollections(opts, req.Page, req.PageSize) | ||||
| 	if err != nil { | ||||
| 		if errors.Is(err, repository.ErrInvalidPagination) { | ||||
| 			c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) | ||||
| 			controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取待采集请求失败: "+err.Error(), actionType, "服务层查询失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	resp := dto.NewListPendingCollectionResponse(data, total, req.Page, req.PageSize) | ||||
| 	c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total) | ||||
| 	controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取待采集请求成功", resp, actionType, "获取待采集请求成功", req) | ||||
| } | ||||
|  | ||||
| // ListUserActionLogs godoc | ||||
| // @Summary      获取用户操作日志列表 | ||||
| // @Description  根据提供的过滤条件,分页获取用户操作日志 | ||||
| // @Tags         数据监控 | ||||
| // @Security     BearerAuth | ||||
| // @Produce      json | ||||
| // @Param        query query dto.ListUserActionLogRequest true "查询参数" | ||||
| // @Success      200 {object} controller.Response{data=dto.ListUserActionLogResponse} | ||||
| // @Router       /api/v1/monitor/user-action-logs [get] | ||||
| func (c *Controller) ListUserActionLogs(ctx *gin.Context) { | ||||
| 	const actionType = "获取用户操作日志列表" | ||||
|  | ||||
| 	var req dto.ListUserActionLogRequest | ||||
| 	if err := ctx.ShouldBindQuery(&req); err != nil { | ||||
| 		c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	opts := repository.UserActionLogListOptions{ | ||||
| 		UserID:     req.UserID, | ||||
| 		Username:   req.Username, | ||||
| 		ActionType: req.ActionType, | ||||
| 		OrderBy:    req.OrderBy, | ||||
| 		StartTime:  req.StartTime, | ||||
| 		EndTime:    req.EndTime, | ||||
| 	} | ||||
| 	if req.Status != nil { | ||||
| 		status := models.AuditStatus(*req.Status) | ||||
| 		opts.Status = &status | ||||
| 	} | ||||
|  | ||||
| 	data, total, err := c.monitorService.ListUserActionLogs(opts, req.Page, req.PageSize) | ||||
| 	if err != nil { | ||||
| 		if errors.Is(err, repository.ErrInvalidPagination) { | ||||
| 			c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) | ||||
| 			controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取用户操作日志失败: "+err.Error(), actionType, "服务层查询失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	resp := dto.NewListUserActionLogResponse(data, total, req.Page, req.PageSize) | ||||
| 	c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total) | ||||
| 	controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取用户操作日志成功", resp, actionType, "获取用户操作日志成功", req) | ||||
| } | ||||
|  | ||||
| // ListRawMaterialPurchases godoc | ||||
| // @Summary      获取原料采购记录列表 | ||||
| // @Description  根据提供的过滤条件,分页获取原料采购记录 | ||||
| // @Tags         数据监控 | ||||
| // @Security     BearerAuth | ||||
| // @Produce      json | ||||
| // @Param        query query dto.ListRawMaterialPurchaseRequest true "查询参数" | ||||
| // @Success      200 {object} controller.Response{data=dto.ListRawMaterialPurchaseResponse} | ||||
| // @Router       /api/v1/monitor/raw-material-purchases [get] | ||||
| func (c *Controller) ListRawMaterialPurchases(ctx *gin.Context) { | ||||
| 	const actionType = "获取原料采购记录列表" | ||||
|  | ||||
| 	var req dto.ListRawMaterialPurchaseRequest | ||||
| 	if err := ctx.ShouldBindQuery(&req); err != nil { | ||||
| 		c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	opts := repository.RawMaterialPurchaseListOptions{ | ||||
| 		RawMaterialID: req.RawMaterialID, | ||||
| 		Supplier:      req.Supplier, | ||||
| 		OrderBy:       req.OrderBy, | ||||
| 		StartTime:     req.StartTime, | ||||
| 		EndTime:       req.EndTime, | ||||
| 	} | ||||
|  | ||||
| 	data, total, err := c.monitorService.ListRawMaterialPurchases(opts, req.Page, req.PageSize) | ||||
| 	if err != nil { | ||||
| 		if errors.Is(err, repository.ErrInvalidPagination) { | ||||
| 			c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) | ||||
| 			controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取原料采购记录失败: "+err.Error(), actionType, "服务层查询失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	resp := dto.NewListRawMaterialPurchaseResponse(data, total, req.Page, req.PageSize) | ||||
| 	c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total) | ||||
| 	controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取原料采购记录成功", resp, actionType, "获取原料采购记录成功", req) | ||||
| } | ||||
|  | ||||
| // ListRawMaterialStockLogs godoc | ||||
| // @Summary      获取原料库存日志列表 | ||||
| // @Description  根据提供的过滤条件,分页获取原料库存日志 | ||||
| // @Tags         数据监控 | ||||
| // @Security     BearerAuth | ||||
| // @Produce      json | ||||
| // @Param        query query dto.ListRawMaterialStockLogRequest true "查询参数" | ||||
| // @Success      200 {object} controller.Response{data=dto.ListRawMaterialStockLogResponse} | ||||
| // @Router       /api/v1/monitor/raw-material-stock-logs [get] | ||||
| func (c *Controller) ListRawMaterialStockLogs(ctx *gin.Context) { | ||||
| 	const actionType = "获取原料库存日志列表" | ||||
|  | ||||
| 	var req dto.ListRawMaterialStockLogRequest | ||||
| 	if err := ctx.ShouldBindQuery(&req); err != nil { | ||||
| 		c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	opts := repository.RawMaterialStockLogListOptions{ | ||||
| 		RawMaterialID: req.RawMaterialID, | ||||
| 		SourceID:      req.SourceID, | ||||
| 		OrderBy:       req.OrderBy, | ||||
| 		StartTime:     req.StartTime, | ||||
| 		EndTime:       req.EndTime, | ||||
| 	} | ||||
| 	if req.SourceType != nil { | ||||
| 		sourceType := models.StockLogSourceType(*req.SourceType) | ||||
| 		opts.SourceType = &sourceType | ||||
| 	} | ||||
|  | ||||
| 	data, total, err := c.monitorService.ListRawMaterialStockLogs(opts, req.Page, req.PageSize) | ||||
| 	if err != nil { | ||||
| 		if errors.Is(err, repository.ErrInvalidPagination) { | ||||
| 			c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) | ||||
| 			controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取原料库存日志失败: "+err.Error(), actionType, "服务层查询失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	resp := dto.NewListRawMaterialStockLogResponse(data, total, req.Page, req.PageSize) | ||||
| 	c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total) | ||||
| 	controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取原料库存日志成功", resp, actionType, "获取原料库存日志成功", req) | ||||
| } | ||||
|  | ||||
| // ListFeedUsageRecords godoc | ||||
| // @Summary      获取饲料使用记录列表 | ||||
| // @Description  根据提供的过滤条件,分页获取饲料使用记录 | ||||
| // @Tags         数据监控 | ||||
| // @Security     BearerAuth | ||||
| // @Produce      json | ||||
| // @Param        query query dto.ListFeedUsageRecordRequest true "查询参数" | ||||
| // @Success      200 {object} controller.Response{data=dto.ListFeedUsageRecordResponse} | ||||
| // @Router       /api/v1/monitor/feed-usage-records [get] | ||||
| func (c *Controller) ListFeedUsageRecords(ctx *gin.Context) { | ||||
| 	const actionType = "获取饲料使用记录列表" | ||||
|  | ||||
| 	var req dto.ListFeedUsageRecordRequest | ||||
| 	if err := ctx.ShouldBindQuery(&req); err != nil { | ||||
| 		c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	opts := repository.FeedUsageRecordListOptions{ | ||||
| 		PenID:         req.PenID, | ||||
| 		FeedFormulaID: req.FeedFormulaID, | ||||
| 		OperatorID:    req.OperatorID, | ||||
| 		OrderBy:       req.OrderBy, | ||||
| 		StartTime:     req.StartTime, | ||||
| 		EndTime:       req.EndTime, | ||||
| 	} | ||||
|  | ||||
| 	data, total, err := c.monitorService.ListFeedUsageRecords(opts, req.Page, req.PageSize) | ||||
| 	if err != nil { | ||||
| 		if errors.Is(err, repository.ErrInvalidPagination) { | ||||
| 			c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) | ||||
| 			controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取饲料使用记录失败: "+err.Error(), actionType, "服务层查询失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	resp := dto.NewListFeedUsageRecordResponse(data, total, req.Page, req.PageSize) | ||||
| 	c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total) | ||||
| 	controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取饲料使用记录成功", resp, actionType, "获取饲料使用记录成功", req) | ||||
| } | ||||
|  | ||||
| // ListMedicationLogs godoc | ||||
| // @Summary      获取用药记录列表 | ||||
| // @Description  根据提供的过滤条件,分页获取用药记录 | ||||
| // @Tags         数据监控 | ||||
| // @Security     BearerAuth | ||||
| // @Produce      json | ||||
| // @Param        query query dto.ListMedicationLogRequest true "查询参数" | ||||
| // @Success      200 {object} controller.Response{data=dto.ListMedicationLogResponse} | ||||
| // @Router       /api/v1/monitor/medication-logs [get] | ||||
| func (c *Controller) ListMedicationLogs(ctx *gin.Context) { | ||||
| 	const actionType = "获取用药记录列表" | ||||
|  | ||||
| 	var req dto.ListMedicationLogRequest | ||||
| 	if err := ctx.ShouldBindQuery(&req); err != nil { | ||||
| 		c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	opts := repository.MedicationLogListOptions{ | ||||
| 		PigBatchID:   req.PigBatchID, | ||||
| 		MedicationID: req.MedicationID, | ||||
| 		OperatorID:   req.OperatorID, | ||||
| 		OrderBy:      req.OrderBy, | ||||
| 		StartTime:    req.StartTime, | ||||
| 		EndTime:      req.EndTime, | ||||
| 	} | ||||
| 	if req.Reason != nil { | ||||
| 		reason := models.MedicationReasonType(*req.Reason) | ||||
| 		opts.Reason = &reason | ||||
| 	} | ||||
|  | ||||
| 	data, total, err := c.monitorService.ListMedicationLogs(opts, req.Page, req.PageSize) | ||||
| 	if err != nil { | ||||
| 		if errors.Is(err, repository.ErrInvalidPagination) { | ||||
| 			c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) | ||||
| 			controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取用药记录失败: "+err.Error(), actionType, "服务层查询失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	resp := dto.NewListMedicationLogResponse(data, total, req.Page, req.PageSize) | ||||
| 	c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total) | ||||
| 	controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取用药记录成功", resp, actionType, "获取用药记录成功", req) | ||||
| } | ||||
|  | ||||
| // ListPigBatchLogs godoc | ||||
| // @Summary      获取猪批次日志列表 | ||||
| // @Description  根据提供的过滤条件,分页获取猪批次日志 | ||||
| // @Tags         数据监控 | ||||
| // @Security     BearerAuth | ||||
| // @Produce      json | ||||
| // @Param        query query dto.ListPigBatchLogRequest true "查询参数" | ||||
| // @Success      200 {object} controller.Response{data=dto.ListPigBatchLogResponse} | ||||
| // @Router       /api/v1/monitor/pig-batch-logs [get] | ||||
| func (c *Controller) ListPigBatchLogs(ctx *gin.Context) { | ||||
| 	const actionType = "获取猪批次日志列表" | ||||
|  | ||||
| 	var req dto.ListPigBatchLogRequest | ||||
| 	if err := ctx.ShouldBindQuery(&req); err != nil { | ||||
| 		c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	opts := repository.PigBatchLogListOptions{ | ||||
| 		PigBatchID: req.PigBatchID, | ||||
| 		OperatorID: req.OperatorID, | ||||
| 		OrderBy:    req.OrderBy, | ||||
| 		StartTime:  req.StartTime, | ||||
| 		EndTime:    req.EndTime, | ||||
| 	} | ||||
| 	if req.ChangeType != nil { | ||||
| 		changeType := models.LogChangeType(*req.ChangeType) | ||||
| 		opts.ChangeType = &changeType | ||||
| 	} | ||||
|  | ||||
| 	data, total, err := c.monitorService.ListPigBatchLogs(opts, req.Page, req.PageSize) | ||||
| 	if err != nil { | ||||
| 		if errors.Is(err, repository.ErrInvalidPagination) { | ||||
| 			c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) | ||||
| 			controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取猪批次日志失败: "+err.Error(), actionType, "服务层查询失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	resp := dto.NewListPigBatchLogResponse(data, total, req.Page, req.PageSize) | ||||
| 	c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total) | ||||
| 	controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取猪批次日志成功", resp, actionType, "获取猪批次日志成功", req) | ||||
| } | ||||
|  | ||||
| // ListWeighingBatches godoc | ||||
| // @Summary      获取批次称重记录列表 | ||||
| // @Description  根据提供的过滤条件,分页获取批次称重记录 | ||||
| // @Tags         数据监控 | ||||
| // @Security     BearerAuth | ||||
| // @Produce      json | ||||
| // @Param        query query dto.ListWeighingBatchRequest true "查询参数" | ||||
| // @Success      200 {object} controller.Response{data=dto.ListWeighingBatchResponse} | ||||
| // @Router       /api/v1/monitor/weighing-batches [get] | ||||
| func (c *Controller) ListWeighingBatches(ctx *gin.Context) { | ||||
| 	const actionType = "获取批次称重记录列表" | ||||
|  | ||||
| 	var req dto.ListWeighingBatchRequest | ||||
| 	if err := ctx.ShouldBindQuery(&req); err != nil { | ||||
| 		c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	opts := repository.WeighingBatchListOptions{ | ||||
| 		PigBatchID: req.PigBatchID, | ||||
| 		OrderBy:    req.OrderBy, | ||||
| 		StartTime:  req.StartTime, | ||||
| 		EndTime:    req.EndTime, | ||||
| 	} | ||||
|  | ||||
| 	data, total, err := c.monitorService.ListWeighingBatches(opts, req.Page, req.PageSize) | ||||
| 	if err != nil { | ||||
| 		if errors.Is(err, repository.ErrInvalidPagination) { | ||||
| 			c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) | ||||
| 			controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取批次称重记录失败: "+err.Error(), actionType, "服务层查询失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	resp := dto.NewListWeighingBatchResponse(data, total, req.Page, req.PageSize) | ||||
| 	c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total) | ||||
| 	controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取批次称重记录成功", resp, actionType, "获取批次称重记录成功", req) | ||||
| } | ||||
|  | ||||
| // ListWeighingRecords godoc | ||||
| // @Summary      获取单次称重记录列表 | ||||
| // @Description  根据提供的过滤条件,分页获取单次称重记录 | ||||
| // @Tags         数据监控 | ||||
| // @Security     BearerAuth | ||||
| // @Produce      json | ||||
| // @Param        query query dto.ListWeighingRecordRequest true "查询参数" | ||||
| // @Success      200 {object} controller.Response{data=dto.ListWeighingRecordResponse} | ||||
| // @Router       /api/v1/monitor/weighing-records [get] | ||||
| func (c *Controller) ListWeighingRecords(ctx *gin.Context) { | ||||
| 	const actionType = "获取单次称重记录列表" | ||||
|  | ||||
| 	var req dto.ListWeighingRecordRequest | ||||
| 	if err := ctx.ShouldBindQuery(&req); err != nil { | ||||
| 		c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	opts := repository.WeighingRecordListOptions{ | ||||
| 		WeighingBatchID: req.WeighingBatchID, | ||||
| 		PenID:           req.PenID, | ||||
| 		OperatorID:      req.OperatorID, | ||||
| 		OrderBy:         req.OrderBy, | ||||
| 		StartTime:       req.StartTime, | ||||
| 		EndTime:         req.EndTime, | ||||
| 	} | ||||
|  | ||||
| 	data, total, err := c.monitorService.ListWeighingRecords(opts, req.Page, req.PageSize) | ||||
| 	if err != nil { | ||||
| 		if errors.Is(err, repository.ErrInvalidPagination) { | ||||
| 			c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) | ||||
| 			controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取单次称重记录失败: "+err.Error(), actionType, "服务层查询失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	resp := dto.NewListWeighingRecordResponse(data, total, req.Page, req.PageSize) | ||||
| 	c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total) | ||||
| 	controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取单次称重记录成功", resp, actionType, "获取单次称重记录成功", req) | ||||
| } | ||||
|  | ||||
| // ListPigTransferLogs godoc | ||||
| // @Summary      获取猪只迁移日志列表 | ||||
| // @Description  根据提供的过滤条件,分页获取猪只迁移日志 | ||||
| // @Tags         数据监控 | ||||
| // @Security     BearerAuth | ||||
| // @Produce      json | ||||
| // @Param        query query dto.ListPigTransferLogRequest true "查询参数" | ||||
| // @Success      200 {object} controller.Response{data=dto.ListPigTransferLogResponse} | ||||
| // @Router       /api/v1/monitor/pig-transfer-logs [get] | ||||
| func (c *Controller) ListPigTransferLogs(ctx *gin.Context) { | ||||
| 	const actionType = "获取猪只迁移日志列表" | ||||
|  | ||||
| 	var req dto.ListPigTransferLogRequest | ||||
| 	if err := ctx.ShouldBindQuery(&req); err != nil { | ||||
| 		c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	opts := repository.PigTransferLogListOptions{ | ||||
| 		PigBatchID:    req.PigBatchID, | ||||
| 		PenID:         req.PenID, | ||||
| 		OperatorID:    req.OperatorID, | ||||
| 		CorrelationID: req.CorrelationID, | ||||
| 		OrderBy:       req.OrderBy, | ||||
| 		StartTime:     req.StartTime, | ||||
| 		EndTime:       req.EndTime, | ||||
| 	} | ||||
| 	if req.TransferType != nil { | ||||
| 		transferType := models.PigTransferType(*req.TransferType) | ||||
| 		opts.TransferType = &transferType | ||||
| 	} | ||||
|  | ||||
| 	data, total, err := c.monitorService.ListPigTransferLogs(opts, req.Page, req.PageSize) | ||||
| 	if err != nil { | ||||
| 		if errors.Is(err, repository.ErrInvalidPagination) { | ||||
| 			c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) | ||||
| 			controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取猪只迁移日志失败: "+err.Error(), actionType, "服务层查询失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	resp := dto.NewListPigTransferLogResponse(data, total, req.Page, req.PageSize) | ||||
| 	c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total) | ||||
| 	controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取猪只迁移日志成功", resp, actionType, "获取猪只迁移日志成功", req) | ||||
| } | ||||
|  | ||||
| // ListPigSickLogs godoc | ||||
| // @Summary      获取病猪日志列表 | ||||
| // @Description  根据提供的过滤条件,分页获取病猪日志 | ||||
| // @Tags         数据监控 | ||||
| // @Security     BearerAuth | ||||
| // @Produce      json | ||||
| // @Param        query query dto.ListPigSickLogRequest true "查询参数" | ||||
| // @Success      200 {object} controller.Response{data=dto.ListPigSickLogResponse} | ||||
| // @Router       /api/v1/monitor/pig-sick-logs [get] | ||||
| func (c *Controller) ListPigSickLogs(ctx *gin.Context) { | ||||
| 	const actionType = "获取病猪日志列表" | ||||
|  | ||||
| 	var req dto.ListPigSickLogRequest | ||||
| 	if err := ctx.ShouldBindQuery(&req); err != nil { | ||||
| 		c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	opts := repository.PigSickLogListOptions{ | ||||
| 		PigBatchID: req.PigBatchID, | ||||
| 		PenID:      req.PenID, | ||||
| 		OperatorID: req.OperatorID, | ||||
| 		OrderBy:    req.OrderBy, | ||||
| 		StartTime:  req.StartTime, | ||||
| 		EndTime:    req.EndTime, | ||||
| 	} | ||||
| 	if req.Reason != nil { | ||||
| 		reason := models.PigBatchSickPigReasonType(*req.Reason) | ||||
| 		opts.Reason = &reason | ||||
| 	} | ||||
| 	if req.TreatmentLocation != nil { | ||||
| 		treatmentLocation := models.PigBatchSickPigTreatmentLocation(*req.TreatmentLocation) | ||||
| 		opts.TreatmentLocation = &treatmentLocation | ||||
| 	} | ||||
|  | ||||
| 	data, total, err := c.monitorService.ListPigSickLogs(opts, req.Page, req.PageSize) | ||||
| 	if err != nil { | ||||
| 		if errors.Is(err, repository.ErrInvalidPagination) { | ||||
| 			c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) | ||||
| 			controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取病猪日志失败: "+err.Error(), actionType, "服务层查询失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	resp := dto.NewListPigSickLogResponse(data, total, req.Page, req.PageSize) | ||||
| 	c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total) | ||||
| 	controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取病猪日志成功", resp, actionType, "获取病猪日志成功", req) | ||||
| } | ||||
|  | ||||
| // ListPigPurchases godoc | ||||
| // @Summary      获取猪只采购记录列表 | ||||
| // @Description  根据提供的过滤条件,分页获取猪只采购记录 | ||||
| // @Tags         数据监控 | ||||
| // @Security     BearerAuth | ||||
| // @Produce      json | ||||
| // @Param        query query dto.ListPigPurchaseRequest true "查询参数" | ||||
| // @Success      200 {object} controller.Response{data=dto.ListPigPurchaseResponse} | ||||
| // @Router       /api/v1/monitor/pig-purchases [get] | ||||
| func (c *Controller) ListPigPurchases(ctx *gin.Context) { | ||||
| 	const actionType = "获取猪只采购记录列表" | ||||
|  | ||||
| 	var req dto.ListPigPurchaseRequest | ||||
| 	if err := ctx.ShouldBindQuery(&req); err != nil { | ||||
| 		c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	opts := repository.PigPurchaseListOptions{ | ||||
| 		PigBatchID: req.PigBatchID, | ||||
| 		Supplier:   req.Supplier, | ||||
| 		OperatorID: req.OperatorID, | ||||
| 		OrderBy:    req.OrderBy, | ||||
| 		StartTime:  req.StartTime, | ||||
| 		EndTime:    req.EndTime, | ||||
| 	} | ||||
|  | ||||
| 	data, total, err := c.monitorService.ListPigPurchases(opts, req.Page, req.PageSize) | ||||
| 	if err != nil { | ||||
| 		if errors.Is(err, repository.ErrInvalidPagination) { | ||||
| 			c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) | ||||
| 			controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取猪只采购记录失败: "+err.Error(), actionType, "服务层查询失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	resp := dto.NewListPigPurchaseResponse(data, total, req.Page, req.PageSize) | ||||
| 	c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total) | ||||
| 	controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取猪只采购记录成功", resp, actionType, "获取猪只采购记录成功", req) | ||||
| } | ||||
|  | ||||
| // ListPigSales godoc | ||||
| // @Summary      获取猪只售卖记录列表 | ||||
| // @Description  根据提供的过滤条件,分页获取猪只售卖记录 | ||||
| // @Tags         数据监控 | ||||
| // @Security     BearerAuth | ||||
| // @Produce      json | ||||
| // @Param        query query dto.ListPigSaleRequest true "查询参数" | ||||
| // @Success      200 {object} controller.Response{data=dto.ListPigSaleResponse} | ||||
| // @Router       /api/v1/monitor/pig-sales [get] | ||||
| func (c *Controller) ListPigSales(ctx *gin.Context) { | ||||
| 	const actionType = "获取猪只售卖记录列表" | ||||
|  | ||||
| 	var req dto.ListPigSaleRequest | ||||
| 	if err := ctx.ShouldBindQuery(&req); err != nil { | ||||
| 		c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	opts := repository.PigSaleListOptions{ | ||||
| 		PigBatchID: req.PigBatchID, | ||||
| 		Buyer:      req.Buyer, | ||||
| 		OperatorID: req.OperatorID, | ||||
| 		OrderBy:    req.OrderBy, | ||||
| 		StartTime:  req.StartTime, | ||||
| 		EndTime:    req.EndTime, | ||||
| 	} | ||||
|  | ||||
| 	data, total, err := c.monitorService.ListPigSales(opts, req.Page, req.PageSize) | ||||
| 	if err != nil { | ||||
| 		if errors.Is(err, repository.ErrInvalidPagination) { | ||||
| 			c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) | ||||
| 			controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", req) | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取猪只售卖记录失败: "+err.Error(), actionType, "服务层查询失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	resp := dto.NewListPigSaleResponse(data, total, req.Page, req.PageSize) | ||||
| 	c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total) | ||||
| 	controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取猪只售卖记录成功", resp, actionType, "获取猪只售卖记录成功", req) | ||||
| } | ||||
| @@ -1,11 +1,12 @@ | ||||
| package user | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"strconv" | ||||
| 	"time" | ||||
|  | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/app/controller" | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/app/dto" | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/app/service" | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/domain/token" | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/infra/models" | ||||
| @@ -16,19 +17,19 @@ import ( | ||||
|  | ||||
| // Controller 用户控制器 | ||||
| type Controller struct { | ||||
| 	userRepo     repository.UserRepository | ||||
| 	auditRepo    repository.UserActionLogRepository | ||||
| 	logger       *logs.Logger | ||||
| 	tokenService token.TokenService // 注入 token 服务 | ||||
| 	userRepo       repository.UserRepository | ||||
| 	monitorService service.MonitorService | ||||
| 	tokenService   token.TokenService // 注入 token 服务 | ||||
| 	logger         *logs.Logger | ||||
| } | ||||
|  | ||||
| // NewController 创建用户控制器实例 | ||||
| func NewController(userRepo repository.UserRepository, auditRepo repository.UserActionLogRepository, logger *logs.Logger, tokenService token.TokenService) *Controller { | ||||
| func NewController(userRepo repository.UserRepository, monitorService service.MonitorService, logger *logs.Logger, tokenService token.TokenService) *Controller { | ||||
| 	return &Controller{ | ||||
| 		userRepo:     userRepo, | ||||
| 		auditRepo:    auditRepo, | ||||
| 		logger:       logger, | ||||
| 		tokenService: tokenService, | ||||
| 		userRepo:       userRepo, | ||||
| 		monitorService: monitorService, | ||||
| 		tokenService:   tokenService, | ||||
| 		logger:         logger, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @@ -128,20 +129,18 @@ func (c *Controller) Login(ctx *gin.Context) { | ||||
|  | ||||
| // ListUserHistory godoc | ||||
| // @Summary      获取指定用户的操作历史 | ||||
| // @Description  根据用户ID,分页获取该用户的操作审计日志。 | ||||
| // @Description  根据用户ID,分页获取该用户的操作审计日志。支持与通用日志查询接口相同的过滤和排序参数。 | ||||
| // @Tags         用户管理 | ||||
| // @Security     BearerAuth | ||||
| // @Produce      json | ||||
| // @Param        id   path      int    true  "用户ID" | ||||
| // @Param        page query     int    false "页码" default(1) | ||||
| // @Param        page_size query int    false "每页大小" default(10) | ||||
| // @Param        action_type query string false "按操作类型过滤" | ||||
| // @Success      200  {object}  controller.Response{data=dto.ListHistoryResponse} "业务码为200代表成功获取" | ||||
| // @Param        id    path      int  true  "用户ID" | ||||
| // @Param        query query     dto.ListUserActionLogRequest false "查询参数 (除了 user_id,它被路径中的ID覆盖)" | ||||
| // @Success      200   {object}  controller.Response{data=dto.ListUserActionLogResponse} "业务码为200代表成功获取" | ||||
| // @Router       /api/v1/users/{id}/history [get] | ||||
| func (c *Controller) ListUserHistory(ctx *gin.Context) { | ||||
| 	const actionType = "获取用户操作历史" | ||||
|  | ||||
| 	// 1. 解析路径中的用户ID | ||||
| 	// 1. 解析路径中的用户ID,它的优先级最高 | ||||
| 	userIDStr := ctx.Param("id") | ||||
| 	userID, err := strconv.ParseUint(userIDStr, 10, 64) | ||||
| 	if err != nil { | ||||
| @@ -150,52 +149,46 @@ func (c *Controller) ListUserHistory(ctx *gin.Context) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// 2. 解析分页和过滤参数 | ||||
| 	page, _ := strconv.Atoi(ctx.DefaultQuery("page", "1")) | ||||
| 	pageSize, _ := strconv.Atoi(ctx.DefaultQuery("page_size", "10")) | ||||
| 	actionTypeFilter := ctx.Query("action_type") | ||||
|  | ||||
| 	// 确保分页参数有效 | ||||
| 	if page <= 0 { | ||||
| 		page = 1 | ||||
| 	} | ||||
| 	if pageSize <= 0 || pageSize > 100 { | ||||
| 		pageSize = 10 | ||||
| 	} | ||||
|  | ||||
| 	// 3. 调用审计仓库层获取历史数据 | ||||
| 	id := uint(userID) | ||||
| 	findOptions := repository.FindAuditLogOptions{ | ||||
| 		UserID:     &id, | ||||
| 		ActionType: actionTypeFilter, | ||||
| 		Page:       page, | ||||
| 		PageSize:   pageSize, | ||||
| 	} | ||||
| 	logs, total, err := c.auditRepo.List(findOptions) | ||||
| 	if err != nil { | ||||
| 		c.logger.Errorf("%s: 查询历史记录失败: %v, Options: %+v", actionType, err, findOptions) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "查询历史记录失败", actionType, "查询历史记录失败", findOptions) | ||||
| 	// 2. 绑定通用的查询请求 DTO | ||||
| 	var req dto.ListUserActionLogRequest | ||||
| 	if err := ctx.ShouldBindQuery(&req); err != nil { | ||||
| 		c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// 4. 将数据库模型转换为响应 DTO | ||||
| 	historyResponses := make([]dto.HistoryResponse, 0, len(logs)) | ||||
| 	for _, log := range logs { | ||||
| 		historyResponses = append(historyResponses, dto.HistoryResponse{ | ||||
| 			UserID:         log.UserID, | ||||
| 			Username:       log.Username, | ||||
| 			ActionType:     log.ActionType, | ||||
| 			Description:    log.Description, | ||||
| 			TargetResource: log.TargetResource, | ||||
| 			Time:           log.Time.Format(time.RFC3339), | ||||
| 		}) | ||||
| 	// 3. 准备 Service 调用参数,并强制使用路径中的 UserID | ||||
| 	uid := uint(userID) | ||||
| 	req.UserID = &uid // 强制覆盖 | ||||
|  | ||||
| 	opts := repository.UserActionLogListOptions{ | ||||
| 		UserID:     req.UserID, | ||||
| 		Username:   req.Username, | ||||
| 		ActionType: req.ActionType, | ||||
| 		OrderBy:    req.OrderBy, | ||||
| 		StartTime:  req.StartTime, | ||||
| 		EndTime:    req.EndTime, | ||||
| 	} | ||||
| 	if req.Status != nil { | ||||
| 		status := models.AuditStatus(*req.Status) | ||||
| 		opts.Status = &status | ||||
| 	} | ||||
|  | ||||
| 	// 5. 发送成功响应 | ||||
| 	resp := dto.ListHistoryResponse{ | ||||
| 		History: historyResponses, | ||||
| 		Total:   total, | ||||
| 	// 4. 调用 monitorService,复用其业务逻辑 | ||||
| 	data, total, err := c.monitorService.ListUserActionLogs(opts, req.Page, req.PageSize) | ||||
| 	if err != nil { | ||||
| 		if errors.Is(err, repository.ErrInvalidPagination) { | ||||
| 			c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err) | ||||
| 			controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", opts) | ||||
| 			return | ||||
| 		} | ||||
| 		c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取用户历史记录失败", actionType, "服务层查询失败", opts) | ||||
| 		return | ||||
| 	} | ||||
| 	c.logger.Infof("%s: 成功获取用户 %d 的操作历史, 数量: %d", actionType, userID, len(historyResponses)) | ||||
| 	controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取用户操作历史成功", resp, actionType, "获取用户操作历史成功", resp) | ||||
|  | ||||
| 	// 5. 使用复用的 DTO 构建并发送成功响应 | ||||
| 	resp := dto.NewListUserActionLogResponse(data, total, req.Page, req.PageSize) | ||||
| 	c.logger.Infof("%s: 成功获取用户 %d 的操作历史, 数量: %d", actionType, userID, len(data)) | ||||
| 	controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取用户操作历史成功", resp, actionType, "获取用户操作历史成功", opts) | ||||
| } | ||||
|   | ||||
							
								
								
									
										483
									
								
								internal/app/dto/monitor_converter.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										483
									
								
								internal/app/dto/monitor_converter.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,483 @@ | ||||
| package dto | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
|  | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/infra/models" | ||||
| ) | ||||
|  | ||||
| // NewListSensorDataResponse 从模型数据创建列表响应 DTO | ||||
| func NewListSensorDataResponse(data []models.SensorData, total int64, page, pageSize int) *ListSensorDataResponse { | ||||
| 	dtos := make([]SensorDataDTO, len(data)) | ||||
| 	for i, item := range data { | ||||
| 		dtos[i] = SensorDataDTO{ | ||||
| 			Time:                 item.Time, | ||||
| 			DeviceID:             item.DeviceID, | ||||
| 			RegionalControllerID: item.RegionalControllerID, | ||||
| 			SensorType:           item.SensorType, | ||||
| 			Data:                 json.RawMessage(item.Data), | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return &ListSensorDataResponse{ | ||||
| 		List: dtos, | ||||
| 		Pagination: PaginationDTO{ | ||||
| 			Total:    total, | ||||
| 			Page:     page, | ||||
| 			PageSize: pageSize, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewListDeviceCommandLogResponse 从模型数据创建列表响应 DTO | ||||
| func NewListDeviceCommandLogResponse(data []models.DeviceCommandLog, total int64, page, pageSize int) *ListDeviceCommandLogResponse { | ||||
| 	dtos := make([]DeviceCommandLogDTO, len(data)) | ||||
| 	for i, item := range data { | ||||
| 		dtos[i] = DeviceCommandLogDTO{ | ||||
| 			MessageID:       item.MessageID, | ||||
| 			DeviceID:        item.DeviceID, | ||||
| 			SentAt:          item.SentAt, | ||||
| 			AcknowledgedAt:  item.AcknowledgedAt, | ||||
| 			ReceivedSuccess: item.ReceivedSuccess, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return &ListDeviceCommandLogResponse{ | ||||
| 		List: dtos, | ||||
| 		Pagination: PaginationDTO{ | ||||
| 			Total:    total, | ||||
| 			Page:     page, | ||||
| 			PageSize: pageSize, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewListPlanExecutionLogResponse 从模型数据创建列表响应 DTO | ||||
| func NewListPlanExecutionLogResponse(data []models.PlanExecutionLog, total int64, page, pageSize int) *ListPlanExecutionLogResponse { | ||||
| 	dtos := make([]PlanExecutionLogDTO, len(data)) | ||||
| 	for i, item := range data { | ||||
| 		dtos[i] = PlanExecutionLogDTO{ | ||||
| 			ID:        item.ID, | ||||
| 			CreatedAt: item.CreatedAt, | ||||
| 			UpdatedAt: item.UpdatedAt, | ||||
| 			PlanID:    item.PlanID, | ||||
| 			Status:    item.Status, | ||||
| 			StartedAt: item.StartedAt, | ||||
| 			EndedAt:   item.EndedAt, | ||||
| 			Error:     item.Error, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return &ListPlanExecutionLogResponse{ | ||||
| 		List: dtos, | ||||
| 		Pagination: PaginationDTO{ | ||||
| 			Total:    total, | ||||
| 			Page:     page, | ||||
| 			PageSize: pageSize, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewListTaskExecutionLogResponse 从模型数据创建列表响应 DTO | ||||
| func NewListTaskExecutionLogResponse(data []models.TaskExecutionLog, total int64, page, pageSize int) *ListTaskExecutionLogResponse { | ||||
| 	dtos := make([]TaskExecutionLogDTO, len(data)) | ||||
| 	for i, item := range data { | ||||
| 		dtos[i] = TaskExecutionLogDTO{ | ||||
| 			ID:                 item.ID, | ||||
| 			CreatedAt:          item.CreatedAt, | ||||
| 			UpdatedAt:          item.UpdatedAt, | ||||
| 			PlanExecutionLogID: item.PlanExecutionLogID, | ||||
| 			TaskID:             item.TaskID, | ||||
| 			Task: TaskDTO{ | ||||
| 				ID:          uint(item.Task.ID), | ||||
| 				Name:        item.Task.Name, | ||||
| 				Description: item.Task.Description, | ||||
| 			}, | ||||
| 			Status:    item.Status, | ||||
| 			Output:    item.Output, | ||||
| 			StartedAt: item.StartedAt, | ||||
| 			EndedAt:   item.EndedAt, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return &ListTaskExecutionLogResponse{ | ||||
| 		List: dtos, | ||||
| 		Pagination: PaginationDTO{ | ||||
| 			Total:    total, | ||||
| 			Page:     page, | ||||
| 			PageSize: pageSize, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewListPendingCollectionResponse 从模型数据创建列表响应 DTO | ||||
| func NewListPendingCollectionResponse(data []models.PendingCollection, total int64, page, pageSize int) *ListPendingCollectionResponse { | ||||
| 	dtos := make([]PendingCollectionDTO, len(data)) | ||||
| 	for i, item := range data { | ||||
| 		dtos[i] = PendingCollectionDTO{ | ||||
| 			CorrelationID:   item.CorrelationID, | ||||
| 			DeviceID:        item.DeviceID, | ||||
| 			CommandMetadata: item.CommandMetadata, | ||||
| 			Status:          item.Status, | ||||
| 			FulfilledAt:     item.FulfilledAt, | ||||
| 			CreatedAt:       item.CreatedAt, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return &ListPendingCollectionResponse{ | ||||
| 		List: dtos, | ||||
| 		Pagination: PaginationDTO{ | ||||
| 			Total:    total, | ||||
| 			Page:     page, | ||||
| 			PageSize: pageSize, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewListUserActionLogResponse 从模型数据创建列表响应 DTO | ||||
| func NewListUserActionLogResponse(data []models.UserActionLog, total int64, page, pageSize int) *ListUserActionLogResponse { | ||||
| 	dtos := make([]UserActionLogDTO, len(data)) | ||||
| 	for i, item := range data { | ||||
| 		dtos[i] = UserActionLogDTO{ | ||||
| 			ID:             item.ID, | ||||
| 			Time:           item.Time, | ||||
| 			UserID:         item.UserID, | ||||
| 			Username:       item.Username, | ||||
| 			SourceIP:       item.SourceIP, | ||||
| 			ActionType:     item.ActionType, | ||||
| 			TargetResource: json.RawMessage(item.TargetResource), | ||||
| 			Description:    item.Description, | ||||
| 			Status:         item.Status, | ||||
| 			HTTPPath:       item.HTTPPath, | ||||
| 			HTTPMethod:     item.HTTPMethod, | ||||
| 			ResultDetails:  item.ResultDetails, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return &ListUserActionLogResponse{ | ||||
| 		List: dtos, | ||||
| 		Pagination: PaginationDTO{ | ||||
| 			Total:    total, | ||||
| 			Page:     page, | ||||
| 			PageSize: pageSize, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewListRawMaterialPurchaseResponse 从模型数据创建列表响应 DTO | ||||
| func NewListRawMaterialPurchaseResponse(data []models.RawMaterialPurchase, total int64, page, pageSize int) *ListRawMaterialPurchaseResponse { | ||||
| 	dtos := make([]RawMaterialPurchaseDTO, len(data)) | ||||
| 	for i, item := range data { | ||||
| 		dtos[i] = RawMaterialPurchaseDTO{ | ||||
| 			ID:            item.ID, | ||||
| 			RawMaterialID: item.RawMaterialID, | ||||
| 			RawMaterial: RawMaterialDTO{ | ||||
| 				ID:   item.RawMaterial.ID, | ||||
| 				Name: item.RawMaterial.Name, | ||||
| 			}, | ||||
| 			Supplier:     item.Supplier, | ||||
| 			Amount:       item.Amount, | ||||
| 			UnitPrice:    item.UnitPrice, | ||||
| 			TotalPrice:   item.TotalPrice, | ||||
| 			PurchaseDate: item.PurchaseDate, | ||||
| 			CreatedAt:    item.CreatedAt, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return &ListRawMaterialPurchaseResponse{ | ||||
| 		List: dtos, | ||||
| 		Pagination: PaginationDTO{ | ||||
| 			Total:    total, | ||||
| 			Page:     page, | ||||
| 			PageSize: pageSize, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewListRawMaterialStockLogResponse 从模型数据创建列表响应 DTO | ||||
| func NewListRawMaterialStockLogResponse(data []models.RawMaterialStockLog, total int64, page, pageSize int) *ListRawMaterialStockLogResponse { | ||||
| 	dtos := make([]RawMaterialStockLogDTO, len(data)) | ||||
| 	for i, item := range data { | ||||
| 		dtos[i] = RawMaterialStockLogDTO{ | ||||
| 			ID:            item.ID, | ||||
| 			RawMaterialID: item.RawMaterialID, | ||||
| 			ChangeAmount:  item.ChangeAmount, | ||||
| 			SourceType:    item.SourceType, | ||||
| 			SourceID:      item.SourceID, | ||||
| 			HappenedAt:    item.HappenedAt, | ||||
| 			Remarks:       item.Remarks, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return &ListRawMaterialStockLogResponse{ | ||||
| 		List: dtos, | ||||
| 		Pagination: PaginationDTO{ | ||||
| 			Total:    total, | ||||
| 			Page:     page, | ||||
| 			PageSize: pageSize, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewListFeedUsageRecordResponse 从模型数据创建列表响应 DTO | ||||
| func NewListFeedUsageRecordResponse(data []models.FeedUsageRecord, total int64, page, pageSize int) *ListFeedUsageRecordResponse { | ||||
| 	dtos := make([]FeedUsageRecordDTO, len(data)) | ||||
| 	for i, item := range data { | ||||
| 		dtos[i] = FeedUsageRecordDTO{ | ||||
| 			ID:    item.ID, | ||||
| 			PenID: item.PenID, | ||||
| 			Pen: PenDTO{ | ||||
| 				ID:   item.Pen.ID, | ||||
| 				Name: item.Pen.PenNumber, | ||||
| 			}, | ||||
| 			FeedFormulaID: item.FeedFormulaID, | ||||
| 			FeedFormula: FeedFormulaDTO{ | ||||
| 				ID:   item.FeedFormula.ID, | ||||
| 				Name: item.FeedFormula.Name, | ||||
| 			}, | ||||
| 			Amount:     item.Amount, | ||||
| 			RecordedAt: item.RecordedAt, | ||||
| 			OperatorID: item.OperatorID, | ||||
| 			Remarks:    item.Remarks, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return &ListFeedUsageRecordResponse{ | ||||
| 		List: dtos, | ||||
| 		Pagination: PaginationDTO{ | ||||
| 			Total:    total, | ||||
| 			Page:     page, | ||||
| 			PageSize: pageSize, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewListMedicationLogResponse 从模型数据创建列表响应 DTO | ||||
| func NewListMedicationLogResponse(data []models.MedicationLog, total int64, page, pageSize int) *ListMedicationLogResponse { | ||||
| 	dtos := make([]MedicationLogDTO, len(data)) | ||||
| 	for i, item := range data { | ||||
| 		dtos[i] = MedicationLogDTO{ | ||||
| 			ID:           item.ID, | ||||
| 			PigBatchID:   item.PigBatchID, | ||||
| 			MedicationID: item.MedicationID, | ||||
| 			Medication: MedicationDTO{ | ||||
| 				ID:   item.Medication.ID, | ||||
| 				Name: item.Medication.Name, | ||||
| 			}, | ||||
| 			DosageUsed:  item.DosageUsed, | ||||
| 			TargetCount: item.TargetCount, | ||||
| 			Reason:      item.Reason, | ||||
| 			Description: item.Description, | ||||
| 			OperatorID:  item.OperatorID, | ||||
| 			HappenedAt:  item.HappenedAt, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return &ListMedicationLogResponse{ | ||||
| 		List: dtos, | ||||
| 		Pagination: PaginationDTO{ | ||||
| 			Total:    total, | ||||
| 			Page:     page, | ||||
| 			PageSize: pageSize, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewListPigBatchLogResponse 从模型数据创建列表响应 DTO | ||||
| func NewListPigBatchLogResponse(data []models.PigBatchLog, total int64, page, pageSize int) *ListPigBatchLogResponse { | ||||
| 	dtos := make([]PigBatchLogDTO, len(data)) | ||||
| 	for i, item := range data { | ||||
| 		dtos[i] = PigBatchLogDTO{ | ||||
| 			ID:          item.ID, | ||||
| 			CreatedAt:   item.CreatedAt, | ||||
| 			UpdatedAt:   item.UpdatedAt, | ||||
| 			PigBatchID:  item.PigBatchID, | ||||
| 			ChangeType:  item.ChangeType, | ||||
| 			ChangeCount: item.ChangeCount, | ||||
| 			Reason:      item.Reason, | ||||
| 			BeforeCount: item.BeforeCount, | ||||
| 			AfterCount:  item.AfterCount, | ||||
| 			OperatorID:  item.OperatorID, | ||||
| 			HappenedAt:  item.HappenedAt, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return &ListPigBatchLogResponse{ | ||||
| 		List: dtos, | ||||
| 		Pagination: PaginationDTO{ | ||||
| 			Total:    total, | ||||
| 			Page:     page, | ||||
| 			PageSize: pageSize, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewListWeighingBatchResponse 从模型数据创建列表响应 DTO | ||||
| func NewListWeighingBatchResponse(data []models.WeighingBatch, total int64, page, pageSize int) *ListWeighingBatchResponse { | ||||
| 	dtos := make([]WeighingBatchDTO, len(data)) | ||||
| 	for i, item := range data { | ||||
| 		dtos[i] = WeighingBatchDTO{ | ||||
| 			ID:           item.ID, | ||||
| 			CreatedAt:    item.CreatedAt, | ||||
| 			UpdatedAt:    item.UpdatedAt, | ||||
| 			WeighingTime: item.WeighingTime, | ||||
| 			Description:  item.Description, | ||||
| 			PigBatchID:   item.PigBatchID, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return &ListWeighingBatchResponse{ | ||||
| 		List: dtos, | ||||
| 		Pagination: PaginationDTO{ | ||||
| 			Total:    total, | ||||
| 			Page:     page, | ||||
| 			PageSize: pageSize, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewListWeighingRecordResponse 从模型数据创建列表响应 DTO | ||||
| func NewListWeighingRecordResponse(data []models.WeighingRecord, total int64, page, pageSize int) *ListWeighingRecordResponse { | ||||
| 	dtos := make([]WeighingRecordDTO, len(data)) | ||||
| 	for i, item := range data { | ||||
| 		dtos[i] = WeighingRecordDTO{ | ||||
| 			ID:              item.ID, | ||||
| 			CreatedAt:       item.CreatedAt, | ||||
| 			UpdatedAt:       item.UpdatedAt, | ||||
| 			Weight:          item.Weight, | ||||
| 			WeighingBatchID: item.WeighingBatchID, | ||||
| 			PenID:           item.PenID, | ||||
| 			OperatorID:      item.OperatorID, | ||||
| 			Remark:          item.Remark, | ||||
| 			WeighingTime:    item.WeighingTime, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return &ListWeighingRecordResponse{ | ||||
| 		List: dtos, | ||||
| 		Pagination: PaginationDTO{ | ||||
| 			Total:    total, | ||||
| 			Page:     page, | ||||
| 			PageSize: pageSize, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewListPigTransferLogResponse 从模型数据创建列表响应 DTO | ||||
| func NewListPigTransferLogResponse(data []models.PigTransferLog, total int64, page, pageSize int) *ListPigTransferLogResponse { | ||||
| 	dtos := make([]PigTransferLogDTO, len(data)) | ||||
| 	for i, item := range data { | ||||
| 		// 注意:PigTransferLog 的 ID, CreatedAt, UpdatedAt 字段是 gorm.Model 嵌入的 | ||||
| 		dtos[i] = PigTransferLogDTO{ | ||||
| 			ID:            item.ID, | ||||
| 			CreatedAt:     item.CreatedAt, | ||||
| 			UpdatedAt:     item.UpdatedAt, | ||||
| 			TransferTime:  item.TransferTime, | ||||
| 			PigBatchID:    item.PigBatchID, | ||||
| 			PenID:         item.PenID, | ||||
| 			Quantity:      item.Quantity, | ||||
| 			Type:          item.Type, | ||||
| 			CorrelationID: item.CorrelationID, | ||||
| 			OperatorID:    item.OperatorID, | ||||
| 			Remarks:       item.Remarks, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return &ListPigTransferLogResponse{ | ||||
| 		List: dtos, | ||||
| 		Pagination: PaginationDTO{ | ||||
| 			Total:    total, | ||||
| 			Page:     page, | ||||
| 			PageSize: pageSize, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewListPigSickLogResponse 从模型数据创建列表响应 DTO | ||||
| func NewListPigSickLogResponse(data []models.PigSickLog, total int64, page, pageSize int) *ListPigSickLogResponse { | ||||
| 	dtos := make([]PigSickLogDTO, len(data)) | ||||
| 	for i, item := range data { | ||||
| 		dtos[i] = PigSickLogDTO{ | ||||
| 			ID:                item.ID, | ||||
| 			CreatedAt:         item.CreatedAt, | ||||
| 			UpdatedAt:         item.UpdatedAt, | ||||
| 			PigBatchID:        item.PigBatchID, | ||||
| 			PenID:             item.PenID, | ||||
| 			ChangeCount:       item.ChangeCount, | ||||
| 			Reason:            item.Reason, | ||||
| 			BeforeCount:       item.BeforeCount, | ||||
| 			AfterCount:        item.AfterCount, | ||||
| 			Remarks:           item.Remarks, | ||||
| 			TreatmentLocation: item.TreatmentLocation, | ||||
| 			OperatorID:        item.OperatorID, | ||||
| 			HappenedAt:        item.HappenedAt, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return &ListPigSickLogResponse{ | ||||
| 		List: dtos, | ||||
| 		Pagination: PaginationDTO{ | ||||
| 			Total:    total, | ||||
| 			Page:     page, | ||||
| 			PageSize: pageSize, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewListPigPurchaseResponse 从模型数据创建列表响应 DTO | ||||
| func NewListPigPurchaseResponse(data []models.PigPurchase, total int64, page, pageSize int) *ListPigPurchaseResponse { | ||||
| 	dtos := make([]PigPurchaseDTO, len(data)) | ||||
| 	for i, item := range data { | ||||
| 		dtos[i] = PigPurchaseDTO{ | ||||
| 			ID:           item.ID, | ||||
| 			CreatedAt:    item.CreatedAt, | ||||
| 			UpdatedAt:    item.UpdatedAt, | ||||
| 			PigBatchID:   item.PigBatchID, | ||||
| 			PurchaseDate: item.PurchaseDate, | ||||
| 			Supplier:     item.Supplier, | ||||
| 			Quantity:     item.Quantity, | ||||
| 			UnitPrice:    item.UnitPrice, | ||||
| 			TotalPrice:   item.TotalPrice, | ||||
| 			Remarks:      item.Remarks, | ||||
| 			OperatorID:   item.OperatorID, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return &ListPigPurchaseResponse{ | ||||
| 		List: dtos, | ||||
| 		Pagination: PaginationDTO{ | ||||
| 			Total:    total, | ||||
| 			Page:     page, | ||||
| 			PageSize: pageSize, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewListPigSaleResponse 从模型数据创建列表响应 DTO | ||||
| func NewListPigSaleResponse(data []models.PigSale, total int64, page, pageSize int) *ListPigSaleResponse { | ||||
| 	dtos := make([]PigSaleDTO, len(data)) | ||||
| 	for i, item := range data { | ||||
| 		dtos[i] = PigSaleDTO{ | ||||
| 			ID:         item.ID, | ||||
| 			CreatedAt:  item.CreatedAt, | ||||
| 			UpdatedAt:  item.UpdatedAt, | ||||
| 			PigBatchID: item.PigBatchID, | ||||
| 			SaleDate:   item.SaleDate, | ||||
| 			Buyer:      item.Buyer, | ||||
| 			Quantity:   item.Quantity, | ||||
| 			UnitPrice:  item.UnitPrice, | ||||
| 			TotalPrice: item.TotalPrice, | ||||
| 			Remarks:    item.Remarks, | ||||
| 			OperatorID: item.OperatorID, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return &ListPigSaleResponse{ | ||||
| 		List: dtos, | ||||
| 		Pagination: PaginationDTO{ | ||||
| 			Total:    total, | ||||
| 			Page:     page, | ||||
| 			PageSize: pageSize, | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										608
									
								
								internal/app/dto/monitor_dto.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										608
									
								
								internal/app/dto/monitor_dto.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,608 @@ | ||||
| package dto | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"time" | ||||
|  | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/infra/models" | ||||
| ) | ||||
|  | ||||
| // --- General --- | ||||
|  | ||||
| // PaginationDTO 定义了分页信息的标准结构 | ||||
| type PaginationDTO struct { | ||||
| 	Total    int64 `json:"total"` | ||||
| 	Page     int   `json:"page"` | ||||
| 	PageSize int   `json:"pageSize"` | ||||
| } | ||||
|  | ||||
| // --- SensorData --- | ||||
|  | ||||
| // ListSensorDataRequest 定义了获取传感器数据列表的请求参数 | ||||
| type ListSensorDataRequest struct { | ||||
| 	Page       int        `form:"page,default=1"` | ||||
| 	PageSize   int        `form:"pageSize,default=10"` | ||||
| 	DeviceID   *uint      `form:"device_id"` | ||||
| 	SensorType *string    `form:"sensor_type"` | ||||
| 	StartTime  *time.Time `form:"start_time" time_format:"rfc3339"` | ||||
| 	EndTime    *time.Time `form:"end_time" time_format:"rfc3339"` | ||||
| 	OrderBy    string     `form:"order_by"` | ||||
| } | ||||
|  | ||||
| // SensorDataDTO 是用于API响应的传感器数据结构 | ||||
| type SensorDataDTO struct { | ||||
| 	Time                 time.Time         `json:"time"` | ||||
| 	DeviceID             uint              `json:"device_id"` | ||||
| 	RegionalControllerID uint              `json:"regional_controller_id"` | ||||
| 	SensorType           models.SensorType `json:"sensor_type"` | ||||
| 	Data                 json.RawMessage   `json:"data"` | ||||
| } | ||||
|  | ||||
| // ListSensorDataResponse 是获取传感器数据列表的响应结构 | ||||
| type ListSensorDataResponse struct { | ||||
| 	List       []SensorDataDTO `json:"list"` | ||||
| 	Pagination PaginationDTO   `json:"pagination"` | ||||
| } | ||||
|  | ||||
| // --- DeviceCommandLog --- | ||||
|  | ||||
| // ListDeviceCommandLogRequest 定义了获取设备命令日志列表的请求参数 | ||||
| type ListDeviceCommandLogRequest struct { | ||||
| 	Page            int        `form:"page,default=1"` | ||||
| 	PageSize        int        `form:"pageSize,default=10"` | ||||
| 	DeviceID        *uint      `form:"device_id"` | ||||
| 	ReceivedSuccess *bool      `form:"received_success"` | ||||
| 	StartTime       *time.Time `form:"start_time" time_format:"rfc3339"` | ||||
| 	EndTime         *time.Time `form:"end_time" time_format:"rfc3339"` | ||||
| 	OrderBy         string     `form:"order_by"` | ||||
| } | ||||
|  | ||||
| // DeviceCommandLogDTO 是用于API响应的设备命令日志结构 | ||||
| type DeviceCommandLogDTO struct { | ||||
| 	MessageID       string     `json:"message_id"` | ||||
| 	DeviceID        uint       `json:"device_id"` | ||||
| 	SentAt          time.Time  `json:"sent_at"` | ||||
| 	AcknowledgedAt  *time.Time `json:"acknowledged_at"` | ||||
| 	ReceivedSuccess bool       `json:"received_success"` | ||||
| } | ||||
|  | ||||
| // ListDeviceCommandLogResponse 是获取设备命令日志列表的响应结构 | ||||
| type ListDeviceCommandLogResponse struct { | ||||
| 	List       []DeviceCommandLogDTO `json:"list"` | ||||
| 	Pagination PaginationDTO         `json:"pagination"` | ||||
| } | ||||
|  | ||||
| // --- PlanExecutionLog --- | ||||
|  | ||||
| // ListPlanExecutionLogRequest 定义了获取计划执行日志列表的请求参数 | ||||
| type ListPlanExecutionLogRequest struct { | ||||
| 	Page      int        `form:"page,default=1"` | ||||
| 	PageSize  int        `form:"pageSize,default=10"` | ||||
| 	PlanID    *uint      `form:"plan_id"` | ||||
| 	Status    *string    `form:"status"` | ||||
| 	StartTime *time.Time `form:"start_time" time_format:"rfc3339"` | ||||
| 	EndTime   *time.Time `form:"end_time" time_format:"rfc3339"` | ||||
| 	OrderBy   string     `form:"order_by"` | ||||
| } | ||||
|  | ||||
| // PlanExecutionLogDTO 是用于API响应的计划执行日志结构 | ||||
| type PlanExecutionLogDTO struct { | ||||
| 	ID        uint                   `json:"id"` | ||||
| 	CreatedAt time.Time              `json:"created_at"` | ||||
| 	UpdatedAt time.Time              `json:"updated_at"` | ||||
| 	PlanID    uint                   `json:"plan_id"` | ||||
| 	Status    models.ExecutionStatus `json:"status"` | ||||
| 	StartedAt time.Time              `json:"started_at"` | ||||
| 	EndedAt   time.Time              `json:"ended_at"` | ||||
| 	Error     string                 `json:"error"` | ||||
| } | ||||
|  | ||||
| // ListPlanExecutionLogResponse 是获取计划执行日志列表的响应结构 | ||||
| type ListPlanExecutionLogResponse struct { | ||||
| 	List       []PlanExecutionLogDTO `json:"list"` | ||||
| 	Pagination PaginationDTO         `json:"pagination"` | ||||
| } | ||||
|  | ||||
| // --- TaskExecutionLog --- | ||||
|  | ||||
| // ListTaskExecutionLogRequest 定义了获取任务执行日志列表的请求参数 | ||||
| type ListTaskExecutionLogRequest struct { | ||||
| 	Page               int        `form:"page,default=1"` | ||||
| 	PageSize           int        `form:"pageSize,default=10"` | ||||
| 	PlanExecutionLogID *uint      `form:"plan_execution_log_id"` | ||||
| 	TaskID             *int       `form:"task_id"` | ||||
| 	Status             *string    `form:"status"` | ||||
| 	StartTime          *time.Time `form:"start_time" time_format:"rfc3339"` | ||||
| 	EndTime            *time.Time `form:"end_time" time_format:"rfc3339"` | ||||
| 	OrderBy            string     `form:"order_by"` | ||||
| } | ||||
|  | ||||
| // TaskDTO 是用于API响应的简化版任务结构 | ||||
| type TaskDTO struct { | ||||
| 	ID          uint   `json:"id"` | ||||
| 	Name        string `json:"name"` | ||||
| 	Description string `json:"description"` | ||||
| } | ||||
|  | ||||
| // TaskExecutionLogDTO 是用于API响应的任务执行日志结构 | ||||
| type TaskExecutionLogDTO struct { | ||||
| 	ID                 uint                   `json:"id"` | ||||
| 	CreatedAt          time.Time              `json:"created_at"` | ||||
| 	UpdatedAt          time.Time              `json:"updated_at"` | ||||
| 	PlanExecutionLogID uint                   `json:"plan_execution_log_id"` | ||||
| 	TaskID             int                    `json:"task_id"` | ||||
| 	Task               TaskDTO                `json:"task"` // 嵌套的任务信息 | ||||
| 	Status             models.ExecutionStatus `json:"status"` | ||||
| 	Output             string                 `json:"output"` | ||||
| 	StartedAt          time.Time              `json:"started_at"` | ||||
| 	EndedAt            time.Time              `json:"ended_at"` | ||||
| } | ||||
|  | ||||
| // ListTaskExecutionLogResponse 是获取任务执行日志列表的响应结构 | ||||
| type ListTaskExecutionLogResponse struct { | ||||
| 	List       []TaskExecutionLogDTO `json:"list"` | ||||
| 	Pagination PaginationDTO         `json:"pagination"` | ||||
| } | ||||
|  | ||||
| // --- PendingCollection --- | ||||
|  | ||||
| // ListPendingCollectionRequest 定义了获取待采集请求列表的请求参数 | ||||
| type ListPendingCollectionRequest struct { | ||||
| 	Page      int        `form:"page,default=1"` | ||||
| 	PageSize  int        `form:"pageSize,default=10"` | ||||
| 	DeviceID  *uint      `form:"device_id"` | ||||
| 	Status    *string    `form:"status"` | ||||
| 	StartTime *time.Time `form:"start_time" time_format:"rfc3339"` | ||||
| 	EndTime   *time.Time `form:"end_time" time_format:"rfc3339"` | ||||
| 	OrderBy   string     `form:"order_by"` | ||||
| } | ||||
|  | ||||
| // PendingCollectionDTO 是用于API响应的待采集请求结构 | ||||
| type PendingCollectionDTO struct { | ||||
| 	CorrelationID   string                         `json:"correlation_id"` | ||||
| 	DeviceID        uint                           `json:"device_id"` | ||||
| 	CommandMetadata models.UintArray               `json:"command_metadata"` | ||||
| 	Status          models.PendingCollectionStatus `json:"status"` | ||||
| 	FulfilledAt     *time.Time                     `json:"fulfilled_at"` | ||||
| 	CreatedAt       time.Time                      `json:"created_at"` | ||||
| } | ||||
|  | ||||
| // ListPendingCollectionResponse 是获取待采集请求列表的响应结构 | ||||
| type ListPendingCollectionResponse struct { | ||||
| 	List       []PendingCollectionDTO `json:"list"` | ||||
| 	Pagination PaginationDTO          `json:"pagination"` | ||||
| } | ||||
|  | ||||
| // --- UserActionLog --- | ||||
|  | ||||
| // ListUserActionLogRequest 定义了获取用户操作日志列表的请求参数 | ||||
| type ListUserActionLogRequest struct { | ||||
| 	Page       int        `form:"page,default=1"` | ||||
| 	PageSize   int        `form:"pageSize,default=10"` | ||||
| 	UserID     *uint      `form:"user_id"` | ||||
| 	Username   *string    `form:"username"` | ||||
| 	ActionType *string    `form:"action_type"` | ||||
| 	Status     *string    `form:"status"` | ||||
| 	StartTime  *time.Time `form:"start_time" time_format:"rfc3339"` | ||||
| 	EndTime    *time.Time `form:"end_time" time_format:"rfc3339"` | ||||
| 	OrderBy    string     `form:"order_by"` | ||||
| } | ||||
|  | ||||
| // UserActionLogDTO 是用于API响应的用户操作日志结构 | ||||
| type UserActionLogDTO struct { | ||||
| 	ID             uint               `json:"id"` | ||||
| 	Time           time.Time          `json:"time"` | ||||
| 	UserID         uint               `json:"user_id"` | ||||
| 	Username       string             `json:"username"` | ||||
| 	SourceIP       string             `json:"source_ip"` | ||||
| 	ActionType     string             `json:"action_type"` | ||||
| 	TargetResource json.RawMessage    `json:"target_resource"` | ||||
| 	Description    string             `json:"description"` | ||||
| 	Status         models.AuditStatus `json:"status"` | ||||
| 	HTTPPath       string             `json:"http_path"` | ||||
| 	HTTPMethod     string             `json:"http_method"` | ||||
| 	ResultDetails  string             `json:"result_details"` | ||||
| } | ||||
|  | ||||
| // ListUserActionLogResponse 是获取用户操作日志列表的响应结构 | ||||
| type ListUserActionLogResponse struct { | ||||
| 	List       []UserActionLogDTO `json:"list"` | ||||
| 	Pagination PaginationDTO      `json:"pagination"` | ||||
| } | ||||
|  | ||||
| // --- RawMaterialPurchase --- | ||||
|  | ||||
| // ListRawMaterialPurchaseRequest 定义了获取原料采购列表的请求参数 | ||||
| type ListRawMaterialPurchaseRequest struct { | ||||
| 	Page          int        `form:"page,default=1"` | ||||
| 	PageSize      int        `form:"pageSize,default=10"` | ||||
| 	RawMaterialID *uint      `form:"raw_material_id"` | ||||
| 	Supplier      *string    `form:"supplier"` | ||||
| 	StartTime     *time.Time `form:"start_time" time_format:"rfc3339"` | ||||
| 	EndTime       *time.Time `form:"end_time" time_format:"rfc3339"` | ||||
| 	OrderBy       string     `form:"order_by"` | ||||
| } | ||||
|  | ||||
| // RawMaterialDTO 是用于API响应的简化版原料结构 | ||||
| type RawMaterialDTO struct { | ||||
| 	ID   uint   `json:"id"` | ||||
| 	Name string `json:"name"` | ||||
| } | ||||
|  | ||||
| // RawMaterialPurchaseDTO 是用于API响应的原料采购结构 | ||||
| type RawMaterialPurchaseDTO struct { | ||||
| 	ID            uint           `json:"id"` | ||||
| 	RawMaterialID uint           `json:"raw_material_id"` | ||||
| 	RawMaterial   RawMaterialDTO `json:"raw_material"` | ||||
| 	Supplier      string         `json:"supplier"` | ||||
| 	Amount        float64        `json:"amount"` | ||||
| 	UnitPrice     float64        `json:"unit_price"` | ||||
| 	TotalPrice    float64        `json:"total_price"` | ||||
| 	PurchaseDate  time.Time      `json:"purchase_date"` | ||||
| 	CreatedAt     time.Time      `json:"created_at"` | ||||
| } | ||||
|  | ||||
| // ListRawMaterialPurchaseResponse 是获取原料采购列表的响应结构 | ||||
| type ListRawMaterialPurchaseResponse struct { | ||||
| 	List       []RawMaterialPurchaseDTO `json:"list"` | ||||
| 	Pagination PaginationDTO            `json:"pagination"` | ||||
| } | ||||
|  | ||||
| // --- RawMaterialStockLog --- | ||||
|  | ||||
| // ListRawMaterialStockLogRequest 定义了获取原料库存日志列表的请求参数 | ||||
| type ListRawMaterialStockLogRequest struct { | ||||
| 	Page          int        `form:"page,default=1"` | ||||
| 	PageSize      int        `form:"pageSize,default=10"` | ||||
| 	RawMaterialID *uint      `form:"raw_material_id"` | ||||
| 	SourceType    *string    `form:"source_type"` | ||||
| 	SourceID      *uint      `form:"source_id"` | ||||
| 	StartTime     *time.Time `form:"start_time" time_format:"rfc3339"` | ||||
| 	EndTime       *time.Time `form:"end_time" time_format:"rfc3339"` | ||||
| 	OrderBy       string     `form:"order_by"` | ||||
| } | ||||
|  | ||||
| // RawMaterialStockLogDTO 是用于API响应的原料库存日志结构 | ||||
| type RawMaterialStockLogDTO struct { | ||||
| 	ID            uint                      `json:"id"` | ||||
| 	RawMaterialID uint                      `json:"raw_material_id"` | ||||
| 	ChangeAmount  float64                   `json:"change_amount"` | ||||
| 	SourceType    models.StockLogSourceType `json:"source_type"` | ||||
| 	SourceID      uint                      `json:"source_id"` | ||||
| 	HappenedAt    time.Time                 `json:"happened_at"` | ||||
| 	Remarks       string                    `json:"remarks"` | ||||
| } | ||||
|  | ||||
| // ListRawMaterialStockLogResponse 是获取原料库存日志列表的响应结构 | ||||
| type ListRawMaterialStockLogResponse struct { | ||||
| 	List       []RawMaterialStockLogDTO `json:"list"` | ||||
| 	Pagination PaginationDTO            `json:"pagination"` | ||||
| } | ||||
|  | ||||
| // --- FeedUsageRecord --- | ||||
|  | ||||
| // ListFeedUsageRecordRequest 定义了获取饲料使用记录列表的请求参数 | ||||
| type ListFeedUsageRecordRequest struct { | ||||
| 	Page          int        `form:"page,default=1"` | ||||
| 	PageSize      int        `form:"pageSize,default=10"` | ||||
| 	PenID         *uint      `form:"pen_id"` | ||||
| 	FeedFormulaID *uint      `form:"feed_formula_id"` | ||||
| 	OperatorID    *uint      `form:"operator_id"` | ||||
| 	StartTime     *time.Time `form:"start_time" time_format:"rfc3339"` | ||||
| 	EndTime       *time.Time `form:"end_time" time_format:"rfc3339"` | ||||
| 	OrderBy       string     `form:"order_by"` | ||||
| } | ||||
|  | ||||
| // PenDTO 是用于API响应的简化版猪栏结构 | ||||
| type PenDTO struct { | ||||
| 	ID   uint   `json:"id"` | ||||
| 	Name string `json:"name"` | ||||
| } | ||||
|  | ||||
| // FeedFormulaDTO 是用于API响应的简化版饲料配方结构 | ||||
| type FeedFormulaDTO struct { | ||||
| 	ID   uint   `json:"id"` | ||||
| 	Name string `json:"name"` | ||||
| } | ||||
|  | ||||
| // FeedUsageRecordDTO 是用于API响应的饲料使用记录结构 | ||||
| type FeedUsageRecordDTO struct { | ||||
| 	ID            uint           `json:"id"` | ||||
| 	PenID         uint           `json:"pen_id"` | ||||
| 	Pen           PenDTO         `json:"pen"` | ||||
| 	FeedFormulaID uint           `json:"feed_formula_id"` | ||||
| 	FeedFormula   FeedFormulaDTO `json:"feed_formula"` | ||||
| 	Amount        float64        `json:"amount"` | ||||
| 	RecordedAt    time.Time      `json:"recorded_at"` | ||||
| 	OperatorID    uint           `json:"operator_id"` | ||||
| 	Remarks       string         `json:"remarks"` | ||||
| } | ||||
|  | ||||
| // ListFeedUsageRecordResponse 是获取饲料使用记录列表的响应结构 | ||||
| type ListFeedUsageRecordResponse struct { | ||||
| 	List       []FeedUsageRecordDTO `json:"list"` | ||||
| 	Pagination PaginationDTO        `json:"pagination"` | ||||
| } | ||||
|  | ||||
| // --- MedicationLog --- | ||||
|  | ||||
| // ListMedicationLogRequest 定义了获取用药记录列表的请求参数 | ||||
| type ListMedicationLogRequest struct { | ||||
| 	Page         int        `form:"page,default=1"` | ||||
| 	PageSize     int        `form:"pageSize,default=10"` | ||||
| 	PigBatchID   *uint      `form:"pig_batch_id"` | ||||
| 	MedicationID *uint      `form:"medication_id"` | ||||
| 	Reason       *string    `form:"reason"` | ||||
| 	OperatorID   *uint      `form:"operator_id"` | ||||
| 	StartTime    *time.Time `form:"start_time" time_format:"rfc3339"` | ||||
| 	EndTime      *time.Time `form:"end_time" time_format:"rfc3339"` | ||||
| 	OrderBy      string     `form:"order_by"` | ||||
| } | ||||
|  | ||||
| // MedicationDTO 是用于API响应的简化版药品结构 | ||||
| type MedicationDTO struct { | ||||
| 	ID   uint   `json:"id"` | ||||
| 	Name string `json:"name"` | ||||
| } | ||||
|  | ||||
| // MedicationLogDTO 是用于API响应的用药记录结构 | ||||
| type MedicationLogDTO struct { | ||||
| 	ID           uint                        `json:"id"` | ||||
| 	PigBatchID   uint                        `json:"pig_batch_id"` | ||||
| 	MedicationID uint                        `json:"medication_id"` | ||||
| 	Medication   MedicationDTO               `json:"medication"` | ||||
| 	DosageUsed   float64                     `json:"dosage_used"` | ||||
| 	TargetCount  int                         `json:"target_count"` | ||||
| 	Reason       models.MedicationReasonType `json:"reason"` | ||||
| 	Description  string                      `json:"description"` | ||||
| 	OperatorID   uint                        `json:"operator_id"` | ||||
| 	HappenedAt   time.Time                   `json:"happened_at"` | ||||
| } | ||||
|  | ||||
| // ListMedicationLogResponse 是获取用药记录列表的响应结构 | ||||
| type ListMedicationLogResponse struct { | ||||
| 	List       []MedicationLogDTO `json:"list"` | ||||
| 	Pagination PaginationDTO      `json:"pagination"` | ||||
| } | ||||
|  | ||||
| // --- PigBatchLog --- | ||||
|  | ||||
| // ListPigBatchLogRequest 定义了获取猪批次日志列表的请求参数 | ||||
| type ListPigBatchLogRequest struct { | ||||
| 	Page       int        `form:"page,default=1"` | ||||
| 	PageSize   int        `form:"pageSize,default=10"` | ||||
| 	PigBatchID *uint      `form:"pig_batch_id"` | ||||
| 	ChangeType *string    `form:"change_type"` | ||||
| 	OperatorID *uint      `form:"operator_id"` | ||||
| 	StartTime  *time.Time `form:"start_time" time_format:"rfc3339"` | ||||
| 	EndTime    *time.Time `form:"end_time" time_format:"rfc3339"` | ||||
| 	OrderBy    string     `form:"order_by"` | ||||
| } | ||||
|  | ||||
| // PigBatchLogDTO 是用于API响应的猪批次日志结构 | ||||
| type PigBatchLogDTO struct { | ||||
| 	ID          uint                 `json:"id"` | ||||
| 	CreatedAt   time.Time            `json:"created_at"` | ||||
| 	UpdatedAt   time.Time            `json:"updated_at"` | ||||
| 	PigBatchID  uint                 `json:"pig_batch_id"` | ||||
| 	ChangeType  models.LogChangeType `json:"change_type"` | ||||
| 	ChangeCount int                  `json:"change_count"` | ||||
| 	Reason      string               `json:"reason"` | ||||
| 	BeforeCount int                  `json:"before_count"` | ||||
| 	AfterCount  int                  `json:"after_count"` | ||||
| 	OperatorID  uint                 `json:"operator_id"` | ||||
| 	HappenedAt  time.Time            `json:"happened_at"` | ||||
| } | ||||
|  | ||||
| // ListPigBatchLogResponse 是获取猪批次日志列表的响应结构 | ||||
| type ListPigBatchLogResponse struct { | ||||
| 	List       []PigBatchLogDTO `json:"list"` | ||||
| 	Pagination PaginationDTO    `json:"pagination"` | ||||
| } | ||||
|  | ||||
| // --- WeighingBatch --- | ||||
|  | ||||
| // ListWeighingBatchRequest 定义了获取批次称重记录列表的请求参数 | ||||
| type ListWeighingBatchRequest struct { | ||||
| 	Page       int        `form:"page,default=1"` | ||||
| 	PageSize   int        `form:"pageSize,default=10"` | ||||
| 	PigBatchID *uint      `form:"pig_batch_id"` | ||||
| 	StartTime  *time.Time `form:"start_time" time_format:"rfc3339"` | ||||
| 	EndTime    *time.Time `form:"end_time" time_format:"rfc3339"` | ||||
| 	OrderBy    string     `form:"order_by"` | ||||
| } | ||||
|  | ||||
| // WeighingBatchDTO 是用于API响应的批次称重记录结构 | ||||
| type WeighingBatchDTO struct { | ||||
| 	ID           uint      `json:"id"` | ||||
| 	CreatedAt    time.Time `json:"created_at"` | ||||
| 	UpdatedAt    time.Time `json:"updated_at"` | ||||
| 	WeighingTime time.Time `json:"weighing_time"` | ||||
| 	Description  string    `json:"description"` | ||||
| 	PigBatchID   uint      `json:"pig_batch_id"` | ||||
| } | ||||
|  | ||||
| // ListWeighingBatchResponse 是获取批次称重记录列表的响应结构 | ||||
| type ListWeighingBatchResponse struct { | ||||
| 	List       []WeighingBatchDTO `json:"list"` | ||||
| 	Pagination PaginationDTO      `json:"pagination"` | ||||
| } | ||||
|  | ||||
| // --- WeighingRecord --- | ||||
|  | ||||
| // ListWeighingRecordRequest 定义了获取单次称重记录列表的请求参数 | ||||
| type ListWeighingRecordRequest struct { | ||||
| 	Page            int        `form:"page,default=1"` | ||||
| 	PageSize        int        `form:"pageSize,default=10"` | ||||
| 	WeighingBatchID *uint      `form:"weighing_batch_id"` | ||||
| 	PenID           *uint      `form:"pen_id"` | ||||
| 	OperatorID      *uint      `form:"operator_id"` | ||||
| 	StartTime       *time.Time `form:"start_time" time_format:"rfc3339"` | ||||
| 	EndTime         *time.Time `form:"end_time" time_format:"rfc3339"` | ||||
| 	OrderBy         string     `form:"order_by"` | ||||
| } | ||||
|  | ||||
| // WeighingRecordDTO 是用于API响应的单次称重记录结构 | ||||
| type WeighingRecordDTO struct { | ||||
| 	ID              uint      `json:"id"` | ||||
| 	CreatedAt       time.Time `json:"created_at"` | ||||
| 	UpdatedAt       time.Time `json:"updated_at"` | ||||
| 	Weight          float64   `json:"weight"` | ||||
| 	WeighingBatchID uint      `json:"weighing_batch_id"` | ||||
| 	PenID           uint      `json:"pen_id"` | ||||
| 	OperatorID      uint      `json:"operator_id"` | ||||
| 	Remark          string    `json:"remark"` | ||||
| 	WeighingTime    time.Time `json:"weighing_time"` | ||||
| } | ||||
|  | ||||
| // ListWeighingRecordResponse 是获取单次称重记录列表的响应结构 | ||||
| type ListWeighingRecordResponse struct { | ||||
| 	List       []WeighingRecordDTO `json:"list"` | ||||
| 	Pagination PaginationDTO       `json:"pagination"` | ||||
| } | ||||
|  | ||||
| // --- PigTransferLog --- | ||||
|  | ||||
| // ListPigTransferLogRequest 定义了获取猪只迁移日志列表的请求参数 | ||||
| type ListPigTransferLogRequest struct { | ||||
| 	Page          int        `form:"page,default=1"` | ||||
| 	PageSize      int        `form:"pageSize,default=10"` | ||||
| 	PigBatchID    *uint      `form:"pig_batch_id"` | ||||
| 	PenID         *uint      `form:"pen_id"` | ||||
| 	TransferType  *string    `form:"transfer_type"` | ||||
| 	OperatorID    *uint      `form:"operator_id"` | ||||
| 	CorrelationID *string    `form:"correlation_id"` | ||||
| 	StartTime     *time.Time `form:"start_time" time_format:"rfc3339"` | ||||
| 	EndTime       *time.Time `form:"end_time" time_format:"rfc3339"` | ||||
| 	OrderBy       string     `form:"order_by"` | ||||
| } | ||||
|  | ||||
| // PigTransferLogDTO 是用于API响应的猪只迁移日志结构 | ||||
| type PigTransferLogDTO struct { | ||||
| 	ID            uint                   `json:"id"` | ||||
| 	CreatedAt     time.Time              `json:"created_at"` | ||||
| 	UpdatedAt     time.Time              `json:"updated_at"` | ||||
| 	TransferTime  time.Time              `json:"transfer_time"` | ||||
| 	PigBatchID    uint                   `json:"pig_batch_id"` | ||||
| 	PenID         uint                   `json:"pen_id"` | ||||
| 	Quantity      int                    `json:"quantity"` | ||||
| 	Type          models.PigTransferType `json:"type"` | ||||
| 	CorrelationID string                 `json:"correlation_id"` | ||||
| 	OperatorID    uint                   `json:"operator_id"` | ||||
| 	Remarks       string                 `json:"remarks"` | ||||
| } | ||||
|  | ||||
| // ListPigTransferLogResponse 是获取猪只迁移日志列表的响应结构 | ||||
| type ListPigTransferLogResponse struct { | ||||
| 	List       []PigTransferLogDTO `json:"list"` | ||||
| 	Pagination PaginationDTO       `json:"pagination"` | ||||
| } | ||||
|  | ||||
| // --- PigSickLog --- | ||||
|  | ||||
| // ListPigSickLogRequest 定义了获取病猪日志列表的请求参数 | ||||
| type ListPigSickLogRequest struct { | ||||
| 	Page              int        `form:"page,default=1"` | ||||
| 	PageSize          int        `form:"pageSize,default=10"` | ||||
| 	PigBatchID        *uint      `form:"pig_batch_id"` | ||||
| 	PenID             *uint      `form:"pen_id"` | ||||
| 	Reason            *string    `form:"reason"` | ||||
| 	TreatmentLocation *string    `form:"treatment_location"` | ||||
| 	OperatorID        *uint      `form:"operator_id"` | ||||
| 	StartTime         *time.Time `form:"start_time" time_format:"rfc3339"` | ||||
| 	EndTime           *time.Time `form:"end_time" time_format:"rfc3339"` | ||||
| 	OrderBy           string     `form:"order_by"` | ||||
| } | ||||
|  | ||||
| // PigSickLogDTO 是用于API响应的病猪日志结构 | ||||
| type PigSickLogDTO struct { | ||||
| 	ID                uint                                    `json:"id"` | ||||
| 	CreatedAt         time.Time                               `json:"created_at"` | ||||
| 	UpdatedAt         time.Time                               `json:"updated_at"` | ||||
| 	PigBatchID        uint                                    `json:"pig_batch_id"` | ||||
| 	PenID             uint                                    `json:"pen_id"` | ||||
| 	ChangeCount       int                                     `json:"change_count"` | ||||
| 	Reason            models.PigBatchSickPigReasonType        `json:"reason"` | ||||
| 	BeforeCount       int                                     `json:"before_count"` | ||||
| 	AfterCount        int                                     `json:"after_count"` | ||||
| 	Remarks           string                                  `json:"remarks"` | ||||
| 	TreatmentLocation models.PigBatchSickPigTreatmentLocation `json:"treatment_location"` | ||||
| 	OperatorID        uint                                    `json:"operator_id"` | ||||
| 	HappenedAt        time.Time                               `json:"happened_at"` | ||||
| } | ||||
|  | ||||
| // ListPigSickLogResponse 是获取病猪日志列表的响应结构 | ||||
| type ListPigSickLogResponse struct { | ||||
| 	List       []PigSickLogDTO `json:"list"` | ||||
| 	Pagination PaginationDTO   `json:"pagination"` | ||||
| } | ||||
|  | ||||
| // --- PigPurchase --- | ||||
|  | ||||
| // ListPigPurchaseRequest 定义了获取猪只采购记录列表的请求参数 | ||||
| type ListPigPurchaseRequest struct { | ||||
| 	Page       int        `form:"page,default=1"` | ||||
| 	PageSize   int        `form:"pageSize,default=10"` | ||||
| 	PigBatchID *uint      `form:"pig_batch_id"` | ||||
| 	Supplier   *string    `form:"supplier"` | ||||
| 	OperatorID *uint      `form:"operator_id"` | ||||
| 	StartTime  *time.Time `form:"start_time" time_format:"rfc3339"` | ||||
| 	EndTime    *time.Time `form:"end_time" time_format:"rfc3339"` | ||||
| 	OrderBy    string     `form:"order_by"` | ||||
| } | ||||
|  | ||||
| // PigPurchaseDTO 是用于API响应的猪只采购记录结构 | ||||
| type PigPurchaseDTO struct { | ||||
| 	ID           uint      `json:"id"` | ||||
| 	CreatedAt    time.Time `json:"created_at"` | ||||
| 	UpdatedAt    time.Time `json:"updated_at"` | ||||
| 	PigBatchID   uint      `json:"pig_batch_id"` | ||||
| 	PurchaseDate time.Time `json:"purchase_date"` | ||||
| 	Supplier     string    `json:"supplier"` | ||||
| 	Quantity     int       `json:"quantity"` | ||||
| 	UnitPrice    float64   `json:"unit_price"` | ||||
| 	TotalPrice   float64   `json:"total_price"` | ||||
| 	Remarks      string    `json:"remarks"` | ||||
| 	OperatorID   uint      `json:"operator_id"` | ||||
| } | ||||
|  | ||||
| // ListPigPurchaseResponse 是获取猪只采购记录列表的响应结构 | ||||
| type ListPigPurchaseResponse struct { | ||||
| 	List       []PigPurchaseDTO `json:"list"` | ||||
| 	Pagination PaginationDTO    `json:"pagination"` | ||||
| } | ||||
|  | ||||
| // --- PigSale --- | ||||
|  | ||||
| // ListPigSaleRequest 定义了获取猪只销售记录列表的请求参数 | ||||
| type ListPigSaleRequest struct { | ||||
| 	Page       int        `form:"page,default=1"` | ||||
| 	PageSize   int        `form:"pageSize,default=10"` | ||||
| 	PigBatchID *uint      `form:"pig_batch_id"` | ||||
| 	Buyer      *string    `form:"buyer"` | ||||
| 	OperatorID *uint      `form:"operator_id"` | ||||
| 	StartTime  *time.Time `form:"start_time" time_format:"rfc3339"` | ||||
| 	EndTime    *time.Time `form:"end_time" time_format:"rfc3339"` | ||||
| 	OrderBy    string     `form:"order_by"` | ||||
| } | ||||
|  | ||||
| // PigSaleDTO 是用于API响应的猪只销售记录结构 | ||||
| type PigSaleDTO struct { | ||||
| 	ID         uint      `json:"id"` | ||||
| 	CreatedAt  time.Time `json:"created_at"` | ||||
| 	UpdatedAt  time.Time `json:"updated_at"` | ||||
| 	PigBatchID uint      `json:"pig_batch_id"` | ||||
| 	SaleDate   time.Time `json:"sale_date"` | ||||
| 	Buyer      string    `json:"buyer"` | ||||
| 	Quantity   int       `json:"quantity"` | ||||
| 	UnitPrice  float64   `json:"unit_price"` | ||||
| 	TotalPrice float64   `json:"total_price"` | ||||
| 	Remarks    string    `json:"remarks"` | ||||
| 	OperatorID uint      `json:"operator_id"` | ||||
| } | ||||
|  | ||||
| // ListPigSaleResponse 是获取猪只销售记录列表的响应结构 | ||||
| type ListPigSaleResponse struct { | ||||
| 	List       []PigSaleDTO  `json:"list"` | ||||
| 	Pagination PaginationDTO `json:"pagination"` | ||||
| } | ||||
| @@ -25,7 +25,7 @@ func AuthMiddleware(tokenService token.TokenService, userRepo repository.UserRep | ||||
|  | ||||
| 		// 授权标头的格式应为 "Bearer <token>" | ||||
| 		parts := strings.Split(authHeader, " ") | ||||
| 		if len(parts) != 2 || parts[0] != "Bearer" { | ||||
| 		if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" { | ||||
| 			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "授权标头格式不正确"}) | ||||
| 			return | ||||
| 		} | ||||
|   | ||||
							
								
								
									
										159
									
								
								internal/app/service/monitor_service.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								internal/app/service/monitor_service.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,159 @@ | ||||
| package service | ||||
|  | ||||
| import ( | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/infra/models" | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository" | ||||
| ) | ||||
|  | ||||
| // MonitorService 定义了监控相关的业务逻辑服务接口 | ||||
| type MonitorService interface { | ||||
| 	ListSensorData(opts repository.SensorDataListOptions, page, pageSize int) ([]models.SensorData, int64, error) | ||||
| 	ListDeviceCommandLogs(opts repository.DeviceCommandLogListOptions, page, pageSize int) ([]models.DeviceCommandLog, int64, error) | ||||
| 	ListPlanExecutionLogs(opts repository.PlanExecutionLogListOptions, page, pageSize int) ([]models.PlanExecutionLog, int64, error) | ||||
| 	ListTaskExecutionLogs(opts repository.TaskExecutionLogListOptions, page, pageSize int) ([]models.TaskExecutionLog, int64, error) | ||||
| 	ListPendingCollections(opts repository.PendingCollectionListOptions, page, pageSize int) ([]models.PendingCollection, int64, error) | ||||
| 	ListUserActionLogs(opts repository.UserActionLogListOptions, page, pageSize int) ([]models.UserActionLog, int64, error) | ||||
| 	ListRawMaterialPurchases(opts repository.RawMaterialPurchaseListOptions, page, pageSize int) ([]models.RawMaterialPurchase, int64, error) | ||||
| 	ListRawMaterialStockLogs(opts repository.RawMaterialStockLogListOptions, page, pageSize int) ([]models.RawMaterialStockLog, int64, error) | ||||
| 	ListFeedUsageRecords(opts repository.FeedUsageRecordListOptions, page, pageSize int) ([]models.FeedUsageRecord, int64, error) | ||||
| 	ListMedicationLogs(opts repository.MedicationLogListOptions, page, pageSize int) ([]models.MedicationLog, int64, error) | ||||
| 	ListPigBatchLogs(opts repository.PigBatchLogListOptions, page, pageSize int) ([]models.PigBatchLog, int64, error) | ||||
| 	ListWeighingBatches(opts repository.WeighingBatchListOptions, page, pageSize int) ([]models.WeighingBatch, int64, error) | ||||
| 	ListWeighingRecords(opts repository.WeighingRecordListOptions, page, pageSize int) ([]models.WeighingRecord, int64, error) | ||||
| 	ListPigTransferLogs(opts repository.PigTransferLogListOptions, page, pageSize int) ([]models.PigTransferLog, int64, error) | ||||
| 	ListPigSickLogs(opts repository.PigSickLogListOptions, page, pageSize int) ([]models.PigSickLog, int64, error) | ||||
| 	ListPigPurchases(opts repository.PigPurchaseListOptions, page, pageSize int) ([]models.PigPurchase, int64, error) | ||||
| 	ListPigSales(opts repository.PigSaleListOptions, page, pageSize int) ([]models.PigSale, int64, error) | ||||
| } | ||||
|  | ||||
| // monitorService 是 MonitorService 接口的具体实现 | ||||
| type monitorService struct { | ||||
| 	sensorDataRepo        repository.SensorDataRepository | ||||
| 	deviceCommandLogRepo  repository.DeviceCommandLogRepository | ||||
| 	executionLogRepo      repository.ExecutionLogRepository | ||||
| 	pendingCollectionRepo repository.PendingCollectionRepository | ||||
| 	userActionLogRepo     repository.UserActionLogRepository | ||||
| 	rawMaterialRepo       repository.RawMaterialRepository | ||||
| 	medicationRepo        repository.MedicationLogRepository | ||||
| 	pigBatchRepo          repository.PigBatchRepository | ||||
| 	pigBatchLogRepo       repository.PigBatchLogRepository | ||||
| 	pigTransferLogRepo    repository.PigTransferLogRepository | ||||
| 	pigSickLogRepo        repository.PigSickLogRepository | ||||
| 	pigTradeRepo          repository.PigTradeRepository | ||||
| } | ||||
|  | ||||
| // NewMonitorService 创建一个新的 MonitorService 实例 | ||||
| func NewMonitorService( | ||||
| 	sensorDataRepo repository.SensorDataRepository, | ||||
| 	deviceCommandLogRepo repository.DeviceCommandLogRepository, | ||||
| 	executionLogRepo repository.ExecutionLogRepository, | ||||
| 	pendingCollectionRepo repository.PendingCollectionRepository, | ||||
| 	userActionLogRepo repository.UserActionLogRepository, | ||||
| 	rawMaterialRepo repository.RawMaterialRepository, | ||||
| 	medicationRepo repository.MedicationLogRepository, | ||||
| 	pigBatchRepo repository.PigBatchRepository, | ||||
| 	pigBatchLogRepo repository.PigBatchLogRepository, | ||||
| 	pigTransferLogRepo repository.PigTransferLogRepository, | ||||
| 	pigSickLogRepo repository.PigSickLogRepository, | ||||
| 	pigTradeRepo repository.PigTradeRepository, | ||||
| ) MonitorService { | ||||
| 	return &monitorService{ | ||||
| 		sensorDataRepo:        sensorDataRepo, | ||||
| 		deviceCommandLogRepo:  deviceCommandLogRepo, | ||||
| 		executionLogRepo:      executionLogRepo, | ||||
| 		pendingCollectionRepo: pendingCollectionRepo, | ||||
| 		userActionLogRepo:     userActionLogRepo, | ||||
| 		rawMaterialRepo:       rawMaterialRepo, | ||||
| 		medicationRepo:        medicationRepo, | ||||
| 		pigBatchRepo:          pigBatchRepo, | ||||
| 		pigBatchLogRepo:       pigBatchLogRepo, | ||||
| 		pigTransferLogRepo:    pigTransferLogRepo, | ||||
| 		pigSickLogRepo:        pigSickLogRepo, | ||||
| 		pigTradeRepo:          pigTradeRepo, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // ListSensorData 负责处理查询传感器数据列表的业务逻辑 | ||||
| func (s *monitorService) ListSensorData(opts repository.SensorDataListOptions, page, pageSize int) ([]models.SensorData, int64, error) { | ||||
| 	return s.sensorDataRepo.List(opts, page, pageSize) | ||||
| } | ||||
|  | ||||
| // ListDeviceCommandLogs 负责处理查询设备命令日志列表的业务逻辑 | ||||
| func (s *monitorService) ListDeviceCommandLogs(opts repository.DeviceCommandLogListOptions, page, pageSize int) ([]models.DeviceCommandLog, int64, error) { | ||||
| 	return s.deviceCommandLogRepo.List(opts, page, pageSize) | ||||
| } | ||||
|  | ||||
| // ListPlanExecutionLogs 负责处理查询计划执行日志列表的业务逻辑 | ||||
| func (s *monitorService) ListPlanExecutionLogs(opts repository.PlanExecutionLogListOptions, page, pageSize int) ([]models.PlanExecutionLog, int64, error) { | ||||
| 	return s.executionLogRepo.ListPlanExecutionLogs(opts, page, pageSize) | ||||
| } | ||||
|  | ||||
| // ListTaskExecutionLogs 负责处理查询任务执行日志列表的业务逻辑 | ||||
| func (s *monitorService) ListTaskExecutionLogs(opts repository.TaskExecutionLogListOptions, page, pageSize int) ([]models.TaskExecutionLog, int64, error) { | ||||
| 	return s.executionLogRepo.ListTaskExecutionLogs(opts, page, pageSize) | ||||
| } | ||||
|  | ||||
| // ListPendingCollections 负责处理查询待采集请求列表的业务逻辑 | ||||
| func (s *monitorService) ListPendingCollections(opts repository.PendingCollectionListOptions, page, pageSize int) ([]models.PendingCollection, int64, error) { | ||||
| 	return s.pendingCollectionRepo.List(opts, page, pageSize) | ||||
| } | ||||
|  | ||||
| // ListUserActionLogs 负责处理查询用户操作日志列表的业务逻辑 | ||||
| func (s *monitorService) ListUserActionLogs(opts repository.UserActionLogListOptions, page, pageSize int) ([]models.UserActionLog, int64, error) { | ||||
| 	return s.userActionLogRepo.List(opts, page, pageSize) | ||||
| } | ||||
|  | ||||
| // ListRawMaterialPurchases 负责处理查询原料采购记录列表的业务逻辑 | ||||
| func (s *monitorService) ListRawMaterialPurchases(opts repository.RawMaterialPurchaseListOptions, page, pageSize int) ([]models.RawMaterialPurchase, int64, error) { | ||||
| 	return s.rawMaterialRepo.ListRawMaterialPurchases(opts, page, pageSize) | ||||
| } | ||||
|  | ||||
| // ListRawMaterialStockLogs 负责处理查询原料库存日志列表的业务逻辑 | ||||
| func (s *monitorService) ListRawMaterialStockLogs(opts repository.RawMaterialStockLogListOptions, page, pageSize int) ([]models.RawMaterialStockLog, int64, error) { | ||||
| 	return s.rawMaterialRepo.ListRawMaterialStockLogs(opts, page, pageSize) | ||||
| } | ||||
|  | ||||
| // ListFeedUsageRecords 负责处理查询饲料使用记录列表的业务逻辑 | ||||
| func (s *monitorService) ListFeedUsageRecords(opts repository.FeedUsageRecordListOptions, page, pageSize int) ([]models.FeedUsageRecord, int64, error) { | ||||
| 	return s.rawMaterialRepo.ListFeedUsageRecords(opts, page, pageSize) | ||||
| } | ||||
|  | ||||
| // ListMedicationLogs 负责处理查询用药记录列表的业务逻辑 | ||||
| func (s *monitorService) ListMedicationLogs(opts repository.MedicationLogListOptions, page, pageSize int) ([]models.MedicationLog, int64, error) { | ||||
| 	return s.medicationRepo.ListMedicationLogs(opts, page, pageSize) | ||||
| } | ||||
|  | ||||
| // ListPigBatchLogs 负责处理查询猪批次日志列表的业务逻辑 | ||||
| func (s *monitorService) ListPigBatchLogs(opts repository.PigBatchLogListOptions, page, pageSize int) ([]models.PigBatchLog, int64, error) { | ||||
| 	return s.pigBatchLogRepo.List(opts, page, pageSize) | ||||
| } | ||||
|  | ||||
| // ListWeighingBatches 负责处理查询批次称重记录列表的业务逻辑 | ||||
| func (s *monitorService) ListWeighingBatches(opts repository.WeighingBatchListOptions, page, pageSize int) ([]models.WeighingBatch, int64, error) { | ||||
| 	return s.pigBatchRepo.ListWeighingBatches(opts, page, pageSize) | ||||
| } | ||||
|  | ||||
| // ListWeighingRecords 负责处理查询单次称重记录列表的业务逻辑 | ||||
| func (s *monitorService) ListWeighingRecords(opts repository.WeighingRecordListOptions, page, pageSize int) ([]models.WeighingRecord, int64, error) { | ||||
| 	return s.pigBatchRepo.ListWeighingRecords(opts, page, pageSize) | ||||
| } | ||||
|  | ||||
| // ListPigTransferLogs 负责处理查询猪只迁移日志列表的业务逻辑 | ||||
| func (s *monitorService) ListPigTransferLogs(opts repository.PigTransferLogListOptions, page, pageSize int) ([]models.PigTransferLog, int64, error) { | ||||
| 	return s.pigTransferLogRepo.ListPigTransferLogs(opts, page, pageSize) | ||||
| } | ||||
|  | ||||
| // ListPigSickLogs 负责处理查询病猪日志列表的业务逻辑 | ||||
| func (s *monitorService) ListPigSickLogs(opts repository.PigSickLogListOptions, page, pageSize int) ([]models.PigSickLog, int64, error) { | ||||
| 	return s.pigSickLogRepo.ListPigSickLogs(opts, page, pageSize) | ||||
| } | ||||
|  | ||||
| // ListPigPurchases 负责处理查询猪只采购记录列表的业务逻辑 | ||||
| func (s *monitorService) ListPigPurchases(opts repository.PigPurchaseListOptions, page, pageSize int) ([]models.PigPurchase, int64, error) { | ||||
| 	return s.pigTradeRepo.ListPigPurchases(opts, page, pageSize) | ||||
| } | ||||
|  | ||||
| // ListPigSales 负责处理查询猪只销售记录列表的业务逻辑 | ||||
| func (s *monitorService) ListPigSales(opts repository.PigSaleListOptions, page, pageSize int) ([]models.PigSale, int64, error) { | ||||
| 	return s.pigTradeRepo.ListPigSales(opts, page, pageSize) | ||||
| } | ||||
| @@ -84,6 +84,7 @@ func NewApplication(configPath string) (*Application, error) { | ||||
| 	pigTradeRepo := repository.NewGormPigTradeRepository(storage.GetDB()) | ||||
| 	pigSickPigLogRepo := repository.NewGormPigSickLogRepository(storage.GetDB()) | ||||
| 	medicationLogRepo := repository.NewGormMedicationLogRepository(storage.GetDB()) | ||||
| 	rawMaterialRepo := repository.NewGormRawMaterialRepository(storage.GetDB()) | ||||
|  | ||||
| 	// 初始化事务管理器 | ||||
| 	unitOfWork := repository.NewGormUnitOfWork(storage.GetDB(), logger) | ||||
| @@ -98,6 +99,20 @@ func NewApplication(configPath string) (*Application, error) { | ||||
| 	// --- 业务逻辑处理器初始化 --- | ||||
| 	pigFarmService := service.NewPigFarmService(pigFarmRepo, pigPenRepo, pigBatchRepo, unitOfWork, logger) | ||||
| 	pigBatchService := service.NewPigBatchService(pigBatchDomain, logger) | ||||
| 	monitorService := service.NewMonitorService( | ||||
| 		sensorDataRepo, | ||||
| 		deviceCommandLogRepo, | ||||
| 		executionLogRepo, | ||||
| 		pendingCollectionRepo, | ||||
| 		userActionLogRepo, | ||||
| 		rawMaterialRepo, | ||||
| 		medicationLogRepo, | ||||
| 		pigBatchRepo, | ||||
| 		pigBatchLogRepo, | ||||
| 		pigTransferLogRepo, | ||||
| 		pigSickPigLogRepo, | ||||
| 		pigTradeRepo, | ||||
| 	) | ||||
|  | ||||
| 	// 初始化审计服务 | ||||
| 	auditService := audit.NewService(userActionLogRepo, logger) | ||||
| @@ -160,6 +175,7 @@ func NewApplication(configPath string) (*Application, error) { | ||||
| 		planRepo, | ||||
| 		pigFarmService, | ||||
| 		pigBatchService, | ||||
| 		monitorService, | ||||
| 		userActionLogRepo, | ||||
| 		tokenService, | ||||
| 		auditService, | ||||
|   | ||||
| @@ -7,13 +7,22 @@ import ( | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| // DeviceCommandLogListOptions 定义了查询设备命令日志时的可选参数 | ||||
| type DeviceCommandLogListOptions struct { | ||||
| 	DeviceID        *uint | ||||
| 	ReceivedSuccess *bool | ||||
| 	StartTime       *time.Time // 基于 sent_at 字段 | ||||
| 	EndTime         *time.Time // 基于 sent_at 字段 | ||||
| 	OrderBy         string     // 例如 "sent_at asc" | ||||
| } | ||||
|  | ||||
| // DeviceCommandLogRepository 定义了设备下行命令历史记录的数据访问接口 | ||||
| type DeviceCommandLogRepository interface { | ||||
| 	Create(record *models.DeviceCommandLog) error | ||||
| 	FindByMessageID(messageID string) (*models.DeviceCommandLog, error) | ||||
| 	// UpdateAcknowledgedAt 用于更新指定 MessageID 的下行命令记录的确认时间及接收成功状态。 | ||||
| 	// AcknowledgedAt 和 ReceivedSuccess 字段会被更新。 | ||||
| 	UpdateAcknowledgedAt(messageID string, acknowledgedAt time.Time, receivedSuccess bool) error | ||||
| 	// List 支持分页和过滤的列表查询 | ||||
| 	List(opts DeviceCommandLogListOptions, page, pageSize int) ([]models.DeviceCommandLog, int64, error) | ||||
| } | ||||
|  | ||||
| // gormDeviceCommandLogRepository 是 DeviceCommandLogRepository 接口的 GORM 实现 | ||||
| @@ -42,7 +51,6 @@ func (r *gormDeviceCommandLogRepository) FindByMessageID(messageID string) (*mod | ||||
|  | ||||
| // UpdateAcknowledgedAt 实现 DeviceCommandLogRepository 接口的 UpdateAcknowledgedAt 方法 | ||||
| func (r *gormDeviceCommandLogRepository) UpdateAcknowledgedAt(messageID string, acknowledgedAt time.Time, receivedSuccess bool) error { | ||||
| 	// 使用 Updates 方法更新指定字段 | ||||
| 	return r.db.Model(&models.DeviceCommandLog{}). | ||||
| 		Where("message_id = ?", messageID). | ||||
| 		Updates(map[string]interface{}{ | ||||
| @@ -50,3 +58,48 @@ func (r *gormDeviceCommandLogRepository) UpdateAcknowledgedAt(messageID string, | ||||
| 			"received_success": receivedSuccess, | ||||
| 		}).Error | ||||
| } | ||||
|  | ||||
| // List 实现了分页和过滤查询设备命令日志的功能 | ||||
| func (r *gormDeviceCommandLogRepository) List(opts DeviceCommandLogListOptions, page, pageSize int) ([]models.DeviceCommandLog, int64, error) { | ||||
| 	// --- 校验分页参数 --- | ||||
| 	if page <= 0 || pageSize <= 0 { | ||||
| 		return nil, 0, ErrInvalidPagination | ||||
| 	} | ||||
|  | ||||
| 	var results []models.DeviceCommandLog | ||||
| 	var total int64 | ||||
|  | ||||
| 	query := r.db.Model(&models.DeviceCommandLog{}) | ||||
|  | ||||
| 	// --- 应用过滤条件 --- | ||||
| 	if opts.DeviceID != nil { | ||||
| 		query = query.Where("device_id = ?", *opts.DeviceID) | ||||
| 	} | ||||
| 	if opts.ReceivedSuccess != nil { | ||||
| 		query = query.Where("received_success = ?", *opts.ReceivedSuccess) | ||||
| 	} | ||||
| 	if opts.StartTime != nil { | ||||
| 		query = query.Where("sent_at >= ?", *opts.StartTime) | ||||
| 	} | ||||
| 	if opts.EndTime != nil { | ||||
| 		query = query.Where("sent_at <= ?", *opts.EndTime) | ||||
| 	} | ||||
|  | ||||
| 	// --- 计算总数 --- | ||||
| 	if err := query.Count(&total).Error; err != nil { | ||||
| 		return nil, 0, err | ||||
| 	} | ||||
|  | ||||
| 	// --- 应用排序条件 --- | ||||
| 	orderBy := "sent_at DESC" // 默认排序 | ||||
| 	if opts.OrderBy != "" { | ||||
| 		orderBy = opts.OrderBy | ||||
| 	} | ||||
| 	query = query.Order(orderBy) | ||||
|  | ||||
| 	// --- 分页 --- | ||||
| 	offset := (page - 1) * pageSize | ||||
| 	err := query.Limit(pageSize).Offset(offset).Find(&results).Error | ||||
|  | ||||
| 	return results, total, err | ||||
| } | ||||
|   | ||||
| @@ -8,9 +8,28 @@ import ( | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| // PlanExecutionLogListOptions 定义了查询计划执行日志时的可选参数 | ||||
| type PlanExecutionLogListOptions struct { | ||||
| 	PlanID    *uint | ||||
| 	Status    *models.ExecutionStatus | ||||
| 	StartTime *time.Time // 基于 created_at 字段 | ||||
| 	EndTime   *time.Time // 基于 created_at 字段 | ||||
| 	OrderBy   string     // 例如 "created_at asc" | ||||
| } | ||||
|  | ||||
| // TaskExecutionLogListOptions 定义了查询任务执行日志时的可选参数 | ||||
| type TaskExecutionLogListOptions struct { | ||||
| 	PlanExecutionLogID *uint | ||||
| 	TaskID             *int | ||||
| 	Status             *models.ExecutionStatus | ||||
| 	StartTime          *time.Time // 基于 created_at 字段 | ||||
| 	EndTime            *time.Time // 基于 created_at 字段 | ||||
| 	OrderBy            string     // 例如 "created_at asc" | ||||
| } | ||||
|  | ||||
| // ExecutionLogRepository 定义了与执行日志交互的接口。 | ||||
| // 这为服务层提供了一个清晰的契约,并允许在测试中轻松地进行模拟。 | ||||
| type ExecutionLogRepository interface { | ||||
| 	// --- Existing methods --- | ||||
| 	UpdateTaskExecutionLogStatusByIDs(logIDs []uint, status models.ExecutionStatus) error | ||||
| 	UpdateTaskExecutionLogStatus(logID uint, status models.ExecutionStatus) error | ||||
| 	CreateTaskExecutionLog(log *models.TaskExecutionLog) error | ||||
| @@ -48,6 +67,10 @@ type ExecutionLogRepository interface { | ||||
|  | ||||
| 	// CancelIncompleteTasksByPlanLogID 取消一个计划执行中的所有未完成任务 | ||||
| 	CancelIncompleteTasksByPlanLogID(planLogID uint, reason string) error | ||||
|  | ||||
| 	// --- New methods --- | ||||
| 	ListPlanExecutionLogs(opts PlanExecutionLogListOptions, page, pageSize int) ([]models.PlanExecutionLog, int64, error) | ||||
| 	ListTaskExecutionLogs(opts TaskExecutionLogListOptions, page, pageSize int) ([]models.TaskExecutionLog, int64, error) | ||||
| } | ||||
|  | ||||
| // gormExecutionLogRepository 是使用 GORM 的具体实现。 | ||||
| @@ -56,18 +79,101 @@ type gormExecutionLogRepository struct { | ||||
| } | ||||
|  | ||||
| // NewGormExecutionLogRepository 创建一个新的执行日志仓库。 | ||||
| // 它接收一个 GORM DB 实例作为依赖。 | ||||
| func NewGormExecutionLogRepository(db *gorm.DB) ExecutionLogRepository { | ||||
| 	return &gormExecutionLogRepository{db: db} | ||||
| } | ||||
|  | ||||
| // ListPlanExecutionLogs 实现了分页和过滤查询计划执行日志的功能 | ||||
| func (r *gormExecutionLogRepository) ListPlanExecutionLogs(opts PlanExecutionLogListOptions, page, pageSize int) ([]models.PlanExecutionLog, int64, error) { | ||||
| 	if page <= 0 || pageSize <= 0 { | ||||
| 		return nil, 0, ErrInvalidPagination | ||||
| 	} | ||||
|  | ||||
| 	var results []models.PlanExecutionLog | ||||
| 	var total int64 | ||||
|  | ||||
| 	query := r.db.Model(&models.PlanExecutionLog{}) | ||||
|  | ||||
| 	if opts.PlanID != nil { | ||||
| 		query = query.Where("plan_id = ?", *opts.PlanID) | ||||
| 	} | ||||
| 	if opts.Status != nil { | ||||
| 		query = query.Where("status = ?", *opts.Status) | ||||
| 	} | ||||
| 	if opts.StartTime != nil { | ||||
| 		query = query.Where("created_at >= ?", *opts.StartTime) | ||||
| 	} | ||||
| 	if opts.EndTime != nil { | ||||
| 		query = query.Where("created_at <= ?", *opts.EndTime) | ||||
| 	} | ||||
|  | ||||
| 	if err := query.Count(&total).Error; err != nil { | ||||
| 		return nil, 0, err | ||||
| 	} | ||||
|  | ||||
| 	orderBy := "created_at DESC" | ||||
| 	if opts.OrderBy != "" { | ||||
| 		orderBy = opts.OrderBy | ||||
| 	} | ||||
| 	query = query.Order(orderBy) | ||||
|  | ||||
| 	offset := (page - 1) * pageSize | ||||
| 	err := query.Limit(pageSize).Offset(offset).Find(&results).Error | ||||
|  | ||||
| 	return results, total, err | ||||
| } | ||||
|  | ||||
| // ListTaskExecutionLogs 实现了分页和过滤查询任务执行日志的功能 | ||||
| func (r *gormExecutionLogRepository) ListTaskExecutionLogs(opts TaskExecutionLogListOptions, page, pageSize int) ([]models.TaskExecutionLog, int64, error) { | ||||
| 	if page <= 0 || pageSize <= 0 { | ||||
| 		return nil, 0, ErrInvalidPagination | ||||
| 	} | ||||
|  | ||||
| 	var results []models.TaskExecutionLog | ||||
| 	var total int64 | ||||
|  | ||||
| 	query := r.db.Model(&models.TaskExecutionLog{}) | ||||
|  | ||||
| 	if opts.PlanExecutionLogID != nil { | ||||
| 		query = query.Where("plan_execution_log_id = ?", *opts.PlanExecutionLogID) | ||||
| 	} | ||||
| 	if opts.TaskID != nil { | ||||
| 		query = query.Where("task_id = ?", *opts.TaskID) | ||||
| 	} | ||||
| 	if opts.Status != nil { | ||||
| 		query = query.Where("status = ?", *opts.Status) | ||||
| 	} | ||||
| 	if opts.StartTime != nil { | ||||
| 		query = query.Where("created_at >= ?", *opts.StartTime) | ||||
| 	} | ||||
| 	if opts.EndTime != nil { | ||||
| 		query = query.Where("created_at <= ?", *opts.EndTime) | ||||
| 	} | ||||
|  | ||||
| 	if err := query.Count(&total).Error; err != nil { | ||||
| 		return nil, 0, err | ||||
| 	} | ||||
|  | ||||
| 	orderBy := "created_at DESC" | ||||
| 	if opts.OrderBy != "" { | ||||
| 		orderBy = opts.OrderBy | ||||
| 	} | ||||
| 	// 预加载关联的Task信息 | ||||
| 	query = query.Order(orderBy).Preload("Task") | ||||
|  | ||||
| 	offset := (page - 1) * pageSize | ||||
| 	err := query.Limit(pageSize).Offset(offset).Find(&results).Error | ||||
|  | ||||
| 	return results, total, err | ||||
| } | ||||
|  | ||||
| // --- Existing method implementations --- | ||||
|  | ||||
| func (r *gormExecutionLogRepository) UpdateTaskExecutionLogStatusByIDs(logIDs []uint, status models.ExecutionStatus) error { | ||||
| 	if len(logIDs) == 0 { | ||||
| 		return nil | ||||
| 	} | ||||
| 	return r.db.Model(&models.TaskExecutionLog{}). | ||||
| 		Where("id IN ?", logIDs). | ||||
| 		Update("status", status).Error | ||||
| 	return r.db.Model(&models.TaskExecutionLog{}).Where("id IN ?", logIDs).Update("status", status).Error | ||||
| } | ||||
|  | ||||
| func (r *gormExecutionLogRepository) UpdateTaskExecutionLogStatus(logID uint, status models.ExecutionStatus) error { | ||||
|   | ||||
| @@ -1,26 +0,0 @@ | ||||
| package repository | ||||
|  | ||||
| import ( | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/infra/models" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| // MedicationLogRepository 定义了与群体用药日志模型相关的数据库操作接口。 | ||||
| type MedicationLogRepository interface { | ||||
| 	CreateMedicationLog(log *models.MedicationLog) error | ||||
| } | ||||
|  | ||||
| // gormMedicationLogRepository 是 MedicationLogRepository 接口的 GORM 实现。 | ||||
| type gormMedicationLogRepository struct { | ||||
| 	db *gorm.DB | ||||
| } | ||||
|  | ||||
| // NewGormMedicationLogRepository 创建一个新的 MedicationLogRepository GORM 实现实例。 | ||||
| func NewGormMedicationLogRepository(db *gorm.DB) MedicationLogRepository { | ||||
| 	return &gormMedicationLogRepository{db: db} | ||||
| } | ||||
|  | ||||
| // CreateMedicationLog 创建一条新的群体用药日志记录 | ||||
| func (r *gormMedicationLogRepository) CreateMedicationLog(log *models.MedicationLog) error { | ||||
| 	return r.db.Create(log).Error | ||||
| } | ||||
							
								
								
									
										86
									
								
								internal/infra/repository/medication_log_repository.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								internal/infra/repository/medication_log_repository.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,86 @@ | ||||
| package repository | ||||
|  | ||||
| import ( | ||||
| 	"time" | ||||
|  | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/infra/models" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| // MedicationLogListOptions 定义了查询用药记录时的可选参数 | ||||
| type MedicationLogListOptions struct { | ||||
| 	PigBatchID   *uint | ||||
| 	MedicationID *uint | ||||
| 	Reason       *models.MedicationReasonType | ||||
| 	OperatorID   *uint | ||||
| 	StartTime    *time.Time | ||||
| 	EndTime      *time.Time | ||||
| 	OrderBy      string // 例如 "happened_at desc" | ||||
| } | ||||
|  | ||||
| // MedicationLogRepository 定义了与群体用药日志模型相关的数据库操作接口。 | ||||
| type MedicationLogRepository interface { | ||||
| 	CreateMedicationLog(log *models.MedicationLog) error | ||||
| 	ListMedicationLogs(opts MedicationLogListOptions, page, pageSize int) ([]models.MedicationLog, int64, error) | ||||
| } | ||||
|  | ||||
| // gormMedicationLogRepository 是 MedicationLogRepository 接口的 GORM 实现。 | ||||
| type gormMedicationLogRepository struct { | ||||
| 	db *gorm.DB | ||||
| } | ||||
|  | ||||
| // NewGormMedicationLogRepository 创建一个新的 MedicationLogRepository GORM 实现实例。 | ||||
| func NewGormMedicationLogRepository(db *gorm.DB) MedicationLogRepository { | ||||
| 	return &gormMedicationLogRepository{db: db} | ||||
| } | ||||
|  | ||||
| // CreateMedicationLog 创建一条新的群体用药日志记录 | ||||
| func (r *gormMedicationLogRepository) CreateMedicationLog(log *models.MedicationLog) error { | ||||
| 	return r.db.Create(log).Error | ||||
| } | ||||
|  | ||||
| // ListMedicationLogs 实现了分页和过滤查询用药记录的功能 | ||||
| func (r *gormMedicationLogRepository) ListMedicationLogs(opts MedicationLogListOptions, page, pageSize int) ([]models.MedicationLog, int64, error) { | ||||
| 	if page <= 0 || pageSize <= 0 { | ||||
| 		return nil, 0, ErrInvalidPagination | ||||
| 	} | ||||
|  | ||||
| 	var results []models.MedicationLog | ||||
| 	var total int64 | ||||
|  | ||||
| 	query := r.db.Model(&models.MedicationLog{}) | ||||
|  | ||||
| 	if opts.PigBatchID != nil { | ||||
| 		query = query.Where("pig_batch_id = ?", *opts.PigBatchID) | ||||
| 	} | ||||
| 	if opts.MedicationID != nil { | ||||
| 		query = query.Where("medication_id = ?", *opts.MedicationID) | ||||
| 	} | ||||
| 	if opts.Reason != nil { | ||||
| 		query = query.Where("reason = ?", *opts.Reason) | ||||
| 	} | ||||
| 	if opts.OperatorID != nil { | ||||
| 		query = query.Where("operator_id = ?", *opts.OperatorID) | ||||
| 	} | ||||
| 	if opts.StartTime != nil { | ||||
| 		query = query.Where("happened_at >= ?", *opts.StartTime) | ||||
| 	} | ||||
| 	if opts.EndTime != nil { | ||||
| 		query = query.Where("happened_at <= ?", *opts.EndTime) | ||||
| 	} | ||||
|  | ||||
| 	if err := query.Count(&total).Error; err != nil { | ||||
| 		return nil, 0, err | ||||
| 	} | ||||
|  | ||||
| 	orderBy := "happened_at DESC" | ||||
| 	if opts.OrderBy != "" { | ||||
| 		orderBy = opts.OrderBy | ||||
| 	} | ||||
| 	query = query.Order(orderBy) | ||||
|  | ||||
| 	offset := (page - 1) * pageSize | ||||
| 	err := query.Limit(pageSize).Offset(offset).Find(&results).Error | ||||
|  | ||||
| 	return results, total, err | ||||
| } | ||||
| @@ -7,6 +7,15 @@ import ( | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| // PendingCollectionListOptions 定义了查询待采集请求时的可选参数 | ||||
| type PendingCollectionListOptions struct { | ||||
| 	DeviceID  *uint | ||||
| 	Status    *models.PendingCollectionStatus | ||||
| 	StartTime *time.Time // 基于 created_at 字段 | ||||
| 	EndTime   *time.Time // 基于 created_at 字段 | ||||
| 	OrderBy   string     // 例如 "created_at asc" | ||||
| } | ||||
|  | ||||
| // PendingCollectionRepository 定义了与待采集请求相关的数据库操作接口。 | ||||
| type PendingCollectionRepository interface { | ||||
| 	// Create 创建一个新的待采集请求。 | ||||
| @@ -20,6 +29,9 @@ type PendingCollectionRepository interface { | ||||
|  | ||||
| 	// MarkAllPendingAsTimedOut 将所有“待处理”请求更新为“已超时”。 | ||||
| 	MarkAllPendingAsTimedOut() (int64, error) | ||||
|  | ||||
| 	// List 支持分页和过滤的列表查询 | ||||
| 	List(opts PendingCollectionListOptions, page, pageSize int) ([]models.PendingCollection, int64, error) | ||||
| } | ||||
|  | ||||
| // gormPendingCollectionRepository 是 PendingCollectionRepository 的 GORM 实现。 | ||||
| @@ -65,3 +77,43 @@ func (r *gormPendingCollectionRepository) MarkAllPendingAsTimedOut() (int64, err | ||||
|  | ||||
| 	return result.RowsAffected, result.Error | ||||
| } | ||||
|  | ||||
| // List 实现了分页和过滤查询待采集请求的功能 | ||||
| func (r *gormPendingCollectionRepository) List(opts PendingCollectionListOptions, page, pageSize int) ([]models.PendingCollection, int64, error) { | ||||
| 	if page <= 0 || pageSize <= 0 { | ||||
| 		return nil, 0, ErrInvalidPagination | ||||
| 	} | ||||
|  | ||||
| 	var results []models.PendingCollection | ||||
| 	var total int64 | ||||
|  | ||||
| 	query := r.db.Model(&models.PendingCollection{}) | ||||
|  | ||||
| 	if opts.DeviceID != nil { | ||||
| 		query = query.Where("device_id = ?", *opts.DeviceID) | ||||
| 	} | ||||
| 	if opts.Status != nil { | ||||
| 		query = query.Where("status = ?", *opts.Status) | ||||
| 	} | ||||
| 	if opts.StartTime != nil { | ||||
| 		query = query.Where("created_at >= ?", *opts.StartTime) | ||||
| 	} | ||||
| 	if opts.EndTime != nil { | ||||
| 		query = query.Where("created_at <= ?", *opts.EndTime) | ||||
| 	} | ||||
|  | ||||
| 	if err := query.Count(&total).Error; err != nil { | ||||
| 		return nil, 0, err | ||||
| 	} | ||||
|  | ||||
| 	orderBy := "created_at DESC" | ||||
| 	if opts.OrderBy != "" { | ||||
| 		orderBy = opts.OrderBy | ||||
| 	} | ||||
| 	query = query.Order(orderBy) | ||||
|  | ||||
| 	offset := (page - 1) * pageSize | ||||
| 	err := query.Limit(pageSize).Offset(offset).Find(&results).Error | ||||
|  | ||||
| 	return results, total, err | ||||
| } | ||||
|   | ||||
| @@ -1,12 +1,22 @@ | ||||
| package repository | ||||
|  | ||||
| import ( | ||||
| 	"time" // 引入 time 包 | ||||
| 	"time" | ||||
|  | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/infra/models" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| // PigBatchLogListOptions 定义了查询猪批次日志时的可选参数 | ||||
| type PigBatchLogListOptions struct { | ||||
| 	PigBatchID *uint | ||||
| 	ChangeType *models.LogChangeType | ||||
| 	OperatorID *uint | ||||
| 	StartTime  *time.Time // 基于 happened_at 字段 | ||||
| 	EndTime    *time.Time // 基于 happened_at 字段 | ||||
| 	OrderBy    string     // 例如 "happened_at asc" | ||||
| } | ||||
|  | ||||
| // PigBatchLogRepository 定义了与猪批次日志相关的数据库操作接口。 | ||||
| type PigBatchLogRepository interface { | ||||
| 	// CreateTx 在指定的事务中创建一条新的猪批次日志。 | ||||
| @@ -17,6 +27,9 @@ type PigBatchLogRepository interface { | ||||
|  | ||||
| 	// GetLastLogByBatchIDTx 在指定的事务中,获取某批次的最后一条日志记录。 | ||||
| 	GetLastLogByBatchIDTx(tx *gorm.DB, batchID uint) (*models.PigBatchLog, error) | ||||
|  | ||||
| 	// List 支持分页和过滤的列表查询 | ||||
| 	List(opts PigBatchLogListOptions, page, pageSize int) ([]models.PigBatchLog, int64, error) | ||||
| } | ||||
|  | ||||
| // gormPigBatchLogRepository 是 PigBatchLogRepository 的 GORM 实现。 | ||||
| @@ -29,7 +42,7 @@ func NewGormPigBatchLogRepository(db *gorm.DB) PigBatchLogRepository { | ||||
| 	return &gormPigBatchLogRepository{db: db} | ||||
| } | ||||
|  | ||||
| // Create 实现了创建猪批次日志的逻辑。 | ||||
| // CreateTx 实现了在事务中创建猪批次日志的逻辑。 | ||||
| func (r *gormPigBatchLogRepository) CreateTx(tx *gorm.DB, log *models.PigBatchLog) error { | ||||
| 	return tx.Create(log).Error | ||||
| } | ||||
| @@ -53,3 +66,46 @@ func (r *gormPigBatchLogRepository) GetLastLogByBatchIDTx(tx *gorm.DB, batchID u | ||||
| 	} | ||||
| 	return &log, nil | ||||
| } | ||||
|  | ||||
| // List 实现了分页和过滤查询猪批次日志的功能 | ||||
| func (r *gormPigBatchLogRepository) List(opts PigBatchLogListOptions, page, pageSize int) ([]models.PigBatchLog, int64, error) { | ||||
| 	if page <= 0 || pageSize <= 0 { | ||||
| 		return nil, 0, ErrInvalidPagination | ||||
| 	} | ||||
|  | ||||
| 	var results []models.PigBatchLog | ||||
| 	var total int64 | ||||
|  | ||||
| 	query := r.db.Model(&models.PigBatchLog{}) | ||||
|  | ||||
| 	if opts.PigBatchID != nil { | ||||
| 		query = query.Where("pig_batch_id = ?", *opts.PigBatchID) | ||||
| 	} | ||||
| 	if opts.ChangeType != nil { | ||||
| 		query = query.Where("change_type = ?", *opts.ChangeType) | ||||
| 	} | ||||
| 	if opts.OperatorID != nil { | ||||
| 		query = query.Where("operator_id = ?", *opts.OperatorID) | ||||
| 	} | ||||
| 	if opts.StartTime != nil { | ||||
| 		query = query.Where("happened_at >= ?", *opts.StartTime) | ||||
| 	} | ||||
| 	if opts.EndTime != nil { | ||||
| 		query = query.Where("happened_at <= ?", *opts.EndTime) | ||||
| 	} | ||||
|  | ||||
| 	if err := query.Count(&total).Error; err != nil { | ||||
| 		return nil, 0, err | ||||
| 	} | ||||
|  | ||||
| 	orderBy := "happened_at DESC" | ||||
| 	if opts.OrderBy != "" { | ||||
| 		orderBy = opts.OrderBy | ||||
| 	} | ||||
| 	query = query.Order(orderBy) | ||||
|  | ||||
| 	offset := (page - 1) * pageSize | ||||
| 	err := query.Limit(pageSize).Offset(offset).Find(&results).Error | ||||
|  | ||||
| 	return results, total, err | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,8 @@ | ||||
| package repository | ||||
|  | ||||
| import ( | ||||
| 	"time" | ||||
|  | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/infra/models" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
| @@ -17,6 +19,30 @@ type PigBatchRepository interface { | ||||
| 	DeletePigBatch(id uint) (int64, error) | ||||
| 	DeletePigBatchTx(tx *gorm.DB, id uint) (int64, error) | ||||
| 	ListPigBatches(isActive *bool) ([]*models.PigBatch, error) | ||||
|  | ||||
| 	// ListWeighingBatches 支持分页和过滤的批次称重列表查询 | ||||
| 	ListWeighingBatches(opts WeighingBatchListOptions, page, pageSize int) ([]models.WeighingBatch, int64, error) | ||||
|  | ||||
| 	// ListWeighingRecords 支持分页和过滤的单次称重记录列表查询 | ||||
| 	ListWeighingRecords(opts WeighingRecordListOptions, page, pageSize int) ([]models.WeighingRecord, int64, error) | ||||
| } | ||||
|  | ||||
| // WeighingBatchListOptions 定义了查询批次称重记录时的可选参数 | ||||
| type WeighingBatchListOptions struct { | ||||
| 	PigBatchID *uint | ||||
| 	StartTime  *time.Time // 基于 weighing_time 字段 | ||||
| 	EndTime    *time.Time // 基于 weighing_time 字段 | ||||
| 	OrderBy    string     // 例如 "weighing_time asc" | ||||
| } | ||||
|  | ||||
| // WeighingRecordListOptions 定义了查询单次称重记录时的可选参数 | ||||
| type WeighingRecordListOptions struct { | ||||
| 	WeighingBatchID *uint | ||||
| 	PenID           *uint | ||||
| 	OperatorID      *uint | ||||
| 	StartTime       *time.Time // 基于 weighing_time 字段 | ||||
| 	EndTime         *time.Time // 基于 weighing_time 字段 | ||||
| 	OrderBy         string     // 例如 "weighing_time asc" | ||||
| } | ||||
|  | ||||
| // gormPigBatchRepository 是 PigBatchRepository 的 GORM 实现 | ||||
| @@ -100,3 +126,83 @@ func (r *gormPigBatchRepository) GetPigBatchByIDTx(tx *gorm.DB, id uint) (*model | ||||
| 	} | ||||
| 	return &batch, nil | ||||
| } | ||||
|  | ||||
| // ListWeighingBatches 实现了分页和过滤查询批次称重记录的功能 | ||||
| func (r *gormPigBatchRepository) ListWeighingBatches(opts WeighingBatchListOptions, page, pageSize int) ([]models.WeighingBatch, int64, error) { | ||||
| 	if page <= 0 || pageSize <= 0 { | ||||
| 		return nil, 0, ErrInvalidPagination | ||||
| 	} | ||||
|  | ||||
| 	var results []models.WeighingBatch | ||||
| 	var total int64 | ||||
|  | ||||
| 	query := r.db.Model(&models.WeighingBatch{}) | ||||
|  | ||||
| 	if opts.PigBatchID != nil { | ||||
| 		query = query.Where("pig_batch_id = ?", *opts.PigBatchID) | ||||
| 	} | ||||
| 	if opts.StartTime != nil { | ||||
| 		query = query.Where("weighing_time >= ?", *opts.StartTime) | ||||
| 	} | ||||
| 	if opts.EndTime != nil { | ||||
| 		query = query.Where("weighing_time <= ?", *opts.EndTime) | ||||
| 	} | ||||
|  | ||||
| 	if err := query.Count(&total).Error; err != nil { | ||||
| 		return nil, 0, err | ||||
| 	} | ||||
|  | ||||
| 	orderBy := "weighing_time DESC" | ||||
| 	if opts.OrderBy != "" { | ||||
| 		orderBy = opts.OrderBy | ||||
| 	} | ||||
| 	query = query.Order(orderBy) | ||||
|  | ||||
| 	offset := (page - 1) * pageSize | ||||
| 	err := query.Limit(pageSize).Offset(offset).Find(&results).Error | ||||
|  | ||||
| 	return results, total, err | ||||
| } | ||||
|  | ||||
| // ListWeighingRecords 实现了分页和过滤查询单次称重记录的功能 | ||||
| func (r *gormPigBatchRepository) ListWeighingRecords(opts WeighingRecordListOptions, page, pageSize int) ([]models.WeighingRecord, int64, error) { | ||||
| 	if page <= 0 || pageSize <= 0 { | ||||
| 		return nil, 0, ErrInvalidPagination | ||||
| 	} | ||||
|  | ||||
| 	var results []models.WeighingRecord | ||||
| 	var total int64 | ||||
|  | ||||
| 	query := r.db.Model(&models.WeighingRecord{}) | ||||
|  | ||||
| 	if opts.WeighingBatchID != nil { | ||||
| 		query = query.Where("weighing_batch_id = ?", *opts.WeighingBatchID) | ||||
| 	} | ||||
| 	if opts.PenID != nil { | ||||
| 		query = query.Where("pen_id = ?", *opts.PenID) | ||||
| 	} | ||||
| 	if opts.OperatorID != nil { | ||||
| 		query = query.Where("operator_id = ?", *opts.OperatorID) | ||||
| 	} | ||||
| 	if opts.StartTime != nil { | ||||
| 		query = query.Where("weighing_time >= ?", *opts.StartTime) | ||||
| 	} | ||||
| 	if opts.EndTime != nil { | ||||
| 		query = query.Where("weighing_time <= ?", *opts.EndTime) | ||||
| 	} | ||||
|  | ||||
| 	if err := query.Count(&total).Error; err != nil { | ||||
| 		return nil, 0, err | ||||
| 	} | ||||
|  | ||||
| 	orderBy := "weighing_time DESC" | ||||
| 	if opts.OrderBy != "" { | ||||
| 		orderBy = opts.OrderBy | ||||
| 	} | ||||
| 	query = query.Order(orderBy) | ||||
|  | ||||
| 	offset := (page - 1) * pageSize | ||||
| 	err := query.Limit(pageSize).Offset(offset).Find(&results).Error | ||||
|  | ||||
| 	return results, total, err | ||||
| } | ||||
|   | ||||
| @@ -2,11 +2,24 @@ package repository | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"time" | ||||
|  | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/infra/models" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| // PigSickLogListOptions 定义了查询病猪日志时的可选参数 | ||||
| type PigSickLogListOptions struct { | ||||
| 	PigBatchID        *uint | ||||
| 	PenID             *uint | ||||
| 	Reason            *models.PigBatchSickPigReasonType | ||||
| 	TreatmentLocation *models.PigBatchSickPigTreatmentLocation | ||||
| 	OperatorID        *uint | ||||
| 	StartTime         *time.Time // 基于 happened_at 字段 | ||||
| 	EndTime           *time.Time // 基于 happened_at 字段 | ||||
| 	OrderBy           string     // 例如 "happened_at desc" | ||||
| } | ||||
|  | ||||
| // PigSickLogRepository 定义了与病猪日志模型相关的数据库操作接口。 | ||||
| type PigSickLogRepository interface { | ||||
| 	// CreatePigSickLog 创建一条新的病猪日志记录 | ||||
| @@ -15,6 +28,9 @@ type PigSickLogRepository interface { | ||||
|  | ||||
| 	// GetLastLogByBatchTx 在事务中获取指定批次和猪栏的最新一条 PigSickLog 记录 | ||||
| 	GetLastLogByBatchTx(tx *gorm.DB, batchID uint) (*models.PigSickLog, error) | ||||
|  | ||||
| 	// ListPigSickLogs 支持分页和过滤的病猪日志列表查询 | ||||
| 	ListPigSickLogs(opts PigSickLogListOptions, page, pageSize int) ([]models.PigSickLog, int64, error) | ||||
| } | ||||
|  | ||||
| // gormPigSickLogRepository 是 PigSickLogRepository 接口的 GORM 实现。 | ||||
| @@ -51,3 +67,52 @@ func (r *gormPigSickLogRepository) GetLastLogByBatchTx(tx *gorm.DB, batchID uint | ||||
| 	} | ||||
| 	return &lastLog, nil | ||||
| } | ||||
|  | ||||
| // ListPigSickLogs 实现了分页和过滤查询病猪日志的功能 | ||||
| func (r *gormPigSickLogRepository) ListPigSickLogs(opts PigSickLogListOptions, page, pageSize int) ([]models.PigSickLog, int64, error) { | ||||
| 	if page <= 0 || pageSize <= 0 { | ||||
| 		return nil, 0, ErrInvalidPagination | ||||
| 	} | ||||
|  | ||||
| 	var results []models.PigSickLog | ||||
| 	var total int64 | ||||
|  | ||||
| 	query := r.db.Model(&models.PigSickLog{}) | ||||
|  | ||||
| 	if opts.PigBatchID != nil { | ||||
| 		query = query.Where("pig_batch_id = ?", *opts.PigBatchID) | ||||
| 	} | ||||
| 	if opts.PenID != nil { | ||||
| 		query = query.Where("pen_id = ?", *opts.PenID) | ||||
| 	} | ||||
| 	if opts.Reason != nil { | ||||
| 		query = query.Where("reason = ?", *opts.Reason) | ||||
| 	} | ||||
| 	if opts.TreatmentLocation != nil { | ||||
| 		query = query.Where("treatment_location = ?", *opts.TreatmentLocation) | ||||
| 	} | ||||
| 	if opts.OperatorID != nil { | ||||
| 		query = query.Where("operator_id = ?", *opts.OperatorID) | ||||
| 	} | ||||
| 	if opts.StartTime != nil { | ||||
| 		query = query.Where("happened_at >= ?", *opts.StartTime) | ||||
| 	} | ||||
| 	if opts.EndTime != nil { | ||||
| 		query = query.Where("happened_at <= ?", *opts.EndTime) | ||||
| 	} | ||||
|  | ||||
| 	if err := query.Count(&total).Error; err != nil { | ||||
| 		return nil, 0, err | ||||
| 	} | ||||
|  | ||||
| 	orderBy := "happened_at DESC" | ||||
| 	if opts.OrderBy != "" { | ||||
| 		orderBy = opts.OrderBy | ||||
| 	} | ||||
| 	query = query.Order(orderBy) | ||||
|  | ||||
| 	offset := (page - 1) * pageSize | ||||
| 	err := query.Limit(pageSize).Offset(offset).Find(&results).Error | ||||
|  | ||||
| 	return results, total, err | ||||
| } | ||||
|   | ||||
| @@ -1,10 +1,32 @@ | ||||
| package repository | ||||
|  | ||||
| import ( | ||||
| 	"time" | ||||
|  | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/infra/models" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| // PigPurchaseListOptions 定义了查询猪只采购记录时的可选参数 | ||||
| type PigPurchaseListOptions struct { | ||||
| 	PigBatchID *uint | ||||
| 	Supplier   *string | ||||
| 	OperatorID *uint | ||||
| 	StartTime  *time.Time // 基于 purchase_date 字段 | ||||
| 	EndTime    *time.Time // 基于 purchase_date 字段 | ||||
| 	OrderBy    string     // 例如 "purchase_date desc" | ||||
| } | ||||
|  | ||||
| // PigSaleListOptions 定义了查询猪只销售记录时的可选参数 | ||||
| type PigSaleListOptions struct { | ||||
| 	PigBatchID *uint | ||||
| 	Buyer      *string | ||||
| 	OperatorID *uint | ||||
| 	StartTime  *time.Time // 基于 sale_date 字段 | ||||
| 	EndTime    *time.Time // 基于 sale_date 字段 | ||||
| 	OrderBy    string     // 例如 "sale_date desc" | ||||
| } | ||||
|  | ||||
| // PigTradeRepository 定义了猪只交易数据持久化的接口。 | ||||
| // 领域服务通过此接口与数据层交互,实现解耦。 | ||||
| type PigTradeRepository interface { | ||||
| @@ -13,6 +35,12 @@ type PigTradeRepository interface { | ||||
|  | ||||
| 	// CreatePigPurchaseTx 在数据库中创建一条猪只采购记录。 | ||||
| 	CreatePigPurchaseTx(tx *gorm.DB, purchase *models.PigPurchase) error | ||||
|  | ||||
| 	// ListPigPurchases 支持分页和过滤的猪只采购记录列表查询 | ||||
| 	ListPigPurchases(opts PigPurchaseListOptions, page, pageSize int) ([]models.PigPurchase, int64, error) | ||||
|  | ||||
| 	// ListPigSales 支持分页和过滤的猪只销售记录列表查询 | ||||
| 	ListPigSales(opts PigSaleListOptions, page, pageSize int) ([]models.PigSale, int64, error) | ||||
| } | ||||
|  | ||||
| // gormPigTradeRepository 是 PigTradeRepository 接口的 GORM 实现。 | ||||
| @@ -34,3 +62,89 @@ func (r *gormPigTradeRepository) CreatePigSaleTx(tx *gorm.DB, sale *models.PigSa | ||||
| func (r *gormPigTradeRepository) CreatePigPurchaseTx(tx *gorm.DB, purchase *models.PigPurchase) error { | ||||
| 	return tx.Create(purchase).Error | ||||
| } | ||||
|  | ||||
| // ListPigPurchases 实现了分页和过滤查询猪只采购记录的功能 | ||||
| func (r *gormPigTradeRepository) ListPigPurchases(opts PigPurchaseListOptions, page, pageSize int) ([]models.PigPurchase, int64, error) { | ||||
| 	if page <= 0 || pageSize <= 0 { | ||||
| 		return nil, 0, ErrInvalidPagination | ||||
| 	} | ||||
|  | ||||
| 	var results []models.PigPurchase | ||||
| 	var total int64 | ||||
|  | ||||
| 	query := r.db.Model(&models.PigPurchase{}) | ||||
|  | ||||
| 	if opts.PigBatchID != nil { | ||||
| 		query = query.Where("pig_batch_id = ?", *opts.PigBatchID) | ||||
| 	} | ||||
| 	if opts.Supplier != nil { | ||||
| 		query = query.Where("supplier LIKE ?", "%"+*opts.Supplier+"%") | ||||
| 	} | ||||
| 	if opts.OperatorID != nil { | ||||
| 		query = query.Where("operator_id = ?", *opts.OperatorID) | ||||
| 	} | ||||
| 	if opts.StartTime != nil { | ||||
| 		query = query.Where("purchase_date >= ?", *opts.StartTime) | ||||
| 	} | ||||
| 	if opts.EndTime != nil { | ||||
| 		query = query.Where("purchase_date <= ?", *opts.EndTime) | ||||
| 	} | ||||
|  | ||||
| 	if err := query.Count(&total).Error; err != nil { | ||||
| 		return nil, 0, err | ||||
| 	} | ||||
|  | ||||
| 	orderBy := "purchase_date DESC" | ||||
| 	if opts.OrderBy != "" { | ||||
| 		orderBy = opts.OrderBy | ||||
| 	} | ||||
| 	query = query.Order(orderBy) | ||||
|  | ||||
| 	offset := (page - 1) * pageSize | ||||
| 	err := query.Limit(pageSize).Offset(offset).Find(&results).Error | ||||
|  | ||||
| 	return results, total, err | ||||
| } | ||||
|  | ||||
| // ListPigSales 实现了分页和过滤查询猪只销售记录的功能 | ||||
| func (r *gormPigTradeRepository) ListPigSales(opts PigSaleListOptions, page, pageSize int) ([]models.PigSale, int64, error) { | ||||
| 	if page <= 0 || pageSize <= 0 { | ||||
| 		return nil, 0, ErrInvalidPagination | ||||
| 	} | ||||
|  | ||||
| 	var results []models.PigSale | ||||
| 	var total int64 | ||||
|  | ||||
| 	query := r.db.Model(&models.PigSale{}) | ||||
|  | ||||
| 	if opts.PigBatchID != nil { | ||||
| 		query = query.Where("pig_batch_id = ?", *opts.PigBatchID) | ||||
| 	} | ||||
| 	if opts.Buyer != nil { | ||||
| 		query = query.Where("buyer LIKE ?", "%"+*opts.Buyer+"%") | ||||
| 	} | ||||
| 	if opts.OperatorID != nil { | ||||
| 		query = query.Where("operator_id = ?", *opts.OperatorID) | ||||
| 	} | ||||
| 	if opts.StartTime != nil { | ||||
| 		query = query.Where("sale_date >= ?", *opts.StartTime) | ||||
| 	} | ||||
| 	if opts.EndTime != nil { | ||||
| 		query = query.Where("sale_date <= ?", *opts.EndTime) | ||||
| 	} | ||||
|  | ||||
| 	if err := query.Count(&total).Error; err != nil { | ||||
| 		return nil, 0, err | ||||
| 	} | ||||
|  | ||||
| 	orderBy := "sale_date DESC" | ||||
| 	if opts.OrderBy != "" { | ||||
| 		orderBy = opts.OrderBy | ||||
| 	} | ||||
| 	query = query.Order(orderBy) | ||||
|  | ||||
| 	offset := (page - 1) * pageSize | ||||
| 	err := query.Limit(pageSize).Offset(offset).Find(&results).Error | ||||
|  | ||||
| 	return results, total, err | ||||
| } | ||||
|   | ||||
| @@ -7,6 +7,18 @@ import ( | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| // PigTransferLogListOptions 定义了查询猪只迁移日志时的可选参数 | ||||
| type PigTransferLogListOptions struct { | ||||
| 	PigBatchID    *uint | ||||
| 	PenID         *uint | ||||
| 	TransferType  *models.PigTransferType // 迁移类型 | ||||
| 	OperatorID    *uint | ||||
| 	CorrelationID *string | ||||
| 	StartTime     *time.Time // 基于 transfer_time 字段 | ||||
| 	EndTime       *time.Time // 基于 transfer_time 字段 | ||||
| 	OrderBy       string     // 例如 "transfer_time desc" | ||||
| } | ||||
|  | ||||
| // PigTransferLogRepository 定义了猪只迁移日志数据持久化的接口。 | ||||
| type PigTransferLogRepository interface { | ||||
| 	// CreatePigTransferLog 在数据库中创建一条猪只迁移日志记录。 | ||||
| @@ -14,6 +26,9 @@ type PigTransferLogRepository interface { | ||||
|  | ||||
| 	// GetLogsForPenSince 获取指定猪栏自特定时间点以来的所有迁移日志,按时间倒序排列。 | ||||
| 	GetLogsForPenSince(tx *gorm.DB, penID uint, since time.Time) ([]*models.PigTransferLog, error) | ||||
|  | ||||
| 	// ListPigTransferLogs 支持分页和过滤的猪只迁移日志列表查询 | ||||
| 	ListPigTransferLogs(opts PigTransferLogListOptions, page, pageSize int) ([]models.PigTransferLog, int64, error) | ||||
| } | ||||
|  | ||||
| // gormPigTransferLogRepository 是 PigTransferLogRepository 接口的 GORM 实现。 | ||||
| @@ -37,3 +52,52 @@ func (r *gormPigTransferLogRepository) GetLogsForPenSince(tx *gorm.DB, penID uin | ||||
| 	err := tx.Where("pen_id = ? AND transfer_time >= ?", penID, since).Order("transfer_time DESC").Find(&logs).Error | ||||
| 	return logs, err | ||||
| } | ||||
|  | ||||
| // ListPigTransferLogs 实现了分页和过滤查询猪只迁移日志的功能 | ||||
| func (r *gormPigTransferLogRepository) ListPigTransferLogs(opts PigTransferLogListOptions, page, pageSize int) ([]models.PigTransferLog, int64, error) { | ||||
| 	if page <= 0 || pageSize <= 0 { | ||||
| 		return nil, 0, ErrInvalidPagination | ||||
| 	} | ||||
|  | ||||
| 	var results []models.PigTransferLog | ||||
| 	var total int64 | ||||
|  | ||||
| 	query := r.db.Model(&models.PigTransferLog{}) | ||||
|  | ||||
| 	if opts.PigBatchID != nil { | ||||
| 		query = query.Where("pig_batch_id = ?", *opts.PigBatchID) | ||||
| 	} | ||||
| 	if opts.PenID != nil { | ||||
| 		query = query.Where("pen_id = ?", *opts.PenID) | ||||
| 	} | ||||
| 	if opts.TransferType != nil { | ||||
| 		query = query.Where("type = ?", *opts.TransferType) | ||||
| 	} | ||||
| 	if opts.OperatorID != nil { | ||||
| 		query = query.Where("operator_id = ?", *opts.OperatorID) | ||||
| 	} | ||||
| 	if opts.CorrelationID != nil { | ||||
| 		query = query.Where("correlation_id = ?", *opts.CorrelationID) | ||||
| 	} | ||||
| 	if opts.StartTime != nil { | ||||
| 		query = query.Where("transfer_time >= ?", *opts.StartTime) | ||||
| 	} | ||||
| 	if opts.EndTime != nil { | ||||
| 		query = query.Where("transfer_time <= ?", *opts.EndTime) | ||||
| 	} | ||||
|  | ||||
| 	if err := query.Count(&total).Error; err != nil { | ||||
| 		return nil, 0, err | ||||
| 	} | ||||
|  | ||||
| 	orderBy := "transfer_time DESC" | ||||
| 	if opts.OrderBy != "" { | ||||
| 		orderBy = opts.OrderBy | ||||
| 	} | ||||
| 	query = query.Order(orderBy) | ||||
|  | ||||
| 	offset := (page - 1) * pageSize | ||||
| 	err := query.Limit(pageSize).Offset(offset).Find(&results).Error | ||||
|  | ||||
| 	return results, total, err | ||||
| } | ||||
|   | ||||
							
								
								
									
										180
									
								
								internal/infra/repository/raw_material_repository.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										180
									
								
								internal/infra/repository/raw_material_repository.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,180 @@ | ||||
| package repository | ||||
|  | ||||
| import ( | ||||
| 	"time" | ||||
|  | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/infra/models" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| // RawMaterialPurchaseListOptions 定义了查询原料采购记录时的可选参数 | ||||
| type RawMaterialPurchaseListOptions struct { | ||||
| 	RawMaterialID *uint | ||||
| 	Supplier      *string | ||||
| 	StartTime     *time.Time // 基于 purchase_date 字段 | ||||
| 	EndTime       *time.Time // 基于 purchase_date 字段 | ||||
| 	OrderBy       string     // 例如 "purchase_date asc" | ||||
| } | ||||
|  | ||||
| // RawMaterialStockLogListOptions 定义了查询原料库存日志时的可选参数 | ||||
| type RawMaterialStockLogListOptions struct { | ||||
| 	RawMaterialID *uint | ||||
| 	SourceType    *models.StockLogSourceType | ||||
| 	SourceID      *uint | ||||
| 	StartTime     *time.Time // 基于 happened_at 字段 | ||||
| 	EndTime       *time.Time // 基于 happened_at 字段 | ||||
| 	OrderBy       string     // 例如 "happened_at asc" | ||||
| } | ||||
|  | ||||
| // FeedUsageRecordListOptions 定义了查询饲料使用记录时的可选参数 | ||||
| type FeedUsageRecordListOptions struct { | ||||
| 	PenID         *uint | ||||
| 	FeedFormulaID *uint | ||||
| 	OperatorID    *uint | ||||
| 	StartTime     *time.Time // 基于 recorded_at 字段 | ||||
| 	EndTime       *time.Time // 基于 recorded_at 字段 | ||||
| 	OrderBy       string     // 例如 "recorded_at asc" | ||||
| } | ||||
|  | ||||
| // RawMaterialRepository 定义了与原料相关的数据库操作接口 | ||||
| type RawMaterialRepository interface { | ||||
| 	ListRawMaterialPurchases(opts RawMaterialPurchaseListOptions, page, pageSize int) ([]models.RawMaterialPurchase, int64, error) | ||||
| 	ListRawMaterialStockLogs(opts RawMaterialStockLogListOptions, page, pageSize int) ([]models.RawMaterialStockLog, int64, error) | ||||
| 	ListFeedUsageRecords(opts FeedUsageRecordListOptions, page, pageSize int) ([]models.FeedUsageRecord, int64, error) | ||||
| } | ||||
|  | ||||
| // gormRawMaterialRepository 是 RawMaterialRepository 的 GORM 实现 | ||||
| type gormRawMaterialRepository struct { | ||||
| 	db *gorm.DB | ||||
| } | ||||
|  | ||||
| // NewGormRawMaterialRepository 创建一个新的 RawMaterialRepository GORM 实现实例 | ||||
| func NewGormRawMaterialRepository(db *gorm.DB) RawMaterialRepository { | ||||
| 	return &gormRawMaterialRepository{db: db} | ||||
| } | ||||
|  | ||||
| // ListRawMaterialPurchases 实现了分页和过滤查询原料采购记录的功能 | ||||
| func (r *gormRawMaterialRepository) ListRawMaterialPurchases(opts RawMaterialPurchaseListOptions, page, pageSize int) ([]models.RawMaterialPurchase, int64, error) { | ||||
| 	if page <= 0 || pageSize <= 0 { | ||||
| 		return nil, 0, ErrInvalidPagination | ||||
| 	} | ||||
|  | ||||
| 	var results []models.RawMaterialPurchase | ||||
| 	var total int64 | ||||
|  | ||||
| 	query := r.db.Model(&models.RawMaterialPurchase{}) | ||||
|  | ||||
| 	if opts.RawMaterialID != nil { | ||||
| 		query = query.Where("raw_material_id = ?", *opts.RawMaterialID) | ||||
| 	} | ||||
| 	if opts.Supplier != nil { | ||||
| 		query = query.Where("supplier LIKE ?", "%"+*opts.Supplier+"%") | ||||
| 	} | ||||
| 	if opts.StartTime != nil { | ||||
| 		query = query.Where("purchase_date >= ?", *opts.StartTime) | ||||
| 	} | ||||
| 	if opts.EndTime != nil { | ||||
| 		query = query.Where("purchase_date <= ?", *opts.EndTime) | ||||
| 	} | ||||
|  | ||||
| 	if err := query.Count(&total).Error; err != nil { | ||||
| 		return nil, 0, err | ||||
| 	} | ||||
|  | ||||
| 	orderBy := "purchase_date DESC" | ||||
| 	if opts.OrderBy != "" { | ||||
| 		orderBy = opts.OrderBy | ||||
| 	} | ||||
| 	query = query.Order(orderBy).Preload("RawMaterial") | ||||
|  | ||||
| 	offset := (page - 1) * pageSize | ||||
| 	err := query.Limit(pageSize).Offset(offset).Find(&results).Error | ||||
|  | ||||
| 	return results, total, err | ||||
| } | ||||
|  | ||||
| // ListRawMaterialStockLogs 实现了分页和过滤查询原料库存日志的功能 | ||||
| func (r *gormRawMaterialRepository) ListRawMaterialStockLogs(opts RawMaterialStockLogListOptions, page, pageSize int) ([]models.RawMaterialStockLog, int64, error) { | ||||
| 	if page <= 0 || pageSize <= 0 { | ||||
| 		return nil, 0, ErrInvalidPagination | ||||
| 	} | ||||
|  | ||||
| 	var results []models.RawMaterialStockLog | ||||
| 	var total int64 | ||||
|  | ||||
| 	query := r.db.Model(&models.RawMaterialStockLog{}) | ||||
|  | ||||
| 	if opts.RawMaterialID != nil { | ||||
| 		query = query.Where("raw_material_id = ?", *opts.RawMaterialID) | ||||
| 	} | ||||
| 	if opts.SourceType != nil { | ||||
| 		query = query.Where("source_type = ?", *opts.SourceType) | ||||
| 	} | ||||
| 	if opts.SourceID != nil { | ||||
| 		query = query.Where("source_id = ?", *opts.SourceID) | ||||
| 	} | ||||
| 	if opts.StartTime != nil { | ||||
| 		query = query.Where("happened_at >= ?", *opts.StartTime) | ||||
| 	} | ||||
| 	if opts.EndTime != nil { | ||||
| 		query = query.Where("happened_at <= ?", *opts.EndTime) | ||||
| 	} | ||||
|  | ||||
| 	if err := query.Count(&total).Error; err != nil { | ||||
| 		return nil, 0, err | ||||
| 	} | ||||
|  | ||||
| 	orderBy := "happened_at DESC" | ||||
| 	if opts.OrderBy != "" { | ||||
| 		orderBy = opts.OrderBy | ||||
| 	} | ||||
| 	query = query.Order(orderBy) | ||||
|  | ||||
| 	offset := (page - 1) * pageSize | ||||
| 	err := query.Limit(pageSize).Offset(offset).Find(&results).Error | ||||
|  | ||||
| 	return results, total, err | ||||
| } | ||||
|  | ||||
| // ListFeedUsageRecords 实现了分页和过滤查询饲料使用记录的功能 | ||||
| func (r *gormRawMaterialRepository) ListFeedUsageRecords(opts FeedUsageRecordListOptions, page, pageSize int) ([]models.FeedUsageRecord, int64, error) { | ||||
| 	if page <= 0 || pageSize <= 0 { | ||||
| 		return nil, 0, ErrInvalidPagination | ||||
| 	} | ||||
|  | ||||
| 	var results []models.FeedUsageRecord | ||||
| 	var total int64 | ||||
|  | ||||
| 	query := r.db.Model(&models.FeedUsageRecord{}) | ||||
|  | ||||
| 	if opts.PenID != nil { | ||||
| 		query = query.Where("pen_id = ?", *opts.PenID) | ||||
| 	} | ||||
| 	if opts.FeedFormulaID != nil { | ||||
| 		query = query.Where("feed_formula_id = ?", *opts.FeedFormulaID) | ||||
| 	} | ||||
| 	if opts.OperatorID != nil { | ||||
| 		query = query.Where("operator_id = ?", *opts.OperatorID) | ||||
| 	} | ||||
| 	if opts.StartTime != nil { | ||||
| 		query = query.Where("recorded_at >= ?", *opts.StartTime) | ||||
| 	} | ||||
| 	if opts.EndTime != nil { | ||||
| 		query = query.Where("recorded_at <= ?", *opts.EndTime) | ||||
| 	} | ||||
|  | ||||
| 	if err := query.Count(&total).Error; err != nil { | ||||
| 		return nil, 0, err | ||||
| 	} | ||||
|  | ||||
| 	orderBy := "recorded_at DESC" | ||||
| 	if opts.OrderBy != "" { | ||||
| 		orderBy = opts.OrderBy | ||||
| 	} | ||||
| 	query = query.Order(orderBy).Preload("Pen").Preload("FeedFormula") | ||||
|  | ||||
| 	offset := (page - 1) * pageSize | ||||
| 	err := query.Limit(pageSize).Offset(offset).Find(&results).Error | ||||
|  | ||||
| 	return results, total, err | ||||
| } | ||||
| @@ -1,16 +1,31 @@ | ||||
| package repository | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"time" | ||||
|  | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/infra/models" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| // ErrInvalidPagination 表示分页参数无效 | ||||
| var ErrInvalidPagination = errors.New("无效的分页参数:page和pageSize必须为大于0") | ||||
|  | ||||
| // SensorDataListOptions 定义了查询传感器数据列表时的可选参数 | ||||
| type SensorDataListOptions struct { | ||||
| 	DeviceID   *uint | ||||
| 	SensorType *models.SensorType | ||||
| 	StartTime  *time.Time | ||||
| 	EndTime    *time.Time | ||||
| 	OrderBy    string // 例如 "time DESC" | ||||
| } | ||||
|  | ||||
| // SensorDataRepository 定义了与传感器数据相关的数据库操作接口。 | ||||
| type SensorDataRepository interface { | ||||
| 	Create(sensorData *models.SensorData) error | ||||
| 	GetLatestSensorDataByDeviceIDAndSensorType(deviceID uint, sensorType models.SensorType) (*models.SensorData, error) | ||||
| 	// List 支持分页和过滤的列表查询 | ||||
| 	List(opts SensorDataListOptions, page, pageSize int) ([]models.SensorData, int64, error) | ||||
| } | ||||
|  | ||||
| // gormSensorDataRepository 是 SensorDataRepository 的 GORM 实现。 | ||||
| @@ -19,7 +34,6 @@ type gormSensorDataRepository struct { | ||||
| } | ||||
|  | ||||
| // NewGormSensorDataRepository 创建一个新的 SensorDataRepository GORM 实现实例。 | ||||
| // 它直接接收一个 *gorm.DB 实例作为依赖,完全遵循项目中的既定模式。 | ||||
| func NewGormSensorDataRepository(db *gorm.DB) SensorDataRepository { | ||||
| 	return &gormSensorDataRepository{db: db} | ||||
| } | ||||
| @@ -38,3 +52,48 @@ func (r *gormSensorDataRepository) GetLatestSensorDataByDeviceIDAndSensorType(de | ||||
| 		First(&sensorData).Error | ||||
| 	return &sensorData, err | ||||
| } | ||||
|  | ||||
| // List 实现了分页和过滤查询传感器数据的功能 | ||||
| func (r *gormSensorDataRepository) List(opts SensorDataListOptions, page, pageSize int) ([]models.SensorData, int64, error) { | ||||
| 	// --- 校验分页参数 --- | ||||
| 	if page <= 0 || pageSize <= 0 { | ||||
| 		return nil, 0, ErrInvalidPagination | ||||
| 	} | ||||
|  | ||||
| 	var results []models.SensorData | ||||
| 	var total int64 | ||||
|  | ||||
| 	query := r.db.Model(&models.SensorData{}) | ||||
|  | ||||
| 	// --- 应用过滤条件 --- | ||||
| 	if opts.DeviceID != nil { | ||||
| 		query = query.Where("device_id = ?", *opts.DeviceID) | ||||
| 	} | ||||
| 	if opts.SensorType != nil { | ||||
| 		query = query.Where("sensor_type = ?", *opts.SensorType) | ||||
| 	} | ||||
| 	if opts.StartTime != nil { | ||||
| 		query = query.Where("time >= ?", *opts.StartTime) | ||||
| 	} | ||||
| 	if opts.EndTime != nil { | ||||
| 		query = query.Where("time <= ?", *opts.EndTime) | ||||
| 	} | ||||
|  | ||||
| 	// --- 计算总数 --- | ||||
| 	if err := query.Count(&total).Error; err != nil { | ||||
| 		return nil, 0, err | ||||
| 	} | ||||
|  | ||||
| 	// --- 应用排序条件 --- | ||||
| 	orderBy := "time DESC" // 默认排序 | ||||
| 	if opts.OrderBy != "" { | ||||
| 		orderBy = opts.OrderBy | ||||
| 	} | ||||
| 	query = query.Order(orderBy) | ||||
|  | ||||
| 	// --- 分页 --- | ||||
| 	offset := (page - 1) * pageSize | ||||
| 	err := query.Limit(pageSize).Offset(offset).Find(&results).Error | ||||
|  | ||||
| 	return results, total, err | ||||
| } | ||||
|   | ||||
| @@ -1,22 +1,27 @@ | ||||
| package repository | ||||
|  | ||||
| import ( | ||||
| 	"time" | ||||
|  | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/infra/models" | ||||
| 	"gorm.io/gorm" | ||||
| ) | ||||
|  | ||||
| // FindAuditLogOptions 定义了查询审计日志的选项 | ||||
| type FindAuditLogOptions struct { | ||||
| 	UserID     *uint  // 根据用户ID过滤 | ||||
| 	ActionType string // 根据操作类型过滤 | ||||
| 	Page       int    // 页码 | ||||
| 	PageSize   int    // 每页大小 | ||||
| // UserActionLogListOptions 定义了查询用户操作日志时的可选参数 | ||||
| type UserActionLogListOptions struct { | ||||
| 	UserID     *uint | ||||
| 	Username   *string | ||||
| 	ActionType *string | ||||
| 	Status     *models.AuditStatus | ||||
| 	StartTime  *time.Time // 基于 time 字段 | ||||
| 	EndTime    *time.Time // 基于 time 字段 | ||||
| 	OrderBy    string     // 例如 "time asc" | ||||
| } | ||||
|  | ||||
| // UserActionLogRepository 定义了与用户操作日志相关的数据库操作接口 | ||||
| type UserActionLogRepository interface { | ||||
| 	Create(log *models.UserActionLog) error | ||||
| 	List(options FindAuditLogOptions) ([]*models.UserActionLog, int64, error) | ||||
| 	List(opts UserActionLogListOptions, page, pageSize int) ([]models.UserActionLog, int64, error) | ||||
| } | ||||
|  | ||||
| // gormUserActionLogRepository 是 UserActionLogRepository 的 GORM 实现 | ||||
| @@ -35,17 +40,33 @@ func (r *gormUserActionLogRepository) Create(log *models.UserActionLog) error { | ||||
| } | ||||
|  | ||||
| // List 根据选项查询用户操作日志,并返回总数 | ||||
| func (r *gormUserActionLogRepository) List(options FindAuditLogOptions) ([]*models.UserActionLog, int64, error) { | ||||
| 	var logs []*models.UserActionLog | ||||
| func (r *gormUserActionLogRepository) List(opts UserActionLogListOptions, page, pageSize int) ([]models.UserActionLog, int64, error) { | ||||
| 	if page <= 0 || pageSize <= 0 { | ||||
| 		return nil, 0, ErrInvalidPagination | ||||
| 	} | ||||
|  | ||||
| 	var logs []models.UserActionLog | ||||
| 	var total int64 | ||||
|  | ||||
| 	query := r.db.Model(&models.UserActionLog{}) | ||||
|  | ||||
| 	if options.UserID != nil { | ||||
| 		query = query.Where("user_id = ?", *options.UserID) | ||||
| 	if opts.UserID != nil { | ||||
| 		query = query.Where("user_id = ?", *opts.UserID) | ||||
| 	} | ||||
| 	if options.ActionType != "" { | ||||
| 		query = query.Where("action_type = ?", options.ActionType) | ||||
| 	if opts.Username != nil { | ||||
| 		query = query.Where("username LIKE ?", "%"+*opts.Username+"%") | ||||
| 	} | ||||
| 	if opts.ActionType != nil { | ||||
| 		query = query.Where("action_type = ?", *opts.ActionType) | ||||
| 	} | ||||
| 	if opts.Status != nil { | ||||
| 		query = query.Where("status = ?", *opts.Status) | ||||
| 	} | ||||
| 	if opts.StartTime != nil { | ||||
| 		query = query.Where("time >= ?", *opts.StartTime) | ||||
| 	} | ||||
| 	if opts.EndTime != nil { | ||||
| 		query = query.Where("time <= ?", *opts.EndTime) | ||||
| 	} | ||||
|  | ||||
| 	// 统计总数 | ||||
| @@ -53,16 +74,14 @@ func (r *gormUserActionLogRepository) List(options FindAuditLogOptions) ([]*mode | ||||
| 		return nil, 0, err | ||||
| 	} | ||||
|  | ||||
| 	// 分页查询 | ||||
| 	if options.Page > 0 && options.PageSize > 0 { | ||||
| 		offset := (options.Page - 1) * options.PageSize | ||||
| 		query = query.Offset(offset).Limit(options.PageSize) | ||||
| 	orderBy := "time DESC" | ||||
| 	if opts.OrderBy != "" { | ||||
| 		orderBy = opts.OrderBy | ||||
| 	} | ||||
| 	query = query.Order(orderBy) | ||||
|  | ||||
| 	// 默认按创建时间倒序 | ||||
| 	query = query.Order("created_at DESC") | ||||
|  | ||||
| 	if err := query.Find(&logs).Error; err != nil { | ||||
| 	offset := (page - 1) * pageSize | ||||
| 	if err := query.Limit(pageSize).Offset(offset).Find(&logs).Error; err != nil { | ||||
| 		return nil, 0, err | ||||
| 	} | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user