344 lines
10 KiB
Go
344 lines
10 KiB
Go
package controllers
|
||
|
||
import (
|
||
"encoding/json"
|
||
"errors"
|
||
"hr_receiver/config"
|
||
"hr_receiver/models"
|
||
"net/http"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
"gorm.io/gorm"
|
||
)
|
||
|
||
// GatewayAdminController 处理网关相关的HTTP请求
|
||
type GatewayAdminController struct {
|
||
DB *gorm.DB
|
||
}
|
||
|
||
// gatewayPayload 用于接收前端JSON数据的结构体
|
||
type gatewayPayload struct {
|
||
MAC string `json:"mac"`
|
||
Name string `json:"name"`
|
||
RegionID uint32 `json:"regionId"`
|
||
Location string `json:"location"`
|
||
ProjectType string `json:"projectType"`
|
||
IsSold bool `json:"isSold"`
|
||
// SoldAt 是可选字段。如果 IsSold 为 true 且未传此字段,后端自动设为当前时间
|
||
SoldAt *time.Time `json:"soldAt"`
|
||
}
|
||
|
||
// NewGatewayAdminController 初始化控制器
|
||
func NewGatewayAdminController() *GatewayAdminController {
|
||
return &GatewayAdminController{DB: config.DB}
|
||
}
|
||
|
||
// List 获取网关列表
|
||
// GET /api/gateways?keyword=&isSold=&projectType=®ionId=
|
||
func (gc *GatewayAdminController) List(c *gin.Context) {
|
||
var items []models.Gateway
|
||
query := gc.DB.Model(&models.Gateway{}).Order("region_id ASC, created_at DESC")
|
||
|
||
// 模糊搜索
|
||
if keyword := strings.TrimSpace(c.Query("keyword")); keyword != "" {
|
||
likeValue := "%" + keyword + "%"
|
||
query = query.Where("name LIKE ? OR mac LIKE ?", likeValue, likeValue)
|
||
}
|
||
|
||
// 按售出状态筛选 (可选)
|
||
if soldStr := c.Query("isSold"); soldStr != "" {
|
||
isSold, _ := strconv.ParseBool(soldStr)
|
||
query = query.Where("is_sold = ?", isSold)
|
||
}
|
||
|
||
// 按项目类型筛选 (可选)
|
||
if projectType := c.Query("projectType"); projectType != "" {
|
||
query = query.Where("project_type = ?", projectType)
|
||
}
|
||
if regionIDStr := strings.TrimSpace(c.Query("regionId")); regionIDStr != "" {
|
||
regionID, err := strconv.ParseUint(regionIDStr, 10, 32)
|
||
if err != nil || regionID == 0 {
|
||
writeError(c, http.StatusBadRequest, "regionId参数无效")
|
||
return
|
||
}
|
||
query = query.Where("region_id = ?", uint32(regionID))
|
||
}
|
||
|
||
if err := query.Find(&items).Error; err != nil {
|
||
writeError(c, http.StatusInternalServerError, "查询网关列表失败")
|
||
return
|
||
}
|
||
writeSuccess(c, http.StatusOK, "查询成功", items)
|
||
}
|
||
|
||
// Create 创建新网关
|
||
// POST /api/gateways
|
||
func (gc *GatewayAdminController) Create(c *gin.Context) {
|
||
var payload gatewayPayload
|
||
if err := c.ShouldBindJSON(&payload); err != nil {
|
||
writeError(c, http.StatusBadRequest, err.Error())
|
||
return
|
||
}
|
||
|
||
if err := validateGatewayPayload(payload); err != nil {
|
||
writeError(c, http.StatusBadRequest, err.Error())
|
||
return
|
||
}
|
||
projectType, err := gc.findGatewayProjectType(payload.ProjectType)
|
||
if err != nil {
|
||
writeError(c, http.StatusBadRequest, err.Error())
|
||
return
|
||
}
|
||
|
||
// 处理售出逻辑:如果已售出但未提供时间,默认为当前时间 (2026-04-29)
|
||
var soldAt *time.Time
|
||
if payload.IsSold {
|
||
if payload.SoldAt == nil {
|
||
// 设置为指定时间:2020-04-29
|
||
specificTime := time.Date(2026, 4, 29, 0, 0, 0, 0, time.Local)
|
||
soldAt = &specificTime
|
||
} else {
|
||
soldAt = payload.SoldAt
|
||
}
|
||
}
|
||
|
||
record := models.Gateway{
|
||
MAC: strings.ToUpper(strings.TrimSpace(payload.MAC)),
|
||
Name: strings.TrimSpace(payload.Name),
|
||
RegionID: payload.RegionID,
|
||
Location: strings.TrimSpace(payload.Location),
|
||
ProjectType: projectType.Code,
|
||
IsSold: payload.IsSold,
|
||
SoldAt: soldAt,
|
||
}
|
||
|
||
if err := gc.DB.Create(&record).Error; err != nil {
|
||
if strings.Contains(strings.ToLower(err.Error()), "duplicate") {
|
||
writeError(c, http.StatusConflict, "该MAC地址的网关已存在")
|
||
return
|
||
}
|
||
writeError(c, http.StatusInternalServerError, "保存网关失败")
|
||
return
|
||
}
|
||
if err := gc.syncGatewayInventory(record); err != nil {
|
||
writeError(c, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
writeSuccess(c, http.StatusCreated, "创建成功", record)
|
||
}
|
||
|
||
// Update 更新网关信息
|
||
// PUT /api/gateways/:id
|
||
func (gc *GatewayAdminController) Update(c *gin.Context) {
|
||
record, err := gc.findByID(c.Param("id"))
|
||
if err != nil {
|
||
respondGatewayLookupError(c, err)
|
||
return
|
||
}
|
||
|
||
var payload gatewayPayload
|
||
if err := c.ShouldBindJSON(&payload); err != nil {
|
||
writeError(c, http.StatusBadRequest, err.Error())
|
||
return
|
||
}
|
||
|
||
if err := validateGatewayPayload(payload); err != nil {
|
||
writeError(c, http.StatusBadRequest, err.Error())
|
||
return
|
||
}
|
||
projectType, err := gc.findGatewayProjectType(payload.ProjectType)
|
||
if err != nil {
|
||
writeError(c, http.StatusBadRequest, err.Error())
|
||
return
|
||
}
|
||
|
||
// 更新字段
|
||
record.Name = strings.TrimSpace(payload.Name)
|
||
record.RegionID = payload.RegionID
|
||
record.Location = strings.TrimSpace(payload.Location)
|
||
record.ProjectType = projectType.Code
|
||
record.IsSold = payload.IsSold
|
||
|
||
// 核心逻辑:处理售出时间
|
||
// 情况1: 标记为已售出,但 SoldAt 为空 -> 自动填充指定时间 (2026-04-29)
|
||
// 情况2: 标记为已售出,且提供了 SoldAt -> 使用提供的值
|
||
// 情况3: 标记为未售出 -> 强制 SoldAt 为 nil (防止数据残留)
|
||
if payload.IsSold {
|
||
if payload.SoldAt == nil {
|
||
specificTime := time.Date(2026, 4, 29, 0, 0, 0, 0, time.Local)
|
||
record.SoldAt = &specificTime
|
||
} else {
|
||
record.SoldAt = payload.SoldAt
|
||
}
|
||
} else {
|
||
record.SoldAt = nil
|
||
}
|
||
|
||
if err := gc.DB.Save(&record).Error; err != nil {
|
||
writeError(c, http.StatusInternalServerError, "更新网关失败")
|
||
return
|
||
}
|
||
if err := gc.syncGatewayInventory(record); err != nil {
|
||
writeError(c, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
writeSuccess(c, http.StatusOK, "更新成功", record)
|
||
}
|
||
|
||
// Delete 删除网关
|
||
// DELETE /api/gateways/:id
|
||
func (gc *GatewayAdminController) Delete(c *gin.Context) {
|
||
record, err := gc.findByID(c.Param("id"))
|
||
if err != nil {
|
||
respondGatewayLookupError(c, err)
|
||
return
|
||
}
|
||
|
||
// 建议:如果网关已售出,禁止删除,防止数据丢失
|
||
if record.IsSold {
|
||
writeError(c, http.StatusForbidden, "已售出的网关禁止删除")
|
||
return
|
||
}
|
||
|
||
if err := gc.DB.Delete(&record).Error; err != nil {
|
||
writeError(c, http.StatusInternalServerError, "删除网关失败")
|
||
return
|
||
}
|
||
if err := gc.deleteGatewayInventory(record); err != nil {
|
||
writeError(c, http.StatusInternalServerError, err.Error())
|
||
return
|
||
}
|
||
writeSuccess(c, http.StatusOK, "删除成功", nil)
|
||
}
|
||
|
||
// 辅助方法:根据ID查找记录
|
||
func (gc *GatewayAdminController) findByID(id string) (models.Gateway, error) {
|
||
var record models.Gateway
|
||
numericID, err := strconv.ParseUint(strings.TrimSpace(id), 10, 64)
|
||
if err != nil {
|
||
return record, gorm.ErrRecordNotFound
|
||
}
|
||
if err := gc.DB.First(&record, numericID).Error; err != nil {
|
||
return record, err
|
||
}
|
||
return record, nil
|
||
}
|
||
|
||
func (gc *GatewayAdminController) findGatewayProjectType(projectTypeCode string) (models.ProjectType, error) {
|
||
var projectType models.ProjectType
|
||
code := strings.TrimSpace(strings.ToLower(projectTypeCode))
|
||
if code == "" {
|
||
return projectType, errors.New("projectType is required")
|
||
}
|
||
if err := gc.DB.Where("code = ? AND is_active = ?", code, true).First(&projectType).Error; err != nil {
|
||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||
return projectType, errors.New("projectType is invalid")
|
||
}
|
||
return projectType, errors.New("failed to query project type")
|
||
}
|
||
if !projectType.SupportsGateway {
|
||
return projectType, errors.New("current project type does not support gateways")
|
||
}
|
||
return projectType, nil
|
||
}
|
||
|
||
func (gc *GatewayAdminController) syncGatewayInventory(record models.Gateway) error {
|
||
mac := strings.ToUpper(strings.TrimSpace(record.MAC))
|
||
if mac == "" {
|
||
return nil
|
||
}
|
||
|
||
parameterValuesBytes, err := json.Marshal(map[string]interface{}{
|
||
"mac": mac,
|
||
"location": strings.TrimSpace(record.Location),
|
||
})
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
status := models.ProductInventoryStatusInStock
|
||
if record.IsSold {
|
||
status = models.ProductInventoryStatusSold
|
||
}
|
||
|
||
serialNumber := mac
|
||
regionID := record.RegionID
|
||
|
||
var inventory models.ProductInventory
|
||
err = gc.DB.Where("source_type = ? AND source_ref = ?", "gateway", mac).First(&inventory).Error
|
||
switch {
|
||
case err == nil:
|
||
inventory.ProductCode = "collection_gateway"
|
||
inventory.ProjectTypeCode = strings.TrimSpace(strings.ToLower(record.ProjectType))
|
||
inventory.SerialNumber = &serialNumber
|
||
inventory.AssetName = strings.TrimSpace(record.Name)
|
||
inventory.Status = status
|
||
inventory.RegionID = ®ionID
|
||
inventory.StorageLocation = strings.TrimSpace(record.Location)
|
||
inventory.ParameterValues = string(parameterValuesBytes)
|
||
inventory.SourceType = "gateway"
|
||
inventory.SourceRef = mac
|
||
if record.IsSold {
|
||
inventory.SoldAt = record.SoldAt
|
||
} else {
|
||
inventory.SoldAt = nil
|
||
}
|
||
return gc.DB.Save(&inventory).Error
|
||
case errors.Is(err, gorm.ErrRecordNotFound):
|
||
inventory = models.ProductInventory{
|
||
ProductCode: "collection_gateway",
|
||
ProjectTypeCode: strings.TrimSpace(strings.ToLower(record.ProjectType)),
|
||
SerialNumber: &serialNumber,
|
||
AssetName: strings.TrimSpace(record.Name),
|
||
Status: status,
|
||
RegionID: ®ionID,
|
||
StorageLocation: strings.TrimSpace(record.Location),
|
||
SoldAt: record.SoldAt,
|
||
ParameterValues: string(parameterValuesBytes),
|
||
SourceType: "gateway",
|
||
SourceRef: mac,
|
||
}
|
||
return gc.DB.Create(&inventory).Error
|
||
default:
|
||
return err
|
||
}
|
||
}
|
||
|
||
func (gc *GatewayAdminController) deleteGatewayInventory(record models.Gateway) error {
|
||
mac := strings.ToUpper(strings.TrimSpace(record.MAC))
|
||
if mac == "" {
|
||
return nil
|
||
}
|
||
return gc.DB.Where("source_type = ? AND source_ref = ?", "gateway", mac).Delete(&models.ProductInventory{}).Error
|
||
}
|
||
|
||
// 辅助方法:验证输入数据
|
||
func validateGatewayPayload(payload gatewayPayload) error {
|
||
mac := strings.TrimSpace(payload.MAC)
|
||
if mac == "" {
|
||
return errors.New("MAC地址是必填项")
|
||
}
|
||
if len(mac) < 12 {
|
||
return errors.New("MAC地址格式不正确")
|
||
}
|
||
if strings.TrimSpace(payload.Name) == "" {
|
||
return errors.New("网关名称是必填项")
|
||
}
|
||
if strings.TrimSpace(payload.ProjectType) == "" {
|
||
return errors.New("项目类型是必填项")
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// 错误处理函数
|
||
func respondGatewayLookupError(c *gin.Context, err error) {
|
||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||
writeError(c, http.StatusNotFound, "未找到该网关")
|
||
return
|
||
}
|
||
writeError(c, http.StatusInternalServerError, "查询网关时出错")
|
||
}
|