issue-5 #8
4
Makefile
4
Makefile
@@ -37,7 +37,9 @@ test:
|
||||
# 生成swagger文档
|
||||
.PHONY: swag
|
||||
swag:
|
||||
swag init
|
||||
if exist docs rmdir /s /q docs
|
||||
swag init --parseInternal --parseDependency
|
||||
|
||||
|
||||
# 生成protobuf文件
|
||||
.PHONY: proto
|
||||
|
||||
@@ -12,3 +12,6 @@
|
||||
4. 暂时不考虑和区域主控间的同步消息, 假设所有消息都是异步的, 这可能导致无法知道指令是否执行成功
|
||||
5. 如果系统停机时间很长, 待执行任务表中的任务过期了怎么办, 目前没有任务过期机制
|
||||
6. 可以用TimescaleDB代替PGSQL, 优化传感器数据存储性能
|
||||
|
||||
|
||||
任务调度器执行触发器任务时要修改一下对应计划的执行次数(如果是指定次数的计划)
|
||||
303
docs/docs.go
303
docs/docs.go
@@ -15,7 +15,7 @@ const docTemplate = `{
|
||||
"host": "{{.Host}}",
|
||||
"basePath": "{{.BasePath}}",
|
||||
"paths": {
|
||||
"/devices": {
|
||||
"/api/v1/devices": {
|
||||
"get": {
|
||||
"description": "获取系统中所有设备的列表",
|
||||
"produces": [
|
||||
@@ -27,9 +27,24 @@ const docTemplate = `{
|
||||
"summary": "获取设备列表",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "业务失败,具体错误码和信息见响应体",
|
||||
"description": "OK",
|
||||
"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": {
|
||||
"200": {
|
||||
"description": "业务失败,具体错误码和信息见响应体",
|
||||
"description": "OK",
|
||||
"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": {
|
||||
"description": "根据设备ID获取单个设备的详细信息",
|
||||
"produces": [
|
||||
@@ -88,9 +115,21 @@ const docTemplate = `{
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "业务失败,具体错误码和信息见响应体",
|
||||
"description": "OK",
|
||||
"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": {
|
||||
"200": {
|
||||
"description": "业务失败,具体错误码和信息见响应体",
|
||||
"description": "OK",
|
||||
"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": {
|
||||
"200": {
|
||||
"description": "业务失败,具体错误码和信息见响应体",
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.Response"
|
||||
}
|
||||
@@ -162,7 +213,7 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/plans": {
|
||||
"/api/v1/plans": {
|
||||
"get": {
|
||||
"description": "获取所有计划的列表",
|
||||
"produces": [
|
||||
@@ -174,9 +225,21 @@ const docTemplate = `{
|
||||
"summary": "获取计划列表",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "业务失败,具体错误码和信息见响应体(例如400, 500)",
|
||||
"description": "业务码为200代表成功获取列表",
|
||||
"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": {
|
||||
"200": {
|
||||
"description": "业务失败,具体错误码和信息见响应体(例如400, 500)",
|
||||
"description": "业务码为201代表创建成功",
|
||||
"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": {
|
||||
"description": "根据计划ID获取单个计划的详细信息。",
|
||||
"produces": [
|
||||
@@ -235,9 +310,21 @@ const docTemplate = `{
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "业务失败,具体错误码和信息见响应体(例如400, 404, 500)",
|
||||
"description": "业务码为200代表成功获取",
|
||||
"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": {
|
||||
"200": {
|
||||
"description": "业务失败,具体错误码和信息见响应体(例如400, 404, 500)",
|
||||
"description": "业务码为200代表更新成功",
|
||||
"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": {
|
||||
"200": {
|
||||
"description": "业务失败,具体错误码和信息见响应体(例如400, 404, 500)",
|
||||
"description": "业务码为200代表删除成功",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.Response"
|
||||
}
|
||||
@@ -309,7 +408,7 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/plans/{id}/start": {
|
||||
"/api/v1/plans/{id}/start": {
|
||||
"post": {
|
||||
"description": "根据计划ID启动一个计划的执行。",
|
||||
"produces": [
|
||||
@@ -330,7 +429,7 @@ const docTemplate = `{
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "业务失败,具体错误码和信息见响应体(例如400, 404, 500)",
|
||||
"description": "业务码为200代表成功启动计划",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.Response"
|
||||
}
|
||||
@@ -338,7 +437,7 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/plans/{id}/stop": {
|
||||
"/api/v1/plans/{id}/stop": {
|
||||
"post": {
|
||||
"description": "根据计划ID停止一个正在执行的计划。",
|
||||
"produces": [
|
||||
@@ -359,7 +458,7 @@ const docTemplate = `{
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "业务失败,具体错误码和信息见响应体(例如400, 404, 500)",
|
||||
"description": "业务码为200代表成功停止计划",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.Response"
|
||||
}
|
||||
@@ -367,7 +466,7 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/users": {
|
||||
"/api/v1/users": {
|
||||
"post": {
|
||||
"description": "根据用户名和密码创建一个新的系统用户。",
|
||||
"consumes": [
|
||||
@@ -393,15 +492,27 @@ const docTemplate = `{
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "业务失败,具体错误码和信息见响应体(例如400, 409, 500)",
|
||||
"description": "业务码为201代表创建成功",
|
||||
"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": {
|
||||
"description": "用户使用用户名和密码登录,成功后返回 JWT 令牌。",
|
||||
"consumes": [
|
||||
@@ -427,9 +538,21 @@ const docTemplate = `{
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "业务失败,具体错误码和信息见响应体(例如400, 401, 500)",
|
||||
"description": "业务码为200代表登录成功",
|
||||
"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": {
|
||||
"controller.Properties": {
|
||||
"type": "object"
|
||||
},
|
||||
"controller.Response": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -457,17 +577,12 @@ const docTemplate = `{
|
||||
}
|
||||
},
|
||||
"device.CreateDeviceRequest": {
|
||||
"type": "object"
|
||||
},
|
||||
"device.DeviceResponse": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"location": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -478,16 +593,14 @@ const docTemplate = `{
|
||||
"type": "integer"
|
||||
},
|
||||
"properties": {
|
||||
"$ref": "#/definitions/controller.Properties"
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"sub_type": {
|
||||
"$ref": "#/definitions/models.DeviceSubType"
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/models.DeviceType"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -508,7 +621,8 @@ const docTemplate = `{
|
||||
"type": "integer"
|
||||
},
|
||||
"properties": {
|
||||
"$ref": "#/definitions/controller.Properties"
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"sub_type": {
|
||||
"$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": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
@@ -588,18 +735,46 @@ const docTemplate = `{
|
||||
"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": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"plan_analysis",
|
||||
"waiting"
|
||||
],
|
||||
"x-enum-comments": {
|
||||
"TaskPlanAnalysis": "解析Plan的Task列表并添加到待执行队列的特殊任务",
|
||||
"TaskTypeWaiting": "等待任务"
|
||||
},
|
||||
"x-enum-descriptions": [
|
||||
"解析Plan的Task列表并添加到待执行队列的特殊任务",
|
||||
"等待任务"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"TaskPlanAnalysis",
|
||||
"TaskTypeWaiting"
|
||||
]
|
||||
},
|
||||
@@ -627,6 +802,10 @@ const docTemplate = `{
|
||||
"type": "string",
|
||||
"example": "根据温度自动调节风扇和加热器"
|
||||
},
|
||||
"execute_num": {
|
||||
"type": "integer",
|
||||
"example": 10
|
||||
},
|
||||
"execution_type": {
|
||||
"allOf": [
|
||||
{
|
||||
@@ -687,6 +866,14 @@ const docTemplate = `{
|
||||
"type": "string",
|
||||
"example": "根据温度自动调节风扇和加热器"
|
||||
},
|
||||
"execute_count": {
|
||||
"type": "integer",
|
||||
"example": 0
|
||||
},
|
||||
"execute_num": {
|
||||
"type": "integer",
|
||||
"example": 10
|
||||
},
|
||||
"execution_type": {
|
||||
"allOf": [
|
||||
{
|
||||
@@ -703,6 +890,14 @@ const docTemplate = `{
|
||||
"type": "string",
|
||||
"example": "猪舍温度控制计划"
|
||||
},
|
||||
"status": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.PlanStatus"
|
||||
}
|
||||
],
|
||||
"example": 0
|
||||
},
|
||||
"sub_plans": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@@ -757,7 +952,8 @@ const docTemplate = `{
|
||||
"example": "打开风扇"
|
||||
},
|
||||
"parameters": {
|
||||
"$ref": "#/definitions/controller.Properties"
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"type": {
|
||||
"allOf": [
|
||||
@@ -789,7 +985,8 @@ const docTemplate = `{
|
||||
"example": "打开风扇"
|
||||
},
|
||||
"parameters": {
|
||||
"$ref": "#/definitions/controller.Properties"
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"plan_id": {
|
||||
"type": "integer",
|
||||
@@ -824,6 +1021,10 @@ const docTemplate = `{
|
||||
"type": "string",
|
||||
"example": "更新后的描述"
|
||||
},
|
||||
"execute_num": {
|
||||
"type": "integer",
|
||||
"example": 10
|
||||
},
|
||||
"execution_type": {
|
||||
"allOf": [
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"contact": {}
|
||||
},
|
||||
"paths": {
|
||||
"/devices": {
|
||||
"/api/v1/devices": {
|
||||
"get": {
|
||||
"description": "获取系统中所有设备的列表",
|
||||
"produces": [
|
||||
@@ -16,9 +16,24 @@
|
||||
"summary": "获取设备列表",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "业务失败,具体错误码和信息见响应体",
|
||||
"description": "OK",
|
||||
"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": {
|
||||
"200": {
|
||||
"description": "业务失败,具体错误码和信息见响应体",
|
||||
"description": "OK",
|
||||
"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": {
|
||||
"description": "根据设备ID获取单个设备的详细信息",
|
||||
"produces": [
|
||||
@@ -77,9 +104,21 @@
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "业务失败,具体错误码和信息见响应体",
|
||||
"description": "OK",
|
||||
"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": {
|
||||
"200": {
|
||||
"description": "业务失败,具体错误码和信息见响应体",
|
||||
"description": "OK",
|
||||
"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": {
|
||||
"200": {
|
||||
"description": "业务失败,具体错误码和信息见响应体",
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.Response"
|
||||
}
|
||||
@@ -151,7 +202,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/plans": {
|
||||
"/api/v1/plans": {
|
||||
"get": {
|
||||
"description": "获取所有计划的列表",
|
||||
"produces": [
|
||||
@@ -163,9 +214,21 @@
|
||||
"summary": "获取计划列表",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "业务失败,具体错误码和信息见响应体(例如400, 500)",
|
||||
"description": "业务码为200代表成功获取列表",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.Response"
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/controller.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/plan.ListPlansResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -195,15 +258,27 @@
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "业务失败,具体错误码和信息见响应体(例如400, 500)",
|
||||
"description": "业务码为201代表创建成功",
|
||||
"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": {
|
||||
"description": "根据计划ID获取单个计划的详细信息。",
|
||||
"produces": [
|
||||
@@ -224,9 +299,21 @@
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "业务失败,具体错误码和信息见响应体(例如400, 404, 500)",
|
||||
"description": "业务码为200代表成功获取",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.Response"
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/controller.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/plan.PlanResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -263,9 +350,21 @@
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "业务失败,具体错误码和信息见响应体(例如400, 404, 500)",
|
||||
"description": "业务码为200代表更新成功",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.Response"
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/controller.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/plan.PlanResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -290,7 +389,7 @@
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "业务失败,具体错误码和信息见响应体(例如400, 404, 500)",
|
||||
"description": "业务码为200代表删除成功",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.Response"
|
||||
}
|
||||
@@ -298,7 +397,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/plans/{id}/start": {
|
||||
"/api/v1/plans/{id}/start": {
|
||||
"post": {
|
||||
"description": "根据计划ID启动一个计划的执行。",
|
||||
"produces": [
|
||||
@@ -319,7 +418,7 @@
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "业务失败,具体错误码和信息见响应体(例如400, 404, 500)",
|
||||
"description": "业务码为200代表成功启动计划",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.Response"
|
||||
}
|
||||
@@ -327,7 +426,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/plans/{id}/stop": {
|
||||
"/api/v1/plans/{id}/stop": {
|
||||
"post": {
|
||||
"description": "根据计划ID停止一个正在执行的计划。",
|
||||
"produces": [
|
||||
@@ -348,7 +447,7 @@
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "业务失败,具体错误码和信息见响应体(例如400, 404, 500)",
|
||||
"description": "业务码为200代表成功停止计划",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.Response"
|
||||
}
|
||||
@@ -356,7 +455,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/users": {
|
||||
"/api/v1/users": {
|
||||
"post": {
|
||||
"description": "根据用户名和密码创建一个新的系统用户。",
|
||||
"consumes": [
|
||||
@@ -382,15 +481,27 @@
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "业务失败,具体错误码和信息见响应体(例如400, 409, 500)",
|
||||
"description": "业务码为201代表创建成功",
|
||||
"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": {
|
||||
"description": "用户使用用户名和密码登录,成功后返回 JWT 令牌。",
|
||||
"consumes": [
|
||||
@@ -416,9 +527,21 @@
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "业务失败,具体错误码和信息见响应体(例如400, 401, 500)",
|
||||
"description": "业务码为200代表登录成功",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/controller.Response"
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/controller.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/user.LoginResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -426,9 +549,6 @@
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"controller.Properties": {
|
||||
"type": "object"
|
||||
},
|
||||
"controller.Response": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -446,17 +566,12 @@
|
||||
}
|
||||
},
|
||||
"device.CreateDeviceRequest": {
|
||||
"type": "object"
|
||||
},
|
||||
"device.DeviceResponse": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"created_at": {
|
||||
"type": "string"
|
||||
},
|
||||
"id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"location": {
|
||||
"type": "string"
|
||||
},
|
||||
@@ -467,16 +582,14 @@
|
||||
"type": "integer"
|
||||
},
|
||||
"properties": {
|
||||
"$ref": "#/definitions/controller.Properties"
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"sub_type": {
|
||||
"$ref": "#/definitions/models.DeviceSubType"
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/definitions/models.DeviceType"
|
||||
},
|
||||
"updated_at": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -497,7 +610,8 @@
|
||||
"type": "integer"
|
||||
},
|
||||
"properties": {
|
||||
"$ref": "#/definitions/controller.Properties"
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"sub_type": {
|
||||
"$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": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
@@ -577,18 +724,46 @@
|
||||
"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": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"plan_analysis",
|
||||
"waiting"
|
||||
],
|
||||
"x-enum-comments": {
|
||||
"TaskPlanAnalysis": "解析Plan的Task列表并添加到待执行队列的特殊任务",
|
||||
"TaskTypeWaiting": "等待任务"
|
||||
},
|
||||
"x-enum-descriptions": [
|
||||
"解析Plan的Task列表并添加到待执行队列的特殊任务",
|
||||
"等待任务"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"TaskPlanAnalysis",
|
||||
"TaskTypeWaiting"
|
||||
]
|
||||
},
|
||||
@@ -616,6 +791,10 @@
|
||||
"type": "string",
|
||||
"example": "根据温度自动调节风扇和加热器"
|
||||
},
|
||||
"execute_num": {
|
||||
"type": "integer",
|
||||
"example": 10
|
||||
},
|
||||
"execution_type": {
|
||||
"allOf": [
|
||||
{
|
||||
@@ -676,6 +855,14 @@
|
||||
"type": "string",
|
||||
"example": "根据温度自动调节风扇和加热器"
|
||||
},
|
||||
"execute_count": {
|
||||
"type": "integer",
|
||||
"example": 0
|
||||
},
|
||||
"execute_num": {
|
||||
"type": "integer",
|
||||
"example": 10
|
||||
},
|
||||
"execution_type": {
|
||||
"allOf": [
|
||||
{
|
||||
@@ -692,6 +879,14 @@
|
||||
"type": "string",
|
||||
"example": "猪舍温度控制计划"
|
||||
},
|
||||
"status": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/models.PlanStatus"
|
||||
}
|
||||
],
|
||||
"example": 0
|
||||
},
|
||||
"sub_plans": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@@ -746,7 +941,8 @@
|
||||
"example": "打开风扇"
|
||||
},
|
||||
"parameters": {
|
||||
"$ref": "#/definitions/controller.Properties"
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"type": {
|
||||
"allOf": [
|
||||
@@ -778,7 +974,8 @@
|
||||
"example": "打开风扇"
|
||||
},
|
||||
"parameters": {
|
||||
"$ref": "#/definitions/controller.Properties"
|
||||
"type": "object",
|
||||
"additionalProperties": true
|
||||
},
|
||||
"plan_id": {
|
||||
"type": "integer",
|
||||
@@ -813,6 +1010,10 @@
|
||||
"type": "string",
|
||||
"example": "更新后的描述"
|
||||
},
|
||||
"execute_num": {
|
||||
"type": "integer",
|
||||
"example": 10
|
||||
},
|
||||
"execution_type": {
|
||||
"allOf": [
|
||||
{
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
definitions:
|
||||
controller.Properties:
|
||||
type: object
|
||||
controller.Response:
|
||||
properties:
|
||||
code:
|
||||
@@ -13,8 +11,44 @@ definitions:
|
||||
type: string
|
||||
type: object
|
||||
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
|
||||
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:
|
||||
created_at:
|
||||
type: string
|
||||
@@ -27,7 +61,8 @@ definitions:
|
||||
parent_id:
|
||||
type: integer
|
||||
properties:
|
||||
$ref: '#/definitions/controller.Properties'
|
||||
additionalProperties: true
|
||||
type: object
|
||||
sub_type:
|
||||
$ref: '#/definitions/models.DeviceSubType'
|
||||
type:
|
||||
@@ -35,24 +70,6 @@ definitions:
|
||||
updated_at:
|
||||
type: string
|
||||
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:
|
||||
enum:
|
||||
- ""
|
||||
@@ -107,15 +124,38 @@ definitions:
|
||||
x-enum-varnames:
|
||||
- PlanExecutionTypeAutomatic
|
||||
- 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:
|
||||
enum:
|
||||
- plan_analysis
|
||||
- waiting
|
||||
type: string
|
||||
x-enum-comments:
|
||||
TaskPlanAnalysis: 解析Plan的Task列表并添加到待执行队列的特殊任务
|
||||
TaskTypeWaiting: 等待任务
|
||||
x-enum-descriptions:
|
||||
- 解析Plan的Task列表并添加到待执行队列的特殊任务
|
||||
- 等待任务
|
||||
x-enum-varnames:
|
||||
- TaskPlanAnalysis
|
||||
- TaskTypeWaiting
|
||||
plan.CreatePlanRequest:
|
||||
properties:
|
||||
@@ -129,6 +169,9 @@ definitions:
|
||||
description:
|
||||
example: 根据温度自动调节风扇和加热器
|
||||
type: string
|
||||
execute_num:
|
||||
example: 10
|
||||
type: integer
|
||||
execution_type:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.PlanExecutionType'
|
||||
@@ -171,6 +214,12 @@ definitions:
|
||||
description:
|
||||
example: 根据温度自动调节风扇和加热器
|
||||
type: string
|
||||
execute_count:
|
||||
example: 0
|
||||
type: integer
|
||||
execute_num:
|
||||
example: 10
|
||||
type: integer
|
||||
execution_type:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.PlanExecutionType'
|
||||
@@ -181,6 +230,10 @@ definitions:
|
||||
name:
|
||||
example: 猪舍温度控制计划
|
||||
type: string
|
||||
status:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.PlanStatus'
|
||||
example: 0
|
||||
sub_plans:
|
||||
items:
|
||||
$ref: '#/definitions/plan.SubPlanResponse'
|
||||
@@ -219,7 +272,8 @@ definitions:
|
||||
example: 打开风扇
|
||||
type: string
|
||||
parameters:
|
||||
$ref: '#/definitions/controller.Properties'
|
||||
additionalProperties: true
|
||||
type: object
|
||||
type:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.TaskType'
|
||||
@@ -240,7 +294,8 @@ definitions:
|
||||
example: 打开风扇
|
||||
type: string
|
||||
parameters:
|
||||
$ref: '#/definitions/controller.Properties'
|
||||
additionalProperties: true
|
||||
type: object
|
||||
plan_id:
|
||||
example: 1
|
||||
type: integer
|
||||
@@ -261,6 +316,9 @@ definitions:
|
||||
description:
|
||||
example: 更新后的描述
|
||||
type: string
|
||||
execute_num:
|
||||
example: 10
|
||||
type: integer
|
||||
execution_type:
|
||||
allOf:
|
||||
- $ref: '#/definitions/models.PlanExecutionType'
|
||||
@@ -326,16 +384,23 @@ definitions:
|
||||
info:
|
||||
contact: {}
|
||||
paths:
|
||||
/devices:
|
||||
/api/v1/devices:
|
||||
get:
|
||||
description: 获取系统中所有设备的列表
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 业务失败,具体错误码和信息见响应体
|
||||
description: OK
|
||||
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: 获取设备列表
|
||||
tags:
|
||||
- 设备管理
|
||||
@@ -354,13 +419,18 @@ paths:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 业务失败,具体错误码和信息见响应体
|
||||
description: OK
|
||||
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: 创建新设备
|
||||
tags:
|
||||
- 设备管理
|
||||
/devices/{id}:
|
||||
/api/v1/devices/{id}:
|
||||
delete:
|
||||
description: 根据设备ID删除一个设备(软删除)
|
||||
parameters:
|
||||
@@ -373,7 +443,7 @@ paths:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 业务失败,具体错误码和信息见响应体
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/controller.Response'
|
||||
summary: 删除设备
|
||||
@@ -391,9 +461,14 @@ paths:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 业务失败,具体错误码和信息见响应体
|
||||
description: OK
|
||||
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: 获取设备信息
|
||||
tags:
|
||||
- 设备管理
|
||||
@@ -417,22 +492,32 @@ paths:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 业务失败,具体错误码和信息见响应体
|
||||
description: OK
|
||||
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: 更新设备信息
|
||||
tags:
|
||||
- 设备管理
|
||||
/plans:
|
||||
/api/v1/plans:
|
||||
get:
|
||||
description: 获取所有计划的列表
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 业务失败,具体错误码和信息见响应体(例如400, 500)
|
||||
description: 业务码为200代表成功获取列表
|
||||
schema:
|
||||
$ref: '#/definitions/controller.Response'
|
||||
allOf:
|
||||
- $ref: '#/definitions/controller.Response'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/plan.ListPlansResponse'
|
||||
type: object
|
||||
summary: 获取计划列表
|
||||
tags:
|
||||
- 计划管理
|
||||
@@ -451,13 +536,18 @@ paths:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 业务失败,具体错误码和信息见响应体(例如400, 500)
|
||||
description: 业务码为201代表创建成功
|
||||
schema:
|
||||
$ref: '#/definitions/controller.Response'
|
||||
allOf:
|
||||
- $ref: '#/definitions/controller.Response'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/plan.PlanResponse'
|
||||
type: object
|
||||
summary: 创建计划
|
||||
tags:
|
||||
- 计划管理
|
||||
/plans/{id}:
|
||||
/api/v1/plans/{id}:
|
||||
delete:
|
||||
description: 根据计划ID删除计划。
|
||||
parameters:
|
||||
@@ -470,7 +560,7 @@ paths:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 业务失败,具体错误码和信息见响应体(例如400, 404, 500)
|
||||
description: 业务码为200代表删除成功
|
||||
schema:
|
||||
$ref: '#/definitions/controller.Response'
|
||||
summary: 删除计划
|
||||
@@ -488,9 +578,14 @@ paths:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 业务失败,具体错误码和信息见响应体(例如400, 404, 500)
|
||||
description: 业务码为200代表成功获取
|
||||
schema:
|
||||
$ref: '#/definitions/controller.Response'
|
||||
allOf:
|
||||
- $ref: '#/definitions/controller.Response'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/plan.PlanResponse'
|
||||
type: object
|
||||
summary: 获取计划详情
|
||||
tags:
|
||||
- 计划管理
|
||||
@@ -514,13 +609,18 @@ paths:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 业务失败,具体错误码和信息见响应体(例如400, 404, 500)
|
||||
description: 业务码为200代表更新成功
|
||||
schema:
|
||||
$ref: '#/definitions/controller.Response'
|
||||
allOf:
|
||||
- $ref: '#/definitions/controller.Response'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/plan.PlanResponse'
|
||||
type: object
|
||||
summary: 更新计划
|
||||
tags:
|
||||
- 计划管理
|
||||
/plans/{id}/start:
|
||||
/api/v1/plans/{id}/start:
|
||||
post:
|
||||
description: 根据计划ID启动一个计划的执行。
|
||||
parameters:
|
||||
@@ -533,13 +633,13 @@ paths:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 业务失败,具体错误码和信息见响应体(例如400, 404, 500)
|
||||
description: 业务码为200代表成功启动计划
|
||||
schema:
|
||||
$ref: '#/definitions/controller.Response'
|
||||
summary: 启动计划
|
||||
tags:
|
||||
- 计划管理
|
||||
/plans/{id}/stop:
|
||||
/api/v1/plans/{id}/stop:
|
||||
post:
|
||||
description: 根据计划ID停止一个正在执行的计划。
|
||||
parameters:
|
||||
@@ -552,13 +652,13 @@ paths:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 业务失败,具体错误码和信息见响应体(例如400, 404, 500)
|
||||
description: 业务码为200代表成功停止计划
|
||||
schema:
|
||||
$ref: '#/definitions/controller.Response'
|
||||
summary: 停止计划
|
||||
tags:
|
||||
- 计划管理
|
||||
/users:
|
||||
/api/v1/users:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
@@ -574,13 +674,18 @@ paths:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 业务失败,具体错误码和信息见响应体(例如400, 409, 500)
|
||||
description: 业务码为201代表创建成功
|
||||
schema:
|
||||
$ref: '#/definitions/controller.Response'
|
||||
allOf:
|
||||
- $ref: '#/definitions/controller.Response'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/user.CreateUserResponse'
|
||||
type: object
|
||||
summary: 创建新用户
|
||||
tags:
|
||||
- 用户管理
|
||||
/users/login:
|
||||
/api/v1/users/login:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
@@ -596,9 +701,14 @@ paths:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 业务失败,具体错误码和信息见响应体(例如400, 401, 500)
|
||||
description: 业务码为200代表登录成功
|
||||
schema:
|
||||
$ref: '#/definitions/controller.Response'
|
||||
allOf:
|
||||
- $ref: '#/definitions/controller.Response'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/user.LoginResponse'
|
||||
type: object
|
||||
summary: 用户登录
|
||||
tags:
|
||||
- 用户管理
|
||||
|
||||
65
go.mod
65
go.mod
@@ -7,7 +7,7 @@ require (
|
||||
github.com/go-openapi/errors v0.22.2
|
||||
github.com/go-openapi/runtime v0.28.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/golang-jwt/jwt/v5 v5.3.0
|
||||
github.com/panjf2000/ants/v2 v2.11.3
|
||||
@@ -17,8 +17,8 @@ require (
|
||||
github.com/swaggo/gin-swagger v1.6.1
|
||||
github.com/swaggo/swag v1.16.6
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/crypto v0.36.0
|
||||
google.golang.org/protobuf v1.34.1
|
||||
golang.org/x/crypto v0.42.0
|
||||
google.golang.org/protobuf v1.36.9
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gorm.io/datatypes v1.2.6
|
||||
@@ -31,25 +31,38 @@ require (
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/bytedance/sonic v1.11.6 // indirect
|
||||
github.com/bytedance/sonic/loader v0.1.1 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||
github.com/bytedance/sonic v1.14.1 // 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/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.10 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
github.com/go-logr/logr v1.4.1 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/analysis v0.23.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.22.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.1 // indirect
|
||||
github.com/go-openapi/loads v0.22.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/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/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/jackc/pgpassfile v1.0.0 // 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/josharian/intern v1.0.0 // 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/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-sqlite3 v1.14.22 // 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/oklog/ulid v1.3.1 // 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/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/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.opentelemetry.io/otel v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.24.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.24.0 // indirect
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
golang.org/x/arch v0.8.0 // indirect
|
||||
golang.org/x/mod v0.21.0 // indirect
|
||||
golang.org/x/net v0.38.0 // indirect
|
||||
golang.org/x/sync v0.12.0 // indirect
|
||||
golang.org/x/sys v0.31.0 // indirect
|
||||
golang.org/x/text v0.23.0 // indirect
|
||||
golang.org/x/tools v0.26.0 // indirect
|
||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
||||
golang.org/x/arch v0.21.0 // indirect
|
||||
golang.org/x/mod v0.28.0 // indirect
|
||||
golang.org/x/net v0.44.0 // indirect
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/sys v0.36.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
|
||||
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/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/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/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/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/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/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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
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/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/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/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/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
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/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.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/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/go.mod h1:yLsaTCS92mnSAZX5WWoxszLj0u+Ojl+Zs5Stn1oF+rs=
|
||||
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/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.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/go.mod h1:iyeX1sEufmv3nPbBdX3ieNviWnOZaJ1+zquzJEf2BAQ=
|
||||
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/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.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.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
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/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/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
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/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.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||
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/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.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.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/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
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/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
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/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
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/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.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/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/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/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.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
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/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.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=
|
||||
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=
|
||||
@@ -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/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
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.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
|
||||
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-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/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.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
||||
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-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.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/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-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/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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
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.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||
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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
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.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||
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-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.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.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=
|
||||
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.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 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
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=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
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
|
||||
// @license.name Apache 2.0
|
||||
// @license.url http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
// @host localhost:8086
|
||||
// @BasePath /api/v1
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package device
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"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/repository"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/datatypes"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
@@ -33,46 +34,54 @@ func NewController(repo repository.DeviceRepository, logger *logs.Logger) *Contr
|
||||
|
||||
// CreateDeviceRequest 定义了创建设备时需要传入的参数
|
||||
type CreateDeviceRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Type models.DeviceType `json:"type" binding:"required"`
|
||||
SubType models.DeviceSubType `json:"sub_type,omitempty"`
|
||||
ParentID *uint `json:"parent_id,omitempty"`
|
||||
Location string `json:"location,omitempty"`
|
||||
Properties controller.Properties `json:"properties,omitempty"`
|
||||
Name string `json:"name" binding:"required"`
|
||||
Type models.DeviceType `json:"type" binding:"required"`
|
||||
SubType models.DeviceSubType `json:"sub_type,omitempty"`
|
||||
ParentID *uint `json:"parent_id,omitempty"`
|
||||
Location string `json:"location,omitempty"`
|
||||
Properties map[string]interface{} `json:"properties,omitempty"`
|
||||
}
|
||||
|
||||
// UpdateDeviceRequest 定义了更新设备时需要传入的参数
|
||||
type UpdateDeviceRequest struct {
|
||||
Name string `json:"name" binding:"required"`
|
||||
Type models.DeviceType `json:"type" binding:"required"`
|
||||
SubType models.DeviceSubType `json:"sub_type,omitempty"`
|
||||
ParentID *uint `json:"parent_id,omitempty"`
|
||||
Location string `json:"location,omitempty"`
|
||||
Properties controller.Properties `json:"properties,omitempty"`
|
||||
Name string `json:"name" binding:"required"`
|
||||
Type models.DeviceType `json:"type" binding:"required"`
|
||||
SubType models.DeviceSubType `json:"sub_type,omitempty"`
|
||||
ParentID *uint `json:"parent_id,omitempty"`
|
||||
Location string `json:"location,omitempty"`
|
||||
Properties map[string]interface{} `json:"properties,omitempty"`
|
||||
}
|
||||
|
||||
// --- Response DTOs ---
|
||||
|
||||
// DeviceResponse 定义了返回给客户端的单个设备信息的结构
|
||||
type DeviceResponse struct {
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type models.DeviceType `json:"type"`
|
||||
SubType models.DeviceSubType `json:"sub_type"`
|
||||
ParentID *uint `json:"parent_id"`
|
||||
Location string `json:"location"`
|
||||
Properties controller.Properties `json:"properties"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
ID uint `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type models.DeviceType `json:"type"`
|
||||
SubType models.DeviceSubType `json:"sub_type"`
|
||||
ParentID *uint `json:"parent_id"`
|
||||
Location string `json:"location"`
|
||||
Properties map[string]interface{} `json:"properties"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
}
|
||||
|
||||
// --- DTO 转换函数 ---
|
||||
|
||||
// newDeviceResponse 从数据库模型创建一个新的设备响应 DTO
|
||||
func newDeviceResponse(device *models.Device) *DeviceResponse {
|
||||
func newDeviceResponse(device *models.Device) (*DeviceResponse, error) {
|
||||
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{
|
||||
ID: device.ID,
|
||||
Name: device.Name,
|
||||
@@ -80,19 +89,23 @@ func newDeviceResponse(device *models.Device) *DeviceResponse {
|
||||
SubType: device.SubType,
|
||||
ParentID: device.ParentID,
|
||||
Location: device.Location,
|
||||
Properties: controller.Properties(device.Properties),
|
||||
Properties: props,
|
||||
CreatedAt: device.CreatedAt.Format(time.RFC3339),
|
||||
UpdatedAt: device.UpdatedAt.Format(time.RFC3339),
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
// newListDeviceResponse 从数据库模型切片创建一个新的设备列表响应 DTO 切片
|
||||
func newListDeviceResponse(devices []*models.Device) []*DeviceResponse {
|
||||
func newListDeviceResponse(devices []*models.Device) ([]*DeviceResponse, error) {
|
||||
list := make([]*DeviceResponse, 0, len(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 ---
|
||||
@@ -104,9 +117,8 @@ func newListDeviceResponse(devices []*models.Device) []*DeviceResponse {
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param device body CreateDeviceRequest true "设备信息"
|
||||
// @Success 200 {object} controller.Response{data=DeviceResponse} "业务码为201代表创建成功"
|
||||
// @Failure 200 {object} controller.Response "业务失败,具体错误码和信息见响应体"
|
||||
// @Router /devices [post]
|
||||
// @Success 200 {object} controller.Response{data=DeviceResponse}
|
||||
// @Router /api/v1/devices [post]
|
||||
func (c *Controller) CreateDevice(ctx *gin.Context) {
|
||||
var req CreateDeviceRequest
|
||||
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||
@@ -115,13 +127,20 @@ func (c *Controller) CreateDevice(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
propertiesJSON, err := json.Marshal(req.Properties)
|
||||
if err != nil {
|
||||
c.logger.Errorf("创建设备: 序列化属性失败: %v", err)
|
||||
controller.SendErrorResponse(ctx, controller.CodeBadRequest, "属性字段格式错误")
|
||||
return
|
||||
}
|
||||
|
||||
device := &models.Device{
|
||||
Name: req.Name,
|
||||
Type: req.Type,
|
||||
SubType: req.SubType,
|
||||
ParentID: req.ParentID,
|
||||
Location: req.Location,
|
||||
Properties: datatypes.JSON(req.Properties),
|
||||
Properties: propertiesJSON,
|
||||
}
|
||||
|
||||
if err := c.repo.Create(device); err != nil {
|
||||
@@ -130,7 +149,14 @@ func (c *Controller) CreateDevice(ctx *gin.Context) {
|
||||
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
|
||||
@@ -139,9 +165,8 @@ func (c *Controller) CreateDevice(ctx *gin.Context) {
|
||||
// @Tags 设备管理
|
||||
// @Produce json
|
||||
// @Param id path string true "设备ID"
|
||||
// @Success 200 {object} controller.Response{data=DeviceResponse} "业务码为200代表获取成功"
|
||||
// @Failure 200 {object} controller.Response "业务失败,具体错误码和信息见响应体"
|
||||
// @Router /devices/{id} [get]
|
||||
// @Success 200 {object} controller.Response{data=DeviceResponse}
|
||||
// @Router /api/v1/devices/{id} [get]
|
||||
func (c *Controller) GetDevice(ctx *gin.Context) {
|
||||
deviceID := ctx.Param("id")
|
||||
|
||||
@@ -160,7 +185,14 @@ func (c *Controller) GetDevice(ctx *gin.Context) {
|
||||
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
|
||||
@@ -168,9 +200,8 @@ func (c *Controller) GetDevice(ctx *gin.Context) {
|
||||
// @Description 获取系统中所有设备的列表
|
||||
// @Tags 设备管理
|
||||
// @Produce json
|
||||
// @Success 200 {object} controller.Response{data=[]DeviceResponse} "业务码为200代表获取成功"
|
||||
// @Failure 200 {object} controller.Response "业务失败,具体错误码和信息见响应体"
|
||||
// @Router /devices [get]
|
||||
// @Success 200 {object} controller.Response{data=[]DeviceResponse}
|
||||
// @Router /api/v1/devices [get]
|
||||
func (c *Controller) ListDevices(ctx *gin.Context) {
|
||||
devices, err := c.repo.ListAll()
|
||||
if err != nil {
|
||||
@@ -179,7 +210,14 @@ func (c *Controller) ListDevices(ctx *gin.Context) {
|
||||
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
|
||||
@@ -190,9 +228,8 @@ func (c *Controller) ListDevices(ctx *gin.Context) {
|
||||
// @Produce json
|
||||
// @Param id path string true "设备ID"
|
||||
// @Param device body UpdateDeviceRequest true "要更新的设备信息"
|
||||
// @Success 200 {object} controller.Response{data=DeviceResponse} "业务码为200代表更新成功"
|
||||
// @Failure 200 {object} controller.Response "业务失败,具体错误码和信息见响应体"
|
||||
// @Router /devices/{id} [put]
|
||||
// @Success 200 {object} controller.Response{data=DeviceResponse}
|
||||
// @Router /api/v1/devices/{id} [put]
|
||||
func (c *Controller) UpdateDevice(ctx *gin.Context) {
|
||||
deviceID := ctx.Param("id")
|
||||
|
||||
@@ -220,13 +257,20 @@ func (c *Controller) UpdateDevice(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
propertiesJSON, err := json.Marshal(req.Properties)
|
||||
if err != nil {
|
||||
c.logger.Errorf("更新设备: 序列化属性失败: %v", err)
|
||||
controller.SendErrorResponse(ctx, controller.CodeBadRequest, "属性字段格式错误")
|
||||
return
|
||||
}
|
||||
|
||||
// 3. 更新从数据库中查出的现有设备对象的字段
|
||||
existingDevice.Name = req.Name
|
||||
existingDevice.Type = req.Type
|
||||
existingDevice.SubType = req.SubType
|
||||
existingDevice.ParentID = req.ParentID
|
||||
existingDevice.Location = req.Location
|
||||
existingDevice.Properties = datatypes.JSON(req.Properties)
|
||||
existingDevice.Properties = propertiesJSON
|
||||
|
||||
// 4. 将修改后的 existingDevice 对象保存回数据库
|
||||
if err := c.repo.Update(existingDevice); err != nil {
|
||||
@@ -235,7 +279,14 @@ func (c *Controller) UpdateDevice(ctx *gin.Context) {
|
||||
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
|
||||
@@ -244,9 +295,8 @@ func (c *Controller) UpdateDevice(ctx *gin.Context) {
|
||||
// @Tags 设备管理
|
||||
// @Produce json
|
||||
// @Param id path string true "设备ID"
|
||||
// @Success 200 {object} controller.Response "业务码为200代表删除成功"
|
||||
// @Failure 200 {object} controller.Response "业务失败,具体错误码和信息见响应体"
|
||||
// @Router /devices/{id} [delete]
|
||||
// @Success 200 {object} controller.Response
|
||||
// @Router /api/v1/devices/{id} [delete]
|
||||
func (c *Controller) DeleteDevice(ctx *gin.Context) {
|
||||
deviceID := ctx.Param("id")
|
||||
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
package plan
|
||||
|
||||
import (
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/controller"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
"gorm.io/datatypes"
|
||||
)
|
||||
|
||||
// PlanToResponse 将Plan模型转换为PlanResponse
|
||||
func PlanToResponse(plan *models.Plan) *PlanResponse {
|
||||
func PlanToResponse(plan *models.Plan) (*PlanResponse, error) {
|
||||
if plan == nil {
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
response := &PlanResponse{
|
||||
@@ -17,6 +18,9 @@ func PlanToResponse(plan *models.Plan) *PlanResponse {
|
||||
Name: plan.Name,
|
||||
Description: plan.Description,
|
||||
ExecutionType: plan.ExecutionType,
|
||||
Status: plan.Status,
|
||||
ExecuteNum: plan.ExecuteNum,
|
||||
ExecuteCount: plan.ExecuteCount,
|
||||
CronExpression: plan.CronExpression,
|
||||
ContentType: plan.ContentType,
|
||||
}
|
||||
@@ -25,7 +29,11 @@ func PlanToResponse(plan *models.Plan) *PlanResponse {
|
||||
if plan.ContentType == models.PlanContentTypeSubPlans {
|
||||
response.SubPlans = make([]SubPlanResponse, len(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 {
|
||||
response.Tasks = make([]TaskResponse, len(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模型,并进行业务规则验证
|
||||
@@ -50,6 +62,7 @@ func PlanFromCreateRequest(req *CreatePlanRequest) (*models.Plan, error) {
|
||||
Name: req.Name,
|
||||
Description: req.Description,
|
||||
ExecutionType: req.ExecutionType,
|
||||
ExecuteNum: req.ExecuteNum,
|
||||
CronExpression: req.CronExpression,
|
||||
ContentType: req.ContentType,
|
||||
}
|
||||
@@ -69,8 +82,11 @@ func PlanFromCreateRequest(req *CreatePlanRequest) (*models.Plan, error) {
|
||||
if req.ContentType == models.PlanContentTypeTasks && req.Tasks != nil {
|
||||
plan.Tasks = make([]models.Task, len(req.Tasks))
|
||||
for i, taskReq := range req.Tasks {
|
||||
// 使用来自请求的ExecutionOrder
|
||||
plan.Tasks[i] = TaskFromRequest(&taskReq)
|
||||
task, err := 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,
|
||||
Description: req.Description,
|
||||
ExecutionType: req.ExecutionType,
|
||||
ExecuteNum: req.ExecuteNum,
|
||||
CronExpression: req.CronExpression,
|
||||
ContentType: req.ContentType,
|
||||
}
|
||||
@@ -115,8 +132,11 @@ func PlanFromUpdateRequest(req *UpdatePlanRequest) (*models.Plan, error) {
|
||||
if req.ContentType == models.PlanContentTypeTasks && req.Tasks != nil {
|
||||
plan.Tasks = make([]models.Task, len(req.Tasks))
|
||||
for i, taskReq := range req.Tasks {
|
||||
// 使用来自请求的ExecutionOrder
|
||||
plan.Tasks[i] = TaskFromRequest(&taskReq)
|
||||
task, err := 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
|
||||
func SubPlanToResponse(subPlan *models.SubPlan) SubPlanResponse {
|
||||
func SubPlanToResponse(subPlan *models.SubPlan) (SubPlanResponse, error) {
|
||||
if subPlan == nil {
|
||||
return SubPlanResponse{}
|
||||
return SubPlanResponse{}, nil
|
||||
}
|
||||
|
||||
response := SubPlanResponse{
|
||||
@@ -147,16 +167,27 @@ func SubPlanToResponse(subPlan *models.SubPlan) SubPlanResponse {
|
||||
|
||||
// 如果有完整的子计划数据,也进行转换
|
||||
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
|
||||
func TaskToResponse(task *models.Task) TaskResponse {
|
||||
func TaskToResponse(task *models.Task) (TaskResponse, error) {
|
||||
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{
|
||||
@@ -166,14 +197,19 @@ func TaskToResponse(task *models.Task) TaskResponse {
|
||||
Description: task.Description,
|
||||
ExecutionOrder: task.ExecutionOrder,
|
||||
Type: task.Type,
|
||||
Parameters: controller.Properties(task.Parameters),
|
||||
}
|
||||
Parameters: params,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// TaskFromRequest 将TaskRequest转换为Task模型
|
||||
func TaskFromRequest(req *TaskRequest) models.Task {
|
||||
func TaskFromRequest(req *TaskRequest) (models.Task, error) {
|
||||
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{
|
||||
@@ -181,6 +217,6 @@ func TaskFromRequest(req *TaskRequest) models.Task {
|
||||
Description: req.Description,
|
||||
ExecutionOrder: req.ExecutionOrder,
|
||||
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:"猪舍温度控制计划"`
|
||||
Description string `json:"description" example:"根据温度自动调节风扇和加热器"`
|
||||
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 * * *"`
|
||||
ContentType models.PlanContentType `json:"content_type" binding:"required" example:"tasks"`
|
||||
SubPlanIDs []uint `json:"sub_plan_ids,omitempty"`
|
||||
@@ -32,6 +33,9 @@ type PlanResponse struct {
|
||||
Name string `json:"name" example:"猪舍温度控制计划"`
|
||||
Description string `json:"description" example:"根据温度自动调节风扇和加热器"`
|
||||
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 * * *"`
|
||||
ContentType models.PlanContentType `json:"content_type" example:"tasks"`
|
||||
SubPlans []SubPlanResponse `json:"sub_plans,omitempty"`
|
||||
@@ -49,6 +53,7 @@ type UpdatePlanRequest struct {
|
||||
Name string `json:"name" example:"猪舍温度控制计划V2"`
|
||||
Description string `json:"description" example:"更新后的描述"`
|
||||
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 * * *"`
|
||||
ContentType models.PlanContentType `json:"content_type" example:"tasks"`
|
||||
SubPlanIDs []uint `json:"sub_plan_ids,omitempty"`
|
||||
@@ -66,22 +71,22 @@ type SubPlanResponse struct {
|
||||
|
||||
// TaskRequest 定义任务请求结构体
|
||||
type TaskRequest struct {
|
||||
Name string `json:"name" example:"打开风扇"`
|
||||
Description string `json:"description" example:"打开1号风扇"`
|
||||
ExecutionOrder int `json:"execution_order" example:"1"`
|
||||
Type models.TaskType `json:"type" example:"waiting"`
|
||||
Parameters controller.Properties `json:"parameters,omitempty"`
|
||||
Name string `json:"name" example:"打开风扇"`
|
||||
Description string `json:"description" example:"打开1号风扇"`
|
||||
ExecutionOrder int `json:"execution_order" example:"1"`
|
||||
Type models.TaskType `json:"type" example:"waiting"`
|
||||
Parameters map[string]interface{} `json:"parameters,omitempty"`
|
||||
}
|
||||
|
||||
// TaskResponse 定义任务响应结构体
|
||||
type TaskResponse struct {
|
||||
ID int `json:"id" example:"1"`
|
||||
PlanID uint `json:"plan_id" example:"1"`
|
||||
Name string `json:"name" example:"打开风扇"`
|
||||
Description string `json:"description" example:"打开1号风扇"`
|
||||
ExecutionOrder int `json:"execution_order" example:"1"`
|
||||
Type models.TaskType `json:"type" example:"waiting"`
|
||||
Parameters controller.Properties `json:"parameters,omitempty"`
|
||||
ID int `json:"id" example:"1"`
|
||||
PlanID uint `json:"plan_id" example:"1"`
|
||||
Name string `json:"name" example:"打开风扇"`
|
||||
Description string `json:"description" example:"打开1号风扇"`
|
||||
ExecutionOrder int `json:"execution_order" example:"1"`
|
||||
Type models.TaskType `json:"type" example:"waiting"`
|
||||
Parameters map[string]interface{} `json:"parameters,omitempty"`
|
||||
}
|
||||
|
||||
// --- Controller 定义 ---
|
||||
@@ -112,8 +117,7 @@ func NewController(logger *logs.Logger, planRepo repository.PlanRepository, anal
|
||||
// @Produce json
|
||||
// @Param plan body CreatePlanRequest true "计划信息"
|
||||
// @Success 200 {object} controller.Response{data=plan.PlanResponse} "业务码为201代表创建成功"
|
||||
// @Failure 200 {object} controller.Response "业务失败,具体错误码和信息见响应体(例如400, 500)"
|
||||
// @Router /plans [post]
|
||||
// @Router /api/v1/plans [post]
|
||||
func (c *Controller) CreatePlan(ctx *gin.Context) {
|
||||
var req CreatePlanRequest
|
||||
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||
@@ -134,14 +138,19 @@ func (c *Controller) CreatePlan(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 创建成功后,调用 manager 创建或更新触发器
|
||||
if err := c.analysisPlanTaskManager.CreateOrUpdateTrigger(ctx, planToCreate.ID); err != nil {
|
||||
// 创建成功后,调用 manager 确保触发器任务定义存在,但不立即加入待执行队列
|
||||
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)
|
||||
@@ -154,8 +163,7 @@ func (c *Controller) CreatePlan(ctx *gin.Context) {
|
||||
// @Produce json
|
||||
// @Param id path int true "计划ID"
|
||||
// @Success 200 {object} controller.Response{data=plan.PlanResponse} "业务码为200代表成功获取"
|
||||
// @Failure 200 {object} controller.Response "业务失败,具体错误码和信息见响应体(例如400, 404, 500)"
|
||||
// @Router /plans/{id} [get]
|
||||
// @Router /api/v1/plans/{id} [get]
|
||||
func (c *Controller) GetPlan(ctx *gin.Context) {
|
||||
// 1. 从 URL 路径中获取 ID
|
||||
idStr := ctx.Param("id")
|
||||
@@ -180,7 +188,12 @@ func (c *Controller) GetPlan(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
// 3. 将模型转换为响应 DTO
|
||||
resp := PlanToResponse(plan)
|
||||
resp, err := PlanToResponse(plan)
|
||||
if err != nil {
|
||||
c.logger.Errorf("获取计划详情: 序列化响应失败: %v", err)
|
||||
controller.SendErrorResponse(ctx, controller.CodeInternalError, "获取计划详情失败: 内部数据格式错误")
|
||||
return
|
||||
}
|
||||
|
||||
// 4. 发送成功响应
|
||||
controller.SendResponse(ctx, controller.CodeSuccess, "获取计划详情成功", resp)
|
||||
@@ -192,8 +205,7 @@ func (c *Controller) GetPlan(ctx *gin.Context) {
|
||||
// @Tags 计划管理
|
||||
// @Produce json
|
||||
// @Success 200 {object} controller.Response{data=plan.ListPlansResponse} "业务码为200代表成功获取列表"
|
||||
// @Failure 200 {object} controller.Response "业务失败,具体错误码和信息见响应体(例如400, 500)"
|
||||
// @Router /plans [get]
|
||||
// @Router /api/v1/plans [get]
|
||||
func (c *Controller) ListPlans(ctx *gin.Context) {
|
||||
// 1. 调用仓库层获取所有计划
|
||||
plans, err := c.planRepo.ListBasicPlans()
|
||||
@@ -206,7 +218,13 @@ func (c *Controller) ListPlans(ctx *gin.Context) {
|
||||
// 2. 将模型转换为响应 DTO
|
||||
planResponses := make([]PlanResponse, 0, len(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. 构造并发送成功响应
|
||||
@@ -226,8 +244,7 @@ func (c *Controller) ListPlans(ctx *gin.Context) {
|
||||
// @Param id path int true "计划ID"
|
||||
// @Param plan body UpdatePlanRequest true "更新后的计划信息"
|
||||
// @Success 200 {object} controller.Response{data=plan.PlanResponse} "业务码为200代表更新成功"
|
||||
// @Failure 200 {object} controller.Response "业务失败,具体错误码和信息见响应体(例如400, 404, 500)"
|
||||
// @Router /plans/{id} [put]
|
||||
// @Router /api/v1/plans/{id} [put]
|
||||
func (c *Controller) UpdatePlan(ctx *gin.Context) {
|
||||
// 1. 从 URL 路径中获取 ID
|
||||
idStr := ctx.Param("id")
|
||||
@@ -270,10 +287,10 @@ func (c *Controller) UpdatePlan(ctx *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 更新成功后,调用 manager 创建或更新触发器
|
||||
if err := c.analysisPlanTaskManager.CreateOrUpdateTrigger(ctx, planToUpdate.ID); err != nil {
|
||||
// 更新成功后,调用 manager 确保触发器任务定义存在
|
||||
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. 获取更新后的完整计划用于响应
|
||||
@@ -285,7 +302,12 @@ func (c *Controller) UpdatePlan(ctx *gin.Context) {
|
||||
}
|
||||
|
||||
// 7. 将模型转换为响应 DTO
|
||||
resp := PlanToResponse(updatedPlan)
|
||||
resp, err := PlanToResponse(updatedPlan)
|
||||
if err != nil {
|
||||
c.logger.Errorf("更新计划: 序列化响应失败: %v", err)
|
||||
controller.SendErrorResponse(ctx, controller.CodeInternalError, "计划更新成功,但响应生成失败")
|
||||
return
|
||||
}
|
||||
|
||||
// 8. 发送成功响应
|
||||
controller.SendResponse(ctx, controller.CodeSuccess, "计划更新成功", resp)
|
||||
@@ -298,8 +320,7 @@ func (c *Controller) UpdatePlan(ctx *gin.Context) {
|
||||
// @Produce json
|
||||
// @Param id path int true "计划ID"
|
||||
// @Success 200 {object} controller.Response "业务码为200代表删除成功"
|
||||
// @Failure 200 {object} controller.Response "业务失败,具体错误码和信息见响应体(例如400, 404, 500)"
|
||||
// @Router /plans/{id} [delete]
|
||||
// @Router /api/v1/plans/{id} [delete]
|
||||
func (c *Controller) DeletePlan(ctx *gin.Context) {
|
||||
// 1. 从 URL 路径中获取 ID
|
||||
idStr := ctx.Param("id")
|
||||
@@ -327,8 +348,7 @@ func (c *Controller) DeletePlan(ctx *gin.Context) {
|
||||
// @Produce json
|
||||
// @Param id path int true "计划ID"
|
||||
// @Success 200 {object} controller.Response "业务码为200代表成功启动计划"
|
||||
// @Failure 200 {object} controller.Response "业务失败,具体错误码和信息见响应体(例如400, 404, 500)"
|
||||
// @Router /plans/{id}/start [post]
|
||||
// @Router /api/v1/plans/{id}/start [post]
|
||||
func (c *Controller) StartPlan(ctx *gin.Context) {
|
||||
// 占位符:此处应调用服务层或仓库层来启动计划
|
||||
c.logger.Infof("收到启动计划请求 (占位符)")
|
||||
@@ -342,8 +362,7 @@ func (c *Controller) StartPlan(ctx *gin.Context) {
|
||||
// @Produce json
|
||||
// @Param id path int true "计划ID"
|
||||
// @Success 200 {object} controller.Response "业务码为200代表成功停止计划"
|
||||
// @Failure 200 {object} controller.Response "业务失败,具体错误码和信息见响应体(例如400, 404, 500)"
|
||||
// @Router /plans/{id}/stop [post]
|
||||
// @Router /api/v1/plans/{id}/stop [post]
|
||||
func (c *Controller) StopPlan(ctx *gin.Context) {
|
||||
// 占位符:此处应调用服务层或仓库层来停止计划
|
||||
c.logger.Infof("收到停止计划请求 (占位符)")
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"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) {
|
||||
SendResponse(ctx, code, message, nil)
|
||||
}
|
||||
|
||||
// Properties 是一个自定义类型,用于在 Swagger 中正确表示 JSON 对象
|
||||
type Properties json.RawMessage
|
||||
|
||||
@@ -59,8 +59,7 @@ type LoginResponse struct {
|
||||
// @Produce json
|
||||
// @Param user body CreateUserRequest true "用户信息"
|
||||
// @Success 200 {object} controller.Response{data=user.CreateUserResponse} "业务码为201代表创建成功"
|
||||
// @Failure 200 {object} controller.Response "业务失败,具体错误码和信息见响应体(例如400, 409, 500)"
|
||||
// @Router /users [post]
|
||||
// @Router /api/v1/users [post]
|
||||
func (c *Controller) CreateUser(ctx *gin.Context) {
|
||||
var req CreateUserRequest
|
||||
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||
@@ -103,8 +102,7 @@ func (c *Controller) CreateUser(ctx *gin.Context) {
|
||||
// @Produce json
|
||||
// @Param credentials body LoginRequest true "登录凭证"
|
||||
// @Success 200 {object} controller.Response{data=user.LoginResponse} "业务码为200代表登录成功"
|
||||
// @Failure 200 {object} controller.Response "业务失败,具体错误码和信息见响应体(例如400, 401, 500)"
|
||||
// @Router /users/login [post]
|
||||
// @Router /api/v1/users/login [post]
|
||||
func (c *Controller) Login(ctx *gin.Context) {
|
||||
var req LoginRequest
|
||||
if err := ctx.ShouldBindJSON(&req); err != nil {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||
"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"
|
||||
)
|
||||
|
||||
// AnalysisPlanTaskManager 封装了创建和更新计划分析任务(即触发器)的逻辑。
|
||||
// 这是一个可被 Scheduler 和其他应用服务(如 PlanService)共享的无状态组件。
|
||||
// AnalysisPlanTaskManager 负责管理分析计划的触发器任务。
|
||||
// 它确保数据库中可执行的计划在待执行队列中有对应的触发器,并移除无效的触发器。
|
||||
// 这是一个有状态的组件,包含一个互斥锁以确保并发安全。
|
||||
type AnalysisPlanTaskManager struct {
|
||||
planRepo repository.PlanRepository
|
||||
pendingTaskRepo repository.PendingTaskRepository
|
||||
executionLogRepo repository.ExecutionLogRepository
|
||||
logger *logs.Logger
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// NewAnalysisPlanTaskManager 是 AnalysisPlanTaskManager 的构造函数。
|
||||
@@ -33,50 +37,281 @@ func NewAnalysisPlanTaskManager(
|
||||
}
|
||||
}
|
||||
|
||||
// CreateOrUpdateTrigger 为给定的 planID 创建或更新其关联的下一次触发任务。
|
||||
// 这个方法是幂等的,可以安全地被多次调用。
|
||||
func (m *AnalysisPlanTaskManager) CreateOrUpdateTrigger(ctx context.Context, planID uint) error {
|
||||
// 获取计划信息
|
||||
plan, err := m.planRepo.GetBasicPlanByID(planID)
|
||||
// Refresh 同步数据库中的计划状态和待执行队列中的触发器任务。
|
||||
// 这是一个编排方法,将复杂的逻辑分解到多个内部方法中。
|
||||
func (m *AnalysisPlanTaskManager) Refresh() error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
m.logger.Info("开始同步计划任务管理器...")
|
||||
|
||||
// 1. 一次性获取所有需要的数据
|
||||
runnablePlans, invalidPlanIDs, pendingTasks, err := m.getRefreshData()
|
||||
if err != nil {
|
||||
m.logger.Errorf("[严重] 获取计划失败, 错误: %v", err)
|
||||
return err
|
||||
return fmt.Errorf("获取刷新数据失败: %w", err)
|
||||
}
|
||||
|
||||
// 获取触发任务
|
||||
task, err := m.planRepo.FindPlanAnalysisTaskByParamsPlanID(planID)
|
||||
if err != nil {
|
||||
m.logger.Errorf("[严重] 获取计划解析任务失败, 错误: %v", err)
|
||||
return err
|
||||
// 2. 清理所有与失效计划相关的待执行任务
|
||||
if err := m.cleanupInvalidTasks(invalidPlanIDs, pendingTasks); err != nil {
|
||||
// 仅记录错误,清理失败不应阻止新任务的添加
|
||||
m.logger.Errorf("清理无效任务时出错: %v", err)
|
||||
}
|
||||
|
||||
// 写入执行日志
|
||||
taskLog := &models.TaskExecutionLog{
|
||||
TaskID: task.ID,
|
||||
Status: models.ExecutionStatusWaiting,
|
||||
}
|
||||
if err := m.executionLogRepo.CreateTaskExecutionLogsInBatch([]*models.TaskExecutionLog{taskLog}); err != nil {
|
||||
m.logger.Errorf("[严重] 创建任务执行日志失败, 错误: %v", err)
|
||||
return err
|
||||
// 3. 添加或更新触发器
|
||||
if err := m.addOrUpdateTriggers(runnablePlans, pendingTasks); err != nil {
|
||||
return fmt.Errorf("添加或更新触发器时出错: %w", err)
|
||||
}
|
||||
|
||||
// 写入待执行队列
|
||||
next, err := utils.GetNextCronTime(plan.CronExpression)
|
||||
if err != nil {
|
||||
m.logger.Errorf("[严重] 执行时间解析失败, 错误: %v", err)
|
||||
return err
|
||||
}
|
||||
pendingTask := &models.PendingTask{
|
||||
TaskID: task.ID,
|
||||
ExecuteAt: next,
|
||||
TaskExecutionLogID: taskLog.ID,
|
||||
}
|
||||
err = m.pendingTaskRepo.CreatePendingTasksInBatch([]*models.PendingTask{pendingTask})
|
||||
if err != nil {
|
||||
m.logger.Errorf("[严重] 创建待执行任务失败, 错误: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
m.logger.Infof("成功为 Plan %d 创建/更新了下一次的触发任务,执行时间: %v", planID, next)
|
||||
m.logger.Info("计划任务管理器同步完成.")
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateOrUpdateTrigger 为给定的 planID 创建其关联的触发任务。
|
||||
// 这个方法是幂等的:如果一个有效的触发器已存在,它将不会重复创建。
|
||||
// 关键修改:如果触发器已存在,会根据计划类型更新其执行时间。
|
||||
func (m *AnalysisPlanTaskManager) CreateOrUpdateTrigger(planID uint) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
// 检查计划是否可执行
|
||||
plan, err := m.planRepo.GetBasicPlanByID(planID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取计划基本信息失败: %w", err)
|
||||
}
|
||||
if plan.Status != models.PlanStatusEnabled {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -112,10 +112,9 @@ type Scheduler struct {
|
||||
progressTracker *ProgressTracker
|
||||
taskFactory func(taskType models.TaskType) Task // 调度器需要注入一个任务工厂,用于创建任务实例
|
||||
|
||||
pool *ants.Pool // 使用 ants 协程池来管理并发
|
||||
wg sync.WaitGroup
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
pool *ants.Pool // 使用 ants 协程池来管理并发
|
||||
wg sync.WaitGroup
|
||||
stopChan chan struct{} // 用于停止主循环的信号通道
|
||||
}
|
||||
|
||||
// NewScheduler 创建一个新的调度器实例
|
||||
@@ -128,8 +127,6 @@ func NewScheduler(
|
||||
logger *logs.Logger,
|
||||
interval time.Duration,
|
||||
numWorkers int) *Scheduler {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
return &Scheduler{
|
||||
pendingTaskRepo: pendingTaskRepo,
|
||||
executionLogRepo: executionLogRepo,
|
||||
@@ -140,8 +137,7 @@ func NewScheduler(
|
||||
workers: numWorkers,
|
||||
progressTracker: NewProgressTracker(),
|
||||
taskFactory: taskFactory,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
stopChan: make(chan struct{}), // 初始化停止信号通道
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,9 +160,9 @@ func (s *Scheduler) Start() {
|
||||
// Stop 优雅地停止调度器
|
||||
func (s *Scheduler) Stop() {
|
||||
s.logger.Warnf("正在停止任务调度器...")
|
||||
s.cancel() // 1. 发出取消信号,停止主循环
|
||||
s.wg.Wait() // 2. 等待主循环完成
|
||||
s.pool.Release() // 3. 释放 ants 池 (等待所有已提交的任务执行完毕)
|
||||
close(s.stopChan) // 1. 发出停止信号,停止主循环
|
||||
s.wg.Wait() // 2. 等待主循环完成
|
||||
s.pool.Release() // 3. 释放 ants 池 (等待所有已提交的任务执行完毕)
|
||||
s.logger.Warnf("任务调度器已安全停止")
|
||||
}
|
||||
|
||||
@@ -178,9 +174,11 @@ func (s *Scheduler) run() {
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-s.ctx.Done():
|
||||
case <-s.stopChan:
|
||||
// 收到停止信号,退出循环
|
||||
return
|
||||
case <-ticker.C:
|
||||
// 定时触发任务认领和提交
|
||||
go s.claimAndSubmit()
|
||||
}
|
||||
}
|
||||
@@ -236,7 +234,7 @@ func (s *Scheduler) handleRequeue(planExecutionLogID uint, taskToRequeue *models
|
||||
s.logger.Warnf("任务 (原始ID: %d) 已成功重新入队,并已释放计划 %d 的锁。", taskToRequeue.ID, planExecutionLogID)
|
||||
}
|
||||
|
||||
// processTask 处理单个任务的逻辑 (当前为占位符)
|
||||
// processTask 处理单个任务的逻辑
|
||||
func (s *Scheduler) processTask(claimedLog *models.TaskExecutionLog) {
|
||||
s.logger.Warnf("开始处理任务, 日志ID: %d, 任务ID: %d, 任务名称: %s",
|
||||
claimedLog.ID, claimedLog.TaskID, claimedLog.Task.Name)
|
||||
@@ -256,10 +254,40 @@ func (s *Scheduler) processTask(claimedLog *models.TaskExecutionLog) {
|
||||
|
||||
// 任务计数器校验, Plan的任务全部执行完成后需要插入一个新的PlanAnalysisTask用于触发下一次Plan的执行
|
||||
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 {
|
||||
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列表插入待执行队列中
|
||||
func (s *Scheduler) analysisPlan(claimedLog *models.TaskExecutionLog) error {
|
||||
// 创建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{
|
||||
PlanID: claimedLog.Task.PlanID,
|
||||
PlanID: realPlanID, // 使用从参数中解析出的真实 PlanID
|
||||
Status: models.ExecutionStatusStarted,
|
||||
StartedAt: time.Now(),
|
||||
}
|
||||
@@ -312,7 +350,7 @@ func (s *Scheduler) analysisPlan(claimedLog *models.TaskExecutionLog) error {
|
||||
}
|
||||
|
||||
// 解析出Task列表
|
||||
tasks, err := s.planRepo.FlattenPlanTasks(claimedLog.Task.PlanID)
|
||||
tasks, err := s.planRepo.FlattenPlanTasks(realPlanID)
|
||||
if err != nil {
|
||||
s.logger.Errorf("[严重] 解析计划失败, 日志ID: %d, 错误: %v", claimedLog.ID, err)
|
||||
return err
|
||||
@@ -320,12 +358,12 @@ func (s *Scheduler) analysisPlan(claimedLog *models.TaskExecutionLog) error {
|
||||
|
||||
// 写入执行历史
|
||||
taskLogs := make([]*models.TaskExecutionLog, len(tasks))
|
||||
for _, task := range tasks {
|
||||
taskLogs = append(taskLogs, &models.TaskExecutionLog{
|
||||
for i, task := range tasks {
|
||||
taskLogs[i] = &models.TaskExecutionLog{
|
||||
PlanExecutionLogID: planLog.ID,
|
||||
TaskID: task.ID,
|
||||
Status: models.ExecutionStatusWaiting,
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
err = s.executionLogRepo.CreateTaskExecutionLogsInBatch(taskLogs)
|
||||
@@ -337,13 +375,13 @@ func (s *Scheduler) analysisPlan(claimedLog *models.TaskExecutionLog) error {
|
||||
// 写入待执行队列
|
||||
pendingTasks := make([]*models.PendingTask, len(tasks))
|
||||
for i, task := range tasks {
|
||||
pendingTasks = append(pendingTasks, &models.PendingTask{
|
||||
pendingTasks[i] = &models.PendingTask{
|
||||
TaskID: task.ID,
|
||||
TaskExecutionLogID: pendingTasks[i].ID,
|
||||
TaskExecutionLogID: taskLogs[i].ID, // 使用正确的 TaskExecutionLogID
|
||||
|
||||
// 待执行队列是通过任务触发时间排序的, 且只要在调度器获取的时间点之前的都可以被触发
|
||||
ExecuteAt: time.Now().Add(time.Duration(i) * time.Second),
|
||||
})
|
||||
}
|
||||
}
|
||||
err = s.pendingTaskRepo.CreatePendingTasksInBatch(pendingTasks)
|
||||
if err != nil {
|
||||
@@ -352,7 +390,7 @@ func (s *Scheduler) analysisPlan(claimedLog *models.TaskExecutionLog) error {
|
||||
}
|
||||
|
||||
// 将Task列表加入待执行队列中
|
||||
s.progressTracker.AddNewPlan(claimedLog.PlanExecutionLogID, len(tasks))
|
||||
s.progressTracker.AddNewPlan(planLog.ID, len(tasks))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -25,6 +25,12 @@ type Application struct {
|
||||
Storage database.Storage
|
||||
Executor *task.Scheduler
|
||||
API *api.API // 添加 API 对象
|
||||
|
||||
// 新增的仓库和管理器字段,以便在 initializePendingTasks 中访问
|
||||
planRepo repository.PlanRepository
|
||||
pendingTaskRepo repository.PendingTaskRepository
|
||||
executionLogRepo repository.ExecutionLogRepository
|
||||
analysisPlanTaskManager *task.AnalysisPlanTaskManager
|
||||
}
|
||||
|
||||
// NewApplication 创建并初始化一个新的 Application 实例。
|
||||
@@ -82,6 +88,11 @@ func NewApplication(configPath string) (*Application, error) {
|
||||
Storage: storage,
|
||||
Executor: executor,
|
||||
API: apiServer,
|
||||
// 填充新增的字段
|
||||
planRepo: planRepo,
|
||||
pendingTaskRepo: pendingTaskRepo,
|
||||
executionLogRepo: executionLogRepo,
|
||||
analysisPlanTaskManager: analysisPlanTaskManager,
|
||||
}
|
||||
|
||||
return app, nil
|
||||
@@ -91,6 +102,17 @@ func NewApplication(configPath string) (*Application, error) {
|
||||
func (app *Application) Start() error {
|
||||
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()
|
||||
|
||||
@@ -128,6 +150,104 @@ func (app *Application) Stop() error {
|
||||
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 封装了数据库的初始化、连接和迁移逻辑。
|
||||
func initStorage(cfg config.DatabaseConfig, logger *logs.Logger) (database.Storage, error) {
|
||||
// 创建存储实例
|
||||
|
||||
@@ -70,7 +70,7 @@ type Device struct {
|
||||
gorm.Model
|
||||
|
||||
// Name 是设备的业务名称,应清晰可读,例如 "1号猪舍温度传感器" 或 "做料车间主控"
|
||||
Name string `gorm:"unique;not null" json:"name"`
|
||||
Name string `gorm:"not null" json:"name"`
|
||||
|
||||
// Type 是设备的高级类别,用于区分区域主控和普通设备。建立索引以优化按类型查询。
|
||||
Type DeviceType `gorm:"not null;index" json:"type"`
|
||||
|
||||
@@ -38,6 +38,14 @@ const (
|
||||
ParamsPlanID = "plan_id"
|
||||
)
|
||||
|
||||
type PlanStatus uint8
|
||||
|
||||
const (
|
||||
PlanStatusEnabled PlanStatus = 0 // 启用计划
|
||||
PlanStatusDisabled PlanStatus = 1 // 禁用计划
|
||||
PlanStatusStopeed PlanStatus = 2 // 执行完毕
|
||||
)
|
||||
|
||||
// Plan 代表系统中的一个计划,可以包含子计划或任务
|
||||
type Plan struct {
|
||||
gorm.Model
|
||||
@@ -45,6 +53,9 @@ type Plan struct {
|
||||
Name string `gorm:"not null" json:"name"`
|
||||
Description string `json:"description"`
|
||||
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 表达式定义调度规则
|
||||
CronExpression string `json:"cron_expression"`
|
||||
|
||||
@@ -16,11 +16,13 @@ type PendingTask struct {
|
||||
|
||||
// TaskID 使用 int 类型以容纳特殊的负数ID,代表系统任务
|
||||
TaskID int `gorm:"index"`
|
||||
// Task 字段,用于在代码中访问关联的任务详情
|
||||
// GORM 会根据 TaskID 字段自动填充此关联
|
||||
Task *Task `gorm:"foreignKey:TaskID"`
|
||||
|
||||
ExecuteAt time.Time `gorm:"index"` // 任务执行时间
|
||||
TaskExecutionLogID uint `gorm:"unique;not null"` // 对应的执行历史记录ID
|
||||
|
||||
// 关联关系定义
|
||||
// 通过 TaskExecutionLogID 关联到唯一的 TaskExecutionLog 记录
|
||||
// ON DELETE CASCADE 确保如果日志被删除,这个待办任务也会被自动清理
|
||||
TaskExecutionLog TaskExecutionLog `gorm:"foreignKey:TaskExecutionLogID;references:ID;constraint:OnUpdate:CASCADE,OnDelete:CASCADE;"`
|
||||
|
||||
@@ -8,11 +8,22 @@ import (
|
||||
// ExecutionLogRepository 定义了与执行日志交互的接口。
|
||||
// 这为服务层提供了一个清晰的契约,并允许在测试中轻松地进行模拟。
|
||||
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
|
||||
UpdatePlanExecutionLog(log *models.PlanExecutionLog) error
|
||||
CreateTaskExecutionLogsInBatch(logs []*models.TaskExecutionLog) error
|
||||
UpdateTaskExecutionLog(log *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 的具体实现。
|
||||
@@ -26,6 +37,23 @@ func NewGormExecutionLogRepository(db *gorm.DB) ExecutionLogRepository {
|
||||
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 为一次计划执行创建一条新的日志条目。
|
||||
func (r *gormExecutionLogRepository) CreatePlanExecutionLog(log *models.PlanExecutionLog) error {
|
||||
return r.db.Create(log).Error
|
||||
@@ -41,6 +69,9 @@ func (r *gormExecutionLogRepository) UpdatePlanExecutionLog(log *models.PlanExec
|
||||
// CreateTaskExecutionLogsInBatch 在一次数据库调用中创建多个任务执行日志条目。
|
||||
// 这是“预写日志”步骤的关键。
|
||||
func (r *gormExecutionLogRepository) CreateTaskExecutionLogsInBatch(logs []*models.TaskExecutionLog) error {
|
||||
if len(logs) == 0 {
|
||||
return nil
|
||||
}
|
||||
// GORM 的 Create 传入一个切片指针会执行批量插入。
|
||||
return r.db.Create(&logs).Error
|
||||
}
|
||||
@@ -63,3 +94,23 @@ func (r *gormExecutionLogRepository) FindTaskExecutionLogByID(id uint) (*models.
|
||||
}
|
||||
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
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
@@ -10,7 +12,18 @@ import (
|
||||
|
||||
// PendingTaskRepository 定义了与待执行任务队列交互的接口。
|
||||
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
|
||||
|
||||
// UpdatePendingTaskExecuteAt 更新指定待执行任务的执行时间
|
||||
UpdatePendingTaskExecuteAt(id uint, executeAt time.Time) error
|
||||
|
||||
// ClearAllPendingTasks 清空所有待执行任务
|
||||
ClearAllPendingTasks() error
|
||||
|
||||
// ClaimNextAvailableTask 原子地认领下一个可用的任务。
|
||||
// 它会同时返回被认领任务对应的日志对象,以及被删除的待办任务对象的内存副本。
|
||||
ClaimNextAvailableTask(excludePlanIDs []uint) (*models.TaskExecutionLog, *models.PendingTask, error)
|
||||
@@ -28,11 +41,56 @@ func NewGormPendingTaskRepository(db *gorm.DB) PendingTaskRepository {
|
||||
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 在一次数据库调用中创建多个待执行任务条目。
|
||||
func (r *gormPendingTaskRepository) CreatePendingTasksInBatch(tasks []*models.PendingTask) error {
|
||||
if len(tasks) == 0 {
|
||||
return nil
|
||||
}
|
||||
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 以原子方式认领下一个可用的任务。
|
||||
func (r *gormPendingTaskRepository) ClaimNextAvailableTask(excludePlanIDs []uint) (*models.TaskExecutionLog, *models.PendingTask, error) {
|
||||
var log models.TaskExecutionLog
|
||||
|
||||
@@ -42,6 +42,18 @@ type PlanRepository interface {
|
||||
DeleteTask(id int) error
|
||||
// FindPlanAnalysisTaskByParamsPlanID 根据Parameters中的ParamsPlanID字段值查找TaskPlanAnalysis类型的Task
|
||||
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 实现
|
||||
@@ -174,7 +186,9 @@ func (r *gormPlanRepository) CreatePlan(plan *models.Plan) error {
|
||||
}
|
||||
|
||||
// 3. 创建触发器Task
|
||||
if err := r.createPlanAnalysisTask(tx, plan); err != nil {
|
||||
// 关键修改:调用 createPlanAnalysisTask 并处理其返回的 Task 对象
|
||||
_, err := r.createPlanAnalysisTask(tx, plan)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@@ -275,7 +289,7 @@ func (r *gormPlanRepository) reconcilePlanNode(tx *gorm.DB, plan *models.Plan) e
|
||||
return nil
|
||||
}
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -517,63 +531,115 @@ func (r *gormPlanRepository) deleteTask(tx *gorm.DB, id int) error {
|
||||
|
||||
// FindPlanAnalysisTaskByParamsPlanID 根据Parameters中的ParamsPlanID字段值查找TaskPlanAnalysis类型的Task
|
||||
func (r *gormPlanRepository) FindPlanAnalysisTaskByParamsPlanID(paramsPlanID uint) (*models.Task, error) {
|
||||
return r.findPlanAnalysisTaskByParamsPlanID(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
|
||||
return r.findPlanAnalysisTask(r.db, paramsPlanID)
|
||||
}
|
||||
|
||||
// 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{}{
|
||||
models.ParamsPlanID: plan.ID,
|
||||
}
|
||||
parameters, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
task := &models.Task{
|
||||
PlanID: plan.ID,
|
||||
Name: fmt.Sprintf("'%v'计划触发器", plan.Name),
|
||||
Description: fmt.Sprintf("计划名: %v, 计划ID: %v", plan.Name, plan.ID),
|
||||
ExecutionOrder: 0,
|
||||
PlanID: 0, // 关键:设置为 0,避免被常规 PlanID 查询查到
|
||||
Name: fmt.Sprintf("'%s'计划触发器", plan.Name),
|
||||
Description: fmt.Sprintf("计划名: %s, 计划ID: %d", plan.Name, plan.ID),
|
||||
ExecutionOrder: 0, // 触发器任务的执行顺序通常为0或不关心
|
||||
Type: models.TaskPlanAnalysis,
|
||||
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 {
|
||||
task, err := r.findPlanAnalysisTaskByParamsPlanID(tx, plan.ID)
|
||||
if err != nil {
|
||||
task, err := r.findPlanAnalysisTask(tx, plan.ID)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return fmt.Errorf("查找现有计划分析任务失败: %w", err)
|
||||
}
|
||||
|
||||
// 如果触发器任务不存在,则创建一个
|
||||
if task == nil {
|
||||
_, err := r.createPlanAnalysisTask(tx, plan)
|
||||
return err
|
||||
}
|
||||
err = r.deleteTask(tx, task.ID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return r.createPlanAnalysisTask(tx, plan)
|
||||
|
||||
// 如果存在,则更新它的名称和描述以反映计划的最新信息
|
||||
task.Name = fmt.Sprintf("'%s'计划触发器", plan.Name)
|
||||
task.Description = fmt.Sprintf("计划名: %s, 计划ID: %d", plan.Name, plan.ID)
|
||||
|
||||
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