添加区域主控相关路由

This commit is contained in:
2025-09-30 15:25:07 +08:00
parent 5d6fd315e3
commit 5022a2be1f
7 changed files with 1694 additions and 321 deletions

View File

@@ -15,6 +15,204 @@ const docTemplate = `{
"host": "{{.Host}}", "host": "{{.Host}}",
"basePath": "{{.BasePath}}", "basePath": "{{.BasePath}}",
"paths": { "paths": {
"/api/v1/area-controllers": {
"get": {
"description": "获取系统中所有区域主控的列表",
"produces": [
"application/json"
],
"tags": [
"区域主控管理"
],
"summary": "获取所有区域主控列表",
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/controller.Response"
},
{
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/device.AreaControllerResponse"
}
}
}
}
]
}
}
}
},
"post": {
"description": "根据提供的信息创建一个新区域主控",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"区域主控管理"
],
"summary": "创建新区域主控",
"parameters": [
{
"description": "区域主控信息",
"name": "areaController",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/device.CreateAreaControllerRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/controller.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/device.AreaControllerResponse"
}
}
}
]
}
}
}
}
},
"/api/v1/area-controllers/{id}": {
"get": {
"description": "根据ID获取单个区域主控的详细信息",
"produces": [
"application/json"
],
"tags": [
"区域主控管理"
],
"summary": "获取区域主控信息",
"parameters": [
{
"type": "string",
"description": "区域主控ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/controller.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/device.AreaControllerResponse"
}
}
}
]
}
}
}
},
"put": {
"description": "根据ID更新一个已存在的区域主控信息",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"区域主控管理"
],
"summary": "更新区域主控信息",
"parameters": [
{
"type": "string",
"description": "区域主控ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "要更新的区域主控信息",
"name": "areaController",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/device.UpdateAreaControllerRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/controller.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/device.AreaControllerResponse"
}
}
}
]
}
}
}
},
"delete": {
"description": "根据ID删除一个区域主控软删除",
"produces": [
"application/json"
],
"tags": [
"区域主控管理"
],
"summary": "删除区域主控",
"parameters": [
{
"type": "string",
"description": "区域主控ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/controller.Response"
}
}
}
}
},
"/api/v1/devices": { "/api/v1/devices": {
"get": { "get": {
"description": "获取系统中所有设备的列表", "description": "获取系统中所有设备的列表",
@@ -381,7 +579,7 @@ const docTemplate = `{
} }
}, },
"delete": { "delete": {
"description": "根据计划ID删除计划。", "description": "根据计划ID删除计划。(软删除)",
"produces": [ "produces": [
"application/json" "application/json"
], ],
@@ -514,7 +712,7 @@ const docTemplate = `{
}, },
"/api/v1/users/login": { "/api/v1/users/login": {
"post": { "post": {
"description": "用户使用用户名和密码登录,成功后返回 JWT 令牌。", "description": "用户可以使用用户名、邮箱、手机号、微信号或飞书账号进行登录,成功后返回 JWT 令牌。",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -557,6 +755,67 @@ const docTemplate = `{
} }
} }
} }
},
"/api/v1/users/{id}/history": {
"get": {
"description": "根据用户ID分页获取该用户的操作审计日志。",
"produces": [
"application/json"
],
"tags": [
"用户管理"
],
"summary": "获取指定用户的操作历史",
"parameters": [
{
"type": "integer",
"description": "用户ID",
"name": "id",
"in": "path",
"required": true
},
{
"type": "integer",
"default": 1,
"description": "页码",
"name": "page",
"in": "query"
},
{
"type": "integer",
"default": 10,
"description": "每页大小",
"name": "page_size",
"in": "query"
},
{
"type": "string",
"description": "按操作类型过滤",
"name": "action_type",
"in": "query"
}
],
"responses": {
"200": {
"description": "业务码为200代表成功获取",
"schema": {
"allOf": [
{
"$ref": "#/definitions/controller.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/user.ListHistoryResponse"
}
}
}
]
}
}
}
}
} }
}, },
"definitions": { "definitions": {
@@ -565,7 +824,11 @@ const docTemplate = `{
"properties": { "properties": {
"code": { "code": {
"description": "业务状态码", "description": "业务状态码",
"type": "integer" "allOf": [
{
"$ref": "#/definitions/controller.ResponseCode"
}
]
}, },
"data": { "data": {
"description": "业务数据" "description": "业务数据"
@@ -576,63 +839,50 @@ const docTemplate = `{
} }
} }
}, },
"device.CreateDeviceRequest": { "controller.ResponseCode": {
"type": "object", "type": "integer",
"required": [ "enum": [
"name", 2000,
"type" 2001,
4000,
4001,
4004,
4009,
5000,
5003
], ],
"properties": { "x-enum-comments": {
"location": { "CodeBadRequest": "请求参数错误",
"type": "string" "CodeConflict": "资源冲突",
}, "CodeCreated": "创建成功",
"name": { "CodeInternalError": "服务器内部错误",
"type": "string" "CodeNotFound": "资源未找到",
}, "CodeServiceUnavailable": "服务不可用",
"parent_id": { "CodeSuccess": "操作成功",
"type": "integer" "CodeUnauthorized": "未授权"
}, },
"properties": { "x-enum-descriptions": [
"type": "object", "操作成功",
"additionalProperties": true "创建成功",
}, "请求参数错误",
"sub_type": { "未授权",
"$ref": "#/definitions/models.DeviceSubType" "资源未找到",
}, "资源冲突",
"type": { "服务器内部错误",
"$ref": "#/definitions/models.DeviceType" "服务不可用"
}
}
},
"device.UpdateDeviceRequest": {
"type": "object",
"required": [
"name",
"type"
], ],
"properties": { "x-enum-varnames": [
"location": { "CodeSuccess",
"type": "string" "CodeCreated",
}, "CodeBadRequest",
"name": { "CodeUnauthorized",
"type": "string" "CodeNotFound",
}, "CodeConflict",
"parent_id": { "CodeInternalError",
"type": "integer" "CodeServiceUnavailable"
}, ]
"properties": {
"type": "object",
"additionalProperties": true
},
"sub_type": {
"$ref": "#/definitions/models.DeviceSubType"
},
"type": {
"$ref": "#/definitions/models.DeviceType"
}
}
}, },
"git_huangwc_com_pig_pig-farm-controller_internal_app_controller_device.DeviceResponse": { "device.AreaControllerResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
"created_at": { "created_at": {
@@ -647,57 +897,152 @@ const docTemplate = `{
"name": { "name": {
"type": "string" "type": "string"
}, },
"parent_id": { "network_id": {
"type": "integer" "type": "string"
}, },
"properties": { "properties": {
"type": "object", "type": "object",
"additionalProperties": true "additionalProperties": true
}, },
"sub_type": { "status": {
"$ref": "#/definitions/models.DeviceSubType" "type": "string"
},
"type": {
"$ref": "#/definitions/models.DeviceType"
}, },
"updated_at": { "updated_at": {
"type": "string" "type": "string"
} }
} }
}, },
"models.DeviceSubType": { "device.CreateAreaControllerRequest": {
"type": "string", "type": "object",
"enum": [ "required": [
"", "name",
"temperature", "network_id"
"humidity",
"ammonia",
"weight",
"feed_valve",
"fan",
"water_curtain"
], ],
"x-enum-varnames": [ "properties": {
"SubTypeNone", "location": {
"SubTypeSensorTemp", "type": "string"
"SubTypeSensorHumidity", },
"SubTypeSensorAmmonia", "name": {
"SubTypeSensorWeight", "type": "string"
"SubTypeValveFeed", },
"SubTypeFan", "network_id": {
"SubTypeWaterCurtain" "type": "string"
] },
"properties": {
"type": "object",
"additionalProperties": true
}
}
}, },
"models.DeviceType": { "device.CreateDeviceRequest": {
"type": "string", "type": "object",
"enum": [ "required": [
"area_controller", "area_controller_id",
"device" "device_template_id",
"name"
], ],
"x-enum-varnames": [ "properties": {
"DeviceTypeAreaController", "area_controller_id": {
"DeviceTypeDevice" "type": "integer"
] },
"device_template_id": {
"type": "integer"
},
"location": {
"type": "string"
},
"name": {
"type": "string"
},
"properties": {
"type": "object",
"additionalProperties": true
}
}
},
"device.UpdateAreaControllerRequest": {
"type": "object",
"required": [
"name",
"network_id"
],
"properties": {
"location": {
"type": "string"
},
"name": {
"type": "string"
},
"network_id": {
"type": "string"
},
"properties": {
"type": "object",
"additionalProperties": true
}
}
},
"device.UpdateDeviceRequest": {
"type": "object",
"required": [
"area_controller_id",
"device_template_id",
"name"
],
"properties": {
"area_controller_id": {
"type": "integer"
},
"device_template_id": {
"type": "integer"
},
"location": {
"type": "string"
},
"name": {
"type": "string"
},
"properties": {
"type": "object",
"additionalProperties": true
}
}
},
"git_huangwc_com_pig_pig-farm-controller_internal_app_controller_device.DeviceResponse": {
"type": "object",
"properties": {
"area_controller_id": {
"type": "integer"
},
"area_controller_name": {
"type": "string"
},
"created_at": {
"type": "string"
},
"device_template_id": {
"type": "integer"
},
"device_template_name": {
"type": "string"
},
"id": {
"type": "integer"
},
"location": {
"type": "string"
},
"name": {
"type": "string"
},
"properties": {
"type": "object",
"additionalProperties": true
},
"updated_at": {
"type": "string"
}
}
}, },
"models.PlanContentType": { "models.PlanContentType": {
"type": "string", "type": "string",
@@ -1075,16 +1420,24 @@ const docTemplate = `{
} }
} }
}, },
"user.LoginRequest": { "user.HistoryResponse": {
"type": "object", "type": "object",
"required": [
"password",
"username"
],
"properties": { "properties": {
"password": { "action_type": {
"type": "string", "type": "string",
"example": "password123" "example": "更新设备"
},
"description": {
"type": "string",
"example": "设备更新成功"
},
"target_resource": {},
"time": {
"type": "string"
},
"user_id": {
"type": "integer",
"example": 101
}, },
"username": { "username": {
"type": "string", "type": "string",
@@ -1092,6 +1445,39 @@ const docTemplate = `{
} }
} }
}, },
"user.ListHistoryResponse": {
"type": "object",
"properties": {
"history": {
"type": "array",
"items": {
"$ref": "#/definitions/user.HistoryResponse"
}
},
"total": {
"type": "integer",
"example": 100
}
}
},
"user.LoginRequest": {
"type": "object",
"required": [
"identifier",
"password"
],
"properties": {
"identifier": {
"description": "Identifier 可以是用户名、邮箱、手机号、微信号或飞书账号",
"type": "string",
"example": "testuser"
},
"password": {
"type": "string",
"example": "password123"
}
}
},
"user.LoginResponse": { "user.LoginResponse": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@@ -4,6 +4,204 @@
"contact": {} "contact": {}
}, },
"paths": { "paths": {
"/api/v1/area-controllers": {
"get": {
"description": "获取系统中所有区域主控的列表",
"produces": [
"application/json"
],
"tags": [
"区域主控管理"
],
"summary": "获取所有区域主控列表",
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/controller.Response"
},
{
"type": "object",
"properties": {
"data": {
"type": "array",
"items": {
"$ref": "#/definitions/device.AreaControllerResponse"
}
}
}
}
]
}
}
}
},
"post": {
"description": "根据提供的信息创建一个新区域主控",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"区域主控管理"
],
"summary": "创建新区域主控",
"parameters": [
{
"description": "区域主控信息",
"name": "areaController",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/device.CreateAreaControllerRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/controller.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/device.AreaControllerResponse"
}
}
}
]
}
}
}
}
},
"/api/v1/area-controllers/{id}": {
"get": {
"description": "根据ID获取单个区域主控的详细信息",
"produces": [
"application/json"
],
"tags": [
"区域主控管理"
],
"summary": "获取区域主控信息",
"parameters": [
{
"type": "string",
"description": "区域主控ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/controller.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/device.AreaControllerResponse"
}
}
}
]
}
}
}
},
"put": {
"description": "根据ID更新一个已存在的区域主控信息",
"consumes": [
"application/json"
],
"produces": [
"application/json"
],
"tags": [
"区域主控管理"
],
"summary": "更新区域主控信息",
"parameters": [
{
"type": "string",
"description": "区域主控ID",
"name": "id",
"in": "path",
"required": true
},
{
"description": "要更新的区域主控信息",
"name": "areaController",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/device.UpdateAreaControllerRequest"
}
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"allOf": [
{
"$ref": "#/definitions/controller.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/device.AreaControllerResponse"
}
}
}
]
}
}
}
},
"delete": {
"description": "根据ID删除一个区域主控软删除",
"produces": [
"application/json"
],
"tags": [
"区域主控管理"
],
"summary": "删除区域主控",
"parameters": [
{
"type": "string",
"description": "区域主控ID",
"name": "id",
"in": "path",
"required": true
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/controller.Response"
}
}
}
}
},
"/api/v1/devices": { "/api/v1/devices": {
"get": { "get": {
"description": "获取系统中所有设备的列表", "description": "获取系统中所有设备的列表",
@@ -370,7 +568,7 @@
} }
}, },
"delete": { "delete": {
"description": "根据计划ID删除计划。", "description": "根据计划ID删除计划。(软删除)",
"produces": [ "produces": [
"application/json" "application/json"
], ],
@@ -503,7 +701,7 @@
}, },
"/api/v1/users/login": { "/api/v1/users/login": {
"post": { "post": {
"description": "用户使用用户名和密码登录,成功后返回 JWT 令牌。", "description": "用户可以使用用户名、邮箱、手机号、微信号或飞书账号进行登录,成功后返回 JWT 令牌。",
"consumes": [ "consumes": [
"application/json" "application/json"
], ],
@@ -546,6 +744,67 @@
} }
} }
} }
},
"/api/v1/users/{id}/history": {
"get": {
"description": "根据用户ID分页获取该用户的操作审计日志。",
"produces": [
"application/json"
],
"tags": [
"用户管理"
],
"summary": "获取指定用户的操作历史",
"parameters": [
{
"type": "integer",
"description": "用户ID",
"name": "id",
"in": "path",
"required": true
},
{
"type": "integer",
"default": 1,
"description": "页码",
"name": "page",
"in": "query"
},
{
"type": "integer",
"default": 10,
"description": "每页大小",
"name": "page_size",
"in": "query"
},
{
"type": "string",
"description": "按操作类型过滤",
"name": "action_type",
"in": "query"
}
],
"responses": {
"200": {
"description": "业务码为200代表成功获取",
"schema": {
"allOf": [
{
"$ref": "#/definitions/controller.Response"
},
{
"type": "object",
"properties": {
"data": {
"$ref": "#/definitions/user.ListHistoryResponse"
}
}
}
]
}
}
}
}
} }
}, },
"definitions": { "definitions": {
@@ -554,7 +813,11 @@
"properties": { "properties": {
"code": { "code": {
"description": "业务状态码", "description": "业务状态码",
"type": "integer" "allOf": [
{
"$ref": "#/definitions/controller.ResponseCode"
}
]
}, },
"data": { "data": {
"description": "业务数据" "description": "业务数据"
@@ -565,63 +828,50 @@
} }
} }
}, },
"device.CreateDeviceRequest": { "controller.ResponseCode": {
"type": "object", "type": "integer",
"required": [ "enum": [
"name", 2000,
"type" 2001,
4000,
4001,
4004,
4009,
5000,
5003
], ],
"properties": { "x-enum-comments": {
"location": { "CodeBadRequest": "请求参数错误",
"type": "string" "CodeConflict": "资源冲突",
}, "CodeCreated": "创建成功",
"name": { "CodeInternalError": "服务器内部错误",
"type": "string" "CodeNotFound": "资源未找到",
}, "CodeServiceUnavailable": "服务不可用",
"parent_id": { "CodeSuccess": "操作成功",
"type": "integer" "CodeUnauthorized": "未授权"
}, },
"properties": { "x-enum-descriptions": [
"type": "object", "操作成功",
"additionalProperties": true "创建成功",
}, "请求参数错误",
"sub_type": { "未授权",
"$ref": "#/definitions/models.DeviceSubType" "资源未找到",
}, "资源冲突",
"type": { "服务器内部错误",
"$ref": "#/definitions/models.DeviceType" "服务不可用"
}
}
},
"device.UpdateDeviceRequest": {
"type": "object",
"required": [
"name",
"type"
], ],
"properties": { "x-enum-varnames": [
"location": { "CodeSuccess",
"type": "string" "CodeCreated",
}, "CodeBadRequest",
"name": { "CodeUnauthorized",
"type": "string" "CodeNotFound",
}, "CodeConflict",
"parent_id": { "CodeInternalError",
"type": "integer" "CodeServiceUnavailable"
}, ]
"properties": {
"type": "object",
"additionalProperties": true
},
"sub_type": {
"$ref": "#/definitions/models.DeviceSubType"
},
"type": {
"$ref": "#/definitions/models.DeviceType"
}
}
}, },
"git_huangwc_com_pig_pig-farm-controller_internal_app_controller_device.DeviceResponse": { "device.AreaControllerResponse": {
"type": "object", "type": "object",
"properties": { "properties": {
"created_at": { "created_at": {
@@ -636,57 +886,152 @@
"name": { "name": {
"type": "string" "type": "string"
}, },
"parent_id": { "network_id": {
"type": "integer" "type": "string"
}, },
"properties": { "properties": {
"type": "object", "type": "object",
"additionalProperties": true "additionalProperties": true
}, },
"sub_type": { "status": {
"$ref": "#/definitions/models.DeviceSubType" "type": "string"
},
"type": {
"$ref": "#/definitions/models.DeviceType"
}, },
"updated_at": { "updated_at": {
"type": "string" "type": "string"
} }
} }
}, },
"models.DeviceSubType": { "device.CreateAreaControllerRequest": {
"type": "string", "type": "object",
"enum": [ "required": [
"", "name",
"temperature", "network_id"
"humidity",
"ammonia",
"weight",
"feed_valve",
"fan",
"water_curtain"
], ],
"x-enum-varnames": [ "properties": {
"SubTypeNone", "location": {
"SubTypeSensorTemp", "type": "string"
"SubTypeSensorHumidity", },
"SubTypeSensorAmmonia", "name": {
"SubTypeSensorWeight", "type": "string"
"SubTypeValveFeed", },
"SubTypeFan", "network_id": {
"SubTypeWaterCurtain" "type": "string"
] },
"properties": {
"type": "object",
"additionalProperties": true
}
}
}, },
"models.DeviceType": { "device.CreateDeviceRequest": {
"type": "string", "type": "object",
"enum": [ "required": [
"area_controller", "area_controller_id",
"device" "device_template_id",
"name"
], ],
"x-enum-varnames": [ "properties": {
"DeviceTypeAreaController", "area_controller_id": {
"DeviceTypeDevice" "type": "integer"
] },
"device_template_id": {
"type": "integer"
},
"location": {
"type": "string"
},
"name": {
"type": "string"
},
"properties": {
"type": "object",
"additionalProperties": true
}
}
},
"device.UpdateAreaControllerRequest": {
"type": "object",
"required": [
"name",
"network_id"
],
"properties": {
"location": {
"type": "string"
},
"name": {
"type": "string"
},
"network_id": {
"type": "string"
},
"properties": {
"type": "object",
"additionalProperties": true
}
}
},
"device.UpdateDeviceRequest": {
"type": "object",
"required": [
"area_controller_id",
"device_template_id",
"name"
],
"properties": {
"area_controller_id": {
"type": "integer"
},
"device_template_id": {
"type": "integer"
},
"location": {
"type": "string"
},
"name": {
"type": "string"
},
"properties": {
"type": "object",
"additionalProperties": true
}
}
},
"git_huangwc_com_pig_pig-farm-controller_internal_app_controller_device.DeviceResponse": {
"type": "object",
"properties": {
"area_controller_id": {
"type": "integer"
},
"area_controller_name": {
"type": "string"
},
"created_at": {
"type": "string"
},
"device_template_id": {
"type": "integer"
},
"device_template_name": {
"type": "string"
},
"id": {
"type": "integer"
},
"location": {
"type": "string"
},
"name": {
"type": "string"
},
"properties": {
"type": "object",
"additionalProperties": true
},
"updated_at": {
"type": "string"
}
}
}, },
"models.PlanContentType": { "models.PlanContentType": {
"type": "string", "type": "string",
@@ -1064,16 +1409,24 @@
} }
} }
}, },
"user.LoginRequest": { "user.HistoryResponse": {
"type": "object", "type": "object",
"required": [
"password",
"username"
],
"properties": { "properties": {
"password": { "action_type": {
"type": "string", "type": "string",
"example": "password123" "example": "更新设备"
},
"description": {
"type": "string",
"example": "设备更新成功"
},
"target_resource": {},
"time": {
"type": "string"
},
"user_id": {
"type": "integer",
"example": 101
}, },
"username": { "username": {
"type": "string", "type": "string",
@@ -1081,6 +1434,39 @@
} }
} }
}, },
"user.ListHistoryResponse": {
"type": "object",
"properties": {
"history": {
"type": "array",
"items": {
"$ref": "#/definitions/user.HistoryResponse"
}
},
"total": {
"type": "integer",
"example": 100
}
}
},
"user.LoginRequest": {
"type": "object",
"required": [
"identifier",
"password"
],
"properties": {
"identifier": {
"description": "Identifier 可以是用户名、邮箱、手机号、微信号或飞书账号",
"type": "string",
"example": "testuser"
},
"password": {
"type": "string",
"example": "password123"
}
}
},
"user.LoginResponse": { "user.LoginResponse": {
"type": "object", "type": "object",
"properties": { "properties": {

View File

@@ -2,53 +2,54 @@ definitions:
controller.Response: controller.Response:
properties: properties:
code: code:
allOf:
- $ref: '#/definitions/controller.ResponseCode'
description: 业务状态码 description: 业务状态码
type: integer
data: data:
description: 业务数据 description: 业务数据
message: message:
description: 提示信息 description: 提示信息
type: string type: string
type: object type: object
device.CreateDeviceRequest: controller.ResponseCode:
properties: enum:
location: - 2000
type: string - 2001
name: - 4000
type: string - 4001
parent_id: - 4004
type: integer - 4009
properties: - 5000
additionalProperties: true - 5003
type: object type: integer
sub_type: x-enum-comments:
$ref: '#/definitions/models.DeviceSubType' CodeBadRequest: 请求参数错误
type: CodeConflict: 资源冲突
$ref: '#/definitions/models.DeviceType' CodeCreated: 创建成功
required: CodeInternalError: 服务器内部错误
- name CodeNotFound: 资源未找到
- type CodeServiceUnavailable: 服务不可用
type: object CodeSuccess: 操作成功
device.UpdateDeviceRequest: CodeUnauthorized: 未授权
properties: x-enum-descriptions:
location: - 操作成功
type: string - 创建成功
name: - 请求参数错误
type: string - 未授权
parent_id: - 资源未找到
type: integer - 资源冲突
properties: - 服务器内部错误
additionalProperties: true - 服务不可用
type: object x-enum-varnames:
sub_type: - CodeSuccess
$ref: '#/definitions/models.DeviceSubType' - CodeCreated
type: - CodeBadRequest
$ref: '#/definitions/models.DeviceType' - CodeUnauthorized
required: - CodeNotFound
- name - CodeConflict
- type - CodeInternalError
type: object - CodeServiceUnavailable
git_huangwc_com_pig_pig-farm-controller_internal_app_controller_device.DeviceResponse: device.AreaControllerResponse:
properties: properties:
created_at: created_at:
type: string type: string
@@ -58,46 +59,106 @@ definitions:
type: string type: string
name: name:
type: string type: string
parent_id: network_id:
type: integer type: string
properties:
additionalProperties: true
type: object
status:
type: string
updated_at:
type: string
type: object
device.CreateAreaControllerRequest:
properties:
location:
type: string
name:
type: string
network_id:
type: string
properties:
additionalProperties: true
type: object
required:
- name
- network_id
type: object
device.CreateDeviceRequest:
properties:
area_controller_id:
type: integer
device_template_id:
type: integer
location:
type: string
name:
type: string
properties:
additionalProperties: true
type: object
required:
- area_controller_id
- device_template_id
- name
type: object
device.UpdateAreaControllerRequest:
properties:
location:
type: string
name:
type: string
network_id:
type: string
properties:
additionalProperties: true
type: object
required:
- name
- network_id
type: object
device.UpdateDeviceRequest:
properties:
area_controller_id:
type: integer
device_template_id:
type: integer
location:
type: string
name:
type: string
properties:
additionalProperties: true
type: object
required:
- area_controller_id
- device_template_id
- name
type: object
git_huangwc_com_pig_pig-farm-controller_internal_app_controller_device.DeviceResponse:
properties:
area_controller_id:
type: integer
area_controller_name:
type: string
created_at:
type: string
device_template_id:
type: integer
device_template_name:
type: string
id:
type: integer
location:
type: string
name:
type: string
properties: properties:
additionalProperties: true additionalProperties: true
type: object type: object
sub_type:
$ref: '#/definitions/models.DeviceSubType'
type:
$ref: '#/definitions/models.DeviceType'
updated_at: updated_at:
type: string type: string
type: object type: object
models.DeviceSubType:
enum:
- ""
- temperature
- humidity
- ammonia
- weight
- feed_valve
- fan
- water_curtain
type: string
x-enum-varnames:
- SubTypeNone
- SubTypeSensorTemp
- SubTypeSensorHumidity
- SubTypeSensorAmmonia
- SubTypeSensorWeight
- SubTypeValveFeed
- SubTypeFan
- SubTypeWaterCurtain
models.DeviceType:
enum:
- area_controller
- device
type: string
x-enum-varnames:
- DeviceTypeAreaController
- DeviceTypeDevice
models.PlanContentType: models.PlanContentType:
enum: enum:
- sub_plans - sub_plans
@@ -358,17 +419,46 @@ definitions:
example: newuser example: newuser
type: string type: string
type: object type: object
user.LoginRequest: user.HistoryResponse:
properties: properties:
password: action_type:
example: password123 example: 更新设备
type: string type: string
description:
example: 设备更新成功
type: string
target_resource: {}
time:
type: string
user_id:
example: 101
type: integer
username: username:
example: testuser example: testuser
type: string type: string
type: object
user.ListHistoryResponse:
properties:
history:
items:
$ref: '#/definitions/user.HistoryResponse'
type: array
total:
example: 100
type: integer
type: object
user.LoginRequest:
properties:
identifier:
description: Identifier 可以是用户名、邮箱、手机号、微信号或飞书账号
example: testuser
type: string
password:
example: password123
type: string
required: required:
- identifier
- password - password
- username
type: object type: object
user.LoginResponse: user.LoginResponse:
properties: properties:
@@ -385,6 +475,125 @@ definitions:
info: info:
contact: {} contact: {}
paths: paths:
/api/v1/area-controllers:
get:
description: 获取系统中所有区域主控的列表
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/controller.Response'
- properties:
data:
items:
$ref: '#/definitions/device.AreaControllerResponse'
type: array
type: object
summary: 获取所有区域主控列表
tags:
- 区域主控管理
post:
consumes:
- application/json
description: 根据提供的信息创建一个新区域主控
parameters:
- description: 区域主控信息
in: body
name: areaController
required: true
schema:
$ref: '#/definitions/device.CreateAreaControllerRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/controller.Response'
- properties:
data:
$ref: '#/definitions/device.AreaControllerResponse'
type: object
summary: 创建新区域主控
tags:
- 区域主控管理
/api/v1/area-controllers/{id}:
delete:
description: 根据ID删除一个区域主控软删除
parameters:
- description: 区域主控ID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
$ref: '#/definitions/controller.Response'
summary: 删除区域主控
tags:
- 区域主控管理
get:
description: 根据ID获取单个区域主控的详细信息
parameters:
- description: 区域主控ID
in: path
name: id
required: true
type: string
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/controller.Response'
- properties:
data:
$ref: '#/definitions/device.AreaControllerResponse'
type: object
summary: 获取区域主控信息
tags:
- 区域主控管理
put:
consumes:
- application/json
description: 根据ID更新一个已存在的区域主控信息
parameters:
- description: 区域主控ID
in: path
name: id
required: true
type: string
- description: 要更新的区域主控信息
in: body
name: areaController
required: true
schema:
$ref: '#/definitions/device.UpdateAreaControllerRequest'
produces:
- application/json
responses:
"200":
description: OK
schema:
allOf:
- $ref: '#/definitions/controller.Response'
- properties:
data:
$ref: '#/definitions/device.AreaControllerResponse'
type: object
summary: 更新区域主控信息
tags:
- 区域主控管理
/api/v1/devices: /api/v1/devices:
get: get:
description: 获取系统中所有设备的列表 description: 获取系统中所有设备的列表
@@ -550,7 +759,7 @@ paths:
- 计划管理 - 计划管理
/api/v1/plans/{id}: /api/v1/plans/{id}:
delete: delete:
description: 根据计划ID删除计划。 description: 根据计划ID删除计划。(软删除)
parameters: parameters:
- description: 计划ID - description: 计划ID
in: path in: path
@@ -686,11 +895,49 @@ paths:
summary: 创建新用户 summary: 创建新用户
tags: tags:
- 用户管理 - 用户管理
/api/v1/users/{id}/history:
get:
description: 根据用户ID分页获取该用户的操作审计日志。
parameters:
- description: 用户ID
in: path
name: id
required: true
type: integer
- default: 1
description: 页码
in: query
name: page
type: integer
- default: 10
description: 每页大小
in: query
name: page_size
type: integer
- description: 按操作类型过滤
in: query
name: action_type
type: string
produces:
- application/json
responses:
"200":
description: 业务码为200代表成功获取
schema:
allOf:
- $ref: '#/definitions/controller.Response'
- properties:
data:
$ref: '#/definitions/user.ListHistoryResponse'
type: object
summary: 获取指定用户的操作历史
tags:
- 用户管理
/api/v1/users/login: /api/v1/users/login:
post: post:
consumes: consumes:
- application/json - application/json
description: 用户使用用户名和密码登录,成功后返回 JWT 令牌。 description: 用户可以使用用户名、邮箱、手机号、微信号或飞书账号进行登录,成功后返回 JWT 令牌。
parameters: parameters:
- description: 登录凭证 - description: 登录凭证
in: body in: body

View File

@@ -55,6 +55,7 @@ func NewAPI(cfg config.ServerConfig,
logger *logs.Logger, logger *logs.Logger,
userRepo repository.UserRepository, userRepo repository.UserRepository,
deviceRepository repository.DeviceRepository, deviceRepository repository.DeviceRepository,
areaControllerRepository repository.AreaControllerRepository,
planRepository repository.PlanRepository, planRepository repository.PlanRepository,
userActionLogRepository repository.UserActionLogRepository, userActionLogRepository repository.UserActionLogRepository,
tokenService token.TokenService, tokenService token.TokenService,
@@ -85,7 +86,7 @@ func NewAPI(cfg config.ServerConfig,
// 在 NewAPI 中初始化用户控制器,并将其作为 API 结构体的成员 // 在 NewAPI 中初始化用户控制器,并将其作为 API 结构体的成员
userController: user.NewController(userRepo, userActionLogRepository, logger, tokenService), userController: user.NewController(userRepo, userActionLogRepository, logger, tokenService),
// 在 NewAPI 中初始化设备控制器,并将其作为 API 结构体的成员 // 在 NewAPI 中初始化设备控制器,并将其作为 API 结构体的成员
deviceController: device.NewController(deviceRepository, logger), deviceController: device.NewController(deviceRepository, areaControllerRepository, logger),
// 在 NewAPI 中初始化计划控制器,并将其作为 API 结构体的成员 // 在 NewAPI 中初始化计划控制器,并将其作为 API 结构体的成员
planController: plan.NewController(logger, planRepository, analysisTaskManager), planController: plan.NewController(logger, planRepository, analysisTaskManager),
} }
@@ -159,6 +160,17 @@ func (a *API) setupRoutes() {
} }
a.logger.Info("设备相关接口注册成功 (需要认证和审计)") a.logger.Info("设备相关接口注册成功 (需要认证和审计)")
// 区域主控相关路由组
areaControllerGroup := authGroup.Group("/area-controllers")
{
areaControllerGroup.POST("", a.deviceController.CreateAreaController)
areaControllerGroup.GET("", a.deviceController.ListAreaControllers)
areaControllerGroup.GET("/:id", a.deviceController.GetAreaController)
areaControllerGroup.PUT("/:id", a.deviceController.UpdateAreaController)
areaControllerGroup.DELETE("/:id", a.deviceController.DeleteAreaController)
}
a.logger.Info("区域主控相关接口注册成功 (需要认证和审计)")
// 计划相关路由组 // 计划相关路由组
planGroup := authGroup.Group("/plans") planGroup := authGroup.Group("/plans")
{ {

View File

@@ -16,17 +16,23 @@ import (
"gorm.io/gorm" "gorm.io/gorm"
) )
// Controller 设备控制器,封装了所有与设备相关的业务逻辑 // Controller 设备控制器,封装了所有与设备和区域主控相关的业务逻辑
type Controller struct { type Controller struct {
repo repository.DeviceRepository deviceRepo repository.DeviceRepository
logger *logs.Logger areaControllerRepo repository.AreaControllerRepository
logger *logs.Logger
} }
// NewController 创建一个新的设备控制器实例 // NewController 创建一个新的设备控制器实例
func NewController(repo repository.DeviceRepository, logger *logs.Logger) *Controller { func NewController(
deviceRepo repository.DeviceRepository,
areaControllerRepo repository.AreaControllerRepository,
logger *logs.Logger,
) *Controller {
return &Controller{ return &Controller{
repo: repo, deviceRepo: deviceRepo,
logger: logger, areaControllerRepo: areaControllerRepo,
logger: logger,
} }
} }
@@ -50,6 +56,22 @@ type UpdateDeviceRequest struct {
Properties map[string]interface{} `json:"properties,omitempty"` Properties map[string]interface{} `json:"properties,omitempty"`
} }
// CreateAreaControllerRequest 定义了创建区域主控时需要传入的参数
type CreateAreaControllerRequest struct {
Name string `json:"name" binding:"required"`
NetworkID string `json:"network_id" binding:"required"`
Location string `json:"location,omitempty"`
Properties map[string]interface{} `json:"properties,omitempty"`
}
// UpdateAreaControllerRequest 定义了更新区域主控时需要传入的参数
type UpdateAreaControllerRequest struct {
Name string `json:"name" binding:"required"`
NetworkID string `json:"network_id" binding:"required"`
Location string `json:"location,omitempty"`
Properties map[string]interface{} `json:"properties,omitempty"`
}
// --- Response DTOs --- // --- Response DTOs ---
// DeviceResponse 定义了返回给客户端的单个设备信息的结构 // DeviceResponse 定义了返回给客户端的单个设备信息的结构
@@ -66,6 +88,18 @@ type DeviceResponse struct {
UpdatedAt string `json:"updated_at"` UpdatedAt string `json:"updated_at"`
} }
// AreaControllerResponse 定义了返回给客户端的单个区域主控信息的结构
type AreaControllerResponse struct {
ID uint `json:"id"`
Name string `json:"name"`
NetworkID string `json:"network_id"`
Location string `json:"location"`
Status string `json:"status"`
Properties map[string]interface{} `json:"properties"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
// --- DTO 转换函数 --- // --- DTO 转换函数 ---
// newDeviceResponse 从数据库模型创建一个新的设备响应 DTO // newDeviceResponse 从数据库模型创建一个新的设备响应 DTO
@@ -119,7 +153,45 @@ func newListDeviceResponse(devices []*models.Device) ([]*DeviceResponse, error)
return list, nil return list, nil
} }
// --- Controller Methods --- // newAreaControllerResponse 从数据库模型创建一个新的区域主控响应 DTO
func newAreaControllerResponse(ac *models.AreaController) (*AreaControllerResponse, error) {
if ac == nil {
return nil, nil
}
var props map[string]interface{}
if len(ac.Properties) > 0 && string(ac.Properties) != "null" {
if err := json.Unmarshal(ac.Properties, &props); err != nil {
return nil, fmt.Errorf("解析区域主控属性失败 (ID: %d): %w", ac.ID, err)
}
}
return &AreaControllerResponse{
ID: ac.ID,
Name: ac.Name,
NetworkID: ac.NetworkID,
Location: ac.Location,
Status: ac.Status,
Properties: props,
CreatedAt: ac.CreatedAt.Format(time.RFC3339),
UpdatedAt: ac.UpdatedAt.Format(time.RFC3339),
}, nil
}
// newListAreaControllerResponse 从数据库模型切片创建一个新的区域主控列表响应 DTO 切片
func newListAreaControllerResponse(acs []*models.AreaController) ([]*AreaControllerResponse, error) {
list := make([]*AreaControllerResponse, 0, len(acs))
for _, ac := range acs {
resp, err := newAreaControllerResponse(ac)
if err != nil {
return nil, err
}
list = append(list, resp)
}
return list, nil
}
// --- Controller Methods: Devices ---
// CreateDevice godoc // CreateDevice godoc
// @Summary 创建新设备 // @Summary 创建新设备
@@ -154,25 +226,19 @@ func (c *Controller) CreateDevice(ctx *gin.Context) {
Properties: propertiesJSON, Properties: propertiesJSON,
} }
// 在创建设备前进行自检
// 注意:这里的 SelfCheck 依赖于 DeviceTemplate 和 AreaController 字段,
// 但在创建时这些关联对象可能尚未完全加载。如果 SelfCheck 内部需要这些关联对象,
// 则需要在调用 SelfCheck 之前手动加载或调整 SelfCheck 逻辑。
// 目前假设 SelfCheck 仅检查 ID 和 Properties。
if err := device.SelfCheck(); err != nil { if err := device.SelfCheck(); err != nil {
c.logger.Errorf("%s: 设备属性自检失败: %v", actionType, err) c.logger.Errorf("%s: 设备属性自检失败: %v", actionType, err)
controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "设备属性不符合要求: "+err.Error(), actionType, "设备属性自检失败", device) controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "设备属性不符合要求: "+err.Error(), actionType, "设备属性自检失败", device)
return return
} }
if err := c.repo.Create(device); err != nil { if err := c.deviceRepo.Create(device); err != nil {
c.logger.Errorf("%s: 数据库操作失败: %v", actionType, err) c.logger.Errorf("%s: 数据库操作失败: %v", actionType, err)
controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "创建设备失败: "+err.Error(), actionType, "数据库创建失败", device) controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "创建设备失败: "+err.Error(), actionType, "数据库创建失败", device)
return return
} }
// 为了在响应中包含 DeviceTemplateName 和 AreaControllerName需要重新从数据库加载设备并预加载关联。 createdDevice, err := c.deviceRepo.FindByID(device.ID)
createdDevice, err := c.repo.FindByID(device.ID)
if err != nil { if err != nil {
c.logger.Errorf("%s: 重新加载创建的设备失败: %v", actionType, err) c.logger.Errorf("%s: 重新加载创建的设备失败: %v", actionType, err)
controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "设备创建成功,但重新加载设备失败", actionType, "重新加载设备失败", device) controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "设备创建成功,但重新加载设备失败", actionType, "重新加载设备失败", device)
@@ -208,8 +274,7 @@ func (c *Controller) GetDevice(ctx *gin.Context) {
return return
} }
// 假设 FindByIDString 方法会预加载 DeviceTemplate 和 AreaController device, err := c.deviceRepo.FindByIDString(deviceID)
device, err := c.repo.FindByIDString(deviceID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
c.logger.Warnf("%s: 设备不存在, ID: %s", actionType, deviceID) c.logger.Warnf("%s: 设备不存在, ID: %s", actionType, deviceID)
@@ -246,8 +311,7 @@ func (c *Controller) GetDevice(ctx *gin.Context) {
// @Router /api/v1/devices [get] // @Router /api/v1/devices [get]
func (c *Controller) ListDevices(ctx *gin.Context) { func (c *Controller) ListDevices(ctx *gin.Context) {
const actionType = "获取设备列表" const actionType = "获取设备列表"
// 假设 ListAll 方法会预加载 DeviceTemplate 和 AreaController devices, err := c.deviceRepo.ListAll()
devices, err := c.repo.ListAll()
if err != nil { if err != nil {
c.logger.Errorf("%s: 数据库查询失败: %v", actionType, err) c.logger.Errorf("%s: 数据库查询失败: %v", actionType, err)
controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取设备列表失败: "+err.Error(), actionType, "数据库查询失败", nil) controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取设备列表失败: "+err.Error(), actionType, "数据库查询失败", nil)
@@ -279,9 +343,7 @@ func (c *Controller) UpdateDevice(ctx *gin.Context) {
const actionType = "更新设备" const actionType = "更新设备"
deviceID := ctx.Param("id") deviceID := ctx.Param("id")
// 1. 检查设备是否存在 existingDevice, err := c.deviceRepo.FindByIDString(deviceID)
// 假设 FindByIDString 方法会预加载 DeviceTemplate 和 AreaController
existingDevice, err := c.repo.FindByIDString(deviceID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
c.logger.Warnf("%s: 设备不存在, ID: %s", actionType, deviceID) c.logger.Warnf("%s: 设备不存在, ID: %s", actionType, deviceID)
@@ -298,7 +360,6 @@ func (c *Controller) UpdateDevice(ctx *gin.Context) {
return return
} }
// 2. 绑定请求参数
var req UpdateDeviceRequest var req UpdateDeviceRequest
if err := ctx.ShouldBindJSON(&req); err != nil { if err := ctx.ShouldBindJSON(&req); err != nil {
c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err)
@@ -313,33 +374,25 @@ func (c *Controller) UpdateDevice(ctx *gin.Context) {
return return
} }
// 3. 更新从数据库中查出的现有设备对象的字段
existingDevice.Name = req.Name existingDevice.Name = req.Name
existingDevice.DeviceTemplateID = req.DeviceTemplateID existingDevice.DeviceTemplateID = req.DeviceTemplateID
existingDevice.AreaControllerID = req.AreaControllerID existingDevice.AreaControllerID = req.AreaControllerID
existingDevice.Location = req.Location existingDevice.Location = req.Location
existingDevice.Properties = propertiesJSON existingDevice.Properties = propertiesJSON
// 在更新设备前进行自检
// 注意:这里的 SelfCheck 依赖于 DeviceTemplate 和 AreaController 字段,
// 但在更新时这些关联对象可能尚未完全加载。如果 SelfCheck 内部需要这些关联对象,
// 则需要在调用 SelfCheck 之前手动加载或调整 SelfCheck 逻辑。
// 目前假设 SelfCheck 仅检查 ID 和 Properties。
if err := existingDevice.SelfCheck(); err != nil { if err := existingDevice.SelfCheck(); err != nil {
c.logger.Errorf("%s: 设备属性自检失败: %v", actionType, err) c.logger.Errorf("%s: 设备属性自检失败: %v", actionType, err)
controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "设备属性不符合要求: "+err.Error(), actionType, "设备属性自检失败", existingDevice) controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "设备属性不符合要求: "+err.Error(), actionType, "设备属性自检失败", existingDevice)
return return
} }
// 4. 将修改后的 existingDevice 对象保存回数据库 if err := c.deviceRepo.Update(existingDevice); err != nil {
if err := c.repo.Update(existingDevice); err != nil {
c.logger.Errorf("%s: 数据库更新失败: %v, Device: %+v", actionType, err, existingDevice) c.logger.Errorf("%s: 数据库更新失败: %v, Device: %+v", actionType, err, existingDevice)
controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "更新设备失败: "+err.Error(), actionType, "数据库更新失败", existingDevice) controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "更新设备失败: "+err.Error(), actionType, "数据库更新失败", existingDevice)
return return
} }
// 为了在响应中包含 DeviceTemplateName 和 AreaControllerName需要重新从数据库加载设备并预加载关联。 updatedDevice, err := c.deviceRepo.FindByID(existingDevice.ID)
updatedDevice, err := c.repo.FindByID(existingDevice.ID)
if err != nil { if err != nil {
c.logger.Errorf("%s: 重新加载更新的设备失败: %v", actionType, err) c.logger.Errorf("%s: 重新加载更新的设备失败: %v", actionType, err)
controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "设备更新成功,但重新加载设备失败", actionType, "重新加载设备失败", existingDevice) controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "设备更新成功,但重新加载设备失败", actionType, "重新加载设备失败", existingDevice)
@@ -369,7 +422,6 @@ func (c *Controller) DeleteDevice(ctx *gin.Context) {
const actionType = "删除设备" const actionType = "删除设备"
deviceID := ctx.Param("id") deviceID := ctx.Param("id")
// 我们需要先将字符串ID转换为uint因为Delete方法需要uint类型
idUint, err := strconv.ParseUint(deviceID, 10, 64) idUint, err := strconv.ParseUint(deviceID, 10, 64)
if err != nil { if err != nil {
c.logger.Errorf("%s: 设备ID格式错误: %v, ID: %s", actionType, err, deviceID) c.logger.Errorf("%s: 设备ID格式错误: %v, ID: %s", actionType, err, deviceID)
@@ -377,8 +429,7 @@ func (c *Controller) DeleteDevice(ctx *gin.Context) {
return return
} }
// 检查设备是否存在(可选,但通常在删除前会检查) _, err = c.deviceRepo.FindByIDString(deviceID)
_, err = c.repo.FindByIDString(deviceID)
if err != nil { if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) { if errors.Is(err, gorm.ErrRecordNotFound) {
c.logger.Warnf("%s: 设备不存在, ID: %s", actionType, deviceID) c.logger.Warnf("%s: 设备不存在, ID: %s", actionType, deviceID)
@@ -390,7 +441,7 @@ func (c *Controller) DeleteDevice(ctx *gin.Context) {
return return
} }
if err := c.repo.Delete(uint(idUint)); err != nil { if err := c.deviceRepo.Delete(uint(idUint)); err != nil {
c.logger.Errorf("%s: 数据库删除失败: %v, ID: %d", actionType, err, idUint) c.logger.Errorf("%s: 数据库删除失败: %v, ID: %d", actionType, err, idUint)
controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "删除设备失败: "+err.Error(), actionType, "数据库删除失败", deviceID) controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "删除设备失败: "+err.Error(), actionType, "数据库删除失败", deviceID)
return return
@@ -399,3 +450,245 @@ func (c *Controller) DeleteDevice(ctx *gin.Context) {
c.logger.Infof("%s: 设备删除成功, ID: %d", actionType, idUint) c.logger.Infof("%s: 设备删除成功, ID: %d", actionType, idUint)
controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "设备删除成功", nil, actionType, "设备删除成功", deviceID) controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "设备删除成功", nil, actionType, "设备删除成功", deviceID)
} }
// --- Controller Methods: Area Controllers ---
// CreateAreaController godoc
// @Summary 创建新区域主控
// @Description 根据提供的信息创建一个新区域主控
// @Tags 区域主控管理
// @Accept json
// @Produce json
// @Param areaController body CreateAreaControllerRequest true "区域主控信息"
// @Success 200 {object} controller.Response{data=AreaControllerResponse}
// @Router /api/v1/area-controllers [post]
func (c *Controller) CreateAreaController(ctx *gin.Context) {
const actionType = "创建区域主控"
var req CreateAreaControllerRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err)
controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req)
return
}
propertiesJSON, err := json.Marshal(req.Properties)
if err != nil {
c.logger.Errorf("%s: 序列化属性失败: %v", actionType, err)
controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "属性字段格式错误", actionType, "属性序列化失败", req.Properties)
return
}
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)
controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "区域主控参数不符合要求: "+err.Error(), actionType, "区域主控自检失败", ac)
return
}
if err := c.areaControllerRepo.Create(ac); err != nil {
c.logger.Errorf("%s: 数据库操作失败: %v", actionType, err)
controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "创建区域主控失败: "+err.Error(), actionType, "数据库创建失败", ac)
return
}
resp, err := newAreaControllerResponse(ac)
if err != nil {
c.logger.Errorf("%s: 序列化响应失败: %v", actionType, err)
controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "区域主控创建成功,但响应生成失败", actionType, "响应序列化失败", ac)
return
}
c.logger.Infof("%s: 区域主控创建成功, ID: %d", actionType, ac.ID)
controller.SendSuccessWithAudit(ctx, controller.CodeCreated, "区域主控创建成功", resp, actionType, "区域主控创建成功", resp)
}
// GetAreaController godoc
// @Summary 获取区域主控信息
// @Description 根据ID获取单个区域主控的详细信息
// @Tags 区域主控管理
// @Produce json
// @Param id path string true "区域主控ID"
// @Success 200 {object} controller.Response{data=AreaControllerResponse}
// @Router /api/v1/area-controllers/{id} [get]
func (c *Controller) GetAreaController(ctx *gin.Context) {
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)
controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的区域主控ID格式", actionType, "ID格式错误", acID)
return
}
ac, err := c.areaControllerRepo.FindByID(uint(idUint))
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
c.logger.Warnf("%s: 区域主控不存在, ID: %s", actionType, acID)
controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "区域主控未找到", actionType, "区域主控不存在", acID)
return
}
c.logger.Errorf("%s: 数据库查询失败: %v, ID: %s", actionType, err, acID)
controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取区域主控信息失败: "+err.Error(), actionType, "数据库查询失败", acID)
return
}
resp, err := newAreaControllerResponse(ac)
if err != nil {
c.logger.Errorf("%s: 序列化响应失败: %v, AreaController: %+v", actionType, err, ac)
controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取区域主控信息失败: 内部数据格式错误", actionType, "响应序列化失败", ac)
return
}
c.logger.Infof("%s: 获取区域主控信息成功, ID: %d", actionType, ac.ID)
controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取区域主控信息成功", resp, actionType, "获取区域主控信息成功", resp)
}
// ListAreaControllers godoc
// @Summary 获取所有区域主控列表
// @Description 获取系统中所有区域主控的列表
// @Tags 区域主控管理
// @Produce json
// @Success 200 {object} controller.Response{data=[]AreaControllerResponse}
// @Router /api/v1/area-controllers [get]
func (c *Controller) ListAreaControllers(ctx *gin.Context) {
const actionType = "获取区域主控列表"
acs, err := c.areaControllerRepo.ListAll()
if err != nil {
c.logger.Errorf("%s: 数据库查询失败: %v", actionType, err)
controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取区域主控列表失败: "+err.Error(), actionType, "数据库查询失败", nil)
return
}
resp, err := newListAreaControllerResponse(acs)
if err != nil {
c.logger.Errorf("%s: 序列化响应失败: %v, AreaControllers: %+v", actionType, err, acs)
controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取区域主控列表失败: 内部数据格式错误", actionType, "响应序列化失败", acs)
return
}
c.logger.Infof("%s: 获取区域主控列表成功, 数量: %d", actionType, len(acs))
controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "获取区域主控列表成功", resp, actionType, "获取区域主控列表成功", resp)
}
// UpdateAreaController godoc
// @Summary 更新区域主控信息
// @Description 根据ID更新一个已存在的区域主控信息
// @Tags 区域主控管理
// @Accept json
// @Produce json
// @Param id path string true "区域主控ID"
// @Param areaController body UpdateAreaControllerRequest true "要更新的区域主控信息"
// @Success 200 {object} controller.Response{data=AreaControllerResponse}
// @Router /api/v1/area-controllers/{id} [put]
func (c *Controller) UpdateAreaController(ctx *gin.Context) {
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)
controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的区域主控ID格式", actionType, "ID格式错误", acID)
return
}
existingAC, err := c.areaControllerRepo.FindByID(uint(idUint))
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
c.logger.Warnf("%s: 区域主控不存在, ID: %s", actionType, acID)
controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "区域主控未找到", actionType, "区域主控不存在", acID)
return
}
c.logger.Errorf("%s: 数据库查询失败: %v, ID: %s", actionType, err, acID)
controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "更新区域主控失败: "+err.Error(), actionType, "数据库查询失败", acID)
return
}
var req UpdateAreaControllerRequest
if err := ctx.ShouldBindJSON(&req); err != nil {
c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err)
controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的请求体: "+err.Error(), actionType, "请求体绑定失败", req)
return
}
propertiesJSON, err := json.Marshal(req.Properties)
if err != nil {
c.logger.Errorf("%s: 序列化属性失败: %v", actionType, err)
controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "属性字段格式错误", actionType, "属性序列化失败", req.Properties)
return
}
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)
controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "区域主控参数不符合要求: "+err.Error(), actionType, "区域主控自检失败", existingAC)
return
}
if err := c.areaControllerRepo.Update(existingAC); err != nil {
c.logger.Errorf("%s: 数据库更新失败: %v, AreaController: %+v", actionType, err, existingAC)
controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "更新区域主控失败: "+err.Error(), actionType, "数据库更新失败", existingAC)
return
}
resp, err := newAreaControllerResponse(existingAC)
if err != nil {
c.logger.Errorf("%s: 序列化响应失败: %v, AreaController: %+v", actionType, err, existingAC)
controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "区域主控更新成功,但响应生成失败", actionType, "响应序列化失败", existingAC)
return
}
c.logger.Infof("%s: 区域主控更新成功, ID: %d", actionType, existingAC.ID)
controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "区域主控更新成功", resp, actionType, "区域主控更新成功", resp)
}
// DeleteAreaController godoc
// @Summary 删除区域主控
// @Description 根据ID删除一个区域主控软删除
// @Tags 区域主控管理
// @Produce json
// @Param id path string true "区域主控ID"
// @Success 200 {object} controller.Response
// @Router /api/v1/area-controllers/{id} [delete]
func (c *Controller) DeleteAreaController(ctx *gin.Context) {
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)
controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "无效的区域主控ID格式", actionType, "ID格式错误", acID)
return
}
_, err = c.areaControllerRepo.FindByID(uint(idUint))
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
c.logger.Warnf("%s: 区域主控不存在, ID: %s", actionType, acID)
controller.SendErrorWithAudit(ctx, controller.CodeNotFound, "区域主控未找到", actionType, "区域主控不存在", acID)
return
}
c.logger.Errorf("%s: 查找区域主控失败: %v, ID: %s", actionType, err, acID)
controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "删除区域主控失败: 查找时发生内部错误", actionType, "数据库查询失败", acID)
return
}
if err := c.areaControllerRepo.Delete(uint(idUint)); err != nil {
c.logger.Errorf("%s: 数据库删除失败: %v, ID: %d", actionType, err, idUint)
controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "删除区域主控失败: "+err.Error(), actionType, "数据库删除失败", acID)
return
}
c.logger.Infof("%s: 区域主控删除成功, ID: %d", actionType, idUint)
controller.SendSuccessWithAudit(ctx, controller.CodeSuccess, "区域主控删除成功", nil, actionType, "区域主控删除成功", acID)
}

View File

@@ -129,6 +129,7 @@ func NewApplication(configPath string) (*Application, error) {
logger, logger,
userRepo, userRepo,
deviceRepo, deviceRepo,
areaControllerRepo,
planRepo, planRepo,
userActionLogRepo, userActionLogRepo,
tokenService, tokenService,

View File

@@ -1,6 +1,8 @@
package repository package repository
import ( import (
"fmt"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models" "git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"gorm.io/gorm" "gorm.io/gorm"
) )
@@ -8,7 +10,11 @@ import (
// AreaControllerRepository 定义了对 AreaController 模型的数据库操作接口 // AreaControllerRepository 定义了对 AreaController 模型的数据库操作接口
type AreaControllerRepository interface { type AreaControllerRepository interface {
FindByID(id uint) (*models.AreaController, error) FindByID(id uint) (*models.AreaController, error)
FindByNetworkID(networkID string) (*models.AreaController, error) // New method FindByNetworkID(networkID string) (*models.AreaController, error)
Create(ac *models.AreaController) error
ListAll() ([]*models.AreaController, error)
Update(ac *models.AreaController) error
Delete(id uint) error
} }
// gormAreaControllerRepository 是 AreaControllerRepository 的 GORM 实现。 // gormAreaControllerRepository 是 AreaControllerRepository 的 GORM 实现。
@@ -21,6 +27,48 @@ func NewGormAreaControllerRepository(db *gorm.DB) AreaControllerRepository {
return &gormAreaControllerRepository{db: db} return &gormAreaControllerRepository{db: db}
} }
// Create 创建一个新的 AreaController 记录。
func (r *gormAreaControllerRepository) Create(ac *models.AreaController) error {
return r.db.Create(ac).Error
}
// ListAll 返回所有 AreaController 的列表。
func (r *gormAreaControllerRepository) ListAll() ([]*models.AreaController, error) {
var areaControllers []*models.AreaController
if err := r.db.Find(&areaControllers).Error; err != nil {
return nil, err
}
return areaControllers, nil
}
// Update 更新一个已存在的 AreaController 记录。
func (r *gormAreaControllerRepository) Update(ac *models.AreaController) error {
return r.db.Save(ac).Error
}
// Delete 删除一个 AreaController 记录。
// 在删除前会检查是否有设备关联到该主控,如果有,则不允许删除。
func (r *gormAreaControllerRepository) Delete(id uint) error {
return r.db.Transaction(func(tx *gorm.DB) error {
// 检查是否有设备关联到这个区域主控
var count int64
if err := tx.Model(&models.Device{}).Where("area_controller_id = ?", id).Count(&count).Error; err != nil {
return fmt.Errorf("检查关联设备失败: %w", err)
}
if count > 0 {
return fmt.Errorf("无法删除区域主控,因为仍有 %d 个设备关联到它", count)
}
// 如果没有关联设备,则执行删除操作
if err := tx.Delete(&models.AreaController{}, id).Error; err != nil {
return fmt.Errorf("删除区域主控失败: %w", err)
}
return nil
})
}
// FindByID 通过 ID 查找一个 AreaController。 // FindByID 通过 ID 查找一个 AreaController。
func (r *gormAreaControllerRepository) FindByID(id uint) (*models.AreaController, error) { func (r *gormAreaControllerRepository) FindByID(id uint) (*models.AreaController, error) {
var areaController models.AreaController var areaController models.AreaController