controller调整, 增加计划类型
This commit is contained in:
		| @@ -29,7 +29,7 @@ import ( | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/domain/audit" | ||||
| 	domain_device "git.huangwc.com/pig/pig-farm-controller/internal/domain/device" | ||||
| 	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/infra/config" | ||||
| 	"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs" | ||||
| @@ -40,21 +40,21 @@ import ( | ||||
|  | ||||
| // API 结构体定义了 HTTP 服务器及其依赖 | ||||
| type API struct { | ||||
| 	engine              *gin.Engine                    // Gin 引擎实例,用于处理 HTTP 请求 | ||||
| 	logger              *logs.Logger                   // 日志记录器,用于输出日志信息 | ||||
| 	userRepo            repository.UserRepository      // 用户数据仓库接口,用于用户数据操作 | ||||
| 	tokenService        token.Service                  // Token 服务接口,用于 JWT token 的生成和解析 | ||||
| 	auditService        audit.Service                  // 审计服务,用于记录用户操作 | ||||
| 	httpServer          *http.Server                   // 标准库的 HTTP 服务器实例,用于启动和停止服务 | ||||
| 	config              config.ServerConfig            // API 服务器的配置,使用 infra/config 包中的 ServerConfig | ||||
| 	userController      *user.Controller               // 用户控制器实例 | ||||
| 	deviceController    *device.Controller             // 设备控制器实例 | ||||
| 	planController      *plan.Controller               // 计划控制器实例 | ||||
| 	pigFarmController   *management.PigFarmController  // 猪场管理控制器实例 | ||||
| 	pigBatchController  *management.PigBatchController // 猪群控制器实例 | ||||
| 	monitorController   *monitor.Controller            // 数据监控控制器实例 | ||||
| 	listenHandler       webhook.ListenHandler          // 设备上行事件监听器 | ||||
| 	analysisTaskManager *task.AnalysisPlanTaskManager  // 计划触发器管理器实例 | ||||
| 	engine              *gin.Engine                        // Gin 引擎实例,用于处理 HTTP 请求 | ||||
| 	logger              *logs.Logger                       // 日志记录器,用于输出日志信息 | ||||
| 	userRepo            repository.UserRepository          // 用户数据仓库接口,用于用户数据操作 | ||||
| 	tokenService        token.Service                      // Token 服务接口,用于 JWT token 的生成和解析 | ||||
| 	auditService        audit.Service                      // 审计服务,用于记录用户操作 | ||||
| 	httpServer          *http.Server                       // 标准库的 HTTP 服务器实例,用于启动和停止服务 | ||||
| 	config              config.ServerConfig                // API 服务器的配置,使用 infra/config 包中的 ServerConfig | ||||
| 	userController      *user.Controller                   // 用户控制器实例 | ||||
| 	deviceController    *device.Controller                 // 设备控制器实例 | ||||
| 	planController      *plan.Controller                   // 计划控制器实例 | ||||
| 	pigFarmController   *management.PigFarmController      // 猪场管理控制器实例 | ||||
| 	pigBatchController  *management.PigBatchController     // 猪群控制器实例 | ||||
| 	monitorController   *monitor.Controller                // 数据监控控制器实例 | ||||
| 	listenHandler       webhook.ListenHandler              // 设备上行事件监听器 | ||||
| 	analysisTaskManager *scheduler.AnalysisPlanTaskManager // 计划触发器管理器实例 | ||||
| } | ||||
|  | ||||
| // NewAPI 创建并返回一个新的 API 实例 | ||||
| @@ -74,7 +74,7 @@ func NewAPI(cfg config.ServerConfig, | ||||
| 	notifyService domain_notify.Service, | ||||
| 	deviceService domain_device.Service, | ||||
| 	listenHandler webhook.ListenHandler, | ||||
| 	analysisTaskManager *task.AnalysisPlanTaskManager) *API { | ||||
| 	analysisTaskManager *scheduler.AnalysisPlanTaskManager) *API { | ||||
| 	// 设置 Gin 模式,例如 gin.ReleaseMode (生产模式) 或 gin.DebugMode (开发模式) | ||||
| 	// 从配置中获取 Gin 模式 | ||||
| 	gin.SetMode(cfg.Mode) | ||||
|   | ||||
| @@ -61,7 +61,11 @@ func (c *Controller) CreatePlan(ctx *gin.Context) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// --- 自动判断 ContentType --- | ||||
| 	// --- 业务规则处理 --- | ||||
| 	// 1. 设置计划类型:用户创建的计划永远是自定义计划 | ||||
| 	planToCreate.PlanType = models.PlanTypeCustom | ||||
|  | ||||
| 	// 2. 自动判断 ContentType | ||||
| 	if len(req.SubPlanIDs) > 0 { | ||||
| 		planToCreate.ContentType = models.PlanContentTypeSubPlans | ||||
| 	} else { | ||||
| @@ -145,16 +149,25 @@ func (c *Controller) GetPlan(ctx *gin.Context) { | ||||
|  | ||||
| // ListPlans godoc | ||||
| // @Summary      获取计划列表 | ||||
| // @Description  获取所有计划的列表 | ||||
| // @Description  获取所有计划的列表,支持按类型过滤和分页 | ||||
| // @Tags         计划管理 | ||||
| // @Security     BearerAuth | ||||
| // @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] | ||||
| func (c *Controller) ListPlans(ctx *gin.Context) { | ||||
| 	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. 调用仓库层获取所有计划 | ||||
| 	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 { | ||||
| 		c.logger.Errorf("%s: 数据库查询失败: %v", actionType, err) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取计划列表时发生内部错误", actionType, "数据库查询失败", nil) | ||||
| @@ -176,7 +189,7 @@ func (c *Controller) ListPlans(ctx *gin.Context) { | ||||
| 	// 3. 构造并发送成功响应 | ||||
| 	resp := dto.ListPlansResponse{ | ||||
| 		Plans: planResponses, | ||||
| 		Total: len(planResponses), | ||||
| 		Total: total, | ||||
| 	} | ||||
| 	c.logger.Infof("%s: 获取计划列表成功, 数量: %d", actionType, len(planResponses)) | ||||
| 	controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取计划列表成功", resp, actionType, "获取计划列表成功", resp) | ||||
| @@ -184,7 +197,7 @@ func (c *Controller) ListPlans(ctx *gin.Context) { | ||||
|  | ||||
| // UpdatePlan godoc | ||||
| // @Summary      更新计划 | ||||
| // @Description  根据计划ID更新计划的详细信息。 | ||||
| // @Description  根据计划ID更新计划的详细信息。系统计划不允许修改。 | ||||
| // @Tags         计划管理 | ||||
| // @Security     BearerAuth | ||||
| // @Accept       json | ||||
| @@ -212,7 +225,27 @@ func (c *Controller) UpdatePlan(ctx *gin.Context) { | ||||
| 		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) | ||||
| 	if err != nil { | ||||
| 		c.logger.Errorf("%s: 计划数据校验失败: %v", actionType, err) | ||||
| @@ -229,20 +262,7 @@ func (c *Controller) UpdatePlan(ctx *gin.Context) { | ||||
| 		planToUpdate.ContentType = models.PlanContentTypeTasks | ||||
| 	} | ||||
|  | ||||
| 	// 4. 检查计划是否存在 | ||||
| 	_, 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. 调用仓库方法更新计划 | ||||
| 	// 6. 调用仓库方法更新计划 | ||||
| 	// 只要是更新任务,就重置执行计数器 | ||||
| 	planToUpdate.ExecuteCount = 0 // 重置计数器 | ||||
| 	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) | ||||
| 	} | ||||
|  | ||||
| 	// 6. 获取更新后的完整计划用于响应 | ||||
| 	// 7. 获取更新后的完整计划用于响应 | ||||
| 	updatedPlan, err := c.planRepo.GetPlanByID(uint(id)) | ||||
| 	if err != nil { | ||||
| 		c.logger.Errorf("%s: 获取更新后计划详情失败: %v, ID: %d", actionType, err, id) | ||||
| @@ -267,7 +287,7 @@ func (c *Controller) UpdatePlan(ctx *gin.Context) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// 7. 将模型转换为响应 DTO | ||||
| 	// 8. 将模型转换为响应 DTO | ||||
| 	resp, err := dto.NewPlanToResponse(updatedPlan) | ||||
| 	if err != nil { | ||||
| 		c.logger.Errorf("%s: 序列化响应失败: %v, Updated Plan: %+v", actionType, err, updatedPlan) | ||||
| @@ -275,14 +295,14 @@ func (c *Controller) UpdatePlan(ctx *gin.Context) { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// 8. 发送成功响应 | ||||
| 	// 9. 发送成功响应 | ||||
| 	c.logger.Infof("%s: 计划更新成功, ID: %d", actionType, updatedPlan.ID) | ||||
| 	controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "计划更新成功", resp, actionType, "计划更新成功", resp) | ||||
| } | ||||
|  | ||||
| // DeletePlan godoc | ||||
| // @Summary      删除计划 | ||||
| // @Description  根据计划ID删除计划。(软删除) | ||||
| // @Description  根据计划ID删除计划。(软删除)系统计划不允许删除。 | ||||
| // @Tags         计划管理 | ||||
| // @Security     BearerAuth | ||||
| // @Produce      json | ||||
| @@ -313,7 +333,14 @@ func (c *Controller) DeletePlan(ctx *gin.Context) { | ||||
| 		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 err := c.planRepo.StopPlanTransactionally(uint(id)); err != nil { | ||||
| 			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 { | ||||
| 		c.logger.Errorf("%s: 数据库删除失败: %v, ID: %d", actionType, err, id) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "删除计划时发生内部错误", actionType, "数据库删除失败", id) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// 5. 发送成功响应 | ||||
| 	// 6. 发送成功响应 | ||||
| 	c.logger.Infof("%s: 计划删除成功, ID: %d", actionType, id) | ||||
| 	controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "计划删除成功", nil, actionType, "计划删除成功", id) | ||||
| } | ||||
|  | ||||
| // StartPlan godoc | ||||
| // @Summary      启动计划 | ||||
| // @Description  根据计划ID启动一个计划的执行。 | ||||
| // @Description  根据计划ID启动一个计划的执行。系统计划不允许手动启动。 | ||||
| // @Tags         计划管理 | ||||
| // @Security     BearerAuth | ||||
| // @Produce      json | ||||
| @@ -367,7 +394,12 @@ func (c *Controller) StartPlan(ctx *gin.Context) { | ||||
| 		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 { | ||||
| 		c.logger.Warnf("%s: 计划已处于启动状态,无需重复操作, ID: %d", actionType, id) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "计划已处于启动状态,无需重复操作", actionType, "计划已处于启动状态", id) | ||||
| @@ -416,7 +448,7 @@ func (c *Controller) StartPlan(ctx *gin.Context) { | ||||
|  | ||||
| // StopPlan godoc | ||||
| // @Summary      停止计划 | ||||
| // @Description  根据计划ID停止一个正在执行的计划。 | ||||
| // @Description  根据计划ID停止一个正在执行的计划。系统计划不能被停止。 | ||||
| // @Tags         计划管理 | ||||
| // @Security     BearerAuth | ||||
| // @Produce      json | ||||
| @@ -447,21 +479,28 @@ func (c *Controller) StopPlan(ctx *gin.Context) { | ||||
| 		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 { | ||||
| 		c.logger.Warnf("%s: 计划当前不是启用状态, ID: %d, Status: %s", actionType, id, plan.Status) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "计划当前不是启用状态", actionType, "计划未启用", id) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// 4. 调用仓库层方法,该方法内部处理事务 | ||||
| 	// 5. 调用仓库层方法,该方法内部处理事务 | ||||
| 	if err := c.planRepo.StopPlanTransactionally(uint(id)); err != nil { | ||||
| 		c.logger.Errorf("%s: 停止计划失败: %v, ID: %d", actionType, err, id) | ||||
| 		controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "停止计划时发生内部错误: "+err.Error(), actionType, "停止计划失败", id) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// 5. 发送成功响应 | ||||
| 	// 6. 发送成功响应 | ||||
| 	c.logger.Infof("%s: 计划已成功停止, ID: %d", actionType, id) | ||||
| 	controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "计划已成功停止", nil, actionType, "计划已成功停止", id) | ||||
| } | ||||
|   | ||||
| @@ -18,6 +18,7 @@ const ( | ||||
| 	// 客户端错误状态码 (4000-4999) | ||||
| 	CodeBadRequest   ResponseCode = 4000 // 请求参数错误 | ||||
| 	CodeUnauthorized ResponseCode = 4001 // 未授权 | ||||
| 	CodeForbidden    ResponseCode = 4003 // 禁止访问 | ||||
| 	CodeNotFound     ResponseCode = 4004 // 资源未找到 | ||||
| 	CodeConflict     ResponseCode = 4009 // 资源冲突 | ||||
|  | ||||
|   | ||||
| @@ -17,6 +17,7 @@ func NewPlanToResponse(plan *models.Plan) (*PlanResponse, error) { | ||||
| 		ID:             plan.ID, | ||||
| 		Name:           plan.Name, | ||||
| 		Description:    plan.Description, | ||||
| 		PlanType:       plan.PlanType, | ||||
| 		ExecutionType:  plan.ExecutionType, | ||||
| 		Status:         plan.Status, | ||||
| 		ExecuteNum:     plan.ExecuteNum, | ||||
| @@ -64,7 +65,7 @@ func NewPlanFromCreateRequest(req *CreatePlanRequest) (*models.Plan, error) { | ||||
| 		ExecutionType:  req.ExecutionType, | ||||
| 		ExecuteNum:     req.ExecuteNum, | ||||
| 		CronExpression: req.CronExpression, | ||||
| 		// ContentType 在控制器中设置,此处不再处理 | ||||
| 		// ContentType 和 PlanType 在控制器中设置,此处不再处理 | ||||
| 	} | ||||
|  | ||||
| 	// 处理子计划 (通过ID引用) | ||||
| @@ -116,7 +117,7 @@ func NewPlanFromUpdateRequest(req *UpdatePlanRequest) (*models.Plan, error) { | ||||
| 		ExecutionType:  req.ExecutionType, | ||||
| 		ExecuteNum:     req.ExecuteNum, | ||||
| 		CronExpression: req.CronExpression, | ||||
| 		// ContentType 在控制器中设置,此处不再处理 | ||||
| 		// ContentType 和 PlanType 在控制器中设置,此处不再处理 | ||||
| 	} | ||||
|  | ||||
| 	// 处理子计划 (通过ID引用) | ||||
|   | ||||
| @@ -2,6 +2,13 @@ package dto | ||||
|  | ||||
| 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 定义创建计划请求的结构体 | ||||
| type CreatePlanRequest struct { | ||||
| 	Name           string                   `json:"name" binding:"required" example:"猪舍温度控制计划"` | ||||
| @@ -18,6 +25,7 @@ type PlanResponse struct { | ||||
| 	ID             uint                     `json:"id" example:"1"` | ||||
| 	Name           string                   `json:"name" example:"猪舍温度控制计划"` | ||||
| 	Description    string                   `json:"description" example:"根据温度自动调节风扇和加热器"` | ||||
| 	PlanType       models.PlanType          `json:"plan_type" example:"自定义任务"` | ||||
| 	ExecutionType  models.PlanExecutionType `json:"execution_type" example:"自动"` | ||||
| 	Status         models.PlanStatus        `json:"status" example:"已启用"` | ||||
| 	ExecuteNum     uint                     `json:"execute_num" example:"10"` | ||||
| @@ -31,7 +39,7 @@ type PlanResponse struct { | ||||
| // ListPlansResponse 定义获取计划列表响应的结构体 | ||||
| type ListPlansResponse struct { | ||||
| 	Plans []PlanResponse `json:"plans"` | ||||
| 	Total int            `json:"total" example:"100"` | ||||
| 	Total int64          `json:"total" example:"100"` | ||||
| } | ||||
|  | ||||
| // UpdatePlanRequest 定义更新计划请求的结构体 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user