Compare commits
	
		
			14 Commits
		
	
	
		
			46c7b96fed
			...
			ca2dedb2e2
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| ca2dedb2e2 | |||
| 769a0432c8 | |||
| 74e42de7aa | |||
| b0eb135f44 | |||
| 056279bdc2 | |||
| 6711f55fba | |||
| e85d4f8ec3 | |||
| 40a892e09d | |||
| 1f2d54d53e | |||
| cb63437e0e | |||
| 88e0fbfb64 | |||
| 3af1b4949f | |||
| 11502cb5f0 | |||
| d94a18779e | 
							
								
								
									
										4
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								Makefile
									
									
									
									
									
								
							@@ -37,7 +37,9 @@ test:
 | 
				
			|||||||
# 生成swagger文档
 | 
					# 生成swagger文档
 | 
				
			||||||
.PHONY: swag
 | 
					.PHONY: swag
 | 
				
			||||||
swag:
 | 
					swag:
 | 
				
			||||||
	swag init
 | 
						if exist docs rmdir /s /q docs
 | 
				
			||||||
 | 
						swag init --parseInternal --parseDependency
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# 生成protobuf文件
 | 
					# 生成protobuf文件
 | 
				
			||||||
.PHONY: proto
 | 
					.PHONY: proto
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,3 +12,6 @@
 | 
				
			|||||||
4. 暂时不考虑和区域主控间的同步消息, 假设所有消息都是异步的, 这可能导致无法知道指令是否执行成功
 | 
					4. 暂时不考虑和区域主控间的同步消息, 假设所有消息都是异步的, 这可能导致无法知道指令是否执行成功
 | 
				
			||||||
5. 如果系统停机时间很长, 待执行任务表中的任务过期了怎么办, 目前没有任务过期机制
 | 
					5. 如果系统停机时间很长, 待执行任务表中的任务过期了怎么办, 目前没有任务过期机制
 | 
				
			||||||
6. 可以用TimescaleDB代替PGSQL, 优化传感器数据存储性能
 | 
					6. 可以用TimescaleDB代替PGSQL, 优化传感器数据存储性能
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					任务调度器执行触发器任务时要修改一下对应计划的执行次数(如果是指定次数的计划)
 | 
				
			||||||
							
								
								
									
										303
									
								
								docs/docs.go
									
									
									
									
									
								
							
							
						
						
									
										303
									
								
								docs/docs.go
									
									
									
									
									
								
							@@ -15,7 +15,7 @@ const docTemplate = `{
 | 
				
			|||||||
    "host": "{{.Host}}",
 | 
					    "host": "{{.Host}}",
 | 
				
			||||||
    "basePath": "{{.BasePath}}",
 | 
					    "basePath": "{{.BasePath}}",
 | 
				
			||||||
    "paths": {
 | 
					    "paths": {
 | 
				
			||||||
        "/devices": {
 | 
					        "/api/v1/devices": {
 | 
				
			||||||
            "get": {
 | 
					            "get": {
 | 
				
			||||||
                "description": "获取系统中所有设备的列表",
 | 
					                "description": "获取系统中所有设备的列表",
 | 
				
			||||||
                "produces": [
 | 
					                "produces": [
 | 
				
			||||||
@@ -27,9 +27,24 @@ const docTemplate = `{
 | 
				
			|||||||
                "summary": "获取设备列表",
 | 
					                "summary": "获取设备列表",
 | 
				
			||||||
                "responses": {
 | 
					                "responses": {
 | 
				
			||||||
                    "200": {
 | 
					                    "200": {
 | 
				
			||||||
                        "description": "业务失败,具体错误码和信息见响应体",
 | 
					                        "description": "OK",
 | 
				
			||||||
                        "schema": {
 | 
					                        "schema": {
 | 
				
			||||||
                            "$ref": "#/definitions/controller.Response"
 | 
					                            "allOf": [
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "$ref": "#/definitions/controller.Response"
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "type": "object",
 | 
				
			||||||
 | 
					                                    "properties": {
 | 
				
			||||||
 | 
					                                        "data": {
 | 
				
			||||||
 | 
					                                            "type": "array",
 | 
				
			||||||
 | 
					                                            "items": {
 | 
				
			||||||
 | 
					                                                "$ref": "#/definitions/git_huangwc_com_pig_pig-farm-controller_internal_app_controller_device.DeviceResponse"
 | 
				
			||||||
 | 
					                                            }
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            ]
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -59,15 +74,27 @@ const docTemplate = `{
 | 
				
			|||||||
                ],
 | 
					                ],
 | 
				
			||||||
                "responses": {
 | 
					                "responses": {
 | 
				
			||||||
                    "200": {
 | 
					                    "200": {
 | 
				
			||||||
                        "description": "业务失败,具体错误码和信息见响应体",
 | 
					                        "description": "OK",
 | 
				
			||||||
                        "schema": {
 | 
					                        "schema": {
 | 
				
			||||||
                            "$ref": "#/definitions/controller.Response"
 | 
					                            "allOf": [
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "$ref": "#/definitions/controller.Response"
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "type": "object",
 | 
				
			||||||
 | 
					                                    "properties": {
 | 
				
			||||||
 | 
					                                        "data": {
 | 
				
			||||||
 | 
					                                            "$ref": "#/definitions/git_huangwc_com_pig_pig-farm-controller_internal_app_controller_device.DeviceResponse"
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            ]
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "/devices/{id}": {
 | 
					        "/api/v1/devices/{id}": {
 | 
				
			||||||
            "get": {
 | 
					            "get": {
 | 
				
			||||||
                "description": "根据设备ID获取单个设备的详细信息",
 | 
					                "description": "根据设备ID获取单个设备的详细信息",
 | 
				
			||||||
                "produces": [
 | 
					                "produces": [
 | 
				
			||||||
@@ -88,9 +115,21 @@ const docTemplate = `{
 | 
				
			|||||||
                ],
 | 
					                ],
 | 
				
			||||||
                "responses": {
 | 
					                "responses": {
 | 
				
			||||||
                    "200": {
 | 
					                    "200": {
 | 
				
			||||||
                        "description": "业务失败,具体错误码和信息见响应体",
 | 
					                        "description": "OK",
 | 
				
			||||||
                        "schema": {
 | 
					                        "schema": {
 | 
				
			||||||
                            "$ref": "#/definitions/controller.Response"
 | 
					                            "allOf": [
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "$ref": "#/definitions/controller.Response"
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "type": "object",
 | 
				
			||||||
 | 
					                                    "properties": {
 | 
				
			||||||
 | 
					                                        "data": {
 | 
				
			||||||
 | 
					                                            "$ref": "#/definitions/git_huangwc_com_pig_pig-farm-controller_internal_app_controller_device.DeviceResponse"
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            ]
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -127,9 +166,21 @@ const docTemplate = `{
 | 
				
			|||||||
                ],
 | 
					                ],
 | 
				
			||||||
                "responses": {
 | 
					                "responses": {
 | 
				
			||||||
                    "200": {
 | 
					                    "200": {
 | 
				
			||||||
                        "description": "业务失败,具体错误码和信息见响应体",
 | 
					                        "description": "OK",
 | 
				
			||||||
                        "schema": {
 | 
					                        "schema": {
 | 
				
			||||||
                            "$ref": "#/definitions/controller.Response"
 | 
					                            "allOf": [
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "$ref": "#/definitions/controller.Response"
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "type": "object",
 | 
				
			||||||
 | 
					                                    "properties": {
 | 
				
			||||||
 | 
					                                        "data": {
 | 
				
			||||||
 | 
					                                            "$ref": "#/definitions/git_huangwc_com_pig_pig-farm-controller_internal_app_controller_device.DeviceResponse"
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            ]
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -154,7 +205,7 @@ const docTemplate = `{
 | 
				
			|||||||
                ],
 | 
					                ],
 | 
				
			||||||
                "responses": {
 | 
					                "responses": {
 | 
				
			||||||
                    "200": {
 | 
					                    "200": {
 | 
				
			||||||
                        "description": "业务失败,具体错误码和信息见响应体",
 | 
					                        "description": "OK",
 | 
				
			||||||
                        "schema": {
 | 
					                        "schema": {
 | 
				
			||||||
                            "$ref": "#/definitions/controller.Response"
 | 
					                            "$ref": "#/definitions/controller.Response"
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
@@ -162,7 +213,7 @@ const docTemplate = `{
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "/plans": {
 | 
					        "/api/v1/plans": {
 | 
				
			||||||
            "get": {
 | 
					            "get": {
 | 
				
			||||||
                "description": "获取所有计划的列表",
 | 
					                "description": "获取所有计划的列表",
 | 
				
			||||||
                "produces": [
 | 
					                "produces": [
 | 
				
			||||||
@@ -174,9 +225,21 @@ const docTemplate = `{
 | 
				
			|||||||
                "summary": "获取计划列表",
 | 
					                "summary": "获取计划列表",
 | 
				
			||||||
                "responses": {
 | 
					                "responses": {
 | 
				
			||||||
                    "200": {
 | 
					                    "200": {
 | 
				
			||||||
                        "description": "业务失败,具体错误码和信息见响应体(例如400, 500)",
 | 
					                        "description": "业务码为200代表成功获取列表",
 | 
				
			||||||
                        "schema": {
 | 
					                        "schema": {
 | 
				
			||||||
                            "$ref": "#/definitions/controller.Response"
 | 
					                            "allOf": [
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "$ref": "#/definitions/controller.Response"
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "type": "object",
 | 
				
			||||||
 | 
					                                    "properties": {
 | 
				
			||||||
 | 
					                                        "data": {
 | 
				
			||||||
 | 
					                                            "$ref": "#/definitions/plan.ListPlansResponse"
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            ]
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -206,15 +269,27 @@ const docTemplate = `{
 | 
				
			|||||||
                ],
 | 
					                ],
 | 
				
			||||||
                "responses": {
 | 
					                "responses": {
 | 
				
			||||||
                    "200": {
 | 
					                    "200": {
 | 
				
			||||||
                        "description": "业务失败,具体错误码和信息见响应体(例如400, 500)",
 | 
					                        "description": "业务码为201代表创建成功",
 | 
				
			||||||
                        "schema": {
 | 
					                        "schema": {
 | 
				
			||||||
                            "$ref": "#/definitions/controller.Response"
 | 
					                            "allOf": [
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "$ref": "#/definitions/controller.Response"
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "type": "object",
 | 
				
			||||||
 | 
					                                    "properties": {
 | 
				
			||||||
 | 
					                                        "data": {
 | 
				
			||||||
 | 
					                                            "$ref": "#/definitions/plan.PlanResponse"
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            ]
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "/plans/{id}": {
 | 
					        "/api/v1/plans/{id}": {
 | 
				
			||||||
            "get": {
 | 
					            "get": {
 | 
				
			||||||
                "description": "根据计划ID获取单个计划的详细信息。",
 | 
					                "description": "根据计划ID获取单个计划的详细信息。",
 | 
				
			||||||
                "produces": [
 | 
					                "produces": [
 | 
				
			||||||
@@ -235,9 +310,21 @@ const docTemplate = `{
 | 
				
			|||||||
                ],
 | 
					                ],
 | 
				
			||||||
                "responses": {
 | 
					                "responses": {
 | 
				
			||||||
                    "200": {
 | 
					                    "200": {
 | 
				
			||||||
                        "description": "业务失败,具体错误码和信息见响应体(例如400, 404, 500)",
 | 
					                        "description": "业务码为200代表成功获取",
 | 
				
			||||||
                        "schema": {
 | 
					                        "schema": {
 | 
				
			||||||
                            "$ref": "#/definitions/controller.Response"
 | 
					                            "allOf": [
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "$ref": "#/definitions/controller.Response"
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "type": "object",
 | 
				
			||||||
 | 
					                                    "properties": {
 | 
				
			||||||
 | 
					                                        "data": {
 | 
				
			||||||
 | 
					                                            "$ref": "#/definitions/plan.PlanResponse"
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            ]
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -274,9 +361,21 @@ const docTemplate = `{
 | 
				
			|||||||
                ],
 | 
					                ],
 | 
				
			||||||
                "responses": {
 | 
					                "responses": {
 | 
				
			||||||
                    "200": {
 | 
					                    "200": {
 | 
				
			||||||
                        "description": "业务失败,具体错误码和信息见响应体(例如400, 404, 500)",
 | 
					                        "description": "业务码为200代表更新成功",
 | 
				
			||||||
                        "schema": {
 | 
					                        "schema": {
 | 
				
			||||||
                            "$ref": "#/definitions/controller.Response"
 | 
					                            "allOf": [
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "$ref": "#/definitions/controller.Response"
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "type": "object",
 | 
				
			||||||
 | 
					                                    "properties": {
 | 
				
			||||||
 | 
					                                        "data": {
 | 
				
			||||||
 | 
					                                            "$ref": "#/definitions/plan.PlanResponse"
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            ]
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -301,7 +400,7 @@ const docTemplate = `{
 | 
				
			|||||||
                ],
 | 
					                ],
 | 
				
			||||||
                "responses": {
 | 
					                "responses": {
 | 
				
			||||||
                    "200": {
 | 
					                    "200": {
 | 
				
			||||||
                        "description": "业务失败,具体错误码和信息见响应体(例如400, 404, 500)",
 | 
					                        "description": "业务码为200代表删除成功",
 | 
				
			||||||
                        "schema": {
 | 
					                        "schema": {
 | 
				
			||||||
                            "$ref": "#/definitions/controller.Response"
 | 
					                            "$ref": "#/definitions/controller.Response"
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
@@ -309,7 +408,7 @@ const docTemplate = `{
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "/plans/{id}/start": {
 | 
					        "/api/v1/plans/{id}/start": {
 | 
				
			||||||
            "post": {
 | 
					            "post": {
 | 
				
			||||||
                "description": "根据计划ID启动一个计划的执行。",
 | 
					                "description": "根据计划ID启动一个计划的执行。",
 | 
				
			||||||
                "produces": [
 | 
					                "produces": [
 | 
				
			||||||
@@ -330,7 +429,7 @@ const docTemplate = `{
 | 
				
			|||||||
                ],
 | 
					                ],
 | 
				
			||||||
                "responses": {
 | 
					                "responses": {
 | 
				
			||||||
                    "200": {
 | 
					                    "200": {
 | 
				
			||||||
                        "description": "业务失败,具体错误码和信息见响应体(例如400, 404, 500)",
 | 
					                        "description": "业务码为200代表成功启动计划",
 | 
				
			||||||
                        "schema": {
 | 
					                        "schema": {
 | 
				
			||||||
                            "$ref": "#/definitions/controller.Response"
 | 
					                            "$ref": "#/definitions/controller.Response"
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
@@ -338,7 +437,7 @@ const docTemplate = `{
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "/plans/{id}/stop": {
 | 
					        "/api/v1/plans/{id}/stop": {
 | 
				
			||||||
            "post": {
 | 
					            "post": {
 | 
				
			||||||
                "description": "根据计划ID停止一个正在执行的计划。",
 | 
					                "description": "根据计划ID停止一个正在执行的计划。",
 | 
				
			||||||
                "produces": [
 | 
					                "produces": [
 | 
				
			||||||
@@ -359,7 +458,7 @@ const docTemplate = `{
 | 
				
			|||||||
                ],
 | 
					                ],
 | 
				
			||||||
                "responses": {
 | 
					                "responses": {
 | 
				
			||||||
                    "200": {
 | 
					                    "200": {
 | 
				
			||||||
                        "description": "业务失败,具体错误码和信息见响应体(例如400, 404, 500)",
 | 
					                        "description": "业务码为200代表成功停止计划",
 | 
				
			||||||
                        "schema": {
 | 
					                        "schema": {
 | 
				
			||||||
                            "$ref": "#/definitions/controller.Response"
 | 
					                            "$ref": "#/definitions/controller.Response"
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
@@ -367,7 +466,7 @@ const docTemplate = `{
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "/users": {
 | 
					        "/api/v1/users": {
 | 
				
			||||||
            "post": {
 | 
					            "post": {
 | 
				
			||||||
                "description": "根据用户名和密码创建一个新的系统用户。",
 | 
					                "description": "根据用户名和密码创建一个新的系统用户。",
 | 
				
			||||||
                "consumes": [
 | 
					                "consumes": [
 | 
				
			||||||
@@ -393,15 +492,27 @@ const docTemplate = `{
 | 
				
			|||||||
                ],
 | 
					                ],
 | 
				
			||||||
                "responses": {
 | 
					                "responses": {
 | 
				
			||||||
                    "200": {
 | 
					                    "200": {
 | 
				
			||||||
                        "description": "业务失败,具体错误码和信息见响应体(例如400, 409, 500)",
 | 
					                        "description": "业务码为201代表创建成功",
 | 
				
			||||||
                        "schema": {
 | 
					                        "schema": {
 | 
				
			||||||
                            "$ref": "#/definitions/controller.Response"
 | 
					                            "allOf": [
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "$ref": "#/definitions/controller.Response"
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "type": "object",
 | 
				
			||||||
 | 
					                                    "properties": {
 | 
				
			||||||
 | 
					                                        "data": {
 | 
				
			||||||
 | 
					                                            "$ref": "#/definitions/user.CreateUserResponse"
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            ]
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "/users/login": {
 | 
					        "/api/v1/users/login": {
 | 
				
			||||||
            "post": {
 | 
					            "post": {
 | 
				
			||||||
                "description": "用户使用用户名和密码登录,成功后返回 JWT 令牌。",
 | 
					                "description": "用户使用用户名和密码登录,成功后返回 JWT 令牌。",
 | 
				
			||||||
                "consumes": [
 | 
					                "consumes": [
 | 
				
			||||||
@@ -427,9 +538,21 @@ const docTemplate = `{
 | 
				
			|||||||
                ],
 | 
					                ],
 | 
				
			||||||
                "responses": {
 | 
					                "responses": {
 | 
				
			||||||
                    "200": {
 | 
					                    "200": {
 | 
				
			||||||
                        "description": "业务失败,具体错误码和信息见响应体(例如400, 401, 500)",
 | 
					                        "description": "业务码为200代表登录成功",
 | 
				
			||||||
                        "schema": {
 | 
					                        "schema": {
 | 
				
			||||||
                            "$ref": "#/definitions/controller.Response"
 | 
					                            "allOf": [
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "$ref": "#/definitions/controller.Response"
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "type": "object",
 | 
				
			||||||
 | 
					                                    "properties": {
 | 
				
			||||||
 | 
					                                        "data": {
 | 
				
			||||||
 | 
					                                            "$ref": "#/definitions/user.LoginResponse"
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            ]
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -437,9 +560,6 @@ const docTemplate = `{
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "definitions": {
 | 
					    "definitions": {
 | 
				
			||||||
        "controller.Properties": {
 | 
					 | 
				
			||||||
            "type": "object"
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "controller.Response": {
 | 
					        "controller.Response": {
 | 
				
			||||||
            "type": "object",
 | 
					            "type": "object",
 | 
				
			||||||
            "properties": {
 | 
					            "properties": {
 | 
				
			||||||
@@ -457,17 +577,12 @@ const docTemplate = `{
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "device.CreateDeviceRequest": {
 | 
					        "device.CreateDeviceRequest": {
 | 
				
			||||||
            "type": "object"
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "device.DeviceResponse": {
 | 
					 | 
				
			||||||
            "type": "object",
 | 
					            "type": "object",
 | 
				
			||||||
 | 
					            "required": [
 | 
				
			||||||
 | 
					                "name",
 | 
				
			||||||
 | 
					                "type"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
            "properties": {
 | 
					            "properties": {
 | 
				
			||||||
                "created_at": {
 | 
					 | 
				
			||||||
                    "type": "string"
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                "id": {
 | 
					 | 
				
			||||||
                    "type": "integer"
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                "location": {
 | 
					                "location": {
 | 
				
			||||||
                    "type": "string"
 | 
					                    "type": "string"
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
@@ -478,16 +593,14 @@ const docTemplate = `{
 | 
				
			|||||||
                    "type": "integer"
 | 
					                    "type": "integer"
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                "properties": {
 | 
					                "properties": {
 | 
				
			||||||
                    "$ref": "#/definitions/controller.Properties"
 | 
					                    "type": "object",
 | 
				
			||||||
 | 
					                    "additionalProperties": true
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                "sub_type": {
 | 
					                "sub_type": {
 | 
				
			||||||
                    "$ref": "#/definitions/models.DeviceSubType"
 | 
					                    "$ref": "#/definitions/models.DeviceSubType"
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                "type": {
 | 
					                "type": {
 | 
				
			||||||
                    "$ref": "#/definitions/models.DeviceType"
 | 
					                    "$ref": "#/definitions/models.DeviceType"
 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                "updated_at": {
 | 
					 | 
				
			||||||
                    "type": "string"
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
@@ -508,7 +621,8 @@ const docTemplate = `{
 | 
				
			|||||||
                    "type": "integer"
 | 
					                    "type": "integer"
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                "properties": {
 | 
					                "properties": {
 | 
				
			||||||
                    "$ref": "#/definitions/controller.Properties"
 | 
					                    "type": "object",
 | 
				
			||||||
 | 
					                    "additionalProperties": true
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                "sub_type": {
 | 
					                "sub_type": {
 | 
				
			||||||
                    "$ref": "#/definitions/models.DeviceSubType"
 | 
					                    "$ref": "#/definitions/models.DeviceSubType"
 | 
				
			||||||
@@ -518,6 +632,39 @@ const docTemplate = `{
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        "git_huangwc_com_pig_pig-farm-controller_internal_app_controller_device.DeviceResponse": {
 | 
				
			||||||
 | 
					            "type": "object",
 | 
				
			||||||
 | 
					            "properties": {
 | 
				
			||||||
 | 
					                "created_at": {
 | 
				
			||||||
 | 
					                    "type": "string"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "id": {
 | 
				
			||||||
 | 
					                    "type": "integer"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "location": {
 | 
				
			||||||
 | 
					                    "type": "string"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "name": {
 | 
				
			||||||
 | 
					                    "type": "string"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "parent_id": {
 | 
				
			||||||
 | 
					                    "type": "integer"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "properties": {
 | 
				
			||||||
 | 
					                    "type": "object",
 | 
				
			||||||
 | 
					                    "additionalProperties": true
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "sub_type": {
 | 
				
			||||||
 | 
					                    "$ref": "#/definitions/models.DeviceSubType"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "type": {
 | 
				
			||||||
 | 
					                    "$ref": "#/definitions/models.DeviceType"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "updated_at": {
 | 
				
			||||||
 | 
					                    "type": "string"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "models.DeviceSubType": {
 | 
					        "models.DeviceSubType": {
 | 
				
			||||||
            "type": "string",
 | 
					            "type": "string",
 | 
				
			||||||
            "enum": [
 | 
					            "enum": [
 | 
				
			||||||
@@ -588,18 +735,46 @@ const docTemplate = `{
 | 
				
			|||||||
                "PlanExecutionTypeManual"
 | 
					                "PlanExecutionTypeManual"
 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        "models.PlanStatus": {
 | 
				
			||||||
 | 
					            "type": "integer",
 | 
				
			||||||
 | 
					            "format": "int32",
 | 
				
			||||||
 | 
					            "enum": [
 | 
				
			||||||
 | 
					                0,
 | 
				
			||||||
 | 
					                1,
 | 
				
			||||||
 | 
					                2
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "x-enum-comments": {
 | 
				
			||||||
 | 
					                "PlanStatusDisabled": "禁用计划",
 | 
				
			||||||
 | 
					                "PlanStatusEnabled": "启用计划",
 | 
				
			||||||
 | 
					                "PlanStatusStopeed": "执行完毕"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "x-enum-descriptions": [
 | 
				
			||||||
 | 
					                "启用计划",
 | 
				
			||||||
 | 
					                "禁用计划",
 | 
				
			||||||
 | 
					                "执行完毕"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "x-enum-varnames": [
 | 
				
			||||||
 | 
					                "PlanStatusEnabled",
 | 
				
			||||||
 | 
					                "PlanStatusDisabled",
 | 
				
			||||||
 | 
					                "PlanStatusStopeed"
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "models.TaskType": {
 | 
					        "models.TaskType": {
 | 
				
			||||||
            "type": "string",
 | 
					            "type": "string",
 | 
				
			||||||
            "enum": [
 | 
					            "enum": [
 | 
				
			||||||
 | 
					                "plan_analysis",
 | 
				
			||||||
                "waiting"
 | 
					                "waiting"
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
            "x-enum-comments": {
 | 
					            "x-enum-comments": {
 | 
				
			||||||
 | 
					                "TaskPlanAnalysis": "解析Plan的Task列表并添加到待执行队列的特殊任务",
 | 
				
			||||||
                "TaskTypeWaiting": "等待任务"
 | 
					                "TaskTypeWaiting": "等待任务"
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            "x-enum-descriptions": [
 | 
					            "x-enum-descriptions": [
 | 
				
			||||||
 | 
					                "解析Plan的Task列表并添加到待执行队列的特殊任务",
 | 
				
			||||||
                "等待任务"
 | 
					                "等待任务"
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
            "x-enum-varnames": [
 | 
					            "x-enum-varnames": [
 | 
				
			||||||
 | 
					                "TaskPlanAnalysis",
 | 
				
			||||||
                "TaskTypeWaiting"
 | 
					                "TaskTypeWaiting"
 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
@@ -627,6 +802,10 @@ const docTemplate = `{
 | 
				
			|||||||
                    "type": "string",
 | 
					                    "type": "string",
 | 
				
			||||||
                    "example": "根据温度自动调节风扇和加热器"
 | 
					                    "example": "根据温度自动调节风扇和加热器"
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
 | 
					                "execute_num": {
 | 
				
			||||||
 | 
					                    "type": "integer",
 | 
				
			||||||
 | 
					                    "example": 10
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
                "execution_type": {
 | 
					                "execution_type": {
 | 
				
			||||||
                    "allOf": [
 | 
					                    "allOf": [
 | 
				
			||||||
                        {
 | 
					                        {
 | 
				
			||||||
@@ -687,6 +866,14 @@ const docTemplate = `{
 | 
				
			|||||||
                    "type": "string",
 | 
					                    "type": "string",
 | 
				
			||||||
                    "example": "根据温度自动调节风扇和加热器"
 | 
					                    "example": "根据温度自动调节风扇和加热器"
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
 | 
					                "execute_count": {
 | 
				
			||||||
 | 
					                    "type": "integer",
 | 
				
			||||||
 | 
					                    "example": 0
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "execute_num": {
 | 
				
			||||||
 | 
					                    "type": "integer",
 | 
				
			||||||
 | 
					                    "example": 10
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
                "execution_type": {
 | 
					                "execution_type": {
 | 
				
			||||||
                    "allOf": [
 | 
					                    "allOf": [
 | 
				
			||||||
                        {
 | 
					                        {
 | 
				
			||||||
@@ -703,6 +890,14 @@ const docTemplate = `{
 | 
				
			|||||||
                    "type": "string",
 | 
					                    "type": "string",
 | 
				
			||||||
                    "example": "猪舍温度控制计划"
 | 
					                    "example": "猪舍温度控制计划"
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
 | 
					                "status": {
 | 
				
			||||||
 | 
					                    "allOf": [
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            "$ref": "#/definitions/models.PlanStatus"
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    ],
 | 
				
			||||||
 | 
					                    "example": 0
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
                "sub_plans": {
 | 
					                "sub_plans": {
 | 
				
			||||||
                    "type": "array",
 | 
					                    "type": "array",
 | 
				
			||||||
                    "items": {
 | 
					                    "items": {
 | 
				
			||||||
@@ -757,7 +952,8 @@ const docTemplate = `{
 | 
				
			|||||||
                    "example": "打开风扇"
 | 
					                    "example": "打开风扇"
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                "parameters": {
 | 
					                "parameters": {
 | 
				
			||||||
                    "$ref": "#/definitions/controller.Properties"
 | 
					                    "type": "object",
 | 
				
			||||||
 | 
					                    "additionalProperties": true
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                "type": {
 | 
					                "type": {
 | 
				
			||||||
                    "allOf": [
 | 
					                    "allOf": [
 | 
				
			||||||
@@ -789,7 +985,8 @@ const docTemplate = `{
 | 
				
			|||||||
                    "example": "打开风扇"
 | 
					                    "example": "打开风扇"
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                "parameters": {
 | 
					                "parameters": {
 | 
				
			||||||
                    "$ref": "#/definitions/controller.Properties"
 | 
					                    "type": "object",
 | 
				
			||||||
 | 
					                    "additionalProperties": true
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                "plan_id": {
 | 
					                "plan_id": {
 | 
				
			||||||
                    "type": "integer",
 | 
					                    "type": "integer",
 | 
				
			||||||
@@ -824,6 +1021,10 @@ const docTemplate = `{
 | 
				
			|||||||
                    "type": "string",
 | 
					                    "type": "string",
 | 
				
			||||||
                    "example": "更新后的描述"
 | 
					                    "example": "更新后的描述"
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
 | 
					                "execute_num": {
 | 
				
			||||||
 | 
					                    "type": "integer",
 | 
				
			||||||
 | 
					                    "example": 10
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
                "execution_type": {
 | 
					                "execution_type": {
 | 
				
			||||||
                    "allOf": [
 | 
					                    "allOf": [
 | 
				
			||||||
                        {
 | 
					                        {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,7 +4,7 @@
 | 
				
			|||||||
        "contact": {}
 | 
					        "contact": {}
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "paths": {
 | 
					    "paths": {
 | 
				
			||||||
        "/devices": {
 | 
					        "/api/v1/devices": {
 | 
				
			||||||
            "get": {
 | 
					            "get": {
 | 
				
			||||||
                "description": "获取系统中所有设备的列表",
 | 
					                "description": "获取系统中所有设备的列表",
 | 
				
			||||||
                "produces": [
 | 
					                "produces": [
 | 
				
			||||||
@@ -16,9 +16,24 @@
 | 
				
			|||||||
                "summary": "获取设备列表",
 | 
					                "summary": "获取设备列表",
 | 
				
			||||||
                "responses": {
 | 
					                "responses": {
 | 
				
			||||||
                    "200": {
 | 
					                    "200": {
 | 
				
			||||||
                        "description": "业务失败,具体错误码和信息见响应体",
 | 
					                        "description": "OK",
 | 
				
			||||||
                        "schema": {
 | 
					                        "schema": {
 | 
				
			||||||
                            "$ref": "#/definitions/controller.Response"
 | 
					                            "allOf": [
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "$ref": "#/definitions/controller.Response"
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "type": "object",
 | 
				
			||||||
 | 
					                                    "properties": {
 | 
				
			||||||
 | 
					                                        "data": {
 | 
				
			||||||
 | 
					                                            "type": "array",
 | 
				
			||||||
 | 
					                                            "items": {
 | 
				
			||||||
 | 
					                                                "$ref": "#/definitions/git_huangwc_com_pig_pig-farm-controller_internal_app_controller_device.DeviceResponse"
 | 
				
			||||||
 | 
					                                            }
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            ]
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -48,15 +63,27 @@
 | 
				
			|||||||
                ],
 | 
					                ],
 | 
				
			||||||
                "responses": {
 | 
					                "responses": {
 | 
				
			||||||
                    "200": {
 | 
					                    "200": {
 | 
				
			||||||
                        "description": "业务失败,具体错误码和信息见响应体",
 | 
					                        "description": "OK",
 | 
				
			||||||
                        "schema": {
 | 
					                        "schema": {
 | 
				
			||||||
                            "$ref": "#/definitions/controller.Response"
 | 
					                            "allOf": [
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "$ref": "#/definitions/controller.Response"
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "type": "object",
 | 
				
			||||||
 | 
					                                    "properties": {
 | 
				
			||||||
 | 
					                                        "data": {
 | 
				
			||||||
 | 
					                                            "$ref": "#/definitions/git_huangwc_com_pig_pig-farm-controller_internal_app_controller_device.DeviceResponse"
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            ]
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "/devices/{id}": {
 | 
					        "/api/v1/devices/{id}": {
 | 
				
			||||||
            "get": {
 | 
					            "get": {
 | 
				
			||||||
                "description": "根据设备ID获取单个设备的详细信息",
 | 
					                "description": "根据设备ID获取单个设备的详细信息",
 | 
				
			||||||
                "produces": [
 | 
					                "produces": [
 | 
				
			||||||
@@ -77,9 +104,21 @@
 | 
				
			|||||||
                ],
 | 
					                ],
 | 
				
			||||||
                "responses": {
 | 
					                "responses": {
 | 
				
			||||||
                    "200": {
 | 
					                    "200": {
 | 
				
			||||||
                        "description": "业务失败,具体错误码和信息见响应体",
 | 
					                        "description": "OK",
 | 
				
			||||||
                        "schema": {
 | 
					                        "schema": {
 | 
				
			||||||
                            "$ref": "#/definitions/controller.Response"
 | 
					                            "allOf": [
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "$ref": "#/definitions/controller.Response"
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "type": "object",
 | 
				
			||||||
 | 
					                                    "properties": {
 | 
				
			||||||
 | 
					                                        "data": {
 | 
				
			||||||
 | 
					                                            "$ref": "#/definitions/git_huangwc_com_pig_pig-farm-controller_internal_app_controller_device.DeviceResponse"
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            ]
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -116,9 +155,21 @@
 | 
				
			|||||||
                ],
 | 
					                ],
 | 
				
			||||||
                "responses": {
 | 
					                "responses": {
 | 
				
			||||||
                    "200": {
 | 
					                    "200": {
 | 
				
			||||||
                        "description": "业务失败,具体错误码和信息见响应体",
 | 
					                        "description": "OK",
 | 
				
			||||||
                        "schema": {
 | 
					                        "schema": {
 | 
				
			||||||
                            "$ref": "#/definitions/controller.Response"
 | 
					                            "allOf": [
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "$ref": "#/definitions/controller.Response"
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "type": "object",
 | 
				
			||||||
 | 
					                                    "properties": {
 | 
				
			||||||
 | 
					                                        "data": {
 | 
				
			||||||
 | 
					                                            "$ref": "#/definitions/git_huangwc_com_pig_pig-farm-controller_internal_app_controller_device.DeviceResponse"
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            ]
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -143,7 +194,7 @@
 | 
				
			|||||||
                ],
 | 
					                ],
 | 
				
			||||||
                "responses": {
 | 
					                "responses": {
 | 
				
			||||||
                    "200": {
 | 
					                    "200": {
 | 
				
			||||||
                        "description": "业务失败,具体错误码和信息见响应体",
 | 
					                        "description": "OK",
 | 
				
			||||||
                        "schema": {
 | 
					                        "schema": {
 | 
				
			||||||
                            "$ref": "#/definitions/controller.Response"
 | 
					                            "$ref": "#/definitions/controller.Response"
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
@@ -151,7 +202,7 @@
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "/plans": {
 | 
					        "/api/v1/plans": {
 | 
				
			||||||
            "get": {
 | 
					            "get": {
 | 
				
			||||||
                "description": "获取所有计划的列表",
 | 
					                "description": "获取所有计划的列表",
 | 
				
			||||||
                "produces": [
 | 
					                "produces": [
 | 
				
			||||||
@@ -163,9 +214,21 @@
 | 
				
			|||||||
                "summary": "获取计划列表",
 | 
					                "summary": "获取计划列表",
 | 
				
			||||||
                "responses": {
 | 
					                "responses": {
 | 
				
			||||||
                    "200": {
 | 
					                    "200": {
 | 
				
			||||||
                        "description": "业务失败,具体错误码和信息见响应体(例如400, 500)",
 | 
					                        "description": "业务码为200代表成功获取列表",
 | 
				
			||||||
                        "schema": {
 | 
					                        "schema": {
 | 
				
			||||||
                            "$ref": "#/definitions/controller.Response"
 | 
					                            "allOf": [
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "$ref": "#/definitions/controller.Response"
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "type": "object",
 | 
				
			||||||
 | 
					                                    "properties": {
 | 
				
			||||||
 | 
					                                        "data": {
 | 
				
			||||||
 | 
					                                            "$ref": "#/definitions/plan.ListPlansResponse"
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            ]
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -195,15 +258,27 @@
 | 
				
			|||||||
                ],
 | 
					                ],
 | 
				
			||||||
                "responses": {
 | 
					                "responses": {
 | 
				
			||||||
                    "200": {
 | 
					                    "200": {
 | 
				
			||||||
                        "description": "业务失败,具体错误码和信息见响应体(例如400, 500)",
 | 
					                        "description": "业务码为201代表创建成功",
 | 
				
			||||||
                        "schema": {
 | 
					                        "schema": {
 | 
				
			||||||
                            "$ref": "#/definitions/controller.Response"
 | 
					                            "allOf": [
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "$ref": "#/definitions/controller.Response"
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "type": "object",
 | 
				
			||||||
 | 
					                                    "properties": {
 | 
				
			||||||
 | 
					                                        "data": {
 | 
				
			||||||
 | 
					                                            "$ref": "#/definitions/plan.PlanResponse"
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            ]
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "/plans/{id}": {
 | 
					        "/api/v1/plans/{id}": {
 | 
				
			||||||
            "get": {
 | 
					            "get": {
 | 
				
			||||||
                "description": "根据计划ID获取单个计划的详细信息。",
 | 
					                "description": "根据计划ID获取单个计划的详细信息。",
 | 
				
			||||||
                "produces": [
 | 
					                "produces": [
 | 
				
			||||||
@@ -224,9 +299,21 @@
 | 
				
			|||||||
                ],
 | 
					                ],
 | 
				
			||||||
                "responses": {
 | 
					                "responses": {
 | 
				
			||||||
                    "200": {
 | 
					                    "200": {
 | 
				
			||||||
                        "description": "业务失败,具体错误码和信息见响应体(例如400, 404, 500)",
 | 
					                        "description": "业务码为200代表成功获取",
 | 
				
			||||||
                        "schema": {
 | 
					                        "schema": {
 | 
				
			||||||
                            "$ref": "#/definitions/controller.Response"
 | 
					                            "allOf": [
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "$ref": "#/definitions/controller.Response"
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "type": "object",
 | 
				
			||||||
 | 
					                                    "properties": {
 | 
				
			||||||
 | 
					                                        "data": {
 | 
				
			||||||
 | 
					                                            "$ref": "#/definitions/plan.PlanResponse"
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            ]
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -263,9 +350,21 @@
 | 
				
			|||||||
                ],
 | 
					                ],
 | 
				
			||||||
                "responses": {
 | 
					                "responses": {
 | 
				
			||||||
                    "200": {
 | 
					                    "200": {
 | 
				
			||||||
                        "description": "业务失败,具体错误码和信息见响应体(例如400, 404, 500)",
 | 
					                        "description": "业务码为200代表更新成功",
 | 
				
			||||||
                        "schema": {
 | 
					                        "schema": {
 | 
				
			||||||
                            "$ref": "#/definitions/controller.Response"
 | 
					                            "allOf": [
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "$ref": "#/definitions/controller.Response"
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "type": "object",
 | 
				
			||||||
 | 
					                                    "properties": {
 | 
				
			||||||
 | 
					                                        "data": {
 | 
				
			||||||
 | 
					                                            "$ref": "#/definitions/plan.PlanResponse"
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            ]
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -290,7 +389,7 @@
 | 
				
			|||||||
                ],
 | 
					                ],
 | 
				
			||||||
                "responses": {
 | 
					                "responses": {
 | 
				
			||||||
                    "200": {
 | 
					                    "200": {
 | 
				
			||||||
                        "description": "业务失败,具体错误码和信息见响应体(例如400, 404, 500)",
 | 
					                        "description": "业务码为200代表删除成功",
 | 
				
			||||||
                        "schema": {
 | 
					                        "schema": {
 | 
				
			||||||
                            "$ref": "#/definitions/controller.Response"
 | 
					                            "$ref": "#/definitions/controller.Response"
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
@@ -298,7 +397,7 @@
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "/plans/{id}/start": {
 | 
					        "/api/v1/plans/{id}/start": {
 | 
				
			||||||
            "post": {
 | 
					            "post": {
 | 
				
			||||||
                "description": "根据计划ID启动一个计划的执行。",
 | 
					                "description": "根据计划ID启动一个计划的执行。",
 | 
				
			||||||
                "produces": [
 | 
					                "produces": [
 | 
				
			||||||
@@ -319,7 +418,7 @@
 | 
				
			|||||||
                ],
 | 
					                ],
 | 
				
			||||||
                "responses": {
 | 
					                "responses": {
 | 
				
			||||||
                    "200": {
 | 
					                    "200": {
 | 
				
			||||||
                        "description": "业务失败,具体错误码和信息见响应体(例如400, 404, 500)",
 | 
					                        "description": "业务码为200代表成功启动计划",
 | 
				
			||||||
                        "schema": {
 | 
					                        "schema": {
 | 
				
			||||||
                            "$ref": "#/definitions/controller.Response"
 | 
					                            "$ref": "#/definitions/controller.Response"
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
@@ -327,7 +426,7 @@
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "/plans/{id}/stop": {
 | 
					        "/api/v1/plans/{id}/stop": {
 | 
				
			||||||
            "post": {
 | 
					            "post": {
 | 
				
			||||||
                "description": "根据计划ID停止一个正在执行的计划。",
 | 
					                "description": "根据计划ID停止一个正在执行的计划。",
 | 
				
			||||||
                "produces": [
 | 
					                "produces": [
 | 
				
			||||||
@@ -348,7 +447,7 @@
 | 
				
			|||||||
                ],
 | 
					                ],
 | 
				
			||||||
                "responses": {
 | 
					                "responses": {
 | 
				
			||||||
                    "200": {
 | 
					                    "200": {
 | 
				
			||||||
                        "description": "业务失败,具体错误码和信息见响应体(例如400, 404, 500)",
 | 
					                        "description": "业务码为200代表成功停止计划",
 | 
				
			||||||
                        "schema": {
 | 
					                        "schema": {
 | 
				
			||||||
                            "$ref": "#/definitions/controller.Response"
 | 
					                            "$ref": "#/definitions/controller.Response"
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
@@ -356,7 +455,7 @@
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "/users": {
 | 
					        "/api/v1/users": {
 | 
				
			||||||
            "post": {
 | 
					            "post": {
 | 
				
			||||||
                "description": "根据用户名和密码创建一个新的系统用户。",
 | 
					                "description": "根据用户名和密码创建一个新的系统用户。",
 | 
				
			||||||
                "consumes": [
 | 
					                "consumes": [
 | 
				
			||||||
@@ -382,15 +481,27 @@
 | 
				
			|||||||
                ],
 | 
					                ],
 | 
				
			||||||
                "responses": {
 | 
					                "responses": {
 | 
				
			||||||
                    "200": {
 | 
					                    "200": {
 | 
				
			||||||
                        "description": "业务失败,具体错误码和信息见响应体(例如400, 409, 500)",
 | 
					                        "description": "业务码为201代表创建成功",
 | 
				
			||||||
                        "schema": {
 | 
					                        "schema": {
 | 
				
			||||||
                            "$ref": "#/definitions/controller.Response"
 | 
					                            "allOf": [
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "$ref": "#/definitions/controller.Response"
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "type": "object",
 | 
				
			||||||
 | 
					                                    "properties": {
 | 
				
			||||||
 | 
					                                        "data": {
 | 
				
			||||||
 | 
					                                            "$ref": "#/definitions/user.CreateUserResponse"
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            ]
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "/users/login": {
 | 
					        "/api/v1/users/login": {
 | 
				
			||||||
            "post": {
 | 
					            "post": {
 | 
				
			||||||
                "description": "用户使用用户名和密码登录,成功后返回 JWT 令牌。",
 | 
					                "description": "用户使用用户名和密码登录,成功后返回 JWT 令牌。",
 | 
				
			||||||
                "consumes": [
 | 
					                "consumes": [
 | 
				
			||||||
@@ -416,9 +527,21 @@
 | 
				
			|||||||
                ],
 | 
					                ],
 | 
				
			||||||
                "responses": {
 | 
					                "responses": {
 | 
				
			||||||
                    "200": {
 | 
					                    "200": {
 | 
				
			||||||
                        "description": "业务失败,具体错误码和信息见响应体(例如400, 401, 500)",
 | 
					                        "description": "业务码为200代表登录成功",
 | 
				
			||||||
                        "schema": {
 | 
					                        "schema": {
 | 
				
			||||||
                            "$ref": "#/definitions/controller.Response"
 | 
					                            "allOf": [
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "$ref": "#/definitions/controller.Response"
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                                {
 | 
				
			||||||
 | 
					                                    "type": "object",
 | 
				
			||||||
 | 
					                                    "properties": {
 | 
				
			||||||
 | 
					                                        "data": {
 | 
				
			||||||
 | 
					                                            "$ref": "#/definitions/user.LoginResponse"
 | 
				
			||||||
 | 
					                                        }
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                }
 | 
				
			||||||
 | 
					                            ]
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
@@ -426,9 +549,6 @@
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "definitions": {
 | 
					    "definitions": {
 | 
				
			||||||
        "controller.Properties": {
 | 
					 | 
				
			||||||
            "type": "object"
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "controller.Response": {
 | 
					        "controller.Response": {
 | 
				
			||||||
            "type": "object",
 | 
					            "type": "object",
 | 
				
			||||||
            "properties": {
 | 
					            "properties": {
 | 
				
			||||||
@@ -446,17 +566,12 @@
 | 
				
			|||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        "device.CreateDeviceRequest": {
 | 
					        "device.CreateDeviceRequest": {
 | 
				
			||||||
            "type": "object"
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        "device.DeviceResponse": {
 | 
					 | 
				
			||||||
            "type": "object",
 | 
					            "type": "object",
 | 
				
			||||||
 | 
					            "required": [
 | 
				
			||||||
 | 
					                "name",
 | 
				
			||||||
 | 
					                "type"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
            "properties": {
 | 
					            "properties": {
 | 
				
			||||||
                "created_at": {
 | 
					 | 
				
			||||||
                    "type": "string"
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                "id": {
 | 
					 | 
				
			||||||
                    "type": "integer"
 | 
					 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                "location": {
 | 
					                "location": {
 | 
				
			||||||
                    "type": "string"
 | 
					                    "type": "string"
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
@@ -467,16 +582,14 @@
 | 
				
			|||||||
                    "type": "integer"
 | 
					                    "type": "integer"
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                "properties": {
 | 
					                "properties": {
 | 
				
			||||||
                    "$ref": "#/definitions/controller.Properties"
 | 
					                    "type": "object",
 | 
				
			||||||
 | 
					                    "additionalProperties": true
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                "sub_type": {
 | 
					                "sub_type": {
 | 
				
			||||||
                    "$ref": "#/definitions/models.DeviceSubType"
 | 
					                    "$ref": "#/definitions/models.DeviceSubType"
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                "type": {
 | 
					                "type": {
 | 
				
			||||||
                    "$ref": "#/definitions/models.DeviceType"
 | 
					                    "$ref": "#/definitions/models.DeviceType"
 | 
				
			||||||
                },
 | 
					 | 
				
			||||||
                "updated_at": {
 | 
					 | 
				
			||||||
                    "type": "string"
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
@@ -497,7 +610,8 @@
 | 
				
			|||||||
                    "type": "integer"
 | 
					                    "type": "integer"
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                "properties": {
 | 
					                "properties": {
 | 
				
			||||||
                    "$ref": "#/definitions/controller.Properties"
 | 
					                    "type": "object",
 | 
				
			||||||
 | 
					                    "additionalProperties": true
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                "sub_type": {
 | 
					                "sub_type": {
 | 
				
			||||||
                    "$ref": "#/definitions/models.DeviceSubType"
 | 
					                    "$ref": "#/definitions/models.DeviceSubType"
 | 
				
			||||||
@@ -507,6 +621,39 @@
 | 
				
			|||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        "git_huangwc_com_pig_pig-farm-controller_internal_app_controller_device.DeviceResponse": {
 | 
				
			||||||
 | 
					            "type": "object",
 | 
				
			||||||
 | 
					            "properties": {
 | 
				
			||||||
 | 
					                "created_at": {
 | 
				
			||||||
 | 
					                    "type": "string"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "id": {
 | 
				
			||||||
 | 
					                    "type": "integer"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "location": {
 | 
				
			||||||
 | 
					                    "type": "string"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "name": {
 | 
				
			||||||
 | 
					                    "type": "string"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "parent_id": {
 | 
				
			||||||
 | 
					                    "type": "integer"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "properties": {
 | 
				
			||||||
 | 
					                    "type": "object",
 | 
				
			||||||
 | 
					                    "additionalProperties": true
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "sub_type": {
 | 
				
			||||||
 | 
					                    "$ref": "#/definitions/models.DeviceSubType"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "type": {
 | 
				
			||||||
 | 
					                    "$ref": "#/definitions/models.DeviceType"
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "updated_at": {
 | 
				
			||||||
 | 
					                    "type": "string"
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "models.DeviceSubType": {
 | 
					        "models.DeviceSubType": {
 | 
				
			||||||
            "type": "string",
 | 
					            "type": "string",
 | 
				
			||||||
            "enum": [
 | 
					            "enum": [
 | 
				
			||||||
@@ -577,18 +724,46 @@
 | 
				
			|||||||
                "PlanExecutionTypeManual"
 | 
					                "PlanExecutionTypeManual"
 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
 | 
					        "models.PlanStatus": {
 | 
				
			||||||
 | 
					            "type": "integer",
 | 
				
			||||||
 | 
					            "format": "int32",
 | 
				
			||||||
 | 
					            "enum": [
 | 
				
			||||||
 | 
					                0,
 | 
				
			||||||
 | 
					                1,
 | 
				
			||||||
 | 
					                2
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "x-enum-comments": {
 | 
				
			||||||
 | 
					                "PlanStatusDisabled": "禁用计划",
 | 
				
			||||||
 | 
					                "PlanStatusEnabled": "启用计划",
 | 
				
			||||||
 | 
					                "PlanStatusStopeed": "执行完毕"
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            "x-enum-descriptions": [
 | 
				
			||||||
 | 
					                "启用计划",
 | 
				
			||||||
 | 
					                "禁用计划",
 | 
				
			||||||
 | 
					                "执行完毕"
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					            "x-enum-varnames": [
 | 
				
			||||||
 | 
					                "PlanStatusEnabled",
 | 
				
			||||||
 | 
					                "PlanStatusDisabled",
 | 
				
			||||||
 | 
					                "PlanStatusStopeed"
 | 
				
			||||||
 | 
					            ]
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
        "models.TaskType": {
 | 
					        "models.TaskType": {
 | 
				
			||||||
            "type": "string",
 | 
					            "type": "string",
 | 
				
			||||||
            "enum": [
 | 
					            "enum": [
 | 
				
			||||||
 | 
					                "plan_analysis",
 | 
				
			||||||
                "waiting"
 | 
					                "waiting"
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
            "x-enum-comments": {
 | 
					            "x-enum-comments": {
 | 
				
			||||||
 | 
					                "TaskPlanAnalysis": "解析Plan的Task列表并添加到待执行队列的特殊任务",
 | 
				
			||||||
                "TaskTypeWaiting": "等待任务"
 | 
					                "TaskTypeWaiting": "等待任务"
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            "x-enum-descriptions": [
 | 
					            "x-enum-descriptions": [
 | 
				
			||||||
 | 
					                "解析Plan的Task列表并添加到待执行队列的特殊任务",
 | 
				
			||||||
                "等待任务"
 | 
					                "等待任务"
 | 
				
			||||||
            ],
 | 
					            ],
 | 
				
			||||||
            "x-enum-varnames": [
 | 
					            "x-enum-varnames": [
 | 
				
			||||||
 | 
					                "TaskPlanAnalysis",
 | 
				
			||||||
                "TaskTypeWaiting"
 | 
					                "TaskTypeWaiting"
 | 
				
			||||||
            ]
 | 
					            ]
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
@@ -616,6 +791,10 @@
 | 
				
			|||||||
                    "type": "string",
 | 
					                    "type": "string",
 | 
				
			||||||
                    "example": "根据温度自动调节风扇和加热器"
 | 
					                    "example": "根据温度自动调节风扇和加热器"
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
 | 
					                "execute_num": {
 | 
				
			||||||
 | 
					                    "type": "integer",
 | 
				
			||||||
 | 
					                    "example": 10
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
                "execution_type": {
 | 
					                "execution_type": {
 | 
				
			||||||
                    "allOf": [
 | 
					                    "allOf": [
 | 
				
			||||||
                        {
 | 
					                        {
 | 
				
			||||||
@@ -676,6 +855,14 @@
 | 
				
			|||||||
                    "type": "string",
 | 
					                    "type": "string",
 | 
				
			||||||
                    "example": "根据温度自动调节风扇和加热器"
 | 
					                    "example": "根据温度自动调节风扇和加热器"
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
 | 
					                "execute_count": {
 | 
				
			||||||
 | 
					                    "type": "integer",
 | 
				
			||||||
 | 
					                    "example": 0
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                "execute_num": {
 | 
				
			||||||
 | 
					                    "type": "integer",
 | 
				
			||||||
 | 
					                    "example": 10
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
                "execution_type": {
 | 
					                "execution_type": {
 | 
				
			||||||
                    "allOf": [
 | 
					                    "allOf": [
 | 
				
			||||||
                        {
 | 
					                        {
 | 
				
			||||||
@@ -692,6 +879,14 @@
 | 
				
			|||||||
                    "type": "string",
 | 
					                    "type": "string",
 | 
				
			||||||
                    "example": "猪舍温度控制计划"
 | 
					                    "example": "猪舍温度控制计划"
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
 | 
					                "status": {
 | 
				
			||||||
 | 
					                    "allOf": [
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            "$ref": "#/definitions/models.PlanStatus"
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    ],
 | 
				
			||||||
 | 
					                    "example": 0
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
                "sub_plans": {
 | 
					                "sub_plans": {
 | 
				
			||||||
                    "type": "array",
 | 
					                    "type": "array",
 | 
				
			||||||
                    "items": {
 | 
					                    "items": {
 | 
				
			||||||
@@ -746,7 +941,8 @@
 | 
				
			|||||||
                    "example": "打开风扇"
 | 
					                    "example": "打开风扇"
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                "parameters": {
 | 
					                "parameters": {
 | 
				
			||||||
                    "$ref": "#/definitions/controller.Properties"
 | 
					                    "type": "object",
 | 
				
			||||||
 | 
					                    "additionalProperties": true
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                "type": {
 | 
					                "type": {
 | 
				
			||||||
                    "allOf": [
 | 
					                    "allOf": [
 | 
				
			||||||
@@ -778,7 +974,8 @@
 | 
				
			|||||||
                    "example": "打开风扇"
 | 
					                    "example": "打开风扇"
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                "parameters": {
 | 
					                "parameters": {
 | 
				
			||||||
                    "$ref": "#/definitions/controller.Properties"
 | 
					                    "type": "object",
 | 
				
			||||||
 | 
					                    "additionalProperties": true
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                "plan_id": {
 | 
					                "plan_id": {
 | 
				
			||||||
                    "type": "integer",
 | 
					                    "type": "integer",
 | 
				
			||||||
@@ -813,6 +1010,10 @@
 | 
				
			|||||||
                    "type": "string",
 | 
					                    "type": "string",
 | 
				
			||||||
                    "example": "更新后的描述"
 | 
					                    "example": "更新后的描述"
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
 | 
					                "execute_num": {
 | 
				
			||||||
 | 
					                    "type": "integer",
 | 
				
			||||||
 | 
					                    "example": 10
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
                "execution_type": {
 | 
					                "execution_type": {
 | 
				
			||||||
                    "allOf": [
 | 
					                    "allOf": [
 | 
				
			||||||
                        {
 | 
					                        {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,4 @@
 | 
				
			|||||||
definitions:
 | 
					definitions:
 | 
				
			||||||
  controller.Properties:
 | 
					 | 
				
			||||||
    type: object
 | 
					 | 
				
			||||||
  controller.Response:
 | 
					  controller.Response:
 | 
				
			||||||
    properties:
 | 
					    properties:
 | 
				
			||||||
      code:
 | 
					      code:
 | 
				
			||||||
@@ -13,8 +11,44 @@ definitions:
 | 
				
			|||||||
        type: string
 | 
					        type: string
 | 
				
			||||||
    type: object
 | 
					    type: object
 | 
				
			||||||
  device.CreateDeviceRequest:
 | 
					  device.CreateDeviceRequest:
 | 
				
			||||||
 | 
					    properties:
 | 
				
			||||||
 | 
					      location:
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
 | 
					      name:
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
 | 
					      parent_id:
 | 
				
			||||||
 | 
					        type: integer
 | 
				
			||||||
 | 
					      properties:
 | 
				
			||||||
 | 
					        additionalProperties: true
 | 
				
			||||||
 | 
					        type: object
 | 
				
			||||||
 | 
					      sub_type:
 | 
				
			||||||
 | 
					        $ref: '#/definitions/models.DeviceSubType'
 | 
				
			||||||
 | 
					      type:
 | 
				
			||||||
 | 
					        $ref: '#/definitions/models.DeviceType'
 | 
				
			||||||
 | 
					    required:
 | 
				
			||||||
 | 
					    - name
 | 
				
			||||||
 | 
					    - type
 | 
				
			||||||
    type: object
 | 
					    type: object
 | 
				
			||||||
  device.DeviceResponse:
 | 
					  device.UpdateDeviceRequest:
 | 
				
			||||||
 | 
					    properties:
 | 
				
			||||||
 | 
					      location:
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
 | 
					      name:
 | 
				
			||||||
 | 
					        type: string
 | 
				
			||||||
 | 
					      parent_id:
 | 
				
			||||||
 | 
					        type: integer
 | 
				
			||||||
 | 
					      properties:
 | 
				
			||||||
 | 
					        additionalProperties: true
 | 
				
			||||||
 | 
					        type: object
 | 
				
			||||||
 | 
					      sub_type:
 | 
				
			||||||
 | 
					        $ref: '#/definitions/models.DeviceSubType'
 | 
				
			||||||
 | 
					      type:
 | 
				
			||||||
 | 
					        $ref: '#/definitions/models.DeviceType'
 | 
				
			||||||
 | 
					    required:
 | 
				
			||||||
 | 
					    - name
 | 
				
			||||||
 | 
					    - type
 | 
				
			||||||
 | 
					    type: object
 | 
				
			||||||
 | 
					  git_huangwc_com_pig_pig-farm-controller_internal_app_controller_device.DeviceResponse:
 | 
				
			||||||
    properties:
 | 
					    properties:
 | 
				
			||||||
      created_at:
 | 
					      created_at:
 | 
				
			||||||
        type: string
 | 
					        type: string
 | 
				
			||||||
@@ -27,7 +61,8 @@ definitions:
 | 
				
			|||||||
      parent_id:
 | 
					      parent_id:
 | 
				
			||||||
        type: integer
 | 
					        type: integer
 | 
				
			||||||
      properties:
 | 
					      properties:
 | 
				
			||||||
        $ref: '#/definitions/controller.Properties'
 | 
					        additionalProperties: true
 | 
				
			||||||
 | 
					        type: object
 | 
				
			||||||
      sub_type:
 | 
					      sub_type:
 | 
				
			||||||
        $ref: '#/definitions/models.DeviceSubType'
 | 
					        $ref: '#/definitions/models.DeviceSubType'
 | 
				
			||||||
      type:
 | 
					      type:
 | 
				
			||||||
@@ -35,24 +70,6 @@ definitions:
 | 
				
			|||||||
      updated_at:
 | 
					      updated_at:
 | 
				
			||||||
        type: string
 | 
					        type: string
 | 
				
			||||||
    type: object
 | 
					    type: object
 | 
				
			||||||
  device.UpdateDeviceRequest:
 | 
					 | 
				
			||||||
    properties:
 | 
					 | 
				
			||||||
      location:
 | 
					 | 
				
			||||||
        type: string
 | 
					 | 
				
			||||||
      name:
 | 
					 | 
				
			||||||
        type: string
 | 
					 | 
				
			||||||
      parent_id:
 | 
					 | 
				
			||||||
        type: integer
 | 
					 | 
				
			||||||
      properties:
 | 
					 | 
				
			||||||
        $ref: '#/definitions/controller.Properties'
 | 
					 | 
				
			||||||
      sub_type:
 | 
					 | 
				
			||||||
        $ref: '#/definitions/models.DeviceSubType'
 | 
					 | 
				
			||||||
      type:
 | 
					 | 
				
			||||||
        $ref: '#/definitions/models.DeviceType'
 | 
					 | 
				
			||||||
    required:
 | 
					 | 
				
			||||||
    - name
 | 
					 | 
				
			||||||
    - type
 | 
					 | 
				
			||||||
    type: object
 | 
					 | 
				
			||||||
  models.DeviceSubType:
 | 
					  models.DeviceSubType:
 | 
				
			||||||
    enum:
 | 
					    enum:
 | 
				
			||||||
    - ""
 | 
					    - ""
 | 
				
			||||||
@@ -107,15 +124,38 @@ definitions:
 | 
				
			|||||||
    x-enum-varnames:
 | 
					    x-enum-varnames:
 | 
				
			||||||
    - PlanExecutionTypeAutomatic
 | 
					    - PlanExecutionTypeAutomatic
 | 
				
			||||||
    - PlanExecutionTypeManual
 | 
					    - PlanExecutionTypeManual
 | 
				
			||||||
 | 
					  models.PlanStatus:
 | 
				
			||||||
 | 
					    enum:
 | 
				
			||||||
 | 
					    - 0
 | 
				
			||||||
 | 
					    - 1
 | 
				
			||||||
 | 
					    - 2
 | 
				
			||||||
 | 
					    format: int32
 | 
				
			||||||
 | 
					    type: integer
 | 
				
			||||||
 | 
					    x-enum-comments:
 | 
				
			||||||
 | 
					      PlanStatusDisabled: 禁用计划
 | 
				
			||||||
 | 
					      PlanStatusEnabled: 启用计划
 | 
				
			||||||
 | 
					      PlanStatusStopeed: 执行完毕
 | 
				
			||||||
 | 
					    x-enum-descriptions:
 | 
				
			||||||
 | 
					    - 启用计划
 | 
				
			||||||
 | 
					    - 禁用计划
 | 
				
			||||||
 | 
					    - 执行完毕
 | 
				
			||||||
 | 
					    x-enum-varnames:
 | 
				
			||||||
 | 
					    - PlanStatusEnabled
 | 
				
			||||||
 | 
					    - PlanStatusDisabled
 | 
				
			||||||
 | 
					    - PlanStatusStopeed
 | 
				
			||||||
  models.TaskType:
 | 
					  models.TaskType:
 | 
				
			||||||
    enum:
 | 
					    enum:
 | 
				
			||||||
 | 
					    - plan_analysis
 | 
				
			||||||
    - waiting
 | 
					    - waiting
 | 
				
			||||||
    type: string
 | 
					    type: string
 | 
				
			||||||
    x-enum-comments:
 | 
					    x-enum-comments:
 | 
				
			||||||
 | 
					      TaskPlanAnalysis: 解析Plan的Task列表并添加到待执行队列的特殊任务
 | 
				
			||||||
      TaskTypeWaiting: 等待任务
 | 
					      TaskTypeWaiting: 等待任务
 | 
				
			||||||
    x-enum-descriptions:
 | 
					    x-enum-descriptions:
 | 
				
			||||||
 | 
					    - 解析Plan的Task列表并添加到待执行队列的特殊任务
 | 
				
			||||||
    - 等待任务
 | 
					    - 等待任务
 | 
				
			||||||
    x-enum-varnames:
 | 
					    x-enum-varnames:
 | 
				
			||||||
 | 
					    - TaskPlanAnalysis
 | 
				
			||||||
    - TaskTypeWaiting
 | 
					    - TaskTypeWaiting
 | 
				
			||||||
  plan.CreatePlanRequest:
 | 
					  plan.CreatePlanRequest:
 | 
				
			||||||
    properties:
 | 
					    properties:
 | 
				
			||||||
@@ -129,6 +169,9 @@ definitions:
 | 
				
			|||||||
      description:
 | 
					      description:
 | 
				
			||||||
        example: 根据温度自动调节风扇和加热器
 | 
					        example: 根据温度自动调节风扇和加热器
 | 
				
			||||||
        type: string
 | 
					        type: string
 | 
				
			||||||
 | 
					      execute_num:
 | 
				
			||||||
 | 
					        example: 10
 | 
				
			||||||
 | 
					        type: integer
 | 
				
			||||||
      execution_type:
 | 
					      execution_type:
 | 
				
			||||||
        allOf:
 | 
					        allOf:
 | 
				
			||||||
        - $ref: '#/definitions/models.PlanExecutionType'
 | 
					        - $ref: '#/definitions/models.PlanExecutionType'
 | 
				
			||||||
@@ -171,6 +214,12 @@ definitions:
 | 
				
			|||||||
      description:
 | 
					      description:
 | 
				
			||||||
        example: 根据温度自动调节风扇和加热器
 | 
					        example: 根据温度自动调节风扇和加热器
 | 
				
			||||||
        type: string
 | 
					        type: string
 | 
				
			||||||
 | 
					      execute_count:
 | 
				
			||||||
 | 
					        example: 0
 | 
				
			||||||
 | 
					        type: integer
 | 
				
			||||||
 | 
					      execute_num:
 | 
				
			||||||
 | 
					        example: 10
 | 
				
			||||||
 | 
					        type: integer
 | 
				
			||||||
      execution_type:
 | 
					      execution_type:
 | 
				
			||||||
        allOf:
 | 
					        allOf:
 | 
				
			||||||
        - $ref: '#/definitions/models.PlanExecutionType'
 | 
					        - $ref: '#/definitions/models.PlanExecutionType'
 | 
				
			||||||
@@ -181,6 +230,10 @@ definitions:
 | 
				
			|||||||
      name:
 | 
					      name:
 | 
				
			||||||
        example: 猪舍温度控制计划
 | 
					        example: 猪舍温度控制计划
 | 
				
			||||||
        type: string
 | 
					        type: string
 | 
				
			||||||
 | 
					      status:
 | 
				
			||||||
 | 
					        allOf:
 | 
				
			||||||
 | 
					        - $ref: '#/definitions/models.PlanStatus'
 | 
				
			||||||
 | 
					        example: 0
 | 
				
			||||||
      sub_plans:
 | 
					      sub_plans:
 | 
				
			||||||
        items:
 | 
					        items:
 | 
				
			||||||
          $ref: '#/definitions/plan.SubPlanResponse'
 | 
					          $ref: '#/definitions/plan.SubPlanResponse'
 | 
				
			||||||
@@ -219,7 +272,8 @@ definitions:
 | 
				
			|||||||
        example: 打开风扇
 | 
					        example: 打开风扇
 | 
				
			||||||
        type: string
 | 
					        type: string
 | 
				
			||||||
      parameters:
 | 
					      parameters:
 | 
				
			||||||
        $ref: '#/definitions/controller.Properties'
 | 
					        additionalProperties: true
 | 
				
			||||||
 | 
					        type: object
 | 
				
			||||||
      type:
 | 
					      type:
 | 
				
			||||||
        allOf:
 | 
					        allOf:
 | 
				
			||||||
        - $ref: '#/definitions/models.TaskType'
 | 
					        - $ref: '#/definitions/models.TaskType'
 | 
				
			||||||
@@ -240,7 +294,8 @@ definitions:
 | 
				
			|||||||
        example: 打开风扇
 | 
					        example: 打开风扇
 | 
				
			||||||
        type: string
 | 
					        type: string
 | 
				
			||||||
      parameters:
 | 
					      parameters:
 | 
				
			||||||
        $ref: '#/definitions/controller.Properties'
 | 
					        additionalProperties: true
 | 
				
			||||||
 | 
					        type: object
 | 
				
			||||||
      plan_id:
 | 
					      plan_id:
 | 
				
			||||||
        example: 1
 | 
					        example: 1
 | 
				
			||||||
        type: integer
 | 
					        type: integer
 | 
				
			||||||
@@ -261,6 +316,9 @@ definitions:
 | 
				
			|||||||
      description:
 | 
					      description:
 | 
				
			||||||
        example: 更新后的描述
 | 
					        example: 更新后的描述
 | 
				
			||||||
        type: string
 | 
					        type: string
 | 
				
			||||||
 | 
					      execute_num:
 | 
				
			||||||
 | 
					        example: 10
 | 
				
			||||||
 | 
					        type: integer
 | 
				
			||||||
      execution_type:
 | 
					      execution_type:
 | 
				
			||||||
        allOf:
 | 
					        allOf:
 | 
				
			||||||
        - $ref: '#/definitions/models.PlanExecutionType'
 | 
					        - $ref: '#/definitions/models.PlanExecutionType'
 | 
				
			||||||
@@ -326,16 +384,23 @@ definitions:
 | 
				
			|||||||
info:
 | 
					info:
 | 
				
			||||||
  contact: {}
 | 
					  contact: {}
 | 
				
			||||||
paths:
 | 
					paths:
 | 
				
			||||||
  /devices:
 | 
					  /api/v1/devices:
 | 
				
			||||||
    get:
 | 
					    get:
 | 
				
			||||||
      description: 获取系统中所有设备的列表
 | 
					      description: 获取系统中所有设备的列表
 | 
				
			||||||
      produces:
 | 
					      produces:
 | 
				
			||||||
      - application/json
 | 
					      - application/json
 | 
				
			||||||
      responses:
 | 
					      responses:
 | 
				
			||||||
        "200":
 | 
					        "200":
 | 
				
			||||||
          description: 业务失败,具体错误码和信息见响应体
 | 
					          description: OK
 | 
				
			||||||
          schema:
 | 
					          schema:
 | 
				
			||||||
            $ref: '#/definitions/controller.Response'
 | 
					            allOf:
 | 
				
			||||||
 | 
					            - $ref: '#/definitions/controller.Response'
 | 
				
			||||||
 | 
					            - properties:
 | 
				
			||||||
 | 
					                data:
 | 
				
			||||||
 | 
					                  items:
 | 
				
			||||||
 | 
					                    $ref: '#/definitions/git_huangwc_com_pig_pig-farm-controller_internal_app_controller_device.DeviceResponse'
 | 
				
			||||||
 | 
					                  type: array
 | 
				
			||||||
 | 
					              type: object
 | 
				
			||||||
      summary: 获取设备列表
 | 
					      summary: 获取设备列表
 | 
				
			||||||
      tags:
 | 
					      tags:
 | 
				
			||||||
      - 设备管理
 | 
					      - 设备管理
 | 
				
			||||||
@@ -354,13 +419,18 @@ paths:
 | 
				
			|||||||
      - application/json
 | 
					      - application/json
 | 
				
			||||||
      responses:
 | 
					      responses:
 | 
				
			||||||
        "200":
 | 
					        "200":
 | 
				
			||||||
          description: 业务失败,具体错误码和信息见响应体
 | 
					          description: OK
 | 
				
			||||||
          schema:
 | 
					          schema:
 | 
				
			||||||
            $ref: '#/definitions/controller.Response'
 | 
					            allOf:
 | 
				
			||||||
 | 
					            - $ref: '#/definitions/controller.Response'
 | 
				
			||||||
 | 
					            - properties:
 | 
				
			||||||
 | 
					                data:
 | 
				
			||||||
 | 
					                  $ref: '#/definitions/git_huangwc_com_pig_pig-farm-controller_internal_app_controller_device.DeviceResponse'
 | 
				
			||||||
 | 
					              type: object
 | 
				
			||||||
      summary: 创建新设备
 | 
					      summary: 创建新设备
 | 
				
			||||||
      tags:
 | 
					      tags:
 | 
				
			||||||
      - 设备管理
 | 
					      - 设备管理
 | 
				
			||||||
  /devices/{id}:
 | 
					  /api/v1/devices/{id}:
 | 
				
			||||||
    delete:
 | 
					    delete:
 | 
				
			||||||
      description: 根据设备ID删除一个设备(软删除)
 | 
					      description: 根据设备ID删除一个设备(软删除)
 | 
				
			||||||
      parameters:
 | 
					      parameters:
 | 
				
			||||||
@@ -373,7 +443,7 @@ paths:
 | 
				
			|||||||
      - application/json
 | 
					      - application/json
 | 
				
			||||||
      responses:
 | 
					      responses:
 | 
				
			||||||
        "200":
 | 
					        "200":
 | 
				
			||||||
          description: 业务失败,具体错误码和信息见响应体
 | 
					          description: OK
 | 
				
			||||||
          schema:
 | 
					          schema:
 | 
				
			||||||
            $ref: '#/definitions/controller.Response'
 | 
					            $ref: '#/definitions/controller.Response'
 | 
				
			||||||
      summary: 删除设备
 | 
					      summary: 删除设备
 | 
				
			||||||
@@ -391,9 +461,14 @@ paths:
 | 
				
			|||||||
      - application/json
 | 
					      - application/json
 | 
				
			||||||
      responses:
 | 
					      responses:
 | 
				
			||||||
        "200":
 | 
					        "200":
 | 
				
			||||||
          description: 业务失败,具体错误码和信息见响应体
 | 
					          description: OK
 | 
				
			||||||
          schema:
 | 
					          schema:
 | 
				
			||||||
            $ref: '#/definitions/controller.Response'
 | 
					            allOf:
 | 
				
			||||||
 | 
					            - $ref: '#/definitions/controller.Response'
 | 
				
			||||||
 | 
					            - properties:
 | 
				
			||||||
 | 
					                data:
 | 
				
			||||||
 | 
					                  $ref: '#/definitions/git_huangwc_com_pig_pig-farm-controller_internal_app_controller_device.DeviceResponse'
 | 
				
			||||||
 | 
					              type: object
 | 
				
			||||||
      summary: 获取设备信息
 | 
					      summary: 获取设备信息
 | 
				
			||||||
      tags:
 | 
					      tags:
 | 
				
			||||||
      - 设备管理
 | 
					      - 设备管理
 | 
				
			||||||
@@ -417,22 +492,32 @@ paths:
 | 
				
			|||||||
      - application/json
 | 
					      - application/json
 | 
				
			||||||
      responses:
 | 
					      responses:
 | 
				
			||||||
        "200":
 | 
					        "200":
 | 
				
			||||||
          description: 业务失败,具体错误码和信息见响应体
 | 
					          description: OK
 | 
				
			||||||
          schema:
 | 
					          schema:
 | 
				
			||||||
            $ref: '#/definitions/controller.Response'
 | 
					            allOf:
 | 
				
			||||||
 | 
					            - $ref: '#/definitions/controller.Response'
 | 
				
			||||||
 | 
					            - properties:
 | 
				
			||||||
 | 
					                data:
 | 
				
			||||||
 | 
					                  $ref: '#/definitions/git_huangwc_com_pig_pig-farm-controller_internal_app_controller_device.DeviceResponse'
 | 
				
			||||||
 | 
					              type: object
 | 
				
			||||||
      summary: 更新设备信息
 | 
					      summary: 更新设备信息
 | 
				
			||||||
      tags:
 | 
					      tags:
 | 
				
			||||||
      - 设备管理
 | 
					      - 设备管理
 | 
				
			||||||
  /plans:
 | 
					  /api/v1/plans:
 | 
				
			||||||
    get:
 | 
					    get:
 | 
				
			||||||
      description: 获取所有计划的列表
 | 
					      description: 获取所有计划的列表
 | 
				
			||||||
      produces:
 | 
					      produces:
 | 
				
			||||||
      - application/json
 | 
					      - application/json
 | 
				
			||||||
      responses:
 | 
					      responses:
 | 
				
			||||||
        "200":
 | 
					        "200":
 | 
				
			||||||
          description: 业务失败,具体错误码和信息见响应体(例如400, 500)
 | 
					          description: 业务码为200代表成功获取列表
 | 
				
			||||||
          schema:
 | 
					          schema:
 | 
				
			||||||
            $ref: '#/definitions/controller.Response'
 | 
					            allOf:
 | 
				
			||||||
 | 
					            - $ref: '#/definitions/controller.Response'
 | 
				
			||||||
 | 
					            - properties:
 | 
				
			||||||
 | 
					                data:
 | 
				
			||||||
 | 
					                  $ref: '#/definitions/plan.ListPlansResponse'
 | 
				
			||||||
 | 
					              type: object
 | 
				
			||||||
      summary: 获取计划列表
 | 
					      summary: 获取计划列表
 | 
				
			||||||
      tags:
 | 
					      tags:
 | 
				
			||||||
      - 计划管理
 | 
					      - 计划管理
 | 
				
			||||||
@@ -451,13 +536,18 @@ paths:
 | 
				
			|||||||
      - application/json
 | 
					      - application/json
 | 
				
			||||||
      responses:
 | 
					      responses:
 | 
				
			||||||
        "200":
 | 
					        "200":
 | 
				
			||||||
          description: 业务失败,具体错误码和信息见响应体(例如400, 500)
 | 
					          description: 业务码为201代表创建成功
 | 
				
			||||||
          schema:
 | 
					          schema:
 | 
				
			||||||
            $ref: '#/definitions/controller.Response'
 | 
					            allOf:
 | 
				
			||||||
 | 
					            - $ref: '#/definitions/controller.Response'
 | 
				
			||||||
 | 
					            - properties:
 | 
				
			||||||
 | 
					                data:
 | 
				
			||||||
 | 
					                  $ref: '#/definitions/plan.PlanResponse'
 | 
				
			||||||
 | 
					              type: object
 | 
				
			||||||
      summary: 创建计划
 | 
					      summary: 创建计划
 | 
				
			||||||
      tags:
 | 
					      tags:
 | 
				
			||||||
      - 计划管理
 | 
					      - 计划管理
 | 
				
			||||||
  /plans/{id}:
 | 
					  /api/v1/plans/{id}:
 | 
				
			||||||
    delete:
 | 
					    delete:
 | 
				
			||||||
      description: 根据计划ID删除计划。
 | 
					      description: 根据计划ID删除计划。
 | 
				
			||||||
      parameters:
 | 
					      parameters:
 | 
				
			||||||
@@ -470,7 +560,7 @@ paths:
 | 
				
			|||||||
      - application/json
 | 
					      - application/json
 | 
				
			||||||
      responses:
 | 
					      responses:
 | 
				
			||||||
        "200":
 | 
					        "200":
 | 
				
			||||||
          description: 业务失败,具体错误码和信息见响应体(例如400, 404, 500)
 | 
					          description: 业务码为200代表删除成功
 | 
				
			||||||
          schema:
 | 
					          schema:
 | 
				
			||||||
            $ref: '#/definitions/controller.Response'
 | 
					            $ref: '#/definitions/controller.Response'
 | 
				
			||||||
      summary: 删除计划
 | 
					      summary: 删除计划
 | 
				
			||||||
@@ -488,9 +578,14 @@ paths:
 | 
				
			|||||||
      - application/json
 | 
					      - application/json
 | 
				
			||||||
      responses:
 | 
					      responses:
 | 
				
			||||||
        "200":
 | 
					        "200":
 | 
				
			||||||
          description: 业务失败,具体错误码和信息见响应体(例如400, 404, 500)
 | 
					          description: 业务码为200代表成功获取
 | 
				
			||||||
          schema:
 | 
					          schema:
 | 
				
			||||||
            $ref: '#/definitions/controller.Response'
 | 
					            allOf:
 | 
				
			||||||
 | 
					            - $ref: '#/definitions/controller.Response'
 | 
				
			||||||
 | 
					            - properties:
 | 
				
			||||||
 | 
					                data:
 | 
				
			||||||
 | 
					                  $ref: '#/definitions/plan.PlanResponse'
 | 
				
			||||||
 | 
					              type: object
 | 
				
			||||||
      summary: 获取计划详情
 | 
					      summary: 获取计划详情
 | 
				
			||||||
      tags:
 | 
					      tags:
 | 
				
			||||||
      - 计划管理
 | 
					      - 计划管理
 | 
				
			||||||
@@ -514,13 +609,18 @@ paths:
 | 
				
			|||||||
      - application/json
 | 
					      - application/json
 | 
				
			||||||
      responses:
 | 
					      responses:
 | 
				
			||||||
        "200":
 | 
					        "200":
 | 
				
			||||||
          description: 业务失败,具体错误码和信息见响应体(例如400, 404, 500)
 | 
					          description: 业务码为200代表更新成功
 | 
				
			||||||
          schema:
 | 
					          schema:
 | 
				
			||||||
            $ref: '#/definitions/controller.Response'
 | 
					            allOf:
 | 
				
			||||||
 | 
					            - $ref: '#/definitions/controller.Response'
 | 
				
			||||||
 | 
					            - properties:
 | 
				
			||||||
 | 
					                data:
 | 
				
			||||||
 | 
					                  $ref: '#/definitions/plan.PlanResponse'
 | 
				
			||||||
 | 
					              type: object
 | 
				
			||||||
      summary: 更新计划
 | 
					      summary: 更新计划
 | 
				
			||||||
      tags:
 | 
					      tags:
 | 
				
			||||||
      - 计划管理
 | 
					      - 计划管理
 | 
				
			||||||
  /plans/{id}/start:
 | 
					  /api/v1/plans/{id}/start:
 | 
				
			||||||
    post:
 | 
					    post:
 | 
				
			||||||
      description: 根据计划ID启动一个计划的执行。
 | 
					      description: 根据计划ID启动一个计划的执行。
 | 
				
			||||||
      parameters:
 | 
					      parameters:
 | 
				
			||||||
@@ -533,13 +633,13 @@ paths:
 | 
				
			|||||||
      - application/json
 | 
					      - application/json
 | 
				
			||||||
      responses:
 | 
					      responses:
 | 
				
			||||||
        "200":
 | 
					        "200":
 | 
				
			||||||
          description: 业务失败,具体错误码和信息见响应体(例如400, 404, 500)
 | 
					          description: 业务码为200代表成功启动计划
 | 
				
			||||||
          schema:
 | 
					          schema:
 | 
				
			||||||
            $ref: '#/definitions/controller.Response'
 | 
					            $ref: '#/definitions/controller.Response'
 | 
				
			||||||
      summary: 启动计划
 | 
					      summary: 启动计划
 | 
				
			||||||
      tags:
 | 
					      tags:
 | 
				
			||||||
      - 计划管理
 | 
					      - 计划管理
 | 
				
			||||||
  /plans/{id}/stop:
 | 
					  /api/v1/plans/{id}/stop:
 | 
				
			||||||
    post:
 | 
					    post:
 | 
				
			||||||
      description: 根据计划ID停止一个正在执行的计划。
 | 
					      description: 根据计划ID停止一个正在执行的计划。
 | 
				
			||||||
      parameters:
 | 
					      parameters:
 | 
				
			||||||
@@ -552,13 +652,13 @@ paths:
 | 
				
			|||||||
      - application/json
 | 
					      - application/json
 | 
				
			||||||
      responses:
 | 
					      responses:
 | 
				
			||||||
        "200":
 | 
					        "200":
 | 
				
			||||||
          description: 业务失败,具体错误码和信息见响应体(例如400, 404, 500)
 | 
					          description: 业务码为200代表成功停止计划
 | 
				
			||||||
          schema:
 | 
					          schema:
 | 
				
			||||||
            $ref: '#/definitions/controller.Response'
 | 
					            $ref: '#/definitions/controller.Response'
 | 
				
			||||||
      summary: 停止计划
 | 
					      summary: 停止计划
 | 
				
			||||||
      tags:
 | 
					      tags:
 | 
				
			||||||
      - 计划管理
 | 
					      - 计划管理
 | 
				
			||||||
  /users:
 | 
					  /api/v1/users:
 | 
				
			||||||
    post:
 | 
					    post:
 | 
				
			||||||
      consumes:
 | 
					      consumes:
 | 
				
			||||||
      - application/json
 | 
					      - application/json
 | 
				
			||||||
@@ -574,13 +674,18 @@ paths:
 | 
				
			|||||||
      - application/json
 | 
					      - application/json
 | 
				
			||||||
      responses:
 | 
					      responses:
 | 
				
			||||||
        "200":
 | 
					        "200":
 | 
				
			||||||
          description: 业务失败,具体错误码和信息见响应体(例如400, 409, 500)
 | 
					          description: 业务码为201代表创建成功
 | 
				
			||||||
          schema:
 | 
					          schema:
 | 
				
			||||||
            $ref: '#/definitions/controller.Response'
 | 
					            allOf:
 | 
				
			||||||
 | 
					            - $ref: '#/definitions/controller.Response'
 | 
				
			||||||
 | 
					            - properties:
 | 
				
			||||||
 | 
					                data:
 | 
				
			||||||
 | 
					                  $ref: '#/definitions/user.CreateUserResponse'
 | 
				
			||||||
 | 
					              type: object
 | 
				
			||||||
      summary: 创建新用户
 | 
					      summary: 创建新用户
 | 
				
			||||||
      tags:
 | 
					      tags:
 | 
				
			||||||
      - 用户管理
 | 
					      - 用户管理
 | 
				
			||||||
  /users/login:
 | 
					  /api/v1/users/login:
 | 
				
			||||||
    post:
 | 
					    post:
 | 
				
			||||||
      consumes:
 | 
					      consumes:
 | 
				
			||||||
      - application/json
 | 
					      - application/json
 | 
				
			||||||
@@ -596,9 +701,14 @@ paths:
 | 
				
			|||||||
      - application/json
 | 
					      - application/json
 | 
				
			||||||
      responses:
 | 
					      responses:
 | 
				
			||||||
        "200":
 | 
					        "200":
 | 
				
			||||||
          description: 业务失败,具体错误码和信息见响应体(例如400, 401, 500)
 | 
					          description: 业务码为200代表登录成功
 | 
				
			||||||
          schema:
 | 
					          schema:
 | 
				
			||||||
            $ref: '#/definitions/controller.Response'
 | 
					            allOf:
 | 
				
			||||||
 | 
					            - $ref: '#/definitions/controller.Response'
 | 
				
			||||||
 | 
					            - properties:
 | 
				
			||||||
 | 
					                data:
 | 
				
			||||||
 | 
					                  $ref: '#/definitions/user.LoginResponse'
 | 
				
			||||||
 | 
					              type: object
 | 
				
			||||||
      summary: 用户登录
 | 
					      summary: 用户登录
 | 
				
			||||||
      tags:
 | 
					      tags:
 | 
				
			||||||
      - 用户管理
 | 
					      - 用户管理
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										65
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										65
									
								
								go.mod
									
									
									
									
									
								
							@@ -7,7 +7,7 @@ require (
 | 
				
			|||||||
	github.com/go-openapi/errors v0.22.2
 | 
						github.com/go-openapi/errors v0.22.2
 | 
				
			||||||
	github.com/go-openapi/runtime v0.28.0
 | 
						github.com/go-openapi/runtime v0.28.0
 | 
				
			||||||
	github.com/go-openapi/strfmt v0.23.0
 | 
						github.com/go-openapi/strfmt v0.23.0
 | 
				
			||||||
	github.com/go-openapi/swag v0.23.0
 | 
						github.com/go-openapi/swag v0.24.1
 | 
				
			||||||
	github.com/go-openapi/validate v0.24.0
 | 
						github.com/go-openapi/validate v0.24.0
 | 
				
			||||||
	github.com/golang-jwt/jwt/v5 v5.3.0
 | 
						github.com/golang-jwt/jwt/v5 v5.3.0
 | 
				
			||||||
	github.com/panjf2000/ants/v2 v2.11.3
 | 
						github.com/panjf2000/ants/v2 v2.11.3
 | 
				
			||||||
@@ -17,8 +17,8 @@ require (
 | 
				
			|||||||
	github.com/swaggo/gin-swagger v1.6.1
 | 
						github.com/swaggo/gin-swagger v1.6.1
 | 
				
			||||||
	github.com/swaggo/swag v1.16.6
 | 
						github.com/swaggo/swag v1.16.6
 | 
				
			||||||
	go.uber.org/zap v1.27.0
 | 
						go.uber.org/zap v1.27.0
 | 
				
			||||||
	golang.org/x/crypto v0.36.0
 | 
						golang.org/x/crypto v0.42.0
 | 
				
			||||||
	google.golang.org/protobuf v1.34.1
 | 
						google.golang.org/protobuf v1.36.9
 | 
				
			||||||
	gopkg.in/natefinch/lumberjack.v2 v2.2.1
 | 
						gopkg.in/natefinch/lumberjack.v2 v2.2.1
 | 
				
			||||||
	gopkg.in/yaml.v2 v2.4.0
 | 
						gopkg.in/yaml.v2 v2.4.0
 | 
				
			||||||
	gorm.io/datatypes v1.2.6
 | 
						gorm.io/datatypes v1.2.6
 | 
				
			||||||
@@ -31,25 +31,38 @@ require (
 | 
				
			|||||||
	filippo.io/edwards25519 v1.1.0 // indirect
 | 
						filippo.io/edwards25519 v1.1.0 // indirect
 | 
				
			||||||
	github.com/KyleBanks/depth v1.2.1 // indirect
 | 
						github.com/KyleBanks/depth v1.2.1 // indirect
 | 
				
			||||||
	github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
 | 
						github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
 | 
				
			||||||
	github.com/bytedance/sonic v1.11.6 // indirect
 | 
						github.com/bytedance/gopkg v0.1.3 // indirect
 | 
				
			||||||
	github.com/bytedance/sonic/loader v0.1.1 // indirect
 | 
						github.com/bytedance/sonic v1.14.1 // indirect
 | 
				
			||||||
	github.com/cloudwego/base64x v0.1.4 // indirect
 | 
						github.com/bytedance/sonic/loader v0.3.0 // indirect
 | 
				
			||||||
 | 
						github.com/cloudwego/base64x v0.1.6 // indirect
 | 
				
			||||||
	github.com/cloudwego/iasm v0.2.0 // indirect
 | 
						github.com/cloudwego/iasm v0.2.0 // indirect
 | 
				
			||||||
 | 
						github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
 | 
				
			||||||
	github.com/davecgh/go-spew v1.1.1 // indirect
 | 
						github.com/davecgh/go-spew v1.1.1 // indirect
 | 
				
			||||||
	github.com/gabriel-vasile/mimetype v1.4.3 // indirect
 | 
						github.com/gabriel-vasile/mimetype v1.4.10 // indirect
 | 
				
			||||||
	github.com/gin-contrib/sse v0.1.0 // indirect
 | 
						github.com/gin-contrib/sse v1.1.0 // indirect
 | 
				
			||||||
	github.com/go-logr/logr v1.4.1 // indirect
 | 
						github.com/go-logr/logr v1.4.1 // indirect
 | 
				
			||||||
	github.com/go-logr/stdr v1.2.2 // indirect
 | 
						github.com/go-logr/stdr v1.2.2 // indirect
 | 
				
			||||||
	github.com/go-openapi/analysis v0.23.0 // indirect
 | 
						github.com/go-openapi/analysis v0.23.0 // indirect
 | 
				
			||||||
	github.com/go-openapi/jsonpointer v0.21.0 // indirect
 | 
						github.com/go-openapi/jsonpointer v0.22.0 // indirect
 | 
				
			||||||
	github.com/go-openapi/jsonreference v0.21.0 // indirect
 | 
						github.com/go-openapi/jsonreference v0.21.1 // indirect
 | 
				
			||||||
	github.com/go-openapi/loads v0.22.0 // indirect
 | 
						github.com/go-openapi/loads v0.22.0 // indirect
 | 
				
			||||||
	github.com/go-openapi/spec v0.21.0 // indirect
 | 
						github.com/go-openapi/spec v0.21.0 // indirect
 | 
				
			||||||
 | 
						github.com/go-openapi/swag/cmdutils v0.24.0 // indirect
 | 
				
			||||||
 | 
						github.com/go-openapi/swag/conv v0.24.0 // indirect
 | 
				
			||||||
 | 
						github.com/go-openapi/swag/fileutils v0.24.0 // indirect
 | 
				
			||||||
 | 
						github.com/go-openapi/swag/jsonname v0.24.0 // indirect
 | 
				
			||||||
 | 
						github.com/go-openapi/swag/jsonutils v0.24.0 // indirect
 | 
				
			||||||
 | 
						github.com/go-openapi/swag/loading v0.24.0 // indirect
 | 
				
			||||||
 | 
						github.com/go-openapi/swag/mangling v0.24.0 // indirect
 | 
				
			||||||
 | 
						github.com/go-openapi/swag/netutils v0.24.0 // indirect
 | 
				
			||||||
 | 
						github.com/go-openapi/swag/stringutils v0.24.0 // indirect
 | 
				
			||||||
 | 
						github.com/go-openapi/swag/typeutils v0.24.0 // indirect
 | 
				
			||||||
 | 
						github.com/go-openapi/swag/yamlutils v0.24.0 // indirect
 | 
				
			||||||
	github.com/go-playground/locales v0.14.1 // indirect
 | 
						github.com/go-playground/locales v0.14.1 // indirect
 | 
				
			||||||
	github.com/go-playground/universal-translator v0.18.1 // indirect
 | 
						github.com/go-playground/universal-translator v0.18.1 // indirect
 | 
				
			||||||
	github.com/go-playground/validator/v10 v10.20.0 // indirect
 | 
						github.com/go-playground/validator/v10 v10.27.0 // indirect
 | 
				
			||||||
	github.com/go-sql-driver/mysql v1.8.1 // indirect
 | 
						github.com/go-sql-driver/mysql v1.8.1 // indirect
 | 
				
			||||||
	github.com/goccy/go-json v0.10.2 // indirect
 | 
						github.com/goccy/go-json v0.10.5 // indirect
 | 
				
			||||||
	github.com/google/uuid v1.6.0 // indirect
 | 
						github.com/google/uuid v1.6.0 // indirect
 | 
				
			||||||
	github.com/jackc/pgpassfile v1.0.0 // indirect
 | 
						github.com/jackc/pgpassfile v1.0.0 // indirect
 | 
				
			||||||
	github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
 | 
						github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
 | 
				
			||||||
@@ -59,9 +72,9 @@ require (
 | 
				
			|||||||
	github.com/jinzhu/now v1.1.5 // indirect
 | 
						github.com/jinzhu/now v1.1.5 // indirect
 | 
				
			||||||
	github.com/josharian/intern v1.0.0 // indirect
 | 
						github.com/josharian/intern v1.0.0 // indirect
 | 
				
			||||||
	github.com/json-iterator/go v1.1.12 // indirect
 | 
						github.com/json-iterator/go v1.1.12 // indirect
 | 
				
			||||||
	github.com/klauspost/cpuid/v2 v2.2.7 // indirect
 | 
						github.com/klauspost/cpuid/v2 v2.3.0 // indirect
 | 
				
			||||||
	github.com/leodido/go-urn v1.4.0 // indirect
 | 
						github.com/leodido/go-urn v1.4.0 // indirect
 | 
				
			||||||
	github.com/mailru/easyjson v0.7.7 // indirect
 | 
						github.com/mailru/easyjson v0.9.1 // indirect
 | 
				
			||||||
	github.com/mattn/go-isatty v0.0.20 // indirect
 | 
						github.com/mattn/go-isatty v0.0.20 // indirect
 | 
				
			||||||
	github.com/mattn/go-sqlite3 v1.14.22 // indirect
 | 
						github.com/mattn/go-sqlite3 v1.14.22 // indirect
 | 
				
			||||||
	github.com/mitchellh/mapstructure v1.5.0 // indirect
 | 
						github.com/mitchellh/mapstructure v1.5.0 // indirect
 | 
				
			||||||
@@ -69,24 +82,30 @@ require (
 | 
				
			|||||||
	github.com/modern-go/reflect2 v1.0.2 // indirect
 | 
						github.com/modern-go/reflect2 v1.0.2 // indirect
 | 
				
			||||||
	github.com/oklog/ulid v1.3.1 // indirect
 | 
						github.com/oklog/ulid v1.3.1 // indirect
 | 
				
			||||||
	github.com/opentracing/opentracing-go v1.2.0 // indirect
 | 
						github.com/opentracing/opentracing-go v1.2.0 // indirect
 | 
				
			||||||
	github.com/pelletier/go-toml/v2 v2.2.2 // indirect
 | 
						github.com/pelletier/go-toml/v2 v2.2.4 // indirect
 | 
				
			||||||
	github.com/pmezard/go-difflib v1.0.0 // indirect
 | 
						github.com/pmezard/go-difflib v1.0.0 // indirect
 | 
				
			||||||
	github.com/rogpeppe/go-internal v1.14.1 // indirect
 | 
						github.com/rogpeppe/go-internal v1.14.1 // indirect
 | 
				
			||||||
 | 
						github.com/russross/blackfriday/v2 v2.1.0 // indirect
 | 
				
			||||||
 | 
						github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
 | 
				
			||||||
	github.com/stretchr/objx v0.5.2 // indirect
 | 
						github.com/stretchr/objx v0.5.2 // indirect
 | 
				
			||||||
	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
 | 
						github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
 | 
				
			||||||
	github.com/ugorji/go/codec v1.2.12 // indirect
 | 
						github.com/ugorji/go/codec v1.3.0 // indirect
 | 
				
			||||||
 | 
						github.com/urfave/cli/v2 v2.27.7 // indirect
 | 
				
			||||||
 | 
						github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 // indirect
 | 
				
			||||||
	go.mongodb.org/mongo-driver v1.14.0 // indirect
 | 
						go.mongodb.org/mongo-driver v1.14.0 // indirect
 | 
				
			||||||
	go.opentelemetry.io/otel v1.24.0 // indirect
 | 
						go.opentelemetry.io/otel v1.24.0 // indirect
 | 
				
			||||||
	go.opentelemetry.io/otel/metric v1.24.0 // indirect
 | 
						go.opentelemetry.io/otel/metric v1.24.0 // indirect
 | 
				
			||||||
	go.opentelemetry.io/otel/trace v1.24.0 // indirect
 | 
						go.opentelemetry.io/otel/trace v1.24.0 // indirect
 | 
				
			||||||
	go.uber.org/multierr v1.10.0 // indirect
 | 
						go.uber.org/multierr v1.10.0 // indirect
 | 
				
			||||||
	golang.org/x/arch v0.8.0 // indirect
 | 
						go.yaml.in/yaml/v2 v2.4.3 // indirect
 | 
				
			||||||
	golang.org/x/mod v0.21.0 // indirect
 | 
						golang.org/x/arch v0.21.0 // indirect
 | 
				
			||||||
	golang.org/x/net v0.38.0 // indirect
 | 
						golang.org/x/mod v0.28.0 // indirect
 | 
				
			||||||
	golang.org/x/sync v0.12.0 // indirect
 | 
						golang.org/x/net v0.44.0 // indirect
 | 
				
			||||||
	golang.org/x/sys v0.31.0 // indirect
 | 
						golang.org/x/sync v0.17.0 // indirect
 | 
				
			||||||
	golang.org/x/text v0.23.0 // indirect
 | 
						golang.org/x/sys v0.36.0 // indirect
 | 
				
			||||||
	golang.org/x/tools v0.26.0 // indirect
 | 
						golang.org/x/text v0.29.0 // indirect
 | 
				
			||||||
 | 
						golang.org/x/tools v0.37.0 // indirect
 | 
				
			||||||
	gopkg.in/yaml.v3 v3.0.1 // indirect
 | 
						gopkg.in/yaml.v3 v3.0.1 // indirect
 | 
				
			||||||
	gorm.io/driver/mysql v1.5.6 // indirect
 | 
						gorm.io/driver/mysql v1.5.6 // indirect
 | 
				
			||||||
 | 
						sigs.k8s.io/yaml v1.6.0 // indirect
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										85
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										85
									
								
								go.sum
									
									
									
									
									
								
							@@ -4,23 +4,37 @@ github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc
 | 
				
			|||||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
 | 
					github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
 | 
				
			||||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
 | 
					github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so=
 | 
				
			||||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
 | 
					github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
 | 
				
			||||||
 | 
					github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
 | 
				
			||||||
 | 
					github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
 | 
				
			||||||
github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
 | 
					github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0=
 | 
				
			||||||
github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
 | 
					github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4=
 | 
				
			||||||
 | 
					github.com/bytedance/sonic v1.14.1 h1:FBMC0zVz5XUmE4z9wF4Jey0An5FueFvOsTKKKtwIl7w=
 | 
				
			||||||
 | 
					github.com/bytedance/sonic v1.14.1/go.mod h1:gi6uhQLMbTdeP0muCnrjHLeCUPyb70ujhnNlhOylAFc=
 | 
				
			||||||
github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
 | 
					github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM=
 | 
				
			||||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
 | 
					github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
 | 
				
			||||||
 | 
					github.com/bytedance/sonic/loader v0.3.0 h1:dskwH8edlzNMctoruo8FPTJDF3vLtDT0sXZwvZJyqeA=
 | 
				
			||||||
 | 
					github.com/bytedance/sonic/loader v0.3.0/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
 | 
				
			||||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
 | 
					github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
 | 
				
			||||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
 | 
					github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
 | 
				
			||||||
 | 
					github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
 | 
				
			||||||
 | 
					github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
 | 
				
			||||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
 | 
					github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
 | 
				
			||||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
 | 
					github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
 | 
				
			||||||
 | 
					github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
 | 
				
			||||||
 | 
					github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
 | 
				
			||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
					github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
				
			||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
					github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
				
			||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
					github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
				
			||||||
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
 | 
					github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
 | 
				
			||||||
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
 | 
					github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
 | 
				
			||||||
 | 
					github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
 | 
				
			||||||
 | 
					github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
 | 
				
			||||||
github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
 | 
					github.com/gin-contrib/gzip v0.0.6 h1:NjcunTcGAj5CO1gn4N8jHOSIeRFHIbn51z6K+xaN4d4=
 | 
				
			||||||
github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
 | 
					github.com/gin-contrib/gzip v0.0.6/go.mod h1:QOJlmV2xmayAjkNS2Y8NQsMneuRShOU/kjovCXNuzzk=
 | 
				
			||||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
 | 
					github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
 | 
				
			||||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
 | 
					github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
 | 
				
			||||||
 | 
					github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w=
 | 
				
			||||||
 | 
					github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
 | 
				
			||||||
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
 | 
					github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
 | 
				
			||||||
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
 | 
					github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
 | 
				
			||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
 | 
					github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
 | 
				
			||||||
@@ -34,8 +48,12 @@ github.com/go-openapi/errors v0.22.2 h1:rdxhzcBUazEcGccKqbY1Y7NS8FDcMyIRr0934jrY
 | 
				
			|||||||
github.com/go-openapi/errors v0.22.2/go.mod h1:+n/5UdIqdVnLIJ6Q9Se8HNGUXYaY6CN8ImWzfi/Gzp0=
 | 
					github.com/go-openapi/errors v0.22.2/go.mod h1:+n/5UdIqdVnLIJ6Q9Se8HNGUXYaY6CN8ImWzfi/Gzp0=
 | 
				
			||||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
 | 
					github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
 | 
				
			||||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
 | 
					github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
 | 
				
			||||||
 | 
					github.com/go-openapi/jsonpointer v0.22.0 h1:TmMhghgNef9YXxTu1tOopo+0BGEytxA+okbry0HjZsM=
 | 
				
			||||||
 | 
					github.com/go-openapi/jsonpointer v0.22.0/go.mod h1:xt3jV88UtExdIkkL7NloURjRQjbeUgcxFblMjq2iaiU=
 | 
				
			||||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
 | 
					github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
 | 
				
			||||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
 | 
					github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
 | 
				
			||||||
 | 
					github.com/go-openapi/jsonreference v0.21.1 h1:bSKrcl8819zKiOgxkbVNRUBIr6Wwj9KYrDbMjRs0cDA=
 | 
				
			||||||
 | 
					github.com/go-openapi/jsonreference v0.21.1/go.mod h1:PWs8rO4xxTUqKGu+lEvvCxD5k2X7QYkKAepJyCmSTT8=
 | 
				
			||||||
github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco=
 | 
					github.com/go-openapi/loads v0.22.0 h1:ECPGd4jX1U6NApCGG1We+uEozOAvXvJSF4nnwHZ8Aco=
 | 
				
			||||||
github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs=
 | 
					github.com/go-openapi/loads v0.22.0/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs=
 | 
				
			||||||
github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ=
 | 
					github.com/go-openapi/runtime v0.28.0 h1:gpPPmWSNGo214l6n8hzdXYhPuJcGtziTOgUpvsFWGIQ=
 | 
				
			||||||
@@ -46,6 +64,30 @@ github.com/go-openapi/strfmt v0.23.0 h1:nlUS6BCqcnAk0pyhi9Y+kdDVZdZMHfEKQiS4HaMg
 | 
				
			|||||||
github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4=
 | 
					github.com/go-openapi/strfmt v0.23.0/go.mod h1:NrtIpfKtWIygRkKVsxh7XQMDQW5HKQl6S5ik2elW+K4=
 | 
				
			||||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
 | 
					github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
 | 
				
			||||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
 | 
					github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
 | 
				
			||||||
 | 
					github.com/go-openapi/swag v0.24.1 h1:DPdYTZKo6AQCRqzwr/kGkxJzHhpKxZ9i/oX0zag+MF8=
 | 
				
			||||||
 | 
					github.com/go-openapi/swag v0.24.1/go.mod h1:sm8I3lCPlspsBBwUm1t5oZeWZS0s7m/A+Psg0ooRU0A=
 | 
				
			||||||
 | 
					github.com/go-openapi/swag/cmdutils v0.24.0 h1:KlRCffHwXFI6E5MV9n8o8zBRElpY4uK4yWyAMWETo9I=
 | 
				
			||||||
 | 
					github.com/go-openapi/swag/cmdutils v0.24.0/go.mod h1:uxib2FAeQMByyHomTlsP8h1TtPd54Msu2ZDU/H5Vuf8=
 | 
				
			||||||
 | 
					github.com/go-openapi/swag/conv v0.24.0 h1:ejB9+7yogkWly6pnruRX45D1/6J+ZxRu92YFivx54ik=
 | 
				
			||||||
 | 
					github.com/go-openapi/swag/conv v0.24.0/go.mod h1:jbn140mZd7EW2g8a8Y5bwm8/Wy1slLySQQ0ND6DPc2c=
 | 
				
			||||||
 | 
					github.com/go-openapi/swag/fileutils v0.24.0 h1:U9pCpqp4RUytnD689Ek/N1d2N/a//XCeqoH508H5oak=
 | 
				
			||||||
 | 
					github.com/go-openapi/swag/fileutils v0.24.0/go.mod h1:3SCrCSBHyP1/N+3oErQ1gP+OX1GV2QYFSnrTbzwli90=
 | 
				
			||||||
 | 
					github.com/go-openapi/swag/jsonname v0.24.0 h1:2wKS9bgRV/xB8c62Qg16w4AUiIrqqiniJFtZGi3dg5k=
 | 
				
			||||||
 | 
					github.com/go-openapi/swag/jsonname v0.24.0/go.mod h1:GXqrPzGJe611P7LG4QB9JKPtUZ7flE4DOVechNaDd7Q=
 | 
				
			||||||
 | 
					github.com/go-openapi/swag/jsonutils v0.24.0 h1:F1vE1q4pg1xtO3HTyJYRmEuJ4jmIp2iZ30bzW5XgZts=
 | 
				
			||||||
 | 
					github.com/go-openapi/swag/jsonutils v0.24.0/go.mod h1:vBowZtF5Z4DDApIoxcIVfR8v0l9oq5PpYRUuteVu6f0=
 | 
				
			||||||
 | 
					github.com/go-openapi/swag/loading v0.24.0 h1:ln/fWTwJp2Zkj5DdaX4JPiddFC5CHQpvaBKycOlceYc=
 | 
				
			||||||
 | 
					github.com/go-openapi/swag/loading v0.24.0/go.mod h1:gShCN4woKZYIxPxbfbyHgjXAhO61m88tmjy0lp/LkJk=
 | 
				
			||||||
 | 
					github.com/go-openapi/swag/mangling v0.24.0 h1:PGOQpViCOUroIeak/Uj/sjGAq9LADS3mOyjznmHy2pk=
 | 
				
			||||||
 | 
					github.com/go-openapi/swag/mangling v0.24.0/go.mod h1:Jm5Go9LHkycsz0wfoaBDkdc4CkpuSnIEf62brzyCbhc=
 | 
				
			||||||
 | 
					github.com/go-openapi/swag/netutils v0.24.0 h1:Bz02HRjYv8046Ycg/w80q3g9QCWeIqTvlyOjQPDjD8w=
 | 
				
			||||||
 | 
					github.com/go-openapi/swag/netutils v0.24.0/go.mod h1:WRgiHcYTnx+IqfMCtu0hy9oOaPR0HnPbmArSRN1SkZM=
 | 
				
			||||||
 | 
					github.com/go-openapi/swag/stringutils v0.24.0 h1:i4Z/Jawf9EvXOLUbT97O0HbPUja18VdBxeadyAqS1FM=
 | 
				
			||||||
 | 
					github.com/go-openapi/swag/stringutils v0.24.0/go.mod h1:5nUXB4xA0kw2df5PRipZDslPJgJut+NjL7D25zPZ/4w=
 | 
				
			||||||
 | 
					github.com/go-openapi/swag/typeutils v0.24.0 h1:d3szEGzGDf4L2y1gYOSSLeK6h46F+zibnEas2Jm/wIw=
 | 
				
			||||||
 | 
					github.com/go-openapi/swag/typeutils v0.24.0/go.mod h1:q8C3Kmk/vh2VhpCLaoR2MVWOGP8y7Jc8l82qCTd1DYI=
 | 
				
			||||||
 | 
					github.com/go-openapi/swag/yamlutils v0.24.0 h1:bhw4894A7Iw6ne+639hsBNRHg9iZg/ISrOVr+sJGp4c=
 | 
				
			||||||
 | 
					github.com/go-openapi/swag/yamlutils v0.24.0/go.mod h1:DpKv5aYuaGm/sULePoeiG8uwMpZSfReo1HR3Ik0yaG8=
 | 
				
			||||||
github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58=
 | 
					github.com/go-openapi/validate v0.24.0 h1:LdfDKwNbpB6Vn40xhTdNZAnfLECL81w+VX3BumrGD58=
 | 
				
			||||||
github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
 | 
					github.com/go-openapi/validate v0.24.0/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
 | 
				
			||||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
 | 
					github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
 | 
				
			||||||
@@ -56,11 +98,15 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
 | 
				
			|||||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
 | 
					github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
 | 
				
			||||||
github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
 | 
					github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8=
 | 
				
			||||||
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
 | 
					github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
 | 
				
			||||||
 | 
					github.com/go-playground/validator/v10 v10.27.0 h1:w8+XrWVMhGkxOaaowyKH35gFydVHOvC0/uWoy2Fzwn4=
 | 
				
			||||||
 | 
					github.com/go-playground/validator/v10 v10.27.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
 | 
				
			||||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
 | 
					github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
 | 
				
			||||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
 | 
					github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
 | 
				
			||||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
 | 
					github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
 | 
				
			||||||
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
 | 
					github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
 | 
				
			||||||
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 | 
					github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 | 
				
			||||||
 | 
					github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
 | 
				
			||||||
 | 
					github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
 | 
				
			||||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
 | 
					github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
 | 
				
			||||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
 | 
					github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
 | 
				
			||||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
 | 
					github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
 | 
				
			||||||
@@ -69,6 +115,7 @@ github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei
 | 
				
			|||||||
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
 | 
					github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
 | 
				
			||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
 | 
					github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
 | 
				
			||||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 | 
					github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 | 
				
			||||||
 | 
					github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
 | 
				
			||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 | 
					github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 | 
				
			||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
 | 
					github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
 | 
				
			||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
					github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
				
			||||||
@@ -91,6 +138,8 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
 | 
				
			|||||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
 | 
					github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
 | 
				
			||||||
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
 | 
					github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
 | 
				
			||||||
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
 | 
					github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
 | 
				
			||||||
 | 
					github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
 | 
				
			||||||
 | 
					github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
 | 
				
			||||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
 | 
					github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
 | 
				
			||||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 | 
					github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 | 
				
			||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
 | 
					github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
 | 
				
			||||||
@@ -100,6 +149,8 @@ github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
 | 
				
			|||||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
 | 
					github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
 | 
				
			||||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
 | 
					github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
 | 
				
			||||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
 | 
					github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
 | 
				
			||||||
 | 
					github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8=
 | 
				
			||||||
 | 
					github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
 | 
				
			||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
 | 
					github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
 | 
				
			||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
 | 
					github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
 | 
				
			||||||
github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
 | 
					github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU=
 | 
				
			||||||
@@ -121,12 +172,18 @@ github.com/panjf2000/ants/v2 v2.11.3 h1:AfI0ngBoXJmYOpDh9m516vjqoUu2sLrIVgppI9TZ
 | 
				
			|||||||
github.com/panjf2000/ants/v2 v2.11.3/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek=
 | 
					github.com/panjf2000/ants/v2 v2.11.3/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek=
 | 
				
			||||||
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
 | 
					github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
 | 
				
			||||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
 | 
					github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
 | 
				
			||||||
 | 
					github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
 | 
				
			||||||
 | 
					github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
 | 
				
			||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
					github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
				
			||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
					github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
				
			||||||
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
 | 
					github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
 | 
				
			||||||
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
 | 
					github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
 | 
				
			||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
 | 
					github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
 | 
				
			||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
 | 
					github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
 | 
				
			||||||
 | 
					github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
 | 
				
			||||||
 | 
					github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
 | 
				
			||||||
 | 
					github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
 | 
				
			||||||
 | 
					github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
 | 
				
			||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
					github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 | 
				
			||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 | 
					github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 | 
				
			||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 | 
					github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 | 
				
			||||||
@@ -151,6 +208,12 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
 | 
				
			|||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
 | 
					github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
 | 
				
			||||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
 | 
					github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
 | 
				
			||||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
 | 
					github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
 | 
				
			||||||
 | 
					github.com/ugorji/go/codec v1.3.0 h1:Qd2W2sQawAfG8XSvzwhBeoGq71zXOC/Q1E9y/wUcsUA=
 | 
				
			||||||
 | 
					github.com/ugorji/go/codec v1.3.0/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
 | 
				
			||||||
 | 
					github.com/urfave/cli/v2 v2.27.7 h1:bH59vdhbjLv3LAvIu6gd0usJHgoTTPhCFib8qqOwXYU=
 | 
				
			||||||
 | 
					github.com/urfave/cli/v2 v2.27.7/go.mod h1:CyNAG/xg+iAOg0N4MPGZqVmv2rCoP267496AOXUZjA4=
 | 
				
			||||||
 | 
					github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342 h1:FnBeRrxr7OU4VvAzt5X7s6266i6cSVkkFPS0TuXWbIg=
 | 
				
			||||||
 | 
					github.com/xrash/smetrics v0.0.0-20250705151800-55b8f293f342/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
 | 
				
			||||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 | 
					github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 | 
				
			||||||
go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
 | 
					go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
 | 
				
			||||||
go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
 | 
					go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
 | 
				
			||||||
@@ -168,26 +231,38 @@ go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
 | 
				
			|||||||
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
 | 
					go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
 | 
				
			||||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
 | 
					go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
 | 
				
			||||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
 | 
					go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
 | 
				
			||||||
 | 
					go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
 | 
				
			||||||
 | 
					go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
 | 
				
			||||||
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
 | 
					golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
 | 
				
			||||||
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
 | 
					golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
 | 
				
			||||||
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
 | 
					golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
 | 
				
			||||||
 | 
					golang.org/x/arch v0.21.0 h1:iTC9o7+wP6cPWpDWkivCvQFGAHDQ59SrSxsLPcnkArw=
 | 
				
			||||||
 | 
					golang.org/x/arch v0.21.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
 | 
				
			||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 | 
					golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 | 
				
			||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 | 
					golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 | 
				
			||||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
 | 
					golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
 | 
				
			||||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
 | 
					golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
 | 
				
			||||||
 | 
					golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
 | 
				
			||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 | 
					golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 | 
				
			||||||
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
 | 
					golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
 | 
				
			||||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
 | 
					golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
 | 
				
			||||||
 | 
					golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U=
 | 
				
			||||||
 | 
					golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI=
 | 
				
			||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
					golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
				
			||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
					golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
				
			||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 | 
					golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 | 
				
			||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 | 
					golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 | 
				
			||||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
 | 
					golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
 | 
				
			||||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
 | 
					golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
 | 
				
			||||||
 | 
					golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
 | 
				
			||||||
 | 
					golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
 | 
				
			||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
					golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
				
			||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
					golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
				
			||||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
 | 
					golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
 | 
				
			||||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
 | 
					golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
 | 
				
			||||||
 | 
					golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
 | 
				
			||||||
 | 
					golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
					golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
					golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
@@ -197,6 +272,8 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			|||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
					golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
				
			||||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
 | 
					golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
 | 
				
			||||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 | 
					golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
 | 
				
			||||||
 | 
					golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
 | 
				
			||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
					golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
				
			||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 | 
					golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 | 
				
			||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 | 
					golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 | 
				
			||||||
@@ -206,14 +283,20 @@ golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 | 
				
			|||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 | 
					golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 | 
				
			||||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
 | 
					golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
 | 
				
			||||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
 | 
					golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
 | 
				
			||||||
 | 
					golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
 | 
				
			||||||
 | 
					golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
 | 
				
			||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
					golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
				
			||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
					golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
				
			||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 | 
					golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 | 
				
			||||||
golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
 | 
					golang.org/x/tools v0.26.0 h1:v/60pFQmzmT9ExmjDv2gGIfi3OqfKoEP6I5+umXlbnQ=
 | 
				
			||||||
golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
 | 
					golang.org/x/tools v0.26.0/go.mod h1:TPVVj70c7JJ3WCazhD8OdXcZg/og+b9+tH/KxylGwH0=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
 | 
				
			||||||
 | 
					golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
 | 
				
			||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
					golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
				
			||||||
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
 | 
					google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
 | 
				
			||||||
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
 | 
					google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
 | 
				
			||||||
 | 
					google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
 | 
				
			||||||
 | 
					google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
 | 
				
			||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
					gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
				
			||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 | 
					gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 | 
				
			||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 | 
					gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 | 
				
			||||||
@@ -239,3 +322,5 @@ gorm.io/gorm v1.30.5 h1:dvEfYwxL+i+xgCNSGGBT1lDjCzfELK8fHZxL3Ee9X0s=
 | 
				
			|||||||
gorm.io/gorm v1.30.5/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
 | 
					gorm.io/gorm v1.30.5/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
 | 
				
			||||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
 | 
					nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
 | 
				
			||||||
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
 | 
					rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=
 | 
				
			||||||
 | 
					sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs=
 | 
				
			||||||
 | 
					sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4=
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,8 +8,6 @@ package api
 | 
				
			|||||||
// @contact.email divano@example.com
 | 
					// @contact.email divano@example.com
 | 
				
			||||||
// @license.name Apache 2.0
 | 
					// @license.name Apache 2.0
 | 
				
			||||||
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
 | 
					// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
 | 
				
			||||||
// @host localhost:8086
 | 
					 | 
				
			||||||
// @BasePath /api/v1
 | 
					 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,9 @@
 | 
				
			|||||||
package device
 | 
					package device
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
@@ -11,7 +13,6 @@ import (
 | 
				
			|||||||
	"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
 | 
						"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
 | 
				
			||||||
	"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
 | 
						"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
 | 
				
			||||||
	"github.com/gin-gonic/gin"
 | 
						"github.com/gin-gonic/gin"
 | 
				
			||||||
	"gorm.io/datatypes"
 | 
					 | 
				
			||||||
	"gorm.io/gorm"
 | 
						"gorm.io/gorm"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -33,46 +34,54 @@ func NewController(repo repository.DeviceRepository, logger *logs.Logger) *Contr
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// CreateDeviceRequest 定义了创建设备时需要传入的参数
 | 
					// CreateDeviceRequest 定义了创建设备时需要传入的参数
 | 
				
			||||||
type CreateDeviceRequest struct {
 | 
					type CreateDeviceRequest struct {
 | 
				
			||||||
	Name       string                `json:"name" binding:"required"`
 | 
						Name       string                 `json:"name" binding:"required"`
 | 
				
			||||||
	Type       models.DeviceType     `json:"type" binding:"required"`
 | 
						Type       models.DeviceType      `json:"type" binding:"required"`
 | 
				
			||||||
	SubType    models.DeviceSubType  `json:"sub_type,omitempty"`
 | 
						SubType    models.DeviceSubType   `json:"sub_type,omitempty"`
 | 
				
			||||||
	ParentID   *uint                 `json:"parent_id,omitempty"`
 | 
						ParentID   *uint                  `json:"parent_id,omitempty"`
 | 
				
			||||||
	Location   string                `json:"location,omitempty"`
 | 
						Location   string                 `json:"location,omitempty"`
 | 
				
			||||||
	Properties controller.Properties `json:"properties,omitempty"`
 | 
						Properties map[string]interface{} `json:"properties,omitempty"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// UpdateDeviceRequest 定义了更新设备时需要传入的参数
 | 
					// UpdateDeviceRequest 定义了更新设备时需要传入的参数
 | 
				
			||||||
type UpdateDeviceRequest struct {
 | 
					type UpdateDeviceRequest struct {
 | 
				
			||||||
	Name       string                `json:"name" binding:"required"`
 | 
						Name       string                 `json:"name" binding:"required"`
 | 
				
			||||||
	Type       models.DeviceType     `json:"type" binding:"required"`
 | 
						Type       models.DeviceType      `json:"type" binding:"required"`
 | 
				
			||||||
	SubType    models.DeviceSubType  `json:"sub_type,omitempty"`
 | 
						SubType    models.DeviceSubType   `json:"sub_type,omitempty"`
 | 
				
			||||||
	ParentID   *uint                 `json:"parent_id,omitempty"`
 | 
						ParentID   *uint                  `json:"parent_id,omitempty"`
 | 
				
			||||||
	Location   string                `json:"location,omitempty"`
 | 
						Location   string                 `json:"location,omitempty"`
 | 
				
			||||||
	Properties controller.Properties `json:"properties,omitempty"`
 | 
						Properties map[string]interface{} `json:"properties,omitempty"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// --- Response DTOs ---
 | 
					// --- Response DTOs ---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DeviceResponse 定义了返回给客户端的单个设备信息的结构
 | 
					// DeviceResponse 定义了返回给客户端的单个设备信息的结构
 | 
				
			||||||
type DeviceResponse struct {
 | 
					type DeviceResponse struct {
 | 
				
			||||||
	ID         uint                  `json:"id"`
 | 
						ID         uint                   `json:"id"`
 | 
				
			||||||
	Name       string                `json:"name"`
 | 
						Name       string                 `json:"name"`
 | 
				
			||||||
	Type       models.DeviceType     `json:"type"`
 | 
						Type       models.DeviceType      `json:"type"`
 | 
				
			||||||
	SubType    models.DeviceSubType  `json:"sub_type"`
 | 
						SubType    models.DeviceSubType   `json:"sub_type"`
 | 
				
			||||||
	ParentID   *uint                 `json:"parent_id"`
 | 
						ParentID   *uint                  `json:"parent_id"`
 | 
				
			||||||
	Location   string                `json:"location"`
 | 
						Location   string                 `json:"location"`
 | 
				
			||||||
	Properties controller.Properties `json:"properties"`
 | 
						Properties map[string]interface{} `json:"properties"`
 | 
				
			||||||
	CreatedAt  string                `json:"created_at"`
 | 
						CreatedAt  string                 `json:"created_at"`
 | 
				
			||||||
	UpdatedAt  string                `json:"updated_at"`
 | 
						UpdatedAt  string                 `json:"updated_at"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// --- DTO 转换函数 ---
 | 
					// --- DTO 转换函数 ---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// newDeviceResponse 从数据库模型创建一个新的设备响应 DTO
 | 
					// newDeviceResponse 从数据库模型创建一个新的设备响应 DTO
 | 
				
			||||||
func newDeviceResponse(device *models.Device) *DeviceResponse {
 | 
					func newDeviceResponse(device *models.Device) (*DeviceResponse, error) {
 | 
				
			||||||
	if device == nil {
 | 
						if device == nil {
 | 
				
			||||||
		return nil
 | 
							return nil, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var props map[string]interface{}
 | 
				
			||||||
 | 
						if len(device.Properties) > 0 && string(device.Properties) != "null" {
 | 
				
			||||||
 | 
							if err := json.Unmarshal(device.Properties, &props); err != nil {
 | 
				
			||||||
 | 
								return nil, fmt.Errorf("解析设备属性失败 (ID: %d): %w", device.ID, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return &DeviceResponse{
 | 
						return &DeviceResponse{
 | 
				
			||||||
		ID:         device.ID,
 | 
							ID:         device.ID,
 | 
				
			||||||
		Name:       device.Name,
 | 
							Name:       device.Name,
 | 
				
			||||||
@@ -80,19 +89,23 @@ func newDeviceResponse(device *models.Device) *DeviceResponse {
 | 
				
			|||||||
		SubType:    device.SubType,
 | 
							SubType:    device.SubType,
 | 
				
			||||||
		ParentID:   device.ParentID,
 | 
							ParentID:   device.ParentID,
 | 
				
			||||||
		Location:   device.Location,
 | 
							Location:   device.Location,
 | 
				
			||||||
		Properties: controller.Properties(device.Properties),
 | 
							Properties: props,
 | 
				
			||||||
		CreatedAt:  device.CreatedAt.Format(time.RFC3339),
 | 
							CreatedAt:  device.CreatedAt.Format(time.RFC3339),
 | 
				
			||||||
		UpdatedAt:  device.UpdatedAt.Format(time.RFC3339),
 | 
							UpdatedAt:  device.UpdatedAt.Format(time.RFC3339),
 | 
				
			||||||
	}
 | 
						}, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// newListDeviceResponse 从数据库模型切片创建一个新的设备列表响应 DTO 切片
 | 
					// newListDeviceResponse 从数据库模型切片创建一个新的设备列表响应 DTO 切片
 | 
				
			||||||
func newListDeviceResponse(devices []*models.Device) []*DeviceResponse {
 | 
					func newListDeviceResponse(devices []*models.Device) ([]*DeviceResponse, error) {
 | 
				
			||||||
	list := make([]*DeviceResponse, 0, len(devices))
 | 
						list := make([]*DeviceResponse, 0, len(devices))
 | 
				
			||||||
	for _, device := range devices {
 | 
						for _, device := range devices {
 | 
				
			||||||
		list = append(list, newDeviceResponse(device))
 | 
							resp, err := newDeviceResponse(device)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							list = append(list, resp)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return list
 | 
						return list, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// --- Controller Methods ---
 | 
					// --- Controller Methods ---
 | 
				
			||||||
@@ -104,9 +117,8 @@ func newListDeviceResponse(devices []*models.Device) []*DeviceResponse {
 | 
				
			|||||||
// @Accept       json
 | 
					// @Accept       json
 | 
				
			||||||
// @Produce      json
 | 
					// @Produce      json
 | 
				
			||||||
// @Param        device body CreateDeviceRequest true "设备信息"
 | 
					// @Param        device body CreateDeviceRequest true "设备信息"
 | 
				
			||||||
// @Success      200 {object} controller.Response{data=DeviceResponse} "业务码为201代表创建成功"
 | 
					// @Success      200 {object} controller.Response{data=DeviceResponse}
 | 
				
			||||||
// @Failure      200 {object} controller.Response "业务失败,具体错误码和信息见响应体"
 | 
					// @Router       /api/v1/devices [post]
 | 
				
			||||||
// @Router       /devices [post]
 | 
					 | 
				
			||||||
func (c *Controller) CreateDevice(ctx *gin.Context) {
 | 
					func (c *Controller) CreateDevice(ctx *gin.Context) {
 | 
				
			||||||
	var req CreateDeviceRequest
 | 
						var req CreateDeviceRequest
 | 
				
			||||||
	if err := ctx.ShouldBindJSON(&req); err != nil {
 | 
						if err := ctx.ShouldBindJSON(&req); err != nil {
 | 
				
			||||||
@@ -115,13 +127,20 @@ func (c *Controller) CreateDevice(ctx *gin.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						propertiesJSON, err := json.Marshal(req.Properties)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							c.logger.Errorf("创建设备: 序列化属性失败: %v", err)
 | 
				
			||||||
 | 
							controller.SendErrorResponse(ctx, controller.CodeBadRequest, "属性字段格式错误")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	device := &models.Device{
 | 
						device := &models.Device{
 | 
				
			||||||
		Name:       req.Name,
 | 
							Name:       req.Name,
 | 
				
			||||||
		Type:       req.Type,
 | 
							Type:       req.Type,
 | 
				
			||||||
		SubType:    req.SubType,
 | 
							SubType:    req.SubType,
 | 
				
			||||||
		ParentID:   req.ParentID,
 | 
							ParentID:   req.ParentID,
 | 
				
			||||||
		Location:   req.Location,
 | 
							Location:   req.Location,
 | 
				
			||||||
		Properties: datatypes.JSON(req.Properties),
 | 
							Properties: propertiesJSON,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := c.repo.Create(device); err != nil {
 | 
						if err := c.repo.Create(device); err != nil {
 | 
				
			||||||
@@ -130,7 +149,14 @@ func (c *Controller) CreateDevice(ctx *gin.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	controller.SendResponse(ctx, controller.CodeCreated, "设备创建成功", newDeviceResponse(device))
 | 
						resp, err := newDeviceResponse(device)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							c.logger.Errorf("创建设备: 序列化响应失败: %v", err)
 | 
				
			||||||
 | 
							controller.SendErrorResponse(ctx, controller.CodeInternalError, "设备创建成功,但响应生成失败")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						controller.SendResponse(ctx, controller.CodeCreated, "设备创建成功", resp)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetDevice godoc
 | 
					// GetDevice godoc
 | 
				
			||||||
@@ -139,9 +165,8 @@ func (c *Controller) CreateDevice(ctx *gin.Context) {
 | 
				
			|||||||
// @Tags         设备管理
 | 
					// @Tags         设备管理
 | 
				
			||||||
// @Produce      json
 | 
					// @Produce      json
 | 
				
			||||||
// @Param        id path string true "设备ID"
 | 
					// @Param        id path string true "设备ID"
 | 
				
			||||||
// @Success      200 {object} controller.Response{data=DeviceResponse} "业务码为200代表获取成功"
 | 
					// @Success      200 {object} controller.Response{data=DeviceResponse}
 | 
				
			||||||
// @Failure      200 {object} controller.Response "业务失败,具体错误码和信息见响应体"
 | 
					// @Router       /api/v1/devices/{id} [get]
 | 
				
			||||||
// @Router       /devices/{id} [get]
 | 
					 | 
				
			||||||
func (c *Controller) GetDevice(ctx *gin.Context) {
 | 
					func (c *Controller) GetDevice(ctx *gin.Context) {
 | 
				
			||||||
	deviceID := ctx.Param("id")
 | 
						deviceID := ctx.Param("id")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -160,7 +185,14 @@ func (c *Controller) GetDevice(ctx *gin.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	controller.SendResponse(ctx, controller.CodeSuccess, "获取设备信息成功", newDeviceResponse(device))
 | 
						resp, err := newDeviceResponse(device)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							c.logger.Errorf("获取设备: 序列化响应失败: %v", err)
 | 
				
			||||||
 | 
							controller.SendErrorResponse(ctx, controller.CodeInternalError, "获取设备信息失败: 内部数据格式错误")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						controller.SendResponse(ctx, controller.CodeSuccess, "获取设备信息成功", resp)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ListDevices godoc
 | 
					// ListDevices godoc
 | 
				
			||||||
@@ -168,9 +200,8 @@ func (c *Controller) GetDevice(ctx *gin.Context) {
 | 
				
			|||||||
// @Description  获取系统中所有设备的列表
 | 
					// @Description  获取系统中所有设备的列表
 | 
				
			||||||
// @Tags         设备管理
 | 
					// @Tags         设备管理
 | 
				
			||||||
// @Produce      json
 | 
					// @Produce      json
 | 
				
			||||||
// @Success      200 {object} controller.Response{data=[]DeviceResponse} "业务码为200代表获取成功"
 | 
					// @Success      200 {object} controller.Response{data=[]DeviceResponse}
 | 
				
			||||||
// @Failure      200 {object} controller.Response "业务失败,具体错误码和信息见响应体"
 | 
					// @Router       /api/v1/devices [get]
 | 
				
			||||||
// @Router       /devices [get]
 | 
					 | 
				
			||||||
func (c *Controller) ListDevices(ctx *gin.Context) {
 | 
					func (c *Controller) ListDevices(ctx *gin.Context) {
 | 
				
			||||||
	devices, err := c.repo.ListAll()
 | 
						devices, err := c.repo.ListAll()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
@@ -179,7 +210,14 @@ func (c *Controller) ListDevices(ctx *gin.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	controller.SendResponse(ctx, controller.CodeSuccess, "获取设备列表成功", newListDeviceResponse(devices))
 | 
						resp, err := newListDeviceResponse(devices)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							c.logger.Errorf("获取设备列表: 序列化响应失败: %v", err)
 | 
				
			||||||
 | 
							controller.SendErrorResponse(ctx, controller.CodeInternalError, "获取设备列表失败: 内部数据格式错误")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						controller.SendResponse(ctx, controller.CodeSuccess, "获取设备列表成功", resp)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// UpdateDevice godoc
 | 
					// UpdateDevice godoc
 | 
				
			||||||
@@ -190,9 +228,8 @@ func (c *Controller) ListDevices(ctx *gin.Context) {
 | 
				
			|||||||
// @Produce      json
 | 
					// @Produce      json
 | 
				
			||||||
// @Param        id path string true "设备ID"
 | 
					// @Param        id path string true "设备ID"
 | 
				
			||||||
// @Param        device body UpdateDeviceRequest true "要更新的设备信息"
 | 
					// @Param        device body UpdateDeviceRequest true "要更新的设备信息"
 | 
				
			||||||
// @Success      200 {object} controller.Response{data=DeviceResponse} "业务码为200代表更新成功"
 | 
					// @Success      200 {object} controller.Response{data=DeviceResponse}
 | 
				
			||||||
// @Failure      200 {object} controller.Response "业务失败,具体错误码和信息见响应体"
 | 
					// @Router       /api/v1/devices/{id} [put]
 | 
				
			||||||
// @Router       /devices/{id} [put]
 | 
					 | 
				
			||||||
func (c *Controller) UpdateDevice(ctx *gin.Context) {
 | 
					func (c *Controller) UpdateDevice(ctx *gin.Context) {
 | 
				
			||||||
	deviceID := ctx.Param("id")
 | 
						deviceID := ctx.Param("id")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -220,13 +257,20 @@ func (c *Controller) UpdateDevice(ctx *gin.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						propertiesJSON, err := json.Marshal(req.Properties)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							c.logger.Errorf("更新设备: 序列化属性失败: %v", err)
 | 
				
			||||||
 | 
							controller.SendErrorResponse(ctx, controller.CodeBadRequest, "属性字段格式错误")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 3. 更新从数据库中查出的现有设备对象的字段
 | 
						// 3. 更新从数据库中查出的现有设备对象的字段
 | 
				
			||||||
	existingDevice.Name = req.Name
 | 
						existingDevice.Name = req.Name
 | 
				
			||||||
	existingDevice.Type = req.Type
 | 
						existingDevice.Type = req.Type
 | 
				
			||||||
	existingDevice.SubType = req.SubType
 | 
						existingDevice.SubType = req.SubType
 | 
				
			||||||
	existingDevice.ParentID = req.ParentID
 | 
						existingDevice.ParentID = req.ParentID
 | 
				
			||||||
	existingDevice.Location = req.Location
 | 
						existingDevice.Location = req.Location
 | 
				
			||||||
	existingDevice.Properties = datatypes.JSON(req.Properties)
 | 
						existingDevice.Properties = propertiesJSON
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 4. 将修改后的 existingDevice 对象保存回数据库
 | 
						// 4. 将修改后的 existingDevice 对象保存回数据库
 | 
				
			||||||
	if err := c.repo.Update(existingDevice); err != nil {
 | 
						if err := c.repo.Update(existingDevice); err != nil {
 | 
				
			||||||
@@ -235,7 +279,14 @@ func (c *Controller) UpdateDevice(ctx *gin.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	controller.SendResponse(ctx, controller.CodeSuccess, "设备更新成功", newDeviceResponse(existingDevice))
 | 
						resp, err := newDeviceResponse(existingDevice)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							c.logger.Errorf("更新设备: 序列化响应失败: %v", err)
 | 
				
			||||||
 | 
							controller.SendErrorResponse(ctx, controller.CodeInternalError, "设备更新成功,但响应生成失败")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						controller.SendResponse(ctx, controller.CodeSuccess, "设备更新成功", resp)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// DeleteDevice godoc
 | 
					// DeleteDevice godoc
 | 
				
			||||||
@@ -244,9 +295,8 @@ func (c *Controller) UpdateDevice(ctx *gin.Context) {
 | 
				
			|||||||
// @Tags         设备管理
 | 
					// @Tags         设备管理
 | 
				
			||||||
// @Produce      json
 | 
					// @Produce      json
 | 
				
			||||||
// @Param        id path string true "设备ID"
 | 
					// @Param        id path string true "设备ID"
 | 
				
			||||||
// @Success      200 {object} controller.Response "业务码为200代表删除成功"
 | 
					// @Success      200 {object} controller.Response
 | 
				
			||||||
// @Failure      200 {object} controller.Response "业务失败,具体错误码和信息见响应体"
 | 
					// @Router       /api/v1/devices/{id} [delete]
 | 
				
			||||||
// @Router       /devices/{id} [delete]
 | 
					 | 
				
			||||||
func (c *Controller) DeleteDevice(ctx *gin.Context) {
 | 
					func (c *Controller) DeleteDevice(ctx *gin.Context) {
 | 
				
			||||||
	deviceID := ctx.Param("id")
 | 
						deviceID := ctx.Param("id")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,15 +1,16 @@
 | 
				
			|||||||
package plan
 | 
					package plan
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"git.huangwc.com/pig/pig-farm-controller/internal/app/controller"
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
 | 
						"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
 | 
				
			||||||
	"gorm.io/datatypes"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// PlanToResponse 将Plan模型转换为PlanResponse
 | 
					// PlanToResponse 将Plan模型转换为PlanResponse
 | 
				
			||||||
func PlanToResponse(plan *models.Plan) *PlanResponse {
 | 
					func PlanToResponse(plan *models.Plan) (*PlanResponse, error) {
 | 
				
			||||||
	if plan == nil {
 | 
						if plan == nil {
 | 
				
			||||||
		return nil
 | 
							return nil, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	response := &PlanResponse{
 | 
						response := &PlanResponse{
 | 
				
			||||||
@@ -17,6 +18,9 @@ func PlanToResponse(plan *models.Plan) *PlanResponse {
 | 
				
			|||||||
		Name:           plan.Name,
 | 
							Name:           plan.Name,
 | 
				
			||||||
		Description:    plan.Description,
 | 
							Description:    plan.Description,
 | 
				
			||||||
		ExecutionType:  plan.ExecutionType,
 | 
							ExecutionType:  plan.ExecutionType,
 | 
				
			||||||
 | 
							Status:         plan.Status,
 | 
				
			||||||
 | 
							ExecuteNum:     plan.ExecuteNum,
 | 
				
			||||||
 | 
							ExecuteCount:   plan.ExecuteCount,
 | 
				
			||||||
		CronExpression: plan.CronExpression,
 | 
							CronExpression: plan.CronExpression,
 | 
				
			||||||
		ContentType:    plan.ContentType,
 | 
							ContentType:    plan.ContentType,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -25,7 +29,11 @@ func PlanToResponse(plan *models.Plan) *PlanResponse {
 | 
				
			|||||||
	if plan.ContentType == models.PlanContentTypeSubPlans {
 | 
						if plan.ContentType == models.PlanContentTypeSubPlans {
 | 
				
			||||||
		response.SubPlans = make([]SubPlanResponse, len(plan.SubPlans))
 | 
							response.SubPlans = make([]SubPlanResponse, len(plan.SubPlans))
 | 
				
			||||||
		for i, subPlan := range plan.SubPlans {
 | 
							for i, subPlan := range plan.SubPlans {
 | 
				
			||||||
			response.SubPlans[i] = SubPlanToResponse(&subPlan)
 | 
								subPlanResp, err := SubPlanToResponse(&subPlan)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								response.SubPlans[i] = subPlanResp
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -33,11 +41,15 @@ func PlanToResponse(plan *models.Plan) *PlanResponse {
 | 
				
			|||||||
	if plan.ContentType == models.PlanContentTypeTasks {
 | 
						if plan.ContentType == models.PlanContentTypeTasks {
 | 
				
			||||||
		response.Tasks = make([]TaskResponse, len(plan.Tasks))
 | 
							response.Tasks = make([]TaskResponse, len(plan.Tasks))
 | 
				
			||||||
		for i, task := range plan.Tasks {
 | 
							for i, task := range plan.Tasks {
 | 
				
			||||||
			response.Tasks[i] = TaskToResponse(&task)
 | 
								taskResp, err := TaskToResponse(&task)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								response.Tasks[i] = taskResp
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return response
 | 
						return response, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// PlanFromCreateRequest 将CreatePlanRequest转换为Plan模型,并进行业务规则验证
 | 
					// PlanFromCreateRequest 将CreatePlanRequest转换为Plan模型,并进行业务规则验证
 | 
				
			||||||
@@ -50,6 +62,7 @@ func PlanFromCreateRequest(req *CreatePlanRequest) (*models.Plan, error) {
 | 
				
			|||||||
		Name:           req.Name,
 | 
							Name:           req.Name,
 | 
				
			||||||
		Description:    req.Description,
 | 
							Description:    req.Description,
 | 
				
			||||||
		ExecutionType:  req.ExecutionType,
 | 
							ExecutionType:  req.ExecutionType,
 | 
				
			||||||
 | 
							ExecuteNum:     req.ExecuteNum,
 | 
				
			||||||
		CronExpression: req.CronExpression,
 | 
							CronExpression: req.CronExpression,
 | 
				
			||||||
		ContentType:    req.ContentType,
 | 
							ContentType:    req.ContentType,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -69,8 +82,11 @@ func PlanFromCreateRequest(req *CreatePlanRequest) (*models.Plan, error) {
 | 
				
			|||||||
	if req.ContentType == models.PlanContentTypeTasks && req.Tasks != nil {
 | 
						if req.ContentType == models.PlanContentTypeTasks && req.Tasks != nil {
 | 
				
			||||||
		plan.Tasks = make([]models.Task, len(req.Tasks))
 | 
							plan.Tasks = make([]models.Task, len(req.Tasks))
 | 
				
			||||||
		for i, taskReq := range req.Tasks {
 | 
							for i, taskReq := range req.Tasks {
 | 
				
			||||||
			// 使用来自请求的ExecutionOrder
 | 
								task, err := TaskFromRequest(&taskReq)
 | 
				
			||||||
			plan.Tasks[i] = TaskFromRequest(&taskReq)
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								plan.Tasks[i] = task
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -96,6 +112,7 @@ func PlanFromUpdateRequest(req *UpdatePlanRequest) (*models.Plan, error) {
 | 
				
			|||||||
		Name:           req.Name,
 | 
							Name:           req.Name,
 | 
				
			||||||
		Description:    req.Description,
 | 
							Description:    req.Description,
 | 
				
			||||||
		ExecutionType:  req.ExecutionType,
 | 
							ExecutionType:  req.ExecutionType,
 | 
				
			||||||
 | 
							ExecuteNum:     req.ExecuteNum,
 | 
				
			||||||
		CronExpression: req.CronExpression,
 | 
							CronExpression: req.CronExpression,
 | 
				
			||||||
		ContentType:    req.ContentType,
 | 
							ContentType:    req.ContentType,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -115,8 +132,11 @@ func PlanFromUpdateRequest(req *UpdatePlanRequest) (*models.Plan, error) {
 | 
				
			|||||||
	if req.ContentType == models.PlanContentTypeTasks && req.Tasks != nil {
 | 
						if req.ContentType == models.PlanContentTypeTasks && req.Tasks != nil {
 | 
				
			||||||
		plan.Tasks = make([]models.Task, len(req.Tasks))
 | 
							plan.Tasks = make([]models.Task, len(req.Tasks))
 | 
				
			||||||
		for i, taskReq := range req.Tasks {
 | 
							for i, taskReq := range req.Tasks {
 | 
				
			||||||
			// 使用来自请求的ExecutionOrder
 | 
								task, err := TaskFromRequest(&taskReq)
 | 
				
			||||||
			plan.Tasks[i] = TaskFromRequest(&taskReq)
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return nil, err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								plan.Tasks[i] = task
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -133,9 +153,9 @@ func PlanFromUpdateRequest(req *UpdatePlanRequest) (*models.Plan, error) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// SubPlanToResponse 将SubPlan模型转换为SubPlanResponse
 | 
					// SubPlanToResponse 将SubPlan模型转换为SubPlanResponse
 | 
				
			||||||
func SubPlanToResponse(subPlan *models.SubPlan) SubPlanResponse {
 | 
					func SubPlanToResponse(subPlan *models.SubPlan) (SubPlanResponse, error) {
 | 
				
			||||||
	if subPlan == nil {
 | 
						if subPlan == nil {
 | 
				
			||||||
		return SubPlanResponse{}
 | 
							return SubPlanResponse{}, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	response := SubPlanResponse{
 | 
						response := SubPlanResponse{
 | 
				
			||||||
@@ -147,16 +167,27 @@ func SubPlanToResponse(subPlan *models.SubPlan) SubPlanResponse {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// 如果有完整的子计划数据,也进行转换
 | 
						// 如果有完整的子计划数据,也进行转换
 | 
				
			||||||
	if subPlan.ChildPlan != nil {
 | 
						if subPlan.ChildPlan != nil {
 | 
				
			||||||
		response.ChildPlan = PlanToResponse(subPlan.ChildPlan)
 | 
							childPlanResp, err := PlanToResponse(subPlan.ChildPlan)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return SubPlanResponse{}, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							response.ChildPlan = childPlanResp
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return response
 | 
						return response, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TaskToResponse 将Task模型转换为TaskResponse
 | 
					// TaskToResponse 将Task模型转换为TaskResponse
 | 
				
			||||||
func TaskToResponse(task *models.Task) TaskResponse {
 | 
					func TaskToResponse(task *models.Task) (TaskResponse, error) {
 | 
				
			||||||
	if task == nil {
 | 
						if task == nil {
 | 
				
			||||||
		return TaskResponse{}
 | 
							return TaskResponse{}, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var params map[string]interface{}
 | 
				
			||||||
 | 
						if len(task.Parameters) > 0 && string(task.Parameters) != "null" {
 | 
				
			||||||
 | 
							if err := json.Unmarshal(task.Parameters, ¶ms); err != nil {
 | 
				
			||||||
 | 
								return TaskResponse{}, fmt.Errorf("parsing task parameters failed (ID: %d): %w", task.ID, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return TaskResponse{
 | 
						return TaskResponse{
 | 
				
			||||||
@@ -166,14 +197,19 @@ func TaskToResponse(task *models.Task) TaskResponse {
 | 
				
			|||||||
		Description:    task.Description,
 | 
							Description:    task.Description,
 | 
				
			||||||
		ExecutionOrder: task.ExecutionOrder,
 | 
							ExecutionOrder: task.ExecutionOrder,
 | 
				
			||||||
		Type:           task.Type,
 | 
							Type:           task.Type,
 | 
				
			||||||
		Parameters:     controller.Properties(task.Parameters),
 | 
							Parameters:     params,
 | 
				
			||||||
	}
 | 
						}, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TaskFromRequest 将TaskRequest转换为Task模型
 | 
					// TaskFromRequest 将TaskRequest转换为Task模型
 | 
				
			||||||
func TaskFromRequest(req *TaskRequest) models.Task {
 | 
					func TaskFromRequest(req *TaskRequest) (models.Task, error) {
 | 
				
			||||||
	if req == nil {
 | 
						if req == nil {
 | 
				
			||||||
		return models.Task{}
 | 
							return models.Task{}, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						paramsJSON, err := json.Marshal(req.Parameters)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return models.Task{}, fmt.Errorf("serializing task parameters failed: %w", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return models.Task{
 | 
						return models.Task{
 | 
				
			||||||
@@ -181,6 +217,6 @@ func TaskFromRequest(req *TaskRequest) models.Task {
 | 
				
			|||||||
		Description:    req.Description,
 | 
							Description:    req.Description,
 | 
				
			||||||
		ExecutionOrder: req.ExecutionOrder,
 | 
							ExecutionOrder: req.ExecutionOrder,
 | 
				
			||||||
		Type:           req.Type,
 | 
							Type:           req.Type,
 | 
				
			||||||
		Parameters:     datatypes.JSON(req.Parameters),
 | 
							Parameters:     paramsJSON,
 | 
				
			||||||
	}
 | 
						}, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,6 +20,7 @@ type CreatePlanRequest struct {
 | 
				
			|||||||
	Name           string                   `json:"name" binding:"required" example:"猪舍温度控制计划"`
 | 
						Name           string                   `json:"name" binding:"required" example:"猪舍温度控制计划"`
 | 
				
			||||||
	Description    string                   `json:"description" example:"根据温度自动调节风扇和加热器"`
 | 
						Description    string                   `json:"description" example:"根据温度自动调节风扇和加热器"`
 | 
				
			||||||
	ExecutionType  models.PlanExecutionType `json:"execution_type" binding:"required" example:"automatic"`
 | 
						ExecutionType  models.PlanExecutionType `json:"execution_type" binding:"required" example:"automatic"`
 | 
				
			||||||
 | 
						ExecuteNum     uint                     `json:"execute_num,omitempty" example:"10"`
 | 
				
			||||||
	CronExpression string                   `json:"cron_expression" example:"0 0 6 * * *"`
 | 
						CronExpression string                   `json:"cron_expression" example:"0 0 6 * * *"`
 | 
				
			||||||
	ContentType    models.PlanContentType   `json:"content_type" binding:"required" example:"tasks"`
 | 
						ContentType    models.PlanContentType   `json:"content_type" binding:"required" example:"tasks"`
 | 
				
			||||||
	SubPlanIDs     []uint                   `json:"sub_plan_ids,omitempty"`
 | 
						SubPlanIDs     []uint                   `json:"sub_plan_ids,omitempty"`
 | 
				
			||||||
@@ -32,6 +33,9 @@ type PlanResponse struct {
 | 
				
			|||||||
	Name           string                   `json:"name" example:"猪舍温度控制计划"`
 | 
						Name           string                   `json:"name" example:"猪舍温度控制计划"`
 | 
				
			||||||
	Description    string                   `json:"description" example:"根据温度自动调节风扇和加热器"`
 | 
						Description    string                   `json:"description" example:"根据温度自动调节风扇和加热器"`
 | 
				
			||||||
	ExecutionType  models.PlanExecutionType `json:"execution_type" example:"automatic"`
 | 
						ExecutionType  models.PlanExecutionType `json:"execution_type" example:"automatic"`
 | 
				
			||||||
 | 
						Status         models.PlanStatus        `json:"status" example:"0"`
 | 
				
			||||||
 | 
						ExecuteNum     uint                     `json:"execute_num" example:"10"`
 | 
				
			||||||
 | 
						ExecuteCount   uint                     `json:"execute_count" example:"0"`
 | 
				
			||||||
	CronExpression string                   `json:"cron_expression" example:"0 0 6 * * *"`
 | 
						CronExpression string                   `json:"cron_expression" example:"0 0 6 * * *"`
 | 
				
			||||||
	ContentType    models.PlanContentType   `json:"content_type" example:"tasks"`
 | 
						ContentType    models.PlanContentType   `json:"content_type" example:"tasks"`
 | 
				
			||||||
	SubPlans       []SubPlanResponse        `json:"sub_plans,omitempty"`
 | 
						SubPlans       []SubPlanResponse        `json:"sub_plans,omitempty"`
 | 
				
			||||||
@@ -49,6 +53,7 @@ type UpdatePlanRequest struct {
 | 
				
			|||||||
	Name           string                   `json:"name" example:"猪舍温度控制计划V2"`
 | 
						Name           string                   `json:"name" example:"猪舍温度控制计划V2"`
 | 
				
			||||||
	Description    string                   `json:"description" example:"更新后的描述"`
 | 
						Description    string                   `json:"description" example:"更新后的描述"`
 | 
				
			||||||
	ExecutionType  models.PlanExecutionType `json:"execution_type" example:"automatic"`
 | 
						ExecutionType  models.PlanExecutionType `json:"execution_type" example:"automatic"`
 | 
				
			||||||
 | 
						ExecuteNum     uint                     `json:"execute_num,omitempty" example:"10"`
 | 
				
			||||||
	CronExpression string                   `json:"cron_expression" example:"0 0 6 * * *"`
 | 
						CronExpression string                   `json:"cron_expression" example:"0 0 6 * * *"`
 | 
				
			||||||
	ContentType    models.PlanContentType   `json:"content_type" example:"tasks"`
 | 
						ContentType    models.PlanContentType   `json:"content_type" example:"tasks"`
 | 
				
			||||||
	SubPlanIDs     []uint                   `json:"sub_plan_ids,omitempty"`
 | 
						SubPlanIDs     []uint                   `json:"sub_plan_ids,omitempty"`
 | 
				
			||||||
@@ -66,22 +71,22 @@ type SubPlanResponse struct {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// TaskRequest 定义任务请求结构体
 | 
					// TaskRequest 定义任务请求结构体
 | 
				
			||||||
type TaskRequest struct {
 | 
					type TaskRequest struct {
 | 
				
			||||||
	Name           string                `json:"name" example:"打开风扇"`
 | 
						Name           string                 `json:"name" example:"打开风扇"`
 | 
				
			||||||
	Description    string                `json:"description" example:"打开1号风扇"`
 | 
						Description    string                 `json:"description" example:"打开1号风扇"`
 | 
				
			||||||
	ExecutionOrder int                   `json:"execution_order" example:"1"`
 | 
						ExecutionOrder int                    `json:"execution_order" example:"1"`
 | 
				
			||||||
	Type           models.TaskType       `json:"type" example:"waiting"`
 | 
						Type           models.TaskType        `json:"type" example:"waiting"`
 | 
				
			||||||
	Parameters     controller.Properties `json:"parameters,omitempty"`
 | 
						Parameters     map[string]interface{} `json:"parameters,omitempty"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TaskResponse 定义任务响应结构体
 | 
					// TaskResponse 定义任务响应结构体
 | 
				
			||||||
type TaskResponse struct {
 | 
					type TaskResponse struct {
 | 
				
			||||||
	ID             int                   `json:"id" example:"1"`
 | 
						ID             int                    `json:"id" example:"1"`
 | 
				
			||||||
	PlanID         uint                  `json:"plan_id" example:"1"`
 | 
						PlanID         uint                   `json:"plan_id" example:"1"`
 | 
				
			||||||
	Name           string                `json:"name" example:"打开风扇"`
 | 
						Name           string                 `json:"name" example:"打开风扇"`
 | 
				
			||||||
	Description    string                `json:"description" example:"打开1号风扇"`
 | 
						Description    string                 `json:"description" example:"打开1号风扇"`
 | 
				
			||||||
	ExecutionOrder int                   `json:"execution_order" example:"1"`
 | 
						ExecutionOrder int                    `json:"execution_order" example:"1"`
 | 
				
			||||||
	Type           models.TaskType       `json:"type" example:"waiting"`
 | 
						Type           models.TaskType        `json:"type" example:"waiting"`
 | 
				
			||||||
	Parameters     controller.Properties `json:"parameters,omitempty"`
 | 
						Parameters     map[string]interface{} `json:"parameters,omitempty"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// --- Controller 定义 ---
 | 
					// --- Controller 定义 ---
 | 
				
			||||||
@@ -112,8 +117,7 @@ func NewController(logger *logs.Logger, planRepo repository.PlanRepository, anal
 | 
				
			|||||||
// @Produce      json
 | 
					// @Produce      json
 | 
				
			||||||
// @Param        plan body CreatePlanRequest true "计划信息"
 | 
					// @Param        plan body CreatePlanRequest true "计划信息"
 | 
				
			||||||
// @Success      200 {object} controller.Response{data=plan.PlanResponse} "业务码为201代表创建成功"
 | 
					// @Success      200 {object} controller.Response{data=plan.PlanResponse} "业务码为201代表创建成功"
 | 
				
			||||||
// @Failure      200 {object} controller.Response "业务失败,具体错误码和信息见响应体(例如400, 500)"
 | 
					// @Router       /api/v1/plans [post]
 | 
				
			||||||
// @Router       /plans [post]
 | 
					 | 
				
			||||||
func (c *Controller) CreatePlan(ctx *gin.Context) {
 | 
					func (c *Controller) CreatePlan(ctx *gin.Context) {
 | 
				
			||||||
	var req CreatePlanRequest
 | 
						var req CreatePlanRequest
 | 
				
			||||||
	if err := ctx.ShouldBindJSON(&req); err != nil {
 | 
						if err := ctx.ShouldBindJSON(&req); err != nil {
 | 
				
			||||||
@@ -134,14 +138,19 @@ func (c *Controller) CreatePlan(ctx *gin.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 创建成功后,调用 manager 创建或更新触发器
 | 
						// 创建成功后,调用 manager 确保触发器任务定义存在,但不立即加入待执行队列
 | 
				
			||||||
	if err := c.analysisPlanTaskManager.CreateOrUpdateTrigger(ctx, planToCreate.ID); err != nil {
 | 
						if err := c.analysisPlanTaskManager.EnsureAnalysisTaskDefinition(planToCreate.ID); err != nil {
 | 
				
			||||||
		// 这是一个非阻塞性错误,我们只记录日志,因为主流程(创建计划)已经成功
 | 
							// 这是一个非阻塞性错误,我们只记录日志,因为主流程(创建计划)已经成功
 | 
				
			||||||
		c.logger.Errorf("为新创建的计划 %d 创建触发器失败: %v", planToCreate.ID, err)
 | 
							c.logger.Errorf("为新创建的计划 %d 确保触发器任务定义失败: %v", planToCreate.ID, err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 使用已有的转换函数将创建后的模型转换为响应对象
 | 
						// 使用已有的转换函数将创建后的模型转换为响应对象
 | 
				
			||||||
	resp := PlanToResponse(planToCreate)
 | 
						resp, err := PlanToResponse(planToCreate)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							c.logger.Errorf("创建计划: 序列化响应失败: %v", err)
 | 
				
			||||||
 | 
							controller.SendErrorResponse(ctx, controller.CodeInternalError, "计划创建成功,但响应生成失败")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 使用统一的成功响应函数
 | 
						// 使用统一的成功响应函数
 | 
				
			||||||
	controller.SendResponse(ctx, controller.CodeCreated, "计划创建成功", resp)
 | 
						controller.SendResponse(ctx, controller.CodeCreated, "计划创建成功", resp)
 | 
				
			||||||
@@ -154,8 +163,7 @@ func (c *Controller) CreatePlan(ctx *gin.Context) {
 | 
				
			|||||||
// @Produce      json
 | 
					// @Produce      json
 | 
				
			||||||
// @Param        id path int true "计划ID"
 | 
					// @Param        id path int true "计划ID"
 | 
				
			||||||
// @Success      200 {object} controller.Response{data=plan.PlanResponse} "业务码为200代表成功获取"
 | 
					// @Success      200 {object} controller.Response{data=plan.PlanResponse} "业务码为200代表成功获取"
 | 
				
			||||||
// @Failure      200 {object} controller.Response "业务失败,具体错误码和信息见响应体(例如400, 404, 500)"
 | 
					// @Router       /api/v1/plans/{id} [get]
 | 
				
			||||||
// @Router       /plans/{id} [get]
 | 
					 | 
				
			||||||
func (c *Controller) GetPlan(ctx *gin.Context) {
 | 
					func (c *Controller) GetPlan(ctx *gin.Context) {
 | 
				
			||||||
	// 1. 从 URL 路径中获取 ID
 | 
						// 1. 从 URL 路径中获取 ID
 | 
				
			||||||
	idStr := ctx.Param("id")
 | 
						idStr := ctx.Param("id")
 | 
				
			||||||
@@ -180,7 +188,12 @@ func (c *Controller) GetPlan(ctx *gin.Context) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 3. 将模型转换为响应 DTO
 | 
						// 3. 将模型转换为响应 DTO
 | 
				
			||||||
	resp := PlanToResponse(plan)
 | 
						resp, err := PlanToResponse(plan)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							c.logger.Errorf("获取计划详情: 序列化响应失败: %v", err)
 | 
				
			||||||
 | 
							controller.SendErrorResponse(ctx, controller.CodeInternalError, "获取计划详情失败: 内部数据格式错误")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 4. 发送成功响应
 | 
						// 4. 发送成功响应
 | 
				
			||||||
	controller.SendResponse(ctx, controller.CodeSuccess, "获取计划详情成功", resp)
 | 
						controller.SendResponse(ctx, controller.CodeSuccess, "获取计划详情成功", resp)
 | 
				
			||||||
@@ -192,8 +205,7 @@ func (c *Controller) GetPlan(ctx *gin.Context) {
 | 
				
			|||||||
// @Tags         计划管理
 | 
					// @Tags         计划管理
 | 
				
			||||||
// @Produce      json
 | 
					// @Produce      json
 | 
				
			||||||
// @Success      200 {object} controller.Response{data=plan.ListPlansResponse} "业务码为200代表成功获取列表"
 | 
					// @Success      200 {object} controller.Response{data=plan.ListPlansResponse} "业务码为200代表成功获取列表"
 | 
				
			||||||
// @Failure      200 {object} controller.Response "业务失败,具体错误码和信息见响应体(例如400, 500)"
 | 
					// @Router       /api/v1/plans [get]
 | 
				
			||||||
// @Router       /plans [get]
 | 
					 | 
				
			||||||
func (c *Controller) ListPlans(ctx *gin.Context) {
 | 
					func (c *Controller) ListPlans(ctx *gin.Context) {
 | 
				
			||||||
	// 1. 调用仓库层获取所有计划
 | 
						// 1. 调用仓库层获取所有计划
 | 
				
			||||||
	plans, err := c.planRepo.ListBasicPlans()
 | 
						plans, err := c.planRepo.ListBasicPlans()
 | 
				
			||||||
@@ -206,7 +218,13 @@ func (c *Controller) ListPlans(ctx *gin.Context) {
 | 
				
			|||||||
	// 2. 将模型转换为响应 DTO
 | 
						// 2. 将模型转换为响应 DTO
 | 
				
			||||||
	planResponses := make([]PlanResponse, 0, len(plans))
 | 
						planResponses := make([]PlanResponse, 0, len(plans))
 | 
				
			||||||
	for _, p := range plans {
 | 
						for _, p := range plans {
 | 
				
			||||||
		planResponses = append(planResponses, *PlanToResponse(&p))
 | 
							resp, err := PlanToResponse(&p)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								c.logger.Errorf("获取计划列表: 序列化响应失败: %v", err)
 | 
				
			||||||
 | 
								controller.SendErrorResponse(ctx, controller.CodeInternalError, "获取计划列表失败: 内部数据格式错误")
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							planResponses = append(planResponses, *resp)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 3. 构造并发送成功响应
 | 
						// 3. 构造并发送成功响应
 | 
				
			||||||
@@ -226,8 +244,7 @@ func (c *Controller) ListPlans(ctx *gin.Context) {
 | 
				
			|||||||
// @Param        id path int true "计划ID"
 | 
					// @Param        id path int true "计划ID"
 | 
				
			||||||
// @Param        plan body UpdatePlanRequest true "更新后的计划信息"
 | 
					// @Param        plan body UpdatePlanRequest true "更新后的计划信息"
 | 
				
			||||||
// @Success      200 {object} controller.Response{data=plan.PlanResponse} "业务码为200代表更新成功"
 | 
					// @Success      200 {object} controller.Response{data=plan.PlanResponse} "业务码为200代表更新成功"
 | 
				
			||||||
// @Failure      200 {object} controller.Response "业务失败,具体错误码和信息见响应体(例如400, 404, 500)"
 | 
					// @Router       /api/v1/plans/{id} [put]
 | 
				
			||||||
// @Router       /plans/{id} [put]
 | 
					 | 
				
			||||||
func (c *Controller) UpdatePlan(ctx *gin.Context) {
 | 
					func (c *Controller) UpdatePlan(ctx *gin.Context) {
 | 
				
			||||||
	// 1. 从 URL 路径中获取 ID
 | 
						// 1. 从 URL 路径中获取 ID
 | 
				
			||||||
	idStr := ctx.Param("id")
 | 
						idStr := ctx.Param("id")
 | 
				
			||||||
@@ -270,10 +287,10 @@ func (c *Controller) UpdatePlan(ctx *gin.Context) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 更新成功后,调用 manager 创建或更新触发器
 | 
						// 更新成功后,调用 manager 确保触发器任务定义存在
 | 
				
			||||||
	if err := c.analysisPlanTaskManager.CreateOrUpdateTrigger(ctx, planToUpdate.ID); err != nil {
 | 
						if err := c.analysisPlanTaskManager.EnsureAnalysisTaskDefinition(planToUpdate.ID); err != nil {
 | 
				
			||||||
		// 这是一个非阻塞性错误,我们只记录日志
 | 
							// 这是一个非阻塞性错误,我们只记录日志
 | 
				
			||||||
		c.logger.Errorf("为更新后的计划 %d 更新触发器失败: %v", planToUpdate.ID, err)
 | 
							c.logger.Errorf("为更新后的计划 %d 确保触发器任务定义失败: %v", planToUpdate.ID, err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 6. 获取更新后的完整计划用于响应
 | 
						// 6. 获取更新后的完整计划用于响应
 | 
				
			||||||
@@ -285,7 +302,12 @@ func (c *Controller) UpdatePlan(ctx *gin.Context) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 7. 将模型转换为响应 DTO
 | 
						// 7. 将模型转换为响应 DTO
 | 
				
			||||||
	resp := PlanToResponse(updatedPlan)
 | 
						resp, err := PlanToResponse(updatedPlan)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							c.logger.Errorf("更新计划: 序列化响应失败: %v", err)
 | 
				
			||||||
 | 
							controller.SendErrorResponse(ctx, controller.CodeInternalError, "计划更新成功,但响应生成失败")
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 8. 发送成功响应
 | 
						// 8. 发送成功响应
 | 
				
			||||||
	controller.SendResponse(ctx, controller.CodeSuccess, "计划更新成功", resp)
 | 
						controller.SendResponse(ctx, controller.CodeSuccess, "计划更新成功", resp)
 | 
				
			||||||
@@ -298,8 +320,7 @@ func (c *Controller) UpdatePlan(ctx *gin.Context) {
 | 
				
			|||||||
// @Produce      json
 | 
					// @Produce      json
 | 
				
			||||||
// @Param        id path int true "计划ID"
 | 
					// @Param        id path int true "计划ID"
 | 
				
			||||||
// @Success      200 {object} controller.Response "业务码为200代表删除成功"
 | 
					// @Success      200 {object} controller.Response "业务码为200代表删除成功"
 | 
				
			||||||
// @Failure      200 {object} controller.Response "业务失败,具体错误码和信息见响应体(例如400, 404, 500)"
 | 
					// @Router       /api/v1/plans/{id} [delete]
 | 
				
			||||||
// @Router       /plans/{id} [delete]
 | 
					 | 
				
			||||||
func (c *Controller) DeletePlan(ctx *gin.Context) {
 | 
					func (c *Controller) DeletePlan(ctx *gin.Context) {
 | 
				
			||||||
	// 1. 从 URL 路径中获取 ID
 | 
						// 1. 从 URL 路径中获取 ID
 | 
				
			||||||
	idStr := ctx.Param("id")
 | 
						idStr := ctx.Param("id")
 | 
				
			||||||
@@ -327,8 +348,7 @@ func (c *Controller) DeletePlan(ctx *gin.Context) {
 | 
				
			|||||||
// @Produce      json
 | 
					// @Produce      json
 | 
				
			||||||
// @Param        id path int true "计划ID"
 | 
					// @Param        id path int true "计划ID"
 | 
				
			||||||
// @Success      200 {object} controller.Response "业务码为200代表成功启动计划"
 | 
					// @Success      200 {object} controller.Response "业务码为200代表成功启动计划"
 | 
				
			||||||
// @Failure      200 {object} controller.Response "业务失败,具体错误码和信息见响应体(例如400, 404, 500)"
 | 
					// @Router       /api/v1/plans/{id}/start [post]
 | 
				
			||||||
// @Router       /plans/{id}/start [post]
 | 
					 | 
				
			||||||
func (c *Controller) StartPlan(ctx *gin.Context) {
 | 
					func (c *Controller) StartPlan(ctx *gin.Context) {
 | 
				
			||||||
	// 占位符:此处应调用服务层或仓库层来启动计划
 | 
						// 占位符:此处应调用服务层或仓库层来启动计划
 | 
				
			||||||
	c.logger.Infof("收到启动计划请求 (占位符)")
 | 
						c.logger.Infof("收到启动计划请求 (占位符)")
 | 
				
			||||||
@@ -342,8 +362,7 @@ func (c *Controller) StartPlan(ctx *gin.Context) {
 | 
				
			|||||||
// @Produce      json
 | 
					// @Produce      json
 | 
				
			||||||
// @Param        id path int true "计划ID"
 | 
					// @Param        id path int true "计划ID"
 | 
				
			||||||
// @Success      200 {object} controller.Response "业务码为200代表成功停止计划"
 | 
					// @Success      200 {object} controller.Response "业务码为200代表成功停止计划"
 | 
				
			||||||
// @Failure      200 {object} controller.Response "业务失败,具体错误码和信息见响应体(例如400, 404, 500)"
 | 
					// @Router       /api/v1/plans/{id}/stop [post]
 | 
				
			||||||
// @Router       /plans/{id}/stop [post]
 | 
					 | 
				
			||||||
func (c *Controller) StopPlan(ctx *gin.Context) {
 | 
					func (c *Controller) StopPlan(ctx *gin.Context) {
 | 
				
			||||||
	// 占位符:此处应调用服务层或仓库层来停止计划
 | 
						// 占位符:此处应调用服务层或仓库层来停止计划
 | 
				
			||||||
	c.logger.Infof("收到停止计划请求 (占位符)")
 | 
						c.logger.Infof("收到停止计划请求 (占位符)")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,6 @@
 | 
				
			|||||||
package controller
 | 
					package controller
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"encoding/json"
 | 
					 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/gin-gonic/gin"
 | 
						"github.com/gin-gonic/gin"
 | 
				
			||||||
@@ -46,6 +45,3 @@ func SendResponse(ctx *gin.Context, code int, message string, data interface{})
 | 
				
			|||||||
func SendErrorResponse(ctx *gin.Context, code int, message string) {
 | 
					func SendErrorResponse(ctx *gin.Context, code int, message string) {
 | 
				
			||||||
	SendResponse(ctx, code, message, nil)
 | 
						SendResponse(ctx, code, message, nil)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
// Properties 是一个自定义类型,用于在 Swagger 中正确表示 JSON 对象
 | 
					 | 
				
			||||||
type Properties json.RawMessage
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
@@ -59,8 +59,7 @@ type LoginResponse struct {
 | 
				
			|||||||
// @Produce      json
 | 
					// @Produce      json
 | 
				
			||||||
// @Param        user body CreateUserRequest true "用户信息"
 | 
					// @Param        user body CreateUserRequest true "用户信息"
 | 
				
			||||||
// @Success      200 {object} controller.Response{data=user.CreateUserResponse} "业务码为201代表创建成功"
 | 
					// @Success      200 {object} controller.Response{data=user.CreateUserResponse} "业务码为201代表创建成功"
 | 
				
			||||||
// @Failure      200 {object} controller.Response "业务失败,具体错误码和信息见响应体(例如400, 409, 500)"
 | 
					// @Router       /api/v1/users [post]
 | 
				
			||||||
// @Router       /users [post]
 | 
					 | 
				
			||||||
func (c *Controller) CreateUser(ctx *gin.Context) {
 | 
					func (c *Controller) CreateUser(ctx *gin.Context) {
 | 
				
			||||||
	var req CreateUserRequest
 | 
						var req CreateUserRequest
 | 
				
			||||||
	if err := ctx.ShouldBindJSON(&req); err != nil {
 | 
						if err := ctx.ShouldBindJSON(&req); err != nil {
 | 
				
			||||||
@@ -103,8 +102,7 @@ func (c *Controller) CreateUser(ctx *gin.Context) {
 | 
				
			|||||||
// @Produce      json
 | 
					// @Produce      json
 | 
				
			||||||
// @Param        credentials body LoginRequest true "登录凭证"
 | 
					// @Param        credentials body LoginRequest true "登录凭证"
 | 
				
			||||||
// @Success      200 {object} controller.Response{data=user.LoginResponse} "业务码为200代表登录成功"
 | 
					// @Success      200 {object} controller.Response{data=user.LoginResponse} "业务码为200代表登录成功"
 | 
				
			||||||
// @Failure      200 {object} controller.Response "业务失败,具体错误码和信息见响应体(例如400, 401, 500)"
 | 
					// @Router       /api/v1/users/login [post]
 | 
				
			||||||
// @Router       /users/login [post]
 | 
					 | 
				
			||||||
func (c *Controller) Login(ctx *gin.Context) {
 | 
					func (c *Controller) Login(ctx *gin.Context) {
 | 
				
			||||||
	var req LoginRequest
 | 
						var req LoginRequest
 | 
				
			||||||
	if err := ctx.ShouldBindJSON(&req); err != nil {
 | 
						if err := ctx.ShouldBindJSON(&req); err != nil {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,9 @@
 | 
				
			|||||||
package task
 | 
					package task
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"sync"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
 | 
						"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/models"
 | 
				
			||||||
@@ -9,13 +11,15 @@ import (
 | 
				
			|||||||
	"git.huangwc.com/pig/pig-farm-controller/internal/infra/utils"
 | 
						"git.huangwc.com/pig/pig-farm-controller/internal/infra/utils"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// AnalysisPlanTaskManager 封装了创建和更新计划分析任务(即触发器)的逻辑。
 | 
					// AnalysisPlanTaskManager 负责管理分析计划的触发器任务。
 | 
				
			||||||
// 这是一个可被 Scheduler 和其他应用服务(如 PlanService)共享的无状态组件。
 | 
					// 它确保数据库中可执行的计划在待执行队列中有对应的触发器,并移除无效的触发器。
 | 
				
			||||||
 | 
					// 这是一个有状态的组件,包含一个互斥锁以确保并发安全。
 | 
				
			||||||
type AnalysisPlanTaskManager struct {
 | 
					type AnalysisPlanTaskManager struct {
 | 
				
			||||||
	planRepo         repository.PlanRepository
 | 
						planRepo         repository.PlanRepository
 | 
				
			||||||
	pendingTaskRepo  repository.PendingTaskRepository
 | 
						pendingTaskRepo  repository.PendingTaskRepository
 | 
				
			||||||
	executionLogRepo repository.ExecutionLogRepository
 | 
						executionLogRepo repository.ExecutionLogRepository
 | 
				
			||||||
	logger           *logs.Logger
 | 
						logger           *logs.Logger
 | 
				
			||||||
 | 
						mu               sync.Mutex
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewAnalysisPlanTaskManager 是 AnalysisPlanTaskManager 的构造函数。
 | 
					// NewAnalysisPlanTaskManager 是 AnalysisPlanTaskManager 的构造函数。
 | 
				
			||||||
@@ -33,50 +37,281 @@ func NewAnalysisPlanTaskManager(
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CreateOrUpdateTrigger 为给定的 planID 创建或更新其关联的下一次触发任务。
 | 
					// Refresh 同步数据库中的计划状态和待执行队列中的触发器任务。
 | 
				
			||||||
// 这个方法是幂等的,可以安全地被多次调用。
 | 
					// 这是一个编排方法,将复杂的逻辑分解到多个内部方法中。
 | 
				
			||||||
func (m *AnalysisPlanTaskManager) CreateOrUpdateTrigger(ctx context.Context, planID uint) error {
 | 
					func (m *AnalysisPlanTaskManager) Refresh() error {
 | 
				
			||||||
	// 获取计划信息
 | 
						m.mu.Lock()
 | 
				
			||||||
	plan, err := m.planRepo.GetBasicPlanByID(planID)
 | 
						defer m.mu.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						m.logger.Info("开始同步计划任务管理器...")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 1. 一次性获取所有需要的数据
 | 
				
			||||||
 | 
						runnablePlans, invalidPlanIDs, pendingTasks, err := m.getRefreshData()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		m.logger.Errorf("[严重] 获取计划失败, 错误: %v", err)
 | 
							return fmt.Errorf("获取刷新数据失败: %w", err)
 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 获取触发任务
 | 
						// 2. 清理所有与失效计划相关的待执行任务
 | 
				
			||||||
	task, err := m.planRepo.FindPlanAnalysisTaskByParamsPlanID(planID)
 | 
						if err := m.cleanupInvalidTasks(invalidPlanIDs, pendingTasks); err != nil {
 | 
				
			||||||
	if err != nil {
 | 
							// 仅记录错误,清理失败不应阻止新任务的添加
 | 
				
			||||||
		m.logger.Errorf("[严重] 获取计划解析任务失败, 错误: %v", err)
 | 
							m.logger.Errorf("清理无效任务时出错: %v", err)
 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 写入执行日志
 | 
						// 3. 添加或更新触发器
 | 
				
			||||||
	taskLog := &models.TaskExecutionLog{
 | 
						if err := m.addOrUpdateTriggers(runnablePlans, pendingTasks); err != nil {
 | 
				
			||||||
		TaskID: task.ID,
 | 
							return fmt.Errorf("添加或更新触发器时出错: %w", err)
 | 
				
			||||||
		Status: models.ExecutionStatusWaiting,
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if err := m.executionLogRepo.CreateTaskExecutionLogsInBatch([]*models.TaskExecutionLog{taskLog}); err != nil {
 | 
					 | 
				
			||||||
		m.logger.Errorf("[严重] 创建任务执行日志失败, 错误: %v", err)
 | 
					 | 
				
			||||||
		return err
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 写入待执行队列
 | 
						m.logger.Info("计划任务管理器同步完成.")
 | 
				
			||||||
	next, err := utils.GetNextCronTime(plan.CronExpression)
 | 
						return nil
 | 
				
			||||||
	if err != nil {
 | 
					}
 | 
				
			||||||
		m.logger.Errorf("[严重] 执行时间解析失败, 错误: %v", err)
 | 
					
 | 
				
			||||||
		return err
 | 
					// CreateOrUpdateTrigger 为给定的 planID 创建其关联的触发任务。
 | 
				
			||||||
	}
 | 
					// 这个方法是幂等的:如果一个有效的触发器已存在,它将不会重复创建。
 | 
				
			||||||
	pendingTask := &models.PendingTask{
 | 
					// 关键修改:如果触发器已存在,会根据计划类型更新其执行时间。
 | 
				
			||||||
		TaskID:             task.ID,
 | 
					func (m *AnalysisPlanTaskManager) CreateOrUpdateTrigger(planID uint) error {
 | 
				
			||||||
		ExecuteAt:          next,
 | 
						m.mu.Lock()
 | 
				
			||||||
		TaskExecutionLogID: taskLog.ID,
 | 
						defer m.mu.Unlock()
 | 
				
			||||||
	}
 | 
					
 | 
				
			||||||
	err = m.pendingTaskRepo.CreatePendingTasksInBatch([]*models.PendingTask{pendingTask})
 | 
						// 检查计划是否可执行
 | 
				
			||||||
	if err != nil {
 | 
						plan, err := m.planRepo.GetBasicPlanByID(planID)
 | 
				
			||||||
		m.logger.Errorf("[严重] 创建待执行任务失败, 错误: %v", err)
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return fmt.Errorf("获取计划基本信息失败: %w", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						if plan.Status != models.PlanStatusEnabled {
 | 
				
			||||||
	m.logger.Infof("成功为 Plan %d 创建/更新了下一次的触发任务,执行时间: %v", planID, next)
 | 
							return fmt.Errorf("计划 #%d 当前状态为 '%d',无法创建或更新触发器", planID, plan.Status)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 查找现有触发器
 | 
				
			||||||
 | 
						existingTrigger, err := m.pendingTaskRepo.FindPendingTriggerByPlanID(planID)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("查找现有触发器失败: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 如果触发器已存在,则根据计划类型更新其执行时间
 | 
				
			||||||
 | 
						if existingTrigger != nil {
 | 
				
			||||||
 | 
							var expectedExecuteAt time.Time
 | 
				
			||||||
 | 
							if plan.ExecutionType == models.PlanExecutionTypeManual {
 | 
				
			||||||
 | 
								// 手动计划,如果再次触发,则立即执行
 | 
				
			||||||
 | 
								expectedExecuteAt = time.Now()
 | 
				
			||||||
 | 
							} else { // 自动计划
 | 
				
			||||||
 | 
								// 自动计划,根据 Cron 表达式计算下一次执行时间
 | 
				
			||||||
 | 
								next, err := utils.GetNextCronTime(plan.CronExpression)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									m.logger.Errorf("为计划 #%d 解析Cron表达式失败,无法更新触发器: %v", plan.ID, err)
 | 
				
			||||||
 | 
									return fmt.Errorf("解析 Cron 表达式失败: %w", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								expectedExecuteAt = next
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 如果计算出的执行时间与当前待执行任务的时间不一致,则更新
 | 
				
			||||||
 | 
							if !existingTrigger.ExecuteAt.Equal(expectedExecuteAt) {
 | 
				
			||||||
 | 
								m.logger.Infof("计划 #%d 的执行时间已变更,正在更新触发器 #%d 的执行时间从 %v 到 %v...", plan.ID, existingTrigger.ID, existingTrigger.ExecuteAt, expectedExecuteAt)
 | 
				
			||||||
 | 
								if err := m.pendingTaskRepo.UpdatePendingTaskExecuteAt(existingTrigger.ID, expectedExecuteAt); err != nil {
 | 
				
			||||||
 | 
									m.logger.Errorf("更新触发器 #%d 的执行时间失败: %v", existingTrigger.ID, err)
 | 
				
			||||||
 | 
									return fmt.Errorf("更新触发器执行时间失败: %w", err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								m.logger.Infof("计划 #%d 的触发器已存在且执行时间无需更新。", plan.ID)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return nil // 触发器已存在且已处理更新,直接返回
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 如果触发器不存在,则创建新的触发器
 | 
				
			||||||
 | 
						m.logger.Infof("为计划 #%d 创建新的触发器...", planID)
 | 
				
			||||||
 | 
						return m.createTriggerTask(plan)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// EnsureAnalysisTaskDefinition 确保计划的分析任务定义存在于 tasks 表中。
 | 
				
			||||||
 | 
					// 如果不存在,则会自动创建。此方法不涉及待执行队列。
 | 
				
			||||||
 | 
					func (m *AnalysisPlanTaskManager) EnsureAnalysisTaskDefinition(planID uint) error {
 | 
				
			||||||
 | 
						m.mu.Lock()
 | 
				
			||||||
 | 
						defer m.mu.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						plan, err := m.planRepo.GetBasicPlanByID(planID)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("确保分析任务定义失败:获取计划 #%d 基本信息时出错: %w", planID, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						analysisTask, err := m.planRepo.FindPlanAnalysisTaskByPlanID(plan.ID)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("确保分析任务定义失败:查找计划 #%d 的分析任务时出错: %w", plan.ID, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if analysisTask == nil {
 | 
				
			||||||
 | 
							m.logger.Infof("未找到计划 #%d 关联的 'plan_analysis' 任务定义,将自动创建...", plan.ID)
 | 
				
			||||||
 | 
							_, err := m.planRepo.CreatePlanAnalysisTask(plan) // CreatePlanAnalysisTask returns *models.Task, error
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return fmt.Errorf("自动创建 'plan_analysis' 任务定义失败: %w", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							m.logger.Infof("已成功为计划 #%d 创建 'plan_analysis' 任务定义。", plan.ID)
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							m.logger.Infof("计划 #%d 的 'plan_analysis' 任务定义已存在。", plan.ID)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// --- 内部私有方法 ---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// getRefreshData 从数据库获取刷新所需的所有数据。
 | 
				
			||||||
 | 
					func (m *AnalysisPlanTaskManager) getRefreshData() (runnablePlans []*models.Plan, invalidPlanIDs []uint, pendingTasks []models.PendingTask, err error) {
 | 
				
			||||||
 | 
						runnablePlans, err = m.planRepo.FindRunnablePlans()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							m.logger.Errorf("获取可执行计划列表失败: %v", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						invalidPlans, err := m.planRepo.FindDisabledAndStoppedPlans()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							m.logger.Errorf("获取失效计划列表失败: %v", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						invalidPlanIDs = make([]uint, len(invalidPlans))
 | 
				
			||||||
 | 
						for i, p := range invalidPlans {
 | 
				
			||||||
 | 
							invalidPlanIDs[i] = p.ID
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pendingTasks, err = m.pendingTaskRepo.FindAllPendingTasks()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							m.logger.Errorf("获取所有待执行任务失败: %v", err)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// cleanupInvalidTasks 清理所有与失效计划相关的待执行任务。
 | 
				
			||||||
 | 
					func (m *AnalysisPlanTaskManager) cleanupInvalidTasks(invalidPlanIDs []uint, allPendingTasks []models.PendingTask) error {
 | 
				
			||||||
 | 
						if len(invalidPlanIDs) == 0 {
 | 
				
			||||||
 | 
							return nil // 没有需要清理的计划
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						invalidPlanIDSet := make(map[uint]struct{}, len(invalidPlanIDs))
 | 
				
			||||||
 | 
						for _, id := range invalidPlanIDs {
 | 
				
			||||||
 | 
							invalidPlanIDSet[id] = struct{}{}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var tasksToDeleteIDs []uint
 | 
				
			||||||
 | 
						var logsToCancelIDs []uint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, pt := range allPendingTasks {
 | 
				
			||||||
 | 
							if pt.Task == nil { // 防御性编程,确保 Task 被预加载
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if _, isInvalid := invalidPlanIDSet[pt.Task.PlanID]; isInvalid {
 | 
				
			||||||
 | 
								tasksToDeleteIDs = append(tasksToDeleteIDs, pt.ID)
 | 
				
			||||||
 | 
								logsToCancelIDs = append(logsToCancelIDs, pt.TaskExecutionLogID)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if len(tasksToDeleteIDs) == 0 {
 | 
				
			||||||
 | 
							return nil // 没有找到需要清理的任务
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						m.logger.Infof("准备从待执行队列中清理 %d 个与失效计划相关的任务...", len(tasksToDeleteIDs))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 批量删除待执行任务
 | 
				
			||||||
 | 
						if err := m.pendingTaskRepo.DeletePendingTasksByIDs(tasksToDeleteIDs); err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("批量删除待执行任务失败: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 批量更新相关执行日志状态为“已取消”
 | 
				
			||||||
 | 
						if err := m.executionLogRepo.UpdateLogStatusByIDs(logsToCancelIDs, models.ExecutionStatusCancelled); err != nil {
 | 
				
			||||||
 | 
							// 这是一个非关键性错误,只记录日志
 | 
				
			||||||
 | 
							m.logger.Warnf("批量更新日志状态为 'Cancelled' 失败: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// addOrUpdateTriggers 检查、更新或创建触发器。
 | 
				
			||||||
 | 
					func (m *AnalysisPlanTaskManager) addOrUpdateTriggers(runnablePlans []*models.Plan, allPendingTasks []models.PendingTask) error {
 | 
				
			||||||
 | 
						// 创建一个映射,存放所有已在队列中的计划触发器
 | 
				
			||||||
 | 
						pendingTriggersMap := make(map[uint]models.PendingTask)
 | 
				
			||||||
 | 
						for _, pt := range allPendingTasks {
 | 
				
			||||||
 | 
							if pt.Task != nil && pt.Task.Type == models.TaskPlanAnalysis {
 | 
				
			||||||
 | 
								pendingTriggersMap[pt.Task.PlanID] = pt
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, plan := range runnablePlans {
 | 
				
			||||||
 | 
							existingTrigger, exists := pendingTriggersMap[plan.ID]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if exists {
 | 
				
			||||||
 | 
								// --- 新增逻辑:检查并更新现有触发器 ---
 | 
				
			||||||
 | 
								// 只对自动计划检查时间更新
 | 
				
			||||||
 | 
								if plan.ExecutionType == models.PlanExecutionTypeAutomatic {
 | 
				
			||||||
 | 
									next, err := utils.GetNextCronTime(plan.CronExpression)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										m.logger.Errorf("为计划 #%d 解析Cron表达式失败,跳过更新: %v", plan.ID, err)
 | 
				
			||||||
 | 
										continue
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									// 如果数据库中记录的执行时间与根据当前Cron表达式计算出的下一次时间不一致,则更新
 | 
				
			||||||
 | 
									if !existingTrigger.ExecuteAt.Equal(next) {
 | 
				
			||||||
 | 
										m.logger.Infof("计划 #%d 的执行时间已变更,正在更新触发器 #%d 的执行时间从 %v 到 %v...", plan.ID, existingTrigger.ID, existingTrigger.ExecuteAt, next)
 | 
				
			||||||
 | 
										if err := m.pendingTaskRepo.UpdatePendingTaskExecuteAt(existingTrigger.ID, next); err != nil {
 | 
				
			||||||
 | 
											m.logger.Errorf("更新触发器 #%d 的执行时间失败: %v", existingTrigger.ID, err)
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								// --- 原有逻辑:为缺失的计划创建新触发器 ---
 | 
				
			||||||
 | 
								m.logger.Infof("发现应执行但队列中缺失的计划 #%d,正在为其创建触发器...", plan.ID)
 | 
				
			||||||
 | 
								if err := m.createTriggerTask(plan); err != nil {
 | 
				
			||||||
 | 
									m.logger.Errorf("为计划 #%d 创建触发器失败: %v", plan.ID, err)
 | 
				
			||||||
 | 
									// 继续处理下一个,不因单点失败而中断
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// createTriggerTask 是创建触发器任务的内部核心逻辑。
 | 
				
			||||||
 | 
					func (m *AnalysisPlanTaskManager) createTriggerTask(plan *models.Plan) error {
 | 
				
			||||||
 | 
						analysisTask, err := m.planRepo.FindPlanAnalysisTaskByPlanID(plan.ID)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("查找计划分析任务失败: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// --- 如果触发器任务定义不存在,则自动创建 ---
 | 
				
			||||||
 | 
						if analysisTask == nil {
 | 
				
			||||||
 | 
							m.logger.Warnf("未找到计划 #%d 关联的 'plan_analysis' 任务定义,将自动创建...", plan.ID)
 | 
				
			||||||
 | 
							newAnalysisTask, err := m.planRepo.CreatePlanAnalysisTask(plan)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return fmt.Errorf("自动创建 'plan_analysis' 任务定义失败: %w", err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							analysisTask = newAnalysisTask
 | 
				
			||||||
 | 
							m.logger.Infof("已成功为计划 #%d 创建 'plan_analysis' 任务定义 (ID: %d)", plan.ID, analysisTask.ID)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var executeAt time.Time
 | 
				
			||||||
 | 
						if plan.ExecutionType == models.PlanExecutionTypeManual {
 | 
				
			||||||
 | 
							executeAt = time.Now()
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							next, err := utils.GetNextCronTime(plan.CronExpression)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return fmt.Errorf("解析 Cron 表达式 '%s' 失败: %w", plan.CronExpression, err)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							executeAt = next
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						taskLog := &models.TaskExecutionLog{
 | 
				
			||||||
 | 
							TaskID: analysisTask.ID,
 | 
				
			||||||
 | 
							Status: models.ExecutionStatusWaiting,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := m.executionLogRepo.CreateTaskExecutionLog(taskLog); err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("创建任务执行日志失败: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						pendingTask := &models.PendingTask{
 | 
				
			||||||
 | 
							TaskID:             analysisTask.ID,
 | 
				
			||||||
 | 
							ExecuteAt:          executeAt,
 | 
				
			||||||
 | 
							TaskExecutionLogID: taskLog.ID,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := m.pendingTaskRepo.CreatePendingTask(pendingTask); err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("创建待执行任务失败: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						m.logger.Infof("成功为计划 #%d 创建触发器 (任务ID: %d),执行时间: %v", plan.ID, analysisTask.ID, executeAt)
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
package task
 | 
					package task
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"encoding/json"
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
	"sync"
 | 
						"sync"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
@@ -112,10 +112,9 @@ type Scheduler struct {
 | 
				
			|||||||
	progressTracker         *ProgressTracker
 | 
						progressTracker         *ProgressTracker
 | 
				
			||||||
	taskFactory             func(taskType models.TaskType) Task // 调度器需要注入一个任务工厂,用于创建任务实例
 | 
						taskFactory             func(taskType models.TaskType) Task // 调度器需要注入一个任务工厂,用于创建任务实例
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	pool   *ants.Pool // 使用 ants 协程池来管理并发
 | 
						pool     *ants.Pool // 使用 ants 协程池来管理并发
 | 
				
			||||||
	wg     sync.WaitGroup
 | 
						wg       sync.WaitGroup
 | 
				
			||||||
	ctx    context.Context
 | 
						stopChan chan struct{} // 用于停止主循环的信号通道
 | 
				
			||||||
	cancel context.CancelFunc
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewScheduler 创建一个新的调度器实例
 | 
					// NewScheduler 创建一个新的调度器实例
 | 
				
			||||||
@@ -128,8 +127,6 @@ func NewScheduler(
 | 
				
			|||||||
	logger *logs.Logger,
 | 
						logger *logs.Logger,
 | 
				
			||||||
	interval time.Duration,
 | 
						interval time.Duration,
 | 
				
			||||||
	numWorkers int) *Scheduler {
 | 
						numWorkers int) *Scheduler {
 | 
				
			||||||
	ctx, cancel := context.WithCancel(context.Background())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return &Scheduler{
 | 
						return &Scheduler{
 | 
				
			||||||
		pendingTaskRepo:         pendingTaskRepo,
 | 
							pendingTaskRepo:         pendingTaskRepo,
 | 
				
			||||||
		executionLogRepo:        executionLogRepo,
 | 
							executionLogRepo:        executionLogRepo,
 | 
				
			||||||
@@ -140,8 +137,7 @@ func NewScheduler(
 | 
				
			|||||||
		workers:                 numWorkers,
 | 
							workers:                 numWorkers,
 | 
				
			||||||
		progressTracker:         NewProgressTracker(),
 | 
							progressTracker:         NewProgressTracker(),
 | 
				
			||||||
		taskFactory:             taskFactory,
 | 
							taskFactory:             taskFactory,
 | 
				
			||||||
		ctx:                     ctx,
 | 
							stopChan:                make(chan struct{}), // 初始化停止信号通道
 | 
				
			||||||
		cancel:                  cancel,
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -164,9 +160,9 @@ func (s *Scheduler) Start() {
 | 
				
			|||||||
// Stop 优雅地停止调度器
 | 
					// Stop 优雅地停止调度器
 | 
				
			||||||
func (s *Scheduler) Stop() {
 | 
					func (s *Scheduler) Stop() {
 | 
				
			||||||
	s.logger.Warnf("正在停止任务调度器...")
 | 
						s.logger.Warnf("正在停止任务调度器...")
 | 
				
			||||||
	s.cancel()       // 1. 发出取消信号,停止主循环
 | 
						close(s.stopChan) // 1. 发出停止信号,停止主循环
 | 
				
			||||||
	s.wg.Wait()      // 2. 等待主循环完成
 | 
						s.wg.Wait()       // 2. 等待主循环完成
 | 
				
			||||||
	s.pool.Release() // 3. 释放 ants 池 (等待所有已提交的任务执行完毕)
 | 
						s.pool.Release()  // 3. 释放 ants 池 (等待所有已提交的任务执行完毕)
 | 
				
			||||||
	s.logger.Warnf("任务调度器已安全停止")
 | 
						s.logger.Warnf("任务调度器已安全停止")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -178,9 +174,11 @@ func (s *Scheduler) run() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	for {
 | 
						for {
 | 
				
			||||||
		select {
 | 
							select {
 | 
				
			||||||
		case <-s.ctx.Done():
 | 
							case <-s.stopChan:
 | 
				
			||||||
 | 
								// 收到停止信号,退出循环
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		case <-ticker.C:
 | 
							case <-ticker.C:
 | 
				
			||||||
 | 
								// 定时触发任务认领和提交
 | 
				
			||||||
			go s.claimAndSubmit()
 | 
								go s.claimAndSubmit()
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -236,7 +234,7 @@ func (s *Scheduler) handleRequeue(planExecutionLogID uint, taskToRequeue *models
 | 
				
			|||||||
	s.logger.Warnf("任务 (原始ID: %d) 已成功重新入队,并已释放计划 %d 的锁。", taskToRequeue.ID, planExecutionLogID)
 | 
						s.logger.Warnf("任务 (原始ID: %d) 已成功重新入队,并已释放计划 %d 的锁。", taskToRequeue.ID, planExecutionLogID)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// processTask 处理单个任务的逻辑 (当前为占位符)
 | 
					// processTask 处理单个任务的逻辑
 | 
				
			||||||
func (s *Scheduler) processTask(claimedLog *models.TaskExecutionLog) {
 | 
					func (s *Scheduler) processTask(claimedLog *models.TaskExecutionLog) {
 | 
				
			||||||
	s.logger.Warnf("开始处理任务, 日志ID: %d, 任务ID: %d, 任务名称: %s",
 | 
						s.logger.Warnf("开始处理任务, 日志ID: %d, 任务ID: %d, 任务名称: %s",
 | 
				
			||||||
		claimedLog.ID, claimedLog.TaskID, claimedLog.Task.Name)
 | 
							claimedLog.ID, claimedLog.TaskID, claimedLog.Task.Name)
 | 
				
			||||||
@@ -256,10 +254,40 @@ func (s *Scheduler) processTask(claimedLog *models.TaskExecutionLog) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// 任务计数器校验, Plan的任务全部执行完成后需要插入一个新的PlanAnalysisTask用于触发下一次Plan的执行
 | 
						// 任务计数器校验, Plan的任务全部执行完成后需要插入一个新的PlanAnalysisTask用于触发下一次Plan的执行
 | 
				
			||||||
	if s.progressTracker.IsPlanOver(claimedLog.PlanExecutionLogID) {
 | 
						if s.progressTracker.IsPlanOver(claimedLog.PlanExecutionLogID) {
 | 
				
			||||||
		// 调用共享的 Manager 来处理触发器更新逻辑
 | 
							// --- 新增逻辑:更新计划执行次数并判断是否需要触发下一次执行 ---
 | 
				
			||||||
		err = s.analysisPlanTaskManager.CreateOrUpdateTrigger(s.ctx, claimedLog.Task.PlanID)
 | 
							planID := claimedLog.Task.PlanID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 获取计划的最新数据
 | 
				
			||||||
 | 
							plan, err := s.planRepo.GetBasicPlanByID(planID)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			s.logger.Errorf("[严重] 创建计划分析任务失败, 当前Plan(%v)将无法进行下次触发, 错误: %v", claimedLog.Task.PlanID, err)
 | 
								s.logger.Errorf("获取计划 %d 的基本信息失败: %v", planID, err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 更新计划的执行计数器
 | 
				
			||||||
 | 
							plan.ExecuteCount++
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 如果是自动计划且达到执行次数上限,则更新计划状态为已停止
 | 
				
			||||||
 | 
							if (plan.ExecutionType == models.PlanExecutionTypeAutomatic && plan.ExecuteNum > 0 && plan.ExecuteCount >= plan.ExecuteNum) || plan.ExecutionType == models.PlanExecutionTypeManual {
 | 
				
			||||||
 | 
								plan.Status = models.PlanStatusStopeed
 | 
				
			||||||
 | 
								s.logger.Infof("计划 %d (自动执行) 已达到最大执行次数 %d,状态更新为 '执行完毕'。", planID, plan.ExecuteNum)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 保存更新后的计划状态和执行计数
 | 
				
			||||||
 | 
							if err := s.planRepo.UpdatePlan(plan); err != nil { // UpdatePlan 可以更新整个 Plan 对象
 | 
				
			||||||
 | 
								s.logger.Errorf("更新计划 %d 的执行计数和状态失败: %v", planID, err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 更新计划执行日志状态为完成
 | 
				
			||||||
 | 
							if err := s.executionLogRepo.UpdatePlanExecutionLogStatus(claimedLog.PlanExecutionLogID, models.ExecutionStatusCompleted); err != nil {
 | 
				
			||||||
 | 
								s.logger.Errorf("更新计划执行日志 %d 状态为 '完成' 失败: %v", claimedLog.PlanExecutionLogID, err)
 | 
				
			||||||
 | 
								// 这是一个非阻塞性错误,不中断后续流程
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 调用共享的 Manager 来处理触发器更新逻辑 (Manager 会根据最新的 Plan 状态决定是否创建新触发器)
 | 
				
			||||||
 | 
							if err := s.analysisPlanTaskManager.CreateOrUpdateTrigger(planID); err != nil {
 | 
				
			||||||
 | 
								s.logger.Errorf("为计划 %d 创建/更新触发器失败: %v", planID, err)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -301,8 +329,18 @@ func (s *Scheduler) runTask(claimedLog *models.TaskExecutionLog) error {
 | 
				
			|||||||
// analysisPlan 解析Plan并将解析出的Task列表插入待执行队列中
 | 
					// analysisPlan 解析Plan并将解析出的Task列表插入待执行队列中
 | 
				
			||||||
func (s *Scheduler) analysisPlan(claimedLog *models.TaskExecutionLog) error {
 | 
					func (s *Scheduler) analysisPlan(claimedLog *models.TaskExecutionLog) error {
 | 
				
			||||||
	// 创建Plan执行记录
 | 
						// 创建Plan执行记录
 | 
				
			||||||
 | 
						// 从任务的 Parameters 中解析出真实的 PlanID
 | 
				
			||||||
 | 
						var params struct {
 | 
				
			||||||
 | 
							PlanID uint `json:"plan_id"`
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if err := json.Unmarshal(claimedLog.Task.Parameters, ¶ms); err != nil {
 | 
				
			||||||
 | 
							s.logger.Errorf("解析任务参数中的计划ID失败,日志ID: %d, 错误: %v", claimedLog.ID, err)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						realPlanID := params.PlanID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	planLog := &models.PlanExecutionLog{
 | 
						planLog := &models.PlanExecutionLog{
 | 
				
			||||||
		PlanID:    claimedLog.Task.PlanID,
 | 
							PlanID:    realPlanID, // 使用从参数中解析出的真实 PlanID
 | 
				
			||||||
		Status:    models.ExecutionStatusStarted,
 | 
							Status:    models.ExecutionStatusStarted,
 | 
				
			||||||
		StartedAt: time.Now(),
 | 
							StartedAt: time.Now(),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -312,7 +350,7 @@ func (s *Scheduler) analysisPlan(claimedLog *models.TaskExecutionLog) error {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 解析出Task列表
 | 
						// 解析出Task列表
 | 
				
			||||||
	tasks, err := s.planRepo.FlattenPlanTasks(claimedLog.Task.PlanID)
 | 
						tasks, err := s.planRepo.FlattenPlanTasks(realPlanID)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		s.logger.Errorf("[严重] 解析计划失败, 日志ID: %d, 错误: %v", claimedLog.ID, err)
 | 
							s.logger.Errorf("[严重] 解析计划失败, 日志ID: %d, 错误: %v", claimedLog.ID, err)
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
@@ -320,12 +358,12 @@ func (s *Scheduler) analysisPlan(claimedLog *models.TaskExecutionLog) error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// 写入执行历史
 | 
						// 写入执行历史
 | 
				
			||||||
	taskLogs := make([]*models.TaskExecutionLog, len(tasks))
 | 
						taskLogs := make([]*models.TaskExecutionLog, len(tasks))
 | 
				
			||||||
	for _, task := range tasks {
 | 
						for i, task := range tasks {
 | 
				
			||||||
		taskLogs = append(taskLogs, &models.TaskExecutionLog{
 | 
							taskLogs[i] = &models.TaskExecutionLog{
 | 
				
			||||||
			PlanExecutionLogID: planLog.ID,
 | 
								PlanExecutionLogID: planLog.ID,
 | 
				
			||||||
			TaskID:             task.ID,
 | 
								TaskID:             task.ID,
 | 
				
			||||||
			Status:             models.ExecutionStatusWaiting,
 | 
								Status:             models.ExecutionStatusWaiting,
 | 
				
			||||||
		})
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	err = s.executionLogRepo.CreateTaskExecutionLogsInBatch(taskLogs)
 | 
						err = s.executionLogRepo.CreateTaskExecutionLogsInBatch(taskLogs)
 | 
				
			||||||
@@ -337,13 +375,13 @@ func (s *Scheduler) analysisPlan(claimedLog *models.TaskExecutionLog) error {
 | 
				
			|||||||
	// 写入待执行队列
 | 
						// 写入待执行队列
 | 
				
			||||||
	pendingTasks := make([]*models.PendingTask, len(tasks))
 | 
						pendingTasks := make([]*models.PendingTask, len(tasks))
 | 
				
			||||||
	for i, task := range tasks {
 | 
						for i, task := range tasks {
 | 
				
			||||||
		pendingTasks = append(pendingTasks, &models.PendingTask{
 | 
							pendingTasks[i] = &models.PendingTask{
 | 
				
			||||||
			TaskID:             task.ID,
 | 
								TaskID:             task.ID,
 | 
				
			||||||
			TaskExecutionLogID: pendingTasks[i].ID,
 | 
								TaskExecutionLogID: taskLogs[i].ID, // 使用正确的 TaskExecutionLogID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// 待执行队列是通过任务触发时间排序的, 且只要在调度器获取的时间点之前的都可以被触发
 | 
								// 待执行队列是通过任务触发时间排序的, 且只要在调度器获取的时间点之前的都可以被触发
 | 
				
			||||||
			ExecuteAt: time.Now().Add(time.Duration(i) * time.Second),
 | 
								ExecuteAt: time.Now().Add(time.Duration(i) * time.Second),
 | 
				
			||||||
		})
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	err = s.pendingTaskRepo.CreatePendingTasksInBatch(pendingTasks)
 | 
						err = s.pendingTaskRepo.CreatePendingTasksInBatch(pendingTasks)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
@@ -352,7 +390,7 @@ func (s *Scheduler) analysisPlan(claimedLog *models.TaskExecutionLog) error {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 将Task列表加入待执行队列中
 | 
						// 将Task列表加入待执行队列中
 | 
				
			||||||
	s.progressTracker.AddNewPlan(claimedLog.PlanExecutionLogID, len(tasks))
 | 
						s.progressTracker.AddNewPlan(planLog.ID, len(tasks))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,6 +25,12 @@ type Application struct {
 | 
				
			|||||||
	Storage  database.Storage
 | 
						Storage  database.Storage
 | 
				
			||||||
	Executor *task.Scheduler
 | 
						Executor *task.Scheduler
 | 
				
			||||||
	API      *api.API // 添加 API 对象
 | 
						API      *api.API // 添加 API 对象
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 新增的仓库和管理器字段,以便在 initializePendingTasks 中访问
 | 
				
			||||||
 | 
						planRepo                repository.PlanRepository
 | 
				
			||||||
 | 
						pendingTaskRepo         repository.PendingTaskRepository
 | 
				
			||||||
 | 
						executionLogRepo        repository.ExecutionLogRepository
 | 
				
			||||||
 | 
						analysisPlanTaskManager *task.AnalysisPlanTaskManager
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// NewApplication 创建并初始化一个新的 Application 实例。
 | 
					// NewApplication 创建并初始化一个新的 Application 实例。
 | 
				
			||||||
@@ -82,6 +88,11 @@ func NewApplication(configPath string) (*Application, error) {
 | 
				
			|||||||
		Storage:  storage,
 | 
							Storage:  storage,
 | 
				
			||||||
		Executor: executor,
 | 
							Executor: executor,
 | 
				
			||||||
		API:      apiServer,
 | 
							API:      apiServer,
 | 
				
			||||||
 | 
							// 填充新增的字段
 | 
				
			||||||
 | 
							planRepo:                planRepo,
 | 
				
			||||||
 | 
							pendingTaskRepo:         pendingTaskRepo,
 | 
				
			||||||
 | 
							executionLogRepo:        executionLogRepo,
 | 
				
			||||||
 | 
							analysisPlanTaskManager: analysisPlanTaskManager,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return app, nil
 | 
						return app, nil
 | 
				
			||||||
@@ -91,6 +102,17 @@ func NewApplication(configPath string) (*Application, error) {
 | 
				
			|||||||
func (app *Application) Start() error {
 | 
					func (app *Application) Start() error {
 | 
				
			||||||
	app.Logger.Info("应用启动中...")
 | 
						app.Logger.Info("应用启动中...")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// --- 新增逻辑:初始化待执行任务列表 ---
 | 
				
			||||||
 | 
						if err := app.initializePendingTasks(
 | 
				
			||||||
 | 
							app.planRepo,                // 传入 planRepo
 | 
				
			||||||
 | 
							app.pendingTaskRepo,         // 传入 pendingTaskRepo
 | 
				
			||||||
 | 
							app.executionLogRepo,        // 传入 executionLogRepo
 | 
				
			||||||
 | 
							app.analysisPlanTaskManager, // 传入 analysisPlanTaskManager
 | 
				
			||||||
 | 
							app.Logger,                  // 传入 logger
 | 
				
			||||||
 | 
						); err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("初始化待执行任务列表失败: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 启动任务执行器
 | 
						// 启动任务执行器
 | 
				
			||||||
	app.Executor.Start()
 | 
						app.Executor.Start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -128,6 +150,104 @@ func (app *Application) Stop() error {
 | 
				
			|||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// initializePendingTasks 在应用启动时清理并刷新待执行任务列表。
 | 
				
			||||||
 | 
					func (app *Application) initializePendingTasks(
 | 
				
			||||||
 | 
						planRepo repository.PlanRepository,
 | 
				
			||||||
 | 
						pendingTaskRepo repository.PendingTaskRepository,
 | 
				
			||||||
 | 
						executionLogRepo repository.ExecutionLogRepository,
 | 
				
			||||||
 | 
						analysisPlanTaskManager *task.AnalysisPlanTaskManager,
 | 
				
			||||||
 | 
						logger *logs.Logger,
 | 
				
			||||||
 | 
					) error {
 | 
				
			||||||
 | 
						logger.Info("开始初始化待执行任务列表...")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 阶段一:修正因崩溃导致状态不一致的固定次数计划
 | 
				
			||||||
 | 
						logger.Info("阶段一:开始修正因崩溃导致状态不一致的固定次数计划...")
 | 
				
			||||||
 | 
						plansToCorrect, err := planRepo.FindPlansWithPendingTasks()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("查找需要修正的计划失败: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, plan := range plansToCorrect {
 | 
				
			||||||
 | 
							logger.Infof("发现需要修正的计划 #%d (名称: %s)。", plan.ID, plan.Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 更新计划的执行计数
 | 
				
			||||||
 | 
							plan.ExecuteCount++
 | 
				
			||||||
 | 
							logger.Infof("计划 #%d 执行计数已从 %d 更新为 %d。", plan.ID, plan.ExecuteCount-1, plan.ExecuteCount)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if plan.ExecutionType == models.PlanExecutionTypeManual ||
 | 
				
			||||||
 | 
								(plan.ExecutionType == models.PlanExecutionTypeAutomatic && plan.ExecuteCount >= plan.ExecuteNum) {
 | 
				
			||||||
 | 
								// 更新计划状态为已停止
 | 
				
			||||||
 | 
								plan.Status = models.PlanStatusStopeed
 | 
				
			||||||
 | 
								logger.Infof("计划 #%d 状态已更新为 '执行完毕'。", plan.ID)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// 保存更新后的计划
 | 
				
			||||||
 | 
							if err := planRepo.UpdatePlan(plan); err != nil {
 | 
				
			||||||
 | 
								logger.Errorf("修正计划 #%d 状态失败: %v", plan.ID, err)
 | 
				
			||||||
 | 
								// 这是一个非阻塞性错误,继续处理其他计划
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						logger.Info("阶段一:固定次数计划修正完成。")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 阶段二:清理所有待执行任务和相关日志
 | 
				
			||||||
 | 
						logger.Info("阶段二:开始清理所有待执行任务和相关日志...")
 | 
				
			||||||
 | 
						pendingTasks, err := pendingTaskRepo.FindAllPendingTasks()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("获取待执行任务失败: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var taskLogIDsToCancel []uint
 | 
				
			||||||
 | 
						var planLogIDsToFail []uint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, pt := range pendingTasks {
 | 
				
			||||||
 | 
							// 确保 Task 和 TaskExecutionLog 已预加载
 | 
				
			||||||
 | 
							if pt.Task == nil || pt.TaskExecutionLog.ID == 0 { // TaskExecutionLog.ID为零说明没加载
 | 
				
			||||||
 | 
								logger.Warnf("待执行任务 %d 缺少关联的 Task 或 TaskExecutionLog,跳过处理。", pt.ID)
 | 
				
			||||||
 | 
								continue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 收集任务执行日志ID,所有未完成的任务都标记为取消
 | 
				
			||||||
 | 
							taskLogIDsToCancel = append(taskLogIDsToCancel, pt.TaskExecutionLog.ID)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// 收集计划执行日志ID
 | 
				
			||||||
 | 
							if pt.TaskExecutionLog.PlanExecutionLogID != 0 {
 | 
				
			||||||
 | 
								planLogIDsToFail = append(planLogIDsToFail, pt.TaskExecutionLog.PlanExecutionLogID)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 批量更新 TaskExecutionLog 状态为取消
 | 
				
			||||||
 | 
						if len(taskLogIDsToCancel) > 0 {
 | 
				
			||||||
 | 
							if err := executionLogRepo.UpdateLogStatusByIDs(taskLogIDsToCancel, models.ExecutionStatusCancelled); err != nil {
 | 
				
			||||||
 | 
								logger.Errorf("批量更新任务执行日志状态为取消失败: %v", err)
 | 
				
			||||||
 | 
								// 这是一个非阻塞性错误,继续执行
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 批量更新 PlanExecutionLog 状态为失败
 | 
				
			||||||
 | 
						if len(planLogIDsToFail) > 0 {
 | 
				
			||||||
 | 
							if err := executionLogRepo.UpdatePlanExecutionLogsStatusByIDs(planLogIDsToFail, models.ExecutionStatusFailed); err != nil {
 | 
				
			||||||
 | 
								logger.Errorf("批量更新计划执行日志状态为失败失败: %v", err)
 | 
				
			||||||
 | 
								// 这是一个非阻塞性错误,继续执行
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 清空待执行列表
 | 
				
			||||||
 | 
						if err := pendingTaskRepo.ClearAllPendingTasks(); err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("清空待执行任务列表失败: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						logger.Info("阶段二:待执行任务和相关日志清理完成。")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 阶段三:初始刷新
 | 
				
			||||||
 | 
						logger.Info("阶段三:开始刷新待执行列表...")
 | 
				
			||||||
 | 
						if err := analysisPlanTaskManager.Refresh(); err != nil {
 | 
				
			||||||
 | 
							return fmt.Errorf("刷新待执行任务列表失败: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						logger.Info("阶段三:待执行任务列表初始化完成。")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						logger.Info("待执行任务列表初始化完成。")
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// initStorage 封装了数据库的初始化、连接和迁移逻辑。
 | 
					// initStorage 封装了数据库的初始化、连接和迁移逻辑。
 | 
				
			||||||
func initStorage(cfg config.DatabaseConfig, logger *logs.Logger) (database.Storage, error) {
 | 
					func initStorage(cfg config.DatabaseConfig, logger *logs.Logger) (database.Storage, error) {
 | 
				
			||||||
	// 创建存储实例
 | 
						// 创建存储实例
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -70,7 +70,7 @@ type Device struct {
 | 
				
			|||||||
	gorm.Model
 | 
						gorm.Model
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Name 是设备的业务名称,应清晰可读,例如 "1号猪舍温度传感器" 或 "做料车间主控"
 | 
						// Name 是设备的业务名称,应清晰可读,例如 "1号猪舍温度传感器" 或 "做料车间主控"
 | 
				
			||||||
	Name string `gorm:"unique;not null" json:"name"`
 | 
						Name string `gorm:"not null" json:"name"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Type 是设备的高级类别,用于区分区域主控和普通设备。建立索引以优化按类型查询。
 | 
						// Type 是设备的高级类别,用于区分区域主控和普通设备。建立索引以优化按类型查询。
 | 
				
			||||||
	Type DeviceType `gorm:"not null;index" json:"type"`
 | 
						Type DeviceType `gorm:"not null;index" json:"type"`
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -38,6 +38,14 @@ const (
 | 
				
			|||||||
	ParamsPlanID = "plan_id"
 | 
						ParamsPlanID = "plan_id"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type PlanStatus uint8
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						PlanStatusEnabled  PlanStatus = 0 // 启用计划
 | 
				
			||||||
 | 
						PlanStatusDisabled PlanStatus = 1 // 禁用计划
 | 
				
			||||||
 | 
						PlanStatusStopeed  PlanStatus = 2 // 执行完毕
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Plan 代表系统中的一个计划,可以包含子计划或任务
 | 
					// Plan 代表系统中的一个计划,可以包含子计划或任务
 | 
				
			||||||
type Plan struct {
 | 
					type Plan struct {
 | 
				
			||||||
	gorm.Model
 | 
						gorm.Model
 | 
				
			||||||
@@ -45,6 +53,9 @@ type Plan struct {
 | 
				
			|||||||
	Name          string            `gorm:"not null" json:"name"`
 | 
						Name          string            `gorm:"not null" json:"name"`
 | 
				
			||||||
	Description   string            `json:"description"`
 | 
						Description   string            `json:"description"`
 | 
				
			||||||
	ExecutionType PlanExecutionType `gorm:"not null" json:"execution_type"`
 | 
						ExecutionType PlanExecutionType `gorm:"not null" json:"execution_type"`
 | 
				
			||||||
 | 
						Status        PlanStatus        `gorm:"default:0" json:"status"`        // 计划是否被启动
 | 
				
			||||||
 | 
						ExecuteNum    uint              `gorm:"default:0" json:"execute_num"`   // 计划预期执行次数
 | 
				
			||||||
 | 
						ExecuteCount  uint              `gorm:"default:0" json:"execute_count"` // 执行计数器
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 针对 PlanExecutionTypeAutomatic,使用 Cron 表达式定义调度规则
 | 
						// 针对 PlanExecutionTypeAutomatic,使用 Cron 表达式定义调度规则
 | 
				
			||||||
	CronExpression string `json:"cron_expression"`
 | 
						CronExpression string `json:"cron_expression"`
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,11 +16,13 @@ type PendingTask struct {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	// TaskID 使用 int 类型以容纳特殊的负数ID,代表系统任务
 | 
						// TaskID 使用 int 类型以容纳特殊的负数ID,代表系统任务
 | 
				
			||||||
	TaskID int `gorm:"index"`
 | 
						TaskID int `gorm:"index"`
 | 
				
			||||||
 | 
						// Task 字段,用于在代码中访问关联的任务详情
 | 
				
			||||||
 | 
						// GORM 会根据 TaskID 字段自动填充此关联
 | 
				
			||||||
 | 
						Task *Task `gorm:"foreignKey:TaskID"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ExecuteAt          time.Time `gorm:"index"`           // 任务执行时间
 | 
						ExecuteAt          time.Time `gorm:"index"`           // 任务执行时间
 | 
				
			||||||
	TaskExecutionLogID uint      `gorm:"unique;not null"` // 对应的执行历史记录ID
 | 
						TaskExecutionLogID uint      `gorm:"unique;not null"` // 对应的执行历史记录ID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// 关联关系定义
 | 
					 | 
				
			||||||
	// 通过 TaskExecutionLogID 关联到唯一的 TaskExecutionLog 记录
 | 
						// 通过 TaskExecutionLogID 关联到唯一的 TaskExecutionLog 记录
 | 
				
			||||||
	// ON DELETE CASCADE 确保如果日志被删除,这个待办任务也会被自动清理
 | 
						// ON DELETE CASCADE 确保如果日志被删除,这个待办任务也会被自动清理
 | 
				
			||||||
	TaskExecutionLog TaskExecutionLog `gorm:"foreignKey:TaskExecutionLogID;references:ID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
 | 
						TaskExecutionLog TaskExecutionLog `gorm:"foreignKey:TaskExecutionLogID;references:ID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,11 +8,22 @@ import (
 | 
				
			|||||||
// ExecutionLogRepository 定义了与执行日志交互的接口。
 | 
					// ExecutionLogRepository 定义了与执行日志交互的接口。
 | 
				
			||||||
// 这为服务层提供了一个清晰的契约,并允许在测试中轻松地进行模拟。
 | 
					// 这为服务层提供了一个清晰的契约,并允许在测试中轻松地进行模拟。
 | 
				
			||||||
type ExecutionLogRepository interface {
 | 
					type ExecutionLogRepository interface {
 | 
				
			||||||
 | 
						UpdateLogStatusByIDs(logIDs []uint, status models.ExecutionStatus) error
 | 
				
			||||||
 | 
						UpdateLogStatus(logID uint, status models.ExecutionStatus) error
 | 
				
			||||||
 | 
						CreateTaskExecutionLog(log *models.TaskExecutionLog) error
 | 
				
			||||||
	CreatePlanExecutionLog(log *models.PlanExecutionLog) error
 | 
						CreatePlanExecutionLog(log *models.PlanExecutionLog) error
 | 
				
			||||||
	UpdatePlanExecutionLog(log *models.PlanExecutionLog) error
 | 
						UpdatePlanExecutionLog(log *models.PlanExecutionLog) error
 | 
				
			||||||
	CreateTaskExecutionLogsInBatch(logs []*models.TaskExecutionLog) error
 | 
						CreateTaskExecutionLogsInBatch(logs []*models.TaskExecutionLog) error
 | 
				
			||||||
	UpdateTaskExecutionLog(log *models.TaskExecutionLog) error
 | 
						UpdateTaskExecutionLog(log *models.TaskExecutionLog) error
 | 
				
			||||||
	FindTaskExecutionLogByID(id uint) (*models.TaskExecutionLog, error)
 | 
						FindTaskExecutionLogByID(id uint) (*models.TaskExecutionLog, error)
 | 
				
			||||||
 | 
						// UpdatePlanExecutionLogStatus 更新计划执行日志的状态
 | 
				
			||||||
 | 
						UpdatePlanExecutionLogStatus(logID uint, status models.ExecutionStatus) error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// UpdatePlanExecutionLogsStatusByIDs 批量更新计划执行日志的状态
 | 
				
			||||||
 | 
						UpdatePlanExecutionLogsStatusByIDs(logIDs []uint, status models.ExecutionStatus) error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// FindIncompletePlanExecutionLogs 查找所有未完成的计划执行日志
 | 
				
			||||||
 | 
						FindIncompletePlanExecutionLogs() ([]models.PlanExecutionLog, error)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// gormExecutionLogRepository 是使用 GORM 的具体实现。
 | 
					// gormExecutionLogRepository 是使用 GORM 的具体实现。
 | 
				
			||||||
@@ -26,6 +37,23 @@ func NewGormExecutionLogRepository(db *gorm.DB) ExecutionLogRepository {
 | 
				
			|||||||
	return &gormExecutionLogRepository{db: db}
 | 
						return &gormExecutionLogRepository{db: db}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *gormExecutionLogRepository) UpdateLogStatusByIDs(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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *gormExecutionLogRepository) UpdateLogStatus(logID uint, status models.ExecutionStatus) error {
 | 
				
			||||||
 | 
						return r.db.Model(&models.TaskExecutionLog{}).Where("id = ?", logID).Update("status", status).Error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *gormExecutionLogRepository) CreateTaskExecutionLog(log *models.TaskExecutionLog) error {
 | 
				
			||||||
 | 
						return r.db.Create(log).Error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CreatePlanExecutionLog 为一次计划执行创建一条新的日志条目。
 | 
					// CreatePlanExecutionLog 为一次计划执行创建一条新的日志条目。
 | 
				
			||||||
func (r *gormExecutionLogRepository) CreatePlanExecutionLog(log *models.PlanExecutionLog) error {
 | 
					func (r *gormExecutionLogRepository) CreatePlanExecutionLog(log *models.PlanExecutionLog) error {
 | 
				
			||||||
	return r.db.Create(log).Error
 | 
						return r.db.Create(log).Error
 | 
				
			||||||
@@ -41,6 +69,9 @@ func (r *gormExecutionLogRepository) UpdatePlanExecutionLog(log *models.PlanExec
 | 
				
			|||||||
// CreateTaskExecutionLogsInBatch 在一次数据库调用中创建多个任务执行日志条目。
 | 
					// CreateTaskExecutionLogsInBatch 在一次数据库调用中创建多个任务执行日志条目。
 | 
				
			||||||
// 这是“预写日志”步骤的关键。
 | 
					// 这是“预写日志”步骤的关键。
 | 
				
			||||||
func (r *gormExecutionLogRepository) CreateTaskExecutionLogsInBatch(logs []*models.TaskExecutionLog) error {
 | 
					func (r *gormExecutionLogRepository) CreateTaskExecutionLogsInBatch(logs []*models.TaskExecutionLog) error {
 | 
				
			||||||
 | 
						if len(logs) == 0 {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	// GORM 的 Create 传入一个切片指针会执行批量插入。
 | 
						// GORM 的 Create 传入一个切片指针会执行批量插入。
 | 
				
			||||||
	return r.db.Create(&logs).Error
 | 
						return r.db.Create(&logs).Error
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -63,3 +94,23 @@ func (r *gormExecutionLogRepository) FindTaskExecutionLogByID(id uint) (*models.
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	return &log, nil
 | 
						return &log, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UpdatePlanExecutionLogStatus 更新计划执行日志的状态
 | 
				
			||||||
 | 
					func (r *gormExecutionLogRepository) UpdatePlanExecutionLogStatus(logID uint, status models.ExecutionStatus) error {
 | 
				
			||||||
 | 
						return r.db.Model(&models.PlanExecutionLog{}).Where("id = ?", logID).Update("status", status).Error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UpdatePlanExecutionLogsStatusByIDs 批量更新计划执行日志的状态
 | 
				
			||||||
 | 
					func (r *gormExecutionLogRepository) UpdatePlanExecutionLogsStatusByIDs(logIDs []uint, status models.ExecutionStatus) error {
 | 
				
			||||||
 | 
						if len(logIDs) == 0 {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return r.db.Model(&models.PlanExecutionLog{}).Where("id IN ?", logIDs).Update("status", status).Error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FindIncompletePlanExecutionLogs 查找所有未完成的计划执行日志
 | 
				
			||||||
 | 
					func (r *gormExecutionLogRepository) FindIncompletePlanExecutionLogs() ([]models.PlanExecutionLog, error) {
 | 
				
			||||||
 | 
						var logs []models.PlanExecutionLog
 | 
				
			||||||
 | 
						err := r.db.Where("status = ?", models.ExecutionStatusStarted).Find(&logs).Error
 | 
				
			||||||
 | 
						return logs, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,6 +1,8 @@
 | 
				
			|||||||
package repository
 | 
					package repository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
 | 
						"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
 | 
				
			||||||
@@ -10,7 +12,18 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// PendingTaskRepository 定义了与待执行任务队列交互的接口。
 | 
					// PendingTaskRepository 定义了与待执行任务队列交互的接口。
 | 
				
			||||||
type PendingTaskRepository interface {
 | 
					type PendingTaskRepository interface {
 | 
				
			||||||
 | 
						FindAllPendingTasks() ([]models.PendingTask, error)
 | 
				
			||||||
 | 
						FindPendingTriggerByPlanID(planID uint) (*models.PendingTask, error)
 | 
				
			||||||
 | 
						DeletePendingTasksByIDs(ids []uint) error
 | 
				
			||||||
 | 
						CreatePendingTask(task *models.PendingTask) error
 | 
				
			||||||
	CreatePendingTasksInBatch(tasks []*models.PendingTask) error
 | 
						CreatePendingTasksInBatch(tasks []*models.PendingTask) error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// UpdatePendingTaskExecuteAt 更新指定待执行任务的执行时间
 | 
				
			||||||
 | 
						UpdatePendingTaskExecuteAt(id uint, executeAt time.Time) error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// ClearAllPendingTasks 清空所有待执行任务
 | 
				
			||||||
 | 
						ClearAllPendingTasks() error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// ClaimNextAvailableTask 原子地认领下一个可用的任务。
 | 
						// ClaimNextAvailableTask 原子地认领下一个可用的任务。
 | 
				
			||||||
	// 它会同时返回被认领任务对应的日志对象,以及被删除的待办任务对象的内存副本。
 | 
						// 它会同时返回被认领任务对应的日志对象,以及被删除的待办任务对象的内存副本。
 | 
				
			||||||
	ClaimNextAvailableTask(excludePlanIDs []uint) (*models.TaskExecutionLog, *models.PendingTask, error)
 | 
						ClaimNextAvailableTask(excludePlanIDs []uint) (*models.TaskExecutionLog, *models.PendingTask, error)
 | 
				
			||||||
@@ -28,11 +41,56 @@ func NewGormPendingTaskRepository(db *gorm.DB) PendingTaskRepository {
 | 
				
			|||||||
	return &gormPendingTaskRepository{db: db}
 | 
						return &gormPendingTaskRepository{db: db}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *gormPendingTaskRepository) FindAllPendingTasks() ([]models.PendingTask, error) {
 | 
				
			||||||
 | 
						var tasks []models.PendingTask
 | 
				
			||||||
 | 
						// 预加载 Task 以便后续访问 Task.PlanID
 | 
				
			||||||
 | 
						// 预加载 TaskExecutionLog 以便后续访问 PlanExecutionLogID
 | 
				
			||||||
 | 
						err := r.db.Preload("Task").Preload("TaskExecutionLog").Find(&tasks).Error
 | 
				
			||||||
 | 
						return tasks, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *gormPendingTaskRepository) FindPendingTriggerByPlanID(planID uint) (*models.PendingTask, error) {
 | 
				
			||||||
 | 
						var pendingTask models.PendingTask
 | 
				
			||||||
 | 
						// 关键修改:通过 JOIN tasks 表并查询 parameters JSON 字段来查找触发器,而不是依赖 task.plan_id
 | 
				
			||||||
 | 
						err := r.db.
 | 
				
			||||||
 | 
							Joins("JOIN tasks ON tasks.id = pending_tasks.task_id").
 | 
				
			||||||
 | 
							Where("tasks.type = ? AND tasks.parameters->>'plan_id' = ?", models.TaskPlanAnalysis, fmt.Sprintf("%d", planID)).
 | 
				
			||||||
 | 
							First(&pendingTask).Error
 | 
				
			||||||
 | 
						if errors.Is(err, gorm.ErrRecordNotFound) {
 | 
				
			||||||
 | 
							return nil, nil // 未找到不是错误
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &pendingTask, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *gormPendingTaskRepository) DeletePendingTasksByIDs(ids []uint) error {
 | 
				
			||||||
 | 
						if len(ids) == 0 {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return r.db.Where("id IN ?", ids).Delete(&models.PendingTask{}).Error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *gormPendingTaskRepository) CreatePendingTask(task *models.PendingTask) error {
 | 
				
			||||||
 | 
						return r.db.Create(task).Error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// CreatePendingTasksInBatch 在一次数据库调用中创建多个待执行任务条目。
 | 
					// CreatePendingTasksInBatch 在一次数据库调用中创建多个待执行任务条目。
 | 
				
			||||||
func (r *gormPendingTaskRepository) CreatePendingTasksInBatch(tasks []*models.PendingTask) error {
 | 
					func (r *gormPendingTaskRepository) CreatePendingTasksInBatch(tasks []*models.PendingTask) error {
 | 
				
			||||||
 | 
						if len(tasks) == 0 {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	return r.db.Create(&tasks).Error
 | 
						return r.db.Create(&tasks).Error
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// UpdatePendingTaskExecuteAt 更新指定待执行任务的执行时间
 | 
				
			||||||
 | 
					func (r *gormPendingTaskRepository) UpdatePendingTaskExecuteAt(id uint, executeAt time.Time) error {
 | 
				
			||||||
 | 
						return r.db.Model(&models.PendingTask{}).Where("id = ?", id).Update("execute_at", executeAt).Error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ClearAllPendingTasks 清空所有待执行任务
 | 
				
			||||||
 | 
					func (r *gormPendingTaskRepository) ClearAllPendingTasks() error {
 | 
				
			||||||
 | 
						return r.db.Where("1 = 1").Delete(&models.PendingTask{}).Error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ClaimNextAvailableTask 以原子方式认领下一个可用的任务。
 | 
					// ClaimNextAvailableTask 以原子方式认领下一个可用的任务。
 | 
				
			||||||
func (r *gormPendingTaskRepository) ClaimNextAvailableTask(excludePlanIDs []uint) (*models.TaskExecutionLog, *models.PendingTask, error) {
 | 
					func (r *gormPendingTaskRepository) ClaimNextAvailableTask(excludePlanIDs []uint) (*models.TaskExecutionLog, *models.PendingTask, error) {
 | 
				
			||||||
	var log models.TaskExecutionLog
 | 
						var log models.TaskExecutionLog
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -42,6 +42,18 @@ type PlanRepository interface {
 | 
				
			|||||||
	DeleteTask(id int) error
 | 
						DeleteTask(id int) error
 | 
				
			||||||
	// FindPlanAnalysisTaskByParamsPlanID 根据Parameters中的ParamsPlanID字段值查找TaskPlanAnalysis类型的Task
 | 
						// FindPlanAnalysisTaskByParamsPlanID 根据Parameters中的ParamsPlanID字段值查找TaskPlanAnalysis类型的Task
 | 
				
			||||||
	FindPlanAnalysisTaskByParamsPlanID(paramsPlanID uint) (*models.Task, error)
 | 
						FindPlanAnalysisTaskByParamsPlanID(paramsPlanID uint) (*models.Task, error)
 | 
				
			||||||
 | 
						// FindRunnablePlans 获取所有应执行的计划
 | 
				
			||||||
 | 
						FindRunnablePlans() ([]*models.Plan, error)
 | 
				
			||||||
 | 
						// FindDisabledAndStoppedPlans 获取所有已禁用或已停止的计划
 | 
				
			||||||
 | 
						FindDisabledAndStoppedPlans() ([]*models.Plan, error)
 | 
				
			||||||
 | 
						// FindPlanAnalysisTaskByPlanID 根据 PlanID 找到其关联的 'plan_analysis' 任务
 | 
				
			||||||
 | 
						FindPlanAnalysisTaskByPlanID(planID uint) (*models.Task, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// CreatePlanAnalysisTask 创建一个 plan_analysis 类型的任务并返回它
 | 
				
			||||||
 | 
						CreatePlanAnalysisTask(plan *models.Plan) (*models.Task, error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// FindPlansWithPendingTasks 查找所有正在执行的计划
 | 
				
			||||||
 | 
						FindPlansWithPendingTasks() ([]*models.Plan, error)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// gormPlanRepository 是 PlanRepository 的 GORM 实现
 | 
					// gormPlanRepository 是 PlanRepository 的 GORM 实现
 | 
				
			||||||
@@ -174,7 +186,9 @@ func (r *gormPlanRepository) CreatePlan(plan *models.Plan) error {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// 3. 创建触发器Task
 | 
							// 3. 创建触发器Task
 | 
				
			||||||
		if err := r.createPlanAnalysisTask(tx, plan); err != nil {
 | 
							// 关键修改:调用 createPlanAnalysisTask 并处理其返回的 Task 对象
 | 
				
			||||||
 | 
							_, err := r.createPlanAnalysisTask(tx, plan)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
@@ -275,7 +289,7 @@ func (r *gormPlanRepository) reconcilePlanNode(tx *gorm.DB, plan *models.Plan) e
 | 
				
			|||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	// 1. 更新节点本身的基础字段
 | 
						// 1. 更新节点本身的基础字段
 | 
				
			||||||
	if err := tx.Model(plan).Select("Name", "Description", "ExecutionType", "CronExpression", "ContentType").Updates(plan).Error; err != nil {
 | 
						if err := tx.Model(plan).Select("Name", "Description", "ExecutionType", "ExecuteNum", "CronExpression", "ContentType").Updates(plan).Error; err != nil {
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -517,63 +531,115 @@ func (r *gormPlanRepository) deleteTask(tx *gorm.DB, id int) error {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// FindPlanAnalysisTaskByParamsPlanID 根据Parameters中的ParamsPlanID字段值查找TaskPlanAnalysis类型的Task
 | 
					// FindPlanAnalysisTaskByParamsPlanID 根据Parameters中的ParamsPlanID字段值查找TaskPlanAnalysis类型的Task
 | 
				
			||||||
func (r *gormPlanRepository) FindPlanAnalysisTaskByParamsPlanID(paramsPlanID uint) (*models.Task, error) {
 | 
					func (r *gormPlanRepository) FindPlanAnalysisTaskByParamsPlanID(paramsPlanID uint) (*models.Task, error) {
 | 
				
			||||||
	return r.findPlanAnalysisTaskByParamsPlanID(r.db, paramsPlanID)
 | 
						return r.findPlanAnalysisTask(r.db, paramsPlanID)
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// findPlanAnalysisTaskByParamsPlanID 使用指定db根据Parameters中的ParamsPlanID字段值查找TaskPlanAnalysis类型的Task
 | 
					 | 
				
			||||||
func (r *gormPlanRepository) findPlanAnalysisTaskByParamsPlanID(tx *gorm.DB, paramsPlanID uint) (*models.Task, error) {
 | 
					 | 
				
			||||||
	var task models.Task
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// 构造JSON查询条件,查找Parameters中包含指定ParamsPlanID且Type为TaskPlanAnalysis的任务
 | 
					 | 
				
			||||||
	// TODO 在JSON字段中查找特定键值的语法取决于数据库类型,这里使用PostgreSQL的语法
 | 
					 | 
				
			||||||
	// TODO 如果使用的是MySQL,则需要相应调整查询条件
 | 
					 | 
				
			||||||
	result := tx.Where(
 | 
					 | 
				
			||||||
		"type = ? AND parameters->>'plan_id' = ?",
 | 
					 | 
				
			||||||
		models.TaskPlanAnalysis,
 | 
					 | 
				
			||||||
		fmt.Sprintf("%d", paramsPlanID),
 | 
					 | 
				
			||||||
	).First(&task)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if result.Error != nil {
 | 
					 | 
				
			||||||
		if errors.Is(result.Error, gorm.ErrRecordNotFound) {
 | 
					 | 
				
			||||||
			return nil, fmt.Errorf("未找到Parameters.PlanID为%d的TaskPlanAnalysis类型任务", paramsPlanID)
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		return nil, fmt.Errorf("查找任务时出错: %w", result.Error)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return &task, nil
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// createPlanAnalysisTask 用于创建一个TaskPlanAnalysis类型的Task
 | 
					// createPlanAnalysisTask 用于创建一个TaskPlanAnalysis类型的Task
 | 
				
			||||||
func (r *gormPlanRepository) createPlanAnalysisTask(tx *gorm.DB, plan *models.Plan) error {
 | 
					// 关键修改:Task.PlanID 设置为 0,实际 PlanID 存储在 Parameters 中,并返回创建的 Task
 | 
				
			||||||
 | 
					func (r *gormPlanRepository) createPlanAnalysisTask(tx *gorm.DB, plan *models.Plan) (*models.Task, error) {
 | 
				
			||||||
	m := map[string]interface{}{
 | 
						m := map[string]interface{}{
 | 
				
			||||||
		models.ParamsPlanID: plan.ID,
 | 
							models.ParamsPlanID: plan.ID,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	parameters, err := json.Marshal(m)
 | 
						parameters, err := json.Marshal(m)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	task := &models.Task{
 | 
						task := &models.Task{
 | 
				
			||||||
		PlanID:         plan.ID,
 | 
							PlanID:         0, // 关键:设置为 0,避免被常规 PlanID 查询查到
 | 
				
			||||||
		Name:           fmt.Sprintf("'%v'计划触发器", plan.Name),
 | 
							Name:           fmt.Sprintf("'%s'计划触发器", plan.Name),
 | 
				
			||||||
		Description:    fmt.Sprintf("计划名: %v, 计划ID: %v", plan.Name, plan.ID),
 | 
							Description:    fmt.Sprintf("计划名: %s, 计划ID: %d", plan.Name, plan.ID),
 | 
				
			||||||
		ExecutionOrder: 0,
 | 
							ExecutionOrder: 0, // 触发器任务的执行顺序通常为0或不关心
 | 
				
			||||||
		Type:           models.TaskPlanAnalysis,
 | 
							Type:           models.TaskPlanAnalysis,
 | 
				
			||||||
		Parameters:     datatypes.JSON(parameters),
 | 
							Parameters:     datatypes.JSON(parameters),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return tx.Create(task).Error
 | 
						if err := tx.Create(task).Error; err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return task, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// updatePlanAnalysisTask 使用简单粗暴的删除再创建方式实现更新, 以控制AnalysisPlanTask的定义全部在createPlanAnalysisTask方法中
 | 
					// updatePlanAnalysisTask 使用更安全的方式更新触发器任务
 | 
				
			||||||
func (r *gormPlanRepository) updatePlanAnalysisTask(tx *gorm.DB, plan *models.Plan) error {
 | 
					func (r *gormPlanRepository) updatePlanAnalysisTask(tx *gorm.DB, plan *models.Plan) error {
 | 
				
			||||||
	task, err := r.findPlanAnalysisTaskByParamsPlanID(tx, plan.ID)
 | 
						task, err := r.findPlanAnalysisTask(tx, plan.ID)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
 | 
				
			||||||
 | 
							return fmt.Errorf("查找现有计划分析任务失败: %w", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 如果触发器任务不存在,则创建一个
 | 
				
			||||||
 | 
						if task == nil {
 | 
				
			||||||
 | 
							_, err := r.createPlanAnalysisTask(tx, plan)
 | 
				
			||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	err = r.deleteTask(tx, task.ID)
 | 
					
 | 
				
			||||||
	if err != nil {
 | 
						// 如果存在,则更新它的名称和描述以反映计划的最新信息
 | 
				
			||||||
		return err
 | 
						task.Name = fmt.Sprintf("'%s'计划触发器", plan.Name)
 | 
				
			||||||
	}
 | 
						task.Description = fmt.Sprintf("计划名: %s, 计划ID: %d", plan.Name, plan.ID)
 | 
				
			||||||
	return r.createPlanAnalysisTask(tx, plan)
 | 
					
 | 
				
			||||||
 | 
						return tx.Save(task).Error
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *gormPlanRepository) FindRunnablePlans() ([]*models.Plan, error) {
 | 
				
			||||||
 | 
						var plans []*models.Plan
 | 
				
			||||||
 | 
						err := r.db.
 | 
				
			||||||
 | 
							Where("status = ?", models.PlanStatusEnabled).
 | 
				
			||||||
 | 
							Where(
 | 
				
			||||||
 | 
								r.db.Where("execution_type = ?", models.PlanExecutionTypeManual).
 | 
				
			||||||
 | 
									Or("execution_type = ? AND (execute_num = 0 OR execute_count < execute_num)", models.PlanExecutionTypeAutomatic),
 | 
				
			||||||
 | 
							).
 | 
				
			||||||
 | 
							Find(&plans).Error
 | 
				
			||||||
 | 
						return plans, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (r *gormPlanRepository) FindDisabledAndStoppedPlans() ([]*models.Plan, error) {
 | 
				
			||||||
 | 
						var plans []*models.Plan
 | 
				
			||||||
 | 
						err := r.db.
 | 
				
			||||||
 | 
							Where("status = ? OR status = ?", models.PlanStatusDisabled, models.PlanStatusStopeed).
 | 
				
			||||||
 | 
							Find(&plans).Error
 | 
				
			||||||
 | 
						return plans, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// findPlanAnalysisTask 是一个内部使用的、更高效的查找方法
 | 
				
			||||||
 | 
					// 关键修改:通过查询 parameters JSON 字段来查找
 | 
				
			||||||
 | 
					func (r *gormPlanRepository) findPlanAnalysisTask(tx *gorm.DB, planID uint) (*models.Task, error) {
 | 
				
			||||||
 | 
						var task models.Task
 | 
				
			||||||
 | 
						err := tx.Where("type = ? AND parameters->>'plan_id' = ?", models.TaskPlanAnalysis, fmt.Sprintf("%d", planID)).First(&task).Error
 | 
				
			||||||
 | 
						if errors.Is(err, gorm.ErrRecordNotFound) {
 | 
				
			||||||
 | 
							return nil, nil // 未找到不是错误,返回nil, nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return &task, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FindPlanAnalysisTaskByPlanID 是暴露给外部的公共方法
 | 
				
			||||||
 | 
					// 关键修改:通过查询 parameters JSON 字段来查找
 | 
				
			||||||
 | 
					func (r *gormPlanRepository) FindPlanAnalysisTaskByPlanID(planID uint) (*models.Task, error) {
 | 
				
			||||||
 | 
						return r.findPlanAnalysisTask(r.db, planID)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CreatePlanAnalysisTask 创建一个 plan_analysis 类型的任务并返回它
 | 
				
			||||||
 | 
					// 这个方法是公开的,主要由 TaskManager 在发现触发器任务定义丢失时调用。
 | 
				
			||||||
 | 
					func (r *gormPlanRepository) CreatePlanAnalysisTask(plan *models.Plan) (*models.Task, error) {
 | 
				
			||||||
 | 
						var createdTask *models.Task
 | 
				
			||||||
 | 
						err := r.db.Transaction(func(tx *gorm.DB) error {
 | 
				
			||||||
 | 
							var err error
 | 
				
			||||||
 | 
							createdTask, err = r.createPlanAnalysisTask(tx, plan)
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						return createdTask, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FindPlansWithPendingTasks 查找所有正在执行的计划
 | 
				
			||||||
 | 
					func (r *gormPlanRepository) FindPlansWithPendingTasks() ([]*models.Plan, error) {
 | 
				
			||||||
 | 
						var plans []*models.Plan
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// 关联 pending_tasks, task_execution_logs, tasks 表来查找符合条件的计划
 | 
				
			||||||
 | 
						err := r.db.Table("plans").
 | 
				
			||||||
 | 
							Joins("JOIN tasks ON plans.id = tasks.plan_id").
 | 
				
			||||||
 | 
							Joins("JOIN task_execution_logs ON tasks.id = task_execution_logs.task_id").
 | 
				
			||||||
 | 
							Joins("JOIN pending_tasks ON task_execution_logs.id = pending_tasks.task_execution_log_id").
 | 
				
			||||||
 | 
							Group("plans.id"). // 避免重复,因为一个计划可能有多个待执行任务
 | 
				
			||||||
 | 
							Find(&plans).Error
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return plans, err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user