修改infra.repository包

This commit is contained in:
2025-11-05 23:00:07 +08:00
parent 97aea66f7c
commit 10b123ab93
25 changed files with 877 additions and 608 deletions

View File

@@ -40,7 +40,7 @@ func initInfrastructure(ctx context.Context, cfg *config.Config) (*Infrastructur
return nil, err
}
repos := initRepositories(ctx, storage.GetDB())
repos := initRepositories(ctx, storage.GetDB(ctx))
lora, err := initLora(ctx, cfg, repos)
if err != nil {
@@ -279,12 +279,12 @@ func initLora(
if cfg.Lora.Mode == config.LoraMode_LoRaWAN {
logger.Info("当前运行模式: lora_wan。初始化 ChirpStack 监听器和传输层。")
listenHandler = webhook.NewChirpStackListener(logs.AddCompName(baseCtx, "ChirpStackListener"), repos.sensorDataRepo, repos.deviceRepo, repos.areaControllerRepo, repos.deviceCommandLogRepo, repos.pendingCollectionRepo)
comm = lora.NewChirpStackTransport(cfg.ChirpStack, logs.AddCompName(baseCtx, "ChirpStackTransport"))
comm = lora.NewChirpStackTransport(logs.AddCompName(baseCtx, "ChirpStackTransport"), cfg.ChirpStack)
loraListener = lora.NewPlaceholderTransport(logs.AddCompName(baseCtx, "PlaceholderTransport"))
} else {
logger.Info("当前运行模式: lora_mesh。初始化 LoRa Mesh 传输层和占位符监听器。")
listenHandler = webhook.NewPlaceholderListener(logs.AddCompName(baseCtx, "PlaceholderListener"))
tp, err := lora.NewLoRaMeshUartPassthroughTransport(cfg.LoraMesh, logs.AddCompName(baseCtx, "LoRaMeshTransport"), repos.areaControllerRepo, repos.pendingCollectionRepo, repos.deviceRepo, repos.sensorDataRepo)
tp, err := lora.NewLoRaMeshUartPassthroughTransport(logs.AddCompName(baseCtx, "LoRaMeshTransport"), cfg.LoraMesh, repos.areaControllerRepo, repos.pendingCollectionRepo, repos.deviceRepo, repos.sensorDataRepo)
if err != nil {
return nil, fmt.Errorf("无法初始化 LoRa Mesh 模块: %w", err)
}
@@ -319,6 +319,7 @@ func initNotifyService(
// 2. 根据配置,按需创建并收集所有启用的其他 Notifier 实例
if cfg.SMTP.Enabled {
smtpNotifier := notify.NewSMTPNotifier(
logs.AddCompName(baseCtx, "SMTPNotifier"),
cfg.SMTP.Host,
cfg.SMTP.Port,
cfg.SMTP.Username,
@@ -331,6 +332,7 @@ func initNotifyService(
if cfg.WeChat.Enabled {
wechatNotifier := notify.NewWechatNotifier(
logs.AddCompName(baseCtx, "WechatNotifier"),
cfg.WeChat.CorpID,
cfg.WeChat.AgentID,
cfg.WeChat.Secret,
@@ -341,6 +343,7 @@ func initNotifyService(
if cfg.Lark.Enabled {
larkNotifier := notify.NewLarkNotifier(
logs.AddCompName(baseCtx, "LarkNotifier"),
cfg.Lark.AppID,
cfg.Lark.AppSecret,
)
@@ -387,7 +390,7 @@ func initNotifyService(
func initStorage(ctx context.Context, cfg config.DatabaseConfig) (database.Storage, error) {
// 创建存储实例
storage := database.NewStorage(logs.AddCompName(context.Background(), "Storage"), cfg)
if err := storage.Connect(); err != nil {
if err := storage.Connect(ctx); err != nil {
// 错误已在 Connect 内部被记录,这里只需包装并返回
return nil, fmt.Errorf("数据库连接失败: %w", err)
}

View File

@@ -17,19 +17,19 @@ const (
// initializeState 在应用启动时准备其初始数据状态。
// 这包括清理任何因上次异常关闭而留下的悬空任务或请求。
func (app *Application) initializeState(ctx context.Context) error {
newCtx, logger := logs.Trace(ctx, app.Ctx, "InitializeState")
appCtx, logger := logs.Trace(ctx, app.Ctx, "InitializeState")
// 初始化预定义系统计划 (致命错误)
if err := app.initializeSystemPlans(ctx); err != nil {
return fmt.Errorf("初始化预定义系统计划失败: %w", err)
}
// 清理待采集任务 (非致命错误)
if err := app.initializePendingCollections(newCtx); err != nil {
if err := app.initializePendingCollections(appCtx); err != nil {
logger.Errorw("清理待采集任务时发生非致命错误", "error", err)
}
// 初始化待执行任务列表 (致命错误)
if err := app.initializePendingTasks(newCtx); err != nil {
if err := app.initializePendingTasks(appCtx); err != nil {
return fmt.Errorf("初始化待执行任务列表失败: %w", err)
}
@@ -38,14 +38,14 @@ func (app *Application) initializeState(ctx context.Context) error {
// initializeSystemPlans 确保预定义的系统计划在数据库中存在并保持最新。
func (app *Application) initializeSystemPlans(ctx context.Context) error {
logger := logs.TraceLogger(ctx, app.Ctx, "InitializeSystemPlans")
appCtx, logger := logs.Trace(ctx, app.Ctx, "InitializeSystemPlans")
logger.Info("开始检查并更新预定义的系统计划...")
// 动态构建预定义计划列表
predefinedSystemPlans := app.getPredefinedSystemPlans()
// 1. 获取所有已存在的系统计划
existingPlans, _, err := app.Infra.repos.planRepo.ListPlans(repository.ListPlansOptions{
existingPlans, _, err := app.Infra.repos.planRepo.ListPlans(appCtx, repository.ListPlansOptions{
PlanType: repository.PlanTypeFilterSystem,
}, 1, 99999) // 使用一个较大的 pageSize 来获取所有系统计划
if err != nil {
@@ -70,7 +70,7 @@ func (app *Application) initializeSystemPlans(ctx context.Context) error {
predefinedPlan.ID = foundExistingPlan.ID
predefinedPlan.ExecuteCount = foundExistingPlan.ExecuteCount
if err := app.Infra.repos.planRepo.UpdatePlan(predefinedPlan); err != nil {
if err := app.Infra.repos.planRepo.UpdatePlan(appCtx, predefinedPlan); err != nil {
return fmt.Errorf("更新预定义计划 '%s' 失败: %w", predefinedPlan.Name, err)
} else {
logger.Infof("成功更新预定义计划 '%s'。", predefinedPlan.Name)
@@ -78,7 +78,7 @@ func (app *Application) initializeSystemPlans(ctx context.Context) error {
} else {
// 如果计划不存在, 则创建
logger.Infof("预定义计划 '%s' 不存在,正在创建...", predefinedPlan.Name)
if err := app.Infra.repos.planRepo.CreatePlan(predefinedPlan); err != nil {
if err := app.Infra.repos.planRepo.CreatePlan(appCtx, predefinedPlan); err != nil {
return fmt.Errorf("创建预定义计划 '%s' 失败: %w", predefinedPlan.Name, err)
} else {
logger.Infof("成功创建预定义计划 '%s'。", predefinedPlan.Name)
@@ -124,11 +124,11 @@ func (app *Application) getPredefinedSystemPlans() []models.Plan {
// 我们的策略是:任何在程序重启前仍处于“待处理”状态的请求,都应被视为已失败。
// 这保证了系统在每次启动时都处于一个干净、确定的状态。
func (app *Application) initializePendingCollections(ctx context.Context) error {
logger := logs.TraceLogger(ctx, app.Ctx, "InitializePendingCollections")
appCtx, logger := logs.Trace(ctx, app.Ctx, "InitializePendingCollections")
logger.Info("开始清理所有未完成的采集请求...")
// 直接将所有 'pending' 状态的请求更新为 'timed_out'。
count, err := app.Infra.repos.pendingCollectionRepo.MarkAllPendingAsTimedOut()
count, err := app.Infra.repos.pendingCollectionRepo.MarkAllPendingAsTimedOut(appCtx)
if err != nil {
return fmt.Errorf("清理未完成的采集请求失败: %v", err)
} else if count > 0 {
@@ -142,7 +142,7 @@ func (app *Application) initializePendingCollections(ctx context.Context) error
// initializePendingTasks 在应用启动时清理并刷新待执行任务列表。
func (app *Application) initializePendingTasks(ctx context.Context) error {
logger := logs.TraceLogger(ctx, app.Ctx, "InitializePendingTasks")
appCtx, logger := logs.Trace(ctx, app.Ctx, "InitializePendingTasks")
planRepo := app.Infra.repos.planRepo
pendingTaskRepo := app.Infra.repos.pendingTaskRepo
executionLogRepo := app.Infra.repos.executionLogRepo
@@ -152,7 +152,7 @@ func (app *Application) initializePendingTasks(ctx context.Context) error {
// 阶段一:修正因崩溃导致状态不一致的固定次数计划
logger.Info("阶段一:开始修正因崩溃导致状态不一致的固定次数计划...")
plansToCorrect, err := planRepo.FindPlansWithPendingTasks()
plansToCorrect, err := planRepo.FindPlansWithPendingTasks(appCtx)
if err != nil {
return fmt.Errorf("查找需要修正的计划失败: %w", err)
}
@@ -172,7 +172,7 @@ func (app *Application) initializePendingTasks(ctx context.Context) error {
}
// 保存更新后的计划
if err := planRepo.UpdatePlan(plan); err != nil {
if err := planRepo.UpdatePlan(appCtx, plan); err != nil {
logger.Errorf("修正计划 #%d 状态失败: %v", plan.ID, err)
// 这是一个非阻塞性错误,继续处理其他计划
}
@@ -184,7 +184,7 @@ func (app *Application) initializePendingTasks(ctx context.Context) error {
// --- 新增逻辑:处理因崩溃导致状态不一致的计划主表状态 ---
// 1. 查找所有未完成的计划执行日志 (状态为 Started 或 Waiting)
incompletePlanLogs, err := executionLogRepo.FindIncompletePlanExecutionLogs()
incompletePlanLogs, err := executionLogRepo.FindIncompletePlanExecutionLogs(appCtx)
if err != nil {
return fmt.Errorf("查找未完成的计划执行日志失败: %w", err)
}
@@ -198,7 +198,7 @@ func (app *Application) initializePendingTasks(ctx context.Context) error {
// 3. 对于每个受影响的 PlanID重置其 execute_count 并将其状态设置为 Failed, 系统计划不受此影响
for planID := range affectedPlanIDs {
// 首先,获取计划的详细信息以判断其类型
plan, err := planRepo.GetBasicPlanByID(planID)
plan, err := planRepo.GetBasicPlanByID(appCtx, planID)
if err != nil {
logger.Errorf("在尝试修正计划状态时,获取计划 #%d 的基本信息失败: %v", planID, err)
continue // 获取失败,跳过此计划
@@ -213,7 +213,7 @@ func (app *Application) initializePendingTasks(ctx context.Context) error {
// 对于非系统计划,执行原有的失败标记逻辑
logger.Warnf("检测到计划 #%d 在应用崩溃前处于未完成状态,将重置其计数并标记为失败。", planID)
// 使用 UpdatePlanStateAfterExecution 来更新主表状态,避免影响关联数据
if err := planRepo.UpdatePlanStateAfterExecution(planID, 0, models.PlanStatusFailed); err != nil {
if err := planRepo.UpdatePlanStateAfterExecution(appCtx, planID, 0, models.PlanStatusFailed); err != nil {
logger.Errorf("重置计划 #%d 计数并标记为失败时出错: %v", planID, err)
// 这是一个非阻塞性错误,继续处理其他计划
}
@@ -221,26 +221,26 @@ func (app *Application) initializePendingTasks(ctx context.Context) error {
logger.Info("阶段二:计划主表状态修正完成。")
// 直接调用新的方法来更新计划执行日志状态为失败
if err := executionLogRepo.FailAllIncompletePlanExecutionLogs(); err != nil {
if err := executionLogRepo.FailAllIncompletePlanExecutionLogs(appCtx); err != nil {
logger.Errorf("更新所有未完成计划执行日志状态为失败失败: %v", err)
// 这是一个非阻塞性错误,继续执行
}
// 直接调用新的方法来更新任务执行日志状态为取消
if err := executionLogRepo.CancelAllIncompleteTaskExecutionLogs(); err != nil {
if err := executionLogRepo.CancelAllIncompleteTaskExecutionLogs(appCtx); err != nil {
logger.Errorf("更新所有未完成任务执行日志状态为取消失败: %v", err)
// 这是一个非阻塞性错误,继续执行
}
// 清空待执行列表
if err := pendingTaskRepo.ClearAllPendingTasks(); err != nil {
if err := pendingTaskRepo.ClearAllPendingTasks(appCtx); err != nil {
return fmt.Errorf("清空待执行任务列表失败: %w", err)
}
logger.Info("阶段二:待执行任务和相关日志清理完成。")
// 阶段三:初始刷新
logger.Info("阶段三:开始刷新待执行列表...")
if err := planService.RefreshPlanTriggers(); err != nil {
if err := planService.RefreshPlanTriggers(appCtx); err != nil {
return fmt.Errorf("刷新待执行任务列表失败: %w", err)
}
logger.Info("阶段三:待执行任务列表初始化完成。")

View File

@@ -1,72 +1,85 @@
package repository
import (
"context"
"fmt"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"gorm.io/gorm"
)
// AreaControllerRepository 定义了对 AreaController 模型的数据库操作接口
type AreaControllerRepository interface {
FindByID(id uint) (*models.AreaController, error)
FindByNetworkID(networkID string) (*models.AreaController, error)
Create(ac *models.AreaController) error
ListAll() ([]*models.AreaController, error)
Update(ac *models.AreaController) error
Delete(id uint) error
FindByID(ctx context.Context, id uint) (*models.AreaController, error)
FindByNetworkID(ctx context.Context, networkID string) (*models.AreaController, error)
Create(ctx context.Context, ac *models.AreaController) error
ListAll(ctx context.Context) ([]*models.AreaController, error)
Update(ctx context.Context, ac *models.AreaController) error
Delete(ctx context.Context, id uint) error
}
// gormAreaControllerRepository 是 AreaControllerRepository 的 GORM 实现。
type gormAreaControllerRepository struct {
db *gorm.DB
ctx context.Context
db *gorm.DB
}
// NewGormAreaControllerRepository 创建一个新的 AreaControllerRepository GORM 实现实例。
func NewGormAreaControllerRepository(db *gorm.DB) AreaControllerRepository {
return &gormAreaControllerRepository{db: db}
func NewGormAreaControllerRepository(ctx context.Context, db *gorm.DB) AreaControllerRepository {
return &gormAreaControllerRepository{
ctx: ctx,
db: db,
}
}
// Create 创建一个新的 AreaController 记录。
func (r *gormAreaControllerRepository) Create(ac *models.AreaController) error {
return r.db.Create(ac).Error
func (r *gormAreaControllerRepository) Create(ctx context.Context, ac *models.AreaController) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "Create")
return r.db.WithContext(repoCtx).Create(ac).Error
}
// ListAll 返回所有 AreaController 的列表。
func (r *gormAreaControllerRepository) ListAll() ([]*models.AreaController, error) {
func (r *gormAreaControllerRepository) ListAll(ctx context.Context) ([]*models.AreaController, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "ListAll")
var areaControllers []*models.AreaController
if err := r.db.Find(&areaControllers).Error; err != nil {
if err := r.db.WithContext(repoCtx).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
func (r *gormAreaControllerRepository) Update(ctx context.Context, ac *models.AreaController) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "Update")
return r.db.WithContext(repoCtx).Save(ac).Error
}
// Delete 删除一个 AreaController 记录。
func (r *gormAreaControllerRepository) Delete(id uint) error {
if err := r.db.Delete(&models.AreaController{}, id).Error; err != nil {
func (r *gormAreaControllerRepository) Delete(ctx context.Context, id uint) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "Delete")
if err := r.db.WithContext(repoCtx).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) {
func (r *gormAreaControllerRepository) FindByID(ctx context.Context, id uint) (*models.AreaController, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByID")
var areaController models.AreaController
if err := r.db.First(&areaController, id).Error; err != nil {
if err := r.db.WithContext(repoCtx).First(&areaController, id).Error; err != nil {
return nil, err
}
return &areaController, nil
}
// FindByNetworkID 通过 NetworkID 查找一个 AreaController。
func (r *gormAreaControllerRepository) FindByNetworkID(networkID string) (*models.AreaController, error) {
func (r *gormAreaControllerRepository) FindByNetworkID(ctx context.Context, networkID string) (*models.AreaController, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByNetworkID")
var areaController models.AreaController
if err := r.db.Where("network_id = ?", networkID).First(&areaController).Error; err != nil {
if err := r.db.WithContext(repoCtx).Where("network_id = ?", networkID).First(&areaController).Error; err != nil {
return nil, err
}
return &areaController, nil

View File

@@ -1,9 +1,12 @@
package repository
import (
"context"
"time"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"gorm.io/gorm"
)
@@ -18,40 +21,44 @@ type DeviceCommandLogListOptions struct {
// DeviceCommandLogRepository 定义了设备下行命令历史记录的数据访问接口
type DeviceCommandLogRepository interface {
Create(record *models.DeviceCommandLog) error
FindByMessageID(messageID string) (*models.DeviceCommandLog, error)
UpdateAcknowledgedAt(messageID string, acknowledgedAt time.Time, receivedSuccess bool) error
Create(ctx context.Context, record *models.DeviceCommandLog) error
FindByMessageID(ctx context.Context, messageID string) (*models.DeviceCommandLog, error)
UpdateAcknowledgedAt(ctx context.Context, messageID string, acknowledgedAt time.Time, receivedSuccess bool) error
// List 支持分页和过滤的列表查询
List(opts DeviceCommandLogListOptions, page, pageSize int) ([]models.DeviceCommandLog, int64, error)
List(ctx context.Context, opts DeviceCommandLogListOptions, page, pageSize int) ([]models.DeviceCommandLog, int64, error)
}
// gormDeviceCommandLogRepository 是 DeviceCommandLogRepository 接口的 GORM 实现
type gormDeviceCommandLogRepository struct {
db *gorm.DB
ctx context.Context
db *gorm.DB
}
// NewGormDeviceCommandLogRepository 创建一个新的 DeviceCommandLogRepository GORM 实现
func NewGormDeviceCommandLogRepository(db *gorm.DB) DeviceCommandLogRepository {
return &gormDeviceCommandLogRepository{db: db}
func NewGormDeviceCommandLogRepository(ctx context.Context, db *gorm.DB) DeviceCommandLogRepository {
return &gormDeviceCommandLogRepository{ctx: ctx, db: db}
}
// Create 实现 DeviceCommandLogRepository 接口的 Create 方法
func (r *gormDeviceCommandLogRepository) Create(record *models.DeviceCommandLog) error {
return r.db.Create(record).Error
func (r *gormDeviceCommandLogRepository) Create(ctx context.Context, record *models.DeviceCommandLog) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "Create")
return r.db.WithContext(repoCtx).Create(record).Error
}
// FindByMessageID 实现 DeviceCommandLogRepository 接口的 FindByMessageID 方法
func (r *gormDeviceCommandLogRepository) FindByMessageID(messageID string) (*models.DeviceCommandLog, error) {
func (r *gormDeviceCommandLogRepository) FindByMessageID(ctx context.Context, messageID string) (*models.DeviceCommandLog, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByMessageID")
var record models.DeviceCommandLog
if err := r.db.Where("message_id = ?", messageID).First(&record).Error; err != nil {
if err := r.db.WithContext(repoCtx).Where("message_id = ?", messageID).First(&record).Error; err != nil {
return nil, err
}
return &record, nil
}
// UpdateAcknowledgedAt 实现 DeviceCommandLogRepository 接口的 UpdateAcknowledgedAt 方法
func (r *gormDeviceCommandLogRepository) UpdateAcknowledgedAt(messageID string, acknowledgedAt time.Time, receivedSuccess bool) error {
return r.db.Model(&models.DeviceCommandLog{}).
func (r *gormDeviceCommandLogRepository) UpdateAcknowledgedAt(ctx context.Context, messageID string, acknowledgedAt time.Time, receivedSuccess bool) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdateAcknowledgedAt")
return r.db.WithContext(repoCtx).Model(&models.DeviceCommandLog{}).
Where("message_id = ?", messageID).
Updates(map[string]interface{}{
"acknowledged_at": acknowledgedAt,
@@ -60,7 +67,8 @@ func (r *gormDeviceCommandLogRepository) UpdateAcknowledgedAt(messageID string,
}
// List 实现了分页和过滤查询设备命令日志的功能
func (r *gormDeviceCommandLogRepository) List(opts DeviceCommandLogListOptions, page, pageSize int) ([]models.DeviceCommandLog, int64, error) {
func (r *gormDeviceCommandLogRepository) List(ctx context.Context, opts DeviceCommandLogListOptions, page, pageSize int) ([]models.DeviceCommandLog, int64, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "List")
// --- 校验分页参数 ---
if page <= 0 || pageSize <= 0 {
return nil, 0, ErrInvalidPagination
@@ -69,7 +77,7 @@ func (r *gormDeviceCommandLogRepository) List(opts DeviceCommandLogListOptions,
var results []models.DeviceCommandLog
var total int64
query := r.db.Model(&models.DeviceCommandLog{})
query := r.db.WithContext(repoCtx).Model(&models.DeviceCommandLog{})
// --- 应用过滤条件 ---
if opts.DeviceID != nil {

View File

@@ -1,10 +1,13 @@
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"
)
@@ -12,83 +15,89 @@ import (
// 这一层抽象使得上层业务逻辑无需关心底层数据库的具体实现。
type DeviceRepository interface {
// Create 创建一个新设备记录
Create(device *models.Device) error
Create(ctx context.Context, device *models.Device) error
// FindByID 根据主键 ID 查找设备
FindByID(id uint) (*models.Device, error)
FindByID(ctx context.Context, id uint) (*models.Device, error)
// FindByIDString 根据字符串形式的主键 ID 查找设备
FindByIDString(id string) (*models.Device, error)
FindByIDString(ctx context.Context, id string) (*models.Device, error)
// ListAll 获取所有设备的列表
ListAll() ([]*models.Device, error)
ListAll(ctx context.Context) ([]*models.Device, error)
// ListAllSensors 获取所有传感器类型的设备列表
ListAllSensors() ([]*models.Device, error)
ListAllSensors(ctx context.Context) ([]*models.Device, error)
// ListByAreaControllerID 根据区域主控 ID 列出所有子设备
ListByAreaControllerID(areaControllerID uint) ([]*models.Device, error)
ListByAreaControllerID(ctx context.Context, areaControllerID uint) ([]*models.Device, error)
// FindByDeviceTemplateID 根据设备模板ID查找所有使用该模板的设备
FindByDeviceTemplateID(deviceTemplateID uint) ([]*models.Device, error)
FindByDeviceTemplateID(ctx context.Context, deviceTemplateID uint) ([]*models.Device, error)
// Update 更新一个已有的设备信息
Update(device *models.Device) error
Update(ctx context.Context, device *models.Device) error
// Delete 根据主键 ID 删除一个设备
Delete(id uint) error
Delete(ctx context.Context, id uint) error
// FindByAreaControllerAndPhysicalAddress 根据区域主控ID和物理地址(总线号、总线地址)查找设备
FindByAreaControllerAndPhysicalAddress(areaControllerID uint, busNumber int, busAddress int) (*models.Device, error)
FindByAreaControllerAndPhysicalAddress(ctx context.Context, areaControllerID uint, busNumber int, busAddress int) (*models.Device, error)
// GetDevicesByIDsTx 在指定事务中根据ID列表获取设备
GetDevicesByIDsTx(tx *gorm.DB, ids []uint) ([]models.Device, error)
GetDevicesByIDsTx(ctx context.Context, tx *gorm.DB, ids []uint) ([]models.Device, error)
// IsDeviceInUse 检查设备是否被任何任务使用
IsDeviceInUse(deviceID uint) (bool, error)
IsDeviceInUse(ctx context.Context, deviceID uint) (bool, error)
// IsAreaControllerInUse 检查区域主控是否被任何设备使用
IsAreaControllerInUse(areaControllerID uint) (bool, error)
IsAreaControllerInUse(ctx context.Context, areaControllerID uint) (bool, error)
}
// gormDeviceRepository 是 DeviceRepository 的 GORM 实现
type gormDeviceRepository struct {
db *gorm.DB
ctx context.Context
db *gorm.DB
}
// NewGormDeviceRepository 创建一个新的 DeviceRepository GORM 实现实例
func NewGormDeviceRepository(db *gorm.DB) DeviceRepository {
return &gormDeviceRepository{db: db}
func NewGormDeviceRepository(ctx context.Context, db *gorm.DB) DeviceRepository {
return &gormDeviceRepository{ctx: ctx, db: db}
}
// Create 创建一个新的设备记录
func (r *gormDeviceRepository) Create(device *models.Device) error {
return r.db.Create(device).Error
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(id uint) (*models.Device, error) {
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.Preload("AreaController").Preload("DeviceTemplate").First(&device, id).Error; err != nil {
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(tx *gorm.DB, ids []uint) ([]models.Device, error) {
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.Where("id IN ?", ids).Find(&devices).Error; err != 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(id string) (*models.Device, error) {
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 {
@@ -96,22 +105,24 @@ func (r *gormDeviceRepository) FindByIDString(id string) (*models.Device, error)
return nil, fmt.Errorf("无效的设备ID格式: %w", err)
}
// 调用已有的 FindByID 方法
return r.FindByID(uint(idInt))
return r.FindByID(repoCtx, uint(idInt))
}
// ListAll 获取所有设备的列表
func (r *gormDeviceRepository) ListAll() ([]*models.Device, error) {
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.Preload("AreaController").Preload("DeviceTemplate").Find(&devices).Error; err != nil {
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() ([]*models.Device, error) {
func (r *gormDeviceRepository) ListAllSensors(ctx context.Context) ([]*models.Device, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "ListAllSensors")
var sensors []*models.Device
err := r.db.Preload("AreaController").Preload("DeviceTemplate").
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
@@ -122,9 +133,10 @@ func (r *gormDeviceRepository) ListAllSensors() ([]*models.Device, error) {
}
// ListByAreaControllerID 根据区域主控 ID 列出所有子设备
func (r *gormDeviceRepository) ListByAreaControllerID(areaControllerID uint) ([]*models.Device, error) {
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.Preload("AreaController").Preload("DeviceTemplate").Where("area_controller_id = ?", areaControllerID).Find(&devices).Error
err := r.db.WithContext(repoCtx).Preload("AreaController").Preload("DeviceTemplate").Where("area_controller_id = ?", areaControllerID).Find(&devices).Error
if err != nil {
return nil, err
}
@@ -132,9 +144,10 @@ func (r *gormDeviceRepository) ListByAreaControllerID(areaControllerID uint) ([]
}
// FindByDeviceTemplateID 根据设备模板ID查找所有使用该模板的设备
func (r *gormDeviceRepository) FindByDeviceTemplateID(deviceTemplateID uint) ([]*models.Device, error) {
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.Where("device_template_id = ?", deviceTemplateID).Find(&devices).Error
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)
}
@@ -143,20 +156,23 @@ func (r *gormDeviceRepository) FindByDeviceTemplateID(deviceTemplateID uint) ([]
// Update 更新一个已有的设备信息
// GORM 的 Save 方法会自动处理主键存在时更新,不存在时创建的逻辑,但这里我们明确用于更新。
func (r *gormDeviceRepository) Update(device *models.Device) error {
return r.db.Save(device).Error
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(id uint) error {
return r.db.Delete(&models.Device{}, id).Error
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(areaControllerID uint, busNumber int, busAddress int) (*models.Device, error) {
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.Preload("AreaController").Preload("DeviceTemplate").
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)).
@@ -169,10 +185,11 @@ func (r *gormDeviceRepository) FindByAreaControllerAndPhysicalAddress(areaContro
}
// IsDeviceInUse 检查设备是否被任何任务使用
func (r *gormDeviceRepository) IsDeviceInUse(deviceID uint) (bool, error) {
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.Model(&models.DeviceTask{}).Where("device_id = ?", deviceID).Count(&count).Error
err := r.db.WithContext(repoCtx).Model(&models.DeviceTask{}).Where("device_id = ?", deviceID).Count(&count).Error
if err != nil {
return false, fmt.Errorf("查询设备任务关联失败: %w", err)
}
@@ -180,9 +197,10 @@ func (r *gormDeviceRepository) IsDeviceInUse(deviceID uint) (bool, error) {
}
// IsAreaControllerInUse 检查区域主控是否被任何设备使用
func (r *gormDeviceRepository) IsAreaControllerInUse(areaControllerID uint) (bool, error) {
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.Model(&models.Device{}).Where("area_controller_id = ?", areaControllerID).Count(&count).Error; err != nil {
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

View File

@@ -1,43 +1,49 @@
package repository
import (
"context"
"errors"
"fmt"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"gorm.io/gorm"
)
// DeviceTemplateRepository 定义了设备模板数据访问的接口
type DeviceTemplateRepository interface {
Create(deviceTemplate *models.DeviceTemplate) error
FindByID(id uint) (*models.DeviceTemplate, error)
FindByName(name string) (*models.DeviceTemplate, error)
ListAll() ([]*models.DeviceTemplate, error)
Update(deviceTemplate *models.DeviceTemplate) error
Delete(id uint) error
IsInUse(id uint) (bool, error)
Create(ctx context.Context, deviceTemplate *models.DeviceTemplate) error
FindByID(ctx context.Context, id uint) (*models.DeviceTemplate, error)
FindByName(ctx context.Context, name string) (*models.DeviceTemplate, error)
ListAll(ctx context.Context) ([]*models.DeviceTemplate, error)
Update(ctx context.Context, deviceTemplate *models.DeviceTemplate) error
Delete(ctx context.Context, id uint) error
IsInUse(ctx context.Context, id uint) (bool, error)
}
// gormDeviceTemplateRepository 是 DeviceTemplateRepository 的 GORM 实现
type gormDeviceTemplateRepository struct {
db *gorm.DB
ctx context.Context
db *gorm.DB
}
// NewGormDeviceTemplateRepository 创建一个新的 gormDeviceTemplateRepository 实例
func NewGormDeviceTemplateRepository(db *gorm.DB) DeviceTemplateRepository {
return &gormDeviceTemplateRepository{db: db}
func NewGormDeviceTemplateRepository(ctx context.Context, db *gorm.DB) DeviceTemplateRepository {
return &gormDeviceTemplateRepository{ctx: ctx, db: db}
}
// Create 在数据库中创建一个新的设备模板
func (r *gormDeviceTemplateRepository) Create(deviceTemplate *models.DeviceTemplate) error {
return r.db.Create(deviceTemplate).Error
func (r *gormDeviceTemplateRepository) Create(ctx context.Context, deviceTemplate *models.DeviceTemplate) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "Create")
return r.db.WithContext(repoCtx).Create(deviceTemplate).Error
}
// FindByID 根据ID查找设备模板
func (r *gormDeviceTemplateRepository) FindByID(id uint) (*models.DeviceTemplate, error) {
func (r *gormDeviceTemplateRepository) FindByID(ctx context.Context, id uint) (*models.DeviceTemplate, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByID")
var deviceTemplate models.DeviceTemplate
if err := r.db.First(&deviceTemplate, id).Error; err != nil {
if err := r.db.WithContext(repoCtx).First(&deviceTemplate, id).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fmt.Errorf("设备模板未找到: %w", err)
}
@@ -47,9 +53,10 @@ func (r *gormDeviceTemplateRepository) FindByID(id uint) (*models.DeviceTemplate
}
// FindByName 根据名称查找设备模板
func (r *gormDeviceTemplateRepository) FindByName(name string) (*models.DeviceTemplate, error) {
func (r *gormDeviceTemplateRepository) FindByName(ctx context.Context, name string) (*models.DeviceTemplate, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByName")
var deviceTemplate models.DeviceTemplate
if err := r.db.Where("name = ?", name).First(&deviceTemplate).Error; err != nil {
if err := r.db.WithContext(repoCtx).Where("name = ?", name).First(&deviceTemplate).Error; err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fmt.Errorf("设备模板未找到: %w", err)
}
@@ -59,31 +66,35 @@ func (r *gormDeviceTemplateRepository) FindByName(name string) (*models.DeviceTe
}
// ListAll 获取所有设备模板
func (r *gormDeviceTemplateRepository) ListAll() ([]*models.DeviceTemplate, error) {
func (r *gormDeviceTemplateRepository) ListAll(ctx context.Context) ([]*models.DeviceTemplate, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "ListAll")
var deviceTemplates []*models.DeviceTemplate
if err := r.db.Find(&deviceTemplates).Error; err != nil {
if err := r.db.WithContext(repoCtx).Find(&deviceTemplates).Error; err != nil {
return nil, fmt.Errorf("获取设备模板列表失败: %w", err)
}
return deviceTemplates, nil
}
// Update 更新数据库中的设备模板
func (r *gormDeviceTemplateRepository) Update(deviceTemplate *models.DeviceTemplate) error {
return r.db.Save(deviceTemplate).Error
func (r *gormDeviceTemplateRepository) Update(ctx context.Context, deviceTemplate *models.DeviceTemplate) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "Update")
return r.db.WithContext(repoCtx).Save(deviceTemplate).Error
}
// IsInUse 检查设备模板是否正在被设备使用
func (r *gormDeviceTemplateRepository) IsInUse(id uint) (bool, error) {
func (r *gormDeviceTemplateRepository) IsInUse(ctx context.Context, id uint) (bool, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "IsInUse")
var count int64
if err := r.db.Model(&models.Device{}).Where("device_template_id = ?", id).Count(&count).Error; err != nil {
if err := r.db.WithContext(repoCtx).Model(&models.Device{}).Where("device_template_id = ?", id).Count(&count).Error; err != nil {
return false, fmt.Errorf("检查设备模板使用情况失败: %w", err)
}
return count > 0, nil
}
// Delete 软删除数据库中的设备模板
func (r *gormDeviceTemplateRepository) Delete(id uint) error {
if err := r.db.Delete(&models.DeviceTemplate{}, id).Error; err != nil {
func (r *gormDeviceTemplateRepository) Delete(ctx context.Context, id uint) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "Delete")
if err := r.db.WithContext(repoCtx).Delete(&models.DeviceTemplate{}, id).Error; err != nil {
return fmt.Errorf("删除设备模板失败: %w", err)
}
return nil

View File

@@ -1,10 +1,13 @@
package repository
import (
"context"
"errors"
"time"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"gorm.io/gorm"
)
@@ -30,61 +33,63 @@ type TaskExecutionLogListOptions struct {
// ExecutionLogRepository 定义了与执行日志交互的接口。
type ExecutionLogRepository interface {
// --- Existing methods ---
UpdateTaskExecutionLogStatusByIDs(logIDs []uint, status models.ExecutionStatus) error
UpdateTaskExecutionLogStatus(logID uint, status models.ExecutionStatus) error
CreateTaskExecutionLog(log *models.TaskExecutionLog) error
CreatePlanExecutionLog(log *models.PlanExecutionLog) error
UpdatePlanExecutionLog(log *models.PlanExecutionLog) error
CreateTaskExecutionLogsInBatch(logs []*models.TaskExecutionLog) error
UpdateTaskExecutionLog(log *models.TaskExecutionLog) error
FindTaskExecutionLogByID(id uint) (*models.TaskExecutionLog, error)
UpdateTaskExecutionLogStatusByIDs(ctx context.Context, logIDs []uint, status models.ExecutionStatus) error
UpdateTaskExecutionLogStatus(ctx context.Context, logID uint, status models.ExecutionStatus) error
CreateTaskExecutionLog(ctx context.Context, log *models.TaskExecutionLog) error
CreatePlanExecutionLog(ctx context.Context, log *models.PlanExecutionLog) error
UpdatePlanExecutionLog(ctx context.Context, log *models.PlanExecutionLog) error
CreateTaskExecutionLogsInBatch(ctx context.Context, logs []*models.TaskExecutionLog) error
UpdateTaskExecutionLog(ctx context.Context, log *models.TaskExecutionLog) error
FindTaskExecutionLogByID(ctx context.Context, id uint) (*models.TaskExecutionLog, error)
// UpdatePlanExecutionLogStatus 更新计划执行日志的状态
UpdatePlanExecutionLogStatus(logID uint, status models.ExecutionStatus) error
UpdatePlanExecutionLogStatus(ctx context.Context, logID uint, status models.ExecutionStatus) error
// UpdatePlanExecutionLogsStatusByIDs 批量更新计划执行日志的状态
UpdatePlanExecutionLogsStatusByIDs(logIDs []uint, status models.ExecutionStatus) error
UpdatePlanExecutionLogsStatusByIDs(ctx context.Context, logIDs []uint, status models.ExecutionStatus) error
// FindIncompletePlanExecutionLogs 查找所有未完成的计划执行日志
FindIncompletePlanExecutionLogs() ([]models.PlanExecutionLog, error)
FindIncompletePlanExecutionLogs(ctx context.Context) ([]models.PlanExecutionLog, error)
// FindInProgressPlanExecutionLogByPlanID 根据 PlanID 查找正在进行的计划执行日志
FindInProgressPlanExecutionLogByPlanID(planID uint) (*models.PlanExecutionLog, error)
FindInProgressPlanExecutionLogByPlanID(ctx context.Context, planID uint) (*models.PlanExecutionLog, error)
// FindIncompleteTaskExecutionLogsByPlanLogID 根据计划日志ID查找所有未完成的任务日志
FindIncompleteTaskExecutionLogsByPlanLogID(planLogID uint) ([]models.TaskExecutionLog, error)
FindIncompleteTaskExecutionLogsByPlanLogID(ctx context.Context, planLogID uint) ([]models.TaskExecutionLog, error)
// FailAllIncompletePlanExecutionLogs 将所有状态为 ExecutionStatusStarted 和 ExecutionStatusWaiting 的计划状态都修改为 ExecutionStatusFailed
FailAllIncompletePlanExecutionLogs() error
FailAllIncompletePlanExecutionLogs(ctx context.Context) error
// CancelAllIncompleteTaskExecutionLogs 将所有状态为 ExecutionStatusStarted 和 ExecutionStatusWaiting 的任务状态修改为 ExecutionStatusCancelled
CancelAllIncompleteTaskExecutionLogs() error
CancelAllIncompleteTaskExecutionLogs(ctx context.Context) error
// FindPlanExecutionLogByID 根据ID查找计划执行日志
FindPlanExecutionLogByID(id uint) (*models.PlanExecutionLog, error)
FindPlanExecutionLogByID(ctx context.Context, id uint) (*models.PlanExecutionLog, error)
// CountIncompleteTasksByPlanLogID 计算一个计划执行中未完成的任务数量
CountIncompleteTasksByPlanLogID(planLogID uint) (int64, error)
CountIncompleteTasksByPlanLogID(ctx context.Context, planLogID uint) (int64, error)
// FailPlanExecution 将指定的计划执行标记为失败
FailPlanExecution(planLogID uint, errorMessage string) error
FailPlanExecution(ctx context.Context, planLogID uint, errorMessage string) error
// CancelIncompleteTasksByPlanLogID 取消一个计划执行中的所有未完成任务
CancelIncompleteTasksByPlanLogID(planLogID uint, reason string) error
CancelIncompleteTasksByPlanLogID(ctx context.Context, planLogID uint, reason string) error
// --- New methods ---
ListPlanExecutionLogs(opts PlanExecutionLogListOptions, page, pageSize int) ([]models.PlanExecutionLog, int64, error)
ListTaskExecutionLogs(opts TaskExecutionLogListOptions, page, pageSize int) ([]models.TaskExecutionLog, int64, error)
ListPlanExecutionLogs(ctx context.Context, opts PlanExecutionLogListOptions, page, pageSize int) ([]models.PlanExecutionLog, int64, error)
ListTaskExecutionLogs(ctx context.Context, opts TaskExecutionLogListOptions, page, pageSize int) ([]models.TaskExecutionLog, int64, error)
}
// gormExecutionLogRepository 是使用 GORM 的具体实现。
type gormExecutionLogRepository struct {
db *gorm.DB
ctx context.Context
db *gorm.DB
}
// NewGormExecutionLogRepository 创建一个新的执行日志仓库。
func NewGormExecutionLogRepository(db *gorm.DB) ExecutionLogRepository {
return &gormExecutionLogRepository{db: db}
func NewGormExecutionLogRepository(ctx context.Context, db *gorm.DB) ExecutionLogRepository {
return &gormExecutionLogRepository{ctx: ctx, db: db}
}
// ListPlanExecutionLogs 实现了分页和过滤查询计划执行日志的功能
func (r *gormExecutionLogRepository) ListPlanExecutionLogs(opts PlanExecutionLogListOptions, page, pageSize int) ([]models.PlanExecutionLog, int64, error) {
func (r *gormExecutionLogRepository) ListPlanExecutionLogs(ctx context.Context, opts PlanExecutionLogListOptions, page, pageSize int) ([]models.PlanExecutionLog, int64, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "ListPlanExecutionLogs")
if page <= 0 || pageSize <= 0 {
return nil, 0, ErrInvalidPagination
}
@@ -92,7 +97,7 @@ func (r *gormExecutionLogRepository) ListPlanExecutionLogs(opts PlanExecutionLog
var results []models.PlanExecutionLog
var total int64
query := r.db.Model(&models.PlanExecutionLog{})
query := r.db.WithContext(repoCtx).Model(&models.PlanExecutionLog{})
if opts.PlanID != nil {
query = query.Where("plan_id = ?", *opts.PlanID)
@@ -124,7 +129,8 @@ func (r *gormExecutionLogRepository) ListPlanExecutionLogs(opts PlanExecutionLog
}
// ListTaskExecutionLogs 实现了分页和过滤查询任务执行日志的功能
func (r *gormExecutionLogRepository) ListTaskExecutionLogs(opts TaskExecutionLogListOptions, page, pageSize int) ([]models.TaskExecutionLog, int64, error) {
func (r *gormExecutionLogRepository) ListTaskExecutionLogs(ctx context.Context, opts TaskExecutionLogListOptions, page, pageSize int) ([]models.TaskExecutionLog, int64, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "ListTaskExecutionLogs")
if page <= 0 || pageSize <= 0 {
return nil, 0, ErrInvalidPagination
}
@@ -132,7 +138,7 @@ func (r *gormExecutionLogRepository) ListTaskExecutionLogs(opts TaskExecutionLog
var results []models.TaskExecutionLog
var total int64
query := r.db.Model(&models.TaskExecutionLog{})
query := r.db.WithContext(repoCtx).Model(&models.TaskExecutionLog{})
if opts.PlanExecutionLogID != nil {
query = query.Where("plan_execution_log_id = ?", *opts.PlanExecutionLogID)
@@ -169,56 +175,64 @@ func (r *gormExecutionLogRepository) ListTaskExecutionLogs(opts TaskExecutionLog
// --- Existing method implementations ---
func (r *gormExecutionLogRepository) UpdateTaskExecutionLogStatusByIDs(logIDs []uint, status models.ExecutionStatus) error {
func (r *gormExecutionLogRepository) UpdateTaskExecutionLogStatusByIDs(ctx context.Context, logIDs []uint, status models.ExecutionStatus) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdateTaskExecutionLogStatusByIDs")
if len(logIDs) == 0 {
return nil
}
return r.db.Model(&models.TaskExecutionLog{}).Where("id IN ?", logIDs).Update("status", status).Error
return r.db.WithContext(repoCtx).Model(&models.TaskExecutionLog{}).Where("id IN ?", logIDs).Update("status", status).Error
}
func (r *gormExecutionLogRepository) UpdateTaskExecutionLogStatus(logID uint, status models.ExecutionStatus) error {
return r.db.Model(&models.TaskExecutionLog{}).Where("id = ?", logID).Update("status", status).Error
func (r *gormExecutionLogRepository) UpdateTaskExecutionLogStatus(ctx context.Context, logID uint, status models.ExecutionStatus) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdateTaskExecutionLogStatus")
return r.db.WithContext(repoCtx).Model(&models.TaskExecutionLog{}).Where("id = ?", logID).Update("status", status).Error
}
func (r *gormExecutionLogRepository) CreateTaskExecutionLog(log *models.TaskExecutionLog) error {
return r.db.Create(log).Error
func (r *gormExecutionLogRepository) CreateTaskExecutionLog(ctx context.Context, log *models.TaskExecutionLog) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "CreateTaskExecutionLog")
return r.db.WithContext(repoCtx).Create(log).Error
}
// CreatePlanExecutionLog 为一次计划执行创建一条新的日志条目。
func (r *gormExecutionLogRepository) CreatePlanExecutionLog(log *models.PlanExecutionLog) error {
return r.db.Create(log).Error
func (r *gormExecutionLogRepository) CreatePlanExecutionLog(ctx context.Context, log *models.PlanExecutionLog) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "CreatePlanExecutionLog")
return r.db.WithContext(repoCtx).Create(log).Error
}
// UpdatePlanExecutionLog 使用 Updates 方法更新一个计划执行日志。
// GORM 的 Updates 传入 struct 时,只会更新非零值字段。
// 在这里,我们期望传入的对象一定包含一个有效的 ID。
func (r *gormExecutionLogRepository) UpdatePlanExecutionLog(log *models.PlanExecutionLog) error {
return r.db.Updates(log).Error
func (r *gormExecutionLogRepository) UpdatePlanExecutionLog(ctx context.Context, log *models.PlanExecutionLog) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdatePlanExecutionLog")
return r.db.WithContext(repoCtx).Updates(log).Error
}
// CreateTaskExecutionLogsInBatch 在一次数据库调用中创建多个任务执行日志条目。
// 这是“预写日志”步骤的关键。
func (r *gormExecutionLogRepository) CreateTaskExecutionLogsInBatch(logs []*models.TaskExecutionLog) error {
if len(logs) == 0 {
func (r *gormExecutionLogRepository) CreateTaskExecutionLogsInBatch(ctx context.Context, executionLogs []*models.TaskExecutionLog) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "CreateTaskExecutionLogsInBatch")
if len(executionLogs) == 0 {
return nil
}
// GORM 的 CreateTx 传入一个切片指针会执行批量插入。
return r.db.Create(&logs).Error
return r.db.WithContext(repoCtx).Create(&executionLogs).Error
}
// UpdateTaskExecutionLog 使用 Updates 方法更新一个任务执行日志。
// GORM 的 Updates 传入 struct 时,只会更新非零值字段。
// 这种方式代码更直观,上层服务可以直接修改模型对象后进行保存。
func (r *gormExecutionLogRepository) UpdateTaskExecutionLog(log *models.TaskExecutionLog) error {
return r.db.Updates(log).Error
func (r *gormExecutionLogRepository) UpdateTaskExecutionLog(ctx context.Context, log *models.TaskExecutionLog) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdateTaskExecutionLog")
return r.db.WithContext(repoCtx).Updates(log).Error
}
// FindTaskExecutionLogByID 根据 ID 查找单个任务执行日志。
// 它会预加载关联的 Task 信息。
func (r *gormExecutionLogRepository) FindTaskExecutionLogByID(id uint) (*models.TaskExecutionLog, error) {
func (r *gormExecutionLogRepository) FindTaskExecutionLogByID(ctx context.Context, id uint) (*models.TaskExecutionLog, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindTaskExecutionLogByID")
var log models.TaskExecutionLog
// 使用 Preload("Task") 来确保关联的任务信息被一并加载
err := r.db.Preload("Task").First(&log, id).Error
err := r.db.WithContext(repoCtx).Preload("Task").First(&log, id).Error
if err != nil {
return nil, err
}
@@ -226,29 +240,33 @@ func (r *gormExecutionLogRepository) FindTaskExecutionLogByID(id uint) (*models.
}
// UpdatePlanExecutionLogStatus 更新计划执行日志的状态
func (r *gormExecutionLogRepository) UpdatePlanExecutionLogStatus(logID uint, status models.ExecutionStatus) error {
return r.db.Model(&models.PlanExecutionLog{}).Where("id = ?", logID).Update("status", status).Error
func (r *gormExecutionLogRepository) UpdatePlanExecutionLogStatus(ctx context.Context, logID uint, status models.ExecutionStatus) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdatePlanExecutionLogStatus")
return r.db.WithContext(repoCtx).Model(&models.PlanExecutionLog{}).Where("id = ?", logID).Update("status", status).Error
}
// UpdatePlanExecutionLogsStatusByIDs 批量更新计划执行日志的状态
func (r *gormExecutionLogRepository) UpdatePlanExecutionLogsStatusByIDs(logIDs []uint, status models.ExecutionStatus) error {
func (r *gormExecutionLogRepository) UpdatePlanExecutionLogsStatusByIDs(ctx context.Context, logIDs []uint, status models.ExecutionStatus) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdatePlanExecutionLogsStatusByIDs")
if len(logIDs) == 0 {
return nil
}
return r.db.Model(&models.PlanExecutionLog{}).Where("id IN ?", logIDs).Update("status", status).Error
return r.db.WithContext(repoCtx).Model(&models.PlanExecutionLog{}).Where("id IN ?", logIDs).Update("status", status).Error
}
// FindIncompletePlanExecutionLogs 查找所有未完成的计划执行日志
func (r *gormExecutionLogRepository) FindIncompletePlanExecutionLogs() ([]models.PlanExecutionLog, error) {
func (r *gormExecutionLogRepository) FindIncompletePlanExecutionLogs(ctx context.Context) ([]models.PlanExecutionLog, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindIncompletePlanExecutionLogs")
var logs []models.PlanExecutionLog
err := r.db.Where("status = ? OR status = ?", models.ExecutionStatusStarted, models.ExecutionStatusWaiting).Find(&logs).Error
err := r.db.WithContext(repoCtx).Where("status = ? OR status = ?", models.ExecutionStatusStarted, models.ExecutionStatusWaiting).Find(&logs).Error
return logs, err
}
// FindInProgressPlanExecutionLogByPlanID 根据 PlanID 查找正在进行的计划执行日志
func (r *gormExecutionLogRepository) FindInProgressPlanExecutionLogByPlanID(planID uint) (*models.PlanExecutionLog, error) {
func (r *gormExecutionLogRepository) FindInProgressPlanExecutionLogByPlanID(ctx context.Context, planID uint) (*models.PlanExecutionLog, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindInProgressPlanExecutionLogByPlanID")
var log models.PlanExecutionLog
err := r.db.Where("plan_id = ? AND status = ?", planID, models.ExecutionStatusStarted).First(&log).Error
err := r.db.WithContext(repoCtx).Where("plan_id = ? AND status = ?", planID, models.ExecutionStatusStarted).First(&log).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
// 未找到不是一个需要上报的错误,代表计划当前没有在运行
@@ -261,31 +279,35 @@ func (r *gormExecutionLogRepository) FindInProgressPlanExecutionLogByPlanID(plan
}
// FindIncompleteTaskExecutionLogsByPlanLogID 根据计划日志ID查找所有未完成的任务日志
func (r *gormExecutionLogRepository) FindIncompleteTaskExecutionLogsByPlanLogID(planLogID uint) ([]models.TaskExecutionLog, error) {
func (r *gormExecutionLogRepository) FindIncompleteTaskExecutionLogsByPlanLogID(ctx context.Context, planLogID uint) ([]models.TaskExecutionLog, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindIncompleteTaskExecutionLogsByPlanLogID")
var logs []models.TaskExecutionLog
err := r.db.Where("plan_execution_log_id = ? AND (status = ? OR status = ?)",
err := r.db.WithContext(repoCtx).Where("plan_execution_log_id = ? AND (status = ? OR status = ?)",
planLogID, models.ExecutionStatusWaiting, models.ExecutionStatusStarted).Find(&logs).Error
return logs, err
}
// FailAllIncompletePlanExecutionLogs 将所有状态为 ExecutionStatusStarted 和 ExecutionStatusWaiting 的计划状态都修改为 ExecutionStatusFailed
func (r *gormExecutionLogRepository) FailAllIncompletePlanExecutionLogs() error {
return r.db.Model(&models.PlanExecutionLog{}).
func (r *gormExecutionLogRepository) FailAllIncompletePlanExecutionLogs(ctx context.Context) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "FailAllIncompletePlanExecutionLogs")
return r.db.WithContext(repoCtx).Model(&models.PlanExecutionLog{}).
Where("status IN (?, ?)", models.ExecutionStatusStarted, models.ExecutionStatusWaiting).
Updates(map[string]interface{}{"status": models.ExecutionStatusFailed, "ended_at": time.Now(), "error": "系统中断"}).Error
}
// CancelAllIncompleteTaskExecutionLogs 将所有状态为 ExecutionStatusStarted 和 ExecutionStatusWaiting 的任务状态修改为 ExecutionStatusCancelled
func (r *gormExecutionLogRepository) CancelAllIncompleteTaskExecutionLogs() error {
return r.db.Model(&models.TaskExecutionLog{}).
func (r *gormExecutionLogRepository) CancelAllIncompleteTaskExecutionLogs(ctx context.Context) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "CancelAllIncompleteTaskExecutionLogs")
return r.db.WithContext(repoCtx).Model(&models.TaskExecutionLog{}).
Where("status IN (?, ?)", models.ExecutionStatusStarted, models.ExecutionStatusWaiting).
Updates(map[string]interface{}{"status": models.ExecutionStatusCancelled, "ended_at": time.Now(), "output": "系统中断"}).Error
}
// FindPlanExecutionLogByID 根据ID查找计划执行日志
func (r *gormExecutionLogRepository) FindPlanExecutionLogByID(id uint) (*models.PlanExecutionLog, error) {
func (r *gormExecutionLogRepository) FindPlanExecutionLogByID(ctx context.Context, id uint) (*models.PlanExecutionLog, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindPlanExecutionLogByID")
var log models.PlanExecutionLog
err := r.db.First(&log, id).Error
err := r.db.WithContext(repoCtx).First(&log, id).Error
if err != nil {
return nil, err
}
@@ -293,9 +315,10 @@ func (r *gormExecutionLogRepository) FindPlanExecutionLogByID(id uint) (*models.
}
// CountIncompleteTasksByPlanLogID 计算一个计划执行中未完成的任务数量
func (r *gormExecutionLogRepository) CountIncompleteTasksByPlanLogID(planLogID uint) (int64, error) {
func (r *gormExecutionLogRepository) CountIncompleteTasksByPlanLogID(ctx context.Context, planLogID uint) (int64, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "CountIncompleteTasksByPlanLogID")
var count int64
err := r.db.Model(&models.TaskExecutionLog{}).
err := r.db.WithContext(repoCtx).Model(&models.TaskExecutionLog{}).
Where("plan_execution_log_id = ? AND status IN (?, ?)",
planLogID, models.ExecutionStatusWaiting, models.ExecutionStatusStarted).
Count(&count).Error
@@ -303,8 +326,9 @@ func (r *gormExecutionLogRepository) CountIncompleteTasksByPlanLogID(planLogID u
}
// FailPlanExecution 将指定的计划执行标记为失败
func (r *gormExecutionLogRepository) FailPlanExecution(planLogID uint, errorMessage string) error {
return r.db.Model(&models.PlanExecutionLog{}).
func (r *gormExecutionLogRepository) FailPlanExecution(ctx context.Context, planLogID uint, errorMessage string) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "FailPlanExecution")
return r.db.WithContext(repoCtx).Model(&models.PlanExecutionLog{}).
Where("id = ?", planLogID).
Updates(map[string]interface{}{
"status": models.ExecutionStatusFailed,
@@ -314,8 +338,9 @@ func (r *gormExecutionLogRepository) FailPlanExecution(planLogID uint, errorMess
}
// CancelIncompleteTasksByPlanLogID 取消一个计划执行中的所有未完成任务
func (r *gormExecutionLogRepository) CancelIncompleteTasksByPlanLogID(planLogID uint, reason string) error {
return r.db.Model(&models.TaskExecutionLog{}).
func (r *gormExecutionLogRepository) CancelIncompleteTasksByPlanLogID(ctx context.Context, planLogID uint, reason string) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "CancelIncompleteTasksByPlanLogID")
return r.db.WithContext(repoCtx).Model(&models.TaskExecutionLog{}).
Where("plan_execution_log_id = ? AND status IN (?, ?)",
planLogID, models.ExecutionStatusWaiting, models.ExecutionStatusStarted).
Updates(map[string]interface{}{

View File

@@ -1,9 +1,12 @@
package repository
import (
"context"
"time"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"gorm.io/gorm"
)
@@ -20,27 +23,30 @@ type MedicationLogListOptions struct {
// MedicationLogRepository 定义了与群体用药日志模型相关的数据库操作接口。
type MedicationLogRepository interface {
CreateMedicationLog(log *models.MedicationLog) error
ListMedicationLogs(opts MedicationLogListOptions, page, pageSize int) ([]models.MedicationLog, int64, error)
CreateMedicationLog(ctx context.Context, log *models.MedicationLog) error
ListMedicationLogs(ctx context.Context, opts MedicationLogListOptions, page, pageSize int) ([]models.MedicationLog, int64, error)
}
// gormMedicationLogRepository 是 MedicationLogRepository 接口的 GORM 实现。
type gormMedicationLogRepository struct {
db *gorm.DB
ctx context.Context
db *gorm.DB
}
// NewGormMedicationLogRepository 创建一个新的 MedicationLogRepository GORM 实现实例。
func NewGormMedicationLogRepository(db *gorm.DB) MedicationLogRepository {
return &gormMedicationLogRepository{db: db}
func NewGormMedicationLogRepository(ctx context.Context, db *gorm.DB) MedicationLogRepository {
return &gormMedicationLogRepository{ctx: ctx, db: db}
}
// CreateMedicationLog 创建一条新的群体用药日志记录
func (r *gormMedicationLogRepository) CreateMedicationLog(log *models.MedicationLog) error {
return r.db.Create(log).Error
func (r *gormMedicationLogRepository) CreateMedicationLog(ctx context.Context, log *models.MedicationLog) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "CreateMedicationLog")
return r.db.WithContext(repoCtx).Create(log).Error
}
// ListMedicationLogs 实现了分页和过滤查询用药记录的功能
func (r *gormMedicationLogRepository) ListMedicationLogs(opts MedicationLogListOptions, page, pageSize int) ([]models.MedicationLog, int64, error) {
func (r *gormMedicationLogRepository) ListMedicationLogs(ctx context.Context, opts MedicationLogListOptions, page, pageSize int) ([]models.MedicationLog, int64, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "ListMedicationLogs")
if page <= 0 || pageSize <= 0 {
return nil, 0, ErrInvalidPagination
}
@@ -48,7 +54,7 @@ func (r *gormMedicationLogRepository) ListMedicationLogs(opts MedicationLogListO
var results []models.MedicationLog
var total int64
query := r.db.Model(&models.MedicationLog{})
query := r.db.WithContext(repoCtx).Model(&models.MedicationLog{})
if opts.PigBatchID != nil {
query = query.Where("pig_batch_id = ?", *opts.PigBatchID)

View File

@@ -1,10 +1,13 @@
package repository
import (
"context"
"time"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/notify"
"go.uber.org/zap/zapcore"
"gorm.io/gorm"
)
@@ -23,44 +26,49 @@ type NotificationListOptions struct {
// NotificationRepository 定义了与通知记录相关的数据库操作接口。
type NotificationRepository interface {
// Create 将一条新的通知记录插入数据库。
Create(notification *models.Notification) error
Create(ctx context.Context, notification *models.Notification) error
// CreateInTx 在给定的事务中插入一条新的通知记录。
CreateInTx(tx *gorm.DB, notification *models.Notification) error
CreateInTx(ctx context.Context, tx *gorm.DB, notification *models.Notification) error
// BatchCreate 批量插入多条通知记录。
BatchCreate(notifications []*models.Notification) error
BatchCreate(ctx context.Context, notifications []*models.Notification) error
// List 支持分页和过滤的通知列表查询。
// 返回通知列表、总记录数和错误。
List(opts NotificationListOptions, page, pageSize int) ([]models.Notification, int64, error)
List(ctx context.Context, opts NotificationListOptions, page, pageSize int) ([]models.Notification, int64, error)
}
// gormNotificationRepository 是 NotificationRepository 的 GORM 实现。
type gormNotificationRepository struct {
db *gorm.DB
ctx context.Context
db *gorm.DB
}
// NewGormNotificationRepository 创建一个新的 NotificationRepository GORM 实现实例。
func NewGormNotificationRepository(db *gorm.DB) NotificationRepository {
return &gormNotificationRepository{db: db}
func NewGormNotificationRepository(ctx context.Context, db *gorm.DB) NotificationRepository {
return &gormNotificationRepository{ctx: ctx, db: db}
}
// Create 将一条新的通知记录插入数据库。
func (r *gormNotificationRepository) Create(notification *models.Notification) error {
return r.db.Create(notification).Error
func (r *gormNotificationRepository) Create(ctx context.Context, notification *models.Notification) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "Create")
return r.db.WithContext(repoCtx).Create(notification).Error
}
// CreateInTx 在给定的事务中插入一条新的通知记录。
func (r *gormNotificationRepository) CreateInTx(tx *gorm.DB, notification *models.Notification) error {
return tx.Create(notification).Error
func (r *gormNotificationRepository) CreateInTx(ctx context.Context, tx *gorm.DB, notification *models.Notification) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "CreateInTx")
return tx.WithContext(repoCtx).Create(notification).Error
}
// BatchCreate 批量插入多条通知记录。
func (r *gormNotificationRepository) BatchCreate(notifications []*models.Notification) error {
func (r *gormNotificationRepository) BatchCreate(ctx context.Context, notifications []*models.Notification) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "BatchCreate")
// GORM 的 Create 方法在传入切片时会自动进行批量插入
return r.db.Create(&notifications).Error
return r.db.WithContext(repoCtx).Create(&notifications).Error
}
// List 实现了分页和过滤查询通知记录的功能
func (r *gormNotificationRepository) List(opts NotificationListOptions, page, pageSize int) ([]models.Notification, int64, error) {
func (r *gormNotificationRepository) List(ctx context.Context, opts NotificationListOptions, page, pageSize int) ([]models.Notification, int64, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "List")
// --- 校验分页参数 ---
if page <= 0 || pageSize <= 0 {
return nil, 0, ErrInvalidPagination // 复用已定义的错误
@@ -69,7 +77,7 @@ func (r *gormNotificationRepository) List(opts NotificationListOptions, page, pa
var results []models.Notification
var total int64
query := r.db.Model(&models.Notification{})
query := r.db.WithContext(repoCtx).Model(&models.Notification{})
// --- 应用过滤条件 ---
if opts.UserID != nil {

View File

@@ -1,9 +1,12 @@
package repository
import (
"context"
"time"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"gorm.io/gorm"
)
@@ -19,48 +22,52 @@ type PendingCollectionListOptions struct {
// PendingCollectionRepository 定义了与待采集请求相关的数据库操作接口。
type PendingCollectionRepository interface {
// Create 创建一个新的待采集请求。
Create(req *models.PendingCollection) error
Create(ctx context.Context, req *models.PendingCollection) error
// FindByCorrelationID 根据关联ID查找一个待采集请求。
FindByCorrelationID(correlationID string) (*models.PendingCollection, error)
FindByCorrelationID(ctx context.Context, correlationID string) (*models.PendingCollection, error)
// UpdateStatusToFulfilled 将指定关联ID的请求状态更新为“已完成”。
UpdateStatusToFulfilled(correlationID string, fulfilledAt time.Time) error
UpdateStatusToFulfilled(ctx context.Context, correlationID string, fulfilledAt time.Time) error
// MarkAllPendingAsTimedOut 将所有“待处理”请求更新为“已超时”。
MarkAllPendingAsTimedOut() (int64, error)
MarkAllPendingAsTimedOut(ctx context.Context) (int64, error)
// List 支持分页和过滤的列表查询
List(opts PendingCollectionListOptions, page, pageSize int) ([]models.PendingCollection, int64, error)
List(ctx context.Context, opts PendingCollectionListOptions, page, pageSize int) ([]models.PendingCollection, int64, error)
}
// gormPendingCollectionRepository 是 PendingCollectionRepository 的 GORM 实现。
type gormPendingCollectionRepository struct {
db *gorm.DB
ctx context.Context
db *gorm.DB
}
// NewGormPendingCollectionRepository 创建一个新的 PendingCollectionRepository GORM 实现实例。
func NewGormPendingCollectionRepository(db *gorm.DB) PendingCollectionRepository {
return &gormPendingCollectionRepository{db: db}
func NewGormPendingCollectionRepository(ctx context.Context, db *gorm.DB) PendingCollectionRepository {
return &gormPendingCollectionRepository{ctx: ctx, db: db}
}
// Create 创建一个新的待采集请求。
func (r *gormPendingCollectionRepository) Create(req *models.PendingCollection) error {
return r.db.Create(req).Error
func (r *gormPendingCollectionRepository) Create(ctx context.Context, req *models.PendingCollection) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "Create")
return r.db.WithContext(repoCtx).Create(req).Error
}
// FindByCorrelationID 根据关联ID查找一个待采集请求。
func (r *gormPendingCollectionRepository) FindByCorrelationID(correlationID string) (*models.PendingCollection, error) {
func (r *gormPendingCollectionRepository) FindByCorrelationID(ctx context.Context, correlationID string) (*models.PendingCollection, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByCorrelationID")
var req models.PendingCollection
if err := r.db.First(&req, "correlation_id = ?", correlationID).Error; err != nil {
if err := r.db.WithContext(repoCtx).First(&req, "correlation_id = ?", correlationID).Error; err != nil {
return nil, err
}
return &req, nil
}
// UpdateStatusToFulfilled 将指定关联ID的请求状态更新为“已完成”。
func (r *gormPendingCollectionRepository) UpdateStatusToFulfilled(correlationID string, fulfilledAt time.Time) error {
return r.db.Model(&models.PendingCollection{}).
func (r *gormPendingCollectionRepository) UpdateStatusToFulfilled(ctx context.Context, correlationID string, fulfilledAt time.Time) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdateStatusToFulfilled")
return r.db.WithContext(repoCtx).Model(&models.PendingCollection{}).
Where("correlation_id = ?", correlationID).
Updates(map[string]interface{}{
"status": models.PendingStatusFulfilled,
@@ -70,8 +77,9 @@ func (r *gormPendingCollectionRepository) UpdateStatusToFulfilled(correlationID
// MarkAllPendingAsTimedOut 将所有状态为 'pending' 的记录更新为 'timed_out'。
// 返回被更新的记录数量和错误。
func (r *gormPendingCollectionRepository) MarkAllPendingAsTimedOut() (int64, error) {
result := r.db.Model(&models.PendingCollection{}).
func (r *gormPendingCollectionRepository) MarkAllPendingAsTimedOut(ctx context.Context) (int64, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "MarkAllPendingAsTimedOut")
result := r.db.WithContext(repoCtx).Model(&models.PendingCollection{}).
Where("status = ?", models.PendingStatusPending).
Update("status", models.PendingStatusTimedOut)
@@ -79,7 +87,8 @@ func (r *gormPendingCollectionRepository) MarkAllPendingAsTimedOut() (int64, err
}
// List 实现了分页和过滤查询待采集请求的功能
func (r *gormPendingCollectionRepository) List(opts PendingCollectionListOptions, page, pageSize int) ([]models.PendingCollection, int64, error) {
func (r *gormPendingCollectionRepository) List(ctx context.Context, opts PendingCollectionListOptions, page, pageSize int) ([]models.PendingCollection, int64, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "List")
if page <= 0 || pageSize <= 0 {
return nil, 0, ErrInvalidPagination
}
@@ -87,7 +96,7 @@ func (r *gormPendingCollectionRepository) List(opts PendingCollectionListOptions
var results []models.PendingCollection
var total int64
query := r.db.Model(&models.PendingCollection{})
query := r.db.WithContext(repoCtx).Model(&models.PendingCollection{})
if opts.DeviceID != nil {
query = query.Where("device_id = ?", *opts.DeviceID)

View File

@@ -1,63 +1,69 @@
package repository
import (
"context"
"errors"
"fmt"
"time"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
// PendingTaskRepository 定义了与待执行任务队列交互的接口。
type PendingTaskRepository interface {
FindAllPendingTasks() ([]models.PendingTask, error)
FindPendingTriggerByPlanID(planID uint) (*models.PendingTask, error)
DeletePendingTasksByIDs(ids []uint) error
CreatePendingTask(task *models.PendingTask) error
CreatePendingTasksInBatch(tasks []*models.PendingTask) error
FindAllPendingTasks(ctx context.Context) ([]models.PendingTask, error)
FindPendingTriggerByPlanID(ctx context.Context, planID uint) (*models.PendingTask, error)
DeletePendingTasksByIDs(ctx context.Context, ids []uint) error
CreatePendingTask(ctx context.Context, task *models.PendingTask) error
CreatePendingTasksInBatch(ctx context.Context, tasks []*models.PendingTask) error
// UpdatePendingTaskExecuteAt 更新指定待执行任务的执行时间
UpdatePendingTaskExecuteAt(id uint, executeAt time.Time) error
UpdatePendingTaskExecuteAt(ctx context.Context, id uint, executeAt time.Time) error
// ClearAllPendingTasks 清空所有待执行任务
ClearAllPendingTasks() error
ClearAllPendingTasks(ctx context.Context) error
// ClaimNextAvailableTask 原子地认领下一个可用的任务。
// 它会同时返回被认领任务对应的日志对象,以及被删除的待办任务对象的内存副本。
ClaimNextAvailableTask(excludePlanIDs []uint) (*models.TaskExecutionLog, *models.PendingTask, error)
ClaimNextAvailableTask(ctx context.Context, excludePlanIDs []uint) (*models.TaskExecutionLog, *models.PendingTask, error)
// RequeueTask 安全地将一个任务重新放回队列。
RequeueTask(originalPendingTask *models.PendingTask) error
RequeueTask(ctx context.Context, originalPendingTask *models.PendingTask) error
// FindPendingTasksByTaskLogIDs 根据 TaskExecutionLogID 列表查找对应的待执行任务
FindPendingTasksByTaskLogIDs(taskLogIDs []uint) ([]models.PendingTask, error)
FindPendingTasksByTaskLogIDs(ctx context.Context, taskLogIDs []uint) ([]models.PendingTask, error)
// DeletePendingTasksByPlanLogID 删除与指定计划执行日志ID相关的所有待执行任务
DeletePendingTasksByPlanLogID(planLogID uint) error
DeletePendingTasksByPlanLogID(ctx context.Context, planLogID uint) error
}
// gormPendingTaskRepository 是使用 GORM 的具体实现。
type gormPendingTaskRepository struct {
db *gorm.DB
ctx context.Context
db *gorm.DB
}
// NewGormPendingTaskRepository 创建一个新的待执行任务队列仓库。
func NewGormPendingTaskRepository(db *gorm.DB) PendingTaskRepository {
return &gormPendingTaskRepository{db: db}
func NewGormPendingTaskRepository(ctx context.Context, db *gorm.DB) PendingTaskRepository {
return &gormPendingTaskRepository{ctx: ctx, db: db}
}
func (r *gormPendingTaskRepository) FindAllPendingTasks() ([]models.PendingTask, error) {
func (r *gormPendingTaskRepository) FindAllPendingTasks(ctx context.Context) ([]models.PendingTask, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindAllPendingTasks")
var tasks []models.PendingTask
// 预加载 Task 以便后续访问 Task.PlanID
// 预加载 TaskExecutionLog 以便后续访问 PlanExecutionLogID
err := r.db.Preload("Task").Preload("TaskExecutionLog").Find(&tasks).Error
err := r.db.WithContext(repoCtx).Preload("Task").Preload("TaskExecutionLog").Find(&tasks).Error
return tasks, err
}
func (r *gormPendingTaskRepository) FindPendingTriggerByPlanID(planID uint) (*models.PendingTask, error) {
func (r *gormPendingTaskRepository) FindPendingTriggerByPlanID(ctx context.Context, planID uint) (*models.PendingTask, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindPendingTriggerByPlanID")
var pendingTask models.PendingTask
// 关键修改:通过 JOIN tasks 表并查询 parameters JSON 字段来查找触发器,而不是依赖 task.plan_id
err := r.db.
err := r.db.WithContext(repoCtx).
Joins("JOIN tasks ON tasks.id = pending_tasks.task_id").
Where("tasks.type = ? AND tasks.parameters->>'plan_id' = ?", models.TaskPlanAnalysis, fmt.Sprintf("%d", planID)).
First(&pendingTask).Error
@@ -67,42 +73,48 @@ func (r *gormPendingTaskRepository) FindPendingTriggerByPlanID(planID uint) (*mo
return &pendingTask, err
}
func (r *gormPendingTaskRepository) DeletePendingTasksByIDs(ids []uint) error {
func (r *gormPendingTaskRepository) DeletePendingTasksByIDs(ctx context.Context, ids []uint) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "DeletePendingTasksByIDs")
if len(ids) == 0 {
return nil
}
return r.db.Where("id IN ?", ids).Delete(&models.PendingTask{}).Error
return r.db.WithContext(repoCtx).Where("id IN ?", ids).Delete(&models.PendingTask{}).Error
}
func (r *gormPendingTaskRepository) CreatePendingTask(task *models.PendingTask) error {
return r.db.Create(task).Error
func (r *gormPendingTaskRepository) CreatePendingTask(ctx context.Context, task *models.PendingTask) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "CreatePendingTask")
return r.db.WithContext(repoCtx).Create(task).Error
}
// CreatePendingTasksInBatch 在一次数据库调用中创建多个待执行任务条目。
func (r *gormPendingTaskRepository) CreatePendingTasksInBatch(tasks []*models.PendingTask) error {
func (r *gormPendingTaskRepository) CreatePendingTasksInBatch(ctx context.Context, tasks []*models.PendingTask) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "CreatePendingTasksInBatch")
if len(tasks) == 0 {
return nil
}
return r.db.Create(&tasks).Error
return r.db.WithContext(repoCtx).Create(&tasks).Error
}
// UpdatePendingTaskExecuteAt 更新指定待执行任务的执行时间
func (r *gormPendingTaskRepository) UpdatePendingTaskExecuteAt(id uint, executeAt time.Time) error {
return r.db.Model(&models.PendingTask{}).Where("id = ?", id).Update("execute_at", executeAt).Error
func (r *gormPendingTaskRepository) UpdatePendingTaskExecuteAt(ctx context.Context, id uint, executeAt time.Time) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdatePendingTaskExecuteAt")
return r.db.WithContext(repoCtx).Model(&models.PendingTask{}).Where("id = ?", id).Update("execute_at", executeAt).Error
}
// ClearAllPendingTasks 清空所有待执行任务
func (r *gormPendingTaskRepository) ClearAllPendingTasks() error {
return r.db.Where("1 = 1").Delete(&models.PendingTask{}).Error
func (r *gormPendingTaskRepository) ClearAllPendingTasks(ctx context.Context) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "ClearAllPendingTasks")
return r.db.WithContext(repoCtx).Where("1 = 1").Delete(&models.PendingTask{}).Error
}
// ClaimNextAvailableTask 以原子方式认领下一个可用的任务。
func (r *gormPendingTaskRepository) ClaimNextAvailableTask(excludePlanIDs []uint) (*models.TaskExecutionLog, *models.PendingTask, error) {
func (r *gormPendingTaskRepository) ClaimNextAvailableTask(ctx context.Context, excludePlanIDs []uint) (*models.TaskExecutionLog, *models.PendingTask, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "ClaimNextAvailableTask")
var log models.TaskExecutionLog
var pendingTask models.PendingTask
err := r.db.Transaction(func(tx *gorm.DB) error {
query := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
err := r.db.WithContext(repoCtx).Transaction(func(tx *gorm.DB) error {
query := tx.WithContext(repoCtx).Clauses(clause.Locking{Strength: "UPDATE"}).
Where("execute_at <= ?", time.Now()).
Order("execute_at ASC")
@@ -114,7 +126,7 @@ func (r *gormPendingTaskRepository) ClaimNextAvailableTask(excludePlanIDs []uint
return err
}
if err := tx.Unscoped().Delete(&pendingTask).Error; err != nil {
if err := tx.WithContext(repoCtx).Unscoped().Delete(&pendingTask).Error; err != nil {
return err
}
@@ -122,12 +134,12 @@ func (r *gormPendingTaskRepository) ClaimNextAvailableTask(excludePlanIDs []uint
"status": models.ExecutionStatusStarted,
"started_at": time.Now(),
}
if err := tx.Model(&models.TaskExecutionLog{}).Where("id = ?", pendingTask.TaskExecutionLogID).Updates(updates).Error; err != nil {
if err := tx.WithContext(repoCtx).Model(&models.TaskExecutionLog{}).Where("id = ?", pendingTask.TaskExecutionLogID).Updates(updates).Error; err != nil {
return err
}
// 关键修改:在 Preload("Task") 时,使用 Unscoped() 来忽略 Task 的软删除状态
if err := tx.Preload("Task", func(db *gorm.DB) *gorm.DB {
if err := tx.WithContext(repoCtx).Preload("Task", func(db *gorm.DB) *gorm.DB {
return db.Unscoped()
}).First(&log, pendingTask.TaskExecutionLogID).Error; err != nil {
return err
@@ -145,10 +157,11 @@ func (r *gormPendingTaskRepository) ClaimNextAvailableTask(excludePlanIDs []uint
// RequeueTask 安全地将一个任务重新放回队列。
// 它通过将原始 PendingTask 的 ID 重置为 0并重新创建它来实现。
func (r *gormPendingTaskRepository) RequeueTask(originalPendingTask *models.PendingTask) error {
return r.db.Transaction(func(tx *gorm.DB) error {
func (r *gormPendingTaskRepository) RequeueTask(ctx context.Context, originalPendingTask *models.PendingTask) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "RequeueTask")
return r.db.WithContext(repoCtx).Transaction(func(tx *gorm.DB) error {
// 1. 将日志状态恢复为 waiting
if err := tx.Model(&models.TaskExecutionLog{}).Where("id = ?", originalPendingTask.TaskExecutionLogID).Update("status", models.ExecutionStatusWaiting).Error; err != nil {
if err := tx.WithContext(repoCtx).Model(&models.TaskExecutionLog{}).Where("id = ?", originalPendingTask.TaskExecutionLogID).Update("status", models.ExecutionStatusWaiting).Error; err != nil {
return err
}
@@ -157,25 +170,27 @@ func (r *gormPendingTaskRepository) RequeueTask(originalPendingTask *models.Pend
originalPendingTask.ID = 0
// 3. 重新创建待办任务。GORM 会忽略掉已被重置的 ID并让数据库生成一个新的主键。
return tx.Create(originalPendingTask).Error
return tx.WithContext(repoCtx).Create(originalPendingTask).Error
})
}
// FindPendingTasksByTaskLogIDs 根据 TaskExecutionLogID 列表查找对应的待执行任务
func (r *gormPendingTaskRepository) FindPendingTasksByTaskLogIDs(taskLogIDs []uint) ([]models.PendingTask, error) {
func (r *gormPendingTaskRepository) FindPendingTasksByTaskLogIDs(ctx context.Context, taskLogIDs []uint) ([]models.PendingTask, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindPendingTasksByTaskLogIDs")
if len(taskLogIDs) == 0 {
return []models.PendingTask{}, nil
}
var pendingTasks []models.PendingTask
err := r.db.Where("task_execution_log_id IN ?", taskLogIDs).Find(&pendingTasks).Error
err := r.db.WithContext(repoCtx).Where("task_execution_log_id IN ?", taskLogIDs).Find(&pendingTasks).Error
return pendingTasks, err
}
// DeletePendingTasksByPlanLogID 删除与指定计划执行日志ID相关的所有待执行任务
func (r *gormPendingTaskRepository) DeletePendingTasksByPlanLogID(planLogID uint) error {
func (r *gormPendingTaskRepository) DeletePendingTasksByPlanLogID(ctx context.Context, planLogID uint) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "DeletePendingTasksByPlanLogID")
// 使用子查询找到所有与 planLogID 相关的 task_execution_log_id
subQuery := r.db.Model(&models.TaskExecutionLog{}).Select("id").Where("plan_execution_log_id = ?", planLogID)
subQuery := r.db.WithContext(repoCtx).Model(&models.TaskExecutionLog{}).Select("id").Where("plan_execution_log_id = ?", planLogID)
// 使用子查询的结果来删除待执行任务
return r.db.Where("task_execution_log_id IN (?)", subQuery).Delete(&models.PendingTask{}).Error
return r.db.WithContext(repoCtx).Where("task_execution_log_id IN (?)", subQuery).Delete(&models.PendingTask{}).Error
}

View File

@@ -1,9 +1,12 @@
package repository
import (
"context"
"time"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"gorm.io/gorm"
)
@@ -20,37 +23,40 @@ type PigBatchLogListOptions struct {
// PigBatchLogRepository 定义了与猪批次日志相关的数据库操作接口。
type PigBatchLogRepository interface {
// CreateTx 在指定的事务中创建一条新的猪批次日志。
CreateTx(tx *gorm.DB, log *models.PigBatchLog) error
CreateTx(ctx context.Context, tx *gorm.DB, log *models.PigBatchLog) error
// GetLogsByBatchIDAndDateRangeTx 在指定的事务中,获取指定批次在特定时间范围内的所有日志记录。
GetLogsByBatchIDAndDateRangeTx(tx *gorm.DB, batchID uint, startDate, endDate time.Time) ([]*models.PigBatchLog, error)
GetLogsByBatchIDAndDateRangeTx(ctx context.Context, tx *gorm.DB, batchID uint, startDate, endDate time.Time) ([]*models.PigBatchLog, error)
// GetLastLogByBatchIDTx 在指定的事务中,获取某批次的最后一条日志记录。
GetLastLogByBatchIDTx(tx *gorm.DB, batchID uint) (*models.PigBatchLog, error)
GetLastLogByBatchIDTx(ctx context.Context, tx *gorm.DB, batchID uint) (*models.PigBatchLog, error)
// List 支持分页和过滤的列表查询
List(opts PigBatchLogListOptions, page, pageSize int) ([]models.PigBatchLog, int64, error)
List(ctx context.Context, opts PigBatchLogListOptions, page, pageSize int) ([]models.PigBatchLog, int64, error)
}
// gormPigBatchLogRepository 是 PigBatchLogRepository 的 GORM 实现。
type gormPigBatchLogRepository struct {
db *gorm.DB
ctx context.Context
db *gorm.DB
}
// NewGormPigBatchLogRepository 创建一个新的 PigBatchLogRepository 实例。
func NewGormPigBatchLogRepository(db *gorm.DB) PigBatchLogRepository {
return &gormPigBatchLogRepository{db: db}
func NewGormPigBatchLogRepository(ctx context.Context, db *gorm.DB) PigBatchLogRepository {
return &gormPigBatchLogRepository{ctx: ctx, db: db}
}
// CreateTx 实现了在事务中创建猪批次日志的逻辑。
func (r *gormPigBatchLogRepository) CreateTx(tx *gorm.DB, log *models.PigBatchLog) error {
return tx.Create(log).Error
func (r *gormPigBatchLogRepository) CreateTx(ctx context.Context, tx *gorm.DB, log *models.PigBatchLog) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "CreateTx")
return tx.WithContext(repoCtx).Create(log).Error
}
// GetLogsByBatchIDAndDateRangeTx 实现了在指定的事务中,获取指定批次在特定时间范围内的所有日志记录的逻辑。
func (r *gormPigBatchLogRepository) GetLogsByBatchIDAndDateRangeTx(tx *gorm.DB, batchID uint, startDate, endDate time.Time) ([]*models.PigBatchLog, error) {
func (r *gormPigBatchLogRepository) GetLogsByBatchIDAndDateRangeTx(ctx context.Context, tx *gorm.DB, batchID uint, startDate, endDate time.Time) ([]*models.PigBatchLog, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "GetLogsByBatchIDAndDateRangeTx")
var logs []*models.PigBatchLog
err := tx.Where("pig_batch_id = ? AND created_at >= ? AND created_at <= ?", batchID, startDate, endDate).Find(&logs).Error
err := tx.WithContext(repoCtx).Where("pig_batch_id = ? AND created_at >= ? AND created_at <= ?", batchID, startDate, endDate).Find(&logs).Error
if err != nil {
return nil, err
}
@@ -58,9 +64,10 @@ func (r *gormPigBatchLogRepository) GetLogsByBatchIDAndDateRangeTx(tx *gorm.DB,
}
// GetLastLogByBatchIDTx 实现了在指定的事务中,获取某批次的最后一条日志记录的逻辑。
func (r *gormPigBatchLogRepository) GetLastLogByBatchIDTx(tx *gorm.DB, batchID uint) (*models.PigBatchLog, error) {
func (r *gormPigBatchLogRepository) GetLastLogByBatchIDTx(ctx context.Context, tx *gorm.DB, batchID uint) (*models.PigBatchLog, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "GetLastLogByBatchIDTx")
var log models.PigBatchLog
err := tx.Where("pig_batch_id = ?", batchID).Order("id DESC").First(&log).Error
err := tx.WithContext(repoCtx).Where("pig_batch_id = ?", batchID).Order("id DESC").First(&log).Error
if err != nil {
return nil, err
}
@@ -68,7 +75,8 @@ func (r *gormPigBatchLogRepository) GetLastLogByBatchIDTx(tx *gorm.DB, batchID u
}
// List 实现了分页和过滤查询猪批次日志的功能
func (r *gormPigBatchLogRepository) List(opts PigBatchLogListOptions, page, pageSize int) ([]models.PigBatchLog, int64, error) {
func (r *gormPigBatchLogRepository) List(ctx context.Context, opts PigBatchLogListOptions, page, pageSize int) ([]models.PigBatchLog, int64, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "List")
if page <= 0 || pageSize <= 0 {
return nil, 0, ErrInvalidPagination
}
@@ -76,7 +84,7 @@ func (r *gormPigBatchLogRepository) List(opts PigBatchLogListOptions, page, page
var results []models.PigBatchLog
var total int64
query := r.db.Model(&models.PigBatchLog{})
query := r.db.WithContext(repoCtx).Model(&models.PigBatchLog{})
if opts.PigBatchID != nil {
query = query.Where("pig_batch_id = ?", *opts.PigBatchID)

View File

@@ -1,30 +1,33 @@
package repository
import (
"context"
"time"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"gorm.io/gorm"
)
// PigBatchRepository 定义了与猪批次相关的数据库操作接口
type PigBatchRepository interface {
CreatePigBatch(batch *models.PigBatch) (*models.PigBatch, error)
CreatePigBatchTx(tx *gorm.DB, batch *models.PigBatch) (*models.PigBatch, error)
GetPigBatchByID(id uint) (*models.PigBatch, error)
GetPigBatchByIDTx(tx *gorm.DB, id uint) (*models.PigBatch, error)
CreatePigBatch(ctx context.Context, batch *models.PigBatch) (*models.PigBatch, error)
CreatePigBatchTx(ctx context.Context, tx *gorm.DB, batch *models.PigBatch) (*models.PigBatch, error)
GetPigBatchByID(ctx context.Context, id uint) (*models.PigBatch, error)
GetPigBatchByIDTx(ctx context.Context, tx *gorm.DB, id uint) (*models.PigBatch, error)
// UpdatePigBatch 更新一个猪批次,返回更新后的批次、受影响的行数和错误
UpdatePigBatch(batch *models.PigBatch) (*models.PigBatch, int64, error)
UpdatePigBatch(ctx context.Context, batch *models.PigBatch) (*models.PigBatch, int64, error)
// DeletePigBatch 根据ID删除一个猪批次返回受影响的行数和错误
DeletePigBatch(id uint) (int64, error)
DeletePigBatchTx(tx *gorm.DB, id uint) (int64, error)
ListPigBatches(isActive *bool) ([]*models.PigBatch, error)
DeletePigBatch(ctx context.Context, id uint) (int64, error)
DeletePigBatchTx(ctx context.Context, tx *gorm.DB, id uint) (int64, error)
ListPigBatches(ctx context.Context, isActive *bool) ([]*models.PigBatch, error)
// ListWeighingBatches 支持分页和过滤的批次称重列表查询
ListWeighingBatches(opts WeighingBatchListOptions, page, pageSize int) ([]models.WeighingBatch, int64, error)
ListWeighingBatches(ctx context.Context, opts WeighingBatchListOptions, page, pageSize int) ([]models.WeighingBatch, int64, error)
// ListWeighingRecords 支持分页和过滤的单次称重记录列表查询
ListWeighingRecords(opts WeighingRecordListOptions, page, pageSize int) ([]models.WeighingRecord, int64, error)
ListWeighingRecords(ctx context.Context, opts WeighingRecordListOptions, page, pageSize int) ([]models.WeighingRecord, int64, error)
}
// WeighingBatchListOptions 定义了查询批次称重记录时的可选参数
@@ -47,35 +50,40 @@ type WeighingRecordListOptions struct {
// gormPigBatchRepository 是 PigBatchRepository 的 GORM 实现
type gormPigBatchRepository struct {
db *gorm.DB
ctx context.Context
db *gorm.DB
}
// NewGormPigBatchRepository 创建一个新的 PigBatchRepository GORM 实现实例
func NewGormPigBatchRepository(db *gorm.DB) PigBatchRepository {
return &gormPigBatchRepository{db: db}
func NewGormPigBatchRepository(ctx context.Context, db *gorm.DB) PigBatchRepository {
return &gormPigBatchRepository{ctx: ctx, db: db}
}
// CreatePigBatch 创建一个新的猪批次
func (r *gormPigBatchRepository) CreatePigBatch(batch *models.PigBatch) (*models.PigBatch, error) {
return r.CreatePigBatchTx(r.db, batch)
func (r *gormPigBatchRepository) CreatePigBatch(ctx context.Context, batch *models.PigBatch) (*models.PigBatch, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "CreatePigBatch")
return r.CreatePigBatchTx(repoCtx, r.db, batch)
}
// CreatePigBatchTx 在指定的事务中,创建一个新的猪批次
func (r *gormPigBatchRepository) CreatePigBatchTx(tx *gorm.DB, batch *models.PigBatch) (*models.PigBatch, error) {
if err := tx.Create(batch).Error; err != nil {
func (r *gormPigBatchRepository) CreatePigBatchTx(ctx context.Context, tx *gorm.DB, batch *models.PigBatch) (*models.PigBatch, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "CreatePigBatchTx")
if err := tx.WithContext(repoCtx).Create(batch).Error; err != nil {
return nil, err
}
return batch, nil
}
// GetPigBatchByID 根据ID获取单个猪批次
func (r *gormPigBatchRepository) GetPigBatchByID(id uint) (*models.PigBatch, error) {
return r.GetPigBatchByIDTx(r.db, id)
func (r *gormPigBatchRepository) GetPigBatchByID(ctx context.Context, id uint) (*models.PigBatch, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "GetPigBatchByID")
return r.GetPigBatchByIDTx(repoCtx, r.db, id)
}
// UpdatePigBatch 更新一个猪批次
func (r *gormPigBatchRepository) UpdatePigBatch(batch *models.PigBatch) (*models.PigBatch, int64, error) {
result := r.db.Model(&models.PigBatch{}).Where("id = ?", batch.ID).Updates(batch)
func (r *gormPigBatchRepository) UpdatePigBatch(ctx context.Context, batch *models.PigBatch) (*models.PigBatch, int64, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdatePigBatch")
result := r.db.WithContext(repoCtx).Model(&models.PigBatch{}).Where("id = ?", batch.ID).Updates(batch)
if result.Error != nil {
return nil, 0, result.Error
}
@@ -84,12 +92,14 @@ func (r *gormPigBatchRepository) UpdatePigBatch(batch *models.PigBatch) (*models
}
// DeletePigBatch 根据ID删除一个猪批次 (GORM 会执行软删除)
func (r *gormPigBatchRepository) DeletePigBatch(id uint) (int64, error) {
return r.DeletePigBatchTx(r.db, id)
func (r *gormPigBatchRepository) DeletePigBatch(ctx context.Context, id uint) (int64, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "DeletePigBatch")
return r.DeletePigBatchTx(repoCtx, r.db, id)
}
func (r *gormPigBatchRepository) DeletePigBatchTx(tx *gorm.DB, id uint) (int64, error) {
result := tx.Delete(&models.PigBatch{}, id)
func (r *gormPigBatchRepository) DeletePigBatchTx(ctx context.Context, tx *gorm.DB, id uint) (int64, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "DeletePigBatchTx")
result := tx.WithContext(repoCtx).Delete(&models.PigBatch{}, id)
if result.Error != nil {
return 0, result.Error
}
@@ -98,9 +108,10 @@ func (r *gormPigBatchRepository) DeletePigBatchTx(tx *gorm.DB, id uint) (int64,
}
// ListPigBatches 批量查询猪批次,支持根据 IsActive 筛选
func (r *gormPigBatchRepository) ListPigBatches(isActive *bool) ([]*models.PigBatch, error) {
func (r *gormPigBatchRepository) ListPigBatches(ctx context.Context, isActive *bool) ([]*models.PigBatch, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "ListPigBatches")
var batches []*models.PigBatch
query := r.db.Model(&models.PigBatch{})
query := r.db.WithContext(repoCtx).Model(&models.PigBatch{})
if isActive != nil {
if *isActive {
@@ -119,16 +130,18 @@ func (r *gormPigBatchRepository) ListPigBatches(isActive *bool) ([]*models.PigBa
}
// GetPigBatchByIDTx 在指定的事务中通过ID获取单个猪批次
func (r *gormPigBatchRepository) GetPigBatchByIDTx(tx *gorm.DB, id uint) (*models.PigBatch, error) {
func (r *gormPigBatchRepository) GetPigBatchByIDTx(ctx context.Context, tx *gorm.DB, id uint) (*models.PigBatch, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "GetPigBatchByIDTx")
var batch models.PigBatch
if err := tx.First(&batch, id).Error; err != nil {
if err := tx.WithContext(repoCtx).First(&batch, id).Error; err != nil {
return nil, err
}
return &batch, nil
}
// ListWeighingBatches 实现了分页和过滤查询批次称重记录的功能
func (r *gormPigBatchRepository) ListWeighingBatches(opts WeighingBatchListOptions, page, pageSize int) ([]models.WeighingBatch, int64, error) {
func (r *gormPigBatchRepository) ListWeighingBatches(ctx context.Context, opts WeighingBatchListOptions, page, pageSize int) ([]models.WeighingBatch, int64, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "ListWeighingBatches")
if page <= 0 || pageSize <= 0 {
return nil, 0, ErrInvalidPagination
}
@@ -136,7 +149,7 @@ func (r *gormPigBatchRepository) ListWeighingBatches(opts WeighingBatchListOptio
var results []models.WeighingBatch
var total int64
query := r.db.Model(&models.WeighingBatch{})
query := r.db.WithContext(repoCtx).Model(&models.WeighingBatch{})
if opts.PigBatchID != nil {
query = query.Where("pig_batch_id = ?", *opts.PigBatchID)
@@ -165,7 +178,8 @@ func (r *gormPigBatchRepository) ListWeighingBatches(opts WeighingBatchListOptio
}
// ListWeighingRecords 实现了分页和过滤查询单次称重记录的功能
func (r *gormPigBatchRepository) ListWeighingRecords(opts WeighingRecordListOptions, page, pageSize int) ([]models.WeighingRecord, int64, error) {
func (r *gormPigBatchRepository) ListWeighingRecords(ctx context.Context, opts WeighingRecordListOptions, page, pageSize int) ([]models.WeighingRecord, int64, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "ListWeighingRecords")
if page <= 0 || pageSize <= 0 {
return nil, 0, ErrInvalidPagination
}
@@ -173,7 +187,7 @@ func (r *gormPigBatchRepository) ListWeighingRecords(opts WeighingRecordListOpti
var results []models.WeighingRecord
var total int64
query := r.db.Model(&models.WeighingRecord{})
query := r.db.WithContext(repoCtx).Model(&models.WeighingRecord{})
if opts.WeighingBatchID != nil {
query = query.Where("weighing_batch_id = ?", *opts.WeighingBatchID)

View File

@@ -1,61 +1,70 @@
package repository
import (
"context"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"gorm.io/gorm"
)
// PigFarmRepository 定义了与猪场资产(猪舍、猪栏)相关的数据库操作接口
type PigFarmRepository interface {
// PigHouse methods
CreatePigHouse(house *models.PigHouse) error
GetPigHouseByID(id uint) (*models.PigHouse, error)
ListPigHouses() ([]models.PigHouse, error)
CreatePigHouse(ctx context.Context, house *models.PigHouse) error
GetPigHouseByID(ctx context.Context, id uint) (*models.PigHouse, error)
ListPigHouses(ctx context.Context) ([]models.PigHouse, error)
// UpdatePigHouse 更新一个猪舍,返回受影响的行数和错误
UpdatePigHouse(house *models.PigHouse) (int64, error)
UpdatePigHouse(ctx context.Context, house *models.PigHouse) (int64, error)
// DeletePigHouse 根据ID删除一个猪舍返回受影响的行数和错误
DeletePigHouse(id uint) (int64, error)
CountPensInHouse(houseID uint) (int64, error)
DeletePigHouse(ctx context.Context, id uint) (int64, error)
CountPensInHouse(ctx context.Context, houseID uint) (int64, error)
}
// gormPigFarmRepository 是 PigFarmRepository 的 GORM 实现
type gormPigFarmRepository struct {
db *gorm.DB
ctx context.Context
db *gorm.DB
}
// NewGormPigFarmRepository 创建一个新的 PigFarmRepository GORM 实现实例
func NewGormPigFarmRepository(db *gorm.DB) PigFarmRepository {
return &gormPigFarmRepository{db: db}
func NewGormPigFarmRepository(ctx context.Context, db *gorm.DB) PigFarmRepository {
return &gormPigFarmRepository{ctx: ctx, db: db}
}
// --- PigHouse Implementation ---
// CreatePigHouse 创建一个新的猪舍
func (r *gormPigFarmRepository) CreatePigHouse(house *models.PigHouse) error {
return r.db.Create(house).Error
func (r *gormPigFarmRepository) CreatePigHouse(ctx context.Context, house *models.PigHouse) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "CreatePigHouse")
return r.db.WithContext(repoCtx).Create(house).Error
}
// GetPigHouseByID 根据ID获取单个猪舍
func (r *gormPigFarmRepository) GetPigHouseByID(id uint) (*models.PigHouse, error) {
func (r *gormPigFarmRepository) GetPigHouseByID(ctx context.Context, id uint) (*models.PigHouse, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "GetPigHouseByID")
var house models.PigHouse
if err := r.db.First(&house, id).Error; err != nil {
if err := r.db.WithContext(repoCtx).First(&house, id).Error; err != nil {
return nil, err
}
return &house, nil
}
// ListPigHouses 列出所有猪舍
func (r *gormPigFarmRepository) ListPigHouses() ([]models.PigHouse, error) {
func (r *gormPigFarmRepository) ListPigHouses(ctx context.Context) ([]models.PigHouse, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "ListPigHouses")
var houses []models.PigHouse
if err := r.db.Find(&houses).Error; err != nil {
if err := r.db.WithContext(repoCtx).Find(&houses).Error; err != nil {
return nil, err
}
return houses, nil
}
// UpdatePigHouse 更新一个猪舍,返回受影响的行数和错误
func (r *gormPigFarmRepository) UpdatePigHouse(house *models.PigHouse) (int64, error) {
result := r.db.Model(&models.PigHouse{}).Where("id = ?", house.ID).Updates(house)
func (r *gormPigFarmRepository) UpdatePigHouse(ctx context.Context, house *models.PigHouse) (int64, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdatePigHouse")
result := r.db.WithContext(repoCtx).Model(&models.PigHouse{}).Where("id = ?", house.ID).Updates(house)
if result.Error != nil {
return 0, result.Error
}
@@ -63,8 +72,9 @@ func (r *gormPigFarmRepository) UpdatePigHouse(house *models.PigHouse) (int64, e
}
// DeletePigHouse 根据ID删除一个猪舍返回受影响的行数和错误
func (r *gormPigFarmRepository) DeletePigHouse(id uint) (int64, error) {
result := r.db.Delete(&models.PigHouse{}, id)
func (r *gormPigFarmRepository) DeletePigHouse(ctx context.Context, id uint) (int64, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "DeletePigHouse")
result := r.db.WithContext(repoCtx).Delete(&models.PigHouse{}, id)
if result.Error != nil {
return 0, result.Error
}
@@ -72,8 +82,9 @@ func (r *gormPigFarmRepository) DeletePigHouse(id uint) (int64, error) {
}
// CountPensInHouse 统计猪舍中的猪栏数量
func (r *gormPigFarmRepository) CountPensInHouse(houseID uint) (int64, error) {
func (r *gormPigFarmRepository) CountPensInHouse(ctx context.Context, houseID uint) (int64, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "CountPensInHouse")
var count int64
err := r.db.Model(&models.Pen{}).Where("house_id = ?", houseID).Count(&count).Error
err := r.db.WithContext(repoCtx).Model(&models.Pen{}).Where("house_id = ?", houseID).Count(&count).Error
return count, err
}

View File

@@ -1,69 +1,79 @@
package repository
import (
"context"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"gorm.io/gorm"
)
// PigPenRepository 定义了与猪栏模型相关的数据库操作接口。
type PigPenRepository interface {
CreatePen(pen *models.Pen) error
CreatePen(ctx context.Context, pen *models.Pen) error
// GetPenByID 根据ID获取单个猪栏 (非事务性)
GetPenByID(id uint) (*models.Pen, error)
GetPenByID(ctx context.Context, id uint) (*models.Pen, error)
// GetPenByIDTx 根据ID获取单个猪栏 (事务性)
GetPenByIDTx(tx *gorm.DB, id uint) (*models.Pen, error)
ListPens() ([]models.Pen, error)
GetPenByIDTx(ctx context.Context, tx *gorm.DB, id uint) (*models.Pen, error)
ListPens(ctx context.Context) ([]models.Pen, error)
// UpdatePen 更新一个猪栏,返回受影响的行数和错误
UpdatePen(pen *models.Pen) (int64, error)
UpdatePen(ctx context.Context, pen *models.Pen) (int64, error)
// DeletePen 根据ID删除一个猪栏返回受影响的行数和错误
DeletePen(id uint) (int64, error)
DeletePen(ctx context.Context, id uint) (int64, error)
// GetPensByBatchIDTx 根据批次ID获取所有关联的猪栏 (事务性)
GetPensByBatchIDTx(tx *gorm.DB, batchID uint) ([]*models.Pen, error)
GetPensByBatchIDTx(ctx context.Context, tx *gorm.DB, batchID uint) ([]*models.Pen, error)
// UpdatePenFieldsTx 更新猪栏的指定字段 (事务性)
UpdatePenFieldsTx(tx *gorm.DB, penID uint, updates map[string]interface{}) error
UpdatePenFieldsTx(ctx context.Context, tx *gorm.DB, penID uint, updates map[string]interface{}) error
}
// gormPigPenRepository 是 PigPenRepository 接口的 GORM 实现。
type gormPigPenRepository struct {
db *gorm.DB
ctx context.Context
db *gorm.DB
}
// NewGormPigPenRepository 创建一个新的 PigPenRepository GORM 实现实例。
func NewGormPigPenRepository(db *gorm.DB) PigPenRepository {
return &gormPigPenRepository{db: db}
func NewGormPigPenRepository(ctx context.Context, db *gorm.DB) PigPenRepository {
return &gormPigPenRepository{ctx: ctx, db: db}
}
// CreatePen 创建一个新的猪栏
func (r *gormPigPenRepository) CreatePen(pen *models.Pen) error {
return r.db.Create(pen).Error
func (r *gormPigPenRepository) CreatePen(ctx context.Context, pen *models.Pen) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "CreatePen")
return r.db.WithContext(repoCtx).Create(pen).Error
}
// GetPenByID 根据ID获取单个猪栏 (非事务性)
func (r *gormPigPenRepository) GetPenByID(id uint) (*models.Pen, error) {
return r.GetPenByIDTx(r.db, id) // 非Tx方法直接调用Tx方法
func (r *gormPigPenRepository) GetPenByID(ctx context.Context, id uint) (*models.Pen, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "GetPenByID")
return r.GetPenByIDTx(repoCtx, r.db, id) // 非Tx方法直接调用Tx方法
}
// GetPenByIDTx 在指定的事务中通过ID获取单个猪栏信息。
func (r *gormPigPenRepository) GetPenByIDTx(tx *gorm.DB, id uint) (*models.Pen, error) {
func (r *gormPigPenRepository) GetPenByIDTx(ctx context.Context, tx *gorm.DB, id uint) (*models.Pen, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "GetPenByIDTx")
var pen models.Pen
if err := tx.First(&pen, id).Error; err != nil {
if err := tx.WithContext(repoCtx).First(&pen, id).Error; err != nil {
return nil, err
}
return &pen, nil
}
// ListPens 列出所有猪栏
func (r *gormPigPenRepository) ListPens() ([]models.Pen, error) {
func (r *gormPigPenRepository) ListPens(ctx context.Context) ([]models.Pen, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "ListPens")
var pens []models.Pen
if err := r.db.Find(&pens).Error; err != nil {
if err := r.db.WithContext(repoCtx).Find(&pens).Error; err != nil {
return nil, err
}
return pens, nil
}
// UpdatePen 更新一个猪栏,返回受影响的行数和错误
func (r *gormPigPenRepository) UpdatePen(pen *models.Pen) (int64, error) {
result := r.db.Model(&models.Pen{}).Where("id = ?", pen.ID).Updates(pen)
func (r *gormPigPenRepository) UpdatePen(ctx context.Context, pen *models.Pen) (int64, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdatePen")
result := r.db.WithContext(repoCtx).Model(&models.Pen{}).Where("id = ?", pen.ID).Updates(pen)
if result.Error != nil {
return 0, result.Error
}
@@ -71,8 +81,9 @@ func (r *gormPigPenRepository) UpdatePen(pen *models.Pen) (int64, error) {
}
// DeletePen 根据ID删除一个猪栏返回受影响的行数和错误
func (r *gormPigPenRepository) DeletePen(id uint) (int64, error) {
result := r.db.Delete(&models.Pen{}, id)
func (r *gormPigPenRepository) DeletePen(ctx context.Context, id uint) (int64, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "DeletePen")
result := r.db.WithContext(repoCtx).Delete(&models.Pen{}, id)
if result.Error != nil {
return 0, result.Error
}
@@ -80,10 +91,11 @@ func (r *gormPigPenRepository) DeletePen(id uint) (int64, error) {
}
// GetPensByBatchIDTx 在指定的事务中,获取一个猪群当前关联的所有猪栏。
func (r *gormPigPenRepository) GetPensByBatchIDTx(tx *gorm.DB, batchID uint) ([]*models.Pen, error) {
func (r *gormPigPenRepository) GetPensByBatchIDTx(ctx context.Context, tx *gorm.DB, batchID uint) ([]*models.Pen, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "GetPensByBatchIDTx")
var pens []*models.Pen
// 注意PigBatchID 是指针类型,需要处理 nil 值
result := tx.Where("pig_batch_id = ?", batchID).Find(&pens)
result := tx.WithContext(repoCtx).Where("pig_batch_id = ?", batchID).Find(&pens)
if result.Error != nil {
return nil, result.Error
}
@@ -91,7 +103,8 @@ func (r *gormPigPenRepository) GetPensByBatchIDTx(tx *gorm.DB, batchID uint) ([]
}
// UpdatePenFieldsTx 在指定的事务中,更新一个猪栏的指定字段。
func (r *gormPigPenRepository) UpdatePenFieldsTx(tx *gorm.DB, penID uint, updates map[string]interface{}) error {
result := tx.Model(&models.Pen{}).Where("id = ?", penID).Updates(updates)
func (r *gormPigPenRepository) UpdatePenFieldsTx(ctx context.Context, tx *gorm.DB, penID uint, updates map[string]interface{}) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdatePenFieldsTx")
result := tx.WithContext(repoCtx).Model(&models.Pen{}).Where("id = ?", penID).Updates(updates)
return result.Error
}

View File

@@ -1,10 +1,13 @@
package repository
import (
"context"
"errors"
"time"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"gorm.io/gorm"
)
@@ -23,38 +26,42 @@ type PigSickLogListOptions struct {
// PigSickLogRepository 定义了与病猪日志模型相关的数据库操作接口。
type PigSickLogRepository interface {
// CreatePigSickLog 创建一条新的病猪日志记录
CreatePigSickLog(log *models.PigSickLog) error
CreatePigSickLogTx(tx *gorm.DB, log *models.PigSickLog) error
CreatePigSickLog(ctx context.Context, log *models.PigSickLog) error
CreatePigSickLogTx(ctx context.Context, tx *gorm.DB, log *models.PigSickLog) error
// GetLastLogByBatchTx 在事务中获取指定批次和猪栏的最新一条 PigSickLog 记录
GetLastLogByBatchTx(tx *gorm.DB, batchID uint) (*models.PigSickLog, error)
GetLastLogByBatchTx(ctx context.Context, tx *gorm.DB, batchID uint) (*models.PigSickLog, error)
// ListPigSickLogs 支持分页和过滤的病猪日志列表查询
ListPigSickLogs(opts PigSickLogListOptions, page, pageSize int) ([]models.PigSickLog, int64, error)
ListPigSickLogs(ctx context.Context, opts PigSickLogListOptions, page, pageSize int) ([]models.PigSickLog, int64, error)
}
// gormPigSickLogRepository 是 PigSickLogRepository 接口的 GORM 实现。
type gormPigSickLogRepository struct {
db *gorm.DB
ctx context.Context
db *gorm.DB
}
// NewGormPigSickLogRepository 创建一个新的 PigSickLogRepository GORM 实现实例。
func NewGormPigSickLogRepository(db *gorm.DB) PigSickLogRepository {
return &gormPigSickLogRepository{db: db}
func NewGormPigSickLogRepository(ctx context.Context, db *gorm.DB) PigSickLogRepository {
return &gormPigSickLogRepository{ctx: ctx, db: db}
}
// CreatePigSickLog 创建一条新的病猪日志记录
func (r *gormPigSickLogRepository) CreatePigSickLog(log *models.PigSickLog) error {
return r.CreatePigSickLogTx(r.db, log)
func (r *gormPigSickLogRepository) CreatePigSickLog(ctx context.Context, log *models.PigSickLog) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "CreatePigSickLog")
return r.CreatePigSickLogTx(repoCtx, r.db, log)
}
func (r *gormPigSickLogRepository) CreatePigSickLogTx(tx *gorm.DB, log *models.PigSickLog) error {
return tx.Create(log).Error
func (r *gormPigSickLogRepository) CreatePigSickLogTx(ctx context.Context, tx *gorm.DB, log *models.PigSickLog) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "CreatePigSickLogTx")
return tx.WithContext(repoCtx).Create(log).Error
}
// GetLastLogByBatchTx 在事务中获取指定批次和猪栏的最新一条 PigSickLog 记录
func (r *gormPigSickLogRepository) GetLastLogByBatchTx(tx *gorm.DB, batchID uint) (*models.PigSickLog, error) {
func (r *gormPigSickLogRepository) GetLastLogByBatchTx(ctx context.Context, tx *gorm.DB, batchID uint) (*models.PigSickLog, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "GetLastLogByBatchTx")
var lastLog models.PigSickLog
err := tx.
err := tx.WithContext(repoCtx).
Where("pig_batch_id = ?", batchID).
Order("happened_at DESC"). // 按时间降序排列
First(&lastLog).Error // 获取第一条记录 (即最新一条)
@@ -69,7 +76,8 @@ func (r *gormPigSickLogRepository) GetLastLogByBatchTx(tx *gorm.DB, batchID uint
}
// ListPigSickLogs 实现了分页和过滤查询病猪日志的功能
func (r *gormPigSickLogRepository) ListPigSickLogs(opts PigSickLogListOptions, page, pageSize int) ([]models.PigSickLog, int64, error) {
func (r *gormPigSickLogRepository) ListPigSickLogs(ctx context.Context, opts PigSickLogListOptions, page, pageSize int) ([]models.PigSickLog, int64, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "ListPigSickLogs")
if page <= 0 || pageSize <= 0 {
return nil, 0, ErrInvalidPagination
}
@@ -77,7 +85,7 @@ func (r *gormPigSickLogRepository) ListPigSickLogs(opts PigSickLogListOptions, p
var results []models.PigSickLog
var total int64
query := r.db.Model(&models.PigSickLog{})
query := r.db.WithContext(repoCtx).Model(&models.PigSickLog{})
if opts.PigBatchID != nil {
query = query.Where("pig_batch_id = ?", *opts.PigBatchID)

View File

@@ -1,9 +1,12 @@
package repository
import (
"context"
"time"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"gorm.io/gorm"
)
@@ -31,40 +34,44 @@ type PigSaleListOptions struct {
// 领域服务通过此接口与数据层交互,实现解耦。
type PigTradeRepository interface {
// CreatePigSaleTx 在数据库中创建一条猪只销售记录。
CreatePigSaleTx(tx *gorm.DB, sale *models.PigSale) error
CreatePigSaleTx(ctx context.Context, tx *gorm.DB, sale *models.PigSale) error
// CreatePigPurchaseTx 在数据库中创建一条猪只采购记录。
CreatePigPurchaseTx(tx *gorm.DB, purchase *models.PigPurchase) error
CreatePigPurchaseTx(ctx context.Context, tx *gorm.DB, purchase *models.PigPurchase) error
// ListPigPurchases 支持分页和过滤的猪只采购记录列表查询
ListPigPurchases(opts PigPurchaseListOptions, page, pageSize int) ([]models.PigPurchase, int64, error)
ListPigPurchases(ctx context.Context, opts PigPurchaseListOptions, page, pageSize int) ([]models.PigPurchase, int64, error)
// ListPigSales 支持分页和过滤的猪只销售记录列表查询
ListPigSales(opts PigSaleListOptions, page, pageSize int) ([]models.PigSale, int64, error)
ListPigSales(ctx context.Context, opts PigSaleListOptions, page, pageSize int) ([]models.PigSale, int64, error)
}
// gormPigTradeRepository 是 PigTradeRepository 接口的 GORM 实现。
type gormPigTradeRepository struct {
db *gorm.DB
ctx context.Context
db *gorm.DB
}
// NewGormPigTradeRepository 创建一个新的 PigTradeRepository GORM 实现实例。
func NewGormPigTradeRepository(db *gorm.DB) PigTradeRepository {
return &gormPigTradeRepository{db: db}
func NewGormPigTradeRepository(ctx context.Context, db *gorm.DB) PigTradeRepository {
return &gormPigTradeRepository{ctx: ctx, db: db}
}
// CreatePigSaleTx 实现了在数据库中创建猪只销售记录的逻辑。
func (r *gormPigTradeRepository) CreatePigSaleTx(tx *gorm.DB, sale *models.PigSale) error {
return tx.Create(sale).Error
func (r *gormPigTradeRepository) CreatePigSaleTx(ctx context.Context, tx *gorm.DB, sale *models.PigSale) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "CreatePigSaleTx")
return tx.WithContext(repoCtx).Create(sale).Error
}
// CreatePigPurchaseTx 实现了在数据库中创建猪只采购记录的逻辑。
func (r *gormPigTradeRepository) CreatePigPurchaseTx(tx *gorm.DB, purchase *models.PigPurchase) error {
return tx.Create(purchase).Error
func (r *gormPigTradeRepository) CreatePigPurchaseTx(ctx context.Context, tx *gorm.DB, purchase *models.PigPurchase) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "CreatePigPurchaseTx")
return tx.WithContext(repoCtx).Create(purchase).Error
}
// ListPigPurchases 实现了分页和过滤查询猪只采购记录的功能
func (r *gormPigTradeRepository) ListPigPurchases(opts PigPurchaseListOptions, page, pageSize int) ([]models.PigPurchase, int64, error) {
func (r *gormPigTradeRepository) ListPigPurchases(ctx context.Context, opts PigPurchaseListOptions, page, pageSize int) ([]models.PigPurchase, int64, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "ListPigPurchases")
if page <= 0 || pageSize <= 0 {
return nil, 0, ErrInvalidPagination
}
@@ -72,7 +79,7 @@ func (r *gormPigTradeRepository) ListPigPurchases(opts PigPurchaseListOptions, p
var results []models.PigPurchase
var total int64
query := r.db.Model(&models.PigPurchase{})
query := r.db.WithContext(repoCtx).Model(&models.PigPurchase{})
if opts.PigBatchID != nil {
query = query.Where("pig_batch_id = ?", *opts.PigBatchID)
@@ -107,7 +114,8 @@ func (r *gormPigTradeRepository) ListPigPurchases(opts PigPurchaseListOptions, p
}
// ListPigSales 实现了分页和过滤查询猪只销售记录的功能
func (r *gormPigTradeRepository) ListPigSales(opts PigSaleListOptions, page, pageSize int) ([]models.PigSale, int64, error) {
func (r *gormPigTradeRepository) ListPigSales(ctx context.Context, opts PigSaleListOptions, page, pageSize int) ([]models.PigSale, int64, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "ListPigSales")
if page <= 0 || pageSize <= 0 {
return nil, 0, ErrInvalidPagination
}
@@ -115,7 +123,7 @@ func (r *gormPigTradeRepository) ListPigSales(opts PigSaleListOptions, page, pag
var results []models.PigSale
var total int64
query := r.db.Model(&models.PigSale{})
query := r.db.WithContext(repoCtx).Model(&models.PigSale{})
if opts.PigBatchID != nil {
query = query.Where("pig_batch_id = ?", *opts.PigBatchID)

View File

@@ -1,9 +1,12 @@
package repository
import (
"context"
"time"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"gorm.io/gorm"
)
@@ -22,39 +25,43 @@ type PigTransferLogListOptions struct {
// PigTransferLogRepository 定义了猪只迁移日志数据持久化的接口。
type PigTransferLogRepository interface {
// CreatePigTransferLog 在数据库中创建一条猪只迁移日志记录。
CreatePigTransferLog(tx *gorm.DB, log *models.PigTransferLog) error
CreatePigTransferLog(ctx context.Context, tx *gorm.DB, log *models.PigTransferLog) error
// GetLogsForPenSince 获取指定猪栏自特定时间点以来的所有迁移日志,按时间倒序排列。
GetLogsForPenSince(tx *gorm.DB, penID uint, since time.Time) ([]*models.PigTransferLog, error)
GetLogsForPenSince(ctx context.Context, tx *gorm.DB, penID uint, since time.Time) ([]*models.PigTransferLog, error)
// ListPigTransferLogs 支持分页和过滤的猪只迁移日志列表查询
ListPigTransferLogs(opts PigTransferLogListOptions, page, pageSize int) ([]models.PigTransferLog, int64, error)
ListPigTransferLogs(ctx context.Context, opts PigTransferLogListOptions, page, pageSize int) ([]models.PigTransferLog, int64, error)
}
// gormPigTransferLogRepository 是 PigTransferLogRepository 接口的 GORM 实现。
type gormPigTransferLogRepository struct {
db *gorm.DB
ctx context.Context
db *gorm.DB
}
// NewGormPigTransferLogRepository 创建一个新的 PigTransferLogRepository GORM 实现实例。
func NewGormPigTransferLogRepository(db *gorm.DB) PigTransferLogRepository {
return &gormPigTransferLogRepository{db: db}
func NewGormPigTransferLogRepository(ctx context.Context, db *gorm.DB) PigTransferLogRepository {
return &gormPigTransferLogRepository{ctx: ctx, db: db}
}
// CreatePigTransferLog 实现了在数据库中创建猪只迁移日志记录的逻辑。
func (r *gormPigTransferLogRepository) CreatePigTransferLog(tx *gorm.DB, log *models.PigTransferLog) error {
return tx.Create(log).Error
func (r *gormPigTransferLogRepository) CreatePigTransferLog(ctx context.Context, tx *gorm.DB, log *models.PigTransferLog) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "CreatePigTransferLog")
return tx.WithContext(repoCtx).Create(log).Error
}
// GetLogsForPenSince 实现了获取猪栏自特定时间点以来所有迁移日志的逻辑。
func (r *gormPigTransferLogRepository) GetLogsForPenSince(tx *gorm.DB, penID uint, since time.Time) ([]*models.PigTransferLog, error) {
func (r *gormPigTransferLogRepository) GetLogsForPenSince(ctx context.Context, tx *gorm.DB, penID uint, since time.Time) ([]*models.PigTransferLog, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "GetLogsForPenSince")
var logs []*models.PigTransferLog
err := tx.Where("pen_id = ? AND transfer_time >= ?", penID, since).Order("transfer_time DESC").Find(&logs).Error
err := tx.WithContext(repoCtx).Where("pen_id = ? AND transfer_time >= ?", penID, since).Order("transfer_time DESC").Find(&logs).Error
return logs, err
}
// ListPigTransferLogs 实现了分页和过滤查询猪只迁移日志的功能
func (r *gormPigTransferLogRepository) ListPigTransferLogs(opts PigTransferLogListOptions, page, pageSize int) ([]models.PigTransferLog, int64, error) {
func (r *gormPigTransferLogRepository) ListPigTransferLogs(ctx context.Context, opts PigTransferLogListOptions, page, pageSize int) ([]models.PigTransferLog, int64, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "ListPigTransferLogs")
if page <= 0 || pageSize <= 0 {
return nil, 0, ErrInvalidPagination
}
@@ -62,7 +69,7 @@ func (r *gormPigTransferLogRepository) ListPigTransferLogs(opts PigTransferLogLi
var results []models.PigTransferLog
var total int64
query := r.db.Model(&models.PigTransferLog{})
query := r.db.WithContext(repoCtx).Model(&models.PigTransferLog{})
if opts.PigBatchID != nil {
query = query.Where("pig_batch_id = ?", *opts.PigBatchID)

View File

@@ -1,11 +1,14 @@
package repository
import (
"context"
"encoding/json"
"errors"
"fmt"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"gorm.io/datatypes"
"gorm.io/gorm"
)
@@ -39,67 +42,71 @@ type ListPlansOptions struct {
// 这是为了让业务逻辑层依赖于抽象,而不是具体的数据库实现
type PlanRepository interface {
// ListPlans 获取计划列表,支持过滤和分页
ListPlans(opts ListPlansOptions, page, pageSize int) ([]models.Plan, int64, error)
ListPlans(ctx context.Context, opts ListPlansOptions, page, pageSize int) ([]models.Plan, int64, error)
// GetBasicPlanByID 根据ID获取计划的基本信息不包含子计划和任务详情
GetBasicPlanByID(id uint) (*models.Plan, error)
GetBasicPlanByID(ctx context.Context, id uint) (*models.Plan, error)
// GetPlanByID 根据ID获取计划包含子计划和任务详情
GetPlanByID(id uint) (*models.Plan, error)
GetPlanByID(ctx context.Context, id uint) (*models.Plan, error)
// GetPlansByIDs 根据ID列表获取计划不包含子计划和任务详情
GetPlansByIDs(ids []uint) ([]models.Plan, error)
GetPlansByIDs(ctx context.Context, ids []uint) ([]models.Plan, error)
// CreatePlan 创建一个新的计划
CreatePlan(plan *models.Plan) error
CreatePlan(ctx context.Context, plan *models.Plan) error
// CreatePlanTx 在指定事务中创建一个新的计划
CreatePlanTx(tx *gorm.DB, plan *models.Plan) error
CreatePlanTx(ctx context.Context, tx *gorm.DB, plan *models.Plan) error
// UpdatePlanMetadataAndStructure 更新计划的元数据和结构,但不包括状态等运行时信息
UpdatePlanMetadataAndStructure(plan *models.Plan) error
UpdatePlanMetadataAndStructure(ctx context.Context, plan *models.Plan) error
// UpdatePlan 更新计划的所有字段
UpdatePlan(plan *models.Plan) error
UpdatePlan(ctx context.Context, plan *models.Plan) error
// UpdatePlanStatus 更新指定计划的状态
UpdatePlanStatus(id uint, status models.PlanStatus) error
UpdatePlanStatus(ctx context.Context, id uint, status models.PlanStatus) error
// UpdateExecuteCount 更新指定计划的执行计数
UpdateExecuteCount(id uint, count uint) error
UpdateExecuteCount(ctx context.Context, id uint, count uint) error
// DeletePlan 根据ID删除计划同时删除其关联的任务非子任务或子计划关联
DeletePlan(id uint) error
DeletePlan(ctx context.Context, id uint) error
// FlattenPlanTasks 递归展开计划,返回按执行顺序排列的所有任务列表
FlattenPlanTasks(planID uint) ([]models.Task, error)
FlattenPlanTasks(ctx context.Context, planID uint) ([]models.Task, error)
// DeleteTask 根据ID删除任务
DeleteTask(id int) error
DeleteTask(ctx context.Context, id int) error
// FindPlanAnalysisTaskByParamsPlanID 根据Parameters中的ParamsPlanID字段值查找TaskPlanAnalysis类型的Task
FindPlanAnalysisTaskByParamsPlanID(paramsPlanID uint) (*models.Task, error)
FindPlanAnalysisTaskByParamsPlanID(ctx context.Context, paramsPlanID uint) (*models.Task, error)
// FindRunnablePlans 获取所有应执行的计划
FindRunnablePlans() ([]*models.Plan, error)
FindRunnablePlans(ctx context.Context) ([]*models.Plan, error)
// FindInactivePlans 获取所有已禁用或已停止的计划
FindInactivePlans() ([]*models.Plan, error)
FindInactivePlans(ctx context.Context) ([]*models.Plan, error)
// FindPlanAnalysisTaskByPlanID 根据 PlanID 找到其关联的 'plan_analysis' 任务
FindPlanAnalysisTaskByPlanID(planID uint) (*models.Task, error)
FindPlanAnalysisTaskByPlanID(ctx context.Context, planID uint) (*models.Task, error)
// CreatePlanAnalysisTask 创建一个 plan_analysis 类型的任务并返回它
CreatePlanAnalysisTask(plan *models.Plan) (*models.Task, error)
CreatePlanAnalysisTask(ctx context.Context, plan *models.Plan) (*models.Task, error)
// FindPlansWithPendingTasks 查找所有正在执行的计划
FindPlansWithPendingTasks() ([]*models.Plan, error)
FindPlansWithPendingTasks(ctx context.Context) ([]*models.Plan, error)
// StopPlanTransactionally 停止一个计划的执行,包括更新状态、移除待执行任务和更新执行日志
StopPlanTransactionally(planID uint) error
StopPlanTransactionally(ctx context.Context, planID uint) error
// UpdatePlanStateAfterExecution 更新计划执行后的状态(计数和状态)
UpdatePlanStateAfterExecution(planID uint, newCount uint, newStatus models.PlanStatus) error
UpdatePlanStateAfterExecution(ctx context.Context, planID uint, newCount uint, newStatus models.PlanStatus) error
}
// gormPlanRepository 是 PlanRepository 的 GORM 实现
type gormPlanRepository struct {
db *gorm.DB
ctx context.Context
db *gorm.DB
}
// NewGormPlanRepository 创建一个新的 PlanRepository GORM 实现实例
func NewGormPlanRepository(db *gorm.DB) PlanRepository {
func NewGormPlanRepository(ctx context.Context, db *gorm.DB) PlanRepository {
return &gormPlanRepository{
db: db,
ctx: ctx,
db: db,
}
}
// ListPlans 获取计划列表,支持过滤和分页
func (r *gormPlanRepository) ListPlans(opts ListPlansOptions, page, pageSize int) ([]models.Plan, int64, error) {
func (r *gormPlanRepository) ListPlans(ctx context.Context, opts ListPlansOptions, page, pageSize int) ([]models.Plan, int64, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "ListPlans")
if page <= 0 || pageSize <= 0 {
return nil, 0, ErrInvalidPagination
}
@@ -107,7 +114,7 @@ func (r *gormPlanRepository) ListPlans(opts ListPlansOptions, page, pageSize int
var plans []models.Plan
var total int64
query := r.db.Model(&models.Plan{})
query := r.db.WithContext(repoCtx).Model(&models.Plan{})
switch opts.PlanType {
case PlanTypeFilterCustom:
@@ -132,10 +139,11 @@ func (r *gormPlanRepository) ListPlans(opts ListPlansOptions, page, pageSize int
}
// GetBasicPlanByID 根据ID获取计划的基本信息不包含子计划和任务详情
func (r *gormPlanRepository) GetBasicPlanByID(id uint) (*models.Plan, error) {
func (r *gormPlanRepository) GetBasicPlanByID(ctx context.Context, id uint) (*models.Plan, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "GetBasicPlanByID")
var plan models.Plan
// GORM 默认不会加载关联,除非使用 Preload所以直接 First 即可满足要求
result := r.db.First(&plan, id)
result := r.db.WithContext(repoCtx).First(&plan, id)
if result.Error != nil {
return nil, result.Error
}
@@ -143,12 +151,13 @@ func (r *gormPlanRepository) GetBasicPlanByID(id uint) (*models.Plan, error) {
}
// GetPlansByIDs 根据ID列表获取计划不包含子计划和任务详情
func (r *gormPlanRepository) GetPlansByIDs(ids []uint) ([]models.Plan, error) {
func (r *gormPlanRepository) GetPlansByIDs(ctx context.Context, ids []uint) ([]models.Plan, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "GetPlansByIDs")
var plans []models.Plan
if len(ids) == 0 {
return plans, nil
}
err := r.db.Where("id IN ?", ids).Find(&plans).Error
err := r.db.WithContext(repoCtx).Where("id IN ?", ids).Find(&plans).Error
if err != nil {
return nil, err
}
@@ -156,11 +165,12 @@ func (r *gormPlanRepository) GetPlansByIDs(ids []uint) ([]models.Plan, error) {
}
// GetPlanByID 根据ID获取计划包含子计划和任务详情
func (r *gormPlanRepository) GetPlanByID(id uint) (*models.Plan, error) {
func (r *gormPlanRepository) GetPlanByID(ctx context.Context, id uint) (*models.Plan, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "GetPlanByID")
var plan models.Plan
// 先获取基本计划信息
result := r.db.First(&plan, id)
result := r.db.WithContext(repoCtx).First(&plan, id)
if result.Error != nil {
return nil, result.Error
}
@@ -170,14 +180,14 @@ func (r *gormPlanRepository) GetPlanByID(id uint) (*models.Plan, error) {
case models.PlanContentTypeSubPlans:
// 加载子计划引用
var subPlans []models.SubPlan
result = r.db.Where("parent_plan_id = ?", plan.ID).Order("execution_order").Find(&subPlans)
result = r.db.WithContext(repoCtx).Where("parent_plan_id = ?", plan.ID).Order("execution_order").Find(&subPlans)
if result.Error != nil {
return nil, result.Error
}
// 递归加载每个子计划的完整信息
for i := range subPlans {
childPlan, err := r.GetPlanByID(subPlans[i].ChildPlanID)
childPlan, err := r.GetPlanByID(repoCtx, subPlans[i].ChildPlanID)
if err != nil {
return nil, err
}
@@ -187,7 +197,7 @@ func (r *gormPlanRepository) GetPlanByID(id uint) (*models.Plan, error) {
plan.SubPlans = subPlans
case models.PlanContentTypeTasks:
// 加载任务
result = r.db.Preload("Tasks", func(taskDB *gorm.DB) *gorm.DB {
result = r.db.WithContext(repoCtx).Preload("Tasks", func(taskDB *gorm.DB) *gorm.DB {
return taskDB.Order("execution_order")
}).First(&plan, id)
if result.Error != nil {
@@ -201,12 +211,14 @@ func (r *gormPlanRepository) GetPlanByID(id uint) (*models.Plan, error) {
}
// CreatePlan 创建一个新的计划
func (r *gormPlanRepository) CreatePlan(plan *models.Plan) error {
return r.CreatePlanTx(r.db, plan)
func (r *gormPlanRepository) CreatePlan(ctx context.Context, plan *models.Plan) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "CreatePlan")
return r.CreatePlanTx(repoCtx, r.db, plan)
}
// CreatePlanTx 在指定事务中创建一个新的计划
func (r *gormPlanRepository) CreatePlanTx(tx *gorm.DB, plan *models.Plan) error {
func (r *gormPlanRepository) CreatePlanTx(ctx context.Context, tx *gorm.DB, plan *models.Plan) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "CreatePlanTx")
// 1. 前置校验
if plan.ID != 0 {
return ErrCreateWithNonZeroID
@@ -239,7 +251,7 @@ func (r *gormPlanRepository) CreatePlanTx(tx *gorm.DB, plan *models.Plan) error
if len(ids) > 0 {
var count int64
if err := tx.Model(&models.Plan{}).Where("id IN ?", ids).Count(&count).Error; err != nil {
if err := tx.WithContext(repoCtx).Model(&models.Plan{}).Where("id IN ?", ids).Count(&count).Error; err != nil {
return fmt.Errorf("验证子计划存在性失败: %w", err)
}
if int(count) != len(ids) {
@@ -251,13 +263,13 @@ func (r *gormPlanRepository) CreatePlanTx(tx *gorm.DB, plan *models.Plan) error
// 2. 创建根计划
// GORM 会自动处理关联的 Tasks (如果 ContentType 是 tasks 且 Task.ID 为 0),
// 以及 Tasks 内部已经填充好的 Devices 关联。
if err := tx.Create(plan).Error; err != nil {
if err := tx.WithContext(repoCtx).Create(plan).Error; err != nil {
return err
}
// 3. 创建触发器Task
// 关键修改:调用 createPlanAnalysisTask 并处理其返回的 Task 对象
_, err := r.createPlanAnalysisTask(tx, plan)
_, err := r.createPlanAnalysisTask(repoCtx, tx, plan)
if err != nil {
return err
}
@@ -265,32 +277,36 @@ func (r *gormPlanRepository) CreatePlanTx(tx *gorm.DB, plan *models.Plan) error
}
// UpdatePlan 更新计划
func (r *gormPlanRepository) UpdatePlan(plan *models.Plan) error {
return r.db.Save(plan).Error
func (r *gormPlanRepository) UpdatePlan(ctx context.Context, plan *models.Plan) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdatePlan")
return r.db.WithContext(repoCtx).Save(plan).Error
}
// UpdatePlanMetadataAndStructure 是更新计划元数据和结构的公共入口点
func (r *gormPlanRepository) UpdatePlanMetadataAndStructure(plan *models.Plan) error {
return r.db.Transaction(func(tx *gorm.DB) error {
return r.updatePlanMetadataAndStructureTx(tx, plan)
func (r *gormPlanRepository) UpdatePlanMetadataAndStructure(ctx context.Context, plan *models.Plan) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdatePlanMetadataAndStructure")
return r.db.WithContext(repoCtx).Transaction(func(tx *gorm.DB) error {
return r.updatePlanMetadataAndStructureTx(repoCtx, tx, plan)
})
}
// updatePlanMetadataAndStructureTx 在事务中协调整个更新过程
func (r *gormPlanRepository) updatePlanMetadataAndStructureTx(tx *gorm.DB, plan *models.Plan) error {
if err := r.validatePlanTree(tx, plan); err != nil {
func (r *gormPlanRepository) updatePlanMetadataAndStructureTx(ctx context.Context, tx *gorm.DB, plan *models.Plan) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "updatePlanMetadataAndStructureTx")
if err := r.validatePlanTree(repoCtx, tx, plan); err != nil {
return err
}
if err := r.reconcilePlanNode(tx, plan); err != nil {
if err := r.reconcilePlanNode(repoCtx, tx, plan); err != nil {
return err
}
// 更新Plan触发器
return r.updatePlanAnalysisTask(tx, plan)
return r.updatePlanAnalysisTask(repoCtx, tx, plan)
}
// validatePlanTree 对整个计划树进行全面的只读健康检查
func (r *gormPlanRepository) validatePlanTree(tx *gorm.DB, plan *models.Plan) error {
func (r *gormPlanRepository) validatePlanTree(ctx context.Context, tx *gorm.DB, plan *models.Plan) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "validatePlanTree")
// 1. 检查根节点
if plan == nil || plan.ID == 0 {
return ErrUpdateWithInvalidRoot
@@ -319,7 +335,7 @@ func (r *gormPlanRepository) validatePlanTree(tx *gorm.DB, plan *models.Plan) er
if len(idsToCheck) > 0 {
var count int64
if err := tx.Model(&models.Plan{}).Where("id IN ?", idsToCheck).Count(&count).Error; err != nil {
if err := tx.WithContext(repoCtx).Model(&models.Plan{}).Where("id IN ?", idsToCheck).Count(&count).Error; err != nil {
return fmt.Errorf("检查计划存在性时出错: %w", err)
}
if int(count) != len(idsToCheck) {
@@ -358,12 +374,13 @@ func validateNodeAndDetectCycles(plan *models.Plan, allIDs, recursionStack map[u
}
// reconcilePlanNode 递归地同步数据库状态以匹配给定的计划节点
func (r *gormPlanRepository) reconcilePlanNode(tx *gorm.DB, plan *models.Plan) error {
func (r *gormPlanRepository) reconcilePlanNode(ctx context.Context, tx *gorm.DB, plan *models.Plan) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "reconcilePlanNode")
if plan == nil {
return nil
}
// 1. 更新节点本身的基础字段
if err := tx.Model(plan).Select("Name", "Description", "ExecutionType", "ExecuteNum", "CronExpression", "ContentType").Updates(plan).Error; err != nil {
if err := tx.WithContext(repoCtx).Model(plan).Select("Name", "Description", "ExecutionType", "ExecuteNum", "CronExpression", "ContentType").Updates(plan).Error; err != nil {
return err
}
@@ -371,26 +388,27 @@ func (r *gormPlanRepository) reconcilePlanNode(tx *gorm.DB, plan *models.Plan) e
switch plan.ContentType {
case models.PlanContentTypeTasks:
// 清理旧的子计划关联
if err := tx.Where("parent_plan_id = ?", plan.ID).Delete(&models.SubPlan{}).Error; err != nil {
if err := tx.WithContext(repoCtx).Where("parent_plan_id = ?", plan.ID).Delete(&models.SubPlan{}).Error; err != nil {
return fmt.Errorf("更新时清理旧的子计划关联失败: %w", err)
}
// 协调任务列表
return r.reconcileTasks(tx, plan)
return r.reconcileTasks(repoCtx, tx, plan)
case models.PlanContentTypeSubPlans:
// 清理旧的任务
if err := tx.Where("plan_id = ?", plan.ID).Delete(&models.Task{}).Error; err != nil {
if err := tx.WithContext(repoCtx).Where("plan_id = ?", plan.ID).Delete(&models.Task{}).Error; err != nil {
return fmt.Errorf("更新时清理旧的任务失败: %w", err)
}
// 协调子计划关联
return r.reconcileSubPlans(tx, plan)
return r.reconcileSubPlans(repoCtx, tx, plan)
}
return nil
}
// reconcileTasks 精确同步任务列表
func (r *gormPlanRepository) reconcileTasks(tx *gorm.DB, plan *models.Plan) error {
func (r *gormPlanRepository) reconcileTasks(ctx context.Context, tx *gorm.DB, plan *models.Plan) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "reconcileTasks")
var existingTasks []models.Task
if err := tx.Where("plan_id = ?", plan.ID).Find(&existingTasks).Error; err != nil {
if err := tx.WithContext(repoCtx).Where("plan_id = ?", plan.ID).Find(&existingTasks).Error; err != nil {
return err
}
@@ -403,12 +421,12 @@ func (r *gormPlanRepository) reconcileTasks(tx *gorm.DB, plan *models.Plan) erro
task := &plan.Tasks[i]
if task.ID == 0 {
task.PlanID = plan.ID
if err := tx.Create(task).Error; err != nil {
if err := tx.WithContext(repoCtx).Create(task).Error; err != nil {
return err
}
} else {
delete(existingTaskMap, task.ID) // 从待删除map中移除
if err := tx.Model(task).Updates(task).Error; err != nil {
if err := tx.WithContext(repoCtx).Model(task).Updates(task).Error; err != nil {
return err
}
}
@@ -420,15 +438,16 @@ func (r *gormPlanRepository) reconcileTasks(tx *gorm.DB, plan *models.Plan) erro
}
if len(tasksToDelete) > 0 {
return r.deleteTasksTx(tx, tasksToDelete)
return r.deleteTasksTx(repoCtx, tx, tasksToDelete)
}
return nil
}
// reconcileSubPlans 精确同步子计划关联并递归
func (r *gormPlanRepository) reconcileSubPlans(tx *gorm.DB, plan *models.Plan) error {
func (r *gormPlanRepository) reconcileSubPlans(ctx context.Context, tx *gorm.DB, plan *models.Plan) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "reconcileSubPlans")
var existingLinks []models.SubPlan
if err := tx.Where("parent_plan_id = ?", plan.ID).Find(&existingLinks).Error; err != nil {
if err := tx.WithContext(repoCtx).Where("parent_plan_id = ?", plan.ID).Find(&existingLinks).Error; err != nil {
return err
}
@@ -441,12 +460,12 @@ func (r *gormPlanRepository) reconcileSubPlans(tx *gorm.DB, plan *models.Plan) e
link := &plan.SubPlans[i]
link.ParentPlanID = plan.ID
if link.ID == 0 {
if err := tx.Create(link).Error; err != nil {
if err := tx.WithContext(repoCtx).Create(link).Error; err != nil {
return err
}
} else {
delete(existingLinkMap, link.ID) // 从待删除map中移除
if err := tx.Model(link).Updates(link).Error; err != nil {
if err := tx.WithContext(repoCtx).Model(link).Updates(link).Error; err != nil {
return err
}
}
@@ -458,7 +477,7 @@ func (r *gormPlanRepository) reconcileSubPlans(tx *gorm.DB, plan *models.Plan) e
}
if len(linksToDelete) > 0 {
if err := tx.Delete(&models.SubPlan{}, linksToDelete).Error; err != nil {
if err := tx.WithContext(repoCtx).Delete(&models.SubPlan{}, linksToDelete).Error; err != nil {
return err
}
}
@@ -466,11 +485,12 @@ func (r *gormPlanRepository) reconcileSubPlans(tx *gorm.DB, plan *models.Plan) e
}
// DeletePlan 根据ID删除计划同时删除其关联的任务非子任务或子计划关联
func (r *gormPlanRepository) DeletePlan(id uint) error {
return r.db.Transaction(func(tx *gorm.DB) error {
func (r *gormPlanRepository) DeletePlan(ctx context.Context, id uint) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "DeletePlan")
return r.db.WithContext(repoCtx).Transaction(func(tx *gorm.DB) error {
// 1. 检查该计划是否是其他计划的子计划
var count int64
if err := tx.Model(&models.SubPlan{}).Where("child_plan_id = ?", id).Count(&count).Error; err != nil {
if err := tx.WithContext(repoCtx).Model(&models.SubPlan{}).Where("child_plan_id = ?", id).Count(&count).Error; err != nil {
return fmt.Errorf("检查计划是否为子计划失败: %w", err)
}
if count > 0 {
@@ -479,7 +499,7 @@ func (r *gormPlanRepository) DeletePlan(id uint) error {
var plan models.Plan
// 2. 获取计划以确定其内容类型
if err := tx.First(&plan, id).Error; err != nil {
if err := tx.WithContext(repoCtx).First(&plan, id).Error; err != nil {
return err
}
@@ -487,18 +507,18 @@ func (r *gormPlanRepository) DeletePlan(id uint) error {
switch plan.ContentType {
case models.PlanContentTypeTasks:
// 删除与此计划关联的所有非子任务
if err := tx.Where("plan_id = ?", id).Delete(&models.Task{}).Error; err != nil {
if err := tx.WithContext(repoCtx).Where("plan_id = ?", id).Delete(&models.Task{}).Error; err != nil {
return fmt.Errorf("删除计划ID %d 的任务失败: %w", id, err)
}
case models.PlanContentTypeSubPlans:
// 删除与此计划关联的所有子计划链接
if err := tx.Where("parent_plan_id = ?", id).Delete(&models.SubPlan{}).Error; err != nil {
if err := tx.WithContext(repoCtx).Where("parent_plan_id = ?", id).Delete(&models.SubPlan{}).Error; err != nil {
return fmt.Errorf("删除计划ID %d 的子计划关联失败: %w", id, err)
}
}
// 4. 删除计划本身
if err := tx.Delete(&models.Plan{}, id).Error; err != nil {
if err := tx.WithContext(repoCtx).Delete(&models.Plan{}, id).Error; err != nil {
return fmt.Errorf("删除计划ID %d 失败: %w", id, err)
}
@@ -507,17 +527,19 @@ func (r *gormPlanRepository) DeletePlan(id uint) error {
}
// FlattenPlanTasks 递归展开计划,返回按执行顺序排列的所有任务列表
func (r *gormPlanRepository) FlattenPlanTasks(planID uint) ([]models.Task, error) {
plan, err := r.GetPlanByID(planID)
func (r *gormPlanRepository) FlattenPlanTasks(ctx context.Context, planID uint) ([]models.Task, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "FlattenPlanTasks")
plan, err := r.GetPlanByID(repoCtx, planID)
if err != nil {
return nil, fmt.Errorf("获取计划(ID: %d)失败: %w", planID, err)
}
return r.flattenPlanTasksRecursive(plan)
return r.flattenPlanTasksRecursive(repoCtx, plan)
}
// flattenPlanTasksRecursive 递归展开计划的内部实现
func (r *gormPlanRepository) flattenPlanTasksRecursive(plan *models.Plan) ([]models.Task, error) {
func (r *gormPlanRepository) flattenPlanTasksRecursive(ctx context.Context, plan *models.Plan) ([]models.Task, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "flattenPlanTasksRecursive")
var tasks []models.Task
switch plan.ContentType {
@@ -535,10 +557,10 @@ func (r *gormPlanRepository) flattenPlanTasksRecursive(plan *models.Plan) ([]mod
// 确保子计划已经被加载
if subPlan.ChildPlan != nil {
subTasks, err = r.flattenPlanTasksRecursive(subPlan.ChildPlan)
subTasks, err = r.flattenPlanTasksRecursive(repoCtx, subPlan.ChildPlan)
} else {
// 如果子计划未加载,则从数据库获取并递归展开
subTasks, err = r.FlattenPlanTasks(subPlan.ChildPlanID)
subTasks, err = r.FlattenPlanTasks(repoCtx, subPlan.ChildPlanID)
}
if err != nil {
@@ -556,22 +578,24 @@ func (r *gormPlanRepository) flattenPlanTasksRecursive(plan *models.Plan) ([]mod
}
// DeleteTask 根据ID删除任务
func (r *gormPlanRepository) DeleteTask(id int) error {
func (r *gormPlanRepository) DeleteTask(ctx context.Context, id int) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "DeleteTask")
// 使用事务确保操作的原子性
return r.db.Transaction(func(tx *gorm.DB) error {
return r.deleteTasksTx(tx, []int{id})
return r.db.WithContext(repoCtx).Transaction(func(tx *gorm.DB) error {
return r.deleteTasksTx(repoCtx, tx, []int{id})
})
}
// deleteTasksTx 在事务中批量软删除任务,并物理删除其在关联表中的记录
func (r *gormPlanRepository) deleteTasksTx(tx *gorm.DB, ids []int) error {
func (r *gormPlanRepository) deleteTasksTx(ctx context.Context, tx *gorm.DB, ids []int) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "deleteTasksTx")
if len(ids) == 0 {
return nil
}
// 检查是否有待执行任务引用了这些任务
var pendingTaskCount int64
if err := tx.Model(&models.PendingTask{}).Where("task_id IN ?", ids).Count(&pendingTaskCount).Error; err != nil {
if err := tx.WithContext(repoCtx).Model(&models.PendingTask{}).Where("task_id IN ?", ids).Count(&pendingTaskCount).Error; err != nil {
return fmt.Errorf("检查待执行任务时出错: %w", err)
}
@@ -584,12 +608,12 @@ func (r *gormPlanRepository) deleteTasksTx(tx *gorm.DB, ids []int) error {
// 1. 直接、高效地从关联表中物理删除所有相关记录
// 这是最关键的优化,避免了不必要的查询和循环
if err := tx.Where("task_id IN ?", ids).Delete(&models.DeviceTask{}).Error; err != nil {
if err := tx.WithContext(repoCtx).Where("task_id IN ?", ids).Delete(&models.DeviceTask{}).Error; err != nil {
return fmt.Errorf("清理任务的设备关联失败: %w", err)
}
// 2. 对任务本身进行软删除
result := tx.Delete(&models.Task{}, ids)
result := tx.WithContext(repoCtx).Delete(&models.Task{}, ids)
if result.Error != nil {
return result.Error
}
@@ -603,13 +627,15 @@ func (r *gormPlanRepository) deleteTasksTx(tx *gorm.DB, ids []int) error {
}
// FindPlanAnalysisTaskByParamsPlanID 根据Parameters中的ParamsPlanID字段值查找TaskPlanAnalysis类型的Task
func (r *gormPlanRepository) FindPlanAnalysisTaskByParamsPlanID(paramsPlanID uint) (*models.Task, error) {
return r.findPlanAnalysisTask(r.db, paramsPlanID)
func (r *gormPlanRepository) FindPlanAnalysisTaskByParamsPlanID(ctx context.Context, paramsPlanID uint) (*models.Task, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindPlanAnalysisTaskByParamsPlanID")
return r.findPlanAnalysisTask(repoCtx, r.db, paramsPlanID)
}
// createPlanAnalysisTask 用于创建一个TaskPlanAnalysis类型的Task
// 关键修改Task.PlanID 设置为 0实际 PlanID 存储在 Parameters 中,并返回创建的 Task
func (r *gormPlanRepository) createPlanAnalysisTask(tx *gorm.DB, plan *models.Plan) (*models.Task, error) {
func (r *gormPlanRepository) createPlanAnalysisTask(ctx context.Context, tx *gorm.DB, plan *models.Plan) (*models.Task, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "createPlanAnalysisTask")
m := map[string]interface{}{
models.ParamsPlanID: plan.ID,
}
@@ -627,22 +653,23 @@ func (r *gormPlanRepository) createPlanAnalysisTask(tx *gorm.DB, plan *models.Pl
Parameters: datatypes.JSON(parameters),
}
if err := tx.Create(task).Error; err != nil {
if err := tx.WithContext(repoCtx).Create(task).Error; err != nil {
return nil, err
}
return task, nil
}
// updatePlanAnalysisTask 使用更安全的方式更新触发器任务
func (r *gormPlanRepository) updatePlanAnalysisTask(tx *gorm.DB, plan *models.Plan) error {
task, err := r.findPlanAnalysisTask(tx, plan.ID)
func (r *gormPlanRepository) updatePlanAnalysisTask(ctx context.Context, tx *gorm.DB, plan *models.Plan) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "updatePlanAnalysisTask")
task, err := r.findPlanAnalysisTask(repoCtx, tx, plan.ID)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return fmt.Errorf("查找现有计划分析任务失败: %w", err)
}
// 如果触发器任务不存在,则创建一个
if task == nil {
_, err := r.createPlanAnalysisTask(tx, plan)
_, err := r.createPlanAnalysisTask(repoCtx, tx, plan)
return err
}
@@ -650,24 +677,26 @@ func (r *gormPlanRepository) updatePlanAnalysisTask(tx *gorm.DB, plan *models.Pl
task.Name = fmt.Sprintf("'%s'计划触发器", plan.Name)
task.Description = fmt.Sprintf("计划名: %s, 计划ID: %d", plan.Name, plan.ID)
return tx.Save(task).Error
return tx.WithContext(repoCtx).Save(task).Error
}
func (r *gormPlanRepository) FindRunnablePlans() ([]*models.Plan, error) {
func (r *gormPlanRepository) FindRunnablePlans(ctx context.Context) ([]*models.Plan, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindRunnablePlans")
var plans []*models.Plan
err := r.db.
err := r.db.WithContext(repoCtx).
Where("status = ?", models.PlanStatusEnabled).
Where(
r.db.Where("execution_type = ?", models.PlanExecutionTypeManual).
r.db.WithContext(repoCtx).Where("execution_type = ?", models.PlanExecutionTypeManual).
Or("execution_type = ? AND (execute_num = 0 OR execute_count < execute_num)", models.PlanExecutionTypeAutomatic),
).
Find(&plans).Error
return plans, err
}
func (r *gormPlanRepository) FindInactivePlans() ([]*models.Plan, error) {
func (r *gormPlanRepository) FindInactivePlans(ctx context.Context) ([]*models.Plan, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindInactivePlans")
var plans []*models.Plan
err := r.db.
err := r.db.WithContext(repoCtx).
Where("status != ?", models.PlanStatusEnabled).
Find(&plans).Error
return plans, err
@@ -675,9 +704,10 @@ func (r *gormPlanRepository) FindInactivePlans() ([]*models.Plan, error) {
// findPlanAnalysisTask 是一个内部使用的、更高效的查找方法
// 关键修改:通过查询 parameters JSON 字段来查找
func (r *gormPlanRepository) findPlanAnalysisTask(tx *gorm.DB, planID uint) (*models.Task, error) {
func (r *gormPlanRepository) findPlanAnalysisTask(ctx context.Context, tx *gorm.DB, planID uint) (*models.Task, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "findPlanAnalysisTask")
var task models.Task
err := tx.Where("type = ? AND parameters->>'plan_id' = ?", models.TaskPlanAnalysis, fmt.Sprintf("%d", planID)).First(&task).Error
err := tx.WithContext(repoCtx).Where("type = ? AND parameters->>'plan_id' = ?", models.TaskPlanAnalysis, fmt.Sprintf("%d", planID)).First(&task).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, nil // 未找到不是错误返回nil, nil
}
@@ -686,28 +716,31 @@ func (r *gormPlanRepository) findPlanAnalysisTask(tx *gorm.DB, planID uint) (*mo
// FindPlanAnalysisTaskByPlanID 是暴露给外部的公共方法
// 关键修改:通过查询 parameters JSON 字段来查找
func (r *gormPlanRepository) FindPlanAnalysisTaskByPlanID(planID uint) (*models.Task, error) {
return r.findPlanAnalysisTask(r.db, planID)
func (r *gormPlanRepository) FindPlanAnalysisTaskByPlanID(ctx context.Context, planID uint) (*models.Task, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindPlanAnalysisTaskByPlanID")
return r.findPlanAnalysisTask(repoCtx, r.db, planID)
}
// CreatePlanAnalysisTask 创建一个 plan_analysis 类型的任务并返回它
// 这个方法是公开的,主要由 TaskManager 在发现触发器任务定义丢失时调用。
func (r *gormPlanRepository) CreatePlanAnalysisTask(plan *models.Plan) (*models.Task, error) {
func (r *gormPlanRepository) CreatePlanAnalysisTask(ctx context.Context, plan *models.Plan) (*models.Task, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "CreatePlanAnalysisTask")
var createdTask *models.Task
err := r.db.Transaction(func(tx *gorm.DB) error {
err := r.db.WithContext(repoCtx).Transaction(func(tx *gorm.DB) error {
var err error
createdTask, err = r.createPlanAnalysisTask(tx, plan)
createdTask, err = r.createPlanAnalysisTask(repoCtx, tx, plan)
return err
})
return createdTask, err
}
// FindPlansWithPendingTasks 查找所有正在执行的计划
func (r *gormPlanRepository) FindPlansWithPendingTasks() ([]*models.Plan, error) {
func (r *gormPlanRepository) FindPlansWithPendingTasks(ctx context.Context) ([]*models.Plan, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindPlansWithPendingTasks")
var plans []*models.Plan
// 关联 pending_tasks, task_execution_logs, tasks 表来查找符合条件的计划
err := r.db.Table("plans").
err := r.db.WithContext(repoCtx).Table("plans").
Joins("JOIN tasks ON plans.id = tasks.plan_id").
Joins("JOIN task_execution_logs ON tasks.id = task_execution_logs.task_id").
Joins("JOIN pending_tasks ON task_execution_logs.id = pending_tasks.task_execution_log_id").
@@ -718,20 +751,21 @@ func (r *gormPlanRepository) FindPlansWithPendingTasks() ([]*models.Plan, error)
}
// StopPlanTransactionally 停止一个计划的执行,包括更新状态、移除待执行任务和更新执行日志。
func (r *gormPlanRepository) StopPlanTransactionally(planID uint) error {
return r.db.Transaction(func(tx *gorm.DB) error {
func (r *gormPlanRepository) StopPlanTransactionally(ctx context.Context, planID uint) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "StopPlanTransactionally")
return r.db.WithContext(repoCtx).Transaction(func(tx *gorm.DB) error {
// 使用事务创建新的仓库实例,确保所有操作都在同一个事务中
planRepoTx := NewGormPlanRepository(tx)
executionLogRepoTx := NewGormExecutionLogRepository(tx)
pendingTaskRepoTx := NewGormPendingTaskRepository(tx)
planRepoTx := NewGormPlanRepository(repoCtx, tx)
executionLogRepoTx := NewGormExecutionLogRepository(repoCtx, tx)
pendingTaskRepoTx := NewGormPendingTaskRepository(repoCtx, tx)
// 1. 更新计划状态为“已停止”
if err := planRepoTx.UpdatePlanStatus(planID, models.PlanStatusDisabled); err != nil {
if err := planRepoTx.UpdatePlanStatus(repoCtx, planID, models.PlanStatusDisabled); err != nil {
return fmt.Errorf("更新计划 #%d 状态为 '已停止' 失败: %w", planID, err)
}
// 2. 查找当前正在进行的计划执行日志
planLog, err := executionLogRepoTx.FindInProgressPlanExecutionLogByPlanID(planID)
planLog, err := executionLogRepoTx.FindInProgressPlanExecutionLogByPlanID(repoCtx, planID)
if err != nil {
return fmt.Errorf("查找计划 #%d 正在进行的执行日志失败: %w", planID, err)
}
@@ -742,7 +776,7 @@ func (r *gormPlanRepository) StopPlanTransactionally(planID uint) error {
}
// 3. 查找所有需要被取消的任务执行日志
taskLogs, err := executionLogRepoTx.FindIncompleteTaskExecutionLogsByPlanLogID(planLog.ID)
taskLogs, err := executionLogRepoTx.FindIncompleteTaskExecutionLogsByPlanLogID(repoCtx, planLog.ID)
if err != nil {
return fmt.Errorf("查找计划执行日志 #%d 下未完成的任务日志失败: %w", planLog.ID, err)
}
@@ -754,12 +788,12 @@ func (r *gormPlanRepository) StopPlanTransactionally(planID uint) error {
}
// 3.1 批量更新任务执行日志状态为“已取消”
if err := executionLogRepoTx.UpdateTaskExecutionLogStatusByIDs(taskLogIDs, models.ExecutionStatusCancelled); err != nil {
if err := executionLogRepoTx.UpdateTaskExecutionLogStatusByIDs(repoCtx, taskLogIDs, models.ExecutionStatusCancelled); err != nil {
return fmt.Errorf("批量更新任务执行日志状态为 '已取消' 失败: %w", err)
}
// 3.2 查找并删除待执行队列中对应的任务
pendingTasks, err := pendingTaskRepoTx.FindPendingTasksByTaskLogIDs(taskLogIDs)
pendingTasks, err := pendingTaskRepoTx.FindPendingTasksByTaskLogIDs(repoCtx, taskLogIDs)
if err != nil {
return fmt.Errorf("查找计划执行日志 #%d 下对应的待执行任务失败: %w", planLog.ID, err)
}
@@ -769,14 +803,14 @@ func (r *gormPlanRepository) StopPlanTransactionally(planID uint) error {
for _, pt := range pendingTasks {
pendingTaskIDs = append(pendingTaskIDs, pt.ID)
}
if err := pendingTaskRepoTx.DeletePendingTasksByIDs(pendingTaskIDs); err != nil {
if err := pendingTaskRepoTx.DeletePendingTasksByIDs(repoCtx, pendingTaskIDs); err != nil {
return fmt.Errorf("批量删除待执行任务失败: %w", err)
}
}
}
// 4. 更新计划执行历史的总状态为“失败”
if err := executionLogRepoTx.UpdatePlanExecutionLogStatus(planLog.ID, models.ExecutionStatusFailed); err != nil {
if err := executionLogRepoTx.UpdatePlanExecutionLogStatus(repoCtx, planLog.ID, models.ExecutionStatusFailed); err != nil {
return fmt.Errorf("更新计划执行日志 #%d 状态为 '失败' 失败: %w", planLog.ID, err)
}
@@ -785,8 +819,9 @@ func (r *gormPlanRepository) StopPlanTransactionally(planID uint) error {
}
// UpdatePlanStatus 更新指定计划的状态
func (r *gormPlanRepository) UpdatePlanStatus(id uint, status models.PlanStatus) error {
result := r.db.Model(&models.Plan{}).Where("id = ?", id).Update("status", status)
func (r *gormPlanRepository) UpdatePlanStatus(ctx context.Context, id uint, status models.PlanStatus) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdatePlanStatus")
result := r.db.WithContext(repoCtx).Model(&models.Plan{}).Where("id = ?", id).Update("status", status)
if result.Error != nil {
return result.Error
}
@@ -796,16 +831,18 @@ func (r *gormPlanRepository) UpdatePlanStatus(id uint, status models.PlanStatus)
return nil
}
func (r *gormPlanRepository) UpdatePlanStateAfterExecution(planID uint, newCount uint, newStatus models.PlanStatus) error {
return r.db.Model(&models.Plan{}).Where("id = ?", planID).Updates(map[string]interface{}{
func (r *gormPlanRepository) UpdatePlanStateAfterExecution(ctx context.Context, planID uint, newCount uint, newStatus models.PlanStatus) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdatePlanStateAfterExecution")
return r.db.WithContext(repoCtx).Model(&models.Plan{}).Where("id = ?", planID).Updates(map[string]interface{}{
"execute_count": newCount,
"status": newStatus,
}).Error
}
// UpdateExecuteCount 更新指定计划的执行计数
func (r *gormPlanRepository) UpdateExecuteCount(id uint, count uint) error {
result := r.db.Model(&models.Plan{}).Where("id = ?", id).Update("execute_count", count)
func (r *gormPlanRepository) UpdateExecuteCount(ctx context.Context, id uint, count uint) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "UpdateExecuteCount")
result := r.db.WithContext(repoCtx).Model(&models.Plan{}).Where("id = ?", id).Update("execute_count", count)
if result.Error != nil {
return result.Error
}

View File

@@ -1,9 +1,12 @@
package repository
import (
"context"
"time"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"gorm.io/gorm"
)
@@ -38,23 +41,25 @@ type FeedUsageRecordListOptions struct {
// RawMaterialRepository 定义了与原料相关的数据库操作接口
type RawMaterialRepository interface {
ListRawMaterialPurchases(opts RawMaterialPurchaseListOptions, page, pageSize int) ([]models.RawMaterialPurchase, int64, error)
ListRawMaterialStockLogs(opts RawMaterialStockLogListOptions, page, pageSize int) ([]models.RawMaterialStockLog, int64, error)
ListFeedUsageRecords(opts FeedUsageRecordListOptions, page, pageSize int) ([]models.FeedUsageRecord, int64, error)
ListRawMaterialPurchases(ctx context.Context, opts RawMaterialPurchaseListOptions, page, pageSize int) ([]models.RawMaterialPurchase, int64, error)
ListRawMaterialStockLogs(ctx context.Context, opts RawMaterialStockLogListOptions, page, pageSize int) ([]models.RawMaterialStockLog, int64, error)
ListFeedUsageRecords(ctx context.Context, opts FeedUsageRecordListOptions, page, pageSize int) ([]models.FeedUsageRecord, int64, error)
}
// gormRawMaterialRepository 是 RawMaterialRepository 的 GORM 实现
type gormRawMaterialRepository struct {
db *gorm.DB
ctx context.Context
db *gorm.DB
}
// NewGormRawMaterialRepository 创建一个新的 RawMaterialRepository GORM 实现实例
func NewGormRawMaterialRepository(db *gorm.DB) RawMaterialRepository {
return &gormRawMaterialRepository{db: db}
func NewGormRawMaterialRepository(ctx context.Context, db *gorm.DB) RawMaterialRepository {
return &gormRawMaterialRepository{ctx: ctx, db: db}
}
// ListRawMaterialPurchases 实现了分页和过滤查询原料采购记录的功能
func (r *gormRawMaterialRepository) ListRawMaterialPurchases(opts RawMaterialPurchaseListOptions, page, pageSize int) ([]models.RawMaterialPurchase, int64, error) {
func (r *gormRawMaterialRepository) ListRawMaterialPurchases(ctx context.Context, opts RawMaterialPurchaseListOptions, page, pageSize int) ([]models.RawMaterialPurchase, int64, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "ListRawMaterialPurchases")
if page <= 0 || pageSize <= 0 {
return nil, 0, ErrInvalidPagination
}
@@ -62,7 +67,7 @@ func (r *gormRawMaterialRepository) ListRawMaterialPurchases(opts RawMaterialPur
var results []models.RawMaterialPurchase
var total int64
query := r.db.Model(&models.RawMaterialPurchase{})
query := r.db.WithContext(repoCtx).Model(&models.RawMaterialPurchase{})
if opts.RawMaterialID != nil {
query = query.Where("raw_material_id = ?", *opts.RawMaterialID)
@@ -94,7 +99,8 @@ func (r *gormRawMaterialRepository) ListRawMaterialPurchases(opts RawMaterialPur
}
// ListRawMaterialStockLogs 实现了分页和过滤查询原料库存日志的功能
func (r *gormRawMaterialRepository) ListRawMaterialStockLogs(opts RawMaterialStockLogListOptions, page, pageSize int) ([]models.RawMaterialStockLog, int64, error) {
func (r *gormRawMaterialRepository) ListRawMaterialStockLogs(ctx context.Context, opts RawMaterialStockLogListOptions, page, pageSize int) ([]models.RawMaterialStockLog, int64, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "ListRawMaterialStockLogs")
if page <= 0 || pageSize <= 0 {
return nil, 0, ErrInvalidPagination
}
@@ -102,7 +108,7 @@ func (r *gormRawMaterialRepository) ListRawMaterialStockLogs(opts RawMaterialSto
var results []models.RawMaterialStockLog
var total int64
query := r.db.Model(&models.RawMaterialStockLog{})
query := r.db.WithContext(repoCtx).Model(&models.RawMaterialStockLog{})
if opts.RawMaterialID != nil {
query = query.Where("raw_material_id = ?", *opts.RawMaterialID)
@@ -137,7 +143,8 @@ func (r *gormRawMaterialRepository) ListRawMaterialStockLogs(opts RawMaterialSto
}
// ListFeedUsageRecords 实现了分页和过滤查询饲料使用记录的功能
func (r *gormRawMaterialRepository) ListFeedUsageRecords(opts FeedUsageRecordListOptions, page, pageSize int) ([]models.FeedUsageRecord, int64, error) {
func (r *gormRawMaterialRepository) ListFeedUsageRecords(ctx context.Context, opts FeedUsageRecordListOptions, page, pageSize int) ([]models.FeedUsageRecord, int64, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "ListFeedUsageRecords")
if page <= 0 || pageSize <= 0 {
return nil, 0, ErrInvalidPagination
}
@@ -145,7 +152,7 @@ func (r *gormRawMaterialRepository) ListFeedUsageRecords(opts FeedUsageRecordLis
var results []models.FeedUsageRecord
var total int64
query := r.db.Model(&models.FeedUsageRecord{})
query := r.db.WithContext(repoCtx).Model(&models.FeedUsageRecord{})
if opts.PenID != nil {
query = query.Where("pen_id = ?", *opts.PenID)

View File

@@ -1,9 +1,12 @@
package repository
import (
"context"
"time"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"gorm.io/gorm"
)
@@ -18,39 +21,43 @@ type SensorDataListOptions struct {
// SensorDataRepository 定义了与传感器数据相关的数据库操作接口。
type SensorDataRepository interface {
Create(sensorData *models.SensorData) error
GetLatestSensorDataByDeviceIDAndSensorType(deviceID uint, sensorType models.SensorType) (*models.SensorData, error)
Create(ctx context.Context, sensorData *models.SensorData) error
GetLatestSensorDataByDeviceIDAndSensorType(ctx context.Context, deviceID uint, sensorType models.SensorType) (*models.SensorData, error)
// List 支持分页和过滤的列表查询
List(opts SensorDataListOptions, page, pageSize int) ([]models.SensorData, int64, error)
List(ctx context.Context, opts SensorDataListOptions, page, pageSize int) ([]models.SensorData, int64, error)
}
// gormSensorDataRepository 是 SensorDataRepository 的 GORM 实现。
type gormSensorDataRepository struct {
db *gorm.DB
ctx context.Context
db *gorm.DB
}
// NewGormSensorDataRepository 创建一个新的 SensorDataRepository GORM 实现实例。
func NewGormSensorDataRepository(db *gorm.DB) SensorDataRepository {
return &gormSensorDataRepository{db: db}
func NewGormSensorDataRepository(ctx context.Context, db *gorm.DB) SensorDataRepository {
return &gormSensorDataRepository{ctx: ctx, db: db}
}
// Create 将一条新的传感器数据记录插入数据库。
func (r *gormSensorDataRepository) Create(sensorData *models.SensorData) error {
return r.db.Create(sensorData).Error
func (r *gormSensorDataRepository) Create(ctx context.Context, sensorData *models.SensorData) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "Create")
return r.db.WithContext(repoCtx).Create(sensorData).Error
}
// GetLatestSensorDataByDeviceIDAndSensorType 根据设备ID和传感器类型查询最新的传感器数据。
func (r *gormSensorDataRepository) GetLatestSensorDataByDeviceIDAndSensorType(deviceID uint, sensorType models.SensorType) (*models.SensorData, error) {
func (r *gormSensorDataRepository) GetLatestSensorDataByDeviceIDAndSensorType(ctx context.Context, deviceID uint, sensorType models.SensorType) (*models.SensorData, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "GetLatestSensorDataByDeviceIDAndSensorType")
var sensorData models.SensorData
// 增加一个时间范围来缩小查询范围, 从而加快查找速度, 当使用时序数据库时时间范围可以让数据库忽略时间靠前的分片
err := r.db.Where("device_id = ? AND sensor_type = ? AND time >=?", deviceID, sensorType, time.Now().Add(-24*time.Hour)).
err := r.db.WithContext(repoCtx).Where("device_id = ? AND sensor_type = ? AND time >=?", deviceID, sensorType, time.Now().Add(-24*time.Hour)).
Order("time DESC").
First(&sensorData).Error
return &sensorData, err
}
// List 实现了分页和过滤查询传感器数据的功能
func (r *gormSensorDataRepository) List(opts SensorDataListOptions, page, pageSize int) ([]models.SensorData, int64, error) {
func (r *gormSensorDataRepository) List(ctx context.Context, opts SensorDataListOptions, page, pageSize int) ([]models.SensorData, int64, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "List")
// --- 校验分页参数 ---
if page <= 0 || pageSize <= 0 {
return nil, 0, ErrInvalidPagination
@@ -59,7 +66,7 @@ func (r *gormSensorDataRepository) List(opts SensorDataListOptions, page, pageSi
var results []models.SensorData
var total int64
query := r.db.Model(&models.SensorData{})
query := r.db.WithContext(repoCtx).Model(&models.SensorData{})
// --- 应用过滤条件 ---
if opts.DeviceID != nil {

View File

@@ -1,9 +1,11 @@
package repository
import (
"context"
"fmt"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"gorm.io/gorm"
)
@@ -12,35 +14,40 @@ type UnitOfWork interface {
// ExecuteInTransaction 在一个数据库事务中执行给定的函数。
// 如果函数返回错误,事务将被回滚;否则,事务将被提交。
// tx 参数是当前事务的 GORM DB 实例,应传递给所有仓库方法。
ExecuteInTransaction(fn func(tx *gorm.DB) error) error
ExecuteInTransaction(ctx context.Context, fn func(tx *gorm.DB) error) error
}
// gormUnitOfWork 是 UnitOfWork 接口的 GORM 实现
type gormUnitOfWork struct {
db *gorm.DB
logger *logs.Logger // 添加日志记录器
ctx context.Context
db *gorm.DB
}
// NewGormUnitOfWork 创建一个新的 gormUnitOfWork 实例
func NewGormUnitOfWork(db *gorm.DB, logger *logs.Logger) UnitOfWork {
return &gormUnitOfWork{db: db, logger: logger}
func NewGormUnitOfWork(ctx context.Context, db *gorm.DB) UnitOfWork {
return &gormUnitOfWork{
ctx: ctx,
db: db,
}
}
// ExecuteInTransaction 实现了 UnitOfWork 接口的事务执行逻辑
func (u *gormUnitOfWork) ExecuteInTransaction(fn func(tx *gorm.DB) error) error {
tx := u.db.Begin()
func (u *gormUnitOfWork) ExecuteInTransaction(ctx context.Context, fn func(tx *gorm.DB) error) error {
uowCtx, logger := logs.Trace(ctx, u.ctx, "ExecuteInTransaction")
tx := u.db.WithContext(uowCtx).Begin()
if tx.Error != nil {
u.logger.Errorf("开启数据库事务失败: %v", tx.Error) // 记录错误日志
logger.Errorf("开启数据库事务失败: %v", tx.Error) // 记录错误日志
return fmt.Errorf("开启事务失败: %w", tx.Error)
}
defer func() {
if r := recover(); r != nil {
tx.Rollback()
u.logger.Errorf("事务中发生 panic已回滚: %v", r) // 记录 panic 日志
logger.Errorf("事务中发生 panic已回滚: %v", r) // 记录 panic 日志
} else if tx.Error != nil { // 如果函数执行过程中返回错误,或者事务本身有错误,则回滚
tx.Rollback()
u.logger.Errorf("事务执行失败,已回滚: %v", tx.Error) // 记录错误日志
logger.Errorf("事务执行失败,已回滚: %v", tx.Error) // 记录错误日志
}
}()
@@ -52,7 +59,7 @@ func (u *gormUnitOfWork) ExecuteInTransaction(fn func(tx *gorm.DB) error) error
// 提交事务
if err := tx.Commit().Error; err != nil {
u.logger.Errorf("提交数据库事务失败: %v", err) // 记录错误日志
logger.Errorf("提交数据库事务失败: %v", err) // 记录错误日志
return fmt.Errorf("提交事务失败: %w", err)
}

View File

@@ -1,9 +1,12 @@
package repository
import (
"context"
"time"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"gorm.io/gorm"
)
@@ -20,27 +23,30 @@ type UserActionLogListOptions struct {
// UserActionLogRepository 定义了与用户操作日志相关的数据库操作接口
type UserActionLogRepository interface {
Create(log *models.UserActionLog) error
List(opts UserActionLogListOptions, page, pageSize int) ([]models.UserActionLog, int64, error)
Create(ctx context.Context, log *models.UserActionLog) error
List(ctx context.Context, opts UserActionLogListOptions, page, pageSize int) ([]models.UserActionLog, int64, error)
}
// gormUserActionLogRepository 是 UserActionLogRepository 的 GORM 实现
type gormUserActionLogRepository struct {
db *gorm.DB
ctx context.Context
db *gorm.DB
}
// NewGormUserActionLogRepository 创建一个新的 UserActionLogRepository GORM 实现实例
func NewGormUserActionLogRepository(db *gorm.DB) UserActionLogRepository {
return &gormUserActionLogRepository{db: db}
func NewGormUserActionLogRepository(ctx context.Context, db *gorm.DB) UserActionLogRepository {
return &gormUserActionLogRepository{ctx: ctx, db: db}
}
// Create 创建一条新的用户操作日志记录
func (r *gormUserActionLogRepository) Create(log *models.UserActionLog) error {
return r.db.Create(log).Error
func (r *gormUserActionLogRepository) Create(ctx context.Context, log *models.UserActionLog) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "Create")
return r.db.WithContext(repoCtx).Create(log).Error
}
// List 根据选项查询用户操作日志,并返回总数
func (r *gormUserActionLogRepository) List(opts UserActionLogListOptions, page, pageSize int) ([]models.UserActionLog, int64, error) {
func (r *gormUserActionLogRepository) List(ctx context.Context, opts UserActionLogListOptions, page, pageSize int) ([]models.UserActionLog, int64, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "List")
if page <= 0 || pageSize <= 0 {
return nil, 0, ErrInvalidPagination
}
@@ -48,7 +54,7 @@ func (r *gormUserActionLogRepository) List(opts UserActionLogListOptions, page,
var logs []models.UserActionLog
var total int64
query := r.db.Model(&models.UserActionLog{})
query := r.db.WithContext(repoCtx).Model(&models.UserActionLog{})
if opts.UserID != nil {
query = query.Where("user_id = ?", *opts.UserID)

View File

@@ -2,40 +2,47 @@
package repository
import (
"context"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/logs"
"git.huangwc.com/pig/pig-farm-controller/internal/infra/models"
"gorm.io/gorm"
)
// UserRepository 定义了与用户模型相关的数据库操作接口
// 这是为了让业务逻辑层依赖于抽象,而不是具体的数据库实现
type UserRepository interface {
Create(user *models.User) error
FindByUsername(username string) (*models.User, error)
FindByID(id uint) (*models.User, error)
FindUserForLogin(identifier string) (*models.User, error)
FindAll() ([]*models.User, error)
Create(ctx context.Context, user *models.User) error
FindByUsername(ctx context.Context, username string) (*models.User, error)
FindByID(ctx context.Context, id uint) (*models.User, error)
FindUserForLogin(ctx context.Context, identifier string) (*models.User, error)
FindAll(ctx context.Context) ([]*models.User, error)
}
// gormUserRepository 是 UserRepository 的 GORM 实现
type gormUserRepository struct {
db *gorm.DB
ctx context.Context
db *gorm.DB
}
// NewGormUserRepository 创建一个新的 UserRepository GORM 实现实例
func NewGormUserRepository(db *gorm.DB) UserRepository {
return &gormUserRepository{db: db}
func NewGormUserRepository(ctx context.Context, db *gorm.DB) UserRepository {
return &gormUserRepository{ctx: ctx, db: db}
}
// Create 创建一个新的用户记录
func (r *gormUserRepository) Create(user *models.User) error {
func (r *gormUserRepository) Create(ctx context.Context, user *models.User) error {
repoCtx := logs.AddFuncName(ctx, r.ctx, "Create")
// BeforeSave 钩子会在这里被自动触发
return r.db.Create(user).Error
return r.db.WithContext(repoCtx).Create(user).Error
}
// FindByUsername 根据用户名查找用户
func (r *gormUserRepository) FindByUsername(username string) (*models.User, error) {
func (r *gormUserRepository) FindByUsername(ctx context.Context, username string) (*models.User, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByUsername")
var user models.User
if err := r.db.Where("username = ?", username).First(&user).Error; err != nil {
if err := r.db.WithContext(repoCtx).Where("username = ?", username).First(&user).Error; err != nil {
return nil, err
}
return &user, nil
@@ -43,10 +50,11 @@ func (r *gormUserRepository) FindByUsername(username string) (*models.User, erro
// FindUserForLogin 根据提供的标识符查找用户,可用于登录验证
// 标识符可以是用户名、邮箱、手机号、微信号或飞书账号
func (r *gormUserRepository) FindUserForLogin(identifier string) (*models.User, error) {
func (r *gormUserRepository) FindUserForLogin(ctx context.Context, identifier string) (*models.User, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindUserForLogin")
var user models.User
// 使用 ->> 操作符来查询 JSONB 字段中的文本值
err := r.db.Where(
err := r.db.WithContext(repoCtx).Where(
"username = ? OR contact ->> 'email' = ? OR contact ->> 'phone' = ? OR contact ->> 'wechat' = ? OR contact ->> 'feishu' = ?",
identifier, identifier, identifier, identifier, identifier,
).First(&user).Error
@@ -58,18 +66,20 @@ func (r *gormUserRepository) FindUserForLogin(identifier string) (*models.User,
}
// FindByID 根据 ID 查找用户
func (r *gormUserRepository) FindByID(id uint) (*models.User, error) {
func (r *gormUserRepository) FindByID(ctx context.Context, id uint) (*models.User, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindByID")
var user models.User
if err := r.db.First(&user, id).Error; err != nil {
if err := r.db.WithContext(repoCtx).First(&user, id).Error; err != nil {
return nil, err
}
return &user, nil
}
// FindAll 返回数据库中的所有用户
func (r *gormUserRepository) FindAll() ([]*models.User, error) {
func (r *gormUserRepository) FindAll(ctx context.Context) ([]*models.User, error) {
repoCtx := logs.AddFuncName(ctx, r.ctx, "FindAll")
var users []*models.User
if err := r.db.Where("1 = 1").Find(&users).Error; err != nil {
if err := r.db.WithContext(repoCtx).Where("1 = 1").Find(&users).Error; err != nil {
return nil, err
}
return users, nil

View File

@@ -21,6 +21,15 @@ design/archive/2025-11-3-verification-before-device-deletion/refactor_deletion_c
design/archive/2025-11-3-verification-before-device-deletion/refactor_id_conversion.md
design/provide-logger-with-mothed/implementation.md
design/provide-logger-with-mothed/index.md
design/provide-logger-with-mothed/task-api.md
design/provide-logger-with-mothed/task-controller.md
design/provide-logger-with-mothed/task-domain.md
design/provide-logger-with-mothed/task-infra.md
design/provide-logger-with-mothed/task-list.md
design/provide-logger-with-mothed/task-middleware.md
design/provide-logger-with-mothed/task-repository.md
design/provide-logger-with-mothed/task-service.md
design/provide-logger-with-mothed/task-webhook.md
docs/docs.go
docs/swagger.json
docs/swagger.yaml
@@ -53,6 +62,7 @@ internal/app/dto/plan_dto.go
internal/app/dto/user_dto.go
internal/app/middleware/audit.go
internal/app/middleware/auth.go
internal/app/service/audit_service.go
internal/app/service/device_service.go
internal/app/service/monitor_service.go
internal/app/service/pig_batch_service.go
@@ -67,7 +77,6 @@ internal/app/webhook/transport.go
internal/core/application.go
internal/core/component_initializers.go
internal/core/data_initializer.go
internal/domain/audit/service.go
internal/domain/device/device_service.go
internal/domain/device/general_device_service.go
internal/domain/notify/notify.go
@@ -80,7 +89,6 @@ internal/domain/pig/pig_batch_service_pig_trade.go
internal/domain/pig/pig_sick_manager.go
internal/domain/pig/pig_trade_manager.go
internal/domain/plan/analysis_plan_task_manager.go
internal/domain/plan/device_id_extractor.go
internal/domain/plan/plan_execution_manager.go
internal/domain/plan/plan_service.go
internal/domain/plan/task.go
@@ -88,10 +96,11 @@ internal/domain/task/delay_task.go
internal/domain/task/full_collection_task.go
internal/domain/task/release_feed_weight_task.go
internal/domain/task/task.go
internal/domain/token/token_service.go
internal/infra/config/config.go
internal/infra/database/postgres.go
internal/infra/database/storage.go
internal/infra/logs/context.go
internal/infra/logs/encoder.go
internal/infra/logs/logs.go
internal/infra/models/device.go
internal/infra/models/device_template.go
@@ -145,6 +154,7 @@ internal/infra/transport/proto/device.proto
internal/infra/transport/transport.go
internal/infra/utils/command_generater/modbus_rtu.go
internal/infra/utils/time.go
internal/infra/utils/token/token_service.go
internal/infra/utils/validation.go
main.go
openspec/AGENTS.md