xu преди 4 месеца
родител
ревизия
d8e35b8f65
променени са 43 файла, в които са добавени 1295 реда и са изтрити 528 реда
  1. 6 13
      server/api/v1/parking/booth.go
  2. 6 13
      server/api/v1/parking/channel.go
  3. 0 148
      server/api/v1/parking/device.go
  4. 5 28
      server/api/v1/parking/enter.go
  5. 6 13
      server/api/v1/parking/parking_lot.go
  6. 1 1
      server/api/v1/vehicle/vehicle.go
  7. 1 1
      server/config.yaml
  8. 6 4
      server/dao/booth.go
  9. 22 0
      server/dao/camera.go
  10. 3 1
      server/dao/channel.go
  11. 0 18
      server/dao/device.go
  12. 9 5
      server/dao/parking_lot.go
  13. 30 0
      server/dao/uhf_reader.go
  14. 1 0
      server/dao/vehicle.go
  15. 1 0
      server/go.mod
  16. 2 0
      server/go.sum
  17. 2 1
      server/initialize/gorm.go
  18. 57 0
      server/initialize/reader_init.go
  19. 2 0
      server/initialize/router.go
  20. 25 0
      server/initialize/uhf.go
  21. 1 0
      server/main.go
  22. 7 7
      server/model/owner/request/owner.go
  23. 19 13
      server/model/parking/request/booth.go
  24. 22 16
      server/model/parking/request/channel.go
  25. 0 29
      server/model/parking/request/device.go
  26. 16 13
      server/model/parking/request/parking_lot.go
  27. 0 12
      server/model/parking/response/device.go
  28. 60 0
      server/model/uhf/request/reader.go
  29. 19 0
      server/model/uhf/response/reader.go
  30. 15 15
      server/model/vehicle/request/fee_config.go
  31. 11 9
      server/model/vehicle/request/vehicle.go
  32. 0 16
      server/router/parking/parking.go
  33. BIN
      server/server
  34. 0 138
      server/service/parking/device.go
  35. 0 8
      server/service/parking/enter.go
  36. 94 0
      server/service/uhf/conn_manager.go
  37. 230 0
      server/service/uhf/reader.go
  38. 53 0
      server/service/uhf/reader_interface.go
  39. 123 0
      server/service/uhf/serial_reader.go
  40. 86 0
      server/service/uhf/tcp_reader.go
  41. 6 6
      server/service/vehicle/vehicle.go
  42. 100 0
      server/test/tcp_server_test.go
  43. 248 0
      server/test/uhf_reader_test.go

+ 6 - 13
server/api/v1/parking/booth.go

@@ -5,12 +5,10 @@ import (
 	"server/model/common/response"
 	"server/model/parking/request"
 	parkingResponse "server/model/parking/response"
-	"server/service/parking"
 
 	"github.com/gin-gonic/gin"
 )
 
-// CreateBooth 创建岗亭
 func CreateBooth(c *gin.Context) {
 	var req request.BoothCreate
 	if err := c.ShouldBindJSON(&req); err != nil {
@@ -18,7 +16,7 @@ func CreateBooth(c *gin.Context) {
 		return
 	}
 
-	err := parking.BoothService.CreateBooth(req)
+	err := BoothService.CreateBooth(req)
 	if err != nil {
 		response.FailWithMessage(err.Error(), c)
 		return
@@ -27,7 +25,6 @@ func CreateBooth(c *gin.Context) {
 	response.OkWithMessage("创建成功", c)
 }
 
-// UpdateBooth 更新岗亭
 func UpdateBooth(c *gin.Context) {
 	var req request.BoothUpdate
 	if err := c.ShouldBindJSON(&req); err != nil {
@@ -35,7 +32,7 @@ func UpdateBooth(c *gin.Context) {
 		return
 	}
 
-	err := parking.BoothService.UpdateBooth(req)
+	err := BoothService.UpdateBooth(req)
 	if err != nil {
 		response.FailWithMessage(err.Error(), c)
 		return
@@ -44,7 +41,6 @@ func UpdateBooth(c *gin.Context) {
 	response.OkWithMessage("更新成功", c)
 }
 
-// GetBoothByID 根据ID获取岗亭
 func GetBoothByID(c *gin.Context) {
 	id := c.Query("id")
 	uintID := uint(0)
@@ -54,7 +50,7 @@ func GetBoothByID(c *gin.Context) {
 		return
 	}
 
-	booth, err := parking.BoothService.GetBoothByID(uintID)
+	booth, err := BoothService.GetBoothByID(uintID)
 	if err != nil {
 		response.FailWithMessage(err.Error(), c)
 		return
@@ -63,7 +59,6 @@ func GetBoothByID(c *gin.Context) {
 	response.OkWithData(booth, c)
 }
 
-// GetBoothByCode 根据编码获取岗亭
 func GetBoothByCode(c *gin.Context) {
 	code := c.Query("code")
 	if code == "" {
@@ -71,7 +66,7 @@ func GetBoothByCode(c *gin.Context) {
 		return
 	}
 
-	booth, err := parking.BoothService.GetBoothByCode(code)
+	booth, err := BoothService.GetBoothByCode(code)
 	if err != nil {
 		response.FailWithMessage(err.Error(), c)
 		return
@@ -80,7 +75,6 @@ func GetBoothByCode(c *gin.Context) {
 	response.OkWithData(booth, c)
 }
 
-// ListBooths 获取岗亭列表
 func ListBooths(c *gin.Context) {
 	var req request.BoothQuery
 	if err := c.ShouldBindQuery(&req); err != nil {
@@ -88,7 +82,7 @@ func ListBooths(c *gin.Context) {
 		return
 	}
 
-	total, list, err := parking.BoothService.ListBooths(req)
+	total, list, err := BoothService.ListBooths(req)
 	if err != nil {
 		response.FailWithMessage(err.Error(), c)
 		return
@@ -100,7 +94,6 @@ func ListBooths(c *gin.Context) {
 	}, c)
 }
 
-// DeleteBooth 删除岗亭
 func DeleteBooth(c *gin.Context) {
 	id := c.Query("id")
 	uintID := uint(0)
@@ -110,7 +103,7 @@ func DeleteBooth(c *gin.Context) {
 		return
 	}
 
-	err = parking.BoothService.DeleteBooth(uintID)
+	err = BoothService.DeleteBooth(uintID)
 	if err != nil {
 		response.FailWithMessage(err.Error(), c)
 		return

+ 6 - 13
server/api/v1/parking/channel.go

@@ -5,12 +5,10 @@ import (
 	"server/model/common/response"
 	"server/model/parking/request"
 	parkingResponse "server/model/parking/response"
-	"server/service/parking"
 
 	"github.com/gin-gonic/gin"
 )
 
-// CreateChannel 创建通道
 func CreateChannel(c *gin.Context) {
 	var req request.ChannelCreate
 	if err := c.ShouldBindJSON(&req); err != nil {
@@ -18,7 +16,7 @@ func CreateChannel(c *gin.Context) {
 		return
 	}
 
-	err := parking.ChannelService.CreateChannel(req)
+	err := ChannelService.CreateChannel(req)
 	if err != nil {
 		response.FailWithMessage(err.Error(), c)
 		return
@@ -27,7 +25,6 @@ func CreateChannel(c *gin.Context) {
 	response.OkWithMessage("创建成功", c)
 }
 
-// UpdateChannel 更新通道
 func UpdateChannel(c *gin.Context) {
 	var req request.ChannelUpdate
 	if err := c.ShouldBindJSON(&req); err != nil {
@@ -35,7 +32,7 @@ func UpdateChannel(c *gin.Context) {
 		return
 	}
 
-	err := parking.ChannelService.UpdateChannel(req)
+	err := ChannelService.UpdateChannel(req)
 	if err != nil {
 		response.FailWithMessage(err.Error(), c)
 		return
@@ -44,7 +41,6 @@ func UpdateChannel(c *gin.Context) {
 	response.OkWithMessage("更新成功", c)
 }
 
-// GetChannelByID 根据ID获取通道
 func GetChannelByID(c *gin.Context) {
 	id := c.Query("id")
 	uintID := uint(0)
@@ -54,7 +50,7 @@ func GetChannelByID(c *gin.Context) {
 		return
 	}
 
-	channel, err := parking.ChannelService.GetChannelByID(uintID)
+	channel, err := ChannelService.GetChannelByID(uintID)
 	if err != nil {
 		response.FailWithMessage(err.Error(), c)
 		return
@@ -63,7 +59,6 @@ func GetChannelByID(c *gin.Context) {
 	response.OkWithData(channel, c)
 }
 
-// GetChannelByCode 根据编码获取通道
 func GetChannelByCode(c *gin.Context) {
 	code := c.Query("code")
 	if code == "" {
@@ -71,7 +66,7 @@ func GetChannelByCode(c *gin.Context) {
 		return
 	}
 
-	channel, err := parking.ChannelService.GetChannelByCode(code)
+	channel, err := ChannelService.GetChannelByCode(code)
 	if err != nil {
 		response.FailWithMessage(err.Error(), c)
 		return
@@ -80,7 +75,6 @@ func GetChannelByCode(c *gin.Context) {
 	response.OkWithData(channel, c)
 }
 
-// ListChannels 获取通道列表
 func ListChannels(c *gin.Context) {
 	var req request.ChannelQuery
 	if err := c.ShouldBindQuery(&req); err != nil {
@@ -88,7 +82,7 @@ func ListChannels(c *gin.Context) {
 		return
 	}
 
-	total, list, err := parking.ChannelService.ListChannels(req)
+	total, list, err := ChannelService.ListChannels(req)
 	if err != nil {
 		response.FailWithMessage(err.Error(), c)
 		return
@@ -100,7 +94,6 @@ func ListChannels(c *gin.Context) {
 	}, c)
 }
 
-// DeleteChannel 删除通道
 func DeleteChannel(c *gin.Context) {
 	id := c.Query("id")
 	uintID := uint(0)
@@ -110,7 +103,7 @@ func DeleteChannel(c *gin.Context) {
 		return
 	}
 
-	err = parking.ChannelService.DeleteChannel(uintID)
+	err = ChannelService.DeleteChannel(uintID)
 	if err != nil {
 		response.FailWithMessage(err.Error(), c)
 		return

+ 0 - 148
server/api/v1/parking/device.go

@@ -1,148 +0,0 @@
-package parking
-
-import (
-	"fmt"
-	"server/model/common/response"
-	"server/model/parking/request"
-	parkingResponse "server/model/parking/response"
-	"server/service/parking"
-
-	"github.com/gin-gonic/gin"
-)
-
-// CreateDevice 创建设备
-func CreateDevice(c *gin.Context) {
-	var req request.DeviceCreate
-	if err := c.ShouldBindJSON(&req); err != nil {
-		response.FailWithMessage(err.Error(), c)
-		return
-	}
-
-	err := parking.DeviceService.CreateDevice(req)
-	if err != nil {
-		response.FailWithMessage(err.Error(), c)
-		return
-	}
-
-	response.OkWithMessage("创建成功", c)
-}
-
-// UpdateDevice 更新设备
-func UpdateDevice(c *gin.Context) {
-	var req request.DeviceUpdate
-	if err := c.ShouldBindJSON(&req); err != nil {
-		response.FailWithMessage(err.Error(), c)
-		return
-	}
-
-	err := parking.DeviceService.UpdateDevice(req)
-	if err != nil {
-		response.FailWithMessage(err.Error(), c)
-		return
-	}
-
-	response.OkWithMessage("更新成功", c)
-}
-
-// GetDeviceByID 根据ID获取设备
-func GetDeviceByID(c *gin.Context) {
-	id := c.Query("id")
-	uintID := uint(0)
-	_, err := fmt.Sscanf(id, "%d", &uintID)
-	if err != nil {
-		response.FailWithMessage("无效的ID", c)
-		return
-	}
-
-	device, err := parking.DeviceService.GetDeviceByID(uintID)
-	if err != nil {
-		response.FailWithMessage(err.Error(), c)
-		return
-	}
-
-	response.OkWithData(device, c)
-}
-
-// GetDeviceByCode 根据编码获取设备
-func GetDeviceByCode(c *gin.Context) {
-	code := c.Query("code")
-	if code == "" {
-		response.FailWithMessage("设备编码不能为空", c)
-		return
-	}
-
-	device, err := parking.DeviceService.GetDeviceByCode(code)
-	if err != nil {
-		response.FailWithMessage(err.Error(), c)
-		return
-	}
-
-	response.OkWithData(device, c)
-}
-
-// ListDevices 获取设备列表
-func ListDevices(c *gin.Context) {
-	var req request.DeviceQuery
-	if err := c.ShouldBindQuery(&req); err != nil {
-		response.FailWithMessage(err.Error(), c)
-		return
-	}
-
-	total, list, err := parking.DeviceService.ListDevices(req)
-	if err != nil {
-		response.FailWithMessage(err.Error(), c)
-		return
-	}
-
-	response.OkWithData(parkingResponse.DeviceList{
-		Total: total,
-		List:  list,
-	}, c)
-}
-
-// DeleteDevice 删除设备
-func DeleteDevice(c *gin.Context) {
-	id := c.Query("id")
-	uintID := uint(0)
-	_, err := fmt.Sscanf(id, "%d", &uintID)
-	if err != nil {
-		response.FailWithMessage("无效的ID", c)
-		return
-	}
-
-	err = parking.DeviceService.DeleteDevice(uintID)
-	if err != nil {
-		response.FailWithMessage(err.Error(), c)
-		return
-	}
-
-	response.OkWithMessage("删除成功", c)
-}
-
-// UpdateDeviceStatus 更新设备状态
-func UpdateDeviceStatus(c *gin.Context) {
-	id := c.Query("id")
-	uintID := uint(0)
-	_, err := fmt.Sscanf(id, "%d", &uintID)
-	if err != nil {
-		response.FailWithMessage("无效的ID", c)
-		return
-	}
-
-	var req struct {
-		Status string `json:"status" binding:"required"`
-	}
-
-	if err := c.ShouldBindJSON(&req); err != nil {
-		response.FailWithMessage(err.Error(), c)
-		return
-	}
-
-	err = parking.DeviceService.UpdateDeviceStatus(uintID, req.Status)
-	if err != nil {
-		response.FailWithMessage(err.Error(), c)
-		return
-	}
-
-	response.OkWithMessage("状态更新成功", c)
-}

+ 5 - 28
server/api/v1/parking/enter.go

@@ -1,30 +1,7 @@
 package parking
 
-// 停车相关API
-var ApiGroup = []interface{}{
-	CreateParkingLot,
-	UpdateParkingLot,
-	GetParkingLotByID,
-	GetParkingLotByCode,
-	ListParkingLots,
-	DeleteParkingLot,
-	CreateBooth,
-	UpdateBooth,
-	GetBoothByID,
-	GetBoothByCode,
-	ListBooths,
-	DeleteBooth,
-	CreateChannel,
-	UpdateChannel,
-	GetChannelByID,
-	GetChannelByCode,
-	ListChannels,
-	DeleteChannel,
-	CreateDevice,
-	UpdateDevice,
-	GetDeviceByID,
-	GetDeviceByCode,
-	ListDevices,
-	DeleteDevice,
-	UpdateDeviceStatus,
-}
+import parkingService "server/service/parking"
+
+var BoothService = parkingService.NewBoothService()
+var ChannelService = parkingService.NewChannelService()
+var ParkingLotService = parkingService.NewParkingLotService()

+ 6 - 13
server/api/v1/parking/parking_lot.go

@@ -5,12 +5,10 @@ import (
 	"server/model/common/response"
 	"server/model/parking/request"
 	parkingResponse "server/model/parking/response"
-	"server/service/parking"
 
 	"github.com/gin-gonic/gin"
 )
 
-// CreateParkingLot 创建停车区域
 func CreateParkingLot(c *gin.Context) {
 	var req request.ParkingLotCreate
 	if err := c.ShouldBindJSON(&req); err != nil {
@@ -18,7 +16,7 @@ func CreateParkingLot(c *gin.Context) {
 		return
 	}
 
-	err := parking.ParkingLotService.CreateParkingLot(req)
+	err := ParkingLotService.CreateParkingLot(req)
 	if err != nil {
 		response.FailWithMessage(err.Error(), c)
 		return
@@ -27,7 +25,6 @@ func CreateParkingLot(c *gin.Context) {
 	response.OkWithMessage("创建成功", c)
 }
 
-// UpdateParkingLot 更新停车区域
 func UpdateParkingLot(c *gin.Context) {
 	var req request.ParkingLotUpdate
 	if err := c.ShouldBindJSON(&req); err != nil {
@@ -35,7 +32,7 @@ func UpdateParkingLot(c *gin.Context) {
 		return
 	}
 
-	err := parking.ParkingLotService.UpdateParkingLot(req)
+	err := ParkingLotService.UpdateParkingLot(req)
 	if err != nil {
 		response.FailWithMessage(err.Error(), c)
 		return
@@ -44,7 +41,6 @@ func UpdateParkingLot(c *gin.Context) {
 	response.OkWithMessage("更新成功", c)
 }
 
-// GetParkingLotByID 根据ID获取停车区域
 func GetParkingLotByID(c *gin.Context) {
 	id := c.Query("id")
 	uintID := uint(0)
@@ -54,7 +50,7 @@ func GetParkingLotByID(c *gin.Context) {
 		return
 	}
 
-	lot, err := parking.ParkingLotService.GetParkingLotByID(uintID)
+	lot, err := ParkingLotService.GetParkingLotByID(uintID)
 	if err != nil {
 		response.FailWithMessage(err.Error(), c)
 		return
@@ -63,7 +59,6 @@ func GetParkingLotByID(c *gin.Context) {
 	response.OkWithData(lot, c)
 }
 
-// GetParkingLotByCode 根据编码获取停车区域
 func GetParkingLotByCode(c *gin.Context) {
 	code := c.Query("code")
 	if code == "" {
@@ -71,7 +66,7 @@ func GetParkingLotByCode(c *gin.Context) {
 		return
 	}
 
-	lot, err := parking.ParkingLotService.GetParkingLotByCode(code)
+	lot, err := ParkingLotService.GetParkingLotByCode(code)
 	if err != nil {
 		response.FailWithMessage(err.Error(), c)
 		return
@@ -80,7 +75,6 @@ func GetParkingLotByCode(c *gin.Context) {
 	response.OkWithData(lot, c)
 }
 
-// ListParkingLots 获取停车区域列表
 func ListParkingLots(c *gin.Context) {
 	var req request.ParkingLotQuery
 	if err := c.ShouldBindQuery(&req); err != nil {
@@ -88,7 +82,7 @@ func ListParkingLots(c *gin.Context) {
 		return
 	}
 
-	total, list, err := parking.ParkingLotService.ListParkingLots(req)
+	total, list, err := ParkingLotService.ListParkingLots(req)
 	if err != nil {
 		response.FailWithMessage(err.Error(), c)
 		return
@@ -100,7 +94,6 @@ func ListParkingLots(c *gin.Context) {
 	}, c)
 }
 
-// DeleteParkingLot 删除停车区域
 func DeleteParkingLot(c *gin.Context) {
 	id := c.Query("id")
 	uintID := uint(0)
@@ -110,7 +103,7 @@ func DeleteParkingLot(c *gin.Context) {
 		return
 	}
 
-	err = parking.ParkingLotService.DeleteParkingLot(uintID)
+	err = ParkingLotService.DeleteParkingLot(uintID)
 	if err != nil {
 		response.FailWithMessage(err.Error(), c)
 		return

+ 1 - 1
server/api/v1/vehicle/vehicle.go

@@ -71,7 +71,7 @@ func GetVehicleByPlateNumber(c *gin.Context) {
 		return
 	}
 
-	vehicleInfo, err := vehicle.VehicleService.GetVehicleByPlateNumber(plateNumber)
+	vehicleInfo, err := vehicle.VehicleService.GetVehicleByPlateNumber(plateNumber, "")
 	if err != nil {
 		response.FailWithMessage(err.Error(), c)
 		return

+ 1 - 1
server/config.yaml

@@ -125,7 +125,7 @@ mysql:
     config: charset=utf8mb4&parseTime=True&loc=Local
     db-name: lc_garage
     username: root
-    password: 123456
+    password: root
     path: 127.0.0.1
     engine: ""
     log-mode: error

+ 6 - 4
server/dao/booth.go

@@ -2,12 +2,14 @@ package dao
 
 import "server/global"
 
+// Booth 岗亭模型
 type Booth struct {
 	global.GVA_MODEL
-	BoothCode   string `gorm:"size:50;not null;uniqueIndex" json:"booth_code"` // 岗亭编码
-	BoothName   string `gorm:"size:100;not null" json:"booth_name"`            // 岗亭名称
-	IPAddress   string `gorm:"size:50" json:"ip_address"`                      // IP地址
-	Description string `gorm:"size:200" json:"description"`                    // 备注
+	BoothCode    string `gorm:"size:50;not null;uniqueIndex" json:"booth_code"` // 岗亭编码
+	BoothName    string `gorm:"size:100;not null" json:"booth_name"`            // 岗亭名称
+	IPAddress    string `gorm:"size:50" json:"ip_address"`                      // IP地址
+	Description  string `gorm:"size:200" json:"description"`                    // 备注
+	ParkingLotID uint   `json:"parking_lot_id"`                                 // 所属区域ID
 }
 
 func (Booth) TableName() string {

+ 22 - 0
server/dao/camera.go

@@ -0,0 +1,22 @@
+package dao
+
+import "server/global"
+
+// Camera 摄像头模型
+type Camera struct {
+	global.GVA_MODEL
+	DeviceCode  string `gorm:"size:50;not null;uniqueIndex" json:"device_code"` // 设备编码
+	DeviceName  string `gorm:"size:100;not null" json:"device_name"`            // 设备名称
+	IPAddress   string `gorm:"size:50;not null" json:"ip_address"`              // IP地址
+	Port        int    `gorm:"not null" json:"port"`                            // 端口
+	Username    string `gorm:"size:50" json:"username"`                         // 用户名
+	Password    string `gorm:"size:50" json:"password"`                         // 密码
+	Channel     int    `gorm:"default:1" json:"channel"`                        // 通道号
+	Status      string `gorm:"size:20;default:'offline'" json:"status"`         // 连接状态
+	IsActive    bool   `gorm:"default:true" json:"is_active"`                   // 是否启用
+	Description string `gorm:"size:200" json:"description"`                     // 备注
+}
+
+func (Camera) TableName() string {
+	return "camera"
+}

+ 3 - 1
server/dao/channel.go

@@ -2,13 +2,15 @@ package dao
 
 import "server/global"
 
+// Channel 通道模型
 type Channel struct {
 	global.GVA_MODEL
 	ChannelCode    string `gorm:"size:50;not null;uniqueIndex" json:"channel_code"` // 通道编码
 	ChannelName    string `gorm:"size:100;not null" json:"channel_name"`            // 通道名称
-	Direction      string `gorm:"size:20;not null" json:"direction"`                // 进出方向
+	Direction      string `gorm:"size:20;not null" json:"direction"`                // 进出方向 in/out/inout
 	AllowTemporary bool   `gorm:"not null" json:"allow_temporary"`                  // 是否允许临时车进出
 	Description    string `gorm:"size:200" json:"description"`                      // 备注
+	ParkingLotID   uint   `json:"parking_lot_id"`                                   // 所属区域ID
 }
 
 func (Channel) TableName() string {

+ 0 - 18
server/dao/device.go

@@ -1,18 +0,0 @@
-package dao
-
-import "server/global"
-
-type Device struct {
-	global.GVA_MODEL
-	DeviceCode  string `gorm:"size:50;not null;uniqueIndex" json:"device_code"` // 设备编码
-	DeviceName  string `gorm:"size:100;not null" json:"device_name"`            // 设备名称
-	DeviceType  string `gorm:"size:50;not null" json:"device_type"`             // 设备类别
-	IPAddress   string `gorm:"size:50" json:"ip_address"`                       // IP地址
-	Port        int    `json:"port"`                                            // 端口
-	Status      string `gorm:"size:20;default:'offline'" json:"status"`         // 设备状态
-	Description string `gorm:"size:200" json:"description"`                     // 备注
-}
-
-func (Device) TableName() string {
-	return "device"
-}

+ 9 - 5
server/dao/parking_lot.go

@@ -2,13 +2,17 @@ package dao
 
 import "server/global"
 
+// ParkingLot 停车区域模型
 type ParkingLot struct {
 	global.GVA_MODEL
-	LotCode     string `gorm:"size:50;not null;uniqueIndex" json:"lot_code"` // 区域编码
-	LotName     string `gorm:"size:100;not null" json:"lot_name"`            // 区域名称
-	Capacity    int    `gorm:"not null" json:"capacity"`                     // 车位数量
-	Available   int    `gorm:"not null" json:"available"`                    // 可用车位
-	Description string `gorm:"size:200" json:"description"`                  // 备注
+	LotCode     string      `gorm:"size:50;not null;uniqueIndex" json:"lot_code"` // 区域编码
+	LotName     string      `gorm:"size:100;not null" json:"lot_name"`            // 区域名称
+	Capacity    int         `gorm:"not null" json:"capacity"`                     // 车位数量
+	Available   int         `gorm:"not null" json:"available"`                    // 可用车位
+	Description string      `gorm:"size:200" json:"description"`                  // 备注
+	Booths      []Booth     `gorm:"foreignKey:ParkingLotID" json:"booths"`        // 岗亭列表(一对多)
+	Channels    []Channel   `gorm:"foreignKey:ParkingLotID" json:"channels"`      // 通道列表(一对多)
+	UHFReaders  []UHFReader `gorm:"foreignKey:ParkingLotID" json:"uhf_readers"`   // UHF读写器列表(一对多)
 }
 
 func (ParkingLot) TableName() string {

+ 30 - 0
server/dao/uhf_reader.go

@@ -0,0 +1,30 @@
+package dao
+
+import "server/global"
+
+type UHFReader struct {
+	global.GVA_MODEL
+
+	DeviceCode string `gorm:"size:50;not null;uniqueIndex" json:"device_code"`
+	DeviceName string `gorm:"size:100;not null" json:"device_name"`
+
+	// ========== 连接方式配置 ==========
+	ConnectType string `gorm:"size:20;not null;default:'tcp'" json:"connect_type"` // tcp / serial
+
+	// TCP 配置
+	IPAddress string `gorm:"size:50" json:"ip_address"`
+	Port      int    `gorm:"default:0" json:"port"`
+
+	// Serial(RS232) 配置
+	COMPort  string `gorm:"size:50" json:"com_port"`       // COM3 /dev/ttyUSB0
+	BaudRate int    `gorm:"default:9600" json:"baud_rate"` // 波特率
+
+	Status       string `gorm:"size:20;default:'offline'" json:"status"`
+	IsActive     bool   `gorm:"default:true" json:"is_active"`
+	Description  string `gorm:"size:200" json:"description"`
+	ParkingLotID uint   `json:"parking_lot_id"`
+}
+
+func (UHFReader) TableName() string {
+	return "uhf_reader"
+}

+ 1 - 0
server/dao/vehicle.go

@@ -8,6 +8,7 @@ import (
 type Vehicle struct {
 	global.GVA_MODEL
 	PlateNumber   string    `gorm:"size:20;not null;uniqueIndex" json:"plate_number"` // 车牌号
+	RFIDTag       string    `gorm:"size:20;not null;uniqueIndex" json:"rfid_tag"`     // 电子标签
 	VehicleType   string    `gorm:"size:20;not null" json:"vehicle_type"`             // 车辆类型
 	VehicleBrand  string    `gorm:"size:50" json:"vehicle_brand"`                     // 车辆品牌
 	VehicleColor  string    `gorm:"size:20" json:"vehicle_color"`                     // 车辆颜色

+ 1 - 0
server/go.mod

@@ -113,6 +113,7 @@ require (
 	github.com/spf13/cast v1.7.1 // indirect
 	github.com/spf13/pflag v1.0.5 // indirect
 	github.com/subosito/gotenv v1.6.0 // indirect
+	github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 // indirect
 	github.com/tklauser/go-sysconf v0.3.14 // indirect
 	github.com/tklauser/numcpus v0.9.0 // indirect
 	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect

+ 2 - 0
server/go.sum

@@ -329,6 +329,8 @@ github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+z
 github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo=
 github.com/swaggo/swag v1.16.4 h1:clWJtd9LStiG3VeijiCfOVODP6VpHtKdQy9ELFG3s1A=
 github.com/swaggo/swag v1.16.4/go.mod h1:VBsHJRsDvfYvqoiMKnsdwhNV9LEMHgEDZcyVYX0sxPg=
+github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07 h1:UyzmZLoiDWMRywV4DUYb9Fbt8uiOSooupjTq10vpvnU=
+github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA=
 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.563/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.563/go.mod h1:uom4Nvi9W+Qkom0exYiJ9VWJjXwyxtPYTkKkaLMlfE0=
 github.com/tencentyun/cos-go-sdk-v5 v0.7.60 h1:/e/tmvRmfKexr/QQIBzWhOkZWsmY3EK72NrI6G/Tv0o=

+ 2 - 1
server/initialize/gorm.go

@@ -50,11 +50,12 @@ func RegisterTables() {
 		dao.Vehicle{},
 		dao.Owner{},
 		dao.FeeConfig{},
-		dao.Device{},
 		dao.Booth{},
 		dao.Channel{},
 		dao.ParkingLot{},
 		dao.VehicleRecord{},
+		dao.UHFReader{},
+		dao.Camera{},
 	)
 	if err != nil {
 		global.GVA_LOG.Error("register table failed", zap.Error(err))

+ 57 - 0
server/initialize/reader_init.go

@@ -0,0 +1,57 @@
+package initialize
+
+import (
+	"log"
+	"server/dao"
+	"server/global"
+	"server/service/uhf"
+)
+
+// InitUHFReaders 批量初始化所有UHF设备(多设备)
+func InitUHFReaders() {
+	log.Println("开始批量初始化UHF设备连接...")
+
+	// 1. 查询所有启用的设备
+	var devices []dao.UHFReader
+	if err := global.GVA_DB.Where("is_active = ?", true).Find(&devices).Error; err != nil {
+		log.Printf("查询UHF设备失败:%v", err)
+		return
+	}
+
+	if len(devices) == 0 {
+		log.Println("无启用的UHF设备,跳过初始化")
+		return
+	}
+
+	// 2. 转换为指针切片(适配批量连接)
+	devicePtrs := make([]*dao.UHFReader, 0, len(devices))
+	for i := range devices {
+		devicePtrs = append(devicePtrs, &devices[i])
+	}
+
+	// 3. 批量连接
+	errMap := uhf.BatchConnect(devicePtrs)
+
+	// 4. 处理连接结果
+	successCount := 0
+	for deviceCode, err := range errMap {
+		if err != nil {
+			log.Printf("设备[%s]批量连接失败:%v", deviceCode, err)
+			// 更新离线状态
+			global.GVA_DB.Model(&dao.UHFReader{}).Where("device_code = ?", deviceCode).Update("status", "offline")
+		} else {
+			successCount++
+			global.GVA_DB.Model(&dao.UHFReader{}).Where("device_code = ?", deviceCode).Update("status", "online")
+		}
+	}
+
+	log.Printf("UHF设备批量初始化完成:成功[%d],失败[%d],总计[%d]",
+		successCount, len(errMap)-successCount, len(devices))
+
+	// 5. 设置全局消息回调(处理所有设备的接收数据)
+	uhf.ConnManager.SetRecvCallback(func(deviceCode string, data []byte) {
+		// 这里写你的业务逻辑:比如解析UHF标签数据、入库、推送等
+		log.Printf("业务层处理设备[%s]数据:%s", deviceCode, string(data))
+		// 示例:保存到数据库/推送到消息队列/调用其他服务
+	})
+}

+ 2 - 0
server/initialize/router.go

@@ -98,6 +98,8 @@ func Routers() *gin.Engine {
 
 		// 停车相关路由
 		parking.SetupParkingRouter(PrivateGroup)
+
+		// UHF读写器路由
 	}
 
 	global.GVA_LOG.Info("router register success")

+ 25 - 0
server/initialize/uhf.go

@@ -0,0 +1,25 @@
+package initialize
+
+import (
+	"go.uber.org/zap"
+	"server/dao"
+	"server/global"
+	"server/service/uhf"
+)
+
+func InitUHFDevices() {
+	var devices []dao.UHFReader
+	// 从数据库查询所有激活的设备
+	if err := global.GVA_DB.Where("is_active = ?", true).Find(&devices).Error; err != nil {
+		return
+	}
+
+	// 逐个启动设备处理协程
+	for _, d := range devices {
+		go func(device dao.UHFReader) {
+			if err := uhf.StartDeviceHandler(&device); err != nil {
+				global.GVA_LOG.Error("启动设备失败", zap.String("code", device.DeviceCode), zap.Error(err))
+			}
+		}(d)
+	}
+}

+ 1 - 0
server/main.go

@@ -35,5 +35,6 @@ func main() {
 		db, _ := global.GVA_DB.DB()
 		defer db.Close()
 	}
+	initialize.InitUHFDevices()
 	core.RunWindowsServer()
 }

+ 7 - 7
server/model/owner/request/owner.go

@@ -3,18 +3,18 @@ package request
 import "time"
 
 type OwnerCreate struct {
-	OwnerName     string    `json:"owner_name" binding:"required"`
-	OwnerSurname  string    `json:"owner_surname" binding:"required"`
-	OwnerPhone    string    `json:"owner_phone" binding:"required"`
+	OwnerName     string    `json:"owner_name"`
+	OwnerSurname  string    `json:"owner_surname"`
+	OwnerPhone    string    `json:"owner_phone"`
 	IsVip         bool      `json:"is_vip"`
 	VipExpireTime time.Time `json:"vip_expire_time"`
 }
 
 type OwnerUpdate struct {
-	ID            uint      `json:"id" binding:"required"`
-	OwnerName     string    `json:"owner_name" binding:"required"`
-	OwnerSurname  string    `json:"owner_surname" binding:"required"`
-	OwnerPhone    string    `json:"owner_phone" binding:"required"`
+	ID            uint      `json:"id"`
+	OwnerName     string    `json:"owner_name"`
+	OwnerSurname  string    `json:"owner_surname"`
+	OwnerPhone    string    `json:"owner_phone"`
 	IsVip         bool      `json:"is_vip"`
 	VipExpireTime time.Time `json:"vip_expire_time"`
 }

+ 19 - 13
server/model/parking/request/booth.go

@@ -1,23 +1,29 @@
 package request
 
+// BoothCreate 岗亭创建请求
 type BoothCreate struct {
-	BoothCode   string `json:"booth_code" binding:"required"`
-	BoothName   string `json:"booth_name" binding:"required"`
-	IPAddress   string `json:"ip_address"`
-	Description string `json:"description"`
+	BoothCode    string `json:"booth_code"`     // 岗亭编码
+	BoothName    string `json:"booth_name"`     // 岗亭名称
+	IPAddress    string `json:"ip_address"`     // IP地址
+	Description  string `json:"description"`    // 备注
+	ParkingLotID uint   `json:"parking_lot_id"` // 所属区域ID
 }
 
+// BoothUpdate 岗亭更新请求
 type BoothUpdate struct {
-	ID          uint   `json:"id" binding:"required"`
-	BoothCode   string `json:"booth_code" binding:"required"`
-	BoothName   string `json:"booth_name" binding:"required"`
-	IPAddress   string `json:"ip_address"`
-	Description string `json:"description"`
+	ID           uint   `json:"id"`             // 岗亭ID
+	BoothCode    string `json:"booth_code"`     // 岗亭编码
+	BoothName    string `json:"booth_name"`     // 岗亭名称
+	IPAddress    string `json:"ip_address"`     // IP地址
+	Description  string `json:"description"`    // 备注
+	ParkingLotID uint   `json:"parking_lot_id"` // 所属区域ID
 }
 
+// BoothQuery 岗亭查询请求
 type BoothQuery struct {
-	BoothCode string `form:"booth_code"`
-	BoothName string `form:"booth_name"`
-	Page      int    `form:"page,default=1"`
-	PageSize  int    `form:"page_size,default=10"`
+	BoothCode    string `form:"booth_code"`           // 岗亭编码
+	BoothName    string `form:"booth_name"`           // 岗亭名称
+	ParkingLotID uint   `form:"parking_lot_id"`       // 所属区域ID
+	Page         int    `form:"page,default=1"`       // 页码
+	PageSize     int    `form:"page_size,default=10"` // 每页数量
 }

+ 22 - 16
server/model/parking/request/channel.go

@@ -1,26 +1,32 @@
 package request
 
+// ChannelCreate 通道创建请求
 type ChannelCreate struct {
-	ChannelCode    string `json:"channel_code" binding:"required"`
-	ChannelName    string `json:"channel_name" binding:"required"`
-	Direction      string `json:"direction" binding:"required"`
-	AllowTemporary bool   `json:"allow_temporary"`
-	Description    string `json:"description"`
+	ChannelCode    string `json:"channel_code"`    // 通道编码
+	ChannelName    string `json:"channel_name"`    // 通道名称
+	Direction      string `json:"direction"`       // 进出方向 in/out/inout
+	AllowTemporary bool   `json:"allow_temporary"` // 是否允许临时车进出
+	Description    string `json:"description"`     // 备注
+	ParkingLotID   uint   `json:"parking_lot_id"`  // 所属区域ID
 }
 
+// ChannelUpdate 通道更新请求
 type ChannelUpdate struct {
-	ID             uint   `json:"id" binding:"required"`
-	ChannelCode    string `json:"channel_code" binding:"required"`
-	ChannelName    string `json:"channel_name" binding:"required"`
-	Direction      string `json:"direction" binding:"required"`
-	AllowTemporary bool   `json:"allow_temporary"`
-	Description    string `json:"description"`
+	ID             uint   `json:"id"`              // 通道ID
+	ChannelCode    string `json:"channel_code"`    // 通道编码
+	ChannelName    string `json:"channel_name"`    // 通道名称
+	Direction      string `json:"direction"`       // 进出方向 in/out/inout
+	AllowTemporary bool   `json:"allow_temporary"` // 是否允许临时车进出
+	Description    string `json:"description"`     // 备注
+	ParkingLotID   uint   `json:"parking_lot_id"`  // 所属区域ID
 }
 
+// ChannelQuery 通道查询请求
 type ChannelQuery struct {
-	ChannelCode string `form:"channel_code"`
-	ChannelName string `form:"channel_name"`
-	Direction   string `form:"direction"`
-	Page        int    `form:"page,default=1"`
-	PageSize    int    `form:"page_size,default=10"`
+	ChannelCode  string `form:"channel_code"`         // 通道编码
+	ChannelName  string `form:"channel_name"`         // 通道名称
+	Direction    string `form:"direction"`            // 进出方向
+	ParkingLotID uint   `form:"parking_lot_id"`       // 所属区域ID
+	Page         int    `form:"page,default=1"`       // 页码
+	PageSize     int    `form:"page_size,default=10"` // 每页数量
 }

+ 0 - 29
server/model/parking/request/device.go

@@ -1,29 +0,0 @@
-package request
-
-type DeviceCreate struct {
-	DeviceCode  string `json:"device_code" binding:"required"`
-	DeviceName  string `json:"device_name" binding:"required"`
-	DeviceType  string `json:"device_type" binding:"required"`
-	IPAddress   string `json:"ip_address"`
-	Port        int    `json:"port"`
-	Description string `json:"description"`
-}
-
-type DeviceUpdate struct {
-	ID          uint   `json:"id" binding:"required"`
-	DeviceCode  string `json:"device_code" binding:"required"`
-	DeviceName  string `json:"device_name" binding:"required"`
-	DeviceType  string `json:"device_type" binding:"required"`
-	IPAddress   string `json:"ip_address"`
-	Port        int    `json:"port"`
-	Description string `json:"description"`
-}
-
-type DeviceQuery struct {
-	DeviceCode string `form:"device_code"`
-	DeviceName string `form:"device_name"`
-	DeviceType string `form:"device_type"`
-	Status     string `form:"status"`
-	Page       int    `form:"page,default=1"`
-	PageSize   int    `form:"page_size,default=10"`
-}

+ 16 - 13
server/model/parking/request/parking_lot.go

@@ -1,23 +1,26 @@
 package request
 
+// ParkingLotCreate 停车区域创建请求
 type ParkingLotCreate struct {
-	LotCode     string `json:"lot_code" binding:"required"`
-	LotName     string `json:"lot_name" binding:"required"`
-	Capacity    int    `json:"capacity" binding:"required,min=0"`
-	Description string `json:"description"`
+	LotCode     string `json:"lot_code"`                 // 区域编码
+	LotName     string `json:"lot_name"`                 // 区域名称
+	Capacity    int    `json:"capacity" binding:"min=0"` // 车位数量
+	Description string `json:"description"`              // 备注
 }
 
+// ParkingLotUpdate 停车区域更新请求
 type ParkingLotUpdate struct {
-	ID          uint   `json:"id" binding:"required"`
-	LotCode     string `json:"lot_code" binding:"required"`
-	LotName     string `json:"lot_name" binding:"required"`
-	Capacity    int    `json:"capacity" binding:"required,min=0"`
-	Description string `json:"description"`
+	ID          uint   `json:"id"`                       // 区域ID
+	LotCode     string `json:"lot_code"`                 // 区域编码
+	LotName     string `json:"lot_name"`                 // 区域名称
+	Capacity    int    `json:"capacity" binding:"min=0"` // 车位数量
+	Description string `json:"description"`              // 备注
 }
 
+// ParkingLotQuery 停车区域查询请求
 type ParkingLotQuery struct {
-	LotCode  string `form:"lot_code"`
-	LotName  string `form:"lot_name"`
-	Page     int    `form:"page,default=1"`
-	PageSize int    `form:"page_size,default=10"`
+	LotCode  string `form:"lot_code"`             // 区域编码
+	LotName  string `form:"lot_name"`             // 区域名称
+	Page     int    `form:"page,default=1"`       // 页码
+	PageSize int    `form:"page_size,default=10"` // 每页数量
 }

+ 0 - 12
server/model/parking/response/device.go

@@ -1,12 +0,0 @@
-package response
-
-import "server/dao"
-
-type Device struct {
-	dao.Device
-}
-
-type DeviceList struct {
-	Total int64        `json:"total"`
-	List  []dao.Device `json:"list"`
-}

+ 60 - 0
server/model/uhf/request/reader.go

@@ -0,0 +1,60 @@
+package request
+
+// UHFReaderCreate UHF读写器创建请求
+type UHFReaderCreate struct {
+	DeviceCode   string `json:"device_code"`    // 设备编码
+	DeviceName   string `json:"device_name"`    // 设备名称
+	IPAddress    string `json:"ip_address"`     // IP地址
+	Port         int    `json:"port"`           // 端口
+	ConnectType  string `json:"connect_type"`   // 明确使用串口连接
+	COMPort      string `json:"com_port"`       // 串口名称(根据实际修改)
+	BaudRate     int    `json:"baud_rate"`      // 串口波特率
+	IsActive     bool   `json:"is_active"`      // 是否启用
+	Description  string `json:"description"`    // 备注
+	ParkingLotID uint   `json:"parking_lot_id"` // 所属区域ID
+}
+
+// UHFReaderUpdate UHF读写器更新请求
+type UHFReaderUpdate struct {
+	ID           uint   `json:"id"`             // 设备ID
+	DeviceCode   string `json:"device_code"`    // 设备编码
+	DeviceName   string `json:"device_name"`    // 设备名称
+	IPAddress    string `json:"ip_address"`     // IP地址
+	Port         int    `json:"port"`           // 端口
+	IsActive     bool   `json:"is_active"`      // 是否启用
+	Description  string `json:"description"`    // 备注
+	ParkingLotID uint   `json:"parking_lot_id"` // 所属区域ID
+}
+
+// UHFReaderQuery UHF读写器查询请求
+type UHFReaderQuery struct {
+	DeviceCode   string `form:"device_code"`          // 设备编码
+	DeviceName   string `form:"device_name"`          // 设备名称
+	IPAddress    string `form:"ip_address"`           // IP地址
+	Status       string `form:"status"`               // 状态
+	ParkingLotID uint   `form:"parking_lot_id"`       // 所属区域ID
+	Page         int    `form:"page,default=1"`       // 页码
+	PageSize     int    `form:"page_size,default=10"` // 每页数量
+}
+
+// UHFReaderConnect UHF读写器连接请求
+type UHFReaderConnect struct {
+	ID uint `json:"id"` // 设备ID
+}
+
+// UHFReaderDisconnect UHF读写器断开连接请求
+type UHFReaderDisconnect struct {
+	ID uint `json:"id"` // 设备ID
+}
+
+// UHFReaderPower UHF读写器功率设置请求
+type UHFReaderPower struct {
+	ID    uint `json:"id"`                                    // 设备ID
+	Power byte `json:"power" binding:"required,min=0,max=26"` // 功率值 0-26dBm
+}
+
+// UHFReaderTagReport UHF读写器标签上报请求
+type UHFReaderTagReport struct {
+	DeviceCode string `json:"device_code"` // 设备编码
+	Tag        string `json:"tag"`         // 标签UII
+}

+ 19 - 0
server/model/uhf/response/reader.go

@@ -0,0 +1,19 @@
+package response
+
+import "server/dao"
+
+type UHFReader struct {
+	dao.UHFReader
+}
+
+type UHFReaderList struct {
+	Total int64           `json:"total"`
+	List  []dao.UHFReader `json:"list"`
+}
+
+type TagReadResult struct {
+	ReaderID   uint   `json:"reader_id"`
+	DeviceCode string `json:"device_code"`
+	TagUII     string `json:"tag_uii"`
+	ReadTime   int64  `json:"read_time"`
+}

+ 15 - 15
server/model/vehicle/request/fee_config.go

@@ -1,27 +1,27 @@
 package request
 
 type FeeConfigCreate struct {
-	VehicleType string  `json:"vehicle_type" binding:"required"`
-	StartTime   int     `json:"start_time" binding:"required,min=0"`
-	StartFee    float64 `json:"start_fee" binding:"required,min=0"`
-	UnitTime    int     `json:"unit_time" binding:"required,min=1"`
-	UnitFee     float64 `json:"unit_fee" binding:"required,min=0"`
-	DailyMaxFee float64 `json:"daily_max_fee" binding:"required,min=0"`
+	VehicleType string  `json:"vehicle_type" `
+	StartTime   int     `json:"start_time"`
+	StartFee    float64 `json:"start_fee"`
+	UnitTime    int     `json:"unit_time"`
+	UnitFee     float64 `json:"unit_fee"`
+	DailyMaxFee float64 `json:"daily_max_fee"`
 	IsVIPFree   bool    `json:"is_vip_free"`
-	VIPDiscount float64 `json:"vip_discount" binding:"required,min=0,max=1"`
+	VIPDiscount float64 `json:"vip_discount"`
 	Description string  `json:"description"`
 }
 
 type FeeConfigUpdate struct {
-	ID          uint    `json:"id" binding:"required"`
-	VehicleType string  `json:"vehicle_type" binding:"required"`
-	StartTime   int     `json:"start_time" binding:"required,min=0"`
-	StartFee    float64 `json:"start_fee" binding:"required,min=0"`
-	UnitTime    int     `json:"unit_time" binding:"required,min=1"`
-	UnitFee     float64 `json:"unit_fee" binding:"required,min=0"`
-	DailyMaxFee float64 `json:"daily_max_fee" binding:"required,min=0"`
+	ID          uint    `json:"id" `
+	VehicleType string  `json:"vehicle_type" `
+	StartTime   int     `json:"start_time"`
+	StartFee    float64 `json:"start_fee"`
+	UnitTime    int     `json:"unit_time"`
+	UnitFee     float64 `json:"unit_fee"`
+	DailyMaxFee float64 `json:"daily_max_fee"`
 	IsVIPFree   bool    `json:"is_vip_free"`
-	VIPDiscount float64 `json:"vip_discount" binding:"required,min=0,max=1"`
+	VIPDiscount float64 `json:"vip_discount"`
 	Description string  `json:"description"`
 }
 

+ 11 - 9
server/model/vehicle/request/vehicle.go

@@ -1,17 +1,17 @@
 package request
 
 type VehicleCreate struct {
-	PlateNumber  string `json:"plate_number" binding:"required"`
-	VehicleType  string `json:"vehicle_type" binding:"required"`
+	PlateNumber  string `json:"plate_number"`
+	VehicleType  string `json:"vehicle_type"`
 	VehicleBrand string `json:"vehicle_brand"`
 	VehicleColor string `json:"vehicle_color"`
 	OwnerId      uint   `json:"owner_id"`
 }
 
 type VehicleUpdate struct {
-	ID           uint   `json:"id" binding:"required"`
-	PlateNumber  string `json:"plate_number" binding:"required"`
-	VehicleType  string `json:"vehicle_type" binding:"required"`
+	ID           uint   `json:"id"`
+	PlateNumber  string `json:"plate_number"`
+	VehicleType  string `json:"vehicle_type"`
 	VehicleBrand string `json:"vehicle_brand"`
 	VehicleColor string `json:"vehicle_color"`
 	OwnerId      uint   `json:"owner_id"`
@@ -26,15 +26,17 @@ type VehicleQuery struct {
 }
 
 type VehicleEntry struct {
-	PlateNumber    string `json:"plate_number" binding:"required"`
-	ParkingLotID   uint   `json:"parking_lot_id" binding:"required"`
-	ParkingSpaceID uint   `json:"parking_space_id" binding:"required"`
+	PlateNumber    string `json:"plate_number"`
+	ParkingLotID   uint   `json:"parking_lot_id"`
+	ParkingSpaceID uint   `json:"parking_space_id"`
+	RFIDTag        string `json:"rfid_tag"`
 	EntryImage     string `json:"entry_image"`
 }
 
 type VehicleExit struct {
-	PlateNumber string `json:"plate_number" binding:"required"`
+	PlateNumber string `json:"plate_number"`
 	ExitImage   string `json:"exit_image"`
+	RFIDTag     string `json:"rfid_tag"`
 }
 
 type VehicleEntryResponse struct {

+ 0 - 16
server/router/parking/parking.go

@@ -6,11 +6,9 @@ import (
 	"github.com/gin-gonic/gin"
 )
 
-// SetupParkingRouter 停车相关路由
 func SetupParkingRouter(router *gin.RouterGroup) {
 	parkingGroup := router.Group("/parking")
 	{
-		// 停车区域路由
 		lotGroup := parkingGroup.Group("/lot")
 		{
 			lotGroup.POST("/create", parking.CreateParkingLot)
@@ -21,7 +19,6 @@ func SetupParkingRouter(router *gin.RouterGroup) {
 			lotGroup.DELETE("/delete", parking.DeleteParkingLot)
 		}
 
-		// 岗亭路由
 		boothGroup := parkingGroup.Group("/booth")
 		{
 			boothGroup.POST("/create", parking.CreateBooth)
@@ -32,7 +29,6 @@ func SetupParkingRouter(router *gin.RouterGroup) {
 			boothGroup.DELETE("/delete", parking.DeleteBooth)
 		}
 
-		// 通道路由
 		channelGroup := parkingGroup.Group("/channel")
 		{
 			channelGroup.POST("/create", parking.CreateChannel)
@@ -42,17 +38,5 @@ func SetupParkingRouter(router *gin.RouterGroup) {
 			channelGroup.GET("/list", parking.ListChannels)
 			channelGroup.DELETE("/delete", parking.DeleteChannel)
 		}
-
-		// 设备路由
-		deviceGroup := parkingGroup.Group("/device")
-		{
-			deviceGroup.POST("/create", parking.CreateDevice)
-			deviceGroup.PUT("/update", parking.UpdateDevice)
-			deviceGroup.GET("/get", parking.GetDeviceByID)
-			deviceGroup.GET("/get-by-code", parking.GetDeviceByCode)
-			deviceGroup.GET("/list", parking.ListDevices)
-			deviceGroup.DELETE("/delete", parking.DeleteDevice)
-			deviceGroup.PUT("/update-status", parking.UpdateDeviceStatus)
-		}
 	}
 }

BIN
server/server


+ 0 - 138
server/service/parking/device.go

@@ -1,138 +0,0 @@
-package parking
-
-import (
-	"errors"
-	"server/dao"
-	"server/global"
-	"server/model/parking/request"
-)
-
-type deviceService struct{}
-
-func NewDeviceService() *deviceService {
-	return &deviceService{}
-}
-
-// CreateDevice 创建设备
-func (s *deviceService) CreateDevice(req request.DeviceCreate) error {
-	// 检查设备编码是否已存在
-	var existingDevice dao.Device
-	result := global.GVA_DB.Where("device_code = ?", req.DeviceCode).First(&existingDevice)
-	if result.RowsAffected > 0 {
-		return errors.New("设备编码已存在")
-	}
-
-	device := dao.Device{
-		DeviceCode:  req.DeviceCode,
-		DeviceName:  req.DeviceName,
-		DeviceType:  req.DeviceType,
-		IPAddress:   req.IPAddress,
-		Port:        req.Port,
-		Status:      "offline", // 初始状态为离线
-		Description: req.Description,
-	}
-
-	return global.GVA_DB.Create(&device).Error
-}
-
-// UpdateDevice 更新设备
-func (s *deviceService) UpdateDevice(req request.DeviceUpdate) error {
-	var device dao.Device
-	result := global.GVA_DB.First(&device, req.ID)
-	if result.RowsAffected == 0 {
-		return errors.New("设备不存在")
-	}
-
-	// 检查设备编码是否被其他设备使用
-	var existingDevice dao.Device
-	result = global.GVA_DB.Where("device_code = ? AND id != ?", req.DeviceCode, req.ID).First(&existingDevice)
-	if result.RowsAffected > 0 {
-		return errors.New("设备编码已被其他设备使用")
-	}
-
-	device.DeviceCode = req.DeviceCode
-	device.DeviceName = req.DeviceName
-	device.DeviceType = req.DeviceType
-	device.IPAddress = req.IPAddress
-	device.Port = req.Port
-	device.Description = req.Description
-
-	return global.GVA_DB.Save(&device).Error
-}
-
-// GetDeviceByID 根据ID获取设备
-func (s *deviceService) GetDeviceByID(id uint) (dao.Device, error) {
-	var device dao.Device
-	result := global.GVA_DB.First(&device, id)
-	if result.RowsAffected == 0 {
-		return dao.Device{}, errors.New("设备不存在")
-	}
-	return device, nil
-}
-
-// GetDeviceByCode 根据编码获取设备
-func (s *deviceService) GetDeviceByCode(code string) (dao.Device, error) {
-	var device dao.Device
-	result := global.GVA_DB.Where("device_code = ?", code).First(&device)
-	if result.RowsAffected == 0 {
-		return dao.Device{}, errors.New("设备不存在")
-	}
-	return device, nil
-}
-
-// ListDevices 获取设备列表
-func (s *deviceService) ListDevices(req request.DeviceQuery) (int64, []dao.Device, error) {
-	var devices []dao.Device
-	var total int64
-
-	query := global.GVA_DB.Model(&dao.Device{})
-
-	if req.DeviceCode != "" {
-		query = query.Where("device_code LIKE ?", "%"+req.DeviceCode+"%")
-	}
-
-	if req.DeviceName != "" {
-		query = query.Where("device_name LIKE ?", "%"+req.DeviceName+"%")
-	}
-
-	if req.DeviceType != "" {
-		query = query.Where("device_type = ?", req.DeviceType)
-	}
-
-	if req.Status != "" {
-		query = query.Where("status = ?", req.Status)
-	}
-
-	// 计算总数
-	query.Count(&total)
-
-	// 分页查询
-	offset := (req.Page - 1) * req.PageSize
-	result := query.Offset(offset).Limit(req.PageSize).Find(&devices)
-	if result.Error != nil {
-		return 0, nil, result.Error
-	}
-
-	return total, devices, nil
-}
-
-// DeleteDevice 删除设备
-func (s *deviceService) DeleteDevice(id uint) error {
-	result := global.GVA_DB.Delete(&dao.Device{}, id)
-	if result.RowsAffected == 0 {
-		return errors.New("设备不存在")
-	}
-	return result.Error
-}
-
-// UpdateDeviceStatus 更新设备状态
-func (s *deviceService) UpdateDeviceStatus(id uint, status string) error {
-	var device dao.Device
-	result := global.GVA_DB.First(&device, id)
-	if result.RowsAffected == 0 {
-		return errors.New("设备不存在")
-	}
-
-	device.Status = status
-	return global.GVA_DB.Save(&device).Error
-}

+ 0 - 8
server/service/parking/enter.go

@@ -1,8 +0,0 @@
-package parking
-
-var (
-	ParkingLotService = NewParkingLotService()
-	BoothService      = NewBoothService()
-	ChannelService    = NewChannelService()
-	DeviceService     = NewDeviceService()
-)

+ 94 - 0
server/service/uhf/conn_manager.go

@@ -0,0 +1,94 @@
+package uhf
+
+import (
+	"log"
+	"server/global"
+	"sync"
+)
+
+// ConnManager 全局多设备连接管理器(单例)
+var ConnManager = &connManager{
+	connMap: make(map[string]UHFReaderConn), // key: DeviceCode
+}
+
+type connManager struct {
+	mu      sync.RWMutex // 读写锁,保证多协程安全
+	connMap map[string]UHFReaderConn
+	// 消息回调:设备收到数据后触发的处理函数(可自定义)
+	recvCallback func(deviceCode string, data []byte)
+}
+
+// SetRecvCallback 设置全局消息接收回调
+func (m *connManager) SetRecvCallback(callback func(deviceCode string, data []byte)) {
+	m.recvCallback = callback
+}
+
+// AddConn 添加设备连接(多设备核心:按DeviceCode存储)
+func (m *connManager) AddConn(deviceCode string, conn UHFReaderConn) {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	m.connMap[deviceCode] = conn
+	// 启动消息监听协程(异步接收设备数据)
+	go m.listenDeviceData(deviceCode, conn)
+	log.Printf("设备[%s]已加入连接池", deviceCode)
+}
+
+// GetConn 获取指定设备连接(多设备核心:按DeviceCode检索)
+func (m *connManager) GetConn(deviceCode string) (UHFReaderConn, bool) {
+	m.mu.RLock()
+	defer m.mu.RUnlock()
+	conn, ok := m.connMap[deviceCode]
+	return conn, ok
+}
+
+// GetAllConns 获取所有设备连接(批量操作)
+func (m *connManager) GetAllConns() map[string]UHFReaderConn {
+	m.mu.RLock()
+	defer m.mu.RUnlock()
+	// 返回副本,避免外部修改原map
+	copyMap := make(map[string]UHFReaderConn)
+	for k, v := range m.connMap {
+		copyMap[k] = v
+	}
+	return copyMap
+}
+
+// RemoveConn 移除设备连接
+func (m *connManager) RemoveConn(deviceCode string) {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	delete(m.connMap, deviceCode)
+	log.Printf("设备[%s]已移出连接池", deviceCode)
+}
+
+// listenDeviceData 异步监听设备数据(多设备消息接收核心)
+func (m *connManager) listenDeviceData(deviceCode string, conn UHFReaderConn) {
+	for {
+		// 检查连接是否有效
+		if !conn.IsConnected() {
+			log.Printf("设备[%s]连接已断开,停止监听", deviceCode)
+			// 更新数据库状态
+			//var device dao.UHFReader
+			global.GVA_DB.Where("device_code = ?", deviceCode).Update("status", "offline")
+			return
+		}
+
+		// 阻塞读取设备数据(TCP/串口统一通过ReadData读取)
+		data, err := conn.ReadData() // 新增ReadData接口方法
+		if err != nil {
+			log.Printf("设备[%s]读取数据失败:%v", deviceCode, err)
+			continue
+		}
+
+		// 空数据跳过
+		if len(data) == 0 {
+			continue
+		}
+
+		log.Printf("设备[%s]收到数据:%x", deviceCode, data)
+		// 触发回调处理数据(业务层自定义逻辑)
+		if m.recvCallback != nil {
+			m.recvCallback(deviceCode, data)
+		}
+	}
+}

+ 230 - 0
server/service/uhf/reader.go

@@ -0,0 +1,230 @@
+package uhf
+
+import (
+	"context"
+	"encoding/binary"
+	"encoding/hex"
+	"errors"
+	"fmt"
+	"server/dao"
+	"server/global"
+	"sync"
+	"time"
+)
+
+// Reader 通用读写接口(串口和TCP都实现它)
+type Reader interface {
+	Connect() error
+	Disconnect() error
+	SendData([]byte) error
+	ReadData() ([]byte, error)
+	IsConnected() bool
+}
+
+// 上报数据模型
+type ReportData struct {
+	DeviceCode string   `json:"device_code"` // 设备编码
+	Hex        string   `json:"hex"`         // 原始报文
+	Epcs       []string `json:"epcs"`        // 解析出的EPC列表
+	RSSI       int      `json:"rssi"`        // 信号强度
+	Antenna    int      `json:"antenna"`     // 天线号
+	Timestamp  int64    `json:"timestamp"`   // 上报时间
+}
+
+// 全局设备管理器(管理所有已连接的设备)
+var DeviceManager = &Manager{
+	devices: make(map[string]*DeviceHandler),
+}
+
+type Manager struct {
+	mu      sync.RWMutex
+	devices map[string]*DeviceHandler
+}
+
+// DeviceHandler 每个设备的处理实例(包含读写器和协程)
+type DeviceHandler struct {
+	reader    Reader
+	device    *dao.UHFReader
+	ctx       context.Context
+	cancel    context.CancelFunc
+	dataChan  chan *ReportData
+	isRunning bool
+}
+
+// Register 注册设备
+func (m *Manager) Register(code string, h *DeviceHandler) {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	m.devices[code] = h
+}
+
+// Unregister 注销设备
+func (m *Manager) Unregister(code string) {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+	delete(m.devices, code)
+}
+
+// Get 获取设备
+func (m *Manager) Get(code string) (*DeviceHandler, bool) {
+	m.mu.RLock()
+	defer m.mu.RUnlock()
+	h, ok := m.devices[code]
+	return h, ok
+}
+
+// ===================== 复用原有CRC和解析函数(无需修改)=====================
+func uiCrc16Cal(pucY []byte, ucX uint8) uint16 {
+	const PRESET_VALUE = 0xFFFF
+	const POLYNOMIAL = 0x8408
+	var uiCrcValue uint16 = PRESET_VALUE
+	for ucI := uint8(0); ucI < ucX; ucI++ {
+		uiCrcValue = uiCrcValue ^ uint16(pucY[ucI])
+		for ucJ := uint8(0); ucJ < 8; ucJ++ {
+			if uiCrcValue&0x0001 != 0 {
+				uiCrcValue = (uiCrcValue >> 1) ^ POLYNOMIAL
+			} else {
+				uiCrcValue = uiCrcValue >> 1
+			}
+		}
+	}
+	return (uiCrcValue << 8) | (uiCrcValue >> 8)
+}
+
+// StartDeviceHandler 启动单个设备的读写协程
+func StartDeviceHandler(device *dao.UHFReader) error {
+	// 1. 根据连接类型创建读写器
+	var r Reader
+	switch device.ConnectType {
+	case "serial":
+		r = NewSerialReader(device.COMPort, device.BaudRate)
+	case "tcp":
+		r = NewTCPReader(device.IPAddress, device.Port)
+	default:
+		return fmt.Errorf("不支持的连接类型: %s", device.ConnectType)
+	}
+
+	// 2. 连接设备
+	if err := r.Connect(); err != nil {
+		return err
+	}
+
+	// 3. 创建上下文和handler
+	ctx, cancel := context.WithCancel(context.Background())
+	h := &DeviceHandler{
+		reader:    r,
+		device:    device,
+		ctx:       ctx,
+		cancel:    cancel,
+		dataChan:  make(chan *ReportData, 100),
+		isRunning: true,
+	}
+
+	// 4. 注册到管理器
+	DeviceManager.Register(device.DeviceCode, h)
+
+	// 5. 启动读写协程
+	go func() {
+		defer func() {
+			h.isRunning = false
+			r.Disconnect()
+			close(h.dataChan)
+			DeviceManager.Unregister(device.DeviceCode)
+		}()
+
+		for {
+			select {
+			case <-ctx.Done():
+				return
+			default:
+				buf, err := r.ReadData()
+				if err != nil {
+					// 读取出错,更新设备状态
+					global.GVA_DB.Model(device).Update("status", "offline")
+					// 自动重连
+					time.Sleep(2 * time.Second)
+					if err := r.Connect(); err != nil {
+						continue
+					}
+					global.GVA_DB.Model(device).Update("status", "online")
+					continue
+				}
+
+				if len(buf) == 0 {
+					continue
+				}
+
+				// 解析上报数据
+				report, err := parseReportData(device.DeviceCode, buf)
+				if err != nil {
+					continue
+				}
+
+				// 推送到数据通道
+				select {
+				case h.dataChan <- report:
+				default:
+				}
+			}
+		}
+	}()
+
+	// 6. 启动业务处理协程(你后续在这里写业务逻辑)
+	go func() {
+		for {
+			select {
+			case <-ctx.Done():
+				return
+			case report, ok := <-h.dataChan:
+				if !ok {
+					return
+				}
+				// 这里写你的业务逻辑,比如:
+				// 1. 去重过滤标签
+				// 2. 写入数据库
+				// 3. 推送到前端websocket
+				handleReportData(report)
+			}
+		}
+	}()
+
+	// 更新设备状态
+	global.GVA_DB.Model(device).Update("status", "online")
+	return nil
+}
+
+// parseReportData 解析上报数据(把你之前的逻辑搬过来)
+func parseReportData(deviceCode string, buf []byte) (*ReportData, error) {
+	if len(buf) < 25 || buf[0] != 0xCF {
+		return nil, errors.New("无效帧")
+	}
+
+	// CRC校验(复用你之前的函数)
+	dataWithoutCRC := buf[:len(buf)-2]
+	recvCRC := binary.LittleEndian.Uint16(buf[len(buf)-2:])
+	calcCRC := uiCrc16Cal(dataWithoutCRC, uint8(len(dataWithoutCRC)))
+	if recvCRC != calcCRC {
+		return nil, errors.New("CRC校验失败")
+	}
+
+	// 解析字段
+	rssi := int(buf[6])
+	antenna := int(buf[22])
+	epc := hex.EncodeToString(buf[9:21])
+
+	return &ReportData{
+		DeviceCode: deviceCode,
+		Hex:        hex.EncodeToString(buf),
+		Epcs:       []string{epc},
+		RSSI:       rssi,
+		Antenna:    antenna,
+		Timestamp:  time.Now().Unix(),
+	}, nil
+}
+
+// handleReportData
+func handleReportData(report *ReportData) {
+	// 示例:打印上报数据
+	fmt.Printf("[业务处理] 设备:%s EPC:%s RSSI:%d 天线:%d\n",
+		report.DeviceCode, report.Epcs[0], report.RSSI, report.Antenna)
+}

+ 53 - 0
server/service/uhf/reader_interface.go

@@ -0,0 +1,53 @@
+package uhf
+
+import (
+	"errors"
+	"server/dao"
+)
+
+// UHFReaderConn 统一的设备连接接口(适配多设备+消息收发)
+type UHFReaderConn interface {
+	Connect() error                          // 建立连接
+	Disconnect() error                       // 断开连接
+	IsConnected() bool                       // 检查连接状态
+	SendData(data []byte) error              // 发送数据(仅发送,无返回)
+	ReadData() ([]byte, error)               // 读取数据(阻塞式)
+	SendAndRecv(data []byte) ([]byte, error) // 发送并等待响应(一问一答)
+}
+
+// NewReaderConn 批量创建连接(多设备扩展)
+func NewReaderConn(device *dao.UHFReader) (UHFReaderConn, error) {
+	switch device.ConnectType {
+	case "tcp":
+		return &TCPReader{
+			IP:   device.IPAddress,
+			Port: device.Port,
+		}, nil
+	case "serial":
+		return &SerialReader{
+			COMPort:  device.COMPort,
+			BaudRate: device.BaudRate,
+		}, nil
+	default:
+		return nil, errors.New("不支持的连接类型:" + device.ConnectType)
+	}
+}
+
+// BatchConnect 批量连接设备(多设备初始化)
+func BatchConnect(devices []*dao.UHFReader) map[string]error {
+	result := make(map[string]error)
+	for _, device := range devices {
+		conn, err := NewReaderConn(device)
+		if err != nil {
+			result[device.DeviceCode] = err
+			continue
+		}
+		if err := conn.Connect(); err != nil {
+			result[device.DeviceCode] = err
+			continue
+		}
+		// 加入连接池
+		ConnManager.AddConn(device.DeviceCode, conn)
+	}
+	return result
+}

+ 123 - 0
server/service/uhf/serial_reader.go

@@ -0,0 +1,123 @@
+package uhf
+
+import (
+	"errors"
+	"fmt"
+	"github.com/tarm/serial"
+	"io"
+	"os"
+	"time"
+)
+
+type SerialReader struct {
+	COMPort     string
+	BaudRate    int
+	port        *serial.Port
+	isConnected bool
+	readBufSize int
+}
+
+// 初始化串口
+func NewSerialReader(comPort string, baudRate int) *SerialReader {
+	return &SerialReader{
+		COMPort:     comPort,
+		BaudRate:    baudRate,
+		readBufSize: 1024, // 足够容纳设备响应
+	}
+}
+
+// Connect 建立串口连接(显式配置8数据位,和工具一致)
+func (s *SerialReader) Connect() error {
+	cfg := &serial.Config{
+		Name:        s.COMPort,
+		Baud:        s.BaudRate,
+		ReadTimeout: 1 * time.Second, // 读超时1秒,核心超时配置
+		Size:        8,               // 8位数据位(必须显式配置!)
+		Parity:      serial.ParityNone,
+		StopBits:    serial.Stop1,
+	}
+	port, err := serial.OpenPort(cfg)
+	if err != nil {
+		s.isConnected = false
+		return fmt.Errorf("打开串口失败: %v", err)
+	}
+	s.port = port
+	s.isConnected = true
+	return nil
+}
+
+// Disconnect 断开连接
+func (s *SerialReader) Disconnect() error {
+	if s.port != nil {
+		err := s.port.Close()
+		s.isConnected = false
+		return err
+	}
+	return nil
+}
+
+// IsConnected 检查状态
+func (s *SerialReader) IsConnected() bool {
+	return s.isConnected
+}
+
+// SendData 发送串口数据(移除无效的Flush断言,保留核心发送逻辑)
+func (s *SerialReader) SendData(data []byte) error {
+	if !s.isConnected {
+		return errors.New("串口连接未建立")
+	}
+	// 完整写入所有字节
+	n, err := s.port.Write(data)
+	if err != nil {
+		return fmt.Errorf("发送数据失败: %v", err)
+	}
+	// 校验是否发送完整(核心:确保指令全部发出)
+	if n != len(data) {
+		return fmt.Errorf("发送字节数不完整,发送%d/预期%d", n, len(data))
+	}
+
+	// ✅ 移除无效的Flush断言(tarm/serial的Port无此方法)
+	// 注:tarm/serial的Write本身会立即写入串口,无需手动Flush
+	return nil
+}
+
+// ReadData 循环读取串口数据(无SetReadDeadline,依赖ReadTimeout)
+func (s *SerialReader) ReadData() ([]byte, error) {
+	if !s.isConnected {
+		return nil, errors.New("串口连接未建立")
+	}
+	buf := make([]byte, s.readBufSize)
+	var resp []byte
+
+	// 循环读取,直到超时/拿到完整响应
+	for {
+		n, err := s.port.Read(buf)
+		if err != nil {
+			// 超时/EOF是正常结束(ReadTimeout触发)
+			if err == io.EOF || os.IsTimeout(err) {
+				break
+			}
+			s.isConnected = false
+			return nil, fmt.Errorf("读取数据失败: %v", err)
+		}
+		if n > 0 {
+			resp = append(resp, buf[:n]...)
+			// 检测到完整响应(帧头0xCF+最小长度7),提前退出
+			if len(resp) >= 7 && resp[0] == 0xCF {
+				break
+			}
+		}
+	}
+	return resp, nil
+}
+
+// SendAndRecv 串口发送并等待响应(增加发送后延迟,核心优化)
+func (s *SerialReader) SendAndRecv(data []byte) ([]byte, error) {
+	if err := s.SendData(data); err != nil {
+		return nil, err
+	}
+	// 发送后延迟100ms,给设备处理指令的时间(工具也有此隐性延迟)
+	time.Sleep(100 * time.Millisecond)
+	// 读取响应
+	return s.ReadData()
+}

+ 86 - 0
server/service/uhf/tcp_reader.go

@@ -0,0 +1,86 @@
+package uhf
+
+import (
+	"errors"
+	"net"
+	"time"
+)
+
+type TCPReader struct {
+	IP          string
+	Port        int
+	conn        net.Conn
+	isConnected bool
+	readBufSize int // 读取缓冲区大小
+}
+
+// 初始化时设置默认缓冲区
+func NewTCPReader(ip string, port int) *TCPReader {
+	return &TCPReader{
+		IP:          ip,
+		Port:        port,
+		readBufSize: 1024, // 默认1K缓冲区
+	}
+}
+
+// Connect 建立TCP连接(原有逻辑不变)
+func (t *TCPReader) Connect() error {
+	addr := net.JoinHostPort(t.IP, string(rune(t.Port)))
+	conn, err := net.DialTimeout("tcp", addr, 5*time.Second)
+	if err != nil {
+		t.isConnected = false
+		return err
+	}
+	t.conn = conn
+	t.isConnected = true
+	return nil
+}
+
+// Disconnect 断开连接(原有逻辑不变)
+func (t *TCPReader) Disconnect() error {
+	if t.conn != nil {
+		err := t.conn.Close()
+		t.isConnected = false
+		return err
+	}
+	return nil
+}
+
+// IsConnected 检查状态(原有逻辑不变)
+func (t *TCPReader) IsConnected() bool {
+	return t.isConnected
+}
+
+// SendData 仅发送数据(多设备批量发送用)
+func (t *TCPReader) SendData(data []byte) error {
+	if !t.isConnected {
+		return errors.New("TCP连接未建立")
+	}
+	_, err := t.conn.Write(data)
+	return err
+}
+
+// ReadData 阻塞读取TCP数据(多设备监听用)
+func (t *TCPReader) ReadData() ([]byte, error) {
+	if !t.isConnected {
+		return nil, errors.New("TCP连接未建立")
+	}
+	buf := make([]byte, t.readBufSize)
+	n, err := t.conn.Read(buf)
+	if err != nil {
+		t.isConnected = false // 读失败标记为断开
+		return nil, err
+	}
+	return buf[:n], nil
+}
+
+// SendAndRecv 发送并等待响应(一问一答,多设备指令交互用)
+func (t *TCPReader) SendAndRecv(data []byte) ([]byte, error) {
+	if err := t.SendData(data); err != nil {
+		return nil, err
+	}
+	// 设置读超时,避免永久阻塞
+	t.conn.SetReadDeadline(time.Now().Add(3 * time.Second))
+	defer t.conn.SetReadDeadline(time.Time{}) // 恢复默认
+	return t.ReadData()
+}

+ 6 - 6
server/service/vehicle/vehicle.go

@@ -83,9 +83,9 @@ func (s *vehicleService) GetVehicleByID(id uint) (dao.Vehicle, error) {
 }
 
 // GetVehicleByPlateNumber 根据车牌号获取车辆信息
-func (s *vehicleService) GetVehicleByPlateNumber(plateNumber string) (dao.Vehicle, error) {
+func (s *vehicleService) GetVehicleByPlateNumber(plateNumber, RFIDTag string) (dao.Vehicle, error) {
 	var vehicle dao.Vehicle
-	result := global.GVA_DB.Where("plate_number = ?", plateNumber).First(&vehicle)
+	result := global.GVA_DB.Where("plate_number = ? or rfid_tag = ?", plateNumber, RFIDTag).First(&vehicle)
 	if result.RowsAffected == 0 {
 		return dao.Vehicle{}, errors.New("车辆不存在")
 	}
@@ -136,7 +136,7 @@ func (s *vehicleService) DeleteVehicle(id uint) error {
 // VehicleEntry 车辆入场
 func (s *vehicleService) VehicleEntry(req request.VehicleEntry) (request.VehicleEntryResponse, error) {
 	// 检查车辆是否存在
-	vehicle, err := s.GetVehicleByPlateNumber(req.PlateNumber)
+	vehicle, err := s.GetVehicleByPlateNumber(req.PlateNumber, req.RFIDTag)
 	if err != nil {
 		// 如果车辆不存在,创建新车辆
 		createReq := request.VehicleCreate{
@@ -148,7 +148,7 @@ func (s *vehicleService) VehicleEntry(req request.VehicleEntry) (request.Vehicle
 		if err != nil {
 			return request.VehicleEntryResponse{}, err
 		}
-		vehicle, _ = s.GetVehicleByPlateNumber(req.PlateNumber)
+		vehicle, _ = s.GetVehicleByPlateNumber(req.PlateNumber, req.RFIDTag)
 	}
 
 	// 检查是否有未出场的记录
@@ -200,7 +200,7 @@ func (s *vehicleService) VehicleExit(req request.VehicleExit) (request.VehicleEx
 	}
 
 	// 获取车辆信息
-	vehicle, err := s.GetVehicleByPlateNumber(req.PlateNumber)
+	vehicle, err := s.GetVehicleByPlateNumber(req.PlateNumber, req.RFIDTag)
 	if err != nil {
 		return request.VehicleExitResponse{}, err
 	}
@@ -267,7 +267,7 @@ func (s *vehicleService) calculateFee(vehicle dao.Vehicle, stayTime int64) float
 		return 0
 	}
 
-	// 计算费用
+	//  计算费用
 	fee := feeConfig.StartFee
 	if stayTime > int64(feeConfig.StartTime) {
 		extraTime := stayTime - int64(feeConfig.StartTime)

+ 100 - 0
server/test/tcp_server_test.go

@@ -0,0 +1,100 @@
+package test
+
+import (
+	"bufio"
+	"fmt"
+	"net"
+	"testing"
+	"time"
+)
+
+// TCPServer 定义TCP服务器结构体
+type TCPServer struct {
+	ip   string
+	port int
+}
+
+// NewTCPServer 构造函数
+func NewTCPServer(ip string, port int) *TCPServer {
+	return &TCPServer{
+		ip:   ip,
+		port: port,
+	}
+}
+
+// Start 启动服务器,开始接收数据
+func (t *TCPServer) Start() {
+	addr := fmt.Sprintf("%s:%d", t.ip, t.port)
+	// 1. 监听端口
+	listener, err := net.Listen("tcp", addr)
+	if err != nil {
+		fmt.Printf("❌ 监听失败: %v\n", err)
+		return
+	}
+	defer listener.Close()
+
+	fmt.Printf("✅ 服务器已启动,监听 %s\n", addr)
+	fmt.Println("📶 等待读写器连接并上报数据...")
+
+	for {
+		// 2. 阻塞等待客户端(读写器)连接
+		conn, err := listener.Accept()
+		if err != nil {
+			fmt.Printf("❌ 接受连接失败: %v\n", err)
+			continue
+		}
+
+		fmt.Printf("🎉 读写器已连接: %s\n", conn.RemoteAddr().String())
+		fmt.Println("--------------------------------------------------")
+
+		// 3. 启动协程处理该连接的数据收发
+		go t.handleConnection(conn)
+	}
+}
+
+// handleConnection 处理单个客户端连接
+func (t *TCPServer) handleConnection(conn net.Conn) {
+	defer func() {
+		_ = conn.Close()
+		fmt.Println("❌ 读写器连接断开")
+	}()
+
+	// 设置读取超时,防止一直阻塞(可选)
+	_ = conn.SetReadDeadline(time.Time{})
+
+	reader := bufio.NewReader(conn)
+	buf := make([]byte, 1024)
+
+	for {
+		// 4. 读取读写器上报的数据
+		n, err := reader.Read(buf)
+		if err != nil {
+			// 断开连接或读错会退出循环
+			return
+		}
+
+		if n > 0 {
+			// 5. 打印原始数据(十六进制格式)
+			data := buf[:n]
+			fmt.Printf("📥 收到原始数据 (Hex): %x\n", data)
+
+			// 6. 【关键】解析数据
+			// 根据协议,数据通常包含:帧头 + 长度 + EPC数据 + 校验
+			// 这里可以添加解析逻辑,例如提取 EPC
+			// if len(data) > 7 {
+			//  epcLen := int(data[5]) // 假设第6个字节是长度
+			//  if len(data) >= 6 + epcLen {
+			//   epc := data[6 : 6+epcLen]
+			//   fmt.Printf("🎯 解析到标签EPC: %x\n", epc)
+			//  }
+			// }
+		}
+	}
+}
+
+func TestTCPServer(t *testing.T) {
+	// 启动TCP服务器
+	fmt.Println("启动TCP服务器...")
+	server := NewTCPServer("192.168.110.218", 9532)
+	server.Start()
+}

+ 248 - 0
server/test/uhf_reader_test.go

@@ -0,0 +1,248 @@
+package test
+
+import (
+	"context"
+	"encoding/binary"
+	"encoding/hex"
+	"fmt"
+	"server/service/uhf"
+	"sync"
+	"testing"
+	"time"
+)
+
+// ===================== 复用原有CRC和解析函数(无需修改)=====================
+func uiCrc16Cal(pucY []byte, ucX uint8) uint16 {
+	const PRESET_VALUE = 0xFFFF
+	const POLYNOMIAL = 0x8408
+	var uiCrcValue uint16 = PRESET_VALUE
+	for ucI := uint8(0); ucI < ucX; ucI++ {
+		uiCrcValue = uiCrcValue ^ uint16(pucY[ucI])
+		for ucJ := uint8(0); ucJ < 8; ucJ++ {
+			if uiCrcValue&0x0001 != 0 {
+				uiCrcValue = (uiCrcValue >> 1) ^ POLYNOMIAL
+			} else {
+				uiCrcValue = uiCrcValue >> 1
+			}
+		}
+	}
+	return (uiCrcValue << 8) | (uiCrcValue >> 8)
+}
+
+func parseResp(resp []byte) (bool, error) {
+	if len(resp) < 7 {
+		return false, fmt.Errorf("返回报文长度不足,实际长度: %d", len(resp))
+	}
+	if resp[0] != 0xCF {
+		return false, fmt.Errorf("帧头错误,期望0xCF,实际0x%02X", resp[0])
+	}
+
+	dataWithoutCRC := resp[:len(resp)-2]
+	recvCRC := binary.LittleEndian.Uint16(resp[len(resp)-2:])
+	calcCRC := uiCrc16Cal(dataWithoutCRC, uint8(len(dataWithoutCRC)))
+	if recvCRC != calcCRC {
+		return false, fmt.Errorf("CRC校验失败 | 接收:0x%04X | 计算:0x%04X", recvCRC, calcCRC)
+	}
+
+	fmt.Println("✅ 设备上报原始Hex:", hex.EncodeToString(resp))
+	fmt.Println("=====================================")
+
+	cmd := binary.BigEndian.Uint16(resp[2:4])
+	switch cmd {
+	case 0x0072:
+		fmt.Println("📦 解析【设备版本信息】")
+		if len(resp) >= 8 {
+			fmt.Printf("硬件版本: %d.%d\n", resp[6], resp[7])
+			if len(resp) >= 9 {
+				fmt.Printf("软件版本: %d.%d\n", resp[8], resp[9])
+			}
+		}
+	case 0x0064:
+		fmt.Println("📦 解析【设备远程网口信息】")
+		if len(resp) >= 16 {
+			status := resp[6]
+			option := resp[7]
+			enable := resp[8]
+			ip := fmt.Sprintf("%d.%d.%d.%d", resp[9], resp[10], resp[11], resp[12])
+			port := binary.BigEndian.Uint16([]byte{resp[13], resp[14]})
+			heartTime := resp[15]
+
+			fmt.Printf("执行状态: 0x%02X (%s)\n", status, map[byte]string{0x00: "成功", 0xFF: "失败"}[status])
+			fmt.Printf("操作类型: 0x%02X (0x02=读取)\n", option)
+			fmt.Printf("上报使能: %d (1=开启, 0=关闭)\n", enable)
+			fmt.Printf("远程IP: %s\n", ip)
+			fmt.Printf("远程端口: %d\n", port)
+			fmt.Printf("心跳时间: %d*5S = %dS\n", heartTime, heartTime*5)
+		}
+	default:
+		fmt.Println("📦 解析【设备主动上报数据】")
+		// 这里根据设备上报的自定义指令格式扩展解析逻辑
+	}
+	fmt.Println("=====================================")
+	return true, nil
+}
+
+// ===================== 串口持续读取核心逻辑 =====================
+// SerialReaderWrapper 封装串口读取器,增加协程控制
+type SerialReaderWrapper struct {
+	reader    *uhf.SerialReader
+	ctx       context.Context
+	cancel    context.CancelFunc
+	dataChan  chan []byte    // 上报数据通道
+	errChan   chan error     // 错误通道
+	wg        sync.WaitGroup // 等待组,确保协程优雅退出
+	isRunning bool           // 读取协程运行状态
+	comPort   string         // 串口号
+	baudRate  int            // 波特率
+	reconnect bool           // 是否自动重连
+}
+
+// NewSerialReaderWrapper 创建串口读取包装器
+func NewSerialReaderWrapper(comPort string, baudRate int, reconnect bool) *SerialReaderWrapper {
+	ctx, cancel := context.WithCancel(context.Background())
+	return &SerialReaderWrapper{
+		reader:    uhf.NewSerialReader(comPort, baudRate),
+		ctx:       ctx,
+		cancel:    cancel,
+		dataChan:  make(chan []byte, 100), // 缓冲通道,避免阻塞(根据上报频率调整大小)
+		errChan:   make(chan error, 10),
+		comPort:   comPort,
+		baudRate:  baudRate,
+		reconnect: reconnect,
+	}
+}
+
+// StartReadLoop 启动串口持续读取协程
+func (w *SerialReaderWrapper) StartReadLoop() error {
+	if w.isRunning {
+		return fmt.Errorf("读取协程已在运行")
+	}
+
+	// 先连接串口
+	if err := w.reader.Connect(); err != nil {
+		return fmt.Errorf("串口初始连接失败: %v", err)
+	}
+
+	w.isRunning = true
+	w.wg.Add(1)
+
+	// 启动读取协程
+	go func() {
+		defer func() {
+			// 协程退出时清理
+			w.isRunning = false
+			w.reader.Disconnect()
+			w.wg.Done()
+			close(w.dataChan)
+			close(w.errChan)
+			fmt.Println("🔌 串口读取协程已退出")
+		}()
+
+		fmt.Println("🔄 串口持续读取协程已启动,等待设备上报数据...")
+
+		for {
+			select {
+			// 检测退出信号
+			case <-w.ctx.Done():
+				fmt.Println("📢 收到退出信号,停止读取串口")
+				return
+			default:
+				// 循环读取串口数据
+				buf, err := w.reader.ReadData()
+				if err != nil {
+					w.errChan <- fmt.Errorf("读取串口失败: %v", err)
+
+					// 自动重连逻辑
+					if w.reconnect {
+						fmt.Println("🔁 尝试重新连接串口...")
+						w.reader.Disconnect()
+						// 重连间隔2秒,避免频繁重试
+						time.Sleep(2 * time.Second)
+						if reconnectErr := w.reader.Connect(); reconnectErr != nil {
+							w.errChan <- fmt.Errorf("串口重连失败: %v", reconnectErr)
+						} else {
+							w.errChan <- fmt.Errorf("串口重连成功")
+						}
+					} else {
+						// 不重连则退出协程
+						return
+					}
+					continue
+				}
+
+				// 读取到有效数据,发送到数据通道
+				if len(buf) > 0 {
+					select {
+					case w.dataChan <- buf:
+						// 数据成功写入通道
+					default:
+						// 通道满时丢弃(或根据业务调整,比如阻塞/报错)
+						w.errChan <- fmt.Errorf("数据通道已满,丢弃上报数据: %x", buf)
+					}
+				}
+			}
+		}
+	}()
+
+	return nil
+}
+
+// StopReadLoop 停止读取协程(优雅退出)
+func (w *SerialReaderWrapper) StopReadLoop() {
+	if !w.isRunning {
+		return
+	}
+	// 发送退出信号
+	w.cancel()
+	// 等待协程退出
+	w.wg.Wait()
+	fmt.Println("✅ 串口读取协程已优雅停止")
+}
+
+// ProcessData 处理上报数据(可在主线程/另一个协程中运行)
+func (w *SerialReaderWrapper) ProcessData() {
+	for {
+		select {
+		case <-w.ctx.Done():
+			return
+		// 处理上报数据
+		case data, ok := <-w.dataChan:
+			if !ok {
+				return
+			}
+			fmt.Println("\n📥 收到设备上报数据:")
+			// 解析数据(复用你已有的parseResp函数)
+			if _, err := parseResp(data); err != nil {
+				fmt.Printf("❌ 解析上报数据失败: %v | 原始数据: %x\n", err, data)
+			}
+		// 处理错误
+		case err, ok := <-w.errChan:
+			if !ok {
+				return
+			}
+			fmt.Printf("⚠️  串口读取错误: %v\n", err)
+		}
+	}
+}
+
+// ===================== 测试主函数 =====================
+func TestContinuousSerialRead(t *testing.T) {
+	// 1. 创建串口包装器(COM9,115200波特率,开启自动重连)
+	serialWrapper := NewSerialReaderWrapper("COM9", 115200, true)
+	defer serialWrapper.StopReadLoop() // 测试结束时停止协程
+
+	// 2. 启动持续读取协程
+	if err := serialWrapper.StartReadLoop(); err != nil {
+		t.Fatalf("❌ 启动串口读取协程失败: %v", err)
+	}
+
+	// 3. 启动数据处理协程(主线程也可以直接调用ProcessData,这里用协程避免阻塞)
+	go serialWrapper.ProcessData()
+
+	// 4. 主线程保持运行(模拟业务逻辑,比如运行30秒后退出)
+	fmt.Println("🚀 主线程运行中,按Ctrl+C退出...")
+	time.Sleep(30 * time.Second) // 运行30秒,可替换为无限循环(for {})
+
+	// 5. 主动停止(可选,defer也会自动停止)
+	serialWrapper.StopReadLoop()
+}