Files
pig-farm-controller/internal/infra/repository/device_repository.go
2025-11-05 23:00:07 +08:00

208 lines
8.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package repository
import (
"context"
"fmt"
"strconv"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"gorm.io/gorm"
)
// DeviceRepository 定义了与设备模型相关的数据库操作接口
// 这一层抽象使得上层业务逻辑无需关心底层数据库的具体实现。
type DeviceRepository interface {
// Create 创建一个新设备记录
Create(ctx context.Context, device *models.Device) error
// FindByID 根据主键 ID 查找设备
FindByID(ctx context.Context, id uint) (*models.Device, error)
// FindByIDString 根据字符串形式的主键 ID 查找设备
FindByIDString(ctx context.Context, id string) (*models.Device, error)
// ListAll 获取所有设备的列表
ListAll(ctx context.Context) ([]*models.Device, error)
// ListAllSensors 获取所有传感器类型的设备列表
ListAllSensors(ctx context.Context) ([]*models.Device, error)
// ListByAreaControllerID 根据区域主控 ID 列出所有子设备
ListByAreaControllerID(ctx context.Context, areaControllerID uint) ([]*models.Device, error)
// FindByDeviceTemplateID 根据设备模板ID查找所有使用该模板的设备
FindByDeviceTemplateID(ctx context.Context, deviceTemplateID uint) ([]*models.Device, error)
// Update 更新一个已有的设备信息
Update(ctx context.Context, device *models.Device) error
// Delete 根据主键 ID 删除一个设备
Delete(ctx context.Context, id uint) error
// FindByAreaControllerAndPhysicalAddress 根据区域主控ID和物理地址(总线号、总线地址)查找设备
FindByAreaControllerAndPhysicalAddress(ctx context.Context, areaControllerID uint, busNumber int, busAddress int) (*models.Device, error)
// GetDevicesByIDsTx 在指定事务中根据ID列表获取设备
GetDevicesByIDsTx(ctx context.Context, tx *gorm.DB, ids []uint) ([]models.Device, error)
// IsDeviceInUse 检查设备是否被任何任务使用
IsDeviceInUse(ctx context.Context, deviceID uint) (bool, error)
// IsAreaControllerInUse 检查区域主控是否被任何设备使用
IsAreaControllerInUse(ctx context.Context, areaControllerID uint) (bool, error)
}
// gormDeviceRepository 是 DeviceRepository 的 GORM 实现
type gormDeviceRepository struct {
ctx context.Context
db *gorm.DB
}
// NewGormDeviceRepository 创建一个新的 DeviceRepository GORM 实现实例
func NewGormDeviceRepository(ctx context.Context, db *gorm.DB) DeviceRepository {
return &gormDeviceRepository{ctx: ctx, db: db}
}
// Create 创建一个新的设备记录
func (r *gormDeviceRepository) Create(ctx context.Context, device *models.Device) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "Create")
// BeforeSave 钩子会在这里被自动触发
return r.db.WithContext(repoCtx).Create(device).Error
}
// FindByID 根据 ID 查找设备
func (r *gormDeviceRepository) FindByID(ctx context.Context, id uint) (*models.Device, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByID")
var device models.Device
if err := r.db.WithContext(repoCtx).Preload("AreaController").Preload("DeviceTemplate").First(&device, id).Error; err != nil {
return nil, err
}
return &device, nil
}
// GetDevicesByIDsTx 在指定事务中根据ID列表获取设备
func (r *gormDeviceRepository) GetDevicesByIDsTx(ctx context.Context, tx *gorm.DB, ids []uint) ([]models.Device, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "GetDevicesByIDsTx")
var devices []models.Device
if len(ids) == 0 {
return devices, nil
}
if err := tx.WithContext(repoCtx).Where("id IN ?", ids).Find(&devices).Error; err != nil {
return nil, err
}
return devices, nil
}
// FindByIDString 根据字符串形式的主键 ID 查找设备
func (r *gormDeviceRepository) FindByIDString(ctx context.Context, id string) (*models.Device, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByIDString")
// 将字符串ID转换为uint64
idInt, err := strconv.ParseUint(id, 10, 64)
if err != nil {
// 如果转换失败说明ID格式不正确返回一个明确的错误
return nil, fmt.Errorf("无效的设备ID格式: %w", err)
}
// 调用已有的 FindByID 方法
return r.FindByID(repoCtx, uint(idInt))
}
// ListAll 获取所有设备的列表
func (r *gormDeviceRepository) ListAll(ctx context.Context) ([]*models.Device, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "ListAll")
var devices []*models.Device
if err := r.db.WithContext(repoCtx).Preload("AreaController").Preload("DeviceTemplate").Find(&devices).Error; err != nil {
return nil, err
}
return devices, nil
}
// ListAllSensors 检索归类为传感器的所有设备
func (r *gormDeviceRepository) ListAllSensors(ctx context.Context) ([]*models.Device, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "ListAllSensors")
var sensors []*models.Device
err := r.db.WithContext(repoCtx).Preload("AreaController").Preload("DeviceTemplate").
Joins("JOIN device_templates ON device_templates.id = devices.device_template_id").
Where("device_templates.category = ?", models.CategorySensor).
Find(&sensors).Error
if err != nil {
return nil, fmt.Errorf("查询所有传感器失败: %w", err)
}
return sensors, nil
}
// ListByAreaControllerID 根据区域主控 ID 列出所有子设备
func (r *gormDeviceRepository) ListByAreaControllerID(ctx context.Context, areaControllerID uint) ([]*models.Device, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "ListByAreaControllerID")
var devices []*models.Device
err := r.db.WithContext(repoCtx).Preload("AreaController").Preload("DeviceTemplate").Where("area_controller_id = ?", areaControllerID).Find(&devices).Error
if err != nil {
return nil, err
}
return devices, nil
}
// FindByDeviceTemplateID 根据设备模板ID查找所有使用该模板的设备
func (r *gormDeviceRepository) FindByDeviceTemplateID(ctx context.Context, deviceTemplateID uint) ([]*models.Device, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByDeviceTemplateID")
var devices []*models.Device
err := r.db.WithContext(repoCtx).Where("device_template_id = ?", deviceTemplateID).Find(&devices).Error
if err != nil {
return nil, fmt.Errorf("查询使用设备模板ID %d 的设备失败: %w", deviceTemplateID, err)
}
return devices, nil
}
// Update 更新一个已有的设备信息
// GORM 的 Save 方法会自动处理主键存在时更新,不存在时创建的逻辑,但这里我们明确用于更新。
func (r *gormDeviceRepository) Update(ctx context.Context, device *models.Device) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "Update")
return r.db.WithContext(repoCtx).Save(device).Error
}
// Delete 根据 ID 删除一个设备
// GORM 使用软删除,记录不会从数据库中物理移除,而是设置 DeletedAt 字段。
func (r *gormDeviceRepository) Delete(ctx context.Context, id uint) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "Delete")
return r.db.WithContext(repoCtx).Delete(&models.Device{}, id).Error
}
// FindByAreaControllerAndPhysicalAddress 根据区域主控ID和物理地址(总线号、总线地址)查找设备
func (r *gormDeviceRepository) FindByAreaControllerAndPhysicalAddress(ctx context.Context, areaControllerID uint, busNumber int, busAddress int) (*models.Device, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByAreaControllerAndPhysicalAddress")
var device models.Device
err := r.db.WithContext(repoCtx).Preload("AreaController").Preload("DeviceTemplate").
Where("area_controller_id = ?", areaControllerID).
Where("properties->>'bus_number' = ?", strconv.Itoa(busNumber)).
Where("properties->>'bus_address' = ?", strconv.Itoa(busAddress)).
First(&device).Error
if err != nil {
return nil, fmt.Errorf("根据区域主控ID %d 和物理地址 (总线号: %d, 总线地址: %d) 查找设备失败: %w", areaControllerID, busNumber, busAddress, err)
}
return &device, nil
}
// IsDeviceInUse 检查设备是否被任何任务使用
func (r *gormDeviceRepository) IsDeviceInUse(ctx context.Context, deviceID uint) (bool, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "IsDeviceInUse")
var count int64
// 直接对 device_tasks 关联表进行 COUNT 操作,性能最高
err := r.db.WithContext(repoCtx).Model(&models.DeviceTask{}).Where("device_id = ?", deviceID).Count(&count).Error
if err != nil {
return false, fmt.Errorf("查询设备任务关联失败: %w", err)
}
return count > 0, nil
}
// IsAreaControllerInUse 检查区域主控是否被任何设备使用
func (r *gormDeviceRepository) IsAreaControllerInUse(ctx context.Context, areaControllerID uint) (bool, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "IsAreaControllerInUse")
var count int64
if err := r.db.WithContext(repoCtx).Model(&models.Device{}).Where("area_controller_id = ?", areaControllerID).Count(&count).Error; err != nil {
return false, fmt.Errorf("检查区域主控使用情况失败: %w", err)
}
return count > 0, nil
}