Files
smartmate/backend/dao/notification_channel.go
Losita 0a014f7472 Version: 0.9.60.dev.260430
后端:
1.接入主动调度 worker 与飞书通知链路
- 新增 due job scanner 与 active_schedule.triggered workflow
- 接入 notification.feishu.requested handler、飞书 webhook provider 和用户通知配置接口
- 支持 notification_records 去重、重试、skipped/dead 状态流转
- 完成 api / worker / all 启动模式装配与主动调度验收记录
2.后续要做的就是补全从异常发生到给用户推送消息之间的逻辑缺口
2026-04-30 23:45:27 +08:00

128 lines
4.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package dao
import (
"context"
"errors"
"time"
"github.com/LoveLosita/smartflow/backend/model"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
// NotificationChannelDAO 管理用户外部通知通道配置。
//
// 职责边界:
// 1. 只负责 user_notification_channels 的基础读写;
// 2. 不负责 webhook 请求发送、notification_records 状态机或 outbox 消费;
// 3. webhook_url / bearer_token 的脱敏由 API/service 层处理DAO 保持真实持久化值。
type NotificationChannelDAO struct {
db *gorm.DB
}
func NewNotificationChannelDAO(db *gorm.DB) *NotificationChannelDAO {
return &NotificationChannelDAO{db: db}
}
func (d *NotificationChannelDAO) WithTx(tx *gorm.DB) *NotificationChannelDAO {
return &NotificationChannelDAO{db: tx}
}
func (d *NotificationChannelDAO) ensureDB() error {
if d == nil || d.db == nil {
return errors.New("notification channel dao 未初始化")
}
return nil
}
// UpsertUserNotificationChannel 按 user_id + channel 幂等保存用户通知配置。
//
// 说明:
// 1. 只覆盖开关、webhook、鉴权配置和 updated_at
// 2. 不清空 last_test_*,避免用户保存配置后丢掉最近一次测试结果;
// 3. channel.ID 由数据库自增,调用方不应依赖传入 ID。
func (d *NotificationChannelDAO) UpsertUserNotificationChannel(ctx context.Context, channel *model.UserNotificationChannel) error {
if err := d.ensureDB(); err != nil {
return err
}
if channel == nil || channel.UserID <= 0 || channel.Channel == "" {
return errors.New("notification channel 必须包含 user_id 和 channel")
}
now := time.Now()
values := map[string]any{
"user_id": channel.UserID,
"channel": channel.Channel,
"enabled": channel.Enabled,
"webhook_url": channel.WebhookURL,
"auth_type": channel.AuthType,
"bearer_token": channel.BearerToken,
"created_at": now,
"updated_at": now,
}
return d.db.WithContext(ctx).
Model(&model.UserNotificationChannel{}).
Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "user_id"}, {Name: "channel"}},
DoUpdates: clause.Assignments(map[string]any{
"enabled": channel.Enabled,
"webhook_url": channel.WebhookURL,
"auth_type": channel.AuthType,
"bearer_token": channel.BearerToken,
"updated_at": now,
}),
}).
Create(values).Error
}
// GetUserNotificationChannel 查询用户指定通知通道配置。
func (d *NotificationChannelDAO) GetUserNotificationChannel(ctx context.Context, userID int, channel string) (*model.UserNotificationChannel, error) {
if err := d.ensureDB(); err != nil {
return nil, err
}
if userID <= 0 || channel == "" {
return nil, gorm.ErrRecordNotFound
}
var row model.UserNotificationChannel
err := d.db.WithContext(ctx).
Where("user_id = ? AND channel = ?", userID, channel).
First(&row).Error
if err != nil {
return nil, err
}
return &row, nil
}
// DeleteUserNotificationChannel 删除用户指定通知通道配置。
//
// 说明:当前表不保留软删除列;删除后再次保存会重新创建配置。
func (d *NotificationChannelDAO) DeleteUserNotificationChannel(ctx context.Context, userID int, channel string) error {
if err := d.ensureDB(); err != nil {
return err
}
if userID <= 0 || channel == "" {
return nil
}
return d.db.WithContext(ctx).
Where("user_id = ? AND channel = ?", userID, channel).
Delete(&model.UserNotificationChannel{}).Error
}
// UpdateUserNotificationChannelTestResult 回写用户 webhook 测试结果。
func (d *NotificationChannelDAO) UpdateUserNotificationChannelTestResult(ctx context.Context, userID int, channel string, status string, testErr string, testedAt time.Time) error {
if err := d.ensureDB(); err != nil {
return err
}
if userID <= 0 || channel == "" {
return errors.New("user_id 和 channel 不能为空")
}
updates := map[string]any{
"last_test_status": status,
"last_test_error": testErr,
"last_test_at": &testedAt,
}
return d.db.WithContext(ctx).
Model(&model.UserNotificationChannel{}).
Where("user_id = ? AND channel = ?", userID, channel).
Updates(updates).Error
}