Merge pull request 'issue_49' (#51) from issue_49 into main
Reviewed-on: #51
This commit is contained in:
305
docs/docs.go
305
docs/docs.go
@@ -756,17 +756,17 @@ const docTemplate = `{
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "device_id",
|
||||
"name": "deviceID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -781,12 +781,12 @@ const docTemplate = `{
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"name": "received_success",
|
||||
"name": "receivedSuccess",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
@@ -830,22 +830,22 @@ const docTemplate = `{
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "feed_formula_id",
|
||||
"name": "feedFormulaID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "operator_id",
|
||||
"name": "operatorID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -860,12 +860,12 @@ const docTemplate = `{
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "pen_id",
|
||||
"name": "penID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
@@ -909,22 +909,22 @@ const docTemplate = `{
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "medication_id",
|
||||
"name": "medicationID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "operator_id",
|
||||
"name": "operatorID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -939,7 +939,7 @@ const docTemplate = `{
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "pig_batch_id",
|
||||
"name": "pigBatchID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -949,7 +949,7 @@ const docTemplate = `{
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
@@ -993,12 +993,11 @@ const docTemplate = `{
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
7,
|
||||
-1,
|
||||
0,
|
||||
1,
|
||||
@@ -1008,12 +1007,12 @@ const docTemplate = `{
|
||||
5,
|
||||
-1,
|
||||
5,
|
||||
6
|
||||
6,
|
||||
7
|
||||
],
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"x-enum-varnames": [
|
||||
"_numLevels",
|
||||
"DebugLevel",
|
||||
"InfoLevel",
|
||||
"WarnLevel",
|
||||
@@ -1023,7 +1022,8 @@ const docTemplate = `{
|
||||
"FatalLevel",
|
||||
"_minLevel",
|
||||
"_maxLevel",
|
||||
"InvalidLevel"
|
||||
"InvalidLevel",
|
||||
"_numLevels"
|
||||
],
|
||||
"name": "level",
|
||||
"in": "query"
|
||||
@@ -1042,12 +1042,12 @@ const docTemplate = `{
|
||||
"NotifierTypeLark",
|
||||
"NotifierTypeLog"
|
||||
],
|
||||
"name": "notifier_type",
|
||||
"name": "notifierType",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1062,7 +1062,7 @@ const docTemplate = `{
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1092,7 +1092,7 @@ const docTemplate = `{
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "user_id",
|
||||
"name": "userID",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
@@ -1136,17 +1136,17 @@ const docTemplate = `{
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "device_id",
|
||||
"name": "deviceID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1161,7 +1161,7 @@ const docTemplate = `{
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1210,22 +1210,22 @@ const docTemplate = `{
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "change_type",
|
||||
"name": "changeType",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "operator_id",
|
||||
"name": "operatorID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1240,12 +1240,12 @@ const docTemplate = `{
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "pig_batch_id",
|
||||
"name": "pigBatchID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
@@ -1289,17 +1289,17 @@ const docTemplate = `{
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "operator_id",
|
||||
"name": "operatorID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1314,12 +1314,12 @@ const docTemplate = `{
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "pig_batch_id",
|
||||
"name": "pigBatchID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1373,17 +1373,17 @@ const docTemplate = `{
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "operator_id",
|
||||
"name": "operatorID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1398,12 +1398,12 @@ const docTemplate = `{
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "pig_batch_id",
|
||||
"name": "pigBatchID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
@@ -1447,17 +1447,17 @@ const docTemplate = `{
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "operator_id",
|
||||
"name": "operatorID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1472,12 +1472,12 @@ const docTemplate = `{
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "pen_id",
|
||||
"name": "penID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "pig_batch_id",
|
||||
"name": "pigBatchID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1487,12 +1487,12 @@ const docTemplate = `{
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "treatment_location",
|
||||
"name": "treatmentLocation",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
@@ -1536,22 +1536,22 @@ const docTemplate = `{
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "correlation_id",
|
||||
"name": "correlationID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "operator_id",
|
||||
"name": "operatorID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1566,22 +1566,22 @@ const docTemplate = `{
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "pen_id",
|
||||
"name": "penID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "pig_batch_id",
|
||||
"name": "pigBatchID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "transfer_type",
|
||||
"name": "transferType",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
@@ -1625,12 +1625,12 @@ const docTemplate = `{
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1645,12 +1645,12 @@ const docTemplate = `{
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "plan_id",
|
||||
"name": "planID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1699,12 +1699,12 @@ const docTemplate = `{
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1719,12 +1719,12 @@ const docTemplate = `{
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "raw_material_id",
|
||||
"name": "rawMaterialID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1773,12 +1773,12 @@ const docTemplate = `{
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1793,22 +1793,22 @@ const docTemplate = `{
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "raw_material_id",
|
||||
"name": "rawMaterialID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "source_id",
|
||||
"name": "sourceID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "source_type",
|
||||
"name": "sourceType",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
@@ -1852,17 +1852,17 @@ const docTemplate = `{
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "device_id",
|
||||
"name": "deviceID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1877,12 +1877,12 @@ const docTemplate = `{
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "sensor_type",
|
||||
"name": "sensorType",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
@@ -1926,12 +1926,12 @@ const docTemplate = `{
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1946,12 +1946,12 @@ const docTemplate = `{
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "plan_execution_log_id",
|
||||
"name": "planExecutionLogID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1961,7 +1961,7 @@ const docTemplate = `{
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "task_id",
|
||||
"name": "taskID",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
@@ -2005,17 +2005,17 @@ const docTemplate = `{
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "action_type",
|
||||
"name": "actionType",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -2030,7 +2030,7 @@ const docTemplate = `{
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -2040,7 +2040,7 @@ const docTemplate = `{
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "user_id",
|
||||
"name": "userID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -2089,12 +2089,12 @@ const docTemplate = `{
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -2109,12 +2109,12 @@ const docTemplate = `{
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "pig_batch_id",
|
||||
"name": "pigBatchID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
@@ -2158,17 +2158,17 @@ const docTemplate = `{
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "operator_id",
|
||||
"name": "operatorID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -2183,17 +2183,17 @@ const docTemplate = `{
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "pen_id",
|
||||
"name": "penID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "weighing_batch_id",
|
||||
"name": "weighingBatchID",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
@@ -3415,7 +3415,7 @@ const docTemplate = `{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "创建一个新的猪舍",
|
||||
"description": "根据提供的信息创建一个新猪舍",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
@@ -4003,97 +4003,6 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/users/{id}/history": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "根据用户ID,分页获取该用户的操作审计日志。支持与通用日志查询接口相同的过滤和排序参数。",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"用户管理"
|
||||
],
|
||||
"summary": "获取指定用户的操作历史",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "用户ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "action_type",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "pageSize",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "status",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "user_id",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "username",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "业务码为200代表成功获取",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/controller.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/dto.ListUserActionLogResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/users/{id}/notifications/test": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -4166,7 +4075,7 @@ const docTemplate = `{
|
||||
]
|
||||
},
|
||||
"data": {
|
||||
"description": "业务数据"
|
||||
"description": "业务数据, omitempty表示如果为空则不序列化"
|
||||
},
|
||||
"message": {
|
||||
"description": "提示信息",
|
||||
@@ -4428,6 +4337,7 @@ const docTemplate = `{
|
||||
},
|
||||
"execute_num": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"example": 10
|
||||
},
|
||||
"execution_type": {
|
||||
@@ -6316,6 +6226,7 @@ const docTemplate = `{
|
||||
},
|
||||
"execute_num": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"example": 10
|
||||
},
|
||||
"execution_type": {
|
||||
@@ -6930,7 +6841,6 @@ const docTemplate = `{
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"enum": [
|
||||
7,
|
||||
-1,
|
||||
0,
|
||||
1,
|
||||
@@ -6940,10 +6850,10 @@ const docTemplate = `{
|
||||
5,
|
||||
-1,
|
||||
5,
|
||||
6
|
||||
6,
|
||||
7
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"_numLevels",
|
||||
"DebugLevel",
|
||||
"InfoLevel",
|
||||
"WarnLevel",
|
||||
@@ -6953,7 +6863,8 @@ const docTemplate = `{
|
||||
"FatalLevel",
|
||||
"_minLevel",
|
||||
"_maxLevel",
|
||||
"InvalidLevel"
|
||||
"InvalidLevel",
|
||||
"_numLevels"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -748,17 +748,17 @@
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "device_id",
|
||||
"name": "deviceID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -773,12 +773,12 @@
|
||||
},
|
||||
{
|
||||
"type": "boolean",
|
||||
"name": "received_success",
|
||||
"name": "receivedSuccess",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
@@ -822,22 +822,22 @@
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "feed_formula_id",
|
||||
"name": "feedFormulaID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "operator_id",
|
||||
"name": "operatorID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -852,12 +852,12 @@
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "pen_id",
|
||||
"name": "penID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
@@ -901,22 +901,22 @@
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "medication_id",
|
||||
"name": "medicationID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "operator_id",
|
||||
"name": "operatorID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -931,7 +931,7 @@
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "pig_batch_id",
|
||||
"name": "pigBatchID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -941,7 +941,7 @@
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
@@ -985,12 +985,11 @@
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"enum": [
|
||||
7,
|
||||
-1,
|
||||
0,
|
||||
1,
|
||||
@@ -1000,12 +999,12 @@
|
||||
5,
|
||||
-1,
|
||||
5,
|
||||
6
|
||||
6,
|
||||
7
|
||||
],
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"x-enum-varnames": [
|
||||
"_numLevels",
|
||||
"DebugLevel",
|
||||
"InfoLevel",
|
||||
"WarnLevel",
|
||||
@@ -1015,7 +1014,8 @@
|
||||
"FatalLevel",
|
||||
"_minLevel",
|
||||
"_maxLevel",
|
||||
"InvalidLevel"
|
||||
"InvalidLevel",
|
||||
"_numLevels"
|
||||
],
|
||||
"name": "level",
|
||||
"in": "query"
|
||||
@@ -1034,12 +1034,12 @@
|
||||
"NotifierTypeLark",
|
||||
"NotifierTypeLog"
|
||||
],
|
||||
"name": "notifier_type",
|
||||
"name": "notifierType",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1054,7 +1054,7 @@
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1084,7 +1084,7 @@
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "user_id",
|
||||
"name": "userID",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
@@ -1128,17 +1128,17 @@
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "device_id",
|
||||
"name": "deviceID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1153,7 +1153,7 @@
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1202,22 +1202,22 @@
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "change_type",
|
||||
"name": "changeType",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "operator_id",
|
||||
"name": "operatorID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1232,12 +1232,12 @@
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "pig_batch_id",
|
||||
"name": "pigBatchID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
@@ -1281,17 +1281,17 @@
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "operator_id",
|
||||
"name": "operatorID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1306,12 +1306,12 @@
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "pig_batch_id",
|
||||
"name": "pigBatchID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1365,17 +1365,17 @@
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "operator_id",
|
||||
"name": "operatorID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1390,12 +1390,12 @@
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "pig_batch_id",
|
||||
"name": "pigBatchID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
@@ -1439,17 +1439,17 @@
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "operator_id",
|
||||
"name": "operatorID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1464,12 +1464,12 @@
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "pen_id",
|
||||
"name": "penID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "pig_batch_id",
|
||||
"name": "pigBatchID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1479,12 +1479,12 @@
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "treatment_location",
|
||||
"name": "treatmentLocation",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
@@ -1528,22 +1528,22 @@
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "correlation_id",
|
||||
"name": "correlationID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "operator_id",
|
||||
"name": "operatorID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1558,22 +1558,22 @@
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "pen_id",
|
||||
"name": "penID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "pig_batch_id",
|
||||
"name": "pigBatchID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "transfer_type",
|
||||
"name": "transferType",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
@@ -1617,12 +1617,12 @@
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1637,12 +1637,12 @@
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "plan_id",
|
||||
"name": "planID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1691,12 +1691,12 @@
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1711,12 +1711,12 @@
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "raw_material_id",
|
||||
"name": "rawMaterialID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1765,12 +1765,12 @@
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1785,22 +1785,22 @@
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "raw_material_id",
|
||||
"name": "rawMaterialID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "source_id",
|
||||
"name": "sourceID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "source_type",
|
||||
"name": "sourceType",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
@@ -1844,17 +1844,17 @@
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "device_id",
|
||||
"name": "deviceID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1869,12 +1869,12 @@
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "sensor_type",
|
||||
"name": "sensorType",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
@@ -1918,12 +1918,12 @@
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1938,12 +1938,12 @@
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "plan_execution_log_id",
|
||||
"name": "planExecutionLogID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -1953,7 +1953,7 @@
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "task_id",
|
||||
"name": "taskID",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
@@ -1997,17 +1997,17 @@
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "action_type",
|
||||
"name": "actionType",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -2022,7 +2022,7 @@
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -2032,7 +2032,7 @@
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "user_id",
|
||||
"name": "userID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -2081,12 +2081,12 @@
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -2101,12 +2101,12 @@
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "pig_batch_id",
|
||||
"name": "pigBatchID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
@@ -2150,17 +2150,17 @@
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"name": "endTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "operator_id",
|
||||
"name": "operatorID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"name": "orderBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
@@ -2175,17 +2175,17 @@
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "pen_id",
|
||||
"name": "penID",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"name": "startTime",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "weighing_batch_id",
|
||||
"name": "weighingBatchID",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
@@ -3407,7 +3407,7 @@
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "创建一个新的猪舍",
|
||||
"description": "根据提供的信息创建一个新猪舍",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
@@ -3995,97 +3995,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/users/{id}/history": {
|
||||
"get": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "根据用户ID,分页获取该用户的操作审计日志。支持与通用日志查询接口相同的过滤和排序参数。",
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"用户管理"
|
||||
],
|
||||
"summary": "获取指定用户的操作历史",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "用户ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "action_type",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "end_time",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "order_by",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "pageSize",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "start_time",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "status",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"name": "user_id",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"name": "username",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "业务码为200代表成功获取",
|
||||
"schema": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/controller.Response"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"$ref": "#/definitions/dto.ListUserActionLogResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/users/{id}/notifications/test": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -4158,7 +4067,7 @@
|
||||
]
|
||||
},
|
||||
"data": {
|
||||
"description": "业务数据"
|
||||
"description": "业务数据, omitempty表示如果为空则不序列化"
|
||||
},
|
||||
"message": {
|
||||
"description": "提示信息",
|
||||
@@ -4420,6 +4329,7 @@
|
||||
},
|
||||
"execute_num": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"example": 10
|
||||
},
|
||||
"execution_type": {
|
||||
@@ -6308,6 +6218,7 @@
|
||||
},
|
||||
"execute_num": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"example": 10
|
||||
},
|
||||
"execution_type": {
|
||||
@@ -6922,7 +6833,6 @@
|
||||
"type": "integer",
|
||||
"format": "int32",
|
||||
"enum": [
|
||||
7,
|
||||
-1,
|
||||
0,
|
||||
1,
|
||||
@@ -6932,10 +6842,10 @@
|
||||
5,
|
||||
-1,
|
||||
5,
|
||||
6
|
||||
6,
|
||||
7
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"_numLevels",
|
||||
"DebugLevel",
|
||||
"InfoLevel",
|
||||
"WarnLevel",
|
||||
@@ -6945,7 +6855,8 @@
|
||||
"FatalLevel",
|
||||
"_minLevel",
|
||||
"_maxLevel",
|
||||
"InvalidLevel"
|
||||
"InvalidLevel",
|
||||
"_numLevels"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@ definitions:
|
||||
- $ref: '#/definitions/controller.ResponseCode'
|
||||
description: 业务状态码
|
||||
data:
|
||||
description: 业务数据
|
||||
description: 业务数据, omitempty表示如果为空则不序列化
|
||||
message:
|
||||
description: 提示信息
|
||||
type: string
|
||||
@@ -196,6 +196,7 @@ definitions:
|
||||
type: string
|
||||
execute_num:
|
||||
example: 10
|
||||
minimum: 0
|
||||
type: integer
|
||||
execution_type:
|
||||
allOf:
|
||||
@@ -1459,6 +1460,7 @@ definitions:
|
||||
type: string
|
||||
execute_num:
|
||||
example: 10
|
||||
minimum: 0
|
||||
type: integer
|
||||
execution_type:
|
||||
allOf:
|
||||
@@ -1935,7 +1937,6 @@ definitions:
|
||||
- PlanTypeFilterSystem
|
||||
zapcore.Level:
|
||||
enum:
|
||||
- 7
|
||||
- -1
|
||||
- 0
|
||||
- 1
|
||||
@@ -1946,10 +1947,10 @@ definitions:
|
||||
- -1
|
||||
- 5
|
||||
- 6
|
||||
- 7
|
||||
format: int32
|
||||
type: integer
|
||||
x-enum-varnames:
|
||||
- _numLevels
|
||||
- DebugLevel
|
||||
- InfoLevel
|
||||
- WarnLevel
|
||||
@@ -1960,6 +1961,7 @@ definitions:
|
||||
- _minLevel
|
||||
- _maxLevel
|
||||
- InvalidLevel
|
||||
- _numLevels
|
||||
info:
|
||||
contact:
|
||||
email: divano@example.com
|
||||
@@ -2393,13 +2395,13 @@ paths:
|
||||
description: 根据提供的过滤条件,分页获取设备命令日志
|
||||
parameters:
|
||||
- in: query
|
||||
name: device_id
|
||||
name: deviceID
|
||||
type: integer
|
||||
- in: query
|
||||
name: end_time
|
||||
name: endTime
|
||||
type: string
|
||||
- in: query
|
||||
name: order_by
|
||||
name: orderBy
|
||||
type: string
|
||||
- in: query
|
||||
name: page
|
||||
@@ -2408,10 +2410,10 @@ paths:
|
||||
name: pageSize
|
||||
type: integer
|
||||
- in: query
|
||||
name: received_success
|
||||
name: receivedSuccess
|
||||
type: boolean
|
||||
- in: query
|
||||
name: start_time
|
||||
name: startTime
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
@@ -2435,16 +2437,16 @@ paths:
|
||||
description: 根据提供的过滤条件,分页获取饲料使用记录
|
||||
parameters:
|
||||
- in: query
|
||||
name: end_time
|
||||
name: endTime
|
||||
type: string
|
||||
- in: query
|
||||
name: feed_formula_id
|
||||
name: feedFormulaID
|
||||
type: integer
|
||||
- in: query
|
||||
name: operator_id
|
||||
name: operatorID
|
||||
type: integer
|
||||
- in: query
|
||||
name: order_by
|
||||
name: orderBy
|
||||
type: string
|
||||
- in: query
|
||||
name: page
|
||||
@@ -2453,10 +2455,10 @@ paths:
|
||||
name: pageSize
|
||||
type: integer
|
||||
- in: query
|
||||
name: pen_id
|
||||
name: penID
|
||||
type: integer
|
||||
- in: query
|
||||
name: start_time
|
||||
name: startTime
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
@@ -2480,16 +2482,16 @@ paths:
|
||||
description: 根据提供的过滤条件,分页获取用药记录
|
||||
parameters:
|
||||
- in: query
|
||||
name: end_time
|
||||
name: endTime
|
||||
type: string
|
||||
- in: query
|
||||
name: medication_id
|
||||
name: medicationID
|
||||
type: integer
|
||||
- in: query
|
||||
name: operator_id
|
||||
name: operatorID
|
||||
type: integer
|
||||
- in: query
|
||||
name: order_by
|
||||
name: orderBy
|
||||
type: string
|
||||
- in: query
|
||||
name: page
|
||||
@@ -2498,13 +2500,13 @@ paths:
|
||||
name: pageSize
|
||||
type: integer
|
||||
- in: query
|
||||
name: pig_batch_id
|
||||
name: pigBatchID
|
||||
type: integer
|
||||
- in: query
|
||||
name: reason
|
||||
type: string
|
||||
- in: query
|
||||
name: start_time
|
||||
name: startTime
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
@@ -2528,10 +2530,9 @@ paths:
|
||||
description: 根据提供的过滤条件,分页获取通知列表
|
||||
parameters:
|
||||
- in: query
|
||||
name: end_time
|
||||
name: endTime
|
||||
type: string
|
||||
- enum:
|
||||
- 7
|
||||
- -1
|
||||
- 0
|
||||
- 1
|
||||
@@ -2542,12 +2543,12 @@ paths:
|
||||
- -1
|
||||
- 5
|
||||
- 6
|
||||
- 7
|
||||
format: int32
|
||||
in: query
|
||||
name: level
|
||||
type: integer
|
||||
x-enum-varnames:
|
||||
- _numLevels
|
||||
- DebugLevel
|
||||
- InfoLevel
|
||||
- WarnLevel
|
||||
@@ -2558,13 +2559,14 @@ paths:
|
||||
- _minLevel
|
||||
- _maxLevel
|
||||
- InvalidLevel
|
||||
- _numLevels
|
||||
- enum:
|
||||
- 邮件
|
||||
- 企业微信
|
||||
- 飞书
|
||||
- 日志
|
||||
in: query
|
||||
name: notifier_type
|
||||
name: notifierType
|
||||
type: string
|
||||
x-enum-varnames:
|
||||
- NotifierTypeSMTP
|
||||
@@ -2572,7 +2574,7 @@ paths:
|
||||
- NotifierTypeLark
|
||||
- NotifierTypeLog
|
||||
- in: query
|
||||
name: order_by
|
||||
name: orderBy
|
||||
type: string
|
||||
- in: query
|
||||
name: page
|
||||
@@ -2581,7 +2583,7 @@ paths:
|
||||
name: pageSize
|
||||
type: integer
|
||||
- in: query
|
||||
name: start_time
|
||||
name: startTime
|
||||
type: string
|
||||
- enum:
|
||||
- 发送成功
|
||||
@@ -2603,7 +2605,7 @@ paths:
|
||||
- NotificationStatusFailed
|
||||
- NotificationStatusSkipped
|
||||
- in: query
|
||||
name: user_id
|
||||
name: userID
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
@@ -2627,13 +2629,13 @@ paths:
|
||||
description: 根据提供的过滤条件,分页获取待采集请求
|
||||
parameters:
|
||||
- in: query
|
||||
name: device_id
|
||||
name: deviceID
|
||||
type: integer
|
||||
- in: query
|
||||
name: end_time
|
||||
name: endTime
|
||||
type: string
|
||||
- in: query
|
||||
name: order_by
|
||||
name: orderBy
|
||||
type: string
|
||||
- in: query
|
||||
name: page
|
||||
@@ -2642,7 +2644,7 @@ paths:
|
||||
name: pageSize
|
||||
type: integer
|
||||
- in: query
|
||||
name: start_time
|
||||
name: startTime
|
||||
type: string
|
||||
- in: query
|
||||
name: status
|
||||
@@ -2669,16 +2671,16 @@ paths:
|
||||
description: 根据提供的过滤条件,分页获取猪批次日志
|
||||
parameters:
|
||||
- in: query
|
||||
name: change_type
|
||||
name: changeType
|
||||
type: string
|
||||
- in: query
|
||||
name: end_time
|
||||
name: endTime
|
||||
type: string
|
||||
- in: query
|
||||
name: operator_id
|
||||
name: operatorID
|
||||
type: integer
|
||||
- in: query
|
||||
name: order_by
|
||||
name: orderBy
|
||||
type: string
|
||||
- in: query
|
||||
name: page
|
||||
@@ -2687,10 +2689,10 @@ paths:
|
||||
name: pageSize
|
||||
type: integer
|
||||
- in: query
|
||||
name: pig_batch_id
|
||||
name: pigBatchID
|
||||
type: integer
|
||||
- in: query
|
||||
name: start_time
|
||||
name: startTime
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
@@ -2714,13 +2716,13 @@ paths:
|
||||
description: 根据提供的过滤条件,分页获取猪只采购记录
|
||||
parameters:
|
||||
- in: query
|
||||
name: end_time
|
||||
name: endTime
|
||||
type: string
|
||||
- in: query
|
||||
name: operator_id
|
||||
name: operatorID
|
||||
type: integer
|
||||
- in: query
|
||||
name: order_by
|
||||
name: orderBy
|
||||
type: string
|
||||
- in: query
|
||||
name: page
|
||||
@@ -2729,10 +2731,10 @@ paths:
|
||||
name: pageSize
|
||||
type: integer
|
||||
- in: query
|
||||
name: pig_batch_id
|
||||
name: pigBatchID
|
||||
type: integer
|
||||
- in: query
|
||||
name: start_time
|
||||
name: startTime
|
||||
type: string
|
||||
- in: query
|
||||
name: supplier
|
||||
@@ -2762,13 +2764,13 @@ paths:
|
||||
name: buyer
|
||||
type: string
|
||||
- in: query
|
||||
name: end_time
|
||||
name: endTime
|
||||
type: string
|
||||
- in: query
|
||||
name: operator_id
|
||||
name: operatorID
|
||||
type: integer
|
||||
- in: query
|
||||
name: order_by
|
||||
name: orderBy
|
||||
type: string
|
||||
- in: query
|
||||
name: page
|
||||
@@ -2777,10 +2779,10 @@ paths:
|
||||
name: pageSize
|
||||
type: integer
|
||||
- in: query
|
||||
name: pig_batch_id
|
||||
name: pigBatchID
|
||||
type: integer
|
||||
- in: query
|
||||
name: start_time
|
||||
name: startTime
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
@@ -2804,13 +2806,13 @@ paths:
|
||||
description: 根据提供的过滤条件,分页获取病猪日志
|
||||
parameters:
|
||||
- in: query
|
||||
name: end_time
|
||||
name: endTime
|
||||
type: string
|
||||
- in: query
|
||||
name: operator_id
|
||||
name: operatorID
|
||||
type: integer
|
||||
- in: query
|
||||
name: order_by
|
||||
name: orderBy
|
||||
type: string
|
||||
- in: query
|
||||
name: page
|
||||
@@ -2819,19 +2821,19 @@ paths:
|
||||
name: pageSize
|
||||
type: integer
|
||||
- in: query
|
||||
name: pen_id
|
||||
name: penID
|
||||
type: integer
|
||||
- in: query
|
||||
name: pig_batch_id
|
||||
name: pigBatchID
|
||||
type: integer
|
||||
- in: query
|
||||
name: reason
|
||||
type: string
|
||||
- in: query
|
||||
name: start_time
|
||||
name: startTime
|
||||
type: string
|
||||
- in: query
|
||||
name: treatment_location
|
||||
name: treatmentLocation
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
@@ -2855,16 +2857,16 @@ paths:
|
||||
description: 根据提供的过滤条件,分页获取猪只迁移日志
|
||||
parameters:
|
||||
- in: query
|
||||
name: correlation_id
|
||||
name: correlationID
|
||||
type: string
|
||||
- in: query
|
||||
name: end_time
|
||||
name: endTime
|
||||
type: string
|
||||
- in: query
|
||||
name: operator_id
|
||||
name: operatorID
|
||||
type: integer
|
||||
- in: query
|
||||
name: order_by
|
||||
name: orderBy
|
||||
type: string
|
||||
- in: query
|
||||
name: page
|
||||
@@ -2873,16 +2875,16 @@ paths:
|
||||
name: pageSize
|
||||
type: integer
|
||||
- in: query
|
||||
name: pen_id
|
||||
name: penID
|
||||
type: integer
|
||||
- in: query
|
||||
name: pig_batch_id
|
||||
name: pigBatchID
|
||||
type: integer
|
||||
- in: query
|
||||
name: start_time
|
||||
name: startTime
|
||||
type: string
|
||||
- in: query
|
||||
name: transfer_type
|
||||
name: transferType
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
@@ -2906,10 +2908,10 @@ paths:
|
||||
description: 根据提供的过滤条件,分页获取计划执行日志
|
||||
parameters:
|
||||
- in: query
|
||||
name: end_time
|
||||
name: endTime
|
||||
type: string
|
||||
- in: query
|
||||
name: order_by
|
||||
name: orderBy
|
||||
type: string
|
||||
- in: query
|
||||
name: page
|
||||
@@ -2918,10 +2920,10 @@ paths:
|
||||
name: pageSize
|
||||
type: integer
|
||||
- in: query
|
||||
name: plan_id
|
||||
name: planID
|
||||
type: integer
|
||||
- in: query
|
||||
name: start_time
|
||||
name: startTime
|
||||
type: string
|
||||
- in: query
|
||||
name: status
|
||||
@@ -2948,10 +2950,10 @@ paths:
|
||||
description: 根据提供的过滤条件,分页获取原料采购记录
|
||||
parameters:
|
||||
- in: query
|
||||
name: end_time
|
||||
name: endTime
|
||||
type: string
|
||||
- in: query
|
||||
name: order_by
|
||||
name: orderBy
|
||||
type: string
|
||||
- in: query
|
||||
name: page
|
||||
@@ -2960,10 +2962,10 @@ paths:
|
||||
name: pageSize
|
||||
type: integer
|
||||
- in: query
|
||||
name: raw_material_id
|
||||
name: rawMaterialID
|
||||
type: integer
|
||||
- in: query
|
||||
name: start_time
|
||||
name: startTime
|
||||
type: string
|
||||
- in: query
|
||||
name: supplier
|
||||
@@ -2990,10 +2992,10 @@ paths:
|
||||
description: 根据提供的过滤条件,分页获取原料库存日志
|
||||
parameters:
|
||||
- in: query
|
||||
name: end_time
|
||||
name: endTime
|
||||
type: string
|
||||
- in: query
|
||||
name: order_by
|
||||
name: orderBy
|
||||
type: string
|
||||
- in: query
|
||||
name: page
|
||||
@@ -3002,16 +3004,16 @@ paths:
|
||||
name: pageSize
|
||||
type: integer
|
||||
- in: query
|
||||
name: raw_material_id
|
||||
name: rawMaterialID
|
||||
type: integer
|
||||
- in: query
|
||||
name: source_id
|
||||
name: sourceID
|
||||
type: integer
|
||||
- in: query
|
||||
name: source_type
|
||||
name: sourceType
|
||||
type: string
|
||||
- in: query
|
||||
name: start_time
|
||||
name: startTime
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
@@ -3035,13 +3037,13 @@ paths:
|
||||
description: 根据提供的过滤条件,分页获取传感器数据
|
||||
parameters:
|
||||
- in: query
|
||||
name: device_id
|
||||
name: deviceID
|
||||
type: integer
|
||||
- in: query
|
||||
name: end_time
|
||||
name: endTime
|
||||
type: string
|
||||
- in: query
|
||||
name: order_by
|
||||
name: orderBy
|
||||
type: string
|
||||
- in: query
|
||||
name: page
|
||||
@@ -3050,10 +3052,10 @@ paths:
|
||||
name: pageSize
|
||||
type: integer
|
||||
- in: query
|
||||
name: sensor_type
|
||||
name: sensorType
|
||||
type: string
|
||||
- in: query
|
||||
name: start_time
|
||||
name: startTime
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
@@ -3077,10 +3079,10 @@ paths:
|
||||
description: 根据提供的过滤条件,分页获取任务执行日志
|
||||
parameters:
|
||||
- in: query
|
||||
name: end_time
|
||||
name: endTime
|
||||
type: string
|
||||
- in: query
|
||||
name: order_by
|
||||
name: orderBy
|
||||
type: string
|
||||
- in: query
|
||||
name: page
|
||||
@@ -3089,16 +3091,16 @@ paths:
|
||||
name: pageSize
|
||||
type: integer
|
||||
- in: query
|
||||
name: plan_execution_log_id
|
||||
name: planExecutionLogID
|
||||
type: integer
|
||||
- in: query
|
||||
name: start_time
|
||||
name: startTime
|
||||
type: string
|
||||
- in: query
|
||||
name: status
|
||||
type: string
|
||||
- in: query
|
||||
name: task_id
|
||||
name: taskID
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
@@ -3122,13 +3124,13 @@ paths:
|
||||
description: 根据提供的过滤条件,分页获取用户操作日志
|
||||
parameters:
|
||||
- in: query
|
||||
name: action_type
|
||||
name: actionType
|
||||
type: string
|
||||
- in: query
|
||||
name: end_time
|
||||
name: endTime
|
||||
type: string
|
||||
- in: query
|
||||
name: order_by
|
||||
name: orderBy
|
||||
type: string
|
||||
- in: query
|
||||
name: page
|
||||
@@ -3137,13 +3139,13 @@ paths:
|
||||
name: pageSize
|
||||
type: integer
|
||||
- in: query
|
||||
name: start_time
|
||||
name: startTime
|
||||
type: string
|
||||
- in: query
|
||||
name: status
|
||||
type: string
|
||||
- in: query
|
||||
name: user_id
|
||||
name: userID
|
||||
type: integer
|
||||
- in: query
|
||||
name: username
|
||||
@@ -3170,10 +3172,10 @@ paths:
|
||||
description: 根据提供的过滤条件,分页获取批次称重记录
|
||||
parameters:
|
||||
- in: query
|
||||
name: end_time
|
||||
name: endTime
|
||||
type: string
|
||||
- in: query
|
||||
name: order_by
|
||||
name: orderBy
|
||||
type: string
|
||||
- in: query
|
||||
name: page
|
||||
@@ -3182,10 +3184,10 @@ paths:
|
||||
name: pageSize
|
||||
type: integer
|
||||
- in: query
|
||||
name: pig_batch_id
|
||||
name: pigBatchID
|
||||
type: integer
|
||||
- in: query
|
||||
name: start_time
|
||||
name: startTime
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
@@ -3209,13 +3211,13 @@ paths:
|
||||
description: 根据提供的过滤条件,分页获取单次称重记录
|
||||
parameters:
|
||||
- in: query
|
||||
name: end_time
|
||||
name: endTime
|
||||
type: string
|
||||
- in: query
|
||||
name: operator_id
|
||||
name: operatorID
|
||||
type: integer
|
||||
- in: query
|
||||
name: order_by
|
||||
name: orderBy
|
||||
type: string
|
||||
- in: query
|
||||
name: page
|
||||
@@ -3224,13 +3226,13 @@ paths:
|
||||
name: pageSize
|
||||
type: integer
|
||||
- in: query
|
||||
name: pen_id
|
||||
name: penID
|
||||
type: integer
|
||||
- in: query
|
||||
name: start_time
|
||||
name: startTime
|
||||
type: string
|
||||
- in: query
|
||||
name: weighing_batch_id
|
||||
name: weighingBatchID
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
@@ -3974,7 +3976,7 @@ paths:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: 创建一个新的猪舍
|
||||
description: 根据提供的信息创建一个新猪舍
|
||||
parameters:
|
||||
- description: 猪舍信息
|
||||
in: body
|
||||
@@ -4295,59 +4297,6 @@ paths:
|
||||
summary: 创建新用户
|
||||
tags:
|
||||
- 用户管理
|
||||
/api/v1/users/{id}/history:
|
||||
get:
|
||||
description: 根据用户ID,分页获取该用户的操作审计日志。支持与通用日志查询接口相同的过滤和排序参数。
|
||||
parameters:
|
||||
- description: 用户ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: integer
|
||||
- in: query
|
||||
name: action_type
|
||||
type: string
|
||||
- in: query
|
||||
name: end_time
|
||||
type: string
|
||||
- in: query
|
||||
name: order_by
|
||||
type: string
|
||||
- in: query
|
||||
name: page
|
||||
type: integer
|
||||
- in: query
|
||||
name: pageSize
|
||||
type: integer
|
||||
- in: query
|
||||
name: start_time
|
||||
type: string
|
||||
- in: query
|
||||
name: status
|
||||
type: string
|
||||
- in: query
|
||||
name: user_id
|
||||
type: integer
|
||||
- in: query
|
||||
name: username
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: 业务码为200代表成功获取
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/definitions/controller.Response'
|
||||
- properties:
|
||||
data:
|
||||
$ref: '#/definitions/dto.ListUserActionLogResponse'
|
||||
type: object
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: 获取指定用户的操作历史
|
||||
tags:
|
||||
- 用户管理
|
||||
/api/v1/users/{id}/notifications/test:
|
||||
post:
|
||||
consumes:
|
||||
|
||||
@@ -27,8 +27,6 @@ import (
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/service"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/webhook"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/audit"
|
||||
domain_device "git.huangwc.com/pig/pig-farm-controller/internal/domain/device"
|
||||
domain_notify "git.huangwc.com/pig/pig-farm-controller/internal/domain/notify"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/scheduler"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/token"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/config"
|
||||
@@ -63,19 +61,16 @@ type API struct {
|
||||
func NewAPI(cfg config.ServerConfig,
|
||||
logger *logs.Logger,
|
||||
userRepo repository.UserRepository,
|
||||
deviceRepository repository.DeviceRepository,
|
||||
areaControllerRepository repository.AreaControllerRepository,
|
||||
deviceTemplateRepository repository.DeviceTemplateRepository,
|
||||
planRepository repository.PlanRepository,
|
||||
pigFarmService service.PigFarmService,
|
||||
pigBatchService service.PigBatchService,
|
||||
monitorService service.MonitorService,
|
||||
deviceService service.DeviceService,
|
||||
planService service.PlanService,
|
||||
userService service.UserService,
|
||||
tokenService token.Service,
|
||||
auditService audit.Service,
|
||||
notifyService domain_notify.Service,
|
||||
deviceService domain_device.Service,
|
||||
listenHandler webhook.ListenHandler,
|
||||
analysisTaskManager *scheduler.AnalysisPlanTaskManager) *API {
|
||||
) *API {
|
||||
// 使用 echo.New() 创建一个 Echo 引擎实例
|
||||
e := echo.New()
|
||||
|
||||
@@ -96,11 +91,11 @@ func NewAPI(cfg config.ServerConfig,
|
||||
config: cfg,
|
||||
listenHandler: listenHandler,
|
||||
// 在 NewAPI 中初始化用户控制器,并将其作为 API 结构体的成员
|
||||
userController: user.NewController(userRepo, monitorService, logger, tokenService, notifyService),
|
||||
userController: user.NewController(userService, logger),
|
||||
// 在 NewAPI 中初始化设备控制器,并将其作为 API 结构体的成员
|
||||
deviceController: device.NewController(deviceRepository, areaControllerRepository, deviceTemplateRepository, deviceService, logger),
|
||||
deviceController: device.NewController(deviceService, logger),
|
||||
// 在 NewAPI 中初始化计划控制器,并将其作为 API 结构体的成员
|
||||
planController: plan.NewController(logger, planRepository, analysisTaskManager),
|
||||
planController: plan.NewController(logger, planService),
|
||||
// 在 NewAPI 中初始化猪场管理控制器
|
||||
pigFarmController: management.NewPigFarmController(logger, pigFarmService),
|
||||
// 在 NewAPI 中初始化猪群控制器
|
||||
|
||||
@@ -57,7 +57,6 @@ func (a *API) setupRoutes() {
|
||||
// 用户相关路由组
|
||||
userGroup := authGroup.Group("/users")
|
||||
{
|
||||
userGroup.GET("/:id/history", a.userController.ListUserHistory) // 获取用户操作历史
|
||||
userGroup.POST("/:id/notifications/test", a.userController.SendTestNotification)
|
||||
}
|
||||
a.logger.Debug("用户相关接口注册成功 (需要认证和审计)")
|
||||
|
||||
@@ -1,44 +1,30 @@
|
||||
package device
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/controller"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/dto"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/device"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/service"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
||||
"github.com/labstack/echo/v4"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Controller 设备控制器,封装了所有与设备和区域主控相关的业务逻辑
|
||||
type Controller struct {
|
||||
deviceRepo repository.DeviceRepository
|
||||
areaControllerRepo repository.AreaControllerRepository
|
||||
deviceTemplateRepo repository.DeviceTemplateRepository
|
||||
deviceService device.Service
|
||||
logger *logs.Logger
|
||||
deviceService service.DeviceService
|
||||
logger *logs.Logger
|
||||
}
|
||||
|
||||
// NewController 创建一个新的设备控制器实例
|
||||
func NewController(
|
||||
deviceRepo repository.DeviceRepository,
|
||||
areaControllerRepo repository.AreaControllerRepository,
|
||||
deviceTemplateRepo repository.DeviceTemplateRepository,
|
||||
deviceService device.Service,
|
||||
deviceService service.DeviceService,
|
||||
logger *logs.Logger,
|
||||
) *Controller {
|
||||
return &Controller{
|
||||
deviceRepo: deviceRepo,
|
||||
areaControllerRepo: areaControllerRepo,
|
||||
deviceTemplateRepo: deviceTemplateRepo,
|
||||
deviceService: deviceService,
|
||||
logger: logger,
|
||||
deviceService: deviceService,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,43 +48,13 @@ func (c *Controller) CreateDevice(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req)
|
||||
}
|
||||
|
||||
propertiesJSON, err := json.Marshal(req.Properties)
|
||||
resp, err := c.deviceService.CreateDevice(&req)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 序列化属性失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "属性字段格式错误", actionType, "属性序列化失败", req.Properties)
|
||||
c.logger.Errorf("%s: 服务层创建失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "创建设备失败: "+err.Error(), actionType, "服务层创建失败", req)
|
||||
}
|
||||
|
||||
device := &models.Device{
|
||||
Name: req.Name,
|
||||
DeviceTemplateID: req.DeviceTemplateID,
|
||||
AreaControllerID: req.AreaControllerID,
|
||||
Location: req.Location,
|
||||
Properties: propertiesJSON,
|
||||
}
|
||||
|
||||
if err := device.SelfCheck(); err != nil {
|
||||
c.logger.Errorf("%s: 设备属性自检失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "设备属性不符合要求: "+err.Error(), actionType, "设备属性自检失败", device)
|
||||
}
|
||||
|
||||
if err := c.deviceRepo.Create(device); err != nil {
|
||||
c.logger.Errorf("%s: 数据库操作失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "创建设备失败: "+err.Error(), actionType, "数据库创建失败", device)
|
||||
}
|
||||
|
||||
createdDevice, err := c.deviceRepo.FindByID(device.ID)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 重新加载创建的设备失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "设备创建成功,但重新加载设备失败", actionType, "重新加载设备失败", device)
|
||||
}
|
||||
|
||||
resp, err := dto.NewDeviceResponse(createdDevice)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 序列化响应失败: %v, Device: %+v", actionType, err, createdDevice)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "设备创建成功,但响应生成失败", actionType, "响应序列化失败", createdDevice)
|
||||
}
|
||||
|
||||
c.logger.Infof("%s: 设备创建成功, ID: %d", actionType, device.ID)
|
||||
c.logger.Infof("%s: 设备创建成功, ID: %d", actionType, resp.ID)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeCreated, "设备创建成功", resp, actionType, "设备创建成功", resp)
|
||||
}
|
||||
|
||||
@@ -115,32 +71,17 @@ func (c *Controller) GetDevice(ctx echo.Context) error {
|
||||
const actionType = "获取设备"
|
||||
deviceID := ctx.Param("id")
|
||||
|
||||
if deviceID == "" {
|
||||
c.logger.Errorf("%s: 设备ID为空", actionType)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "设备ID不能为空", actionType, "设备ID为空", nil)
|
||||
}
|
||||
|
||||
device, err := c.deviceRepo.FindByIDString(deviceID)
|
||||
resp, err := c.deviceService.GetDevice(deviceID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.logger.Warnf("%s: 设备不存在, ID: %s", actionType, deviceID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "设备未找到", actionType, "设备不存在", deviceID)
|
||||
}
|
||||
if strings.Contains(err.Error(), "无效的设备ID格式") {
|
||||
c.logger.Errorf("%s: 设备ID格式错误: %v, ID: %s", actionType, err, deviceID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, err.Error(), actionType, "设备ID格式错误", deviceID)
|
||||
}
|
||||
c.logger.Errorf("%s: 数据库查询失败: %v, ID: %s", actionType, err, deviceID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取设备信息失败: "+err.Error(), actionType, "数据库查询失败", deviceID)
|
||||
c.logger.Errorf("%s: 服务层获取失败: %v, ID: %s", actionType, err, deviceID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取设备信息失败: "+err.Error(), actionType, "服务层获取失败", deviceID)
|
||||
}
|
||||
|
||||
resp, err := dto.NewDeviceResponse(device)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 序列化响应失败: %v, Device: %+v", actionType, err, device)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取设备信息失败: 内部数据格式错误", actionType, "响应序列化失败", device)
|
||||
}
|
||||
|
||||
c.logger.Infof("%s: 获取设备信息成功, ID: %d", actionType, device.ID)
|
||||
c.logger.Infof("%s: 获取设备信息成功, ID: %d", actionType, resp.ID)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取设备信息成功", resp, actionType, "获取设备信息成功", resp)
|
||||
}
|
||||
|
||||
@@ -154,19 +95,13 @@ func (c *Controller) GetDevice(ctx echo.Context) error {
|
||||
// @Router /api/v1/devices [get]
|
||||
func (c *Controller) ListDevices(ctx echo.Context) error {
|
||||
const actionType = "获取设备列表"
|
||||
devices, err := c.deviceRepo.ListAll()
|
||||
resp, err := c.deviceService.ListDevices()
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 数据库查询失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取设备列表失败: "+err.Error(), actionType, "数据库查询失败", nil)
|
||||
c.logger.Errorf("%s: 服务层获取列表失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取设备列表失败: "+err.Error(), actionType, "服务层获取列表失败", nil)
|
||||
}
|
||||
|
||||
resp, err := dto.NewListDeviceResponse(devices)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 序列化响应失败: %v, Devices: %+v", actionType, err, devices)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取设备列表失败: 内部数据格式错误", actionType, "响应序列化失败", devices)
|
||||
}
|
||||
|
||||
c.logger.Infof("%s: 获取设备列表成功, 数量: %d", actionType, len(devices))
|
||||
c.logger.Infof("%s: 获取设备列表成功, 数量: %d", actionType, len(resp))
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取设备列表成功", resp, actionType, "获取设备列表成功", resp)
|
||||
}
|
||||
|
||||
@@ -185,61 +120,23 @@ func (c *Controller) UpdateDevice(ctx echo.Context) error {
|
||||
const actionType = "更新设备"
|
||||
deviceID := ctx.Param("id")
|
||||
|
||||
existingDevice, err := c.deviceRepo.FindByIDString(deviceID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.logger.Warnf("%s: 设备不存在, ID: %s", actionType, deviceID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "设备未找到", actionType, "设备不存在", deviceID)
|
||||
}
|
||||
if strings.Contains(err.Error(), "无效的设备ID格式") {
|
||||
c.logger.Errorf("%s: 设备ID格式错误: %v, ID: %s", actionType, err, deviceID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, err.Error(), actionType, "设备ID格式错误", deviceID)
|
||||
}
|
||||
c.logger.Errorf("%s: 数据库查询失败: %v, ID: %s", actionType, err, deviceID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "更新设备失败: "+err.Error(), actionType, "数据库查询失败", deviceID)
|
||||
}
|
||||
|
||||
var req dto.UpdateDeviceRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req)
|
||||
}
|
||||
|
||||
propertiesJSON, err := json.Marshal(req.Properties)
|
||||
resp, err := c.deviceService.UpdateDevice(deviceID, &req)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 序列化属性失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "属性字段格式错误", actionType, "属性序列化失败", req.Properties)
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.logger.Warnf("%s: 设备不存在, ID: %s", actionType, deviceID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "设备未找到", actionType, "设备不存在", deviceID)
|
||||
}
|
||||
c.logger.Errorf("%s: 服务层更新失败: %v, ID: %s", actionType, err, deviceID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "更新设备失败: "+err.Error(), actionType, "服务层更新失败", deviceID)
|
||||
}
|
||||
|
||||
existingDevice.Name = req.Name
|
||||
existingDevice.DeviceTemplateID = req.DeviceTemplateID
|
||||
existingDevice.AreaControllerID = req.AreaControllerID
|
||||
existingDevice.Location = req.Location
|
||||
existingDevice.Properties = propertiesJSON
|
||||
|
||||
if err := existingDevice.SelfCheck(); err != nil {
|
||||
c.logger.Errorf("%s: 设备属性自检失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "设备属性不符合要求: "+err.Error(), actionType, "设备属性自检失败", existingDevice)
|
||||
}
|
||||
|
||||
if err := c.deviceRepo.Update(existingDevice); err != nil {
|
||||
c.logger.Errorf("%s: 数据库更新失败: %v, Device: %+v", actionType, err, existingDevice)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "更新设备失败: "+err.Error(), actionType, "数据库更新失败", deviceID)
|
||||
}
|
||||
|
||||
updatedDevice, err := c.deviceRepo.FindByID(existingDevice.ID)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 重新加载更新的设备失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "设备更新成功,但重新加载设备失败", actionType, "重新加载设备失败", existingDevice)
|
||||
}
|
||||
|
||||
resp, err := dto.NewDeviceResponse(updatedDevice)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 序列化响应失败: %v, Device: %+v", actionType, err, updatedDevice)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "设备更新成功,但响应生成失败", actionType, "响应序列化失败", updatedDevice)
|
||||
}
|
||||
|
||||
c.logger.Infof("%s: 设备更新成功, ID: %d", actionType, existingDevice.ID)
|
||||
c.logger.Infof("%s: 设备更新成功, ID: %d", actionType, resp.ID)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "设备更新成功", resp, actionType, "设备更新成功", resp)
|
||||
}
|
||||
|
||||
@@ -256,28 +153,16 @@ func (c *Controller) DeleteDevice(ctx echo.Context) error {
|
||||
const actionType = "删除设备"
|
||||
deviceID := ctx.Param("id")
|
||||
|
||||
idUint, err := strconv.ParseUint(deviceID, 10, 64)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 设备ID格式错误: %v, ID: %s", actionType, err, deviceID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的设备ID格式", actionType, "设备ID格式错误", deviceID)
|
||||
}
|
||||
|
||||
_, err = c.deviceRepo.FindByIDString(deviceID)
|
||||
if err != nil {
|
||||
if err := c.deviceService.DeleteDevice(deviceID); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.logger.Warnf("%s: 设备不存在, ID: %s", actionType, deviceID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "设备未找到", actionType, "设备不存在", deviceID)
|
||||
}
|
||||
c.logger.Errorf("%s: 查找设备失败: %v, ID: %s", actionType, err, deviceID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "删除设备失败: 查找设备时发生内部错误", actionType, "数据库查询失败", deviceID)
|
||||
c.logger.Errorf("%s: 服务层删除失败: %v, ID: %s", actionType, err, deviceID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "删除设备失败: "+err.Error(), actionType, "服务层删除失败", deviceID)
|
||||
}
|
||||
|
||||
if err := c.deviceRepo.Delete(uint(idUint)); err != nil {
|
||||
c.logger.Errorf("%s: 数据库删除失败: %v, ID: %d", actionType, err, idUint)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "删除设备失败: "+err.Error(), actionType, "数据库删除失败", deviceID)
|
||||
}
|
||||
|
||||
c.logger.Infof("%s: 设备删除成功, ID: %d", actionType, idUint)
|
||||
c.logger.Infof("%s: 设备删除成功, ID: %s", actionType, deviceID)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "设备删除成功", nil, actionType, "设备删除成功", deviceID)
|
||||
}
|
||||
|
||||
@@ -302,45 +187,16 @@ func (c *Controller) ManualControl(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req)
|
||||
}
|
||||
|
||||
dev, err := c.deviceRepo.FindByIDString(deviceID)
|
||||
if err != nil {
|
||||
if err := c.deviceService.ManualControl(deviceID, &req); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.logger.Warnf("%s: 设备不存在, ID: %s", actionType, deviceID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "设备未找到", actionType, "设备不存在", deviceID)
|
||||
}
|
||||
if strings.Contains(err.Error(), "无效的设备ID格式") {
|
||||
c.logger.Errorf("%s: 设备ID格式错误: %v, ID: %s", actionType, err, deviceID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, err.Error(), actionType, "设备ID格式错误", deviceID)
|
||||
}
|
||||
c.logger.Errorf("%s: 数据库查询失败: %v, ID: %s", actionType, err, deviceID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "手动控制失败: "+err.Error(), actionType, "数据库查询失败", deviceID)
|
||||
c.logger.Errorf("%s: 服务层手动控制失败: %v, ID: %s", actionType, err, deviceID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "手动控制失败: "+err.Error(), actionType, "服务层手动控制失败", deviceID)
|
||||
}
|
||||
|
||||
c.logger.Infof("%s: 接收到指令, 设备ID: %s, 动作: %s", actionType, deviceID, req.Action)
|
||||
if req.Action == nil {
|
||||
err = c.deviceService.Collect(dev.AreaControllerID, []*models.Device{dev})
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 获取设备状态失败: %v, 设备ID: %s", actionType, err, deviceID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取设备状态失败: "+err.Error(), actionType, "获取设备状态失败", deviceID)
|
||||
}
|
||||
} else {
|
||||
action := device.DeviceActionStart
|
||||
switch *req.Action {
|
||||
case "off":
|
||||
action = device.DeviceActionStop
|
||||
case "on":
|
||||
default:
|
||||
c.logger.Errorf("%s: 无效的动作: %s, 设备ID: %s", actionType, *req.Action, deviceID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的动作: "+*req.Action, actionType, "无效的动作", req.Action)
|
||||
}
|
||||
err = c.deviceService.Switch(dev, action)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 设备控制失败: %v, 设备ID: %s", actionType, err, deviceID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "设备控制失败: "+err.Error(), actionType, "设备控制失败", deviceID)
|
||||
}
|
||||
}
|
||||
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "指令已发送", map[string]interface{}{"device_id": deviceID}, actionType, "指令发送成功", map[string]interface{}{"device_id": deviceID, "action": req.Action})
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "指令已发送", nil, actionType, "指令发送成功", nil)
|
||||
}
|
||||
|
||||
// --- Controller Methods: Area Controllers ---
|
||||
@@ -363,36 +219,13 @@ func (c *Controller) CreateAreaController(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req)
|
||||
}
|
||||
|
||||
propertiesJSON, err := json.Marshal(req.Properties)
|
||||
resp, err := c.deviceService.CreateAreaController(&req)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 序列化属性失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "属性字段格式错误", actionType, "属性序列化失败", req.Properties)
|
||||
c.logger.Errorf("%s: 服务层创建失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "创建区域主控失败: "+err.Error(), actionType, "服务层创建失败", req)
|
||||
}
|
||||
|
||||
ac := &models.AreaController{
|
||||
Name: req.Name,
|
||||
NetworkID: req.NetworkID,
|
||||
Location: req.Location,
|
||||
Properties: propertiesJSON,
|
||||
}
|
||||
|
||||
if err := ac.SelfCheck(); err != nil {
|
||||
c.logger.Errorf("%s: 区域主控自检失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "区域主控参数不符合要求: "+err.Error(), actionType, "区域主控自检失败", ac)
|
||||
}
|
||||
|
||||
if err := c.areaControllerRepo.Create(ac); err != nil {
|
||||
c.logger.Errorf("%s: 数据库操作失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "创建区域主控失败: "+err.Error(), actionType, "数据库创建失败", ac)
|
||||
}
|
||||
|
||||
resp, err := dto.NewAreaControllerResponse(ac)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 序列化响应失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "区域主控创建成功,但响应生成失败", actionType, "响应序列化失败", ac)
|
||||
}
|
||||
|
||||
c.logger.Infof("%s: 区域主控创建成功, ID: %d", actionType, ac.ID)
|
||||
c.logger.Infof("%s: 区域主控创建成功, ID: %d", actionType, resp.ID)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeCreated, "区域主控创建成功", resp, actionType, "区域主控创建成功", resp)
|
||||
}
|
||||
|
||||
@@ -409,29 +242,17 @@ func (c *Controller) GetAreaController(ctx echo.Context) error {
|
||||
const actionType = "获取区域主控"
|
||||
acID := ctx.Param("id")
|
||||
|
||||
idUint, err := strconv.ParseUint(acID, 10, 64)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 区域主控ID格式错误: %v, ID: %s", actionType, err, acID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的区域主控ID格式", actionType, "ID格式错误", acID)
|
||||
}
|
||||
|
||||
ac, err := c.areaControllerRepo.FindByID(uint(idUint))
|
||||
resp, err := c.deviceService.GetAreaController(acID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.logger.Warnf("%s: 区域主控不存在, ID: %s", actionType, acID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "区域主控未找到", actionType, "区域主控不存在", acID)
|
||||
}
|
||||
c.logger.Errorf("%s: 数据库查询失败: %v, ID: %s", actionType, err, acID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取区域主控信息失败: "+err.Error(), actionType, "数据库查询失败", acID)
|
||||
c.logger.Errorf("%s: 服务层获取失败: %v, ID: %s", actionType, err, acID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取区域主控信息失败: "+err.Error(), actionType, "服务层获取失败", acID)
|
||||
}
|
||||
|
||||
resp, err := dto.NewAreaControllerResponse(ac)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 序列化响应失败: %v, AreaController: %+v", actionType, err, ac)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取区域主控信息失败: 内部数据格式错误", actionType, "响应序列化失败", ac)
|
||||
}
|
||||
|
||||
c.logger.Infof("%s: 获取区域主控信息成功, ID: %d", actionType, ac.ID)
|
||||
c.logger.Infof("%s: 获取区域主控信息成功, ID: %d", actionType, resp.ID)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取区域主控信息成功", resp, actionType, "获取区域主控信息成功", resp)
|
||||
}
|
||||
|
||||
@@ -445,19 +266,13 @@ func (c *Controller) GetAreaController(ctx echo.Context) error {
|
||||
// @Router /api/v1/area-controllers [get]
|
||||
func (c *Controller) ListAreaControllers(ctx echo.Context) error {
|
||||
const actionType = "获取区域主控列表"
|
||||
acs, err := c.areaControllerRepo.ListAll()
|
||||
resp, err := c.deviceService.ListAreaControllers()
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 数据库查询失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取区域主控列表失败: "+err.Error(), actionType, "数据库查询失败", nil)
|
||||
c.logger.Errorf("%s: 服务层获取列表失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取区域主控列表失败: "+err.Error(), actionType, "服务层获取列表失败", nil)
|
||||
}
|
||||
|
||||
resp, err := dto.NewListAreaControllerResponse(acs)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 序列化响应失败: %v, AreaControllers: %+v", actionType, err, acs)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取区域主控列表失败: 内部数据格式错误", actionType, "响应序列化失败", acs)
|
||||
}
|
||||
|
||||
c.logger.Infof("%s: 获取区域主控列表成功, 数量: %d", actionType, len(acs))
|
||||
c.logger.Infof("%s: 获取区域主控列表成功, 数量: %d", actionType, len(resp))
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取区域主控列表成功", resp, actionType, "获取区域主控列表成功", resp)
|
||||
}
|
||||
|
||||
@@ -476,56 +291,23 @@ func (c *Controller) UpdateAreaController(ctx echo.Context) error {
|
||||
const actionType = "更新区域主控"
|
||||
acID := ctx.Param("id")
|
||||
|
||||
idUint, err := strconv.ParseUint(acID, 10, 64)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 区域主控ID格式错误: %v, ID: %s", actionType, err, acID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的区域主控ID格式", actionType, "ID格式错误", acID)
|
||||
}
|
||||
|
||||
existingAC, err := c.areaControllerRepo.FindByID(uint(idUint))
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.logger.Warnf("%s: 区域主控不存在, ID: %s", actionType, acID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "区域主控未找到", actionType, "区域主控不存在", acID)
|
||||
}
|
||||
c.logger.Errorf("%s: 数据库查询失败: %v, ID: %s", actionType, err, acID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "更新区域主控失败: "+err.Error(), actionType, "数据库查询失败", acID)
|
||||
}
|
||||
|
||||
var req dto.UpdateAreaControllerRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req)
|
||||
}
|
||||
|
||||
propertiesJSON, err := json.Marshal(req.Properties)
|
||||
resp, err := c.deviceService.UpdateAreaController(acID, &req)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 序列化属性失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "属性字段格式错误", actionType, "属性序列化失败", req.Properties)
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.logger.Warnf("%s: 区域主控不存在, ID: %s", actionType, acID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "区域主控未找到", actionType, "区域主控不存在", acID)
|
||||
}
|
||||
c.logger.Errorf("%s: 服务层更新失败: %v, ID: %s", actionType, err, acID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "更新区域主控失败: "+err.Error(), actionType, "服务层更新失败", acID)
|
||||
}
|
||||
|
||||
existingAC.Name = req.Name
|
||||
existingAC.NetworkID = req.NetworkID
|
||||
existingAC.Location = req.Location
|
||||
existingAC.Properties = propertiesJSON
|
||||
|
||||
if err := existingAC.SelfCheck(); err != nil {
|
||||
c.logger.Errorf("%s: 区域主控自检失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "区域主控参数不符合要求: "+err.Error(), actionType, "区域主控自检失败", existingAC)
|
||||
}
|
||||
|
||||
if err := c.areaControllerRepo.Update(existingAC); err != nil {
|
||||
c.logger.Errorf("%s: 数据库更新失败: %v, AreaController: %+v", actionType, err, existingAC)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "更新区域主控失败: "+err.Error(), actionType, "数据库更新失败", acID)
|
||||
}
|
||||
|
||||
resp, err := dto.NewAreaControllerResponse(existingAC)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 序列化响应失败: %v, AreaController: %+v", actionType, err, existingAC)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "区域主控更新成功,但响应生成失败", actionType, "响应序列化失败", existingAC)
|
||||
}
|
||||
|
||||
c.logger.Infof("%s: 区域主控更新成功, ID: %d", actionType, existingAC.ID)
|
||||
c.logger.Infof("%s: 区域主控更新成功, ID: %d", actionType, resp.ID)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "区域主控更新成功", resp, actionType, "区域主控更新成功", resp)
|
||||
}
|
||||
|
||||
@@ -542,28 +324,16 @@ func (c *Controller) DeleteAreaController(ctx echo.Context) error {
|
||||
const actionType = "删除区域主控"
|
||||
acID := ctx.Param("id")
|
||||
|
||||
idUint, err := strconv.ParseUint(acID, 10, 64)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 区域主控ID格式错误: %v, ID: %s", actionType, err, acID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的区域主控ID格式", actionType, "ID格式错误", acID)
|
||||
}
|
||||
|
||||
_, err = c.areaControllerRepo.FindByID(uint(idUint))
|
||||
if err != nil {
|
||||
if err := c.deviceService.DeleteAreaController(acID); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.logger.Warnf("%s: 区域主控不存在, ID: %s", actionType, acID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "区域主控未找到", actionType, "区域主控不存在", acID)
|
||||
}
|
||||
c.logger.Errorf("%s: 查找区域主控失败: %v, ID: %s", actionType, err, acID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "删除区域主控失败: 查找时发生内部错误", actionType, "数据库查询失败", acID)
|
||||
c.logger.Errorf("%s: 服务层删除失败: %v, ID: %s", actionType, err, acID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "删除区域主控失败: "+err.Error(), actionType, "服务层删除失败", acID)
|
||||
}
|
||||
|
||||
if err := c.areaControllerRepo.Delete(uint(idUint)); err != nil {
|
||||
c.logger.Errorf("%s: 数据库删除失败: %v, ID: %d", actionType, err, idUint)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "删除区域主控失败: "+err.Error(), actionType, "数据库删除失败", acID)
|
||||
}
|
||||
|
||||
c.logger.Infof("%s: 区域主控删除成功, ID: %d", actionType, idUint)
|
||||
c.logger.Infof("%s: 区域主控删除成功, ID: %s", actionType, acID)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "区域主控删除成功", nil, actionType, "区域主控删除成功", acID)
|
||||
}
|
||||
|
||||
@@ -587,44 +357,13 @@ func (c *Controller) CreateDeviceTemplate(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req)
|
||||
}
|
||||
|
||||
commandsJSON, err := json.Marshal(req.Commands)
|
||||
resp, err := c.deviceService.CreateDeviceTemplate(&req)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 序列化命令失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "命令字段格式错误", actionType, "命令序列化失败", req.Commands)
|
||||
c.logger.Errorf("%s: 服务层创建失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "创建设备模板失败: "+err.Error(), actionType, "服务层创建失败", req)
|
||||
}
|
||||
|
||||
valuesJSON, err := json.Marshal(req.Values)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 序列化值描述符失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "值描述符字段格式错误", actionType, "值描述符序列化失败", req.Values)
|
||||
}
|
||||
|
||||
deviceTemplate := &models.DeviceTemplate{
|
||||
Name: req.Name,
|
||||
Manufacturer: req.Manufacturer,
|
||||
Description: req.Description,
|
||||
Category: req.Category,
|
||||
Commands: commandsJSON,
|
||||
Values: valuesJSON,
|
||||
}
|
||||
|
||||
if err := deviceTemplate.SelfCheck(); err != nil {
|
||||
c.logger.Errorf("%s: 设备模板自检失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "设备模板参数不符合要求: "+err.Error(), actionType, "设备模板自检失败", deviceTemplate)
|
||||
}
|
||||
|
||||
if err := c.deviceTemplateRepo.Create(deviceTemplate); err != nil {
|
||||
c.logger.Errorf("%s: 数据库操作失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "创建设备模板失败: "+err.Error(), actionType, "数据库创建失败", deviceTemplate)
|
||||
}
|
||||
|
||||
resp, err := dto.NewDeviceTemplateResponse(deviceTemplate)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 序列化响应失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "设备模板创建成功,但响应生成失败", actionType, "响应序列化失败", deviceTemplate)
|
||||
}
|
||||
|
||||
c.logger.Infof("%s: 设备模板创建成功, ID: %d", actionType, deviceTemplate.ID)
|
||||
c.logger.Infof("%s: 设备模板创建成功, ID: %d", actionType, resp.ID)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeCreated, "设备模板创建成功", resp, actionType, "设备模板创建成功", resp)
|
||||
}
|
||||
|
||||
@@ -641,29 +380,17 @@ func (c *Controller) GetDeviceTemplate(ctx echo.Context) error {
|
||||
const actionType = "获取设备模板"
|
||||
dtID := ctx.Param("id")
|
||||
|
||||
idUint, err := strconv.ParseUint(dtID, 10, 64)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 设备模板ID格式错误: %v, ID: %s", actionType, err, dtID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的设备模板ID格式", actionType, "ID格式错误", dtID)
|
||||
}
|
||||
|
||||
deviceTemplate, err := c.deviceTemplateRepo.FindByID(uint(idUint))
|
||||
resp, err := c.deviceService.GetDeviceTemplate(dtID)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.logger.Warnf("%s: 设备模板不存在, ID: %s", actionType, dtID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "设备模板未找到", actionType, "设备模板不存在", dtID)
|
||||
}
|
||||
c.logger.Errorf("%s: 数据库查询失败: %v, ID: %s", actionType, err, dtID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取设备模板信息失败: "+err.Error(), actionType, "数据库查询失败", dtID)
|
||||
c.logger.Errorf("%s: 服务层获取失败: %v, ID: %s", actionType, err, dtID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取设备模板信息失败: "+err.Error(), actionType, "服务层获取失败", dtID)
|
||||
}
|
||||
|
||||
resp, err := dto.NewDeviceTemplateResponse(deviceTemplate)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 序列化响应失败: %v, DeviceTemplate: %+v", actionType, err, deviceTemplate)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取设备模板信息失败: 内部数据格式错误", actionType, "响应序列化失败", deviceTemplate)
|
||||
}
|
||||
|
||||
c.logger.Infof("%s: 获取设备模板信息成功, ID: %d", actionType, deviceTemplate.ID)
|
||||
c.logger.Infof("%s: 获取设备模板信息成功, ID: %d", actionType, resp.ID)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取设备模板信息成功", resp, actionType, "获取设备模板信息成功", resp)
|
||||
}
|
||||
|
||||
@@ -677,19 +404,13 @@ func (c *Controller) GetDeviceTemplate(ctx echo.Context) error {
|
||||
// @Router /api/v1/device-templates [get]
|
||||
func (c *Controller) ListDeviceTemplates(ctx echo.Context) error {
|
||||
const actionType = "获取设备模板列表"
|
||||
deviceTemplates, err := c.deviceTemplateRepo.ListAll()
|
||||
resp, err := c.deviceService.ListDeviceTemplates()
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 数据库查询失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取设备模板列表失败: "+err.Error(), actionType, "数据库查询失败", nil)
|
||||
c.logger.Errorf("%s: 服务层获取列表失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取设备模板列表失败: "+err.Error(), actionType, "服务层获取列表失败", nil)
|
||||
}
|
||||
|
||||
resp, err := dto.NewListDeviceTemplateResponse(deviceTemplates)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 序列化响应失败: %v, DeviceTemplates: %+v", actionType, err, deviceTemplates)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取设备模板列表失败: 内部数据格式错误", actionType, "响应序列化失败", deviceTemplates)
|
||||
}
|
||||
|
||||
c.logger.Infof("%s: 获取设备模板列表成功, 数量: %d", actionType, len(deviceTemplates))
|
||||
c.logger.Infof("%s: 获取设备模板列表成功, 数量: %d", actionType, len(resp))
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取设备模板列表成功", resp, actionType, "获取设备模板列表成功", resp)
|
||||
}
|
||||
|
||||
@@ -708,64 +429,23 @@ func (c *Controller) UpdateDeviceTemplate(ctx echo.Context) error {
|
||||
const actionType = "更新设备模板"
|
||||
dtID := ctx.Param("id")
|
||||
|
||||
idUint, err := strconv.ParseUint(dtID, 10, 64)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 设备模板ID格式错误: %v, ID: %s", actionType, err, dtID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的设备模板ID格式", actionType, "ID格式错误", dtID)
|
||||
}
|
||||
|
||||
existingDeviceTemplate, err := c.deviceTemplateRepo.FindByID(uint(idUint))
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.logger.Warnf("%s: 设备模板不存在, ID: %s", actionType, dtID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "设备模板未找到", actionType, "设备模板不存在", dtID)
|
||||
}
|
||||
c.logger.Errorf("%s: 数据库查询失败: %v, ID: %s", actionType, err, dtID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "更新设备模板失败: "+err.Error(), actionType, "数据库查询失败", dtID)
|
||||
}
|
||||
|
||||
var req dto.UpdateDeviceTemplateRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req)
|
||||
}
|
||||
|
||||
commandsJSON, err := json.Marshal(req.Commands)
|
||||
resp, err := c.deviceService.UpdateDeviceTemplate(dtID, &req)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 序列化命令失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "命令字段格式错误", actionType, "命令序列化失败", req.Commands)
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.logger.Warnf("%s: 设备模板不存在, ID: %s", actionType, dtID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "设备模板未找到", actionType, "设备模板不存在", dtID)
|
||||
}
|
||||
c.logger.Errorf("%s: 服务层更新失败: %v, ID: %s", actionType, err, dtID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "更新设备模板失败: "+err.Error(), actionType, "服务层更新失败", dtID)
|
||||
}
|
||||
|
||||
valuesJSON, err := json.Marshal(req.Values)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 序列化值描述符失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "值描述符字段格式错误", actionType, "值描述符序列化失败", req.Values)
|
||||
}
|
||||
|
||||
existingDeviceTemplate.Name = req.Name
|
||||
existingDeviceTemplate.Manufacturer = req.Manufacturer
|
||||
existingDeviceTemplate.Description = req.Description
|
||||
existingDeviceTemplate.Category = req.Category
|
||||
existingDeviceTemplate.Commands = commandsJSON
|
||||
existingDeviceTemplate.Values = valuesJSON
|
||||
|
||||
if err := existingDeviceTemplate.SelfCheck(); err != nil {
|
||||
c.logger.Errorf("%s: 设备模板自检失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "设备模板参数不符合要求: "+err.Error(), actionType, "设备模板自检失败", existingDeviceTemplate)
|
||||
}
|
||||
|
||||
if err := c.deviceTemplateRepo.Update(existingDeviceTemplate); err != nil {
|
||||
c.logger.Errorf("%s: 数据库更新失败: %v, DeviceTemplate: %+v", actionType, err, existingDeviceTemplate)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "更新设备模板失败: "+err.Error(), actionType, "数据库更新失败", dtID)
|
||||
}
|
||||
|
||||
resp, err := dto.NewDeviceTemplateResponse(existingDeviceTemplate)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 序列化响应失败: %v, DeviceTemplate: %+v", actionType, err, existingDeviceTemplate)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "设备模板更新成功,但响应生成失败", actionType, "响应序列化失败", existingDeviceTemplate)
|
||||
}
|
||||
|
||||
c.logger.Infof("%s: 设备模板更新成功, ID: %d", actionType, existingDeviceTemplate.ID)
|
||||
c.logger.Infof("%s: 设备模板更新成功, ID: %d", actionType, resp.ID)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "设备模板更新成功", resp, actionType, "设备模板更新成功", resp)
|
||||
}
|
||||
|
||||
@@ -782,35 +462,15 @@ func (c *Controller) DeleteDeviceTemplate(ctx echo.Context) error {
|
||||
const actionType = "删除设备模板"
|
||||
dtID := ctx.Param("id")
|
||||
|
||||
idUint, err := strconv.ParseUint(dtID, 10, 64)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 设备模板ID格式错误: %v, ID: %s", actionType, err, dtID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的设备模板ID格式", actionType, "ID格式错误", dtID)
|
||||
}
|
||||
|
||||
// 在尝试删除之前,先检查设备模板是否存在
|
||||
_, err = c.deviceTemplateRepo.FindByID(uint(idUint))
|
||||
if err != nil {
|
||||
if err := c.deviceService.DeleteDeviceTemplate(dtID); err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.logger.Warnf("%s: 设备模板不存在, ID: %s", actionType, dtID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "设备模板未找到", actionType, "设备模板不存在", dtID)
|
||||
}
|
||||
c.logger.Errorf("%s: 查找设备模板失败: %v, ID: %s", actionType, err, dtID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "删除设备模板失败: 查找时发生内部错误", actionType, "数据库查询失败", dtID)
|
||||
c.logger.Errorf("%s: 服务层删除失败: %v, ID: %s", actionType, err, dtID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "删除设备模板失败: "+err.Error(), actionType, "服务层删除失败", dtID)
|
||||
}
|
||||
|
||||
// 调用仓库层的删除方法,该方法会检查模板是否被使用
|
||||
if err := c.deviceTemplateRepo.Delete(uint(idUint)); err != nil {
|
||||
c.logger.Errorf("%s: 数据库删除失败: %v, ID: %d", actionType, err, idUint)
|
||||
// 如果错误信息包含“设备模板正在被设备使用,无法删除”,则返回特定的错误码
|
||||
if strings.Contains(err.Error(), "设备模板正在被设备使用,无法删除") {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, err.Error(), actionType, "设备模板正在使用", dtID)
|
||||
} else {
|
||||
// 其他数据库错误
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "删除设备模板失败: "+err.Error(), actionType, "数据库删除失败", dtID)
|
||||
}
|
||||
}
|
||||
|
||||
c.logger.Infof("%s: 设备模板删除成功, ID: %d", actionType, idUint)
|
||||
c.logger.Infof("%s: 设备模板删除成功, ID: %s", actionType, dtID)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "设备模板删除成功", nil, actionType, "设备模板删除成功", dtID)
|
||||
}
|
||||
|
||||
@@ -53,12 +53,7 @@ func (c *PigFarmController) CreatePigHouse(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "创建猪舍失败", action, "业务逻辑失败", req)
|
||||
}
|
||||
|
||||
resp := dto.PigHouseResponse{
|
||||
ID: house.ID,
|
||||
Name: house.Name,
|
||||
Description: house.Description,
|
||||
}
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeCreated, "创建成功", resp, action, "创建成功", resp)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeCreated, "创建成功", house, action, "创建成功", house)
|
||||
}
|
||||
|
||||
// GetPigHouse godoc
|
||||
@@ -86,12 +81,7 @@ func (c *PigFarmController) GetPigHouse(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取猪舍失败", action, "业务逻辑失败", id)
|
||||
}
|
||||
|
||||
resp := dto.PigHouseResponse{
|
||||
ID: house.ID,
|
||||
Name: house.Name,
|
||||
Description: house.Description,
|
||||
}
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取成功", resp, action, "获取成功", resp)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取成功", house, action, "获取成功", house)
|
||||
}
|
||||
|
||||
// ListPigHouses godoc
|
||||
@@ -110,16 +100,7 @@ func (c *PigFarmController) ListPigHouses(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取列表失败", action, "业务逻辑失败", nil)
|
||||
}
|
||||
|
||||
var resp []dto.PigHouseResponse
|
||||
for _, house := range houses {
|
||||
resp = append(resp, dto.PigHouseResponse{
|
||||
ID: house.ID,
|
||||
Name: house.Name,
|
||||
Description: house.Description,
|
||||
})
|
||||
}
|
||||
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取成功", resp, action, "获取成功", resp)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取成功", houses, action, "获取成功", houses)
|
||||
}
|
||||
|
||||
// UpdatePigHouse godoc
|
||||
@@ -154,12 +135,7 @@ func (c *PigFarmController) UpdatePigHouse(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "更新失败", action, "业务逻辑失败", req)
|
||||
}
|
||||
|
||||
resp := dto.PigHouseResponse{
|
||||
ID: house.ID,
|
||||
Name: house.Name,
|
||||
Description: house.Description,
|
||||
}
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "更新成功", resp, action, "更新成功", resp)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "更新成功", house, action, "更新成功", house)
|
||||
}
|
||||
|
||||
// DeletePigHouse godoc
|
||||
@@ -222,14 +198,7 @@ func (c *PigFarmController) CreatePen(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "创建猪栏失败", action, "业务逻辑失败", req)
|
||||
}
|
||||
|
||||
resp := dto.PenResponse{
|
||||
ID: pen.ID,
|
||||
PenNumber: pen.PenNumber,
|
||||
HouseID: pen.HouseID,
|
||||
Capacity: pen.Capacity,
|
||||
Status: pen.Status,
|
||||
}
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeCreated, "创建成功", resp, action, "创建成功", resp)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeCreated, "创建成功", pen, action, "创建成功", pen)
|
||||
}
|
||||
|
||||
// GetPen godoc
|
||||
@@ -312,15 +281,7 @@ func (c *PigFarmController) UpdatePen(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "更新失败", action, "业务逻辑失败", req)
|
||||
}
|
||||
|
||||
resp := dto.PenResponse{
|
||||
ID: pen.ID,
|
||||
PenNumber: pen.PenNumber,
|
||||
HouseID: pen.HouseID,
|
||||
Capacity: pen.Capacity,
|
||||
Status: pen.Status,
|
||||
PigBatchID: pen.PigBatchID,
|
||||
}
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "更新成功", resp, action, "更新成功", resp)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "更新成功", pen, action, "更新成功", pen)
|
||||
}
|
||||
|
||||
// DeletePen godoc
|
||||
@@ -388,13 +349,5 @@ func (c *PigFarmController) UpdatePenStatus(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "更新猪栏状态失败", action, err.Error(), id)
|
||||
}
|
||||
|
||||
resp := dto.PenResponse{
|
||||
ID: pen.ID,
|
||||
PenNumber: pen.PenNumber,
|
||||
HouseID: pen.HouseID,
|
||||
Capacity: pen.Capacity,
|
||||
Status: pen.Status,
|
||||
PigBatchID: pen.PigBatchID,
|
||||
}
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "更新成功", resp, action, "更新成功", resp)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "更新成功", pen, action, "更新成功", pen)
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/dto"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/service"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
@@ -44,18 +43,7 @@ func (c *Controller) ListSensorData(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req)
|
||||
}
|
||||
|
||||
opts := repository.SensorDataListOptions{
|
||||
DeviceID: req.DeviceID,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
if req.SensorType != nil {
|
||||
sensorType := models.SensorType(*req.SensorType)
|
||||
opts.SensorType = &sensorType
|
||||
}
|
||||
|
||||
data, total, err := c.monitorService.ListSensorData(opts, req.Page, req.PageSize)
|
||||
resp, err := c.monitorService.ListSensorData(&req)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrInvalidPagination) {
|
||||
c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err)
|
||||
@@ -66,8 +54,7 @@ func (c *Controller) ListSensorData(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取传感器数据失败: "+err.Error(), actionType, "服务层查询失败", req)
|
||||
}
|
||||
|
||||
resp := dto.NewListSensorDataResponse(data, total, req.Page, req.PageSize)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取传感器数据成功", resp, actionType, "获取传感器数据成功", req)
|
||||
}
|
||||
|
||||
@@ -89,15 +76,7 @@ func (c *Controller) ListDeviceCommandLogs(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req)
|
||||
}
|
||||
|
||||
opts := repository.DeviceCommandLogListOptions{
|
||||
DeviceID: req.DeviceID,
|
||||
ReceivedSuccess: req.ReceivedSuccess,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
|
||||
data, total, err := c.monitorService.ListDeviceCommandLogs(opts, req.Page, req.PageSize)
|
||||
resp, err := c.monitorService.ListDeviceCommandLogs(&req)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrInvalidPagination) {
|
||||
c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err)
|
||||
@@ -108,8 +87,7 @@ func (c *Controller) ListDeviceCommandLogs(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取设备命令日志失败: "+err.Error(), actionType, "服务层查询失败", req)
|
||||
}
|
||||
|
||||
resp := dto.NewListDeviceCommandLogResponse(data, total, req.Page, req.PageSize)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取设备命令日志成功", resp, actionType, "获取设备命令日志成功", req)
|
||||
}
|
||||
|
||||
@@ -131,18 +109,7 @@ func (c *Controller) ListPlanExecutionLogs(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req)
|
||||
}
|
||||
|
||||
opts := repository.PlanExecutionLogListOptions{
|
||||
PlanID: req.PlanID,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
if req.Status != nil {
|
||||
status := models.ExecutionStatus(*req.Status)
|
||||
opts.Status = &status
|
||||
}
|
||||
|
||||
planLogs, plans, total, err := c.monitorService.ListPlanExecutionLogs(opts, req.Page, req.PageSize)
|
||||
resp, err := c.monitorService.ListPlanExecutionLogs(&req)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrInvalidPagination) {
|
||||
c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err)
|
||||
@@ -153,8 +120,7 @@ func (c *Controller) ListPlanExecutionLogs(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取计划执行日志失败: "+err.Error(), actionType, "服务层查询失败", req)
|
||||
}
|
||||
|
||||
resp := dto.NewListPlanExecutionLogResponse(planLogs, plans, total, req.Page, req.PageSize)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(planLogs), total)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取计划执行日志成功", resp, actionType, "获取计划执行日志成功", req)
|
||||
}
|
||||
|
||||
@@ -176,19 +142,7 @@ func (c *Controller) ListTaskExecutionLogs(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req)
|
||||
}
|
||||
|
||||
opts := repository.TaskExecutionLogListOptions{
|
||||
PlanExecutionLogID: req.PlanExecutionLogID,
|
||||
TaskID: req.TaskID,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
if req.Status != nil {
|
||||
status := models.ExecutionStatus(*req.Status)
|
||||
opts.Status = &status
|
||||
}
|
||||
|
||||
data, total, err := c.monitorService.ListTaskExecutionLogs(opts, req.Page, req.PageSize)
|
||||
resp, err := c.monitorService.ListTaskExecutionLogs(&req)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrInvalidPagination) {
|
||||
c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err)
|
||||
@@ -199,8 +153,7 @@ func (c *Controller) ListTaskExecutionLogs(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取任务执行日志失败: "+err.Error(), actionType, "服务层查询失败", req)
|
||||
}
|
||||
|
||||
resp := dto.NewListTaskExecutionLogResponse(data, total, req.Page, req.PageSize)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取任务执行日志成功", resp, actionType, "获取任务执行日志成功", req)
|
||||
}
|
||||
|
||||
@@ -222,18 +175,7 @@ func (c *Controller) ListPendingCollections(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req)
|
||||
}
|
||||
|
||||
opts := repository.PendingCollectionListOptions{
|
||||
DeviceID: req.DeviceID,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
if req.Status != nil {
|
||||
status := models.PendingCollectionStatus(*req.Status)
|
||||
opts.Status = &status
|
||||
}
|
||||
|
||||
data, total, err := c.monitorService.ListPendingCollections(opts, req.Page, req.PageSize)
|
||||
resp, err := c.monitorService.ListPendingCollections(&req)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrInvalidPagination) {
|
||||
c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err)
|
||||
@@ -244,8 +186,7 @@ func (c *Controller) ListPendingCollections(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取待采集请求失败: "+err.Error(), actionType, "服务层查询失败", req)
|
||||
}
|
||||
|
||||
resp := dto.NewListPendingCollectionResponse(data, total, req.Page, req.PageSize)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取待采集请求成功", resp, actionType, "获取待采集请求成功", req)
|
||||
}
|
||||
|
||||
@@ -267,20 +208,7 @@ func (c *Controller) ListUserActionLogs(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req)
|
||||
}
|
||||
|
||||
opts := repository.UserActionLogListOptions{
|
||||
UserID: req.UserID,
|
||||
Username: req.Username,
|
||||
ActionType: req.ActionType,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
if req.Status != nil {
|
||||
status := models.AuditStatus(*req.Status)
|
||||
opts.Status = &status
|
||||
}
|
||||
|
||||
data, total, err := c.monitorService.ListUserActionLogs(opts, req.Page, req.PageSize)
|
||||
resp, err := c.monitorService.ListUserActionLogs(&req)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrInvalidPagination) {
|
||||
c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err)
|
||||
@@ -291,8 +219,7 @@ func (c *Controller) ListUserActionLogs(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取用户操作日志失败: "+err.Error(), actionType, "服务层查询失败", req)
|
||||
}
|
||||
|
||||
resp := dto.NewListUserActionLogResponse(data, total, req.Page, req.PageSize)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取用户操作日志成功", resp, actionType, "获取用户操作日志成功", req)
|
||||
}
|
||||
|
||||
@@ -314,15 +241,7 @@ func (c *Controller) ListRawMaterialPurchases(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req)
|
||||
}
|
||||
|
||||
opts := repository.RawMaterialPurchaseListOptions{
|
||||
RawMaterialID: req.RawMaterialID,
|
||||
Supplier: req.Supplier,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
|
||||
data, total, err := c.monitorService.ListRawMaterialPurchases(opts, req.Page, req.PageSize)
|
||||
resp, err := c.monitorService.ListRawMaterialPurchases(&req)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrInvalidPagination) {
|
||||
c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err)
|
||||
@@ -333,8 +252,7 @@ func (c *Controller) ListRawMaterialPurchases(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取原料采购记录失败: "+err.Error(), actionType, "服务层查询失败", req)
|
||||
}
|
||||
|
||||
resp := dto.NewListRawMaterialPurchaseResponse(data, total, req.Page, req.PageSize)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取原料采购记录成功", resp, actionType, "获取原料采购记录成功", req)
|
||||
}
|
||||
|
||||
@@ -356,19 +274,7 @@ func (c *Controller) ListRawMaterialStockLogs(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req)
|
||||
}
|
||||
|
||||
opts := repository.RawMaterialStockLogListOptions{
|
||||
RawMaterialID: req.RawMaterialID,
|
||||
SourceID: req.SourceID,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
if req.SourceType != nil {
|
||||
sourceType := models.StockLogSourceType(*req.SourceType)
|
||||
opts.SourceType = &sourceType
|
||||
}
|
||||
|
||||
data, total, err := c.monitorService.ListRawMaterialStockLogs(opts, req.Page, req.PageSize)
|
||||
resp, err := c.monitorService.ListRawMaterialStockLogs(&req)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrInvalidPagination) {
|
||||
c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err)
|
||||
@@ -379,8 +285,7 @@ func (c *Controller) ListRawMaterialStockLogs(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取原料库存日志失败: "+err.Error(), actionType, "服务层查询失败", req)
|
||||
}
|
||||
|
||||
resp := dto.NewListRawMaterialStockLogResponse(data, total, req.Page, req.PageSize)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取原料库存日志成功", resp, actionType, "获取原料库存日志成功", req)
|
||||
}
|
||||
|
||||
@@ -402,16 +307,7 @@ func (c *Controller) ListFeedUsageRecords(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req)
|
||||
}
|
||||
|
||||
opts := repository.FeedUsageRecordListOptions{
|
||||
PenID: req.PenID,
|
||||
FeedFormulaID: req.FeedFormulaID,
|
||||
OperatorID: req.OperatorID,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
|
||||
data, total, err := c.monitorService.ListFeedUsageRecords(opts, req.Page, req.PageSize)
|
||||
resp, err := c.monitorService.ListFeedUsageRecords(&req)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrInvalidPagination) {
|
||||
c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err)
|
||||
@@ -422,8 +318,7 @@ func (c *Controller) ListFeedUsageRecords(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取饲料使用记录失败: "+err.Error(), actionType, "服务层查询失败", req)
|
||||
}
|
||||
|
||||
resp := dto.NewListFeedUsageRecordResponse(data, total, req.Page, req.PageSize)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取饲料使用记录成功", resp, actionType, "获取饲料使用记录成功", req)
|
||||
}
|
||||
|
||||
@@ -445,20 +340,7 @@ func (c *Controller) ListMedicationLogs(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req)
|
||||
}
|
||||
|
||||
opts := repository.MedicationLogListOptions{
|
||||
PigBatchID: req.PigBatchID,
|
||||
MedicationID: req.MedicationID,
|
||||
OperatorID: req.OperatorID,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
if req.Reason != nil {
|
||||
reason := models.MedicationReasonType(*req.Reason)
|
||||
opts.Reason = &reason
|
||||
}
|
||||
|
||||
data, total, err := c.monitorService.ListMedicationLogs(opts, req.Page, req.PageSize)
|
||||
resp, err := c.monitorService.ListMedicationLogs(&req)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrInvalidPagination) {
|
||||
c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err)
|
||||
@@ -469,8 +351,7 @@ func (c *Controller) ListMedicationLogs(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取用药记录失败: "+err.Error(), actionType, "服务层查询失败", req)
|
||||
}
|
||||
|
||||
resp := dto.NewListMedicationLogResponse(data, total, req.Page, req.PageSize)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取用药记录成功", resp, actionType, "获取用药记录成功", req)
|
||||
}
|
||||
|
||||
@@ -492,19 +373,7 @@ func (c *Controller) ListPigBatchLogs(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req)
|
||||
}
|
||||
|
||||
opts := repository.PigBatchLogListOptions{
|
||||
PigBatchID: req.PigBatchID,
|
||||
OperatorID: req.OperatorID,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
if req.ChangeType != nil {
|
||||
changeType := models.LogChangeType(*req.ChangeType)
|
||||
opts.ChangeType = &changeType
|
||||
}
|
||||
|
||||
data, total, err := c.monitorService.ListPigBatchLogs(opts, req.Page, req.PageSize)
|
||||
resp, err := c.monitorService.ListPigBatchLogs(&req)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrInvalidPagination) {
|
||||
c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err)
|
||||
@@ -515,8 +384,7 @@ func (c *Controller) ListPigBatchLogs(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取猪批次日志失败: "+err.Error(), actionType, "服务层查询失败", req)
|
||||
}
|
||||
|
||||
resp := dto.NewListPigBatchLogResponse(data, total, req.Page, req.PageSize)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取猪批次日志成功", resp, actionType, "获取猪批次日志成功", req)
|
||||
}
|
||||
|
||||
@@ -538,14 +406,7 @@ func (c *Controller) ListWeighingBatches(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req)
|
||||
}
|
||||
|
||||
opts := repository.WeighingBatchListOptions{
|
||||
PigBatchID: req.PigBatchID,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
|
||||
data, total, err := c.monitorService.ListWeighingBatches(opts, req.Page, req.PageSize)
|
||||
resp, err := c.monitorService.ListWeighingBatches(&req)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrInvalidPagination) {
|
||||
c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err)
|
||||
@@ -556,8 +417,7 @@ func (c *Controller) ListWeighingBatches(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取批次称重记录失败: "+err.Error(), actionType, "服务层查询失败", req)
|
||||
}
|
||||
|
||||
resp := dto.NewListWeighingBatchResponse(data, total, req.Page, req.PageSize)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取批次称重记录成功", resp, actionType, "获取批次称重记录成功", req)
|
||||
}
|
||||
|
||||
@@ -579,16 +439,7 @@ func (c *Controller) ListWeighingRecords(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req)
|
||||
}
|
||||
|
||||
opts := repository.WeighingRecordListOptions{
|
||||
WeighingBatchID: req.WeighingBatchID,
|
||||
PenID: req.PenID,
|
||||
OperatorID: req.OperatorID,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
|
||||
data, total, err := c.monitorService.ListWeighingRecords(opts, req.Page, req.PageSize)
|
||||
resp, err := c.monitorService.ListWeighingRecords(&req)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrInvalidPagination) {
|
||||
c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err)
|
||||
@@ -599,8 +450,7 @@ func (c *Controller) ListWeighingRecords(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取单次称重记录失败: "+err.Error(), actionType, "服务层查询失败", req)
|
||||
}
|
||||
|
||||
resp := dto.NewListWeighingRecordResponse(data, total, req.Page, req.PageSize)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取单次称重记录成功", resp, actionType, "获取单次称重记录成功", req)
|
||||
}
|
||||
|
||||
@@ -622,21 +472,7 @@ func (c *Controller) ListPigTransferLogs(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req)
|
||||
}
|
||||
|
||||
opts := repository.PigTransferLogListOptions{
|
||||
PigBatchID: req.PigBatchID,
|
||||
PenID: req.PenID,
|
||||
OperatorID: req.OperatorID,
|
||||
CorrelationID: req.CorrelationID,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
if req.TransferType != nil {
|
||||
transferType := models.PigTransferType(*req.TransferType)
|
||||
opts.TransferType = &transferType
|
||||
}
|
||||
|
||||
data, total, err := c.monitorService.ListPigTransferLogs(opts, req.Page, req.PageSize)
|
||||
resp, err := c.monitorService.ListPigTransferLogs(&req)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrInvalidPagination) {
|
||||
c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err)
|
||||
@@ -647,8 +483,7 @@ func (c *Controller) ListPigTransferLogs(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取猪只迁移日志失败: "+err.Error(), actionType, "服务层查询失败", req)
|
||||
}
|
||||
|
||||
resp := dto.NewListPigTransferLogResponse(data, total, req.Page, req.PageSize)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取猪只迁移日志成功", resp, actionType, "获取猪只迁移日志成功", req)
|
||||
}
|
||||
|
||||
@@ -670,24 +505,7 @@ func (c *Controller) ListPigSickLogs(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req)
|
||||
}
|
||||
|
||||
opts := repository.PigSickLogListOptions{
|
||||
PigBatchID: req.PigBatchID,
|
||||
PenID: req.PenID,
|
||||
OperatorID: req.OperatorID,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
if req.Reason != nil {
|
||||
reason := models.PigBatchSickPigReasonType(*req.Reason)
|
||||
opts.Reason = &reason
|
||||
}
|
||||
if req.TreatmentLocation != nil {
|
||||
treatmentLocation := models.PigBatchSickPigTreatmentLocation(*req.TreatmentLocation)
|
||||
opts.TreatmentLocation = &treatmentLocation
|
||||
}
|
||||
|
||||
data, total, err := c.monitorService.ListPigSickLogs(opts, req.Page, req.PageSize)
|
||||
resp, err := c.monitorService.ListPigSickLogs(&req)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrInvalidPagination) {
|
||||
c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err)
|
||||
@@ -698,8 +516,7 @@ func (c *Controller) ListPigSickLogs(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取病猪日志失败: "+err.Error(), actionType, "服务层查询失败", req)
|
||||
}
|
||||
|
||||
resp := dto.NewListPigSickLogResponse(data, total, req.Page, req.PageSize)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取病猪日志成功", resp, actionType, "获取病猪日志成功", req)
|
||||
}
|
||||
|
||||
@@ -721,16 +538,7 @@ func (c *Controller) ListPigPurchases(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req)
|
||||
}
|
||||
|
||||
opts := repository.PigPurchaseListOptions{
|
||||
PigBatchID: req.PigBatchID,
|
||||
Supplier: req.Supplier,
|
||||
OperatorID: req.OperatorID,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
|
||||
data, total, err := c.monitorService.ListPigPurchases(opts, req.Page, req.PageSize)
|
||||
resp, err := c.monitorService.ListPigPurchases(&req)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrInvalidPagination) {
|
||||
c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err)
|
||||
@@ -741,8 +549,7 @@ func (c *Controller) ListPigPurchases(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取猪只采购记录失败: "+err.Error(), actionType, "服务层查询失败", req)
|
||||
}
|
||||
|
||||
resp := dto.NewListPigPurchaseResponse(data, total, req.Page, req.PageSize)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取猪只采购记录成功", resp, actionType, "获取猪只采购记录成功", req)
|
||||
}
|
||||
|
||||
@@ -764,16 +571,7 @@ func (c *Controller) ListPigSales(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req)
|
||||
}
|
||||
|
||||
opts := repository.PigSaleListOptions{
|
||||
PigBatchID: req.PigBatchID,
|
||||
Buyer: req.Buyer,
|
||||
OperatorID: req.OperatorID,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
|
||||
data, total, err := c.monitorService.ListPigSales(opts, req.Page, req.PageSize)
|
||||
resp, err := c.monitorService.ListPigSales(&req)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrInvalidPagination) {
|
||||
c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err)
|
||||
@@ -784,8 +582,7 @@ func (c *Controller) ListPigSales(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取猪只售卖记录失败: "+err.Error(), actionType, "服务层查询失败", req)
|
||||
}
|
||||
|
||||
resp := dto.NewListPigSaleResponse(data, total, req.Page, req.PageSize)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取猪只售卖记录成功", resp, actionType, "获取猪只售卖记录成功", req)
|
||||
}
|
||||
|
||||
@@ -807,17 +604,7 @@ func (c *Controller) ListNotifications(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req)
|
||||
}
|
||||
|
||||
opts := repository.NotificationListOptions{
|
||||
UserID: req.UserID,
|
||||
NotifierType: req.NotifierType,
|
||||
Level: req.Level,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
OrderBy: req.OrderBy,
|
||||
Status: req.Status,
|
||||
}
|
||||
|
||||
data, total, err := c.monitorService.ListNotifications(opts, req.Page, req.PageSize)
|
||||
resp, err := c.monitorService.ListNotifications(&req)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrInvalidPagination) {
|
||||
c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err)
|
||||
@@ -828,7 +615,6 @@ func (c *Controller) ListNotifications(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "批量查询通知失败: "+err.Error(), actionType, "服务层查询失败", req)
|
||||
}
|
||||
|
||||
resp := dto.NewListNotificationResponse(data, total, req.Page, req.PageSize)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(data), total)
|
||||
c.logger.Infof("%s: 成功, 获取到 %d 条记录, 总计 %d 条", actionType, len(resp.List), resp.Pagination.Total)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "批量查询通知成功", resp, actionType, "批量查询通知成功", req)
|
||||
}
|
||||
|
||||
@@ -6,29 +6,24 @@ import (
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/controller"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/dto"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/scheduler"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/service"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
||||
"github.com/labstack/echo/v4"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// --- 控制器定义 ---
|
||||
|
||||
// Controller 定义了计划相关的控制器
|
||||
type Controller struct {
|
||||
logger *logs.Logger
|
||||
planRepo repository.PlanRepository
|
||||
analysisPlanTaskManager *scheduler.AnalysisPlanTaskManager
|
||||
logger *logs.Logger
|
||||
planService service.PlanService
|
||||
}
|
||||
|
||||
// NewController 创建一个新的 Controller 实例
|
||||
func NewController(logger *logs.Logger, planRepo repository.PlanRepository, analysisPlanTaskManager *scheduler.AnalysisPlanTaskManager) *Controller {
|
||||
func NewController(logger *logs.Logger, planService service.PlanService) *Controller {
|
||||
return &Controller{
|
||||
logger: logger,
|
||||
planRepo: planRepo,
|
||||
analysisPlanTaskManager: analysisPlanTaskManager,
|
||||
logger: logger,
|
||||
planService: planService,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,46 +47,19 @@ func (c *Controller) CreatePlan(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req)
|
||||
}
|
||||
|
||||
// 使用已有的转换函数,它已经包含了验证和重排逻辑
|
||||
planToCreate, err := dto.NewPlanFromCreateRequest(&req)
|
||||
// 调用服务层创建计划
|
||||
resp, err := c.planService.CreatePlan(&req)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 计划数据校验失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "计划数据校验失败: "+err.Error(), actionType, "计划数据校验失败", req)
|
||||
}
|
||||
|
||||
// --- 业务规则处理 ---
|
||||
// 1. 设置计划类型:用户创建的计划永远是自定义计划
|
||||
planToCreate.PlanType = models.PlanTypeCustom
|
||||
|
||||
// 2. 自动判断 ContentType
|
||||
if len(req.SubPlanIDs) > 0 {
|
||||
planToCreate.ContentType = models.PlanContentTypeSubPlans
|
||||
} else {
|
||||
// 如果 SubPlanIDs 未提供,则默认为 Tasks 类型(即使 Tasks 字段也未提供)
|
||||
planToCreate.ContentType = models.PlanContentTypeTasks
|
||||
}
|
||||
|
||||
// 调用仓库方法创建计划
|
||||
if err := c.planRepo.CreatePlan(planToCreate); err != nil {
|
||||
c.logger.Errorf("%s: 数据库创建计划失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "创建计划失败: "+err.Error(), actionType, "数据库创建计划失败", planToCreate)
|
||||
}
|
||||
|
||||
// 创建成功后,调用 manager 确保触发器任务定义存在,但不立即加入待执行队列
|
||||
if err := c.analysisPlanTaskManager.EnsureAnalysisTaskDefinition(planToCreate.ID); err != nil {
|
||||
// 这是一个非阻塞性错误,我们只记录日志,因为主流程(创建计划)已经成功
|
||||
c.logger.Errorf("为新创建的计划 %d 确保触发器任务定义失败: %v", planToCreate.ID, err)
|
||||
}
|
||||
|
||||
// 使用已有的转换函数将创建后的模型转换为响应对象
|
||||
resp, err := dto.NewPlanToResponse(planToCreate)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 序列化响应失败: %v, Plan: %+v", actionType, err, planToCreate)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "计划创建成功,但响应生成失败", actionType, "响应序列化失败", planToCreate)
|
||||
c.logger.Errorf("%s: 服务层创建计划失败: %v", actionType, err)
|
||||
// 根据服务层返回的错误类型,转换为相应的HTTP状态码
|
||||
if errors.Is(err, service.ErrPlanNotFound) {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, err.Error(), actionType, "计划数据校验失败或关联计划不存在", req)
|
||||
}
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "创建计划失败: "+err.Error(), actionType, "服务层创建计划失败", req)
|
||||
}
|
||||
|
||||
// 使用统一的成功响应函数
|
||||
c.logger.Infof("%s: 计划创建成功, ID: %d", actionType, planToCreate.ID)
|
||||
c.logger.Infof("%s: 计划创建成功, ID: %d", actionType, resp.ID)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeCreated, "计划创建成功", resp, actionType, "计划创建成功", resp)
|
||||
}
|
||||
|
||||
@@ -114,24 +82,14 @@ func (c *Controller) GetPlan(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的计划ID格式", actionType, "计划ID格式错误", idStr)
|
||||
}
|
||||
|
||||
// 2. 调用仓库层获取计划详情
|
||||
plan, err := c.planRepo.GetPlanByID(uint(id))
|
||||
// 调用服务层获取计划详情
|
||||
resp, err := c.planService.GetPlanByID(uint(id))
|
||||
if err != nil {
|
||||
// 判断是否为“未找到”错误
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.logger.Warnf("%s: 计划不存在, ID: %d", actionType, id)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "计划不存在", actionType, "计划不存在", id)
|
||||
c.logger.Errorf("%s: 服务层获取计划详情失败: %v, ID: %d", actionType, err, id)
|
||||
if errors.Is(err, service.ErrPlanNotFound) {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, err.Error(), actionType, "计划不存在", id)
|
||||
}
|
||||
// 其他数据库错误视为内部错误
|
||||
c.logger.Errorf("%s: 数据库查询失败: %v, ID: %d", actionType, err, id)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取计划详情时发生内部错误", actionType, "数据库查询失败", id)
|
||||
}
|
||||
|
||||
// 3. 将模型转换为响应 DTO
|
||||
resp, err := dto.NewPlanToResponse(plan)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 序列化响应失败: %v, Plan: %+v", actionType, err, plan)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取计划详情失败: 内部数据格式错误", actionType, "响应序列化失败", plan)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取计划详情失败: "+err.Error(), actionType, "服务层获取计划详情失败", id)
|
||||
}
|
||||
|
||||
// 4. 发送成功响应
|
||||
@@ -156,31 +114,14 @@ func (c *Controller) ListPlans(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "查询参数绑定失败", query)
|
||||
}
|
||||
|
||||
// 1. 调用仓库层获取所有计划
|
||||
opts := repository.ListPlansOptions{PlanType: query.PlanType}
|
||||
plans, total, err := c.planRepo.ListPlans(opts, query.Page, query.PageSize)
|
||||
// 调用服务层获取计划列表
|
||||
resp, err := c.planService.ListPlans(&query)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 数据库查询失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取计划列表时发生内部错误", actionType, "数据库查询失败", nil)
|
||||
c.logger.Errorf("%s: 服务层获取计划列表失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取计划列表失败: "+err.Error(), actionType, "服务层获取计划列表失败", nil)
|
||||
}
|
||||
|
||||
// 2. 将模型转换为响应 DTO
|
||||
planResponses := make([]dto.PlanResponse, 0, len(plans))
|
||||
for _, p := range plans {
|
||||
resp, err := dto.NewPlanToResponse(&p)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 序列化响应失败: %v, Plan: %+v", actionType, err, p)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取计划列表失败: 内部数据格式错误", actionType, "响应序列化失败", p)
|
||||
}
|
||||
planResponses = append(planResponses, *resp)
|
||||
}
|
||||
|
||||
// 3. 构造并发送成功响应
|
||||
resp := dto.ListPlansResponse{
|
||||
Plans: planResponses,
|
||||
Total: total,
|
||||
}
|
||||
c.logger.Infof("%s: 获取计划列表成功, 数量: %d", actionType, len(planResponses))
|
||||
c.logger.Infof("%s: 获取计划列表成功, 数量: %d", actionType, len(resp.Plans))
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取计划列表成功", resp, actionType, "获取计划列表成功", resp)
|
||||
}
|
||||
|
||||
@@ -212,71 +153,20 @@ func (c *Controller) UpdatePlan(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req)
|
||||
}
|
||||
|
||||
// 3. 检查计划是否存在
|
||||
existingPlan, err := c.planRepo.GetBasicPlanByID(uint(id))
|
||||
// 调用服务层更新计划
|
||||
resp, err := c.planService.UpdatePlan(uint(id), &req)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.logger.Warnf("%s: 计划不存在, ID: %d", actionType, id)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "计划不存在", actionType, "计划不存在", id)
|
||||
c.logger.Errorf("%s: 服务层更新计划失败: %v, ID: %d", actionType, err, id)
|
||||
if errors.Is(err, service.ErrPlanNotFound) {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, err.Error(), actionType, "计划不存在", id)
|
||||
} else if errors.Is(err, service.ErrPlanCannotBeModified) {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeForbidden, err.Error(), actionType, "系统计划不允许修改", id)
|
||||
}
|
||||
c.logger.Errorf("%s: 获取计划信息失败: %v, ID: %d", actionType, err, id)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取计划信息时发生内部错误", actionType, "数据库查询失败", id)
|
||||
}
|
||||
|
||||
// 4. 业务规则:系统计划不允许修改
|
||||
if existingPlan.PlanType == models.PlanTypeSystem {
|
||||
c.logger.Warnf("%s: 尝试修改系统计划, ID: %d", actionType, id)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeForbidden, "系统计划不允许修改", actionType, "尝试修改系统计划", id)
|
||||
}
|
||||
|
||||
// 5. 将请求转换为模型(转换函数带校验)
|
||||
planToUpdate, err := dto.NewPlanFromUpdateRequest(&req)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 计划数据校验失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "计划数据校验失败: "+err.Error(), actionType, "计划数据校验失败", req)
|
||||
}
|
||||
planToUpdate.ID = uint(id) // 确保ID被设置
|
||||
|
||||
// --- 自动判断 ContentType ---
|
||||
if len(req.SubPlanIDs) > 0 {
|
||||
planToUpdate.ContentType = models.PlanContentTypeSubPlans
|
||||
} else {
|
||||
// 如果 SubPlanIDs 未提供,则默认为 Tasks 类型(即使 Tasks 字段也未提供)
|
||||
planToUpdate.ContentType = models.PlanContentTypeTasks
|
||||
}
|
||||
|
||||
// 6. 调用仓库方法更新计划
|
||||
// 只要是更新任务,就重置执行计数器
|
||||
planToUpdate.ExecuteCount = 0 // 重置计数器
|
||||
c.logger.Infof("计划 #%d 被更新,执行计数器已重置为 0。", planToUpdate.ID)
|
||||
|
||||
if err := c.planRepo.UpdatePlan(planToUpdate); err != nil {
|
||||
c.logger.Errorf("%s: 数据库更新计划失败: %v, Plan: %+v", actionType, err, planToUpdate)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "更新计划失败: "+err.Error(), actionType, "数据库更新计划失败", planToUpdate)
|
||||
}
|
||||
|
||||
// 更新成功后,调用 manager 确保触发器任务定义存在
|
||||
if err := c.analysisPlanTaskManager.EnsureAnalysisTaskDefinition(planToUpdate.ID); err != nil {
|
||||
// 这是一个非阻塞性错误,我们只记录日志
|
||||
c.logger.Errorf("为更新后的计划 %d 确保触发器任务定义失败: %v", planToUpdate.ID, err)
|
||||
}
|
||||
|
||||
// 7. 获取更新后的完整计划用于响应
|
||||
updatedPlan, err := c.planRepo.GetPlanByID(uint(id))
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 获取更新后计划详情失败: %v, ID: %d", actionType, err, id)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取更新后计划详情时发生内部错误", actionType, "获取更新后计划详情失败", id)
|
||||
}
|
||||
|
||||
// 8. 将模型转换为响应 DTO
|
||||
resp, err := dto.NewPlanToResponse(updatedPlan)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 序列化响应失败: %v, Updated Plan: %+v", actionType, err, updatedPlan)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "计划更新成功,但响应生成失败", actionType, "响应序列化失败", updatedPlan)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "更新计划失败: "+err.Error(), actionType, "服务层更新计划失败", req)
|
||||
}
|
||||
|
||||
// 9. 发送成功响应
|
||||
c.logger.Infof("%s: 计划更新成功, ID: %d", actionType, updatedPlan.ID)
|
||||
c.logger.Infof("%s: 计划更新成功, ID: %d", actionType, resp.ID)
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "计划更新成功", resp, actionType, "计划更新成功", resp)
|
||||
}
|
||||
|
||||
@@ -299,35 +189,16 @@ func (c *Controller) DeletePlan(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的计划ID格式", actionType, "计划ID格式错误", idStr)
|
||||
}
|
||||
|
||||
// 2. 检查计划是否存在
|
||||
plan, err := c.planRepo.GetBasicPlanByID(uint(id))
|
||||
// 调用服务层删除计划
|
||||
err = c.planService.DeletePlan(uint(id))
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.logger.Warnf("%s: 计划不存在, ID: %d", actionType, id)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "计划不存在", actionType, "计划不存在", id)
|
||||
c.logger.Errorf("%s: 服务层删除计划失败: %v, ID: %d", actionType, err, id)
|
||||
if errors.Is(err, service.ErrPlanNotFound) {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, err.Error(), actionType, "计划不存在", id)
|
||||
} else if errors.Is(err, service.ErrPlanCannotBeDeleted) {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeForbidden, err.Error(), actionType, "系统计划不允许删除", id)
|
||||
}
|
||||
c.logger.Errorf("%s: 获取计划信息失败: %v, ID: %d", actionType, err, id)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取计划信息时发生内部错误", actionType, "数据库查询失败", id)
|
||||
}
|
||||
|
||||
// 3. 业务规则:系统计划不允许删除
|
||||
if plan.PlanType == models.PlanTypeSystem {
|
||||
c.logger.Warnf("%s: 尝试删除系统计划, ID: %d", actionType, id)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeForbidden, "系统计划不允许删除", actionType, "尝试删除系统计划", id)
|
||||
}
|
||||
|
||||
// 4. 停止这个计划
|
||||
if plan.Status == models.PlanStatusEnabled {
|
||||
if err := c.planRepo.StopPlanTransactionally(uint(id)); err != nil {
|
||||
c.logger.Errorf("%s: 停止计划失败: %v, ID: %d", actionType, err, id)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "停止计划时发生内部错误: "+err.Error(), actionType, "停止计划失败", id)
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 调用仓库层删除计划
|
||||
if err := c.planRepo.DeletePlan(uint(id)); err != nil {
|
||||
c.logger.Errorf("%s: 数据库删除失败: %v, ID: %d", actionType, err, id)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "删除计划时发生内部错误", actionType, "数据库删除失败", id)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "删除计划失败: "+err.Error(), actionType, "服务层删除计划失败", id)
|
||||
}
|
||||
|
||||
// 6. 发送成功响应
|
||||
@@ -354,56 +225,18 @@ func (c *Controller) StartPlan(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的计划ID格式", actionType, "计划ID格式错误", idStr)
|
||||
}
|
||||
|
||||
// 2. 检查计划是否存在
|
||||
plan, err := c.planRepo.GetBasicPlanByID(uint(id))
|
||||
// 调用服务层启动计划
|
||||
err = c.planService.StartPlan(uint(id))
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.logger.Warnf("%s: 计划不存在, ID: %d", actionType, id)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "计划不存在", actionType, "计划不存在", id)
|
||||
c.logger.Errorf("%s: 服务层启动计划失败: %v, ID: %d", actionType, err, id)
|
||||
if errors.Is(err, service.ErrPlanNotFound) {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, err.Error(), actionType, "计划不存在", id)
|
||||
} else if errors.Is(err, service.ErrPlanCannotBeStarted) {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeForbidden, err.Error(), actionType, "系统计划不允许手动启动", id)
|
||||
} else if errors.Is(err, service.ErrPlanAlreadyEnabled) {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, err.Error(), actionType, "计划已处于启动状态", id)
|
||||
}
|
||||
c.logger.Errorf("%s: 获取计划信息失败: %v, ID: %d", actionType, err, id)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取计划信息时发生内部错误", actionType, "数据库查询失败", id)
|
||||
}
|
||||
|
||||
// 3. 业务规则检查
|
||||
if plan.PlanType == models.PlanTypeSystem {
|
||||
c.logger.Warnf("%s: 尝试手动启动系统计划, ID: %d", actionType, id)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeForbidden, "系统计划不允许手动启动", actionType, "尝试手动启动系统计划", id)
|
||||
}
|
||||
if plan.Status == models.PlanStatusEnabled {
|
||||
c.logger.Warnf("%s: 计划已处于启动状态,无需重复操作, ID: %d", actionType, id)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "计划已处于启动状态,无需重复操作", actionType, "计划已处于启动状态", id)
|
||||
}
|
||||
|
||||
// 4. 检查并重置执行计数器,然后更新计划状态为“已启动”
|
||||
// 只有当计划是从非 Enabled 状态(如 Disabled, Stopeed, Failed)启动时,才需要重置计数器
|
||||
if plan.Status != models.PlanStatusEnabled {
|
||||
// 如果计划是从停止或失败状态重新启动,且计数器不为0,则重置执行计数
|
||||
if plan.ExecuteCount > 0 {
|
||||
if err := c.planRepo.UpdateExecuteCount(plan.ID, 0); err != nil {
|
||||
c.logger.Errorf("%s: 重置计划执行计数失败: %v, ID: %d", actionType, err, plan.ID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "重置计划执行计数失败", actionType, "重置执行计数失败", plan.ID)
|
||||
}
|
||||
c.logger.Infof("计划 #%d 的执行计数器已重置为 0。", plan.ID)
|
||||
}
|
||||
|
||||
// 更新计划状态为“已启动”
|
||||
if err := c.planRepo.UpdatePlanStatus(plan.ID, models.PlanStatusEnabled); err != nil {
|
||||
c.logger.Errorf("%s: 更新计划状态失败: %v, ID: %d", actionType, err, plan.ID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "更新计划状态失败", actionType, "更新计划状态失败", plan.ID)
|
||||
}
|
||||
c.logger.Infof("已成功更新计划 #%d 的状态为 '已启动'。", plan.ID)
|
||||
} else {
|
||||
// 如果计划已经处于 Enabled 状态,则无需更新
|
||||
c.logger.Infof("计划 #%d 已处于启动状态,无需重复操作。", plan.ID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "计划已处于启动状态,无需重复操作", actionType, "计划已处于启动状态", plan.ID)
|
||||
}
|
||||
|
||||
// 5. 为计划创建或更新触发器
|
||||
if err := c.analysisPlanTaskManager.CreateOrUpdateTrigger(plan.ID); err != nil {
|
||||
// 此处错误不回滚状态,因为状态更新已成功,但需要明确告知用户触发器创建失败
|
||||
c.logger.Errorf("%s: 创建或更新触发器失败: %v, ID: %d", actionType, err, plan.ID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "计划状态已更新,但创建执行触发器失败,请检查计划配置或稍后重试", actionType, "创建执行触发器失败", plan.ID)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "启动计划失败: "+err.Error(), actionType, "服务层启动计划失败", id)
|
||||
}
|
||||
|
||||
// 6. 发送成功响应
|
||||
@@ -430,33 +263,18 @@ func (c *Controller) StopPlan(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的计划ID格式", actionType, "计划ID格式错误", idStr)
|
||||
}
|
||||
|
||||
// 2. 检查计划是否存在
|
||||
plan, err := c.planRepo.GetBasicPlanByID(uint(id))
|
||||
// 调用服务层停止计划
|
||||
err = c.planService.StopPlan(uint(id))
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.logger.Warnf("%s: 计划不存在, ID: %d", actionType, id)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "计划不存在", actionType, "计划不存在", id)
|
||||
c.logger.Errorf("%s: 服务层停止计划失败: %v, ID: %d", actionType, err, id)
|
||||
if errors.Is(err, service.ErrPlanNotFound) {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeNotFound, err.Error(), actionType, "计划不存在", id)
|
||||
} else if errors.Is(err, service.ErrPlanCannotBeStopped) {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeForbidden, err.Error(), actionType, "系统计划不允许停止", id)
|
||||
} else if errors.Is(err, service.ErrPlanNotEnabled) {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, err.Error(), actionType, "计划未启用", id)
|
||||
}
|
||||
c.logger.Errorf("%s: 获取计划信息失败: %v, ID: %d", actionType, err, id)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取计划信息时发生内部错误", actionType, "数据库查询失败", id)
|
||||
}
|
||||
|
||||
// 3. 业务规则:系统计划不允许停止
|
||||
if plan.PlanType == models.PlanTypeSystem {
|
||||
c.logger.Warnf("%s: 尝试停止系统计划, ID: %d", actionType, id)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeForbidden, "系统计划不允许停止", actionType, "尝试停止系统计划", id)
|
||||
}
|
||||
|
||||
// 4. 检查计划当前状态
|
||||
if plan.Status != models.PlanStatusEnabled {
|
||||
c.logger.Warnf("%s: 计划当前不是启用状态, ID: %d, Status: %s", actionType, id, plan.Status)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "计划当前不是启用状态", actionType, "计划未启用", id)
|
||||
}
|
||||
|
||||
// 5. 调用仓库层方法,该方法内部处理事务
|
||||
if err := c.planRepo.StopPlanTransactionally(uint(id)); err != nil {
|
||||
c.logger.Errorf("%s: 停止计划失败: %v, ID: %d", actionType, err, id)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "停止计划时发生内部错误: "+err.Error(), actionType, "停止计划失败", id)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "停止计划失败: "+err.Error(), actionType, "服务层停止计划失败", id)
|
||||
}
|
||||
|
||||
// 6. 发送成功响应
|
||||
|
||||
@@ -1,44 +1,29 @@
|
||||
package user
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/controller"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/dto"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/service"
|
||||
domain_notify "git.huangwc.com/pig/pig-farm-controller/internal/domain/notify"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/token"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
||||
"github.com/labstack/echo/v4"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Controller 用户控制器
|
||||
type Controller struct {
|
||||
userRepo repository.UserRepository
|
||||
monitorService service.MonitorService
|
||||
tokenService token.Service
|
||||
notifyService domain_notify.Service
|
||||
logger *logs.Logger
|
||||
userService service.UserService
|
||||
logger *logs.Logger
|
||||
}
|
||||
|
||||
// NewController 创建用户控制器实例
|
||||
func NewController(
|
||||
userRepo repository.UserRepository,
|
||||
monitorService service.MonitorService,
|
||||
userService service.UserService,
|
||||
logger *logs.Logger,
|
||||
tokenService token.Service,
|
||||
notifyService domain_notify.Service,
|
||||
) *Controller {
|
||||
return &Controller{
|
||||
userRepo: userRepo,
|
||||
monitorService: monitorService,
|
||||
tokenService: tokenService,
|
||||
notifyService: notifyService,
|
||||
logger: logger,
|
||||
userService: userService,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,28 +45,13 @@ func (c *Controller) CreateUser(ctx echo.Context) error {
|
||||
return controller.SendErrorResponse(ctx, controller.CodeBadRequest, err.Error())
|
||||
}
|
||||
|
||||
user := &models.User{
|
||||
Username: req.Username,
|
||||
Password: req.Password, // 密码会在 BeforeSave 钩子中哈希
|
||||
resp, err := c.userService.CreateUser(&req)
|
||||
if err != nil {
|
||||
c.logger.Errorf("创建用户: 服务层调用失败: %v", err)
|
||||
return controller.SendErrorResponse(ctx, controller.CodeInternalError, err.Error())
|
||||
}
|
||||
|
||||
if err := c.userRepo.Create(user); err != nil {
|
||||
c.logger.Errorf("创建用户: 创建用户失败: %v", err)
|
||||
|
||||
// 尝试查询用户,以判断是否是用户名重复导致的错误
|
||||
_, findErr := c.userRepo.FindByUsername(req.Username)
|
||||
if findErr == nil { // 如果能找到用户,说明是用户名重复
|
||||
return controller.SendErrorResponse(ctx, controller.CodeConflict, "用户名已存在")
|
||||
}
|
||||
|
||||
// 其他创建失败的情况
|
||||
return controller.SendErrorResponse(ctx, controller.CodeInternalError, "创建用户失败")
|
||||
}
|
||||
|
||||
return controller.SendResponse(ctx, controller.CodeCreated, "用户创建成功", dto.CreateUserResponse{
|
||||
Username: user.Username,
|
||||
ID: user.ID,
|
||||
})
|
||||
return controller.SendResponse(ctx, controller.CodeCreated, "用户创建成功", resp)
|
||||
}
|
||||
|
||||
// Login godoc
|
||||
@@ -100,94 +70,13 @@ func (c *Controller) Login(ctx echo.Context) error {
|
||||
return controller.SendErrorResponse(ctx, controller.CodeBadRequest, err.Error())
|
||||
}
|
||||
|
||||
// 使用新的方法,通过唯一标识符(用户名、邮箱等)查找用户
|
||||
user, err := c.userRepo.FindUserForLogin(req.Identifier)
|
||||
resp, err := c.userService.Login(&req)
|
||||
if err != nil {
|
||||
if err == gorm.ErrRecordNotFound {
|
||||
return controller.SendErrorResponse(ctx, controller.CodeUnauthorized, "登录凭证不正确")
|
||||
}
|
||||
c.logger.Errorf("登录: 查询用户失败: %v", err)
|
||||
return controller.SendErrorResponse(ctx, controller.CodeInternalError, "登录失败")
|
||||
c.logger.Errorf("登录: 服务层调用失败: %v", err)
|
||||
return controller.SendErrorResponse(ctx, controller.CodeUnauthorized, err.Error())
|
||||
}
|
||||
|
||||
if !user.CheckPassword(req.Password) {
|
||||
return controller.SendErrorResponse(ctx, controller.CodeUnauthorized, "登录凭证不正确")
|
||||
}
|
||||
|
||||
// 登录成功,生成 JWT token
|
||||
tokenString, err := c.tokenService.GenerateToken(user.ID)
|
||||
if err != nil {
|
||||
c.logger.Errorf("登录: 生成令牌失败: %v", err)
|
||||
return controller.SendErrorResponse(ctx, controller.CodeInternalError, "登录失败,无法生成认证信息")
|
||||
}
|
||||
|
||||
return controller.SendResponse(ctx, controller.CodeSuccess, "登录成功", dto.LoginResponse{
|
||||
Username: user.Username,
|
||||
ID: user.ID,
|
||||
Token: tokenString,
|
||||
})
|
||||
}
|
||||
|
||||
// ListUserHistory godoc
|
||||
// @Summary 获取指定用户的操作历史
|
||||
// @Description 根据用户ID,分页获取该用户的操作审计日志。支持与通用日志查询接口相同的过滤和排序参数。
|
||||
// @Tags 用户管理
|
||||
// @Security BearerAuth
|
||||
// @Produce json
|
||||
// @Param id path int true "用户ID"
|
||||
// @Param query query dto.ListUserActionLogRequest false "查询参数 (除了 user_id,它被路径中的ID覆盖)"
|
||||
// @Success 200 {object} controller.Response{data=dto.ListUserActionLogResponse} "业务码为200代表成功获取"
|
||||
// @Router /api/v1/users/{id}/history [get]
|
||||
func (c *Controller) ListUserHistory(ctx echo.Context) error {
|
||||
const actionType = "获取用户操作历史"
|
||||
|
||||
// 1. 解析路径中的用户ID,它的优先级最高
|
||||
userIDStr := ctx.Param("id")
|
||||
userID, err := strconv.ParseUint(userIDStr, 10, 64)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 无效的用户ID格式: %v, ID: %s", actionType, err, userIDStr)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的用户ID格式", actionType, "无效的用户ID格式", userIDStr)
|
||||
}
|
||||
|
||||
// 2. 绑定通用的查询请求 DTO
|
||||
var req dto.ListUserActionLogRequest
|
||||
if err := ctx.Bind(&req); err != nil {
|
||||
c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的查询参数: "+err.Error(), actionType, "参数绑定失败", req)
|
||||
}
|
||||
|
||||
// 3. 准备 Service 调用参数,并强制使用路径中的 UserID
|
||||
uid := uint(userID)
|
||||
req.UserID = &uid // 强制覆盖
|
||||
|
||||
opts := repository.UserActionLogListOptions{
|
||||
UserID: req.UserID,
|
||||
Username: req.Username,
|
||||
ActionType: req.ActionType,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
if req.Status != nil {
|
||||
status := models.AuditStatus(*req.Status)
|
||||
opts.Status = &status
|
||||
}
|
||||
|
||||
// 4. 调用 monitorService,复用其业务逻辑
|
||||
data, total, err := c.monitorService.ListUserActionLogs(opts, req.Page, req.PageSize)
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrInvalidPagination) {
|
||||
c.logger.Warnf("%s: 无效的分页参数: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的分页参数: "+err.Error(), actionType, "无效分页参数", opts)
|
||||
}
|
||||
c.logger.Errorf("%s: 服务层查询失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取用户历史记录失败", actionType, "服务层查询失败", opts)
|
||||
}
|
||||
|
||||
// 5. 使用复用的 DTO 构建并发送成功响应
|
||||
resp := dto.NewListUserActionLogResponse(data, total, req.Page, req.PageSize)
|
||||
c.logger.Infof("%s: 成功获取用户 %d 的操作历史, 数量: %d", actionType, userID, len(data))
|
||||
return controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取用户操作历史成功", resp, actionType, "获取用户操作历史成功", opts)
|
||||
return controller.SendResponse(ctx, controller.CodeSuccess, "登录成功", resp)
|
||||
}
|
||||
|
||||
// SendTestNotification godoc
|
||||
@@ -218,8 +107,8 @@ func (c *Controller) SendTestNotification(ctx echo.Context) error {
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "请求体格式错误或缺少 'type' 字段: "+err.Error(), actionType, "请求体绑定失败", req)
|
||||
}
|
||||
|
||||
// 3. 调用领域服务
|
||||
err = c.notifyService.SendTestMessage(uint(userID), req.Type)
|
||||
// 3. 调用服务层
|
||||
err = c.userService.SendTestNotification(uint(userID), &req)
|
||||
if err != nil {
|
||||
c.logger.Errorf("%s: 服务层调用失败: %v", actionType, err)
|
||||
return controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "发送测试消息失败: "+err.Error(), actionType, "服务层调用失败", map[string]interface{}{"userID": userID, "type": req.Type})
|
||||
|
||||
373
internal/app/service/device_service.go
Normal file
373
internal/app/service/device_service.go
Normal file
@@ -0,0 +1,373 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strconv"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/dto"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/device"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
||||
)
|
||||
|
||||
// DeviceService 定义了应用层的设备服务接口,用于协调设备相关的业务逻辑。
|
||||
type DeviceService interface {
|
||||
CreateDevice(req *dto.CreateDeviceRequest) (*dto.DeviceResponse, error)
|
||||
GetDevice(id string) (*dto.DeviceResponse, error)
|
||||
ListDevices() ([]*dto.DeviceResponse, error)
|
||||
UpdateDevice(id string, req *dto.UpdateDeviceRequest) (*dto.DeviceResponse, error)
|
||||
DeleteDevice(id string) error
|
||||
ManualControl(id string, req *dto.ManualControlDeviceRequest) error
|
||||
|
||||
CreateAreaController(req *dto.CreateAreaControllerRequest) (*dto.AreaControllerResponse, error)
|
||||
GetAreaController(id string) (*dto.AreaControllerResponse, error)
|
||||
ListAreaControllers() ([]*dto.AreaControllerResponse, error)
|
||||
UpdateAreaController(id string, req *dto.UpdateAreaControllerRequest) (*dto.AreaControllerResponse, error)
|
||||
DeleteAreaController(id string) error
|
||||
|
||||
CreateDeviceTemplate(req *dto.CreateDeviceTemplateRequest) (*dto.DeviceTemplateResponse, error)
|
||||
GetDeviceTemplate(id string) (*dto.DeviceTemplateResponse, error)
|
||||
ListDeviceTemplates() ([]*dto.DeviceTemplateResponse, error)
|
||||
UpdateDeviceTemplate(id string, req *dto.UpdateDeviceTemplateRequest) (*dto.DeviceTemplateResponse, error)
|
||||
DeleteDeviceTemplate(id string) error
|
||||
}
|
||||
|
||||
// deviceService 是 DeviceService 接口的具体实现。
|
||||
type deviceService struct {
|
||||
deviceRepo repository.DeviceRepository
|
||||
areaControllerRepo repository.AreaControllerRepository
|
||||
deviceTemplateRepo repository.DeviceTemplateRepository
|
||||
deviceDomainSvc device.Service // 依赖领域服务
|
||||
}
|
||||
|
||||
// NewDeviceService 创建一个新的 DeviceService 实例。
|
||||
func NewDeviceService(
|
||||
deviceRepo repository.DeviceRepository,
|
||||
areaControllerRepo repository.AreaControllerRepository,
|
||||
deviceTemplateRepo repository.DeviceTemplateRepository,
|
||||
deviceDomainSvc device.Service,
|
||||
) DeviceService {
|
||||
return &deviceService{
|
||||
deviceRepo: deviceRepo,
|
||||
areaControllerRepo: areaControllerRepo,
|
||||
deviceTemplateRepo: deviceTemplateRepo,
|
||||
deviceDomainSvc: deviceDomainSvc,
|
||||
}
|
||||
}
|
||||
|
||||
// --- Devices ---
|
||||
|
||||
func (s *deviceService) CreateDevice(req *dto.CreateDeviceRequest) (*dto.DeviceResponse, error) {
|
||||
propertiesJSON, err := json.Marshal(req.Properties)
|
||||
if err != nil {
|
||||
return nil, err // Consider wrapping this error for better context
|
||||
}
|
||||
|
||||
device := &models.Device{
|
||||
Name: req.Name,
|
||||
DeviceTemplateID: req.DeviceTemplateID,
|
||||
AreaControllerID: req.AreaControllerID,
|
||||
Location: req.Location,
|
||||
Properties: propertiesJSON,
|
||||
}
|
||||
|
||||
if err := device.SelfCheck(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.deviceRepo.Create(device); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
createdDevice, err := s.deviceRepo.FindByID(device.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dto.NewDeviceResponse(createdDevice)
|
||||
}
|
||||
|
||||
func (s *deviceService) GetDevice(id string) (*dto.DeviceResponse, error) {
|
||||
device, err := s.deviceRepo.FindByIDString(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dto.NewDeviceResponse(device)
|
||||
}
|
||||
|
||||
func (s *deviceService) ListDevices() ([]*dto.DeviceResponse, error) {
|
||||
devices, err := s.deviceRepo.ListAll()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dto.NewListDeviceResponse(devices)
|
||||
}
|
||||
|
||||
func (s *deviceService) UpdateDevice(id string, req *dto.UpdateDeviceRequest) (*dto.DeviceResponse, error) {
|
||||
existingDevice, err := s.deviceRepo.FindByIDString(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
propertiesJSON, err := json.Marshal(req.Properties)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
existingDevice.Name = req.Name
|
||||
existingDevice.DeviceTemplateID = req.DeviceTemplateID
|
||||
existingDevice.AreaControllerID = req.AreaControllerID
|
||||
existingDevice.Location = req.Location
|
||||
existingDevice.Properties = propertiesJSON
|
||||
|
||||
if err := existingDevice.SelfCheck(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.deviceRepo.Update(existingDevice); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
updatedDevice, err := s.deviceRepo.FindByID(existingDevice.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dto.NewDeviceResponse(updatedDevice)
|
||||
}
|
||||
|
||||
func (s *deviceService) DeleteDevice(id string) error {
|
||||
idUint, err := strconv.ParseUint(id, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if device exists before deleting
|
||||
_, err = s.deviceRepo.FindByID(uint(idUint))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.deviceRepo.Delete(uint(idUint))
|
||||
}
|
||||
|
||||
func (s *deviceService) ManualControl(id string, req *dto.ManualControlDeviceRequest) error {
|
||||
dev, err := s.deviceRepo.FindByIDString(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if req.Action == nil {
|
||||
return s.deviceDomainSvc.Collect(dev.AreaControllerID, []*models.Device{dev})
|
||||
} else {
|
||||
action := device.DeviceActionStart
|
||||
switch *req.Action {
|
||||
case "off":
|
||||
action = device.DeviceActionStop
|
||||
case "on":
|
||||
action = device.DeviceActionStart
|
||||
default:
|
||||
return errors.New("invalid action")
|
||||
}
|
||||
return s.deviceDomainSvc.Switch(dev, action)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Area Controllers ---
|
||||
|
||||
func (s *deviceService) CreateAreaController(req *dto.CreateAreaControllerRequest) (*dto.AreaControllerResponse, error) {
|
||||
propertiesJSON, err := json.Marshal(req.Properties)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ac := &models.AreaController{
|
||||
Name: req.Name,
|
||||
NetworkID: req.NetworkID,
|
||||
Location: req.Location,
|
||||
Properties: propertiesJSON,
|
||||
}
|
||||
|
||||
if err := ac.SelfCheck(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.areaControllerRepo.Create(ac); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dto.NewAreaControllerResponse(ac)
|
||||
}
|
||||
|
||||
func (s *deviceService) GetAreaController(id string) (*dto.AreaControllerResponse, error) {
|
||||
idUint, err := strconv.ParseUint(id, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ac, err := s.areaControllerRepo.FindByID(uint(idUint))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dto.NewAreaControllerResponse(ac)
|
||||
}
|
||||
|
||||
func (s *deviceService) ListAreaControllers() ([]*dto.AreaControllerResponse, error) {
|
||||
acs, err := s.areaControllerRepo.ListAll()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dto.NewListAreaControllerResponse(acs)
|
||||
}
|
||||
|
||||
func (s *deviceService) UpdateAreaController(id string, req *dto.UpdateAreaControllerRequest) (*dto.AreaControllerResponse, error) {
|
||||
idUint, err := strconv.ParseUint(id, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
existingAC, err := s.areaControllerRepo.FindByID(uint(idUint))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
propertiesJSON, err := json.Marshal(req.Properties)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
existingAC.Name = req.Name
|
||||
existingAC.NetworkID = req.NetworkID
|
||||
existingAC.Location = req.Location
|
||||
existingAC.Properties = propertiesJSON
|
||||
|
||||
if err := existingAC.SelfCheck(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.areaControllerRepo.Update(existingAC); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dto.NewAreaControllerResponse(existingAC)
|
||||
}
|
||||
|
||||
func (s *deviceService) DeleteAreaController(id string) error {
|
||||
idUint, err := strconv.ParseUint(id, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = s.areaControllerRepo.FindByID(uint(idUint))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.areaControllerRepo.Delete(uint(idUint))
|
||||
}
|
||||
|
||||
// --- Device Templates ---
|
||||
|
||||
func (s *deviceService) CreateDeviceTemplate(req *dto.CreateDeviceTemplateRequest) (*dto.DeviceTemplateResponse, error) {
|
||||
commandsJSON, err := json.Marshal(req.Commands)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
valuesJSON, err := json.Marshal(req.Values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
deviceTemplate := &models.DeviceTemplate{
|
||||
Name: req.Name,
|
||||
Manufacturer: req.Manufacturer,
|
||||
Description: req.Description,
|
||||
Category: req.Category,
|
||||
Commands: commandsJSON,
|
||||
Values: valuesJSON,
|
||||
}
|
||||
|
||||
if err := deviceTemplate.SelfCheck(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.deviceTemplateRepo.Create(deviceTemplate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dto.NewDeviceTemplateResponse(deviceTemplate)
|
||||
}
|
||||
|
||||
func (s *deviceService) GetDeviceTemplate(id string) (*dto.DeviceTemplateResponse, error) {
|
||||
idUint, err := strconv.ParseUint(id, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deviceTemplate, err := s.deviceTemplateRepo.FindByID(uint(idUint))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dto.NewDeviceTemplateResponse(deviceTemplate)
|
||||
}
|
||||
|
||||
func (s *deviceService) ListDeviceTemplates() ([]*dto.DeviceTemplateResponse, error) {
|
||||
deviceTemplates, err := s.deviceTemplateRepo.ListAll()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dto.NewListDeviceTemplateResponse(deviceTemplates)
|
||||
}
|
||||
|
||||
func (s *deviceService) UpdateDeviceTemplate(id string, req *dto.UpdateDeviceTemplateRequest) (*dto.DeviceTemplateResponse, error) {
|
||||
idUint, err := strconv.ParseUint(id, 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
existingDeviceTemplate, err := s.deviceTemplateRepo.FindByID(uint(idUint))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
commandsJSON, err := json.Marshal(req.Commands)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
valuesJSON, err := json.Marshal(req.Values)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
existingDeviceTemplate.Name = req.Name
|
||||
existingDeviceTemplate.Manufacturer = req.Manufacturer
|
||||
existingDeviceTemplate.Description = req.Description
|
||||
existingDeviceTemplate.Category = req.Category
|
||||
existingDeviceTemplate.Commands = commandsJSON
|
||||
existingDeviceTemplate.Values = valuesJSON
|
||||
|
||||
if err := existingDeviceTemplate.SelfCheck(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.deviceTemplateRepo.Update(existingDeviceTemplate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dto.NewDeviceTemplateResponse(existingDeviceTemplate)
|
||||
}
|
||||
|
||||
func (s *deviceService) DeleteDeviceTemplate(id string) error {
|
||||
idUint, err := strconv.ParseUint(id, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = s.deviceTemplateRepo.FindByID(uint(idUint))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.deviceTemplateRepo.Delete(uint(idUint))
|
||||
}
|
||||
@@ -1,30 +1,31 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/dto"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
||||
)
|
||||
|
||||
// MonitorService 定义了监控相关的业务逻辑服务接口
|
||||
type MonitorService interface {
|
||||
ListSensorData(opts repository.SensorDataListOptions, page, pageSize int) ([]models.SensorData, int64, error)
|
||||
ListDeviceCommandLogs(opts repository.DeviceCommandLogListOptions, page, pageSize int) ([]models.DeviceCommandLog, int64, error)
|
||||
ListPlanExecutionLogs(opts repository.PlanExecutionLogListOptions, page, pageSize int) ([]models.PlanExecutionLog, []models.Plan, int64, error)
|
||||
ListTaskExecutionLogs(opts repository.TaskExecutionLogListOptions, page, pageSize int) ([]models.TaskExecutionLog, int64, error)
|
||||
ListPendingCollections(opts repository.PendingCollectionListOptions, page, pageSize int) ([]models.PendingCollection, int64, error)
|
||||
ListUserActionLogs(opts repository.UserActionLogListOptions, page, pageSize int) ([]models.UserActionLog, int64, error)
|
||||
ListRawMaterialPurchases(opts repository.RawMaterialPurchaseListOptions, page, pageSize int) ([]models.RawMaterialPurchase, int64, error)
|
||||
ListRawMaterialStockLogs(opts repository.RawMaterialStockLogListOptions, page, pageSize int) ([]models.RawMaterialStockLog, int64, error)
|
||||
ListFeedUsageRecords(opts repository.FeedUsageRecordListOptions, page, pageSize int) ([]models.FeedUsageRecord, int64, error)
|
||||
ListMedicationLogs(opts repository.MedicationLogListOptions, page, pageSize int) ([]models.MedicationLog, int64, error)
|
||||
ListPigBatchLogs(opts repository.PigBatchLogListOptions, page, pageSize int) ([]models.PigBatchLog, int64, error)
|
||||
ListWeighingBatches(opts repository.WeighingBatchListOptions, page, pageSize int) ([]models.WeighingBatch, int64, error)
|
||||
ListWeighingRecords(opts repository.WeighingRecordListOptions, page, pageSize int) ([]models.WeighingRecord, int64, error)
|
||||
ListPigTransferLogs(opts repository.PigTransferLogListOptions, page, pageSize int) ([]models.PigTransferLog, int64, error)
|
||||
ListPigSickLogs(opts repository.PigSickLogListOptions, page, pageSize int) ([]models.PigSickLog, int64, error)
|
||||
ListPigPurchases(opts repository.PigPurchaseListOptions, page, pageSize int) ([]models.PigPurchase, int64, error)
|
||||
ListPigSales(opts repository.PigSaleListOptions, page, pageSize int) ([]models.PigSale, int64, error)
|
||||
ListNotifications(opts repository.NotificationListOptions, page, pageSize int) ([]models.Notification, int64, error)
|
||||
ListSensorData(req *dto.ListSensorDataRequest) (*dto.ListSensorDataResponse, error)
|
||||
ListDeviceCommandLogs(req *dto.ListDeviceCommandLogRequest) (*dto.ListDeviceCommandLogResponse, error)
|
||||
ListPlanExecutionLogs(req *dto.ListPlanExecutionLogRequest) (*dto.ListPlanExecutionLogResponse, error)
|
||||
ListTaskExecutionLogs(req *dto.ListTaskExecutionLogRequest) (*dto.ListTaskExecutionLogResponse, error)
|
||||
ListPendingCollections(req *dto.ListPendingCollectionRequest) (*dto.ListPendingCollectionResponse, error)
|
||||
ListUserActionLogs(req *dto.ListUserActionLogRequest) (*dto.ListUserActionLogResponse, error)
|
||||
ListRawMaterialPurchases(req *dto.ListRawMaterialPurchaseRequest) (*dto.ListRawMaterialPurchaseResponse, error)
|
||||
ListRawMaterialStockLogs(req *dto.ListRawMaterialStockLogRequest) (*dto.ListRawMaterialStockLogResponse, error)
|
||||
ListFeedUsageRecords(req *dto.ListFeedUsageRecordRequest) (*dto.ListFeedUsageRecordResponse, error)
|
||||
ListMedicationLogs(req *dto.ListMedicationLogRequest) (*dto.ListMedicationLogResponse, error)
|
||||
ListPigBatchLogs(req *dto.ListPigBatchLogRequest) (*dto.ListPigBatchLogResponse, error)
|
||||
ListWeighingBatches(req *dto.ListWeighingBatchRequest) (*dto.ListWeighingBatchResponse, error)
|
||||
ListWeighingRecords(req *dto.ListWeighingRecordRequest) (*dto.ListWeighingRecordResponse, error)
|
||||
ListPigTransferLogs(req *dto.ListPigTransferLogRequest) (*dto.ListPigTransferLogResponse, error)
|
||||
ListPigSickLogs(req *dto.ListPigSickLogRequest) (*dto.ListPigSickLogResponse, error)
|
||||
ListPigPurchases(req *dto.ListPigPurchaseRequest) (*dto.ListPigPurchaseResponse, error)
|
||||
ListPigSales(req *dto.ListPigSaleRequest) (*dto.ListPigSaleResponse, error)
|
||||
ListNotifications(req *dto.ListNotificationRequest) (*dto.ListNotificationResponse, error)
|
||||
}
|
||||
|
||||
// monitorService 是 MonitorService 接口的具体实现
|
||||
@@ -81,22 +82,63 @@ func NewMonitorService(
|
||||
}
|
||||
|
||||
// ListSensorData 负责处理查询传感器数据列表的业务逻辑
|
||||
func (s *monitorService) ListSensorData(opts repository.SensorDataListOptions, page, pageSize int) ([]models.SensorData, int64, error) {
|
||||
return s.sensorDataRepo.List(opts, page, pageSize)
|
||||
func (s *monitorService) ListSensorData(req *dto.ListSensorDataRequest) (*dto.ListSensorDataResponse, error) {
|
||||
opts := repository.SensorDataListOptions{
|
||||
DeviceID: req.DeviceID,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
if req.SensorType != nil {
|
||||
sensorType := models.SensorType(*req.SensorType)
|
||||
opts.SensorType = &sensorType
|
||||
}
|
||||
|
||||
data, total, err := s.sensorDataRepo.List(opts, req.Page, req.PageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dto.NewListSensorDataResponse(data, total, req.Page, req.PageSize), nil
|
||||
}
|
||||
|
||||
// ListDeviceCommandLogs 负责处理查询设备命令日志列表的业务逻辑
|
||||
func (s *monitorService) ListDeviceCommandLogs(opts repository.DeviceCommandLogListOptions, page, pageSize int) ([]models.DeviceCommandLog, int64, error) {
|
||||
return s.deviceCommandLogRepo.List(opts, page, pageSize)
|
||||
func (s *monitorService) ListDeviceCommandLogs(req *dto.ListDeviceCommandLogRequest) (*dto.ListDeviceCommandLogResponse, error) {
|
||||
opts := repository.DeviceCommandLogListOptions{
|
||||
DeviceID: req.DeviceID,
|
||||
ReceivedSuccess: req.ReceivedSuccess,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
|
||||
data, total, err := s.deviceCommandLogRepo.List(opts, req.Page, req.PageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dto.NewListDeviceCommandLogResponse(data, total, req.Page, req.PageSize), nil
|
||||
}
|
||||
|
||||
// ListPlanExecutionLogs 负责处理查询计划执行日志列表的业务逻辑
|
||||
func (s *monitorService) ListPlanExecutionLogs(opts repository.PlanExecutionLogListOptions, page, pageSize int) ([]models.PlanExecutionLog, []models.Plan, int64, error) {
|
||||
planLogs, total, err := s.executionLogRepo.ListPlanExecutionLogs(opts, page, pageSize)
|
||||
if err != nil {
|
||||
return nil, nil, 0, err
|
||||
func (s *monitorService) ListPlanExecutionLogs(req *dto.ListPlanExecutionLogRequest) (*dto.ListPlanExecutionLogResponse, error) {
|
||||
opts := repository.PlanExecutionLogListOptions{
|
||||
PlanID: req.PlanID,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
planIds := []uint{}
|
||||
if req.Status != nil {
|
||||
status := models.ExecutionStatus(*req.Status)
|
||||
opts.Status = &status
|
||||
}
|
||||
|
||||
planLogs, total, err := s.executionLogRepo.ListPlanExecutionLogs(opts, req.Page, req.PageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
planIds := make([]uint, 0, len(planLogs))
|
||||
for _, datum := range planLogs {
|
||||
has := false
|
||||
for _, id := range planIds {
|
||||
@@ -111,82 +153,322 @@ func (s *monitorService) ListPlanExecutionLogs(opts repository.PlanExecutionLogL
|
||||
}
|
||||
plans, err := s.planRepository.GetPlansByIDs(planIds)
|
||||
if err != nil {
|
||||
return nil, nil, 0, err
|
||||
return nil, err
|
||||
}
|
||||
return planLogs, plans, total, nil
|
||||
return dto.NewListPlanExecutionLogResponse(planLogs, plans, total, req.Page, req.PageSize), nil
|
||||
}
|
||||
|
||||
// ListTaskExecutionLogs 负责处理查询任务执行日志列表的业务逻辑
|
||||
func (s *monitorService) ListTaskExecutionLogs(opts repository.TaskExecutionLogListOptions, page, pageSize int) ([]models.TaskExecutionLog, int64, error) {
|
||||
return s.executionLogRepo.ListTaskExecutionLogs(opts, page, pageSize)
|
||||
func (s *monitorService) ListTaskExecutionLogs(req *dto.ListTaskExecutionLogRequest) (*dto.ListTaskExecutionLogResponse, error) {
|
||||
opts := repository.TaskExecutionLogListOptions{
|
||||
PlanExecutionLogID: req.PlanExecutionLogID,
|
||||
TaskID: req.TaskID,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
if req.Status != nil {
|
||||
status := models.ExecutionStatus(*req.Status)
|
||||
opts.Status = &status
|
||||
}
|
||||
|
||||
data, total, err := s.executionLogRepo.ListTaskExecutionLogs(opts, req.Page, req.PageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dto.NewListTaskExecutionLogResponse(data, total, req.Page, req.PageSize), nil
|
||||
}
|
||||
|
||||
// ListPendingCollections 负责处理查询待采集请求列表的业务逻辑
|
||||
func (s *monitorService) ListPendingCollections(opts repository.PendingCollectionListOptions, page, pageSize int) ([]models.PendingCollection, int64, error) {
|
||||
return s.pendingCollectionRepo.List(opts, page, pageSize)
|
||||
func (s *monitorService) ListPendingCollections(req *dto.ListPendingCollectionRequest) (*dto.ListPendingCollectionResponse, error) {
|
||||
opts := repository.PendingCollectionListOptions{
|
||||
DeviceID: req.DeviceID,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
if req.Status != nil {
|
||||
status := models.PendingCollectionStatus(*req.Status)
|
||||
opts.Status = &status
|
||||
}
|
||||
|
||||
data, total, err := s.pendingCollectionRepo.List(opts, req.Page, req.PageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dto.NewListPendingCollectionResponse(data, total, req.Page, req.PageSize), nil
|
||||
}
|
||||
|
||||
// ListUserActionLogs 负责处理查询用户操作日志列表的业务逻辑
|
||||
func (s *monitorService) ListUserActionLogs(opts repository.UserActionLogListOptions, page, pageSize int) ([]models.UserActionLog, int64, error) {
|
||||
return s.userActionLogRepo.List(opts, page, pageSize)
|
||||
func (s *monitorService) ListUserActionLogs(req *dto.ListUserActionLogRequest) (*dto.ListUserActionLogResponse, error) {
|
||||
opts := repository.UserActionLogListOptions{
|
||||
UserID: req.UserID,
|
||||
Username: req.Username,
|
||||
ActionType: req.ActionType,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
if req.Status != nil {
|
||||
status := models.AuditStatus(*req.Status)
|
||||
opts.Status = &status
|
||||
}
|
||||
|
||||
data, total, err := s.userActionLogRepo.List(opts, req.Page, req.PageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dto.NewListUserActionLogResponse(data, total, req.Page, req.PageSize), nil
|
||||
}
|
||||
|
||||
// ListRawMaterialPurchases 负责处理查询原料采购记录列表的业务逻辑
|
||||
func (s *monitorService) ListRawMaterialPurchases(opts repository.RawMaterialPurchaseListOptions, page, pageSize int) ([]models.RawMaterialPurchase, int64, error) {
|
||||
return s.rawMaterialRepo.ListRawMaterialPurchases(opts, page, pageSize)
|
||||
func (s *monitorService) ListRawMaterialPurchases(req *dto.ListRawMaterialPurchaseRequest) (*dto.ListRawMaterialPurchaseResponse, error) {
|
||||
opts := repository.RawMaterialPurchaseListOptions{
|
||||
RawMaterialID: req.RawMaterialID,
|
||||
Supplier: req.Supplier,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
|
||||
data, total, err := s.rawMaterialRepo.ListRawMaterialPurchases(opts, req.Page, req.PageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dto.NewListRawMaterialPurchaseResponse(data, total, req.Page, req.PageSize), nil
|
||||
}
|
||||
|
||||
// ListRawMaterialStockLogs 负责处理查询原料库存日志列表的业务逻辑
|
||||
func (s *monitorService) ListRawMaterialStockLogs(opts repository.RawMaterialStockLogListOptions, page, pageSize int) ([]models.RawMaterialStockLog, int64, error) {
|
||||
return s.rawMaterialRepo.ListRawMaterialStockLogs(opts, page, pageSize)
|
||||
func (s *monitorService) ListRawMaterialStockLogs(req *dto.ListRawMaterialStockLogRequest) (*dto.ListRawMaterialStockLogResponse, error) {
|
||||
opts := repository.RawMaterialStockLogListOptions{
|
||||
RawMaterialID: req.RawMaterialID,
|
||||
SourceID: req.SourceID,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
if req.SourceType != nil {
|
||||
sourceType := models.StockLogSourceType(*req.SourceType)
|
||||
opts.SourceType = &sourceType
|
||||
}
|
||||
|
||||
data, total, err := s.rawMaterialRepo.ListRawMaterialStockLogs(opts, req.Page, req.PageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dto.NewListRawMaterialStockLogResponse(data, total, req.Page, req.PageSize), nil
|
||||
}
|
||||
|
||||
// ListFeedUsageRecords 负责处理查询饲料使用记录列表的业务逻辑
|
||||
func (s *monitorService) ListFeedUsageRecords(opts repository.FeedUsageRecordListOptions, page, pageSize int) ([]models.FeedUsageRecord, int64, error) {
|
||||
return s.rawMaterialRepo.ListFeedUsageRecords(opts, page, pageSize)
|
||||
func (s *monitorService) ListFeedUsageRecords(req *dto.ListFeedUsageRecordRequest) (*dto.ListFeedUsageRecordResponse, error) {
|
||||
opts := repository.FeedUsageRecordListOptions{
|
||||
PenID: req.PenID,
|
||||
FeedFormulaID: req.FeedFormulaID,
|
||||
OperatorID: req.OperatorID,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
|
||||
data, total, err := s.rawMaterialRepo.ListFeedUsageRecords(opts, req.Page, req.PageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dto.NewListFeedUsageRecordResponse(data, total, req.Page, req.PageSize), nil
|
||||
}
|
||||
|
||||
// ListMedicationLogs 负责处理查询用药记录列表的业务逻辑
|
||||
func (s *monitorService) ListMedicationLogs(opts repository.MedicationLogListOptions, page, pageSize int) ([]models.MedicationLog, int64, error) {
|
||||
return s.medicationRepo.ListMedicationLogs(opts, page, pageSize)
|
||||
func (s *monitorService) ListMedicationLogs(req *dto.ListMedicationLogRequest) (*dto.ListMedicationLogResponse, error) {
|
||||
opts := repository.MedicationLogListOptions{
|
||||
PigBatchID: req.PigBatchID,
|
||||
MedicationID: req.MedicationID,
|
||||
OperatorID: req.OperatorID,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
if req.Reason != nil {
|
||||
reason := models.MedicationReasonType(*req.Reason)
|
||||
opts.Reason = &reason
|
||||
}
|
||||
|
||||
data, total, err := s.medicationRepo.ListMedicationLogs(opts, req.Page, req.PageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dto.NewListMedicationLogResponse(data, total, req.Page, req.PageSize), nil
|
||||
}
|
||||
|
||||
// ListPigBatchLogs 负责处理查询猪批次日志列表的业务逻辑
|
||||
func (s *monitorService) ListPigBatchLogs(opts repository.PigBatchLogListOptions, page, pageSize int) ([]models.PigBatchLog, int64, error) {
|
||||
return s.pigBatchLogRepo.List(opts, page, pageSize)
|
||||
func (s *monitorService) ListPigBatchLogs(req *dto.ListPigBatchLogRequest) (*dto.ListPigBatchLogResponse, error) {
|
||||
opts := repository.PigBatchLogListOptions{
|
||||
PigBatchID: req.PigBatchID,
|
||||
OperatorID: req.OperatorID,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
if req.ChangeType != nil {
|
||||
changeType := models.LogChangeType(*req.ChangeType)
|
||||
opts.ChangeType = &changeType
|
||||
}
|
||||
|
||||
data, total, err := s.pigBatchLogRepo.List(opts, req.Page, req.PageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dto.NewListPigBatchLogResponse(data, total, req.Page, req.PageSize), nil
|
||||
}
|
||||
|
||||
// ListWeighingBatches 负责处理查询批次称重记录列表的业务逻辑
|
||||
func (s *monitorService) ListWeighingBatches(opts repository.WeighingBatchListOptions, page, pageSize int) ([]models.WeighingBatch, int64, error) {
|
||||
return s.pigBatchRepo.ListWeighingBatches(opts, page, pageSize)
|
||||
func (s *monitorService) ListWeighingBatches(req *dto.ListWeighingBatchRequest) (*dto.ListWeighingBatchResponse, error) {
|
||||
opts := repository.WeighingBatchListOptions{
|
||||
PigBatchID: req.PigBatchID,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
|
||||
data, total, err := s.pigBatchRepo.ListWeighingBatches(opts, req.Page, req.PageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dto.NewListWeighingBatchResponse(data, total, req.Page, req.PageSize), nil
|
||||
}
|
||||
|
||||
// ListWeighingRecords 负责处理查询单次称重记录列表的业务逻辑
|
||||
func (s *monitorService) ListWeighingRecords(opts repository.WeighingRecordListOptions, page, pageSize int) ([]models.WeighingRecord, int64, error) {
|
||||
return s.pigBatchRepo.ListWeighingRecords(opts, page, pageSize)
|
||||
func (s *monitorService) ListWeighingRecords(req *dto.ListWeighingRecordRequest) (*dto.ListWeighingRecordResponse, error) {
|
||||
opts := repository.WeighingRecordListOptions{
|
||||
WeighingBatchID: req.WeighingBatchID,
|
||||
PenID: req.PenID,
|
||||
OperatorID: req.OperatorID,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
|
||||
data, total, err := s.pigBatchRepo.ListWeighingRecords(opts, req.Page, req.PageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dto.NewListWeighingRecordResponse(data, total, req.Page, req.PageSize), nil
|
||||
}
|
||||
|
||||
// ListPigTransferLogs 负责处理查询猪只迁移日志列表的业务逻辑
|
||||
func (s *monitorService) ListPigTransferLogs(opts repository.PigTransferLogListOptions, page, pageSize int) ([]models.PigTransferLog, int64, error) {
|
||||
return s.pigTransferLogRepo.ListPigTransferLogs(opts, page, pageSize)
|
||||
func (s *monitorService) ListPigTransferLogs(req *dto.ListPigTransferLogRequest) (*dto.ListPigTransferLogResponse, error) {
|
||||
opts := repository.PigTransferLogListOptions{
|
||||
PigBatchID: req.PigBatchID,
|
||||
PenID: req.PenID,
|
||||
OperatorID: req.OperatorID,
|
||||
CorrelationID: req.CorrelationID,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
if req.TransferType != nil {
|
||||
transferType := models.PigTransferType(*req.TransferType)
|
||||
opts.TransferType = &transferType
|
||||
}
|
||||
|
||||
data, total, err := s.pigTransferLogRepo.ListPigTransferLogs(opts, req.Page, req.PageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dto.NewListPigTransferLogResponse(data, total, req.Page, req.PageSize), nil
|
||||
}
|
||||
|
||||
// ListPigSickLogs 负责处理查询病猪日志列表的业务逻辑
|
||||
func (s *monitorService) ListPigSickLogs(opts repository.PigSickLogListOptions, page, pageSize int) ([]models.PigSickLog, int64, error) {
|
||||
return s.pigSickLogRepo.ListPigSickLogs(opts, page, pageSize)
|
||||
func (s *monitorService) ListPigSickLogs(req *dto.ListPigSickLogRequest) (*dto.ListPigSickLogResponse, error) {
|
||||
opts := repository.PigSickLogListOptions{
|
||||
PigBatchID: req.PigBatchID,
|
||||
PenID: req.PenID,
|
||||
OperatorID: req.OperatorID,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
if req.Reason != nil {
|
||||
reason := models.PigBatchSickPigReasonType(*req.Reason)
|
||||
opts.Reason = &reason
|
||||
}
|
||||
if req.TreatmentLocation != nil {
|
||||
treatmentLocation := models.PigBatchSickPigTreatmentLocation(*req.TreatmentLocation)
|
||||
opts.TreatmentLocation = &treatmentLocation
|
||||
}
|
||||
|
||||
data, total, err := s.pigSickLogRepo.ListPigSickLogs(opts, req.Page, req.PageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dto.NewListPigSickLogResponse(data, total, req.Page, req.PageSize), nil
|
||||
}
|
||||
|
||||
// ListPigPurchases 负责处理查询猪只采购记录列表的业务逻辑
|
||||
func (s *monitorService) ListPigPurchases(opts repository.PigPurchaseListOptions, page, pageSize int) ([]models.PigPurchase, int64, error) {
|
||||
return s.pigTradeRepo.ListPigPurchases(opts, page, pageSize)
|
||||
func (s *monitorService) ListPigPurchases(req *dto.ListPigPurchaseRequest) (*dto.ListPigPurchaseResponse, error) {
|
||||
opts := repository.PigPurchaseListOptions{
|
||||
PigBatchID: req.PigBatchID,
|
||||
Supplier: req.Supplier,
|
||||
OperatorID: req.OperatorID,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
|
||||
data, total, err := s.pigTradeRepo.ListPigPurchases(opts, req.Page, req.PageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dto.NewListPigPurchaseResponse(data, total, req.Page, req.PageSize), nil
|
||||
}
|
||||
|
||||
// ListPigSales 负责处理查询猪只销售记录列表的业务逻辑
|
||||
func (s *monitorService) ListPigSales(opts repository.PigSaleListOptions, page, pageSize int) ([]models.PigSale, int64, error) {
|
||||
return s.pigTradeRepo.ListPigSales(opts, page, pageSize)
|
||||
func (s *monitorService) ListPigSales(req *dto.ListPigSaleRequest) (*dto.ListPigSaleResponse, error) {
|
||||
opts := repository.PigSaleListOptions{
|
||||
PigBatchID: req.PigBatchID,
|
||||
Buyer: req.Buyer,
|
||||
OperatorID: req.OperatorID,
|
||||
OrderBy: req.OrderBy,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
}
|
||||
|
||||
data, total, err := s.pigTradeRepo.ListPigSales(opts, req.Page, req.PageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dto.NewListPigSaleResponse(data, total, req.Page, req.PageSize), nil
|
||||
}
|
||||
|
||||
// ListNotifications 负责处理查询通知列表的业务逻辑
|
||||
func (s *monitorService) ListNotifications(opts repository.NotificationListOptions, page, pageSize int) ([]models.Notification, int64, error) {
|
||||
return s.notificationRepo.List(opts, page, pageSize)
|
||||
func (s *monitorService) ListNotifications(req *dto.ListNotificationRequest) (*dto.ListNotificationResponse, error) {
|
||||
opts := repository.NotificationListOptions{
|
||||
UserID: req.UserID,
|
||||
NotifierType: req.NotifierType,
|
||||
Level: req.Level,
|
||||
StartTime: req.StartTime,
|
||||
EndTime: req.EndTime,
|
||||
OrderBy: req.OrderBy,
|
||||
Status: req.Status,
|
||||
}
|
||||
|
||||
data, total, err := s.notificationRepo.List(opts, req.Page, req.PageSize)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dto.NewListNotificationResponse(data, total, req.Page, req.PageSize), nil
|
||||
}
|
||||
|
||||
@@ -16,20 +16,20 @@ import (
|
||||
// PigFarmService 提供了猪场资产管理的业务逻辑
|
||||
type PigFarmService interface {
|
||||
// PigHouse methods
|
||||
CreatePigHouse(name, description string) (*models.PigHouse, error)
|
||||
GetPigHouseByID(id uint) (*models.PigHouse, error)
|
||||
ListPigHouses() ([]models.PigHouse, error)
|
||||
UpdatePigHouse(id uint, name, description string) (*models.PigHouse, error)
|
||||
CreatePigHouse(name, description string) (*dto.PigHouseResponse, error)
|
||||
GetPigHouseByID(id uint) (*dto.PigHouseResponse, error)
|
||||
ListPigHouses() ([]dto.PigHouseResponse, error)
|
||||
UpdatePigHouse(id uint, name, description string) (*dto.PigHouseResponse, error)
|
||||
DeletePigHouse(id uint) error
|
||||
|
||||
// Pen methods
|
||||
CreatePen(penNumber string, houseID uint, capacity int) (*models.Pen, error)
|
||||
CreatePen(penNumber string, houseID uint, capacity int) (*dto.PenResponse, error)
|
||||
GetPenByID(id uint) (*dto.PenResponse, error)
|
||||
ListPens() ([]*dto.PenResponse, error)
|
||||
UpdatePen(id uint, penNumber string, houseID uint, capacity int, status models.PenStatus) (*models.Pen, error)
|
||||
UpdatePen(id uint, penNumber string, houseID uint, capacity int, status models.PenStatus) (*dto.PenResponse, error)
|
||||
DeletePen(id uint) error
|
||||
// UpdatePenStatus 更新猪栏状态
|
||||
UpdatePenStatus(id uint, newStatus models.PenStatus) (*models.Pen, error)
|
||||
UpdatePenStatus(id uint, newStatus models.PenStatus) (*dto.PenResponse, error)
|
||||
}
|
||||
|
||||
type pigFarmService struct {
|
||||
@@ -60,24 +60,51 @@ func NewPigFarmService(farmRepository repository.PigFarmRepository,
|
||||
|
||||
// --- PigHouse Implementation ---
|
||||
|
||||
func (s *pigFarmService) CreatePigHouse(name, description string) (*models.PigHouse, error) {
|
||||
func (s *pigFarmService) CreatePigHouse(name, description string) (*dto.PigHouseResponse, error) {
|
||||
house := &models.PigHouse{
|
||||
Name: name,
|
||||
Description: description,
|
||||
}
|
||||
err := s.farmRepository.CreatePigHouse(house)
|
||||
return house, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &dto.PigHouseResponse{
|
||||
ID: house.ID,
|
||||
Name: house.Name,
|
||||
Description: house.Description,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *pigFarmService) GetPigHouseByID(id uint) (*models.PigHouse, error) {
|
||||
return s.farmRepository.GetPigHouseByID(id)
|
||||
func (s *pigFarmService) GetPigHouseByID(id uint) (*dto.PigHouseResponse, error) {
|
||||
house, err := s.farmRepository.GetPigHouseByID(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &dto.PigHouseResponse{
|
||||
ID: house.ID,
|
||||
Name: house.Name,
|
||||
Description: house.Description,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *pigFarmService) ListPigHouses() ([]models.PigHouse, error) {
|
||||
return s.farmRepository.ListPigHouses()
|
||||
func (s *pigFarmService) ListPigHouses() ([]dto.PigHouseResponse, error) {
|
||||
houses, err := s.farmRepository.ListPigHouses()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var resp []dto.PigHouseResponse
|
||||
for _, house := range houses {
|
||||
resp = append(resp, dto.PigHouseResponse{
|
||||
ID: house.ID,
|
||||
Name: house.Name,
|
||||
Description: house.Description,
|
||||
})
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (s *pigFarmService) UpdatePigHouse(id uint, name, description string) (*models.PigHouse, error) {
|
||||
func (s *pigFarmService) UpdatePigHouse(id uint, name, description string) (*dto.PigHouseResponse, error) {
|
||||
house := &models.PigHouse{
|
||||
Model: gorm.Model{ID: id},
|
||||
Name: name,
|
||||
@@ -91,7 +118,15 @@ func (s *pigFarmService) UpdatePigHouse(id uint, name, description string) (*mod
|
||||
return nil, ErrHouseNotFound
|
||||
}
|
||||
// 返回更新后的完整信息
|
||||
return s.farmRepository.GetPigHouseByID(id)
|
||||
updatedHouse, err := s.farmRepository.GetPigHouseByID(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &dto.PigHouseResponse{
|
||||
ID: updatedHouse.ID,
|
||||
Name: updatedHouse.Name,
|
||||
Description: updatedHouse.Description,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *pigFarmService) DeletePigHouse(id uint) error {
|
||||
@@ -117,7 +152,7 @@ func (s *pigFarmService) DeletePigHouse(id uint) error {
|
||||
|
||||
// --- Pen Implementation ---
|
||||
|
||||
func (s *pigFarmService) CreatePen(penNumber string, houseID uint, capacity int) (*models.Pen, error) {
|
||||
func (s *pigFarmService) CreatePen(penNumber string, houseID uint, capacity int) (*dto.PenResponse, error) {
|
||||
// 业务逻辑:验证所属猪舍是否存在
|
||||
_, err := s.farmRepository.GetPigHouseByID(houseID)
|
||||
if err != nil {
|
||||
@@ -134,7 +169,16 @@ func (s *pigFarmService) CreatePen(penNumber string, houseID uint, capacity int)
|
||||
Status: models.PenStatusEmpty,
|
||||
}
|
||||
err = s.penRepository.CreatePen(pen)
|
||||
return pen, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &dto.PenResponse{
|
||||
ID: pen.ID,
|
||||
PenNumber: pen.PenNumber,
|
||||
HouseID: pen.HouseID,
|
||||
Capacity: pen.Capacity,
|
||||
Status: pen.Status,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *pigFarmService) GetPenByID(id uint) (*dto.PenResponse, error) {
|
||||
@@ -197,7 +241,7 @@ func (s *pigFarmService) ListPens() ([]*dto.PenResponse, error) {
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func (s *pigFarmService) UpdatePen(id uint, penNumber string, houseID uint, capacity int, status models.PenStatus) (*models.Pen, error) {
|
||||
func (s *pigFarmService) UpdatePen(id uint, penNumber string, houseID uint, capacity int, status models.PenStatus) (*dto.PenResponse, error) {
|
||||
// 业务逻辑:验证所属猪舍是否存在
|
||||
_, err := s.farmRepository.GetPigHouseByID(houseID)
|
||||
if err != nil {
|
||||
@@ -222,7 +266,18 @@ func (s *pigFarmService) UpdatePen(id uint, penNumber string, houseID uint, capa
|
||||
return nil, ErrPenNotFound
|
||||
}
|
||||
// 返回更新后的完整信息
|
||||
return s.penRepository.GetPenByID(id)
|
||||
updatedPen, err := s.penRepository.GetPenByID(id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &dto.PenResponse{
|
||||
ID: updatedPen.ID,
|
||||
PenNumber: updatedPen.PenNumber,
|
||||
HouseID: updatedPen.HouseID,
|
||||
Capacity: updatedPen.Capacity,
|
||||
Status: updatedPen.Status,
|
||||
PigBatchID: updatedPen.PigBatchID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *pigFarmService) DeletePen(id uint) error {
|
||||
@@ -260,7 +315,7 @@ func (s *pigFarmService) DeletePen(id uint) error {
|
||||
}
|
||||
|
||||
// UpdatePenStatus 更新猪栏状态
|
||||
func (s *pigFarmService) UpdatePenStatus(id uint, newStatus models.PenStatus) (*models.Pen, error) {
|
||||
func (s *pigFarmService) UpdatePenStatus(id uint, newStatus models.PenStatus) (*dto.PenResponse, error) {
|
||||
var updatedPen *models.Pen
|
||||
err := s.uow.ExecuteInTransaction(func(tx *gorm.DB) error {
|
||||
pen, err := s.penRepository.GetPenByIDTx(tx, id)
|
||||
@@ -310,5 +365,12 @@ func (s *pigFarmService) UpdatePenStatus(id uint, newStatus models.PenStatus) (*
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return updatedPen, nil
|
||||
return &dto.PenResponse{
|
||||
ID: updatedPen.ID,
|
||||
PenNumber: updatedPen.PenNumber,
|
||||
HouseID: updatedPen.HouseID,
|
||||
Capacity: updatedPen.Capacity,
|
||||
Status: updatedPen.Status,
|
||||
PigBatchID: updatedPen.PigBatchID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
344
internal/app/service/plan_service.go
Normal file
344
internal/app/service/plan_service.go
Normal file
@@ -0,0 +1,344 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/dto"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/scheduler"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrPlanNotFound 表示未找到计划
|
||||
ErrPlanNotFound = errors.New("计划不存在")
|
||||
// ErrPlanCannotBeModified 表示计划不允许修改
|
||||
ErrPlanCannotBeModified = errors.New("系统计划不允许修改")
|
||||
// ErrPlanCannotBeDeleted 表示计划不允许删除
|
||||
ErrPlanCannotBeDeleted = errors.New("系统计划不允许删除")
|
||||
// ErrPlanCannotBeStarted 表示计划不允许手动启动
|
||||
ErrPlanCannotBeStarted = errors.New("系统计划不允许手动启动")
|
||||
// ErrPlanAlreadyEnabled 表示计划已处于启动状态
|
||||
ErrPlanAlreadyEnabled = errors.New("计划已处于启动状态,无需重复操作")
|
||||
// ErrPlanNotEnabled 表示计划未处于启动状态
|
||||
ErrPlanNotEnabled = errors.New("计划当前不是启用状态")
|
||||
// ErrPlanCannotBeStopped 表示计划不允许停止
|
||||
ErrPlanCannotBeStopped = errors.New("系统计划不允许停止")
|
||||
)
|
||||
|
||||
// PlanService 定义了计划相关的应用服务接口
|
||||
type PlanService interface {
|
||||
// CreatePlan 创建一个新的计划
|
||||
CreatePlan(req *dto.CreatePlanRequest) (*dto.PlanResponse, error)
|
||||
// GetPlanByID 根据ID获取计划详情
|
||||
GetPlanByID(id uint) (*dto.PlanResponse, error)
|
||||
// ListPlans 获取计划列表,支持过滤和分页
|
||||
ListPlans(query *dto.ListPlansQuery) (*dto.ListPlansResponse, error)
|
||||
// UpdatePlan 更新计划
|
||||
UpdatePlan(id uint, req *dto.UpdatePlanRequest) (*dto.PlanResponse, error)
|
||||
// DeletePlan 删除计划(软删除)
|
||||
DeletePlan(id uint) error
|
||||
// StartPlan 启动计划
|
||||
StartPlan(id uint) error
|
||||
// StopPlan 停止计划
|
||||
StopPlan(id uint) error
|
||||
}
|
||||
|
||||
// planService 是 PlanService 接口的实现
|
||||
type planService struct {
|
||||
logger *logs.Logger
|
||||
planRepo repository.PlanRepository
|
||||
analysisPlanTaskManager *scheduler.AnalysisPlanTaskManager
|
||||
}
|
||||
|
||||
// NewPlanService 创建一个新的 PlanService 实例
|
||||
func NewPlanService(
|
||||
logger *logs.Logger,
|
||||
planRepo repository.PlanRepository,
|
||||
analysisPlanTaskManager *scheduler.AnalysisPlanTaskManager,
|
||||
) PlanService {
|
||||
return &planService{
|
||||
logger: logger,
|
||||
planRepo: planRepo,
|
||||
analysisPlanTaskManager: analysisPlanTaskManager,
|
||||
}
|
||||
}
|
||||
|
||||
// CreatePlan 创建一个新的计划
|
||||
func (s *planService) CreatePlan(req *dto.CreatePlanRequest) (*dto.PlanResponse, error) {
|
||||
const actionType = "服务层:创建计划"
|
||||
|
||||
// 使用已有的转换函数,它已经包含了验证和重排逻辑
|
||||
planToCreate, err := dto.NewPlanFromCreateRequest(req)
|
||||
if err != nil {
|
||||
s.logger.Errorf("%s: 计划数据校验失败: %v", actionType, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// --- 业务规则处理 ---
|
||||
// 1. 设置计划类型:用户创建的计划永远是自定义计划
|
||||
planToCreate.PlanType = models.PlanTypeCustom
|
||||
|
||||
// 2. 自动判断 ContentType
|
||||
if len(req.SubPlanIDs) > 0 {
|
||||
planToCreate.ContentType = models.PlanContentTypeSubPlans
|
||||
} else {
|
||||
// 如果 SubPlanIDs 未提供,则默认为 Tasks 类型(即使 Tasks 字段也未提供)
|
||||
planToCreate.ContentType = models.PlanContentTypeTasks
|
||||
}
|
||||
|
||||
// 调用仓库方法创建计划
|
||||
if err := s.planRepo.CreatePlan(planToCreate); err != nil {
|
||||
s.logger.Errorf("%s: 数据库创建计划失败: %v", actionType, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 创建成功后,调用 manager 确保触发器任务定义存在,但不立即加入待执行队列
|
||||
if err := s.analysisPlanTaskManager.EnsureAnalysisTaskDefinition(planToCreate.ID); err != nil {
|
||||
// 这是一个非阻塞性错误,我们只记录日志,因为主流程(创建计划)已经成功
|
||||
s.logger.Errorf("为新创建的计划 %d 确保触发器任务定义失败: %v", planToCreate.ID, err)
|
||||
}
|
||||
|
||||
// 使用已有的转换函数将创建后的模型转换为响应对象
|
||||
resp, err := dto.NewPlanToResponse(planToCreate)
|
||||
if err != nil {
|
||||
s.logger.Errorf("%s: 序列化响应失败: %v, Plan: %+v", actionType, err, planToCreate)
|
||||
return nil, errors.New("计划创建成功,但响应生成失败")
|
||||
}
|
||||
|
||||
s.logger.Infof("%s: 计划创建成功, ID: %d", actionType, planToCreate.ID)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// GetPlanByID 根据ID获取计划详情
|
||||
func (s *planService) GetPlanByID(id uint) (*dto.PlanResponse, error) {
|
||||
const actionType = "服务层:获取计划详情"
|
||||
|
||||
plan, err := s.planRepo.GetPlanByID(id)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
s.logger.Warnf("%s: 计划不存在, ID: %d", actionType, id)
|
||||
return nil, ErrPlanNotFound
|
||||
}
|
||||
s.logger.Errorf("%s: 数据库查询失败: %v, ID: %d", actionType, err, id)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := dto.NewPlanToResponse(plan)
|
||||
if err != nil {
|
||||
s.logger.Errorf("%s: 序列化响应失败: %v, Plan: %+v", actionType, err, plan)
|
||||
return nil, errors.New("获取计划详情失败: 内部数据格式错误")
|
||||
}
|
||||
|
||||
s.logger.Infof("%s: 获取计划详情成功, ID: %d", actionType, id)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// ListPlans 获取计划列表,支持过滤和分页
|
||||
func (s *planService) ListPlans(query *dto.ListPlansQuery) (*dto.ListPlansResponse, error) {
|
||||
const actionType = "服务层:获取计划列表"
|
||||
|
||||
opts := repository.ListPlansOptions{PlanType: query.PlanType}
|
||||
plans, total, err := s.planRepo.ListPlans(opts, query.Page, query.PageSize)
|
||||
if err != nil {
|
||||
s.logger.Errorf("%s: 数据库查询失败: %v", actionType, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
planResponses := make([]dto.PlanResponse, 0, len(plans))
|
||||
for _, p := range plans {
|
||||
resp, err := dto.NewPlanToResponse(&p)
|
||||
if err != nil {
|
||||
s.logger.Errorf("%s: 序列化单个计划响应失败: %v, Plan: %+v", actionType, err, p)
|
||||
// 这里选择跳过有问题的计划,并记录错误,而不是中断整个列表的返回
|
||||
continue
|
||||
}
|
||||
planResponses = append(planResponses, *resp)
|
||||
}
|
||||
|
||||
resp := &dto.ListPlansResponse{
|
||||
Plans: planResponses,
|
||||
Total: total,
|
||||
}
|
||||
s.logger.Infof("%s: 获取计划列表成功, 数量: %d", actionType, len(planResponses))
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// UpdatePlan 更新计划
|
||||
func (s *planService) UpdatePlan(id uint, req *dto.UpdatePlanRequest) (*dto.PlanResponse, error) {
|
||||
const actionType = "服务层:更新计划"
|
||||
|
||||
existingPlan, err := s.planRepo.GetBasicPlanByID(id)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
s.logger.Warnf("%s: 计划不存在, ID: %d", actionType, id)
|
||||
return nil, ErrPlanNotFound
|
||||
}
|
||||
s.logger.Errorf("%s: 获取计划信息失败: %v, ID: %d", actionType, err, id)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if existingPlan.PlanType == models.PlanTypeSystem {
|
||||
s.logger.Warnf("%s: 尝试修改系统计划, ID: %d", actionType, id)
|
||||
return nil, ErrPlanCannotBeModified
|
||||
}
|
||||
|
||||
planToUpdate, err := dto.NewPlanFromUpdateRequest(req)
|
||||
if err != nil {
|
||||
s.logger.Errorf("%s: 计划数据校验失败: %v", actionType, err)
|
||||
return nil, err
|
||||
}
|
||||
planToUpdate.ID = id // 确保ID被设置
|
||||
|
||||
if len(req.SubPlanIDs) > 0 {
|
||||
planToUpdate.ContentType = models.PlanContentTypeSubPlans
|
||||
} else {
|
||||
planToUpdate.ContentType = models.PlanContentTypeTasks
|
||||
}
|
||||
|
||||
// 只要是更新任务,就重置执行计数器
|
||||
planToUpdate.ExecuteCount = 0
|
||||
s.logger.Infof("计划 #%d 被更新,执行计数器已重置为 0。", planToUpdate.ID)
|
||||
|
||||
if err := s.planRepo.UpdatePlan(planToUpdate); err != nil {
|
||||
s.logger.Errorf("%s: 数据库更新计划失败: %v, Plan: %+v", actionType, err, planToUpdate)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := s.analysisPlanTaskManager.EnsureAnalysisTaskDefinition(planToUpdate.ID); err != nil {
|
||||
s.logger.Errorf("为更新后的计划 %d 确保触发器任务定义失败: %v", planToUpdate.ID, err)
|
||||
}
|
||||
|
||||
updatedPlan, err := s.planRepo.GetPlanByID(id)
|
||||
if err != nil {
|
||||
s.logger.Errorf("%s: 获取更新后计划详情失败: %v, ID: %d", actionType, err, id)
|
||||
return nil, errors.New("获取更新后计划详情时发生内部错误")
|
||||
}
|
||||
|
||||
resp, err := dto.NewPlanToResponse(updatedPlan)
|
||||
if err != nil {
|
||||
s.logger.Errorf("%s: 序列化响应失败: %v, Updated Plan: %+v", actionType, err, updatedPlan)
|
||||
return nil, errors.New("计划更新成功,但响应生成失败")
|
||||
}
|
||||
|
||||
s.logger.Infof("%s: 计划更新成功, ID: %d", actionType, updatedPlan.ID)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// DeletePlan 删除计划(软删除)
|
||||
func (s *planService) DeletePlan(id uint) error {
|
||||
const actionType = "服务层:删除计划"
|
||||
|
||||
plan, err := s.planRepo.GetBasicPlanByID(id)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
s.logger.Warnf("%s: 计划不存在, ID: %d", actionType, id)
|
||||
return ErrPlanNotFound
|
||||
}
|
||||
s.logger.Errorf("%s: 获取计划信息失败: %v, ID: %d", actionType, err, id)
|
||||
return err
|
||||
}
|
||||
|
||||
if plan.PlanType == models.PlanTypeSystem {
|
||||
s.logger.Warnf("%s: 尝试删除系统计划, ID: %d", actionType, id)
|
||||
return ErrPlanCannotBeDeleted
|
||||
}
|
||||
|
||||
if plan.Status == models.PlanStatusEnabled {
|
||||
if err := s.planRepo.StopPlanTransactionally(id); err != nil {
|
||||
s.logger.Errorf("%s: 停止计划失败: %v, ID: %d", actionType, err, id)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.planRepo.DeletePlan(id); err != nil {
|
||||
s.logger.Errorf("%s: 数据库删除失败: %v, ID: %d", actionType, err, id)
|
||||
return err
|
||||
}
|
||||
|
||||
s.logger.Infof("%s: 计划删除成功, ID: %d", actionType, id)
|
||||
return nil
|
||||
}
|
||||
|
||||
// StartPlan 启动计划
|
||||
func (s *planService) StartPlan(id uint) error {
|
||||
const actionType = "服务层:启动计划"
|
||||
|
||||
plan, err := s.planRepo.GetBasicPlanByID(id)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
s.logger.Warnf("%s: 计划不存在, ID: %d", actionType, id)
|
||||
return ErrPlanNotFound
|
||||
}
|
||||
s.logger.Errorf("%s: 获取计划信息失败: %v, ID: %d", actionType, err, id)
|
||||
return err
|
||||
}
|
||||
|
||||
if plan.PlanType == models.PlanTypeSystem {
|
||||
s.logger.Warnf("%s: 尝试手动启动系统计划, ID: %d", actionType, id)
|
||||
return ErrPlanCannotBeStarted
|
||||
}
|
||||
if plan.Status == models.PlanStatusEnabled {
|
||||
s.logger.Warnf("%s: 计划已处于启动状态,无需重复操作, ID: %d", actionType, id)
|
||||
return ErrPlanAlreadyEnabled
|
||||
}
|
||||
|
||||
if plan.Status != models.PlanStatusEnabled {
|
||||
if plan.ExecuteCount > 0 {
|
||||
if err := s.planRepo.UpdateExecuteCount(plan.ID, 0); err != nil {
|
||||
s.logger.Errorf("%s: 重置计划执行计数失败: %v, ID: %d", actionType, err, plan.ID)
|
||||
return err
|
||||
}
|
||||
s.logger.Infof("计划 #%d 的执行计数器已重置为 0。", plan.ID)
|
||||
}
|
||||
|
||||
if err := s.planRepo.UpdatePlanStatus(plan.ID, models.PlanStatusEnabled); err != nil {
|
||||
s.logger.Errorf("%s: 更新计划状态失败: %v, ID: %d", actionType, err, plan.ID)
|
||||
return err
|
||||
}
|
||||
s.logger.Infof("已成功更新计划 #%d 的状态为 '已启动'。", plan.ID)
|
||||
}
|
||||
|
||||
if err := s.analysisPlanTaskManager.CreateOrUpdateTrigger(plan.ID); err != nil {
|
||||
s.logger.Errorf("%s: 创建或更新触发器失败: %v, ID: %d", actionType, err, plan.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
s.logger.Infof("%s: 计划已成功启动, ID: %d", actionType, id)
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopPlan 停止计划
|
||||
func (s *planService) StopPlan(id uint) error {
|
||||
const actionType = "服务层:停止计划"
|
||||
|
||||
plan, err := s.planRepo.GetBasicPlanByID(id)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
s.logger.Warnf("%s: 计划不存在, ID: %d", actionType, id)
|
||||
return ErrPlanNotFound
|
||||
}
|
||||
s.logger.Errorf("%s: 获取计划信息失败: %v, ID: %d", actionType, err, id)
|
||||
return err
|
||||
}
|
||||
|
||||
if plan.PlanType == models.PlanTypeSystem {
|
||||
s.logger.Warnf("%s: 尝试停止系统计划, ID: %d", actionType, id)
|
||||
return ErrPlanCannotBeStopped
|
||||
}
|
||||
|
||||
if plan.Status != models.PlanStatusEnabled {
|
||||
s.logger.Warnf("%s: 计划当前不是启用状态, ID: %d, Status: %s", actionType, id, plan.Status)
|
||||
return ErrPlanNotEnabled
|
||||
}
|
||||
|
||||
if err := s.planRepo.StopPlanTransactionally(id); err != nil {
|
||||
s.logger.Errorf("%s: 停止计划失败: %v, ID: %d", actionType, err, id)
|
||||
return err
|
||||
}
|
||||
|
||||
s.logger.Infof("%s: 计划已成功停止, ID: %d", actionType, id)
|
||||
return nil
|
||||
}
|
||||
110
internal/app/service/user_service.go
Normal file
110
internal/app/service/user_service.go
Normal file
@@ -0,0 +1,110 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/dto"
|
||||
domain_notify "git.huangwc.com/pig/pig-farm-controller/internal/domain/notify"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/domain/token"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// UserService 定义用户服务接口
|
||||
type UserService interface {
|
||||
CreateUser(req *dto.CreateUserRequest) (*dto.CreateUserResponse, error)
|
||||
Login(req *dto.LoginRequest) (*dto.LoginResponse, error)
|
||||
SendTestNotification(userID uint, req *dto.SendTestNotificationRequest) error
|
||||
}
|
||||
|
||||
// userService 实现了 UserService 接口
|
||||
type userService struct {
|
||||
userRepo repository.UserRepository
|
||||
tokenService token.Service
|
||||
notifyService domain_notify.Service
|
||||
logger *logs.Logger
|
||||
}
|
||||
|
||||
// NewUserService 创建并返回一个新的 UserService 实例
|
||||
func NewUserService(
|
||||
userRepo repository.UserRepository,
|
||||
tokenService token.Service,
|
||||
notifyService domain_notify.Service,
|
||||
logger *logs.Logger,
|
||||
) UserService {
|
||||
return &userService{
|
||||
userRepo: userRepo,
|
||||
tokenService: tokenService,
|
||||
notifyService: notifyService,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateUser 创建新用户
|
||||
func (s *userService) CreateUser(req *dto.CreateUserRequest) (*dto.CreateUserResponse, error) {
|
||||
user := &models.User{
|
||||
Username: req.Username,
|
||||
Password: req.Password, // 密码会在 BeforeSave 钩子中哈希
|
||||
}
|
||||
|
||||
if err := s.userRepo.Create(user); err != nil {
|
||||
s.logger.Errorf("创建用户: 创建用户失败: %v", err)
|
||||
|
||||
// 尝试查询用户,以判断是否是用户名重复导致的错误
|
||||
_, findErr := s.userRepo.FindByUsername(req.Username)
|
||||
if findErr == nil { // 如果能找到用户,说明是用户名重复
|
||||
return nil, errors.New("用户名已存在")
|
||||
}
|
||||
|
||||
// 其他创建失败的情况
|
||||
return nil, errors.New("创建用户失败")
|
||||
}
|
||||
|
||||
return &dto.CreateUserResponse{
|
||||
Username: user.Username,
|
||||
ID: user.ID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Login 用户登录
|
||||
func (s *userService) Login(req *dto.LoginRequest) (*dto.LoginResponse, error) {
|
||||
// 使用新的方法,通过唯一标识符(用户名、邮箱等)查找用户
|
||||
user, err := s.userRepo.FindUserForLogin(req.Identifier)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("登录凭证不正确")
|
||||
}
|
||||
s.logger.Errorf("登录: 查询用户失败: %v", err)
|
||||
return nil, errors.New("登录失败")
|
||||
}
|
||||
|
||||
if !user.CheckPassword(req.Password) {
|
||||
return nil, errors.New("登录凭证不正确")
|
||||
}
|
||||
|
||||
// 登录成功,生成 JWT token
|
||||
tokenString, err := s.tokenService.GenerateToken(user.ID)
|
||||
if err != nil {
|
||||
s.logger.Errorf("登录: 生成令牌失败: %v", err)
|
||||
return nil, errors.New("登录失败,无法生成认证信息")
|
||||
}
|
||||
|
||||
return &dto.LoginResponse{
|
||||
Username: user.Username,
|
||||
ID: user.ID,
|
||||
Token: tokenString,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// SendTestNotification 发送测试通知
|
||||
func (s *userService) SendTestNotification(userID uint, req *dto.SendTestNotificationRequest) error {
|
||||
err := s.notifyService.SendTestMessage(userID, req.Type)
|
||||
if err != nil {
|
||||
s.logger.Errorf("发送测试通知: 服务层调用失败: %v", err)
|
||||
return errors.New("发送测试消息失败: " + err.Error())
|
||||
}
|
||||
s.logger.Infof("发送测试通知: 成功为用户 %d 发送类型为 %s 的测试消息", userID, req.Type)
|
||||
return nil
|
||||
}
|
||||
@@ -45,19 +45,15 @@ func NewApplication(configPath string) (*Application, error) {
|
||||
cfg.Server,
|
||||
logger,
|
||||
infra.Repos.UserRepo,
|
||||
infra.Repos.DeviceRepo,
|
||||
infra.Repos.AreaControllerRepo,
|
||||
infra.Repos.DeviceTemplateRepo,
|
||||
infra.Repos.PlanRepo,
|
||||
appServices.PigFarmService,
|
||||
appServices.PigBatchService,
|
||||
appServices.MonitorService,
|
||||
appServices.DeviceService,
|
||||
appServices.PlanService,
|
||||
appServices.UserService,
|
||||
infra.TokenService,
|
||||
appServices.AuditService,
|
||||
infra.NotifyService,
|
||||
domain.GeneralDeviceService,
|
||||
infra.Lora.ListenHandler,
|
||||
domain.AnalysisPlanTaskManager,
|
||||
)
|
||||
|
||||
// 4. 组装 Application 对象
|
||||
|
||||
@@ -185,6 +185,9 @@ type AppServices struct {
|
||||
PigFarmService service.PigFarmService
|
||||
PigBatchService service.PigBatchService
|
||||
MonitorService service.MonitorService
|
||||
DeviceService service.DeviceService
|
||||
PlanService service.PlanService
|
||||
UserService service.UserService
|
||||
AuditService audit.Service
|
||||
}
|
||||
|
||||
@@ -208,13 +211,24 @@ func initAppServices(infra *Infrastructure, domainServices *DomainServices, logg
|
||||
infra.Repos.PigTradeRepo,
|
||||
infra.Repos.NotificationRepo,
|
||||
)
|
||||
deviceService := service.NewDeviceService(
|
||||
infra.Repos.DeviceRepo,
|
||||
infra.Repos.AreaControllerRepo,
|
||||
infra.Repos.DeviceTemplateRepo,
|
||||
domainServices.GeneralDeviceService,
|
||||
)
|
||||
auditService := audit.NewService(infra.Repos.UserActionLogRepo, logger)
|
||||
planService := service.NewPlanService(logger, infra.Repos.PlanRepo, domainServices.AnalysisPlanTaskManager)
|
||||
userService := service.NewUserService(infra.Repos.UserRepo, infra.TokenService, infra.NotifyService, logger)
|
||||
|
||||
return &AppServices{
|
||||
PigFarmService: pigFarmService,
|
||||
PigBatchService: pigBatchService,
|
||||
MonitorService: monitorService,
|
||||
DeviceService: deviceService,
|
||||
AuditService: auditService,
|
||||
PlanService: planService,
|
||||
UserService: userService,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
package task_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/service/task"
|
||||
)
|
||||
|
||||
func TestNewDelayTask(t *testing.T) {
|
||||
id := "test-delay-task-1"
|
||||
duration := 100 * time.Millisecond
|
||||
priority := 1
|
||||
|
||||
dt := task.NewDelayTask(id, duration, priority)
|
||||
|
||||
if dt.GetID() != id {
|
||||
t.Errorf("期望任务ID为 %s, 实际为 %s", id, dt.GetID())
|
||||
}
|
||||
if dt.GetPriority() != priority {
|
||||
t.Errorf("期望任务优先级为 %d, 实际为 %d", priority, dt.GetPriority())
|
||||
}
|
||||
if dt.IsDone() != false {
|
||||
t.Error("任务初始状态不应为已完成")
|
||||
}
|
||||
// 动态生成的描述,需要匹配 GetDescription 的实现
|
||||
expectedDesc := fmt.Sprintf("延迟任务,ID: %s,延迟时间: %s", id, duration)
|
||||
if dt.GetDescription() != expectedDesc {
|
||||
t.Errorf("期望任务描述为 %s, 实际为 %s", expectedDesc, dt.GetDescription())
|
||||
}
|
||||
}
|
||||
|
||||
func TestDelayTaskExecute(t *testing.T) {
|
||||
id := "test-delay-task-execute"
|
||||
duration := 50 * time.Millisecond // 使用较短的延迟以加快测试速度
|
||||
priority := 1
|
||||
|
||||
dt := task.NewDelayTask(id, duration, priority)
|
||||
|
||||
if dt.IsDone() {
|
||||
t.Error("任务执行前不应为已完成状态")
|
||||
}
|
||||
|
||||
startTime := time.Now()
|
||||
err := dt.Execute()
|
||||
endTime := time.Now()
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("Execute 方法返回错误: %v", err)
|
||||
}
|
||||
if !dt.IsDone() {
|
||||
t.Error("任务执行后应为已完成状态")
|
||||
}
|
||||
|
||||
// 验证延迟时间大致正确,允许一些误差
|
||||
elapsed := endTime.Sub(startTime)
|
||||
if elapsed < duration || elapsed > duration*2 {
|
||||
t.Errorf("期望执行时间在 %v 左右, 但实际耗时 %v", duration, elapsed)
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
package token_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/app/service/token"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
func TestGenerateToken(t *testing.T) {
|
||||
// 使用一个测试密钥初始化 TokenService
|
||||
testSecret := []byte("test_secret_key")
|
||||
service := token.NewTokenService(testSecret)
|
||||
|
||||
userID := uint(123)
|
||||
tokenString, err := service.GenerateToken(userID)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("生成令牌失败: %v", err)
|
||||
}
|
||||
|
||||
if tokenString == "" {
|
||||
t.Fatal("生成的令牌字符串为空")
|
||||
}
|
||||
|
||||
// 解析 token 以确保其有效性及声明
|
||||
claims, err := service.ParseToken(tokenString)
|
||||
if err != nil {
|
||||
t.Fatalf("生成后解析令牌失败: %v", err)
|
||||
}
|
||||
|
||||
if claims.UserID != userID {
|
||||
t.Errorf("期望用户ID %d, 实际为 %d", userID, claims.UserID)
|
||||
}
|
||||
|
||||
// 检查 token 是否未过期 (在合理范围内)
|
||||
if claims.ExpiresAt == nil || claims.ExpiresAt.Time.Before(time.Now().Add(-time.Minute)) {
|
||||
t.Errorf("令牌过期时间无效或已过期")
|
||||
}
|
||||
|
||||
if claims.Issuer != "pig-farm-controller" {
|
||||
t.Errorf("期望签发者 \"pig-farm-controller\", 实际为 \"%s\"", claims.Issuer)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseToken(t *testing.T) {
|
||||
// 使用两个不同的测试密钥
|
||||
correctSecret := []byte("the_correct_secret")
|
||||
wrongSecret := []byte("a_very_wrong_secret")
|
||||
|
||||
serviceWithCorrectKey := token.NewTokenService(correctSecret)
|
||||
serviceWithWrongKey := token.NewTokenService(wrongSecret)
|
||||
|
||||
userID := uint(456)
|
||||
|
||||
// 1. 生成一个有效的 token
|
||||
validToken, err := serviceWithCorrectKey.GenerateToken(userID)
|
||||
if err != nil {
|
||||
t.Fatalf("为解析测试生成有效令牌失败: %v", err)
|
||||
}
|
||||
|
||||
// 测试用例 1: 使用正确的密钥成功解析
|
||||
claims, err := serviceWithCorrectKey.ParseToken(validToken)
|
||||
if err != nil {
|
||||
t.Errorf("使用正确密钥解析有效令牌失败: %v", err)
|
||||
}
|
||||
if claims.UserID != userID {
|
||||
t.Errorf("解析有效令牌时期望用户ID %d, 实际为 %d", userID, claims.UserID)
|
||||
}
|
||||
|
||||
// 测试用例 2: 无效 token (例如, 格式错误的字符串)
|
||||
invalidTokenString := "this.is.not.a.valid.jwt"
|
||||
_, err = serviceWithCorrectKey.ParseToken(invalidTokenString)
|
||||
if err == nil {
|
||||
t.Error("解析格式错误的令牌意外成功")
|
||||
}
|
||||
|
||||
// 测试用C:\Users\divano\Desktop\work\AA-Pig\pig-farm-controller\internal\infra\repository\plan_repository_test.go例 3: 过期 token
|
||||
expiredClaims := token.Claims{
|
||||
UserID: userID,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(-time.Hour)), // 1 小时前
|
||||
Issuer: "pig-farm-controller",
|
||||
},
|
||||
}
|
||||
expiredTokenClaims := jwt.NewWithClaims(jwt.SigningMethodHS256, expiredClaims)
|
||||
expiredTokenString, err := expiredTokenClaims.SignedString(correctSecret)
|
||||
if err != nil {
|
||||
t.Fatalf("生成过期令牌失败: %v", err)
|
||||
}
|
||||
_, err = serviceWithCorrectKey.ParseToken(expiredTokenString)
|
||||
if err == nil {
|
||||
t.Error("解析过期令牌意外成功")
|
||||
}
|
||||
|
||||
// 新增测试用例 4: 使用错误的密钥解析
|
||||
_, err = serviceWithWrongKey.ParseToken(validToken)
|
||||
if err == nil {
|
||||
t.Error("使用错误密钥解析令牌意外成功")
|
||||
}
|
||||
// 我们可以更精确地检查错误类型,以确保它是签名错误
|
||||
if !errors.Is(err, jwt.ErrTokenSignatureInvalid) {
|
||||
t.Errorf("期望得到签名无效错误 (ErrTokenSignatureInvalid),但得到了: %v", err)
|
||||
}
|
||||
}
|
||||
@@ -1,166 +0,0 @@
|
||||
package logs_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/config"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
// captureOutput 是一个辅助函数,用于捕获 logger 的输出到内存缓冲区
|
||||
func captureOutput(cfg config.LogConfig) (*logs.Logger, *bytes.Buffer) {
|
||||
var buf bytes.Buffer
|
||||
|
||||
encoder := logs.GetEncoder(cfg.Format)
|
||||
|
||||
writer := zapcore.AddSync(&buf)
|
||||
|
||||
level := zap.NewAtomicLevel()
|
||||
_ = level.UnmarshalText([]byte(cfg.Level))
|
||||
|
||||
core := zapcore.NewCore(encoder, writer, level)
|
||||
// 匹配 logs.go 中 NewLogger 的行为,添加调用者信息
|
||||
zapLogger := zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1))
|
||||
|
||||
logger := &logs.Logger{SugaredLogger: zapLogger.Sugar()}
|
||||
return logger, &buf
|
||||
}
|
||||
func TestNewLogger(t *testing.T) {
|
||||
t.Run("日志级别应生效", func(t *testing.T) {
|
||||
// 1. 创建一个级别为 WARN 的 logger
|
||||
logger, buf := captureOutput(config.LogConfig{Level: "warn", Format: "console"})
|
||||
|
||||
// 2. 调用不同级别的日志方法
|
||||
logger.Info("这条 info 日志不应被打印")
|
||||
logger.Warn("这条 warn 日志应该被打印")
|
||||
|
||||
// 3. 断言输出
|
||||
output := buf.String()
|
||||
assert.NotContains(t, output, "这条 info 日志不应被打印")
|
||||
assert.Contains(t, output, "这条 warn 日志应该被打印")
|
||||
})
|
||||
|
||||
t.Run("JSON 格式应生效", func(t *testing.T) {
|
||||
// 1. 创建一个格式为 JSON 的 logger
|
||||
logger, buf := captureOutput(config.LogConfig{Level: "info", Format: "json"})
|
||||
|
||||
// 2. 打印一条日志
|
||||
logger.Info("测试json输出")
|
||||
|
||||
// 3. 断言输出
|
||||
output := buf.String()
|
||||
// 验证它是否是合法的 JSON,并且包含预期的键值对
|
||||
var logEntry map[string]interface{}
|
||||
// 注意:由于日志库可能会在行尾添加换行符,我们先 trim space
|
||||
err := json.Unmarshal([]byte(strings.TrimSpace(output)), &logEntry)
|
||||
assert.NoError(t, err, "日志输出应为合法的JSON")
|
||||
assert.Equal(t, "INFO", logEntry["level"])
|
||||
assert.Equal(t, "测试json输出", logEntry["msg"])
|
||||
})
|
||||
|
||||
t.Run("文件日志构造函数不应 panic", func(t *testing.T) {
|
||||
// 这个测试保持原样,只验证构造函数在启用文件时不会崩溃
|
||||
// 注意:我们不在单元测试中实际写入文件
|
||||
cfgFile := config.LogConfig{
|
||||
Level: "info",
|
||||
EnableFile: true,
|
||||
FilePath: "test.log", // 在测试环境中,这个文件不会被真正创建
|
||||
}
|
||||
assert.NotPanics(t, func() { logs.NewLogger(cfgFile) })
|
||||
})
|
||||
}
|
||||
|
||||
func TestLogger_Write_ForGin(t *testing.T) {
|
||||
logger, buf := captureOutput(config.LogConfig{Level: "info"})
|
||||
|
||||
ginLog := "[GIN-debug] Listening and serving HTTP on :8080\n"
|
||||
_, err := logger.Write([]byte(ginLog))
|
||||
|
||||
assert.NoError(t, err)
|
||||
output := buf.String()
|
||||
// logger.Write 会将 gin 的日志转为 info 级别
|
||||
assert.Contains(t, output, "INFO")
|
||||
assert.Contains(t, output, strings.TrimSpace(ginLog))
|
||||
}
|
||||
|
||||
func TestGormLogger(t *testing.T) {
|
||||
logger, buf := captureOutput(config.LogConfig{Level: "debug"}) // 设置为 debug 以捕获所有级别
|
||||
gormLogger := logs.NewGormLogger(logger)
|
||||
|
||||
// 模拟 GORM 的 Trace 调用参数
|
||||
ctx := context.Background()
|
||||
sql := "SELECT * FROM users WHERE id = 1"
|
||||
rows := int64(1)
|
||||
fc := func() (string, int64) {
|
||||
return sql, rows
|
||||
}
|
||||
|
||||
t.Run("慢查询应记录为警告", func(t *testing.T) {
|
||||
buf.Reset()
|
||||
// 模拟一个耗时超过 200ms 的查询
|
||||
begin := time.Now().Add(-300 * time.Millisecond)
|
||||
gormLogger.Trace(ctx, begin, fc, nil)
|
||||
|
||||
output := buf.String()
|
||||
assert.Contains(t, output, "WARN", "应包含 WARN 级别")
|
||||
assert.Contains(t, output, "[GORM] slow query", "应包含慢查询信息")
|
||||
assert.Contains(t, output, "SELECT * FROM users WHERE id = 1", "应包含 SQL 语句")
|
||||
})
|
||||
|
||||
t.Run("普通错误应记录为Error", func(t *testing.T) {
|
||||
buf.Reset()
|
||||
queryError := errors.New("syntax error")
|
||||
gormLogger.Trace(ctx, time.Now(), fc, queryError)
|
||||
|
||||
output := buf.String()
|
||||
assert.Contains(t, output, "ERROR")
|
||||
assert.Contains(t, output, "[GORM] error: syntax error")
|
||||
})
|
||||
|
||||
t.Run("当SkipErrRecordNotFound为true时应跳过RecordNotFound错误", func(t *testing.T) {
|
||||
buf.Reset()
|
||||
// 确保默认设置是 true
|
||||
gormLogger.SkipErrRecordNotFound = true
|
||||
// 错误必须包含 "record not found" 字符串以匹配 logs.go 中的判断逻辑
|
||||
queryError := errors.New("record not found")
|
||||
gormLogger.Trace(ctx, time.Now(), fc, queryError)
|
||||
|
||||
assert.Empty(t, buf.String(), "开启 SkipErrRecordNotFound 后,record not found 错误不应产生任何日志")
|
||||
})
|
||||
|
||||
t.Run("当SkipErrRecordNotFound为false时应记录RecordNotFound错误", func(t *testing.T) {
|
||||
buf.Reset()
|
||||
// 手动将 SkipErrRecordNotFound 设置为 false
|
||||
gormLogger.SkipErrRecordNotFound = false
|
||||
|
||||
queryError := errors.New("record not found")
|
||||
gormLogger.Trace(ctx, time.Now(), fc, queryError)
|
||||
|
||||
// 恢复设置,避免影响其他测试
|
||||
gormLogger.SkipErrRecordNotFound = true
|
||||
|
||||
output := buf.String()
|
||||
assert.NotEmpty(t, output, "关闭 SkipErrRecordNotFound 后,record not found 错误应该产生日志")
|
||||
assert.Contains(t, output, "ERROR")
|
||||
assert.Contains(t, output, "[GORM] error: record not found")
|
||||
})
|
||||
|
||||
t.Run("正常查询应记录为Debug", func(t *testing.T) {
|
||||
buf.Reset()
|
||||
// 模拟一个快速查询
|
||||
gormLogger.Trace(ctx, time.Now(), fc, nil)
|
||||
|
||||
output := buf.String()
|
||||
assert.Contains(t, output, "DEBUG") // 正常查询是 Debug 级别
|
||||
assert.Contains(t, output, "[GORM] trace")
|
||||
})
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
package models_test
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestPlan_ReorderSteps(t *testing.T) {
|
||||
type testCase struct {
|
||||
name string
|
||||
initialPlan *models.Plan
|
||||
expectedOrders []int
|
||||
}
|
||||
|
||||
testCases := []testCase{
|
||||
// --- Test Cases for Tasks ---
|
||||
{
|
||||
name: "Tasks: 完美顺序",
|
||||
initialPlan: &models.Plan{
|
||||
ContentType: models.PlanContentTypeTasks,
|
||||
Tasks: []models.Task{
|
||||
{ExecutionOrder: 1},
|
||||
{ExecutionOrder: 2},
|
||||
{ExecutionOrder: 3},
|
||||
},
|
||||
},
|
||||
expectedOrders: []int{1, 2, 3},
|
||||
},
|
||||
{
|
||||
name: "Tasks: 有间断",
|
||||
initialPlan: &models.Plan{
|
||||
ContentType: models.PlanContentTypeTasks,
|
||||
Tasks: []models.Task{
|
||||
{ExecutionOrder: 1},
|
||||
{ExecutionOrder: 3},
|
||||
{ExecutionOrder: 5},
|
||||
},
|
||||
},
|
||||
expectedOrders: []int{1, 2, 3},
|
||||
},
|
||||
{
|
||||
name: "Tasks: 从0开始",
|
||||
initialPlan: &models.Plan{
|
||||
ContentType: models.PlanContentTypeTasks,
|
||||
Tasks: []models.Task{
|
||||
{ExecutionOrder: 0},
|
||||
{ExecutionOrder: 1},
|
||||
{ExecutionOrder: 2},
|
||||
},
|
||||
},
|
||||
expectedOrders: []int{1, 2, 3},
|
||||
},
|
||||
{
|
||||
name: "Tasks: 完全无序",
|
||||
initialPlan: &models.Plan{
|
||||
ContentType: models.PlanContentTypeTasks,
|
||||
Tasks: []models.Task{
|
||||
{ExecutionOrder: 8},
|
||||
{ExecutionOrder: 2},
|
||||
{ExecutionOrder: 4},
|
||||
},
|
||||
},
|
||||
expectedOrders: []int{1, 2, 3},
|
||||
},
|
||||
{
|
||||
name: "Tasks: 包含负数",
|
||||
initialPlan: &models.Plan{
|
||||
ContentType: models.PlanContentTypeTasks,
|
||||
Tasks: []models.Task{
|
||||
{ExecutionOrder: -5},
|
||||
{ExecutionOrder: 10},
|
||||
{ExecutionOrder: 2},
|
||||
},
|
||||
},
|
||||
expectedOrders: []int{1, 2, 3},
|
||||
},
|
||||
{
|
||||
name: "Tasks: 空切片",
|
||||
initialPlan: &models.Plan{
|
||||
ContentType: models.PlanContentTypeTasks,
|
||||
Tasks: []models.Task{},
|
||||
},
|
||||
expectedOrders: []int{},
|
||||
},
|
||||
{
|
||||
name: "Tasks: 单个元素",
|
||||
initialPlan: &models.Plan{
|
||||
ContentType: models.PlanContentTypeTasks,
|
||||
Tasks: []models.Task{
|
||||
{ExecutionOrder: 100},
|
||||
},
|
||||
},
|
||||
expectedOrders: []int{1},
|
||||
},
|
||||
// --- Test Cases for SubPlans ---
|
||||
{
|
||||
name: "SubPlans: 完美顺序",
|
||||
initialPlan: &models.Plan{
|
||||
ContentType: models.PlanContentTypeSubPlans,
|
||||
SubPlans: []models.SubPlan{
|
||||
{ExecutionOrder: 1},
|
||||
{ExecutionOrder: 2},
|
||||
{ExecutionOrder: 3},
|
||||
},
|
||||
},
|
||||
expectedOrders: []int{1, 2, 3},
|
||||
},
|
||||
{
|
||||
name: "SubPlans: 有间断",
|
||||
initialPlan: &models.Plan{
|
||||
ContentType: models.PlanContentTypeSubPlans,
|
||||
SubPlans: []models.SubPlan{
|
||||
{ExecutionOrder: 1},
|
||||
{ExecutionOrder: 3},
|
||||
{ExecutionOrder: 5},
|
||||
},
|
||||
},
|
||||
expectedOrders: []int{1, 2, 3},
|
||||
},
|
||||
{
|
||||
name: "SubPlans: 从0开始",
|
||||
initialPlan: &models.Plan{
|
||||
ContentType: models.PlanContentTypeSubPlans,
|
||||
SubPlans: []models.SubPlan{
|
||||
{ExecutionOrder: 0},
|
||||
{ExecutionOrder: 1},
|
||||
{ExecutionOrder: 2},
|
||||
},
|
||||
},
|
||||
expectedOrders: []int{1, 2, 3},
|
||||
},
|
||||
{
|
||||
name: "SubPlans: 完全无序",
|
||||
initialPlan: &models.Plan{
|
||||
ContentType: models.PlanContentTypeSubPlans,
|
||||
SubPlans: []models.SubPlan{
|
||||
{ExecutionOrder: 8},
|
||||
{ExecutionOrder: 2},
|
||||
{ExecutionOrder: 4},
|
||||
},
|
||||
},
|
||||
expectedOrders: []int{1, 2, 3},
|
||||
},
|
||||
{
|
||||
name: "SubPlans: 包含负数",
|
||||
initialPlan: &models.Plan{
|
||||
ContentType: models.PlanContentTypeSubPlans,
|
||||
SubPlans: []models.SubPlan{
|
||||
{ExecutionOrder: -5},
|
||||
{ExecutionOrder: 10},
|
||||
{ExecutionOrder: 2},
|
||||
},
|
||||
},
|
||||
expectedOrders: []int{1, 2, 3},
|
||||
},
|
||||
{
|
||||
name: "SubPlans: 空切片",
|
||||
initialPlan: &models.Plan{
|
||||
ContentType: models.PlanContentTypeSubPlans,
|
||||
SubPlans: []models.SubPlan{},
|
||||
},
|
||||
expectedOrders: []int{},
|
||||
},
|
||||
{
|
||||
name: "SubPlans: 单个元素",
|
||||
initialPlan: &models.Plan{
|
||||
ContentType: models.PlanContentTypeSubPlans,
|
||||
SubPlans: []models.SubPlan{
|
||||
{ExecutionOrder: 100},
|
||||
},
|
||||
},
|
||||
expectedOrders: []int{1},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// 调用被测试的方法
|
||||
tc.initialPlan.ReorderSteps()
|
||||
|
||||
// 提取并验证最终的顺序
|
||||
finalOrders := make([]int, 0)
|
||||
if tc.initialPlan.ContentType == models.PlanContentTypeTasks {
|
||||
for _, task := range tc.initialPlan.Tasks {
|
||||
finalOrders = append(finalOrders, task.ExecutionOrder)
|
||||
}
|
||||
} else if tc.initialPlan.ContentType == models.PlanContentTypeSubPlans {
|
||||
for _, subPlan := range tc.initialPlan.SubPlans {
|
||||
finalOrders = append(finalOrders, subPlan.ExecutionOrder)
|
||||
}
|
||||
}
|
||||
|
||||
// 对 finalOrders 进行排序,以确保比较的一致性,因为 ReorderSteps 后的顺序是固定的
|
||||
sort.Ints(finalOrders)
|
||||
|
||||
assert.Equal(t, tc.expectedOrders, finalOrders, "The final execution orders should be a continuous sequence starting from 1.")
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
// Package models_test 包含对 models 包的单元测试
|
||||
package models_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func TestUser_CheckPassword(t *testing.T) {
|
||||
plainPassword := "my-secret-password"
|
||||
|
||||
// 1. 生成一个密码哈希用于测试
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(plainPassword), bcrypt.DefaultCost)
|
||||
assert.NoError(t, err, "生成密码哈希不应出错")
|
||||
|
||||
user := &models.User{
|
||||
Password: string(hashedPassword),
|
||||
}
|
||||
|
||||
t.Run("密码正确", func(t *testing.T) {
|
||||
// 2. 使用正确的明文密码进行校验
|
||||
match := user.CheckPassword(plainPassword)
|
||||
assert.True(t, match, "正确的密码应该校验通过")
|
||||
})
|
||||
|
||||
t.Run("密码错误", func(t *testing.T) {
|
||||
// 3. 使用错误的明文密码进行校验
|
||||
match := user.CheckPassword("wrong-password")
|
||||
assert.False(t, match, "错误的密码应该校验失败")
|
||||
})
|
||||
|
||||
t.Run("空密码", func(t *testing.T) {
|
||||
// 4. 使用空字符串作为密码进行校验
|
||||
match := user.CheckPassword("")
|
||||
assert.False(t, match, "空密码应该校验失败")
|
||||
})
|
||||
}
|
||||
func TestUser_BeforeCreate(t *testing.T) {
|
||||
t.Run("密码应被成功哈希", func(t *testing.T) {
|
||||
plainPassword := "securepassword123"
|
||||
user := &models.User{
|
||||
Username: "testuser",
|
||||
Password: plainPassword,
|
||||
}
|
||||
|
||||
// 模拟 GORM 钩子调用
|
||||
err := user.BeforeCreate(nil) // GORM 钩子通常接收 *gorm.DB,这里我们传入 nil,因为 BeforeCreate 不依赖 DB
|
||||
assert.NoError(t, err, "BeforeCreate 不应返回错误")
|
||||
|
||||
// 验证密码是否已被哈希(不再是明文)
|
||||
assert.NotEqual(t, plainPassword, user.Password, "密码应已被哈希")
|
||||
|
||||
// 验证哈希后的密码是否能被正确校验
|
||||
assert.True(t, user.CheckPassword(plainPassword), "哈希后的密码应能通过校验")
|
||||
})
|
||||
|
||||
t.Run("空密码不应被哈希", func(t *testing.T) {
|
||||
plainPassword := ""
|
||||
user := &models.User{
|
||||
Username: "empty_pass_user",
|
||||
Password: plainPassword,
|
||||
}
|
||||
|
||||
// 模拟 GORM 钩子调用
|
||||
err := user.BeforeCreate(nil)
|
||||
assert.NoError(t, err, "BeforeCreate 不应返回错误")
|
||||
|
||||
// 验证密码仍然是空字符串
|
||||
assert.Equal(t, plainPassword, user.Password, "空密码不应被哈希")
|
||||
})
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
package repository_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// setupTestDB 是一个共享的辅助函数,用于为集成测试创建一个干净的、内存中的 SQLite 数据库实例。
|
||||
func setupTestDB(t *testing.T) *gorm.DB {
|
||||
// "file::memory:?cache=shared" 是 GORM 连接内存 SQLite 的标准方式,确保在同一测试中的不同连接可以访问相同的数据,而我们显然不需要这个
|
||||
db, err := gorm.Open(sqlite.Open("file::memory:"), &gorm.Config{})
|
||||
assert.NoError(t, err, "连接内存数据库时发生错误")
|
||||
|
||||
// 自动迁移所有需要的表结构
|
||||
err = db.AutoMigrate(models.GetAllModels()...)
|
||||
assert.NoError(t, err, "数据库迁移时发生错误")
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
// TestMain 是一个特殊的函数,它会在包内的所有测试运行之前被调用。
|
||||
// 我们可以在这里进行一些全局的设置和清理工作。
|
||||
func TestMain(m *testing.M) {
|
||||
// 在所有测试运行前可以执行一些设置代码
|
||||
|
||||
// 运行包中的所有测试
|
||||
code := m.Run()
|
||||
|
||||
// 在所有测试运行后可以执行一些清理代码
|
||||
|
||||
// 退出测试
|
||||
os.Exit(code)
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
// Package repository_test 包含对 repository 包的集成测试
|
||||
package repository_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
|
||||
"git.huangwc.com/pig/pig-farm-controller/internal/infra/repository"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func TestGormUserRepository(t *testing.T) {
|
||||
db := setupTestDB(t)
|
||||
repo := repository.NewGormUserRepository(db)
|
||||
|
||||
plainPassword := "my-secret-password"
|
||||
userToCreate := &models.User{
|
||||
Username: "testuser",
|
||||
Password: plainPassword, // 我们提供的是明文密码
|
||||
}
|
||||
|
||||
t.Run("创建 - 成功创建并验证密码哈希", func(t *testing.T) {
|
||||
err := repo.Create(userToCreate)
|
||||
assert.NoError(t, err)
|
||||
|
||||
// 验证用户已被创建
|
||||
assert.NotZero(t, userToCreate.ID)
|
||||
|
||||
// 从数据库中直接取回记录,以验证 BeforeSave 钩子是否生效
|
||||
var savedUser models.User
|
||||
db.First(&savedUser, userToCreate.ID)
|
||||
|
||||
// 验证密码字段存储的不是明文
|
||||
assert.NotEqual(t, plainPassword, savedUser.Password, "数据库中存储的密码不应是明文")
|
||||
|
||||
// 验证存储的哈希是正确的
|
||||
assert.True(t, savedUser.CheckPassword(plainPassword), "存储的密码哈希应该能与原明文匹配")
|
||||
})
|
||||
|
||||
t.Run("创建 - 用户名冲突", func(t *testing.T) {
|
||||
// 尝试创建一个同名用户
|
||||
duplicateUser := &models.User{Username: "testuser", Password: "anypassword"}
|
||||
err := repo.Create(duplicateUser)
|
||||
|
||||
// 我们期望一个错误,因为用户名是唯一的
|
||||
assert.Error(t, err, "创建同名用户应该返回错误")
|
||||
// 更精确地,可以检查是否是唯一键冲突错误
|
||||
assert.Contains(t, err.Error(), "UNIQUE constraint failed: users.username", "错误信息应包含唯一键冲突")
|
||||
})
|
||||
|
||||
t.Run("按用户名查找 - 找到用户", func(t *testing.T) {
|
||||
foundUser, err := repo.FindByUsername("testuser")
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, foundUser)
|
||||
assert.Equal(t, userToCreate.ID, foundUser.ID)
|
||||
assert.Equal(t, "testuser", foundUser.Username)
|
||||
})
|
||||
|
||||
t.Run("按用户名查找 - 未找到用户", func(t *testing.T) {
|
||||
_, err := repo.FindByUsername("nonexistent")
|
||||
assert.Error(t, err, "查找不存在的用户应该返回错误")
|
||||
assert.ErrorIs(t, err, gorm.ErrRecordNotFound, "错误类型应为 gorm.ErrRecordNotFound")
|
||||
})
|
||||
|
||||
t.Run("按ID查找 - 找到用户", func(t *testing.T) {
|
||||
foundUser, err := repo.FindByID(userToCreate.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, foundUser)
|
||||
assert.Equal(t, userToCreate.ID, foundUser.ID)
|
||||
})
|
||||
|
||||
t.Run("按ID查找 - 未找到用户", func(t *testing.T) {
|
||||
_, err := repo.FindByID(99999)
|
||||
assert.Error(t, err, "查找不存在的ID应该返回错误")
|
||||
assert.ErrorIs(t, err, gorm.ErrRecordNotFound, "错误类型应为 gorm.ErrRecordNotFound")
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,414 @@
|
||||
# `monitor` 模块重构设计
|
||||
|
||||
## Context
|
||||
|
||||
当前, `monitor` 模块的数据转换逻辑(例如, 将 `repository` 层返回的 `models` 实体转换为 `dto` 对象)主要存在于
|
||||
`internal/app/controller/monitor/monitor_controller.go` 文件中。
|
||||
|
||||
这种设计导致了以下问题:
|
||||
|
||||
- **职责不清**:控制器层承担了过多的数据处理任务, 违反了“关注点分离”原则。控制器应主要负责处理 HTTP 请求、参数绑定和调用服务,
|
||||
而非执行业务或数据转换逻辑。
|
||||
- **代码重复**:如果未来有其他服务需要类似的数据转换, 可能会导致代码重复。
|
||||
- **可测试性差**:由于转换逻辑与 `echo.Context` 紧密耦合, 对其进行单元测试变得更加复杂。
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
### Goals
|
||||
|
||||
- **迁移数据转换逻辑**:将 `monitor` 模块中所有的数据转换逻辑从控制器层 (`monitor_controller.go`) 迁移到服务层 (
|
||||
`monitor_service.go`)。
|
||||
- **统一服务层接口**:使服务层的方法直接接收请求 DTO, 并返回响应 DTO, 从而使服务本身成为一个完整的、自包含的业务逻辑单元。
|
||||
- **简化控制器**:精简控制器中的代码, 使其只关注其核心职责:请求处理和响应发送。
|
||||
|
||||
### Non-Goals
|
||||
|
||||
- **不修改业务逻辑**:本次重构不涉及任何已有业务规则的变更。例如, `ListPlanExecutionLogs` 中获取关联计划信息的逻辑必须保持不变。
|
||||
- **不改变 API 契约**:API 的请求参数和响应结构对最终用户保持不变。
|
||||
- **不引入新的依赖**:仅在现有框架和依赖下进行代码调整。
|
||||
|
||||
## Decisions
|
||||
|
||||
- **决策:在服务层完成 DTO 转换**
|
||||
- **理由**:服务层是封装业务逻辑的核心, 将数据从领域模型 (`models`) 转换为外部表示 (`dto`)
|
||||
是业务服务的一部分。这样做可以确保任何调用该服务的客户端(无论是控制器、gRPC 服务还是其他服务)都能获得一致的、随时可用的数据结构。
|
||||
- **替代方案**:曾考虑在 `dto` 包中创建一个独立的转换层。但最终认为, 将转换逻辑内聚到服务层更能体现其业务属性,
|
||||
因为服务层最清楚需要暴露哪些数据以及如何组织这些数据。
|
||||
|
||||
- **决策:修改服务层接口以直接处理 DTO**
|
||||
- **具体实现**:计划将 `MonitorService` 接口中的所有 `List...` 方法签名从
|
||||
`ListSomething(opts repository.ListOptions, page, pageSize int) ([]models.Something, int64, error)` 修改为
|
||||
`ListSomething(req *dto.ListSomethingRequest) (*dto.ListSomethingResponse, error)`。
|
||||
- **理由**:这种设计将极大地简化控制器与服务之间的交互。控制器将不再需要手动构建 `repository.ListOptions`
|
||||
或在调用服务后手动组装响应 DTO。它只需传递请求 DTO, 然后直接使用服务返回的响应 DTO, 从而实现彻底的解耦。
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- **风险:意外修改或丢失现有业务逻辑**
|
||||
- **描述**:在移动代码的过程中, 尤其是像 `ListPlanExecutionLogs` 这样包含特定业务逻辑(获取关联 `plans`)的方法,
|
||||
存在逻辑被无意中删除或修改的风险。
|
||||
- **缓解措施**:
|
||||
1. **代码审查**:在重构前后仔细比对原有逻辑, 确保其被完整地迁移到了新的服务层方法中。
|
||||
2. **保留原有实现**:在新的服务层方法中, 将严格按照控制器中原有的顺序——先构建查询选项, 再调用仓库,
|
||||
最后进行数据转换——来组织代码, 确保逻辑的等效性。
|
||||
3. **测试**:在完成重构后, 必须进行完整的回归测试, 确保所有受影响的 API 端点的行为与重构前完全一致。
|
||||
|
||||
## Migration Plan
|
||||
|
||||
本次重构将按以下步骤进行:
|
||||
|
||||
1. **修改服务层 (`internal/app/service/monitor_service.go`)**
|
||||
- **更新接口**:修改 `MonitorService` 接口中所有 `List...` 方法的签名, 使其接收请求 DTO 并返回响应 DTO。
|
||||
- **实现数据转换**:在每个 `List...` 方法的实现中, 添加从请求 DTO 到 `repository.ListOptions` 的转换逻辑, 以及从业仓库返回的
|
||||
`models` 到响应 DTO 的转换逻辑。对于 `ListPlanExecutionLogs` 等方法, 确保原有的附加业务逻辑(如查询关联 `Plan`
|
||||
信息)被完整保留。
|
||||
|
||||
2. **修改控制器层 (`internal/app/controller/monitor/monitor_controller.go`)**
|
||||
- **移除转换逻辑**:删除所有手动构建 `repository.ListOptions` 和调用 `dto.NewList...Response` 的代码。
|
||||
- **更新服务调用**:修改对 `monitorService` 的调用, 使其传递完整的请求 DTO, 并直接处理返回的响应 DTO。
|
||||
- **简化日志**:调整日志记录, 以便从服务层返回的 DTO 中获取列表长度和总记录数。
|
||||
|
||||
3. **验证**
|
||||
- 通过静态代码分析和审查, 确认代码风格和逻辑的正确性。
|
||||
- 进行完整的单元测试和集成测试, 以确保重构没有引入任何回归问题。
|
||||
|
||||
## Open Questions
|
||||
|
||||
- 暂无。
|
||||
|
||||
---
|
||||
|
||||
## `device` 模块重构设计
|
||||
|
||||
### Context
|
||||
|
||||
`device_controller.go` 当前直接依赖多个 `repository` 和 `domain.Service`,并在其方法内部执行了大量本应属于应用服务层的逻辑,包括:
|
||||
|
||||
- **直接的数据库操作**:调用 `repository` 的 `Create`, `Update`, `Delete`, `Find` 等方法。
|
||||
- **领域模型实例化**:通过 `&models.Device{...}` 直接创建数据库模型。
|
||||
- **内部字段序列化**:对 `Properties`, `Commands`, `Values` 等字段执行 `json.Marshal`。
|
||||
- **业务规则验证**:调用 `model.SelfCheck()`。
|
||||
- **复杂的错误处理**:通过 `errors.Is` 和 `strings.Contains` 解析底层数据库错误。
|
||||
- **DTO 转换**:在方法末尾调用 `dto.New...Response`。
|
||||
|
||||
这种设计导致控制器与基础设施层和领域层紧密耦合,违反了分层架构的原则。
|
||||
|
||||
### Goals / Non-Goals
|
||||
|
||||
#### Goals
|
||||
|
||||
- **创建应用服务层**:引入一个新的 `internal/app/service/device_service.go` 来封装业务逻辑。
|
||||
- **迁移业务逻辑**:将上述所有在控制器中识别出的业务逻辑和数据处理任务,全部迁移到新的 `DeviceService` 中。
|
||||
- **简化控制器**:使 `device_controller.go` 只负责 HTTP 请求处理和对新 `DeviceService` 的调用。
|
||||
- **保持领域服务纯粹**:确保 `internal/domain/device/device_service.go` 继续专注于核心领域逻辑,不与 DTO 发生耦合。
|
||||
|
||||
#### Non-Goals
|
||||
|
||||
- **不改变领域服务**:不对 `domain.device.Service` 的接口和实现进行任何修改。
|
||||
- **不改变 API 契约**:对外暴露的 API 接口、请求和响应格式保持不变。
|
||||
|
||||
### Decisions
|
||||
|
||||
- **决策:引入新的应用服务 `DeviceService`**
|
||||
- **理由**:这是解决控制器职责过重和分层不清问题的标准做法。该服务将作为应用层门面,协调 `repository` 和
|
||||
`domain.Service`,并为控制器提供一个清晰、稳定的接口。
|
||||
- **结构**:`DeviceService` 将依赖于 `DeviceRepository`, `AreaControllerRepository`, `DeviceTemplateRepository` 和
|
||||
`domain.device.Service`。
|
||||
|
||||
- **决策:`DeviceService` 接口全面采用 DTO**
|
||||
- **具体实现**:接口方法将接收 `dto.Create...Request` 等请求 DTO,并返回 `*dto....Response` 响应 DTO。
|
||||
- **理由**:这与 `monitor` 模块的重构决策一致,可以确保应用服务层的接口统一、清晰,并与上层(控制器)和下层(领域/仓库)完全解耦。
|
||||
|
||||
### Migration Plan
|
||||
|
||||
1. **创建 `internal/app/service/device_service.go` 文件**
|
||||
- 定义 `DeviceService` 接口,为控制器中的每个处理器方法(`CreateDevice`, `UpdateDevice`, `GetDevice`, `ListDevices`,
|
||||
`DeleteDevice`, `ManualControl` 等)创建相应的方法。
|
||||
- 定义 `deviceService` 结构体,并实现 `DeviceService` 接口。
|
||||
- **`Create/Update` 方法实现**:
|
||||
1. 接收请求 DTO。
|
||||
2. 执行 `json.Marshal` 转换 `Properties` 等字段。
|
||||
3. 创建 `models.Xxx` 实例。
|
||||
4. 调用 `model.SelfCheck()`。
|
||||
5. 调用 `repository.Create/Update`。
|
||||
6. 调用 `repository.FindByID` 重新加载模型(确保关联数据完整)。
|
||||
7. 调用 `dto.New...Response` 将模型转换为响应 DTO 并返回。
|
||||
- **`Get/List` 方法实现**:
|
||||
1. 调用 `repository.Find/List`。
|
||||
2. 调用 `dto.New...Response` 转换并返回。
|
||||
- **`Delete` 方法实现**:
|
||||
1. 调用 `repository.Delete`。
|
||||
2. 捕获并转换特定的“资源被使用”错误。
|
||||
- **`ManualControl` 方法实现**:
|
||||
1. 调用 `repository.FindByIDString` 加载模型。
|
||||
2. 实现 `action` 字符串到 `device.DeviceAction` 的映射。
|
||||
3. 调用 `domain.device.Service.Switch/Collect`。
|
||||
|
||||
2. **修改 `internal/app/controller/device/device_controller.go`**
|
||||
- **更新依赖**:将 `Controller` 的依赖从多个 `repository` 和 `domain.Service` 替换为唯一的
|
||||
`app/service.DeviceService`。
|
||||
- **简化所有处理器方法**:
|
||||
1. 移除所有业务逻辑(`json.Marshal`, `SelfCheck`, `repository` 调用, `dto` 转换等)。
|
||||
2. 每个方法仅保留:参数绑定、调用 `c.deviceService.Method(req)`、错误处理和成功响应。
|
||||
|
||||
3. **修改 `internal/core/component_initializers.go`**
|
||||
- 在 `AppServices` 结构体中增加 `DeviceService service.DeviceService` 字段。
|
||||
- 在 `initAppServices` 函数中,调用 `service.NewDeviceService` 创建实例,并将其注入到 `AppServices` 中。
|
||||
|
||||
4. **修改 `internal/app/api/api.go`**
|
||||
- 更新 `NewAPI` 函数的参数,使其接收新的 `app/service.DeviceService`。
|
||||
- 更新 `device.NewController` 的调用,将多个仓库和领域服务的依赖替换为单一的 `DeviceService` 依赖。
|
||||
|
||||
### Open Questions
|
||||
|
||||
- 暂无。
|
||||
|
||||
---
|
||||
|
||||
## `pig-farm` 模块重构设计
|
||||
|
||||
### Context
|
||||
|
||||
与 `monitor` 模块类似, `pig_farm_controller.go` 当前包含了将 `service` 层返回的 `models.PigHouse` 和 `models.Pen`
|
||||
实体手动转换为 `dto.PigHouseResponse` 和 `dto.PenResponse` 的逻辑。此外,
|
||||
控制器还处理了部分本应由服务层处理的业务错误判断 (例如 `service.ErrHouseNotFound`)。
|
||||
|
||||
这种模式导致了与 `monitor` 模块相同的职责不清、代码重复和可测试性差的问题。
|
||||
|
||||
### Goals / Non-Goals
|
||||
|
||||
#### Goals
|
||||
|
||||
- **迁移数据转换逻辑**: 将 `pig-farm` 模块中所有的数据转换逻辑从控制器层 (`pig_farm_controller.go`) 迁移到服务层 (
|
||||
`pig_farm_service.go`)。
|
||||
- **统一服务层接口**: 修改 `PigFarmService` 接口, 使其直接返回响应 DTO (`dto.XxxResponse`)。
|
||||
- **简化控制器**: 精简 `PigFarmController` 中的代码, 移除所有 `models` 到 `dto` 的转换代码, 使其直接使用服务层返回的
|
||||
DTO。
|
||||
|
||||
#### Non-Goals
|
||||
|
||||
- **不修改业务逻辑**: 本次重构严格保证业务逻辑不变。服务层将精确复制控制器层现有的转换逻辑, 不增加或减少任何字段。
|
||||
- **不改变 API 契约**: API 的请求和响应对最终用户保持完全一致。
|
||||
|
||||
### Decisions
|
||||
|
||||
- **决策:在服务层完成 `models` 到 `dto` 的转换**
|
||||
- **理由**: 与其他模块保持一致, 将数据转换视为服务层业务逻辑的一部分。这确保了服务接口的稳定性和调用方的便利性。
|
||||
- **具体实现**: `pig_farm_service.go` 中的方法在从 `repository` 获取 `models` 实体后, 将其转换为对应的 `dto` 再返回。
|
||||
|
||||
### Migration Plan
|
||||
|
||||
1. **修改 `internal/app/service/pig_farm_service.go`**
|
||||
- **更新 `PigFarmService` 接口**:
|
||||
- `CreatePigHouse(...) (*models.PigHouse, error)` -> `CreatePigHouse(...) (*dto.PigHouseResponse, error)`
|
||||
- `GetPigHouseByID(...) (*models.PigHouse, error)` -> `GetPigHouseByID(...) (*dto.PigHouseResponse, error)`
|
||||
- `ListPigHouses(...) ([]models.PigHouse, error)` -> `ListPigHouses(...) ([]dto.PigHouseResponse, error)`
|
||||
- `UpdatePigHouse(...) (*models.PigHouse, error)` -> `UpdatePigHouse(...) (*dto.PigHouseResponse, error)`
|
||||
- `CreatePen(...) (*models.Pen, error)` -> `CreatePen(...) (*dto.PenResponse, error)`
|
||||
- `UpdatePen(...) (*models.Pen, error)` -> `UpdatePen(...) (*dto.PenResponse, error)`
|
||||
- `UpdatePenStatus(...) (*models.Pen, error)` -> `UpdatePenStatus(...) (*dto.PenResponse, error)`
|
||||
- **实现数据转换**:
|
||||
- 在上述每个方法的实现中, 在从 `repository` 获得 `models` 对象后, 添加代码将其转换为对应的 `dto.XxxResponse` 对象。
|
||||
- 转换逻辑将严格按照 `pig_farm_controller.go` 中现有的实现, 确保字段一一对应, 无任何增删。
|
||||
- 例如, 在 `UpdatePigHouse` 中:
|
||||
|
||||
|
||||
2. **修改 `internal/app/controller/management/pig_farm_controller.go`**
|
||||
- **移除 DTO 转换代码**:
|
||||
- 在 `CreatePigHouse`, `GetPigHouse`, `UpdatePigHouse` 方法中, 删除手动创建 `dto.PigHouseResponse` 的代码。
|
||||
- 在 `ListPigHouses` 方法中, 删除用于遍历 `houses` 并创建 `[]dto.PigHouseResponse` 的 `for` 循环。
|
||||
- 在 `CreatePen`, `UpdatePen`, `UpdatePenStatus` 方法中, 删除手动创建 `dto.PenResponse` 的代码。
|
||||
- **更新服务调用**:
|
||||
- 将服务层返回的 DTO 对象直接传递给 `controller.SendSuccessWithAudit`。
|
||||
|
||||
|
||||
3. **验证**
|
||||
- 通过代码审查确认转换逻辑被精确迁移。
|
||||
- 运行相关测试, 并通过手动 API 测试验证端点行为与重构前完全一致。
|
||||
|
||||
### Open Questions
|
||||
|
||||
- 暂无。
|
||||
|
||||
---
|
||||
|
||||
## `plan` 模块重构设计
|
||||
|
||||
### Context
|
||||
|
||||
`plan_controller.go` 当前包含了大量的业务逻辑,这违反了控制器层应只负责请求处理和响应发送的原则。具体问题包括:
|
||||
|
||||
- **业务规则判断**:控制器中直接判断计划类型(如 `models.PlanTypeSystem`)、计划状态(如 `models.PlanStatusEnabled`)以及
|
||||
`ContentType` 的自动判断。
|
||||
- **领域对象创建与转换**:控制器直接使用 `dto.NewPlanFromCreateRequest` 和 `dto.NewPlanFromUpdateRequest` 将请求 DTO 转换为
|
||||
`models.Plan`,并在响应前将 `models.Plan` 转换为 `dto.PlanResponse`。
|
||||
- **直接调用仓库层**:控制器直接调用 `planRepo` 的 `CreatePlan`, `GetPlanByID`, `ListPlans`, `UpdatePlan`, `DeletePlan`,
|
||||
`GetBasicPlanByID`, `UpdatePlanStatus`, `UpdateExecuteCount`, `StopPlanTransactionally` 等方法。
|
||||
- **协调领域服务**:控制器直接协调 `analysisPlanTaskManager` 的 `EnsureAnalysisTaskDefinition` 和 `CreateOrUpdateTrigger`
|
||||
方法。
|
||||
- **错误处理**:控制器直接通过 `errors.Is(err, gorm.ErrRecordNotFound)` 判断仓库层错误,并根据错误类型返回不同的 HTTP 状态码。
|
||||
- **执行计数器重置**:在 `UpdatePlan` 和 `StartPlan` 中,控制器直接处理 `ExecuteCount` 的重置逻辑。
|
||||
|
||||
这种设计导致控制器层职责过重,业务逻辑分散,难以维护和测试。
|
||||
|
||||
### Goals / Non-Goals
|
||||
|
||||
#### Goals
|
||||
|
||||
- **创建应用服务层**:引入一个新的 `internal/app/service/plan_service.go` 来封装 `plan` 模块的所有业务逻辑。
|
||||
- **迁移业务逻辑**:将 `plan_controller.go` 中识别出的所有业务规则判断、领域对象创建与转换、对仓库层的直接调用、对
|
||||
`analysisPlanTaskManager` 的协调以及错误处理逻辑,全部迁移到新的 `PlanService` 中。
|
||||
- **简化控制器**:使 `plan_controller.go` 只负责 HTTP 请求处理、参数绑定、调用新的 `PlanService` 方法,并处理服务层返回的
|
||||
DTO。
|
||||
- **统一服务层接口**:`PlanService` 的方法将接收 DTO 作为输入,并返回 DTO 作为输出,实现服务层接口的标准化。
|
||||
|
||||
#### Non-Goals
|
||||
|
||||
- **不修改业务逻辑**:本次重构不涉及任何已有业务规则的变更。所有业务逻辑将原封不动地从控制器迁移到服务层。
|
||||
- **不改变 API 契约**:对外暴露的 API 接口、请求参数和响应结构对最终用户保持不变。
|
||||
- **不改变领域服务**:不对 `internal/domain/scheduler/analysis_plan_task_manager.go` 的接口和实现进行任何修改。
|
||||
- **不改变仓库层接口**:不对 `internal/infra/repository/plan_repository.go` 的接口进行任何修改。
|
||||
|
||||
### Decisions
|
||||
|
||||
- **决策:引入新的应用服务 `PlanService`**
|
||||
- **理由**:这是解决控制器职责过重和分层不清问题的标准做法。`PlanService` 将作为应用层门面,协调 `PlanRepository` 和
|
||||
`AnalysisPlanTaskManager`,并为控制器提供一个清晰、稳定的接口。
|
||||
- **结构**:`PlanService` 将依赖于 `PlanRepository` 和 `AnalysisPlanTaskManager`。
|
||||
|
||||
- **决策:`PlanService` 接口全面采用 DTO**
|
||||
- **具体实现**:接口方法将接收 `dto.CreatePlanRequest`, `dto.UpdatePlanRequest`, `dto.ListPlansQuery` 等请求 DTO,并返回
|
||||
`*dto.PlanResponse`, `*dto.ListPlansResponse` 等响应 DTO。
|
||||
- **理由**:这与 `monitor`、`device` 和 `pig-farm` 模块的重构决策一致,可以确保应用服务层的接口统一、清晰,并与上层(控制器)和下层(领域/仓库)完全解耦。服务层内部将负责
|
||||
`DTO` 到 `models` 的转换以及 `models` 到 `DTO` 的转换。
|
||||
|
||||
- **决策:将控制器中的业务规则判断和错误处理下沉到服务层**
|
||||
- **理由**:控制器应专注于 HTTP 协议相关的职责。所有业务规则的判断(如计划类型、状态检查、ContentType
|
||||
自动判断、执行计数器重置)以及对底层错误的具体判断(如 `gorm.ErrRecordNotFound`
|
||||
)都属于业务逻辑范畴,应由服务层处理。服务层将返回更抽象的业务错误,控制器只需根据这些抽象错误进行统一的 HTTP 响应处理。
|
||||
|
||||
### Risks / Trade-offs
|
||||
|
||||
- **风险:意外修改或丢失现有业务逻辑**
|
||||
- **描述**:在将控制器中分散的业务逻辑迁移到服务层时,存在逻辑被无意中删除、修改或遗漏的风险,尤其是在处理计划状态转换、执行计数器重置和
|
||||
`ContentType` 自动判断等复杂逻辑时。
|
||||
- **缓解措施**:
|
||||
1. **逐行迁移与比对**:在迁移过程中,将控制器中的每一段业务逻辑代码逐行复制到服务层,并仔细比对,确保逻辑的等效性。
|
||||
2. **详细注释**:在服务层中对迁移过来的业务逻辑添加详细注释,解释其来源和作用。
|
||||
3. **回归测试**:在完成重构后,必须进行完整的回归测试,确保所有受影响的 API 端点的行为与重构前完全一致。
|
||||
|
||||
### Migration Plan
|
||||
|
||||
1. **创建 `internal/app/service/plan_service.go` 文件**:
|
||||
- 定义 `PlanService` 接口,包含 `CreatePlan`, `GetPlanByID`, `ListPlans`, `UpdatePlan`, `DeletePlan`, `StartPlan`,
|
||||
`StopPlan` 等方法。
|
||||
- 定义 `planService` 结构体,并实现 `PlanService` 接口。
|
||||
- 在 `planService` 的实现中,将 `plan_controller.go` 中所有相关的业务逻辑(包括 DTO 转换、业务规则判断、对 `planRepo` 和
|
||||
`analysisPlanTaskManager` 的调用、错误处理)精确迁移到对应的方法中。
|
||||
|
||||
2. **修改 `internal/app/controller/plan/plan_controller.go`**:
|
||||
- 更新 `Controller` 结构体,将 `planRepo` 和 `analysisPlanTaskManager` 替换为 `service.PlanService`。
|
||||
- 修改 `NewController` 函数,注入 `service.PlanService`。
|
||||
- 简化所有处理器方法,移除所有业务逻辑,只保留请求参数绑定、调用 `service.PlanService` 方法、错误处理和响应构建。
|
||||
|
||||
3. **修改 `internal/core/component_initializers.go`**:
|
||||
- 在 `AppServices` 结构体中添加 `PlanService service.PlanService` 字段。
|
||||
- 在 `initAppServices` 函数中,初始化 `PlanService` 实例,并将其注入到 `AppServices` 中。
|
||||
|
||||
4. **修改 `internal/app/api/api.go`**:
|
||||
- 更新 `NewAPI` 函数的参数,移除 `planRepository` 和 `analysisTaskManager`,添加 `service.PlanService`。
|
||||
- 更新 `plan.NewController` 的调用,传入新的 `service.PlanService` 依赖。
|
||||
|
||||
### Open Questions
|
||||
|
||||
- 暂无。
|
||||
|
||||
---
|
||||
|
||||
## `user` 模块重构设计
|
||||
|
||||
### Context
|
||||
|
||||
`user_controller.go` 当前直接依赖 `repository.UserRepository`、`token.Service` 和 `domain_notify.Service`
|
||||
,并在其方法内部执行了大量本应属于应用服务层的逻辑,包括:
|
||||
|
||||
- **直接的数据库操作**:调用 `userRepo` 的 `Create`, `FindByUsername`, `FindUserForLogin` 等方法。
|
||||
- **领域模型实例化**:通过 `&models.User{...}` 直接创建数据库模型。
|
||||
- **业务规则验证**:例如在 `CreateUser` 中判断用户名是否重复,在 `Login` 中进行密码验证。
|
||||
- **协调领域服务**:在 `Login` 中协调 `tokenService` 生成 JWT,在 `SendTestNotification` 中协调 `domain_notify.Service`
|
||||
发送测试消息。
|
||||
- **复杂的错误处理**:通过 `errors.Is` 和 `gorm.ErrRecordNotFound` 解析底层错误。
|
||||
- **DTO 转换**:在方法末尾将 `models.User` 转换为 `dto.CreateUserResponse` 或 `dto.LoginResponse`。
|
||||
|
||||
这种设计导致控制器与基础设施层和领域层紧密耦合,违反了分层架构的原则。
|
||||
|
||||
### Goals / Non-Goals
|
||||
|
||||
#### Goals
|
||||
|
||||
- **创建应用服务层**:引入一个新的 `internal/app/service/user_service.go` 来封装业务逻辑。
|
||||
- **迁移业务逻辑**:将上述所有在控制器中识别出的业务逻辑和数据处理任务,全部迁移到新的 `UserService` 中。
|
||||
- **简化控制器**:使 `user_controller.go` 只负责 HTTP 请求处理和对新 `UserService` 的调用。
|
||||
- **保持领域服务纯粹**:确保 `internal/domain/token.Service` 和 `internal/domain/notify.Service` 继续专注于核心领域逻辑,不与
|
||||
DTO 发生耦合。
|
||||
|
||||
#### Non-Goals
|
||||
|
||||
- **不修改业务逻辑**:本次重构不涉及任何已有业务规则的变更。所有业务逻辑将原封不动地从控制器迁移到服务层。
|
||||
- **不改变 API 契约**:对外暴露的 API 接口、请求和响应格式保持不变。
|
||||
- **不改变领域服务**:不对 `domain.token.Service` 和 `domain.notify.Service` 的接口和实现进行任何修改。
|
||||
- **不改变仓库层接口**:不对 `internal/infra/repository/user_repository.go` 的接口进行任何修改。
|
||||
- **不涉及 `ListUserHistory` 方法**:该方法已从重构范围中移除。
|
||||
|
||||
### Decisions
|
||||
|
||||
- **决策:引入新的应用服务 `UserService`**
|
||||
- **理由**:这是解决控制器职责过重和分层不清问题的标准做法。该服务将作为应用层门面,协调 `UserRepository`、
|
||||
`token.Service` 和 `domain_notify.Service`,并为控制器提供一个清晰、稳定的接口。
|
||||
- **结构**:`UserService` 将依赖于 `repository.UserRepository`, `token.Service`, `domain_notify.Service` 和
|
||||
`logs.Logger`。
|
||||
|
||||
- **决策:`UserService` 接口全面采用 DTO**
|
||||
- **具体实现**:接口方法将接收 `dto.CreateUserRequest`, `dto.LoginRequest`, `dto.SendTestNotificationRequest` 等请求
|
||||
DTO,并返回 `*dto.CreateUserResponse`, `*dto.LoginResponse` 等响应 DTO。
|
||||
- **理由**:这与 `monitor`、`device`、`pig-farm` 和 `plan` 模块的重构决策一致,可以确保应用服务层的接口统一、清晰,并与上层(控制器)和下层(领域/仓库)完全解耦。服务层内部将负责
|
||||
DTO 到 `models` 的转换以及 `models` 到 DTO 的转换。
|
||||
|
||||
- **决策:将控制器中的业务规则判断和错误处理下沉到服务层**
|
||||
- **理由**:控制器应专注于 HTTP 协议相关的职责。所有业务规则的判断(如用户名重复检查、密码验证)以及对底层错误的具体判断(如
|
||||
`gorm.ErrRecordNotFound`)都属于业务逻辑范畴,应由服务层处理。服务层将返回更抽象的业务错误,控制器只需根据这些抽象错误进行统一的
|
||||
HTTP 响应处理。
|
||||
|
||||
### Risks / Trade-offs
|
||||
|
||||
- **风险:意外修改或丢失现有业务逻辑**
|
||||
- **描述**:在将控制器中分散的业务逻辑迁移到服务层时,存在逻辑被无意中删除、修改或遗漏的风险,尤其是在处理用户创建、登录和通知发送等复杂逻辑时。
|
||||
- **缓解措施**:
|
||||
1. **逐行迁移与比对**:在迁移过程中,将控制器中的每一段业务逻辑代码逐行复制到服务层,并仔细比对,确保逻辑的等效性。
|
||||
2. **详细注释**:在服务层中对迁移过来的业务逻辑添加详细注释,解释其来源和作用。
|
||||
3. **回归测试**:在完成重构后,必须进行完整的回归测试,确保所有受影响的 API 端点的行为与重构前完全一致。
|
||||
|
||||
### Migration Plan
|
||||
|
||||
1. **创建 `internal/app/service/user_service.go` 文件**:
|
||||
- 定义 `UserService` 接口,包含 `CreateUser`, `Login`, `SendTestNotification` 等方法。
|
||||
- 定义 `userService` 结构体,并实现 `UserService` 接口。
|
||||
- 在 `userService` 的实现中,将 `user_controller.go` 中所有相关的业务逻辑(包括 DTO 转换、业务规则判断、对 `userRepo`、
|
||||
`tokenService` 和 `notifyService` 的调用、错误处理)精确迁移到对应的方法中。
|
||||
|
||||
2. **修改 `internal/app/controller/user/user_controller.go`**:
|
||||
- 更新 `Controller` 结构体,将 `userRepo`, `tokenService`, `notifyService` 替换为 `service.UserService`。
|
||||
- 修改 `NewController` 函数,注入 `service.UserService`。
|
||||
- 简化所有处理器方法,移除所有业务逻辑,只保留请求参数绑定、调用 `service.UserService` 方法、错误处理和响应构建。
|
||||
|
||||
3. **修改 `internal/core/component_initializers.go`**:
|
||||
- 在 `AppServices` 结构体中添加 `UserService service.UserService` 字段。
|
||||
- 在 `initAppServices` 函数中,初始化 `UserService` 实例,并将其注入到 `AppServices` 中。
|
||||
|
||||
4. **修改 `internal/app/api/api.go`**:
|
||||
- 更新 `NewAPI` 函数的参数,移除 `userRepo`, `tokenService`, `notifyService`,添加 `service.UserService`。
|
||||
- 更新 `user.NewController` 的调用,传入新的 `service.UserService` 依赖。
|
||||
|
||||
### Open Questions
|
||||
|
||||
- 暂无。
|
||||
@@ -0,0 +1,46 @@
|
||||
## Why
|
||||
当前项目中,控制器层与服务层、仓库层之间存在严重的领域侵入问题。具体表现为:
|
||||
1. **服务层直接吐出数据库模型:** 导致控制器层直接感知并操作领域模型,增加了控制器与数据持久化细节的耦合。
|
||||
2. **服务层接收数据库对象或仓库层特定结构:** 控制器层直接构建数据库模型或仓库层查询选项并传递给服务层/仓库层,使得服务层接口不够抽象,且控制器承担了不应有的数据转换职责。
|
||||
3. **业务逻辑散落在控制器层:** 控制器层包含了大量的业务规则判断、领域对象的创建与验证、以及对仓库层和领域服务的直接协调,这违反了控制器层应只做数据校验、绑定解析和调用服务层方法的原则,导致业务逻辑分散、难以维护和测试。
|
||||
* **控制器直接进行领域模型内部字段的序列化/反序列化:** 例如,控制器直接对 `req.Properties` 进行 `json.Marshal` 操作,将领域模型的内部结构(如 JSON 字符串存储)暴露给控制器。
|
||||
* **控制器直接实例化领域模型对象:** 控制器直接通过 `&models.Xxx{...}` 实例化领域模型对象,而非通过服务层进行创建。
|
||||
* **控制器通过检查底层(仓库层或服务层)的特定错误类型或错误信息来执行业务判断:** 例如,通过 `strings.Contains(err.Error(), "...")` 或 `errors.Is(err, service.ErrXxx)` 来判断具体的业务错误类型,使得控制器与底层实现细节紧密耦合。
|
||||
|
||||
这些问题导致了代码的紧密耦合、可维护性差、测试困难,并且不利于后续的业务扩展和架构演进。
|
||||
|
||||
## What Changes
|
||||
本次重构旨在解决上述领域侵入问题,明确各层的职责,提升代码质量。主要变更包括:
|
||||
- **服务层接口标准化:** 确保服务层方法只接收 DTO 或基本参数,并只返回 DTO 或业务领域对象(而非数据库模型)。
|
||||
- **控制器层职责收敛:** 控制器层将仅负责请求参数的绑定与校验、调用服务层方法,并将服务层返回的 DTO 转换为 HTTP 响应。所有业务逻辑、领域对象的创建与验证、以及与仓库层的直接交互都将从控制器层移除并下沉到服务层。
|
||||
* **移除控制器中的领域模型内部字段序列化/反序列化逻辑:** 将此类操作下沉到服务层或专门的转换器中。
|
||||
* **移除控制器中直接实例化领域模型对象的逻辑:** 领域模型的创建应通过服务层完成。
|
||||
* **优化控制器中的业务错误处理:** 服务层将返回更抽象的业务错误,控制器层根据这些抽象错误进行统一的 HTTP 响应处理,避免直接依赖仓库层或服务层内部的具体错误类型或错误信息。
|
||||
- **DTO 转换逻辑下沉:** 将数据库模型与 DTO 之间的转换逻辑从控制器层移动到服务层内部或专门的转换器中。
|
||||
- **业务错误处理优化:** 服务层将返回更抽象的业务错误,控制器层根据这些抽象错误进行统一的 HTTP 响应处理,避免直接依赖仓库层或服务层内部的具体错误类型。
|
||||
|
||||
**BREAKING**:本次变更将涉及服务层接口的修改,以及控制器层对服务层调用的调整,可能对依赖这些接口的代码造成影响。
|
||||
|
||||
## Impact
|
||||
- **Affected specs:**
|
||||
- `specs/monitor/spec.md` (如果存在,需要更新服务层返回 DTO 的要求)
|
||||
- `specs/device/spec.md` (如果存在,需要更新服务层返回 DTO 的要求)
|
||||
- `specs/pig-farm/spec.md` (如果存在,需要更新服务层返回 DTO 的要求)
|
||||
- `specs/plan/spec.md` (如果存在,需要更新服务层返回 DTO 的要求)
|
||||
- `specs/user/spec.md` (如果存在,需要更新服务层返回 DTO 的要求)
|
||||
- **Affected code:**
|
||||
- `internal/app/controller/monitor/monitor_controller.go`
|
||||
- `internal/app/controller/device/device_controller.go`
|
||||
- `internal/app/controller/management/pig_farm_controller.go`
|
||||
- `internal/app/controller/plan/plan_controller.go`
|
||||
- `internal/app/controller/user/user_controller.go`
|
||||
- `internal/app/service/monitor_service.go` (及其实现)
|
||||
- `internal/app/service/device_service.go` (及其实现)
|
||||
- `internal/app/service/pig_farm_service.go` (及其实现)
|
||||
- `internal/app/service/plan_service.go` (及其实现)
|
||||
- `internal/app/service/user_service.go` (及其实现)
|
||||
- `internal/infra/repository/*.go` (可能需要调整接口,以适应服务层接收 DTO 的变化)
|
||||
- `internal/infra/models/*.go` (可能需要添加或修改 DTO 转换方法)
|
||||
- `internal/app/dto/*.go` (可能需要添加新的 DTO 或修改现有 DTO 的构造函数)
|
||||
- `internal/core/component_initializers.go`
|
||||
- `internal/app/api/api.go`
|
||||
@@ -0,0 +1,70 @@
|
||||
# 业务逻辑分层重构规范
|
||||
|
||||
## Purpose
|
||||
本规范旨在明确业务逻辑分层重构的目标、变更内容和预期行为,以解决控制器层职责过重、代码耦合严重、可维护性差的问题。通过本次重构,我们将实现各层职责的清晰划分,提升代码质量和可测试性。
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: 服务层接口标准化
|
||||
- **说明**: 服务层方法现在 **MUST** 只接收数据传输对象 (DTO) 或基本参数,并 **MUST** 只返回 DTO 或业务领域对象,不再直接暴露数据库模型。
|
||||
- **理由**: 减少服务层与持久化细节的耦合,提高接口的抽象性和稳定性。
|
||||
- **影响**: 高。所有调用服务层的方法都需要调整。
|
||||
- **受影响的模块**: `monitor`, `device`, `pig-farm`, `plan`, `user`。
|
||||
|
||||
#### Scenario: 服务层方法接收 DTO 作为输入
|
||||
- **假如**: `UserService` 的 `CreateUser` 方法被调用。
|
||||
- **当**: `CreateUser` 方法接收 `dto.CreateUserRequest` 作为参数。
|
||||
- **那么**: `UserService` 内部负责将 `dto.CreateUserRequest` 转换为 `models.User` 进行处理。
|
||||
|
||||
#### Scenario: 服务层方法返回 DTO 作为输出
|
||||
- **假如**: `UserService` 的 `CreateUser` 方法执行成功。
|
||||
- **当**: `CreateUser` 方法返回 `*dto.CreateUserResponse`。
|
||||
- **那么**: 调用方可以直接使用 `dto.CreateUserResponse`,无需进行额外的模型转换。
|
||||
|
||||
### Requirement: 控制器层职责收敛
|
||||
- **说明**: 控制器层现在 **MUST** 仅负责 HTTP 请求的参数绑定与校验、调用服务层方法,并将服务层返回的 DTO 转换为 HTTP 响应。所有业务逻辑、领域对象的创建与验证、以及与仓库层的直接交互都 **MUST** 从控制器层移除并下沉到服务层。
|
||||
- **理由**: 遵循“关注点分离”原则,使控制器层专注于 HTTP 协议处理,提高代码的可维护性和可测试性。
|
||||
- **影响**: 高。所有控制器方法都需要大幅简化。
|
||||
- **受影响的模块**: `monitor`, `device`, `pig-farm`, `plan`, `user`。
|
||||
|
||||
#### Scenario: 控制器不再直接进行领域模型内部字段的序列化/反序列化
|
||||
- **假如**: `DeviceController` 的 `CreateDevice` 方法被调用。
|
||||
- **当**: `CreateDevice` 方法不再包含 `json.Marshal` 或 `json.Unmarshal` 等操作来处理 `Properties` 等字段。
|
||||
- **那么**: 这些序列化/反序列化逻辑已下沉到 `DeviceService` 中。
|
||||
|
||||
#### Scenario: 控制器不再直接实例化领域模型对象
|
||||
- **假如**: `UserController` 的 `CreateUser` 方法被调用。
|
||||
- **当**: `CreateUser` 方法不再包含 `&models.User{...}` 这样的代码。
|
||||
- **那么**: 领域模型的创建已通过 `UserService` 完成。
|
||||
|
||||
#### Scenario: 控制器不再直接调用仓库层方法
|
||||
- **假如**: `PlanController` 的 `ListPlans` 方法被调用。
|
||||
- **当**: `ListPlans` 方法不再直接调用 `planRepo.ListPlans`。
|
||||
- **那么**: `PlanService` 负责协调 `PlanRepository`。
|
||||
|
||||
#### Scenario: 控制器不再直接进行业务规则判断
|
||||
- **假如**: `PlanController` 的 `UpdatePlan` 方法被调用。
|
||||
- **当**: `UpdatePlan` 方法不再包含对计划类型、状态或 `ContentType` 的直接判断逻辑。
|
||||
- **那么**: 这些业务规则判断已下沉到 `PlanService` 中。
|
||||
|
||||
### Requirement: DTO 转换逻辑下沉
|
||||
- **说明**: 数据库模型与 DTO 之间的转换逻辑 **MUST** 从控制器层移动到服务层内部或专门的转换器中。
|
||||
- **理由**: 确保数据转换逻辑与业务逻辑紧密结合,避免控制器层承担不必要的职责。
|
||||
- **影响**: 中。主要影响数据流转和转换点。
|
||||
- **受影响的模块**: `monitor`, `device`, `pig-farm`, `plan`, `user`。
|
||||
|
||||
#### Scenario: 服务层负责将数据库模型转换为响应 DTO
|
||||
- **假如**: `PigFarmService` 的 `GetPigHouseByID` 方法从 `repository` 获取到 `models.PigHouse`。
|
||||
- **当**: `GetPigHouseByID` 方法在返回前将 `models.PigHouse` 转换为 `dto.PigHouseResponse`。
|
||||
- **那么**: 控制器直接接收 `dto.PigHouseResponse`。
|
||||
|
||||
### Requirement: 业务错误处理优化
|
||||
- **说明**: 服务层现在 **MUST** 返回更抽象的业务错误,控制器层 **MUST** 根据这些抽象错误进行统一的 HTTP 响应处理,避免直接依赖仓库层或服务层内部的具体错误类型或错误信息。
|
||||
- **理由**: 提高错误处理的一致性和可维护性,解耦控制器与底层错误实现。
|
||||
- **影响**: 中。影响错误处理流程。
|
||||
- **受影响的模块**: `monitor`, `device`, `pig-farm`, `plan`, `user`。
|
||||
|
||||
#### Scenario: 服务层返回抽象业务错误
|
||||
- **假如**: `UserService` 的 `CreateUser` 方法因用户名重复而失败。
|
||||
- **当**: `UserService` 返回一个表示“用户名已存在”的抽象错误(例如自定义错误类型或包装后的错误)。
|
||||
- **那么**: `UserController` 接收到此抽象错误后,可以统一转换为相应的 HTTP 状态码和错误信息,而无需解析底层 `gorm.ErrDuplicatedKey` 等具体错误。
|
||||
@@ -0,0 +1,123 @@
|
||||
## 1. 准备工作
|
||||
|
||||
- [x] 1.1 阅读并理解 `openspec/changes/refactor-business-logic-layering/proposal.md`。
|
||||
- [x] 1.2 阅读并理解 `openspec/changes/refactor-business-logic-layering/design.md`。
|
||||
- [x] 1.3 阅读并理解 'AGENTS.md'
|
||||
|
||||
## 2. 统一服务层接口输入输出为 DTO
|
||||
|
||||
### 2.1 `monitor` 模块
|
||||
|
||||
- [x] 2.1.1 **修改 `internal/app/service/monitor_service.go`:**
|
||||
- [x] 将所有 `List...` 方法的 `opts repository.ListOptions` 参数替换为服务层自定义的查询 DTO 或一系列基本参数。
|
||||
- [x] 将所有 `List...` 方法的返回值 `[]models.Xxx` 替换为 `[]dto.XxxResponse`。
|
||||
- [x] 调整 `List...` 方法的实现,在服务层内部将服务层查询 DTO 转换为 `repository.ListOptions`。
|
||||
- [x] 调整 `List...` 方法的实现,在服务层内部将 `repository` 返回的 `models` 对象转换为 `dto.XxxResponse`。
|
||||
- [x] 2.1.2 **修改 `internal/app/controller/monitor/monitor_controller.go`:**
|
||||
- [x] 移除控制器中构建 `repository.ListOptions` 的逻辑。
|
||||
- [x] 移除控制器中将 `models` 转换为 `dto.NewList...Response` 的逻辑。
|
||||
- [x] 移除控制器中直接使用 `models` 进行枚举类型转换的逻辑,将其下沉到服务层或 DTO 转换逻辑中。
|
||||
- [x] 调整服务层方法的调用,使其接收新的服务层查询 DTO 或基本参数,并直接处理服务层返回的 `dto.XxxResponse`。
|
||||
|
||||
### 2.2 `device` 模块
|
||||
|
||||
- [x] 2.2.1 **创建并修改 `internal/app/service/device_service.go`:**
|
||||
- [x] 定义 `DeviceService` 接口,包含 `CreateDevice`, `UpdateDevice`, `CreateAreaController`, `UpdateAreaController`,
|
||||
`CreateDeviceTemplate`, `UpdateDeviceTemplate`, `GetDevice`, `ListDevices`, `GetAreaController`,
|
||||
`ListAreaControllers`, `GetDeviceTemplate`, `ListDeviceTemplates`, `ManualControl` 等方法。
|
||||
- [x] 为 `CreateDevice`, `UpdateDevice`, `CreateAreaController`, `UpdateAreaController`, `CreateDeviceTemplate`,
|
||||
`UpdateDeviceTemplate`, `ManualControl` 方法定义并接收 DTO 作为输入。
|
||||
- [x] 将 `GetDevice`, `ListDevices`, `GetAreaController`, `ListAreaControllers`, `GetDeviceTemplate`,
|
||||
`ListDeviceTemplates` 方法的返回值 `models.Xxx` 或 `[]models.Xxx` 替换为 `dto.XxxResponse` 或 `[]dto.XxxResponse`。
|
||||
- [x] 实现 `DeviceService` 接口。
|
||||
- [x] 在此服务层内部将输入 DTO 转换为 `models` 对象。
|
||||
- [x] 在此服务层内部将 `repository` 或 `domain` 层返回的 `models` 对象转换为 `dto.XxxResponse`。
|
||||
- [x] 将控制器中 `SelfCheck()` 验证逻辑移入此服务层。
|
||||
- [x] 将控制器中 `Properties`, `Commands`, `Values` 的 JSON 序列化逻辑移入此服务层。
|
||||
- [x] 将控制器中 `ManualControl` 的业务逻辑(如动作映射)移入此服务层。
|
||||
- [x] 将控制器中直接调用 `repository` 方法的逻辑移入此服务层。
|
||||
- [x] 将控制器中通过检查 `repository` 错误信息处理业务规则的逻辑移入此服务层。
|
||||
- [x] 调整此服务层对 `internal/domain/device.Service` 的调用,确保传递的是 `models` 或领域对象,而不是 DTO。
|
||||
- [x] 2.2.2 **修改 `internal/app/controller/device/device_controller.go`:**
|
||||
- [x] 引入并使用新创建的 `internal/app/service.DeviceService`。
|
||||
- [x] 移除控制器中直接创建 `models.Device`, `models.AreaController`, `models.DeviceTemplate` 对象的逻辑。
|
||||
- [x] 移除控制器中直接调用 `SelfCheck()` 的逻辑。
|
||||
- [x] 移除控制器中直接调用 `repository` 方法的逻辑。
|
||||
- [x] 移除控制器中通过检查 `repository` 错误信息处理业务规则的逻辑。
|
||||
- [x] 移除控制器中 `Properties`, `Commands`, `Values` 的 JSON 序列化逻辑。
|
||||
- [x] 调整服务层方法的调用,使其接收新的服务层输入 DTO 或基本参数,并直接处理服务层返回的 `dto.XxxResponse`。
|
||||
- [x] 2.2.3 **修改 `internal/core/component_initializers.go`**:创建并提供新的 `DeviceService`。
|
||||
- [x] 2.2.4 **修改 `internal/app/api/api.go`**:更新 `DeviceController` 的依赖注入。
|
||||
|
||||
### 2.3 `pig-farm` 模块
|
||||
|
||||
- [x] 2.3.1 **修改 `internal/app/service/pig_farm_service.go`:**
|
||||
- [x] 将 `CreatePigHouse`, `GetPigHouseByID`, `ListPigHouses`, `UpdatePigHouse`, `CreatePen`, `GetPenByID`,
|
||||
`ListPens`, `UpdatePen`, `UpdatePenStatus` 方法的返回值 `models.Xxx` 或 `[]models.Xxx` 替换为 `dto.XxxResponse` 或
|
||||
`[]dto.XxxResponse`。
|
||||
- [x] 在服务层内部将 `repository` 返回的 `models` 对象转换为 `dto.XxxResponse`。
|
||||
- [x] 将控制器中处理服务层特定业务错误(如 `service.ErrHouseNotFound`)的逻辑移入服务层,服务层应返回更抽象的错误或直接返回
|
||||
DTO。
|
||||
- [x] 2.3.2 **修改 `internal/app/controller/management/pig_farm_controller.go`:**
|
||||
- [x] 移除控制器中手动将领域实体转换为 DTO 的逻辑。
|
||||
- [x] 移除控制器中直接处理服务层特定业务错误类型的逻辑。
|
||||
- [x] 调整服务层方法的调用,使其直接处理服务层返回的 `dto.XxxResponse`。
|
||||
|
||||
### 2.4 `plan` 模块
|
||||
|
||||
- [x] 2.4.1 **创建并修改 `internal/app/service/plan_service.go`:**
|
||||
- [x] 定义 `PlanService` 接口,包含 `CreatePlan`, `GetPlanByID`, `ListPlans`, `UpdatePlan`, `DeletePlan`,
|
||||
`StartPlan`, `StopPlan` 等方法。
|
||||
- [x] 为 `CreatePlan`, `UpdatePlan` 方法定义并接收 DTO 作为输入。
|
||||
- [x] 将 `GetPlanByID`, `ListPlans` 方法的返回值 `models.Plan` 或 `[]models.Plan` 替换为 `dto.PlanResponse` 或
|
||||
`[]dto.PlanResponse`。
|
||||
- [x] 调整 `ListPlans` 方法的 `opts repository.ListPlansOptions` 参数替换为服务层自定义的查询 DTO 或一系列基本参数。
|
||||
- [x] 调整 `DeletePlan`, `StartPlan`, `StopPlan` 方法,使其接收 DTO 或基本参数,并封装所有业务逻辑。
|
||||
- [x] 实现 `PlanService` 接口。
|
||||
- [x] 在服务层内部将输入 DTO 转换为 `models` 对象。
|
||||
- [x] 在服务层内部将 `repository` 返回的 `models` 对象转换为 `dto.XxxResponse`。
|
||||
- [x] 将 `internal/app/controller/plan/plan_controller.go` 中所有的业务规则判断(计划类型检查、状态检查、执行计数器重置、ContentType
|
||||
自动判断)移入服务层。
|
||||
- [x] 将 `internal/app/controller/plan/plan_controller.go` 中对 `repository` 方法的直接调用移入服务层。
|
||||
- [x] 将 `internal/app/controller/plan/plan_controller.go` 中对 `analysisPlanTaskManager` 的协调移入服务层。
|
||||
- [x] 将 `internal/app/controller/plan/plan_controller.go` 中处理仓库层特有错误(`gorm.ErrRecordNotFound`)的逻辑移入服务层。
|
||||
- [x] 2.4.2 **修改 `internal/app/controller/plan/plan_controller.go`:**
|
||||
- [x] 引入并使用新创建的 `plan_service`。
|
||||
- [x] 移除控制器中直接创建 `models.Plan` 对象和 `repository.ListPlansOptions` 的逻辑。
|
||||
- [x] 移除控制器中所有的业务规则判断。
|
||||
- [x] 移除控制器中直接调用 `repository` 方法的逻辑。
|
||||
- [x] 移除控制器中直接协调 `analysisPlanTaskManager` 的逻辑。
|
||||
- [x] 移除控制器中直接处理仓库层特有错误的逻辑。
|
||||
- [x] 调整服务层方法的调用,使其接收新的服务层输入 DTO 或基本参数,并直接处理服务层返回的 `dto.XxxResponse`。
|
||||
- [x] 2.4.3 **修改 `internal/core/component_initializers.go`**:创建并提供新的 `PlanService`。
|
||||
- [x] 2.4.4 **修改 `internal/app/api/api.go`**:更新 `PlanController` 的依赖注入。
|
||||
|
||||
### 2.5 `user` 模块
|
||||
|
||||
- [x] 2.5.1 **创建并修改 `internal/app/service/user_service.go`:**
|
||||
- [x] 定义 `UserService` 接口,包含 `CreateUser`, `Login`, `SendTestNotification` 等方法。
|
||||
- [x] 为 `CreateUser`, `Login` 方法定义并接收 DTO 作为输入。
|
||||
- [x] 将 `CreateUser`, `Login` 方法的返回值 `models.User` 替换为 `dto.CreateUserResponse` 或 `dto.LoginResponse`。
|
||||
- [x] 调整 `SendTestNotification` 方法,使其接收 DTO 或基本参数,并封装所有业务逻辑。
|
||||
- [x] 实现 `UserService` 接口。
|
||||
- [x] 在服务层内部将输入 DTO 转换为 `models` 对象。
|
||||
- [x] 在服务层内部将 `repository` 返回的 `models` 对象转换为 `dto.XxxResponse`。
|
||||
- [x] 将 `CreateUser` 中处理用户名重复的业务逻辑从控制器移入服务层。
|
||||
- [x] 将 `Login` 中进行密码验证的业务逻辑和协调 `tokenService` 的逻辑从控制器移入服务层。
|
||||
- [x] 将 `SendTestNotification` 中调用 `domain_notify.Service` 的逻辑移入服务层。
|
||||
- [x] 将控制器中通过检查底层(仓库层或服务层)的特定错误类型或错误信息来执行业务判断的逻辑移入服务层。
|
||||
- [x] 2.5.2 **修改 `internal/app/controller/user/user_controller.go`:**
|
||||
- [x] 引入并使用新创建的 `user_service`。
|
||||
- [x] 移除控制器中直接创建 `models.User` 对象的逻辑。
|
||||
- [x] 移除控制器中处理用户名重复的业务逻辑。
|
||||
- [x] 移除控制器中进行密码验证的业务逻辑和协调 `tokenService` 的逻辑。
|
||||
- [x] 移除控制器中通过检查底层(仓库层或服务层)的特定错误类型或错误信息来执行业务判断的逻辑。
|
||||
- [x] 调整服务层方法的调用,使其接收新的服务层输入 DTO 或基本参数,并直接处理服务层返回的 `dto.XxxResponse`。
|
||||
- [x] 2.5.2 **修改 `internal/core/component_initializers.go`**:创建并提供新的 `UserService`。
|
||||
- [x] 2.5.3 **修改 `internal/app/api/api.go`**:更新 `UserController` 的依赖注入。
|
||||
|
||||
## 3. 验证与测试
|
||||
|
||||
- [x] 3.1 运行所有单元测试和集成测试,确保重构没有引入新的问题。
|
||||
- [x] 3.2 针对受影响的 API 接口进行手动测试,验证功能是否正常。
|
||||
- [x] 3.3 确保日志输出和审计记录仍然准确无误.
|
||||
69
openspec/specs/business-logic-layering/spec.md
Normal file
69
openspec/specs/business-logic-layering/spec.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# business-logic-layering Specification
|
||||
|
||||
## Purpose
|
||||
TBD - created by archiving change refactor-business-logic-layering. Update Purpose after archive.
|
||||
## Requirements
|
||||
### Requirement: 服务层接口标准化
|
||||
- **说明**: 服务层方法现在 **MUST** 只接收数据传输对象 (DTO) 或基本参数,并 **MUST** 只返回 DTO 或业务领域对象,不再直接暴露数据库模型。
|
||||
- **理由**: 减少服务层与持久化细节的耦合,提高接口的抽象性和稳定性。
|
||||
- **影响**: 高。所有调用服务层的方法都需要调整。
|
||||
- **受影响的模块**: `monitor`, `device`, `pig-farm`, `plan`, `user`。
|
||||
|
||||
#### Scenario: 服务层方法接收 DTO 作为输入
|
||||
- **假如**: `UserService` 的 `CreateUser` 方法被调用。
|
||||
- **当**: `CreateUser` 方法接收 `dto.CreateUserRequest` 作为参数。
|
||||
- **那么**: `UserService` 内部负责将 `dto.CreateUserRequest` 转换为 `models.User` 进行处理。
|
||||
|
||||
#### Scenario: 服务层方法返回 DTO 作为输出
|
||||
- **假如**: `UserService` 的 `CreateUser` 方法执行成功。
|
||||
- **当**: `CreateUser` 方法返回 `*dto.CreateUserResponse`。
|
||||
- **那么**: 调用方可以直接使用 `dto.CreateUserResponse`,无需进行额外的模型转换。
|
||||
|
||||
### Requirement: 控制器层职责收敛
|
||||
- **说明**: 控制器层现在 **MUST** 仅负责 HTTP 请求的参数绑定与校验、调用服务层方法,并将服务层返回的 DTO 转换为 HTTP 响应。所有业务逻辑、领域对象的创建与验证、以及与仓库层的直接交互都 **MUST** 从控制器层移除并下沉到服务层。
|
||||
- **理由**: 遵循“关注点分离”原则,使控制器层专注于 HTTP 协议处理,提高代码的可维护性和可测试性。
|
||||
- **影响**: 高。所有控制器方法都需要大幅简化。
|
||||
- **受影响的模块**: `monitor`, `device`, `pig-farm`, `plan`, `user`。
|
||||
|
||||
#### Scenario: 控制器不再直接进行领域模型内部字段的序列化/反序列化
|
||||
- **假如**: `DeviceController` 的 `CreateDevice` 方法被调用。
|
||||
- **当**: `CreateDevice` 方法不再包含 `json.Marshal` 或 `json.Unmarshal` 等操作来处理 `Properties` 等字段。
|
||||
- **那么**: 这些序列化/反序列化逻辑已下沉到 `DeviceService` 中。
|
||||
|
||||
#### Scenario: 控制器不再直接实例化领域模型对象
|
||||
- **假如**: `UserController` 的 `CreateUser` 方法被调用。
|
||||
- **当**: `CreateUser` 方法不再包含 `&models.User{...}` 这样的代码。
|
||||
- **那么**: 领域模型的创建已通过 `UserService` 完成。
|
||||
|
||||
#### Scenario: 控制器不再直接调用仓库层方法
|
||||
- **假如**: `PlanController` 的 `ListPlans` 方法被调用。
|
||||
- **当**: `ListPlans` 方法不再直接调用 `planRepo.ListPlans`。
|
||||
- **那么**: `PlanService` 负责协调 `PlanRepository`。
|
||||
|
||||
#### Scenario: 控制器不再直接进行业务规则判断
|
||||
- **假如**: `PlanController` 的 `UpdatePlan` 方法被调用。
|
||||
- **当**: `UpdatePlan` 方法不再包含对计划类型、状态或 `ContentType` 的直接判断逻辑。
|
||||
- **那么**: 这些业务规则判断已下沉到 `PlanService` 中。
|
||||
|
||||
### Requirement: DTO 转换逻辑下沉
|
||||
- **说明**: 数据库模型与 DTO 之间的转换逻辑 **MUST** 从控制器层移动到服务层内部或专门的转换器中。
|
||||
- **理由**: 确保数据转换逻辑与业务逻辑紧密结合,避免控制器层承担不必要的职责。
|
||||
- **影响**: 中。主要影响数据流转和转换点。
|
||||
- **受影响的模块**: `monitor`, `device`, `pig-farm`, `plan`, `user`。
|
||||
|
||||
#### Scenario: 服务层负责将数据库模型转换为响应 DTO
|
||||
- **假如**: `PigFarmService` 的 `GetPigHouseByID` 方法从 `repository` 获取到 `models.PigHouse`。
|
||||
- **当**: `GetPigHouseByID` 方法在返回前将 `models.PigHouse` 转换为 `dto.PigHouseResponse`。
|
||||
- **那么**: 控制器直接接收 `dto.PigHouseResponse`。
|
||||
|
||||
### Requirement: 业务错误处理优化
|
||||
- **说明**: 服务层现在 **MUST** 返回更抽象的业务错误,控制器层 **MUST** 根据这些抽象错误进行统一的 HTTP 响应处理,避免直接依赖仓库层或服务层内部的具体错误类型或错误信息。
|
||||
- **理由**: 提高错误处理的一致性和可维护性,解耦控制器与底层错误实现。
|
||||
- **影响**: 中。影响错误处理流程。
|
||||
- **受影响的模块**: `monitor`, `device`, `pig-farm`, `plan`, `user`。
|
||||
|
||||
#### Scenario: 服务层返回抽象业务错误
|
||||
- **假如**: `UserService` 的 `CreateUser` 方法因用户名重复而失败。
|
||||
- **当**: `UserService` 返回一个表示“用户名已存在”的抽象错误(例如自定义错误类型或包装后的错误)。
|
||||
- **那么**: `UserController` 接收到此抽象错误后,可以统一转换为相应的 HTTP 状态码和错误信息,而无需解析底层 `gorm.ErrDuplicatedKey` 等具体错误。
|
||||
|
||||
Reference in New Issue
Block a user