issue_42 #46
@@ -29,7 +29,7 @@ import (
 | 
				
			|||||||
	"git.huangwc.com/pig/pig-farm-controller/internal/domain/audit"
 | 
						"git.huangwc.com/pig/pig-farm-controller/internal/domain/audit"
 | 
				
			||||||
	domain_device "git.huangwc.com/pig/pig-farm-controller/internal/domain/device"
 | 
						domain_device "git.huangwc.com/pig/pig-farm-controller/internal/domain/device"
 | 
				
			||||||
	domain_notify "git.huangwc.com/pig/pig-farm-controller/internal/domain/notify"
 | 
						domain_notify "git.huangwc.com/pig/pig-farm-controller/internal/domain/notify"
 | 
				
			||||||
	"git.huangwc.com/pig/pig-farm-controller/internal/domain/task"
 | 
						"git.huangwc.com/pig/pig-farm-controller/internal/domain/scheduler"
 | 
				
			||||||
	"git.huangwc.com/pig/pig-farm-controller/internal/domain/token"
 | 
						"git.huangwc.com/pig/pig-farm-controller/internal/domain/token"
 | 
				
			||||||
	"git.huangwc.com/pig/pig-farm-controller/internal/infra/config"
 | 
						"git.huangwc.com/pig/pig-farm-controller/internal/infra/config"
 | 
				
			||||||
	"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
 | 
						"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
 | 
				
			||||||
@@ -54,7 +54,7 @@ type API struct {
 | 
				
			|||||||
	pigBatchController  *management.PigBatchController     // 猪群控制器实例
 | 
						pigBatchController  *management.PigBatchController     // 猪群控制器实例
 | 
				
			||||||
	monitorController   *monitor.Controller                // 数据监控控制器实例
 | 
						monitorController   *monitor.Controller                // 数据监控控制器实例
 | 
				
			||||||
	listenHandler       webhook.ListenHandler              // 设备上行事件监听器
 | 
						listenHandler       webhook.ListenHandler              // 设备上行事件监听器
 | 
				
			||||||
	analysisTaskManager *task.AnalysisPlanTaskManager  // 计划触发器管理器实例
 | 
						analysisTaskManager *scheduler.AnalysisPlanTaskManager // 计划触发器管理器实例
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewAPI 创建并返回一个新的 API 实例
 | 
					// NewAPI 创建并返回一个新的 API 实例
 | 
				
			||||||
@@ -74,7 +74,7 @@ func NewAPI(cfg config.ServerConfig,
 | 
				
			|||||||
	notifyService domain_notify.Service,
 | 
						notifyService domain_notify.Service,
 | 
				
			||||||
	deviceService domain_device.Service,
 | 
						deviceService domain_device.Service,
 | 
				
			||||||
	listenHandler webhook.ListenHandler,
 | 
						listenHandler webhook.ListenHandler,
 | 
				
			||||||
	analysisTaskManager *task.AnalysisPlanTaskManager) *API {
 | 
						analysisTaskManager *scheduler.AnalysisPlanTaskManager) *API {
 | 
				
			||||||
	// 设置 Gin 模式,例如 gin.ReleaseMode (生产模式) 或 gin.DebugMode (开发模式)
 | 
						// 设置 Gin 模式,例如 gin.ReleaseMode (生产模式) 或 gin.DebugMode (开发模式)
 | 
				
			||||||
	// 从配置中获取 Gin 模式
 | 
						// 从配置中获取 Gin 模式
 | 
				
			||||||
	gin.SetMode(cfg.Mode)
 | 
						gin.SetMode(cfg.Mode)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -61,7 +61,11 @@ func (c *Controller) CreatePlan(ctx *gin.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// --- 自动判断 ContentType ---
 | 
						// --- 业务规则处理 ---
 | 
				
			||||||
 | 
						// 1. 设置计划类型:用户创建的计划永远是自定义计划
 | 
				
			||||||
 | 
						planToCreate.PlanType = models.PlanTypeCustom
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 2. 自动判断 ContentType
 | 
				
			||||||
	if len(req.SubPlanIDs) > 0 {
 | 
						if len(req.SubPlanIDs) > 0 {
 | 
				
			||||||
		planToCreate.ContentType = models.PlanContentTypeSubPlans
 | 
							planToCreate.ContentType = models.PlanContentTypeSubPlans
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
@@ -145,16 +149,25 @@ func (c *Controller) GetPlan(ctx *gin.Context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// ListPlans godoc
 | 
					// ListPlans godoc
 | 
				
			||||||
// @Summary      获取计划列表
 | 
					// @Summary      获取计划列表
 | 
				
			||||||
// @Description  获取所有计划的列表
 | 
					// @Description  获取所有计划的列表,支持按类型过滤和分页
 | 
				
			||||||
// @Tags         计划管理
 | 
					// @Tags         计划管理
 | 
				
			||||||
// @Security     BearerAuth
 | 
					// @Security     BearerAuth
 | 
				
			||||||
// @Produce      json
 | 
					// @Produce      json
 | 
				
			||||||
// @Success      200 {object} controller.Response{data=[]dto.PlanResponse} "业务码为200代表成功获取列表"
 | 
					// @Param        query query dto.ListPlansQuery false "查询参数"
 | 
				
			||||||
 | 
					// @Success      200 {object} controller.Response{data=dto.ListPlansResponse} "业务码为200代表成功获取列表"
 | 
				
			||||||
// @Router       /api/v1/plans [get]
 | 
					// @Router       /api/v1/plans [get]
 | 
				
			||||||
func (c *Controller) ListPlans(ctx *gin.Context) {
 | 
					func (c *Controller) ListPlans(ctx *gin.Context) {
 | 
				
			||||||
	const actionType = "获取计划列表"
 | 
						const actionType = "获取计划列表"
 | 
				
			||||||
 | 
						var query dto.ListPlansQuery
 | 
				
			||||||
 | 
						if err := ctx.ShouldBindQuery(&query); err != nil {
 | 
				
			||||||
 | 
							c.logger.Errorf("%s: 查询参数绑定失败: %v", actionType, err)
 | 
				
			||||||
 | 
							controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "查询参数绑定失败", query)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 1. 调用仓库层获取所有计划
 | 
						// 1. 调用仓库层获取所有计划
 | 
				
			||||||
	plans, err := c.planRepo.ListBasicPlans()
 | 
						opts := repository.ListPlansOptions{PlanType: repository.PlanTypeFilter(query.PlanType)}
 | 
				
			||||||
 | 
						plans, total, err := c.planRepo.ListPlans(opts, query.Page, query.PageSize)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		c.logger.Errorf("%s: 数据库查询失败: %v", actionType, err)
 | 
							c.logger.Errorf("%s: 数据库查询失败: %v", actionType, err)
 | 
				
			||||||
		controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取计划列表时发生内部错误", actionType, "数据库查询失败", nil)
 | 
							controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取计划列表时发生内部错误", actionType, "数据库查询失败", nil)
 | 
				
			||||||
@@ -176,7 +189,7 @@ func (c *Controller) ListPlans(ctx *gin.Context) {
 | 
				
			|||||||
	// 3. 构造并发送成功响应
 | 
						// 3. 构造并发送成功响应
 | 
				
			||||||
	resp := dto.ListPlansResponse{
 | 
						resp := dto.ListPlansResponse{
 | 
				
			||||||
		Plans: planResponses,
 | 
							Plans: planResponses,
 | 
				
			||||||
		Total: len(planResponses),
 | 
							Total: total,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	c.logger.Infof("%s: 获取计划列表成功, 数量: %d", actionType, len(planResponses))
 | 
						c.logger.Infof("%s: 获取计划列表成功, 数量: %d", actionType, len(planResponses))
 | 
				
			||||||
	controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取计划列表成功", resp, actionType, "获取计划列表成功", resp)
 | 
						controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取计划列表成功", resp, actionType, "获取计划列表成功", resp)
 | 
				
			||||||
@@ -184,7 +197,7 @@ func (c *Controller) ListPlans(ctx *gin.Context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// UpdatePlan godoc
 | 
					// UpdatePlan godoc
 | 
				
			||||||
// @Summary      更新计划
 | 
					// @Summary      更新计划
 | 
				
			||||||
// @Description  根据计划ID更新计划的详细信息。
 | 
					// @Description  根据计划ID更新计划的详细信息。系统计划不允许修改。
 | 
				
			||||||
// @Tags         计划管理
 | 
					// @Tags         计划管理
 | 
				
			||||||
// @Security     BearerAuth
 | 
					// @Security     BearerAuth
 | 
				
			||||||
// @Accept       json
 | 
					// @Accept       json
 | 
				
			||||||
@@ -212,7 +225,27 @@ func (c *Controller) UpdatePlan(ctx *gin.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 3. 将请求转换为模型(转换函数带校验)
 | 
						// 3. 检查计划是否存在
 | 
				
			||||||
 | 
						existingPlan, err := c.planRepo.GetBasicPlanByID(uint(id))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if errors.Is(err, gorm.ErrRecordNotFound) {
 | 
				
			||||||
 | 
								c.logger.Warnf("%s: 计划不存在, ID: %d", actionType, id)
 | 
				
			||||||
 | 
								controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "计划不存在", actionType, "计划不存在", id)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							c.logger.Errorf("%s: 获取计划信息失败: %v, ID: %d", actionType, err, id)
 | 
				
			||||||
 | 
							controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取计划信息时发生内部错误", actionType, "数据库查询失败", id)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 4. 业务规则:系统计划不允许修改
 | 
				
			||||||
 | 
						if existingPlan.PlanType == models.PlanTypeSystem {
 | 
				
			||||||
 | 
							c.logger.Warnf("%s: 尝试修改系统计划, ID: %d", actionType, id)
 | 
				
			||||||
 | 
							controller.SendErrorWithAudit(ctx, controller.CodeForbidden, "系统计划不允许修改", actionType, "尝试修改系统计划", id)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 5. 将请求转换为模型(转换函数带校验)
 | 
				
			||||||
	planToUpdate, err := dto.NewPlanFromUpdateRequest(&req)
 | 
						planToUpdate, err := dto.NewPlanFromUpdateRequest(&req)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		c.logger.Errorf("%s: 计划数据校验失败: %v", actionType, err)
 | 
							c.logger.Errorf("%s: 计划数据校验失败: %v", actionType, err)
 | 
				
			||||||
@@ -229,20 +262,7 @@ func (c *Controller) UpdatePlan(ctx *gin.Context) {
 | 
				
			|||||||
		planToUpdate.ContentType = models.PlanContentTypeTasks
 | 
							planToUpdate.ContentType = models.PlanContentTypeTasks
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 4. 检查计划是否存在
 | 
						// 6. 调用仓库方法更新计划
 | 
				
			||||||
	_, err = c.planRepo.GetBasicPlanByID(uint(id))
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		if errors.Is(err, gorm.ErrRecordNotFound) {
 | 
					 | 
				
			||||||
			c.logger.Warnf("%s: 计划不存在, ID: %d", actionType, id)
 | 
					 | 
				
			||||||
			controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "计划不存在", actionType, "计划不存在", id)
 | 
					 | 
				
			||||||
			return
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		c.logger.Errorf("%s: 获取计划信息失败: %v, ID: %d", actionType, err, id)
 | 
					 | 
				
			||||||
		controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取计划信息时发生内部错误", actionType, "数据库查询失败", id)
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 5. 调用仓库方法更新计划
 | 
					 | 
				
			||||||
	// 只要是更新任务,就重置执行计数器
 | 
						// 只要是更新任务,就重置执行计数器
 | 
				
			||||||
	planToUpdate.ExecuteCount = 0 // 重置计数器
 | 
						planToUpdate.ExecuteCount = 0 // 重置计数器
 | 
				
			||||||
	c.logger.Infof("计划 #%d 被更新,执行计数器已重置为 0。", planToUpdate.ID)
 | 
						c.logger.Infof("计划 #%d 被更新,执行计数器已重置为 0。", planToUpdate.ID)
 | 
				
			||||||
@@ -259,7 +279,7 @@ func (c *Controller) UpdatePlan(ctx *gin.Context) {
 | 
				
			|||||||
		c.logger.Errorf("为更新后的计划 %d 确保触发器任务定义失败: %v", planToUpdate.ID, err)
 | 
							c.logger.Errorf("为更新后的计划 %d 确保触发器任务定义失败: %v", planToUpdate.ID, err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 6. 获取更新后的完整计划用于响应
 | 
						// 7. 获取更新后的完整计划用于响应
 | 
				
			||||||
	updatedPlan, err := c.planRepo.GetPlanByID(uint(id))
 | 
						updatedPlan, err := c.planRepo.GetPlanByID(uint(id))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		c.logger.Errorf("%s: 获取更新后计划详情失败: %v, ID: %d", actionType, err, id)
 | 
							c.logger.Errorf("%s: 获取更新后计划详情失败: %v, ID: %d", actionType, err, id)
 | 
				
			||||||
@@ -267,7 +287,7 @@ func (c *Controller) UpdatePlan(ctx *gin.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 7. 将模型转换为响应 DTO
 | 
						// 8. 将模型转换为响应 DTO
 | 
				
			||||||
	resp, err := dto.NewPlanToResponse(updatedPlan)
 | 
						resp, err := dto.NewPlanToResponse(updatedPlan)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		c.logger.Errorf("%s: 序列化响应失败: %v, Updated Plan: %+v", actionType, err, updatedPlan)
 | 
							c.logger.Errorf("%s: 序列化响应失败: %v, Updated Plan: %+v", actionType, err, updatedPlan)
 | 
				
			||||||
@@ -275,14 +295,14 @@ func (c *Controller) UpdatePlan(ctx *gin.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 8. 发送成功响应
 | 
						// 9. 发送成功响应
 | 
				
			||||||
	c.logger.Infof("%s: 计划更新成功, ID: %d", actionType, updatedPlan.ID)
 | 
						c.logger.Infof("%s: 计划更新成功, ID: %d", actionType, updatedPlan.ID)
 | 
				
			||||||
	controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "计划更新成功", resp, actionType, "计划更新成功", resp)
 | 
						controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "计划更新成功", resp, actionType, "计划更新成功", resp)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DeletePlan godoc
 | 
					// DeletePlan godoc
 | 
				
			||||||
// @Summary      删除计划
 | 
					// @Summary      删除计划
 | 
				
			||||||
// @Description  根据计划ID删除计划。(软删除)
 | 
					// @Description  根据计划ID删除计划。(软删除)系统计划不允许删除。
 | 
				
			||||||
// @Tags         计划管理
 | 
					// @Tags         计划管理
 | 
				
			||||||
// @Security     BearerAuth
 | 
					// @Security     BearerAuth
 | 
				
			||||||
// @Produce      json
 | 
					// @Produce      json
 | 
				
			||||||
@@ -313,7 +333,14 @@ func (c *Controller) DeletePlan(ctx *gin.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 3. 停止这个计划
 | 
						// 3. 业务规则:系统计划不允许删除
 | 
				
			||||||
 | 
						if plan.PlanType == models.PlanTypeSystem {
 | 
				
			||||||
 | 
							c.logger.Warnf("%s: 尝试删除系统计划, ID: %d", actionType, id)
 | 
				
			||||||
 | 
							controller.SendErrorWithAudit(ctx, controller.CodeForbidden, "系统计划不允许删除", actionType, "尝试删除系统计划", id)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 4. 停止这个计划
 | 
				
			||||||
	if plan.Status == models.PlanStatusEnabled {
 | 
						if plan.Status == models.PlanStatusEnabled {
 | 
				
			||||||
		if err := c.planRepo.StopPlanTransactionally(uint(id)); err != nil {
 | 
							if err := c.planRepo.StopPlanTransactionally(uint(id)); err != nil {
 | 
				
			||||||
			c.logger.Errorf("%s: 停止计划失败: %v, ID: %d", actionType, err, id)
 | 
								c.logger.Errorf("%s: 停止计划失败: %v, ID: %d", actionType, err, id)
 | 
				
			||||||
@@ -322,21 +349,21 @@ func (c *Controller) DeletePlan(ctx *gin.Context) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 4. 调用仓库层删除计划
 | 
						// 5. 调用仓库层删除计划
 | 
				
			||||||
	if err := c.planRepo.DeletePlan(uint(id)); err != nil {
 | 
						if err := c.planRepo.DeletePlan(uint(id)); err != nil {
 | 
				
			||||||
		c.logger.Errorf("%s: 数据库删除失败: %v, ID: %d", actionType, err, id)
 | 
							c.logger.Errorf("%s: 数据库删除失败: %v, ID: %d", actionType, err, id)
 | 
				
			||||||
		controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "删除计划时发生内部错误", actionType, "数据库删除失败", id)
 | 
							controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "删除计划时发生内部错误", actionType, "数据库删除失败", id)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 5. 发送成功响应
 | 
						// 6. 发送成功响应
 | 
				
			||||||
	c.logger.Infof("%s: 计划删除成功, ID: %d", actionType, id)
 | 
						c.logger.Infof("%s: 计划删除成功, ID: %d", actionType, id)
 | 
				
			||||||
	controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "计划删除成功", nil, actionType, "计划删除成功", id)
 | 
						controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "计划删除成功", nil, actionType, "计划删除成功", id)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// StartPlan godoc
 | 
					// StartPlan godoc
 | 
				
			||||||
// @Summary      启动计划
 | 
					// @Summary      启动计划
 | 
				
			||||||
// @Description  根据计划ID启动一个计划的执行。
 | 
					// @Description  根据计划ID启动一个计划的执行。系统计划不允许手动启动。
 | 
				
			||||||
// @Tags         计划管理
 | 
					// @Tags         计划管理
 | 
				
			||||||
// @Security     BearerAuth
 | 
					// @Security     BearerAuth
 | 
				
			||||||
// @Produce      json
 | 
					// @Produce      json
 | 
				
			||||||
@@ -367,7 +394,12 @@ func (c *Controller) StartPlan(ctx *gin.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 3. 检查计划当前状态
 | 
						// 3. 业务规则检查
 | 
				
			||||||
 | 
						if plan.PlanType == models.PlanTypeSystem {
 | 
				
			||||||
 | 
							c.logger.Warnf("%s: 尝试手动启动系统计划, ID: %d", actionType, id)
 | 
				
			||||||
 | 
							controller.SendErrorWithAudit(ctx, controller.CodeForbidden, "系统计划不允许手动启动", actionType, "尝试手动启动系统计划", id)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	if plan.Status == models.PlanStatusEnabled {
 | 
						if plan.Status == models.PlanStatusEnabled {
 | 
				
			||||||
		c.logger.Warnf("%s: 计划已处于启动状态,无需重复操作, ID: %d", actionType, id)
 | 
							c.logger.Warnf("%s: 计划已处于启动状态,无需重复操作, ID: %d", actionType, id)
 | 
				
			||||||
		controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "计划已处于启动状态,无需重复操作", actionType, "计划已处于启动状态", id)
 | 
							controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "计划已处于启动状态,无需重复操作", actionType, "计划已处于启动状态", id)
 | 
				
			||||||
@@ -416,7 +448,7 @@ func (c *Controller) StartPlan(ctx *gin.Context) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// StopPlan godoc
 | 
					// StopPlan godoc
 | 
				
			||||||
// @Summary      停止计划
 | 
					// @Summary      停止计划
 | 
				
			||||||
// @Description  根据计划ID停止一个正在执行的计划。
 | 
					// @Description  根据计划ID停止一个正在执行的计划。系统计划不能被停止。
 | 
				
			||||||
// @Tags         计划管理
 | 
					// @Tags         计划管理
 | 
				
			||||||
// @Security     BearerAuth
 | 
					// @Security     BearerAuth
 | 
				
			||||||
// @Produce      json
 | 
					// @Produce      json
 | 
				
			||||||
@@ -447,21 +479,28 @@ func (c *Controller) StopPlan(ctx *gin.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 3. 检查计划当前状态
 | 
						// 3. 业务规则:系统计划不允许停止
 | 
				
			||||||
 | 
						if plan.PlanType == models.PlanTypeSystem {
 | 
				
			||||||
 | 
							c.logger.Warnf("%s: 尝试停止系统计划, ID: %d", actionType, id)
 | 
				
			||||||
 | 
							controller.SendErrorWithAudit(ctx, controller.CodeForbidden, "系统计划不允许停止", actionType, "尝试停止系统计划", id)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 4. 检查计划当前状态
 | 
				
			||||||
	if plan.Status != models.PlanStatusEnabled {
 | 
						if plan.Status != models.PlanStatusEnabled {
 | 
				
			||||||
		c.logger.Warnf("%s: 计划当前不是启用状态, ID: %d, Status: %s", actionType, id, plan.Status)
 | 
							c.logger.Warnf("%s: 计划当前不是启用状态, ID: %d, Status: %s", actionType, id, plan.Status)
 | 
				
			||||||
		controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "计划当前不是启用状态", actionType, "计划未启用", id)
 | 
							controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "计划当前不是启用状态", actionType, "计划未启用", id)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 4. 调用仓库层方法,该方法内部处理事务
 | 
						// 5. 调用仓库层方法,该方法内部处理事务
 | 
				
			||||||
	if err := c.planRepo.StopPlanTransactionally(uint(id)); err != nil {
 | 
						if err := c.planRepo.StopPlanTransactionally(uint(id)); err != nil {
 | 
				
			||||||
		c.logger.Errorf("%s: 停止计划失败: %v, ID: %d", actionType, err, id)
 | 
							c.logger.Errorf("%s: 停止计划失败: %v, ID: %d", actionType, err, id)
 | 
				
			||||||
		controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "停止计划时发生内部错误: "+err.Error(), actionType, "停止计划失败", id)
 | 
							controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "停止计划时发生内部错误: "+err.Error(), actionType, "停止计划失败", id)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 5. 发送成功响应
 | 
						// 6. 发送成功响应
 | 
				
			||||||
	c.logger.Infof("%s: 计划已成功停止, ID: %d", actionType, id)
 | 
						c.logger.Infof("%s: 计划已成功停止, ID: %d", actionType, id)
 | 
				
			||||||
	controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "计划已成功停止", nil, actionType, "计划已成功停止", id)
 | 
						controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "计划已成功停止", nil, actionType, "计划已成功停止", id)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,6 +18,7 @@ const (
 | 
				
			|||||||
	// 客户端错误状态码 (4000-4999)
 | 
						// 客户端错误状态码 (4000-4999)
 | 
				
			||||||
	CodeBadRequest   ResponseCode = 4000 // 请求参数错误
 | 
						CodeBadRequest   ResponseCode = 4000 // 请求参数错误
 | 
				
			||||||
	CodeUnauthorized ResponseCode = 4001 // 未授权
 | 
						CodeUnauthorized ResponseCode = 4001 // 未授权
 | 
				
			||||||
 | 
						CodeForbidden    ResponseCode = 4003 // 禁止访问
 | 
				
			||||||
	CodeNotFound     ResponseCode = 4004 // 资源未找到
 | 
						CodeNotFound     ResponseCode = 4004 // 资源未找到
 | 
				
			||||||
	CodeConflict     ResponseCode = 4009 // 资源冲突
 | 
						CodeConflict     ResponseCode = 4009 // 资源冲突
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,6 +17,7 @@ func NewPlanToResponse(plan *models.Plan) (*PlanResponse, error) {
 | 
				
			|||||||
		ID:             plan.ID,
 | 
							ID:             plan.ID,
 | 
				
			||||||
		Name:           plan.Name,
 | 
							Name:           plan.Name,
 | 
				
			||||||
		Description:    plan.Description,
 | 
							Description:    plan.Description,
 | 
				
			||||||
 | 
							PlanType:       plan.PlanType,
 | 
				
			||||||
		ExecutionType:  plan.ExecutionType,
 | 
							ExecutionType:  plan.ExecutionType,
 | 
				
			||||||
		Status:         plan.Status,
 | 
							Status:         plan.Status,
 | 
				
			||||||
		ExecuteNum:     plan.ExecuteNum,
 | 
							ExecuteNum:     plan.ExecuteNum,
 | 
				
			||||||
@@ -64,7 +65,7 @@ func NewPlanFromCreateRequest(req *CreatePlanRequest) (*models.Plan, error) {
 | 
				
			|||||||
		ExecutionType:  req.ExecutionType,
 | 
							ExecutionType:  req.ExecutionType,
 | 
				
			||||||
		ExecuteNum:     req.ExecuteNum,
 | 
							ExecuteNum:     req.ExecuteNum,
 | 
				
			||||||
		CronExpression: req.CronExpression,
 | 
							CronExpression: req.CronExpression,
 | 
				
			||||||
		// ContentType 在控制器中设置,此处不再处理
 | 
							// ContentType 和 PlanType 在控制器中设置,此处不再处理
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 处理子计划 (通过ID引用)
 | 
						// 处理子计划 (通过ID引用)
 | 
				
			||||||
@@ -116,7 +117,7 @@ func NewPlanFromUpdateRequest(req *UpdatePlanRequest) (*models.Plan, error) {
 | 
				
			|||||||
		ExecutionType:  req.ExecutionType,
 | 
							ExecutionType:  req.ExecutionType,
 | 
				
			||||||
		ExecuteNum:     req.ExecuteNum,
 | 
							ExecuteNum:     req.ExecuteNum,
 | 
				
			||||||
		CronExpression: req.CronExpression,
 | 
							CronExpression: req.CronExpression,
 | 
				
			||||||
		// ContentType 在控制器中设置,此处不再处理
 | 
							// ContentType 和 PlanType 在控制器中设置,此处不再处理
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 处理子计划 (通过ID引用)
 | 
						// 处理子计划 (通过ID引用)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,13 @@ package dto
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import "git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
 | 
					import "git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ListPlansQuery 定义了获取计划列表时的查询参数
 | 
				
			||||||
 | 
					type ListPlansQuery struct {
 | 
				
			||||||
 | 
						PlanType string `form:"planType,default=custom"` // 计划类型 (all, custom, system),默认为 custom
 | 
				
			||||||
 | 
						Page     int    `form:"page,default=1"`          // 页码
 | 
				
			||||||
 | 
						PageSize int    `form:"pageSize,default=10"`     // 每页大小
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CreatePlanRequest 定义创建计划请求的结构体
 | 
					// CreatePlanRequest 定义创建计划请求的结构体
 | 
				
			||||||
type CreatePlanRequest struct {
 | 
					type CreatePlanRequest struct {
 | 
				
			||||||
	Name           string                   `json:"name" binding:"required" example:"猪舍温度控制计划"`
 | 
						Name           string                   `json:"name" binding:"required" example:"猪舍温度控制计划"`
 | 
				
			||||||
@@ -18,6 +25,7 @@ type PlanResponse struct {
 | 
				
			|||||||
	ID             uint                     `json:"id" example:"1"`
 | 
						ID             uint                     `json:"id" example:"1"`
 | 
				
			||||||
	Name           string                   `json:"name" example:"猪舍温度控制计划"`
 | 
						Name           string                   `json:"name" example:"猪舍温度控制计划"`
 | 
				
			||||||
	Description    string                   `json:"description" example:"根据温度自动调节风扇和加热器"`
 | 
						Description    string                   `json:"description" example:"根据温度自动调节风扇和加热器"`
 | 
				
			||||||
 | 
						PlanType       models.PlanType          `json:"plan_type" example:"自定义任务"`
 | 
				
			||||||
	ExecutionType  models.PlanExecutionType `json:"execution_type" example:"自动"`
 | 
						ExecutionType  models.PlanExecutionType `json:"execution_type" example:"自动"`
 | 
				
			||||||
	Status         models.PlanStatus        `json:"status" example:"已启用"`
 | 
						Status         models.PlanStatus        `json:"status" example:"已启用"`
 | 
				
			||||||
	ExecuteNum     uint                     `json:"execute_num" example:"10"`
 | 
						ExecuteNum     uint                     `json:"execute_num" example:"10"`
 | 
				
			||||||
@@ -31,7 +39,7 @@ type PlanResponse struct {
 | 
				
			|||||||
// ListPlansResponse 定义获取计划列表响应的结构体
 | 
					// ListPlansResponse 定义获取计划列表响应的结构体
 | 
				
			||||||
type ListPlansResponse struct {
 | 
					type ListPlansResponse struct {
 | 
				
			||||||
	Plans []PlanResponse `json:"plans"`
 | 
						Plans []PlanResponse `json:"plans"`
 | 
				
			||||||
	Total int            `json:"total" example:"100"`
 | 
						Total int64          `json:"total" example:"100"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// UpdatePlanRequest 定义更新计划请求的结构体
 | 
					// UpdatePlanRequest 定义更新计划请求的结构体
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,11 +21,25 @@ var (
 | 
				
			|||||||
	ErrDeleteWithReferencedPlan = errors.New("禁止删除正在被引用的计划")
 | 
						ErrDeleteWithReferencedPlan = errors.New("禁止删除正在被引用的计划")
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// PlanTypeFilter 定义计划类型的过滤器
 | 
				
			||||||
 | 
					type PlanTypeFilter string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						PlanTypeFilterAll    PlanTypeFilter = "all"
 | 
				
			||||||
 | 
						PlanTypeFilterCustom PlanTypeFilter = "custom"
 | 
				
			||||||
 | 
						PlanTypeFilterSystem PlanTypeFilter = "system"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ListPlansOptions 定义了查询计划时的可选参数
 | 
				
			||||||
 | 
					type ListPlansOptions struct {
 | 
				
			||||||
 | 
						PlanType PlanTypeFilter
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// PlanRepository 定义了与计划模型相关的数据库操作接口
 | 
					// PlanRepository 定义了与计划模型相关的数据库操作接口
 | 
				
			||||||
// 这是为了让业务逻辑层依赖于抽象,而不是具体的数据库实现
 | 
					// 这是为了让业务逻辑层依赖于抽象,而不是具体的数据库实现
 | 
				
			||||||
type PlanRepository interface {
 | 
					type PlanRepository interface {
 | 
				
			||||||
	// ListBasicPlans 获取所有计划的基本信息,不包含子计划和任务详情
 | 
						// ListPlans 获取计划列表,支持过滤和分页
 | 
				
			||||||
	ListBasicPlans() ([]models.Plan, error)
 | 
						ListPlans(opts ListPlansOptions, page, pageSize int) ([]models.Plan, int64, error)
 | 
				
			||||||
	// GetBasicPlanByID 根据ID获取计划的基本信息,不包含子计划和任务详情
 | 
						// GetBasicPlanByID 根据ID获取计划的基本信息,不包含子计划和任务详情
 | 
				
			||||||
	GetBasicPlanByID(id uint) (*models.Plan, error)
 | 
						GetBasicPlanByID(id uint) (*models.Plan, error)
 | 
				
			||||||
	// GetPlanByID 根据ID获取计划,包含子计划和任务详情
 | 
						// GetPlanByID 根据ID获取计划,包含子计划和任务详情
 | 
				
			||||||
@@ -81,15 +95,37 @@ func NewGormPlanRepository(db *gorm.DB) PlanRepository {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ListBasicPlans 获取所有计划的基本信息,不包含子计划和任务详情
 | 
					// ListPlans 获取计划列表,支持过滤和分页
 | 
				
			||||||
func (r *gormPlanRepository) ListBasicPlans() ([]models.Plan, error) {
 | 
					func (r *gormPlanRepository) ListPlans(opts ListPlansOptions, page, pageSize int) ([]models.Plan, int64, error) {
 | 
				
			||||||
	var plans []models.Plan
 | 
						if page <= 0 || pageSize <= 0 {
 | 
				
			||||||
	// GORM 默认不会加载关联,除非使用 Preload,所以直接 Find 即可满足要求
 | 
							return nil, 0, ErrInvalidPagination
 | 
				
			||||||
	result := r.db.Find(&plans)
 | 
					 | 
				
			||||||
	if result.Error != nil {
 | 
					 | 
				
			||||||
		return nil, result.Error
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return plans, nil
 | 
					
 | 
				
			||||||
 | 
						var plans []models.Plan
 | 
				
			||||||
 | 
						var total int64
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						query := r.db.Model(&models.Plan{})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch opts.PlanType {
 | 
				
			||||||
 | 
						case PlanTypeFilterCustom:
 | 
				
			||||||
 | 
							query = query.Where("plan_type = ?", models.PlanTypeCustom)
 | 
				
			||||||
 | 
						case PlanTypeFilterSystem:
 | 
				
			||||||
 | 
							query = query.Where("plan_type = ?", models.PlanTypeSystem)
 | 
				
			||||||
 | 
						case PlanTypeFilterAll:
 | 
				
			||||||
 | 
							// 不添加 plan_type 的过滤条件
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							// 默认查询自定义
 | 
				
			||||||
 | 
							query = query.Where("plan_type = ?", models.PlanTypeCustom)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := query.Count(&total).Error; err != nil {
 | 
				
			||||||
 | 
							return nil, 0, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						offset := (page - 1) * pageSize
 | 
				
			||||||
 | 
						err := query.Limit(pageSize).Offset(offset).Order("id DESC").Find(&plans).Error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return plans, total, err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetBasicPlanByID 根据ID获取计划的基本信息,不包含子计划和任务详情
 | 
					// GetBasicPlanByID 根据ID获取计划的基本信息,不包含子计划和任务详情
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user