diff --git a/docs/docs.go b/docs/docs.go index 7148476..56b9975 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -15,6 +15,204 @@ const docTemplate = `{ "host": "{{.Host}}", "basePath": "{{.BasePath}}", "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": { "get": { "description": "获取系统中所有设备的列表", @@ -381,7 +579,7 @@ const docTemplate = `{ } }, "delete": { - "description": "根据计划ID删除计划。", + "description": "根据计划ID删除计划。(软删除)", "produces": [ "application/json" ], @@ -514,7 +712,7 @@ const docTemplate = `{ }, "/api/v1/users/login": { "post": { - "description": "用户使用用户名和密码登录,成功后返回 JWT 令牌。", + "description": "用户可以使用用户名、邮箱、手机号、微信号或飞书账号进行登录,成功后返回 JWT 令牌。", "consumes": [ "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": { @@ -565,7 +824,11 @@ const docTemplate = `{ "properties": { "code": { "description": "业务状态码", - "type": "integer" + "allOf": [ + { + "$ref": "#/definitions/controller.ResponseCode" + } + ] }, "data": { "description": "业务数据" @@ -576,63 +839,50 @@ const docTemplate = `{ } } }, - "device.CreateDeviceRequest": { - "type": "object", - "required": [ - "name", - "type" + "controller.ResponseCode": { + "type": "integer", + "enum": [ + 2000, + 2001, + 4000, + 4001, + 4004, + 4009, + 5000, + 5003 ], - "properties": { - "location": { - "type": "string" - }, - "name": { - "type": "string" - }, - "parent_id": { - "type": "integer" - }, - "properties": { - "type": "object", - "additionalProperties": true - }, - "sub_type": { - "$ref": "#/definitions/models.DeviceSubType" - }, - "type": { - "$ref": "#/definitions/models.DeviceType" - } - } - }, - "device.UpdateDeviceRequest": { - "type": "object", - "required": [ - "name", - "type" + "x-enum-comments": { + "CodeBadRequest": "请求参数错误", + "CodeConflict": "资源冲突", + "CodeCreated": "创建成功", + "CodeInternalError": "服务器内部错误", + "CodeNotFound": "资源未找到", + "CodeServiceUnavailable": "服务不可用", + "CodeSuccess": "操作成功", + "CodeUnauthorized": "未授权" + }, + "x-enum-descriptions": [ + "操作成功", + "创建成功", + "请求参数错误", + "未授权", + "资源未找到", + "资源冲突", + "服务器内部错误", + "服务不可用" ], - "properties": { - "location": { - "type": "string" - }, - "name": { - "type": "string" - }, - "parent_id": { - "type": "integer" - }, - "properties": { - "type": "object", - "additionalProperties": true - }, - "sub_type": { - "$ref": "#/definitions/models.DeviceSubType" - }, - "type": { - "$ref": "#/definitions/models.DeviceType" - } - } + "x-enum-varnames": [ + "CodeSuccess", + "CodeCreated", + "CodeBadRequest", + "CodeUnauthorized", + "CodeNotFound", + "CodeConflict", + "CodeInternalError", + "CodeServiceUnavailable" + ] }, - "git_huangwc_com_pig_pig-farm-controller_internal_app_controller_device.DeviceResponse": { + "device.AreaControllerResponse": { "type": "object", "properties": { "created_at": { @@ -647,57 +897,152 @@ const docTemplate = `{ "name": { "type": "string" }, - "parent_id": { - "type": "integer" + "network_id": { + "type": "string" }, "properties": { "type": "object", "additionalProperties": true }, - "sub_type": { - "$ref": "#/definitions/models.DeviceSubType" - }, - "type": { - "$ref": "#/definitions/models.DeviceType" + "status": { + "type": "string" }, "updated_at": { "type": "string" } } }, - "models.DeviceSubType": { - "type": "string", - "enum": [ - "", - "temperature", - "humidity", - "ammonia", - "weight", - "feed_valve", - "fan", - "water_curtain" + "device.CreateAreaControllerRequest": { + "type": "object", + "required": [ + "name", + "network_id" ], - "x-enum-varnames": [ - "SubTypeNone", - "SubTypeSensorTemp", - "SubTypeSensorHumidity", - "SubTypeSensorAmmonia", - "SubTypeSensorWeight", - "SubTypeValveFeed", - "SubTypeFan", - "SubTypeWaterCurtain" - ] + "properties": { + "location": { + "type": "string" + }, + "name": { + "type": "string" + }, + "network_id": { + "type": "string" + }, + "properties": { + "type": "object", + "additionalProperties": true + } + } }, - "models.DeviceType": { - "type": "string", - "enum": [ - "area_controller", - "device" + "device.CreateDeviceRequest": { + "type": "object", + "required": [ + "area_controller_id", + "device_template_id", + "name" ], - "x-enum-varnames": [ - "DeviceTypeAreaController", - "DeviceTypeDevice" - ] + "properties": { + "area_controller_id": { + "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": { "type": "string", @@ -1075,16 +1420,24 @@ const docTemplate = `{ } } }, - "user.LoginRequest": { + "user.HistoryResponse": { "type": "object", - "required": [ - "password", - "username" - ], "properties": { - "password": { + "action_type": { "type": "string", - "example": "password123" + "example": "更新设备" + }, + "description": { + "type": "string", + "example": "设备更新成功" + }, + "target_resource": {}, + "time": { + "type": "string" + }, + "user_id": { + "type": "integer", + "example": 101 }, "username": { "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": { "type": "object", "properties": { diff --git a/docs/swagger.json b/docs/swagger.json index 372d873..a5ef27e 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -4,6 +4,204 @@ "contact": {} }, "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": { "get": { "description": "获取系统中所有设备的列表", @@ -370,7 +568,7 @@ } }, "delete": { - "description": "根据计划ID删除计划。", + "description": "根据计划ID删除计划。(软删除)", "produces": [ "application/json" ], @@ -503,7 +701,7 @@ }, "/api/v1/users/login": { "post": { - "description": "用户使用用户名和密码登录,成功后返回 JWT 令牌。", + "description": "用户可以使用用户名、邮箱、手机号、微信号或飞书账号进行登录,成功后返回 JWT 令牌。", "consumes": [ "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": { @@ -554,7 +813,11 @@ "properties": { "code": { "description": "业务状态码", - "type": "integer" + "allOf": [ + { + "$ref": "#/definitions/controller.ResponseCode" + } + ] }, "data": { "description": "业务数据" @@ -565,63 +828,50 @@ } } }, - "device.CreateDeviceRequest": { - "type": "object", - "required": [ - "name", - "type" + "controller.ResponseCode": { + "type": "integer", + "enum": [ + 2000, + 2001, + 4000, + 4001, + 4004, + 4009, + 5000, + 5003 ], - "properties": { - "location": { - "type": "string" - }, - "name": { - "type": "string" - }, - "parent_id": { - "type": "integer" - }, - "properties": { - "type": "object", - "additionalProperties": true - }, - "sub_type": { - "$ref": "#/definitions/models.DeviceSubType" - }, - "type": { - "$ref": "#/definitions/models.DeviceType" - } - } - }, - "device.UpdateDeviceRequest": { - "type": "object", - "required": [ - "name", - "type" + "x-enum-comments": { + "CodeBadRequest": "请求参数错误", + "CodeConflict": "资源冲突", + "CodeCreated": "创建成功", + "CodeInternalError": "服务器内部错误", + "CodeNotFound": "资源未找到", + "CodeServiceUnavailable": "服务不可用", + "CodeSuccess": "操作成功", + "CodeUnauthorized": "未授权" + }, + "x-enum-descriptions": [ + "操作成功", + "创建成功", + "请求参数错误", + "未授权", + "资源未找到", + "资源冲突", + "服务器内部错误", + "服务不可用" ], - "properties": { - "location": { - "type": "string" - }, - "name": { - "type": "string" - }, - "parent_id": { - "type": "integer" - }, - "properties": { - "type": "object", - "additionalProperties": true - }, - "sub_type": { - "$ref": "#/definitions/models.DeviceSubType" - }, - "type": { - "$ref": "#/definitions/models.DeviceType" - } - } + "x-enum-varnames": [ + "CodeSuccess", + "CodeCreated", + "CodeBadRequest", + "CodeUnauthorized", + "CodeNotFound", + "CodeConflict", + "CodeInternalError", + "CodeServiceUnavailable" + ] }, - "git_huangwc_com_pig_pig-farm-controller_internal_app_controller_device.DeviceResponse": { + "device.AreaControllerResponse": { "type": "object", "properties": { "created_at": { @@ -636,57 +886,152 @@ "name": { "type": "string" }, - "parent_id": { - "type": "integer" + "network_id": { + "type": "string" }, "properties": { "type": "object", "additionalProperties": true }, - "sub_type": { - "$ref": "#/definitions/models.DeviceSubType" - }, - "type": { - "$ref": "#/definitions/models.DeviceType" + "status": { + "type": "string" }, "updated_at": { "type": "string" } } }, - "models.DeviceSubType": { - "type": "string", - "enum": [ - "", - "temperature", - "humidity", - "ammonia", - "weight", - "feed_valve", - "fan", - "water_curtain" + "device.CreateAreaControllerRequest": { + "type": "object", + "required": [ + "name", + "network_id" ], - "x-enum-varnames": [ - "SubTypeNone", - "SubTypeSensorTemp", - "SubTypeSensorHumidity", - "SubTypeSensorAmmonia", - "SubTypeSensorWeight", - "SubTypeValveFeed", - "SubTypeFan", - "SubTypeWaterCurtain" - ] + "properties": { + "location": { + "type": "string" + }, + "name": { + "type": "string" + }, + "network_id": { + "type": "string" + }, + "properties": { + "type": "object", + "additionalProperties": true + } + } }, - "models.DeviceType": { - "type": "string", - "enum": [ - "area_controller", - "device" + "device.CreateDeviceRequest": { + "type": "object", + "required": [ + "area_controller_id", + "device_template_id", + "name" ], - "x-enum-varnames": [ - "DeviceTypeAreaController", - "DeviceTypeDevice" - ] + "properties": { + "area_controller_id": { + "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": { "type": "string", @@ -1064,16 +1409,24 @@ } } }, - "user.LoginRequest": { + "user.HistoryResponse": { "type": "object", - "required": [ - "password", - "username" - ], "properties": { - "password": { + "action_type": { "type": "string", - "example": "password123" + "example": "更新设备" + }, + "description": { + "type": "string", + "example": "设备更新成功" + }, + "target_resource": {}, + "time": { + "type": "string" + }, + "user_id": { + "type": "integer", + "example": 101 }, "username": { "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": { "type": "object", "properties": { diff --git a/docs/swagger.yaml b/docs/swagger.yaml index 9bf770d..8e062b6 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -2,53 +2,54 @@ definitions: controller.Response: properties: code: + allOf: + - $ref: '#/definitions/controller.ResponseCode' description: 业务状态码 - type: integer data: description: 业务数据 message: description: 提示信息 type: string type: object - device.CreateDeviceRequest: - properties: - location: - type: string - name: - type: string - parent_id: - type: integer - properties: - additionalProperties: true - type: object - sub_type: - $ref: '#/definitions/models.DeviceSubType' - type: - $ref: '#/definitions/models.DeviceType' - required: - - name - - type - type: object - device.UpdateDeviceRequest: - properties: - location: - type: string - name: - type: string - parent_id: - type: integer - properties: - additionalProperties: true - type: object - sub_type: - $ref: '#/definitions/models.DeviceSubType' - type: - $ref: '#/definitions/models.DeviceType' - required: - - name - - type - type: object - git_huangwc_com_pig_pig-farm-controller_internal_app_controller_device.DeviceResponse: + controller.ResponseCode: + enum: + - 2000 + - 2001 + - 4000 + - 4001 + - 4004 + - 4009 + - 5000 + - 5003 + type: integer + x-enum-comments: + CodeBadRequest: 请求参数错误 + CodeConflict: 资源冲突 + CodeCreated: 创建成功 + CodeInternalError: 服务器内部错误 + CodeNotFound: 资源未找到 + CodeServiceUnavailable: 服务不可用 + CodeSuccess: 操作成功 + CodeUnauthorized: 未授权 + x-enum-descriptions: + - 操作成功 + - 创建成功 + - 请求参数错误 + - 未授权 + - 资源未找到 + - 资源冲突 + - 服务器内部错误 + - 服务不可用 + x-enum-varnames: + - CodeSuccess + - CodeCreated + - CodeBadRequest + - CodeUnauthorized + - CodeNotFound + - CodeConflict + - CodeInternalError + - CodeServiceUnavailable + device.AreaControllerResponse: properties: created_at: type: string @@ -58,46 +59,106 @@ definitions: type: string name: type: string - parent_id: - type: integer + network_id: + 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: additionalProperties: true type: object - sub_type: - $ref: '#/definitions/models.DeviceSubType' - type: - $ref: '#/definitions/models.DeviceType' updated_at: type: string 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: enum: - sub_plans @@ -358,17 +419,46 @@ definitions: example: newuser type: string type: object - user.LoginRequest: + user.HistoryResponse: properties: - password: - example: password123 + action_type: + example: 更新设备 type: string + description: + example: 设备更新成功 + type: string + target_resource: {} + time: + type: string + user_id: + example: 101 + type: integer username: example: testuser 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: + - identifier - password - - username type: object user.LoginResponse: properties: @@ -385,6 +475,125 @@ definitions: info: contact: {} 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: get: description: 获取系统中所有设备的列表 @@ -550,7 +759,7 @@ paths: - 计划管理 /api/v1/plans/{id}: delete: - description: 根据计划ID删除计划。 + description: 根据计划ID删除计划。(软删除) parameters: - description: 计划ID in: path @@ -686,11 +895,49 @@ paths: summary: 创建新用户 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: post: consumes: - application/json - description: 用户使用用户名和密码登录,成功后返回 JWT 令牌。 + description: 用户可以使用用户名、邮箱、手机号、微信号或飞书账号进行登录,成功后返回 JWT 令牌。 parameters: - description: 登录凭证 in: body diff --git a/internal/app/api/api.go b/internal/app/api/api.go index a9e994a..6c29ff5 100644 --- a/internal/app/api/api.go +++ b/internal/app/api/api.go @@ -55,6 +55,7 @@ func NewAPI(cfg config.ServerConfig, logger *logs.Logger, userRepo repository.UserRepository, deviceRepository repository.DeviceRepository, + areaControllerRepository repository.AreaControllerRepository, planRepository repository.PlanRepository, userActionLogRepository repository.UserActionLogRepository, tokenService token.TokenService, @@ -85,7 +86,7 @@ func NewAPI(cfg config.ServerConfig, // 在 NewAPI 中初始化用户控制器,并将其作为 API 结构体的成员 userController: user.NewController(userRepo, userActionLogRepository, logger, tokenService), // 在 NewAPI 中初始化设备控制器,并将其作为 API 结构体的成员 - deviceController: device.NewController(deviceRepository, logger), + deviceController: device.NewController(deviceRepository, areaControllerRepository, logger), // 在 NewAPI 中初始化计划控制器,并将其作为 API 结构体的成员 planController: plan.NewController(logger, planRepository, analysisTaskManager), } @@ -159,6 +160,17 @@ func (a *API) setupRoutes() { } 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") { diff --git a/internal/app/controller/device/device_controller.go b/internal/app/controller/device/device_controller.go index 4661298..f917a87 100644 --- a/internal/app/controller/device/device_controller.go +++ b/internal/app/controller/device/device_controller.go @@ -16,17 +16,23 @@ import ( "gorm.io/gorm" ) -// Controller 设备控制器,封装了所有与设备相关的业务逻辑 +// Controller 设备控制器,封装了所有与设备和区域主控相关的业务逻辑 type Controller struct { - repo repository.DeviceRepository - logger *logs.Logger + deviceRepo repository.DeviceRepository + areaControllerRepo repository.AreaControllerRepository + logger *logs.Logger } // NewController 创建一个新的设备控制器实例 -func NewController(repo repository.DeviceRepository, logger *logs.Logger) *Controller { +func NewController( + deviceRepo repository.DeviceRepository, + areaControllerRepo repository.AreaControllerRepository, + logger *logs.Logger, +) *Controller { return &Controller{ - repo: repo, - logger: logger, + deviceRepo: deviceRepo, + areaControllerRepo: areaControllerRepo, + logger: logger, } } @@ -50,6 +56,22 @@ type UpdateDeviceRequest struct { 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 --- // DeviceResponse 定义了返回给客户端的单个设备信息的结构 @@ -66,6 +88,18 @@ type DeviceResponse struct { 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 转换函数 --- // newDeviceResponse 从数据库模型创建一个新的设备响应 DTO @@ -119,7 +153,45 @@ func newListDeviceResponse(devices []*models.Device) ([]*DeviceResponse, error) 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 // @Summary 创建新设备 @@ -154,25 +226,19 @@ func (c *Controller) CreateDevice(ctx *gin.Context) { Properties: propertiesJSON, } - // 在创建设备前进行自检 - // 注意:这里的 SelfCheck 依赖于 DeviceTemplate 和 AreaController 字段, - // 但在创建时这些关联对象可能尚未完全加载。如果 SelfCheck 内部需要这些关联对象, - // 则需要在调用 SelfCheck 之前手动加载或调整 SelfCheck 逻辑。 - // 目前假设 SelfCheck 仅检查 ID 和 Properties。 if err := device.SelfCheck(); err != nil { c.logger.Errorf("%s: 设备属性自检失败: %v", actionType, err) controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "设备属性不符合要求: "+err.Error(), actionType, "设备属性自检失败", device) return } - if err := c.repo.Create(device); err != nil { + if err := c.deviceRepo.Create(device); err != nil { c.logger.Errorf("%s: 数据库操作失败: %v", actionType, err) controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "创建设备失败: "+err.Error(), actionType, "数据库创建失败", device) return } - // 为了在响应中包含 DeviceTemplateName 和 AreaControllerName,需要重新从数据库加载设备,并预加载关联。 - createdDevice, err := c.repo.FindByID(device.ID) + createdDevice, err := c.deviceRepo.FindByID(device.ID) if err != nil { c.logger.Errorf("%s: 重新加载创建的设备失败: %v", actionType, err) controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "设备创建成功,但重新加载设备失败", actionType, "重新加载设备失败", device) @@ -208,8 +274,7 @@ func (c *Controller) GetDevice(ctx *gin.Context) { return } - // 假设 FindByIDString 方法会预加载 DeviceTemplate 和 AreaController - device, err := c.repo.FindByIDString(deviceID) + device, err := c.deviceRepo.FindByIDString(deviceID) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { c.logger.Warnf("%s: 设备不存在, ID: %s", actionType, deviceID) @@ -246,8 +311,7 @@ func (c *Controller) GetDevice(ctx *gin.Context) { // @Router /api/v1/devices [get] func (c *Controller) ListDevices(ctx *gin.Context) { const actionType = "获取设备列表" - // 假设 ListAll 方法会预加载 DeviceTemplate 和 AreaController - devices, err := c.repo.ListAll() + devices, err := c.deviceRepo.ListAll() if err != nil { c.logger.Errorf("%s: 数据库查询失败: %v", actionType, err) controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "获取设备列表失败: "+err.Error(), actionType, "数据库查询失败", nil) @@ -279,9 +343,7 @@ func (c *Controller) UpdateDevice(ctx *gin.Context) { const actionType = "更新设备" deviceID := ctx.Param("id") - // 1. 检查设备是否存在 - // 假设 FindByIDString 方法会预加载 DeviceTemplate 和 AreaController - existingDevice, err := c.repo.FindByIDString(deviceID) + existingDevice, err := c.deviceRepo.FindByIDString(deviceID) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { c.logger.Warnf("%s: 设备不存在, ID: %s", actionType, deviceID) @@ -298,7 +360,6 @@ func (c *Controller) UpdateDevice(ctx *gin.Context) { return } - // 2. 绑定请求参数 var req UpdateDeviceRequest if err := ctx.ShouldBindJSON(&req); err != nil { c.logger.Errorf("%s: 参数绑定失败: %v", actionType, err) @@ -313,33 +374,25 @@ func (c *Controller) UpdateDevice(ctx *gin.Context) { return } - // 3. 更新从数据库中查出的现有设备对象的字段 existingDevice.Name = req.Name existingDevice.DeviceTemplateID = req.DeviceTemplateID existingDevice.AreaControllerID = req.AreaControllerID existingDevice.Location = req.Location existingDevice.Properties = propertiesJSON - // 在更新设备前进行自检 - // 注意:这里的 SelfCheck 依赖于 DeviceTemplate 和 AreaController 字段, - // 但在更新时这些关联对象可能尚未完全加载。如果 SelfCheck 内部需要这些关联对象, - // 则需要在调用 SelfCheck 之前手动加载或调整 SelfCheck 逻辑。 - // 目前假设 SelfCheck 仅检查 ID 和 Properties。 if err := existingDevice.SelfCheck(); err != nil { c.logger.Errorf("%s: 设备属性自检失败: %v", actionType, err) controller.SendErrorWithAudit(ctx, controller.CodeBadRequest, "设备属性不符合要求: "+err.Error(), actionType, "设备属性自检失败", existingDevice) return } - // 4. 将修改后的 existingDevice 对象保存回数据库 - if err := c.repo.Update(existingDevice); err != nil { + if err := c.deviceRepo.Update(existingDevice); err != nil { c.logger.Errorf("%s: 数据库更新失败: %v, Device: %+v", actionType, err, existingDevice) controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "更新设备失败: "+err.Error(), actionType, "数据库更新失败", existingDevice) return } - // 为了在响应中包含 DeviceTemplateName 和 AreaControllerName,需要重新从数据库加载设备,并预加载关联。 - updatedDevice, err := c.repo.FindByID(existingDevice.ID) + updatedDevice, err := c.deviceRepo.FindByID(existingDevice.ID) if err != nil { c.logger.Errorf("%s: 重新加载更新的设备失败: %v", actionType, err) controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "设备更新成功,但重新加载设备失败", actionType, "重新加载设备失败", existingDevice) @@ -369,7 +422,6 @@ func (c *Controller) DeleteDevice(ctx *gin.Context) { const actionType = "删除设备" deviceID := ctx.Param("id") - // 我们需要先将字符串ID转换为uint,因为Delete方法需要uint类型 idUint, err := strconv.ParseUint(deviceID, 10, 64) if err != nil { c.logger.Errorf("%s: 设备ID格式错误: %v, ID: %s", actionType, err, deviceID) @@ -377,8 +429,7 @@ func (c *Controller) DeleteDevice(ctx *gin.Context) { return } - // 检查设备是否存在(可选,但通常在删除前会检查) - _, err = c.repo.FindByIDString(deviceID) + _, err = c.deviceRepo.FindByIDString(deviceID) if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { c.logger.Warnf("%s: 设备不存在, ID: %s", actionType, deviceID) @@ -390,7 +441,7 @@ func (c *Controller) DeleteDevice(ctx *gin.Context) { 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) controller.SendErrorWithAudit(ctx, controller.CodeInternalError, "删除设备失败: "+err.Error(), actionType, "数据库删除失败", deviceID) return @@ -399,3 +450,245 @@ func (c *Controller) DeleteDevice(ctx *gin.Context) { c.logger.Infof("%s: 设备删除成功, ID: %d", actionType, idUint) 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) +} diff --git a/internal/core/application.go b/internal/core/application.go index 3767c85..0b317de 100644 --- a/internal/core/application.go +++ b/internal/core/application.go @@ -129,6 +129,7 @@ func NewApplication(configPath string) (*Application, error) { logger, userRepo, deviceRepo, + areaControllerRepo, planRepo, userActionLogRepo, tokenService, diff --git a/internal/infra/repository/area_controller_repository.go b/internal/infra/repository/area_controller_repository.go index 15cb7c5..7730ca5 100644 --- a/internal/infra/repository/area_controller_repository.go +++ b/internal/infra/repository/area_controller_repository.go @@ -1,6 +1,8 @@ package repository import ( + "fmt" + "git.huangwc.com/pig/pig-farm-controller/internal/infra/models" "gorm.io/gorm" ) @@ -8,7 +10,11 @@ import ( // AreaControllerRepository 定义了对 AreaController 模型的数据库操作接口 type AreaControllerRepository interface { 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 实现。 @@ -21,6 +27,48 @@ func NewGormAreaControllerRepository(db *gorm.DB) AreaControllerRepository { 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。 func (r *gormAreaControllerRepository) FindByID(id uint) (*models.AreaController, error) { var areaController models.AreaController