mirror of
https://github.com/Wei-Shaw/sub2api.git
synced 2026-09-21 14:19:18 +08:00
feat(渠道监控): 检测间隔支持正负随机抖动配置
新增 jitter_seconds 配置:每轮调度在 interval 基础上 ± [0, jitter] 均匀随机偏移触发,避免多个监控以固定节奏同步请求上游。 - ent schema 新增 jitter_seconds 字段(默认 0),附迁移 151 - 校验:jitter >= 0 且 interval - jitter >= 15s(创建/更新均校验) - runner 由固定 ticker 改为每轮重新随机化的 timer,0 抖动时行为不变 - 前端监控表单新增「随机抖动 (± 秒)」输入框,上限随间隔联动 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -43,6 +43,8 @@ type ChannelMonitor struct {
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
// IntervalSeconds holds the value of the "interval_seconds" field.
|
||||
IntervalSeconds int `json:"interval_seconds,omitempty"`
|
||||
// 每次调度在 interval 基础上 ± [0, jitter] 的均匀随机偏移(秒);0 表示固定间隔。service 层另保证 interval - jitter >= 15
|
||||
JitterSeconds int `json:"jitter_seconds,omitempty"`
|
||||
// LastCheckedAt holds the value of the "last_checked_at" field.
|
||||
LastCheckedAt *time.Time `json:"last_checked_at,omitempty"`
|
||||
// CreatedBy holds the value of the "created_by" field.
|
||||
@@ -112,7 +114,7 @@ func (*ChannelMonitor) scanValues(columns []string) ([]any, error) {
|
||||
values[i] = new([]byte)
|
||||
case channelmonitor.FieldEnabled:
|
||||
values[i] = new(sql.NullBool)
|
||||
case channelmonitor.FieldID, channelmonitor.FieldIntervalSeconds, channelmonitor.FieldCreatedBy, channelmonitor.FieldTemplateID:
|
||||
case channelmonitor.FieldID, channelmonitor.FieldIntervalSeconds, channelmonitor.FieldJitterSeconds, channelmonitor.FieldCreatedBy, channelmonitor.FieldTemplateID:
|
||||
values[i] = new(sql.NullInt64)
|
||||
case channelmonitor.FieldName, channelmonitor.FieldProvider, channelmonitor.FieldAPIMode, channelmonitor.FieldEndpoint, channelmonitor.FieldAPIKeyEncrypted, channelmonitor.FieldPrimaryModel, channelmonitor.FieldGroupName, channelmonitor.FieldBodyOverrideMode:
|
||||
values[i] = new(sql.NullString)
|
||||
@@ -213,6 +215,12 @@ func (_m *ChannelMonitor) assignValues(columns []string, values []any) error {
|
||||
} else if value.Valid {
|
||||
_m.IntervalSeconds = int(value.Int64)
|
||||
}
|
||||
case channelmonitor.FieldJitterSeconds:
|
||||
if value, ok := values[i].(*sql.NullInt64); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field jitter_seconds", values[i])
|
||||
} else if value.Valid {
|
||||
_m.JitterSeconds = int(value.Int64)
|
||||
}
|
||||
case channelmonitor.FieldLastCheckedAt:
|
||||
if value, ok := values[i].(*sql.NullTime); !ok {
|
||||
return fmt.Errorf("unexpected type %T for field last_checked_at", values[i])
|
||||
@@ -341,6 +349,9 @@ func (_m *ChannelMonitor) String() string {
|
||||
builder.WriteString("interval_seconds=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.IntervalSeconds))
|
||||
builder.WriteString(", ")
|
||||
builder.WriteString("jitter_seconds=")
|
||||
builder.WriteString(fmt.Sprintf("%v", _m.JitterSeconds))
|
||||
builder.WriteString(", ")
|
||||
if v := _m.LastCheckedAt; v != nil {
|
||||
builder.WriteString("last_checked_at=")
|
||||
builder.WriteString(v.Format(time.ANSIC))
|
||||
|
||||
@@ -39,6 +39,8 @@ const (
|
||||
FieldEnabled = "enabled"
|
||||
// FieldIntervalSeconds holds the string denoting the interval_seconds field in the database.
|
||||
FieldIntervalSeconds = "interval_seconds"
|
||||
// FieldJitterSeconds holds the string denoting the jitter_seconds field in the database.
|
||||
FieldJitterSeconds = "jitter_seconds"
|
||||
// FieldLastCheckedAt holds the string denoting the last_checked_at field in the database.
|
||||
FieldLastCheckedAt = "last_checked_at"
|
||||
// FieldCreatedBy holds the string denoting the created_by field in the database.
|
||||
@@ -97,6 +99,7 @@ var Columns = []string{
|
||||
FieldGroupName,
|
||||
FieldEnabled,
|
||||
FieldIntervalSeconds,
|
||||
FieldJitterSeconds,
|
||||
FieldLastCheckedAt,
|
||||
FieldCreatedBy,
|
||||
FieldTemplateID,
|
||||
@@ -144,6 +147,10 @@ var (
|
||||
DefaultEnabled bool
|
||||
// IntervalSecondsValidator is a validator for the "interval_seconds" field. It is called by the builders before save.
|
||||
IntervalSecondsValidator func(int) error
|
||||
// DefaultJitterSeconds holds the default value on creation for the "jitter_seconds" field.
|
||||
DefaultJitterSeconds int
|
||||
// JitterSecondsValidator is a validator for the "jitter_seconds" field. It is called by the builders before save.
|
||||
JitterSecondsValidator func(int) error
|
||||
// DefaultExtraHeaders holds the default value on creation for the "extra_headers" field.
|
||||
DefaultExtraHeaders map[string]string
|
||||
// DefaultBodyOverrideMode holds the default value on creation for the "body_override_mode" field.
|
||||
@@ -239,6 +246,11 @@ func ByIntervalSeconds(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldIntervalSeconds, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByJitterSeconds orders the results by the jitter_seconds field.
|
||||
func ByJitterSeconds(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldJitterSeconds, opts...).ToFunc()
|
||||
}
|
||||
|
||||
// ByLastCheckedAt orders the results by the last_checked_at field.
|
||||
func ByLastCheckedAt(opts ...sql.OrderTermOption) OrderOption {
|
||||
return sql.OrderByField(FieldLastCheckedAt, opts...).ToFunc()
|
||||
|
||||
@@ -105,6 +105,11 @@ func IntervalSeconds(v int) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldIntervalSeconds, v))
|
||||
}
|
||||
|
||||
// JitterSeconds applies equality check predicate on the "jitter_seconds" field. It's identical to JitterSecondsEQ.
|
||||
func JitterSeconds(v int) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldJitterSeconds, v))
|
||||
}
|
||||
|
||||
// LastCheckedAt applies equality check predicate on the "last_checked_at" field. It's identical to LastCheckedAtEQ.
|
||||
func LastCheckedAt(v time.Time) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldLastCheckedAt, v))
|
||||
@@ -675,6 +680,46 @@ func IntervalSecondsLTE(v int) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldLTE(FieldIntervalSeconds, v))
|
||||
}
|
||||
|
||||
// JitterSecondsEQ applies the EQ predicate on the "jitter_seconds" field.
|
||||
func JitterSecondsEQ(v int) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldJitterSeconds, v))
|
||||
}
|
||||
|
||||
// JitterSecondsNEQ applies the NEQ predicate on the "jitter_seconds" field.
|
||||
func JitterSecondsNEQ(v int) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldNEQ(FieldJitterSeconds, v))
|
||||
}
|
||||
|
||||
// JitterSecondsIn applies the In predicate on the "jitter_seconds" field.
|
||||
func JitterSecondsIn(vs ...int) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldIn(FieldJitterSeconds, vs...))
|
||||
}
|
||||
|
||||
// JitterSecondsNotIn applies the NotIn predicate on the "jitter_seconds" field.
|
||||
func JitterSecondsNotIn(vs ...int) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldNotIn(FieldJitterSeconds, vs...))
|
||||
}
|
||||
|
||||
// JitterSecondsGT applies the GT predicate on the "jitter_seconds" field.
|
||||
func JitterSecondsGT(v int) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldGT(FieldJitterSeconds, v))
|
||||
}
|
||||
|
||||
// JitterSecondsGTE applies the GTE predicate on the "jitter_seconds" field.
|
||||
func JitterSecondsGTE(v int) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldGTE(FieldJitterSeconds, v))
|
||||
}
|
||||
|
||||
// JitterSecondsLT applies the LT predicate on the "jitter_seconds" field.
|
||||
func JitterSecondsLT(v int) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldLT(FieldJitterSeconds, v))
|
||||
}
|
||||
|
||||
// JitterSecondsLTE applies the LTE predicate on the "jitter_seconds" field.
|
||||
func JitterSecondsLTE(v int) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldLTE(FieldJitterSeconds, v))
|
||||
}
|
||||
|
||||
// LastCheckedAtEQ applies the EQ predicate on the "last_checked_at" field.
|
||||
func LastCheckedAtEQ(v time.Time) predicate.ChannelMonitor {
|
||||
return predicate.ChannelMonitor(sql.FieldEQ(FieldLastCheckedAt, v))
|
||||
|
||||
@@ -137,6 +137,20 @@ func (_c *ChannelMonitorCreate) SetIntervalSeconds(v int) *ChannelMonitorCreate
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetJitterSeconds sets the "jitter_seconds" field.
|
||||
func (_c *ChannelMonitorCreate) SetJitterSeconds(v int) *ChannelMonitorCreate {
|
||||
_c.mutation.SetJitterSeconds(v)
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetNillableJitterSeconds sets the "jitter_seconds" field if the given value is not nil.
|
||||
func (_c *ChannelMonitorCreate) SetNillableJitterSeconds(v *int) *ChannelMonitorCreate {
|
||||
if v != nil {
|
||||
_c.SetJitterSeconds(*v)
|
||||
}
|
||||
return _c
|
||||
}
|
||||
|
||||
// SetLastCheckedAt sets the "last_checked_at" field.
|
||||
func (_c *ChannelMonitorCreate) SetLastCheckedAt(v time.Time) *ChannelMonitorCreate {
|
||||
_c.mutation.SetLastCheckedAt(v)
|
||||
@@ -305,6 +319,10 @@ func (_c *ChannelMonitorCreate) defaults() {
|
||||
v := channelmonitor.DefaultEnabled
|
||||
_c.mutation.SetEnabled(v)
|
||||
}
|
||||
if _, ok := _c.mutation.JitterSeconds(); !ok {
|
||||
v := channelmonitor.DefaultJitterSeconds
|
||||
_c.mutation.SetJitterSeconds(v)
|
||||
}
|
||||
if _, ok := _c.mutation.ExtraHeaders(); !ok {
|
||||
v := channelmonitor.DefaultExtraHeaders
|
||||
_c.mutation.SetExtraHeaders(v)
|
||||
@@ -390,6 +408,14 @@ func (_c *ChannelMonitorCreate) check() error {
|
||||
return &ValidationError{Name: "interval_seconds", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.interval_seconds": %w`, err)}
|
||||
}
|
||||
}
|
||||
if _, ok := _c.mutation.JitterSeconds(); !ok {
|
||||
return &ValidationError{Name: "jitter_seconds", err: errors.New(`ent: missing required field "ChannelMonitor.jitter_seconds"`)}
|
||||
}
|
||||
if v, ok := _c.mutation.JitterSeconds(); ok {
|
||||
if err := channelmonitor.JitterSecondsValidator(v); err != nil {
|
||||
return &ValidationError{Name: "jitter_seconds", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.jitter_seconds": %w`, err)}
|
||||
}
|
||||
}
|
||||
if _, ok := _c.mutation.CreatedBy(); !ok {
|
||||
return &ValidationError{Name: "created_by", err: errors.New(`ent: missing required field "ChannelMonitor.created_by"`)}
|
||||
}
|
||||
@@ -479,6 +505,10 @@ func (_c *ChannelMonitorCreate) createSpec() (*ChannelMonitor, *sqlgraph.CreateS
|
||||
_spec.SetField(channelmonitor.FieldIntervalSeconds, field.TypeInt, value)
|
||||
_node.IntervalSeconds = value
|
||||
}
|
||||
if value, ok := _c.mutation.JitterSeconds(); ok {
|
||||
_spec.SetField(channelmonitor.FieldJitterSeconds, field.TypeInt, value)
|
||||
_node.JitterSeconds = value
|
||||
}
|
||||
if value, ok := _c.mutation.LastCheckedAt(); ok {
|
||||
_spec.SetField(channelmonitor.FieldLastCheckedAt, field.TypeTime, value)
|
||||
_node.LastCheckedAt = &value
|
||||
@@ -744,6 +774,24 @@ func (u *ChannelMonitorUpsert) AddIntervalSeconds(v int) *ChannelMonitorUpsert {
|
||||
return u
|
||||
}
|
||||
|
||||
// SetJitterSeconds sets the "jitter_seconds" field.
|
||||
func (u *ChannelMonitorUpsert) SetJitterSeconds(v int) *ChannelMonitorUpsert {
|
||||
u.Set(channelmonitor.FieldJitterSeconds, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// UpdateJitterSeconds sets the "jitter_seconds" field to the value that was provided on create.
|
||||
func (u *ChannelMonitorUpsert) UpdateJitterSeconds() *ChannelMonitorUpsert {
|
||||
u.SetExcluded(channelmonitor.FieldJitterSeconds)
|
||||
return u
|
||||
}
|
||||
|
||||
// AddJitterSeconds adds v to the "jitter_seconds" field.
|
||||
func (u *ChannelMonitorUpsert) AddJitterSeconds(v int) *ChannelMonitorUpsert {
|
||||
u.Add(channelmonitor.FieldJitterSeconds, v)
|
||||
return u
|
||||
}
|
||||
|
||||
// SetLastCheckedAt sets the "last_checked_at" field.
|
||||
func (u *ChannelMonitorUpsert) SetLastCheckedAt(v time.Time) *ChannelMonitorUpsert {
|
||||
u.Set(channelmonitor.FieldLastCheckedAt, v)
|
||||
@@ -1053,6 +1101,27 @@ func (u *ChannelMonitorUpsertOne) UpdateIntervalSeconds() *ChannelMonitorUpsertO
|
||||
})
|
||||
}
|
||||
|
||||
// SetJitterSeconds sets the "jitter_seconds" field.
|
||||
func (u *ChannelMonitorUpsertOne) SetJitterSeconds(v int) *ChannelMonitorUpsertOne {
|
||||
return u.Update(func(s *ChannelMonitorUpsert) {
|
||||
s.SetJitterSeconds(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddJitterSeconds adds v to the "jitter_seconds" field.
|
||||
func (u *ChannelMonitorUpsertOne) AddJitterSeconds(v int) *ChannelMonitorUpsertOne {
|
||||
return u.Update(func(s *ChannelMonitorUpsert) {
|
||||
s.AddJitterSeconds(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateJitterSeconds sets the "jitter_seconds" field to the value that was provided on create.
|
||||
func (u *ChannelMonitorUpsertOne) UpdateJitterSeconds() *ChannelMonitorUpsertOne {
|
||||
return u.Update(func(s *ChannelMonitorUpsert) {
|
||||
s.UpdateJitterSeconds()
|
||||
})
|
||||
}
|
||||
|
||||
// SetLastCheckedAt sets the "last_checked_at" field.
|
||||
func (u *ChannelMonitorUpsertOne) SetLastCheckedAt(v time.Time) *ChannelMonitorUpsertOne {
|
||||
return u.Update(func(s *ChannelMonitorUpsert) {
|
||||
@@ -1544,6 +1613,27 @@ func (u *ChannelMonitorUpsertBulk) UpdateIntervalSeconds() *ChannelMonitorUpsert
|
||||
})
|
||||
}
|
||||
|
||||
// SetJitterSeconds sets the "jitter_seconds" field.
|
||||
func (u *ChannelMonitorUpsertBulk) SetJitterSeconds(v int) *ChannelMonitorUpsertBulk {
|
||||
return u.Update(func(s *ChannelMonitorUpsert) {
|
||||
s.SetJitterSeconds(v)
|
||||
})
|
||||
}
|
||||
|
||||
// AddJitterSeconds adds v to the "jitter_seconds" field.
|
||||
func (u *ChannelMonitorUpsertBulk) AddJitterSeconds(v int) *ChannelMonitorUpsertBulk {
|
||||
return u.Update(func(s *ChannelMonitorUpsert) {
|
||||
s.AddJitterSeconds(v)
|
||||
})
|
||||
}
|
||||
|
||||
// UpdateJitterSeconds sets the "jitter_seconds" field to the value that was provided on create.
|
||||
func (u *ChannelMonitorUpsertBulk) UpdateJitterSeconds() *ChannelMonitorUpsertBulk {
|
||||
return u.Update(func(s *ChannelMonitorUpsert) {
|
||||
s.UpdateJitterSeconds()
|
||||
})
|
||||
}
|
||||
|
||||
// SetLastCheckedAt sets the "last_checked_at" field.
|
||||
func (u *ChannelMonitorUpsertBulk) SetLastCheckedAt(v time.Time) *ChannelMonitorUpsertBulk {
|
||||
return u.Update(func(s *ChannelMonitorUpsert) {
|
||||
|
||||
@@ -189,6 +189,27 @@ func (_u *ChannelMonitorUpdate) AddIntervalSeconds(v int) *ChannelMonitorUpdate
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetJitterSeconds sets the "jitter_seconds" field.
|
||||
func (_u *ChannelMonitorUpdate) SetJitterSeconds(v int) *ChannelMonitorUpdate {
|
||||
_u.mutation.ResetJitterSeconds()
|
||||
_u.mutation.SetJitterSeconds(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableJitterSeconds sets the "jitter_seconds" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorUpdate) SetNillableJitterSeconds(v *int) *ChannelMonitorUpdate {
|
||||
if v != nil {
|
||||
_u.SetJitterSeconds(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddJitterSeconds adds value to the "jitter_seconds" field.
|
||||
func (_u *ChannelMonitorUpdate) AddJitterSeconds(v int) *ChannelMonitorUpdate {
|
||||
_u.mutation.AddJitterSeconds(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetLastCheckedAt sets the "last_checked_at" field.
|
||||
func (_u *ChannelMonitorUpdate) SetLastCheckedAt(v time.Time) *ChannelMonitorUpdate {
|
||||
_u.mutation.SetLastCheckedAt(v)
|
||||
@@ -462,6 +483,11 @@ func (_u *ChannelMonitorUpdate) check() error {
|
||||
return &ValidationError{Name: "interval_seconds", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.interval_seconds": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.JitterSeconds(); ok {
|
||||
if err := channelmonitor.JitterSecondsValidator(v); err != nil {
|
||||
return &ValidationError{Name: "jitter_seconds", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.jitter_seconds": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.BodyOverrideMode(); ok {
|
||||
if err := channelmonitor.BodyOverrideModeValidator(v); err != nil {
|
||||
return &ValidationError{Name: "body_override_mode", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.body_override_mode": %w`, err)}
|
||||
@@ -526,6 +552,12 @@ func (_u *ChannelMonitorUpdate) sqlSave(ctx context.Context) (_node int, err err
|
||||
if value, ok := _u.mutation.AddedIntervalSeconds(); ok {
|
||||
_spec.AddField(channelmonitor.FieldIntervalSeconds, field.TypeInt, value)
|
||||
}
|
||||
if value, ok := _u.mutation.JitterSeconds(); ok {
|
||||
_spec.SetField(channelmonitor.FieldJitterSeconds, field.TypeInt, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedJitterSeconds(); ok {
|
||||
_spec.AddField(channelmonitor.FieldJitterSeconds, field.TypeInt, value)
|
||||
}
|
||||
if value, ok := _u.mutation.LastCheckedAt(); ok {
|
||||
_spec.SetField(channelmonitor.FieldLastCheckedAt, field.TypeTime, value)
|
||||
}
|
||||
@@ -846,6 +878,27 @@ func (_u *ChannelMonitorUpdateOne) AddIntervalSeconds(v int) *ChannelMonitorUpda
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetJitterSeconds sets the "jitter_seconds" field.
|
||||
func (_u *ChannelMonitorUpdateOne) SetJitterSeconds(v int) *ChannelMonitorUpdateOne {
|
||||
_u.mutation.ResetJitterSeconds()
|
||||
_u.mutation.SetJitterSeconds(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetNillableJitterSeconds sets the "jitter_seconds" field if the given value is not nil.
|
||||
func (_u *ChannelMonitorUpdateOne) SetNillableJitterSeconds(v *int) *ChannelMonitorUpdateOne {
|
||||
if v != nil {
|
||||
_u.SetJitterSeconds(*v)
|
||||
}
|
||||
return _u
|
||||
}
|
||||
|
||||
// AddJitterSeconds adds value to the "jitter_seconds" field.
|
||||
func (_u *ChannelMonitorUpdateOne) AddJitterSeconds(v int) *ChannelMonitorUpdateOne {
|
||||
_u.mutation.AddJitterSeconds(v)
|
||||
return _u
|
||||
}
|
||||
|
||||
// SetLastCheckedAt sets the "last_checked_at" field.
|
||||
func (_u *ChannelMonitorUpdateOne) SetLastCheckedAt(v time.Time) *ChannelMonitorUpdateOne {
|
||||
_u.mutation.SetLastCheckedAt(v)
|
||||
@@ -1132,6 +1185,11 @@ func (_u *ChannelMonitorUpdateOne) check() error {
|
||||
return &ValidationError{Name: "interval_seconds", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.interval_seconds": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.JitterSeconds(); ok {
|
||||
if err := channelmonitor.JitterSecondsValidator(v); err != nil {
|
||||
return &ValidationError{Name: "jitter_seconds", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.jitter_seconds": %w`, err)}
|
||||
}
|
||||
}
|
||||
if v, ok := _u.mutation.BodyOverrideMode(); ok {
|
||||
if err := channelmonitor.BodyOverrideModeValidator(v); err != nil {
|
||||
return &ValidationError{Name: "body_override_mode", err: fmt.Errorf(`ent: validator failed for field "ChannelMonitor.body_override_mode": %w`, err)}
|
||||
@@ -1213,6 +1271,12 @@ func (_u *ChannelMonitorUpdateOne) sqlSave(ctx context.Context) (_node *ChannelM
|
||||
if value, ok := _u.mutation.AddedIntervalSeconds(); ok {
|
||||
_spec.AddField(channelmonitor.FieldIntervalSeconds, field.TypeInt, value)
|
||||
}
|
||||
if value, ok := _u.mutation.JitterSeconds(); ok {
|
||||
_spec.SetField(channelmonitor.FieldJitterSeconds, field.TypeInt, value)
|
||||
}
|
||||
if value, ok := _u.mutation.AddedJitterSeconds(); ok {
|
||||
_spec.AddField(channelmonitor.FieldJitterSeconds, field.TypeInt, value)
|
||||
}
|
||||
if value, ok := _u.mutation.LastCheckedAt(); ok {
|
||||
_spec.SetField(channelmonitor.FieldLastCheckedAt, field.TypeTime, value)
|
||||
}
|
||||
|
||||
@@ -437,6 +437,7 @@ var (
|
||||
{Name: "group_name", Type: field.TypeString, Nullable: true, Size: 100, Default: ""},
|
||||
{Name: "enabled", Type: field.TypeBool, Default: true},
|
||||
{Name: "interval_seconds", Type: field.TypeInt},
|
||||
{Name: "jitter_seconds", Type: field.TypeInt, Default: 0},
|
||||
{Name: "last_checked_at", Type: field.TypeTime, Nullable: true},
|
||||
{Name: "created_by", Type: field.TypeInt64},
|
||||
{Name: "extra_headers", Type: field.TypeJSON},
|
||||
@@ -452,7 +453,7 @@ var (
|
||||
ForeignKeys: []*schema.ForeignKey{
|
||||
{
|
||||
Symbol: "channel_monitors_channel_monitor_request_templates_request_template",
|
||||
Columns: []*schema.Column{ChannelMonitorsColumns[18]},
|
||||
Columns: []*schema.Column{ChannelMonitorsColumns[19]},
|
||||
RefColumns: []*schema.Column{ChannelMonitorRequestTemplatesColumns[0]},
|
||||
OnDelete: schema.SetNull,
|
||||
},
|
||||
@@ -461,7 +462,7 @@ var (
|
||||
{
|
||||
Name: "channelmonitor_enabled_last_checked_at",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{ChannelMonitorsColumns[11], ChannelMonitorsColumns[13]},
|
||||
Columns: []*schema.Column{ChannelMonitorsColumns[11], ChannelMonitorsColumns[14]},
|
||||
},
|
||||
{
|
||||
Name: "channelmonitor_provider",
|
||||
@@ -481,7 +482,7 @@ var (
|
||||
{
|
||||
Name: "channelmonitor_template_id",
|
||||
Unique: false,
|
||||
Columns: []*schema.Column{ChannelMonitorsColumns[18]},
|
||||
Columns: []*schema.Column{ChannelMonitorsColumns[19]},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
+88
-1
@@ -8871,6 +8871,8 @@ type ChannelMonitorMutation struct {
|
||||
enabled *bool
|
||||
interval_seconds *int
|
||||
addinterval_seconds *int
|
||||
jitter_seconds *int
|
||||
addjitter_seconds *int
|
||||
last_checked_at *time.Time
|
||||
created_by *int64
|
||||
addcreated_by *int64
|
||||
@@ -9469,6 +9471,62 @@ func (m *ChannelMonitorMutation) ResetIntervalSeconds() {
|
||||
m.addinterval_seconds = nil
|
||||
}
|
||||
|
||||
// SetJitterSeconds sets the "jitter_seconds" field.
|
||||
func (m *ChannelMonitorMutation) SetJitterSeconds(i int) {
|
||||
m.jitter_seconds = &i
|
||||
m.addjitter_seconds = nil
|
||||
}
|
||||
|
||||
// JitterSeconds returns the value of the "jitter_seconds" field in the mutation.
|
||||
func (m *ChannelMonitorMutation) JitterSeconds() (r int, exists bool) {
|
||||
v := m.jitter_seconds
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
return *v, true
|
||||
}
|
||||
|
||||
// OldJitterSeconds returns the old "jitter_seconds" field's value of the ChannelMonitor entity.
|
||||
// If the ChannelMonitor object wasn't provided to the builder, the object is fetched from the database.
|
||||
// An error is returned if the mutation operation is not UpdateOne, or the database query fails.
|
||||
func (m *ChannelMonitorMutation) OldJitterSeconds(ctx context.Context) (v int, err error) {
|
||||
if !m.op.Is(OpUpdateOne) {
|
||||
return v, errors.New("OldJitterSeconds is only allowed on UpdateOne operations")
|
||||
}
|
||||
if m.id == nil || m.oldValue == nil {
|
||||
return v, errors.New("OldJitterSeconds requires an ID field in the mutation")
|
||||
}
|
||||
oldValue, err := m.oldValue(ctx)
|
||||
if err != nil {
|
||||
return v, fmt.Errorf("querying old value for OldJitterSeconds: %w", err)
|
||||
}
|
||||
return oldValue.JitterSeconds, nil
|
||||
}
|
||||
|
||||
// AddJitterSeconds adds i to the "jitter_seconds" field.
|
||||
func (m *ChannelMonitorMutation) AddJitterSeconds(i int) {
|
||||
if m.addjitter_seconds != nil {
|
||||
*m.addjitter_seconds += i
|
||||
} else {
|
||||
m.addjitter_seconds = &i
|
||||
}
|
||||
}
|
||||
|
||||
// AddedJitterSeconds returns the value that was added to the "jitter_seconds" field in this mutation.
|
||||
func (m *ChannelMonitorMutation) AddedJitterSeconds() (r int, exists bool) {
|
||||
v := m.addjitter_seconds
|
||||
if v == nil {
|
||||
return
|
||||
}
|
||||
return *v, true
|
||||
}
|
||||
|
||||
// ResetJitterSeconds resets all changes to the "jitter_seconds" field.
|
||||
func (m *ChannelMonitorMutation) ResetJitterSeconds() {
|
||||
m.jitter_seconds = nil
|
||||
m.addjitter_seconds = nil
|
||||
}
|
||||
|
||||
// SetLastCheckedAt sets the "last_checked_at" field.
|
||||
func (m *ChannelMonitorMutation) SetLastCheckedAt(t time.Time) {
|
||||
m.last_checked_at = &t
|
||||
@@ -9926,7 +9984,7 @@ func (m *ChannelMonitorMutation) Type() string {
|
||||
// order to get all numeric fields that were incremented/decremented, call
|
||||
// AddedFields().
|
||||
func (m *ChannelMonitorMutation) Fields() []string {
|
||||
fields := make([]string, 0, 18)
|
||||
fields := make([]string, 0, 19)
|
||||
if m.created_at != nil {
|
||||
fields = append(fields, channelmonitor.FieldCreatedAt)
|
||||
}
|
||||
@@ -9963,6 +10021,9 @@ func (m *ChannelMonitorMutation) Fields() []string {
|
||||
if m.interval_seconds != nil {
|
||||
fields = append(fields, channelmonitor.FieldIntervalSeconds)
|
||||
}
|
||||
if m.jitter_seconds != nil {
|
||||
fields = append(fields, channelmonitor.FieldJitterSeconds)
|
||||
}
|
||||
if m.last_checked_at != nil {
|
||||
fields = append(fields, channelmonitor.FieldLastCheckedAt)
|
||||
}
|
||||
@@ -10013,6 +10074,8 @@ func (m *ChannelMonitorMutation) Field(name string) (ent.Value, bool) {
|
||||
return m.Enabled()
|
||||
case channelmonitor.FieldIntervalSeconds:
|
||||
return m.IntervalSeconds()
|
||||
case channelmonitor.FieldJitterSeconds:
|
||||
return m.JitterSeconds()
|
||||
case channelmonitor.FieldLastCheckedAt:
|
||||
return m.LastCheckedAt()
|
||||
case channelmonitor.FieldCreatedBy:
|
||||
@@ -10058,6 +10121,8 @@ func (m *ChannelMonitorMutation) OldField(ctx context.Context, name string) (ent
|
||||
return m.OldEnabled(ctx)
|
||||
case channelmonitor.FieldIntervalSeconds:
|
||||
return m.OldIntervalSeconds(ctx)
|
||||
case channelmonitor.FieldJitterSeconds:
|
||||
return m.OldJitterSeconds(ctx)
|
||||
case channelmonitor.FieldLastCheckedAt:
|
||||
return m.OldLastCheckedAt(ctx)
|
||||
case channelmonitor.FieldCreatedBy:
|
||||
@@ -10163,6 +10228,13 @@ func (m *ChannelMonitorMutation) SetField(name string, value ent.Value) error {
|
||||
}
|
||||
m.SetIntervalSeconds(v)
|
||||
return nil
|
||||
case channelmonitor.FieldJitterSeconds:
|
||||
v, ok := value.(int)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||
}
|
||||
m.SetJitterSeconds(v)
|
||||
return nil
|
||||
case channelmonitor.FieldLastCheckedAt:
|
||||
v, ok := value.(time.Time)
|
||||
if !ok {
|
||||
@@ -10216,6 +10288,9 @@ func (m *ChannelMonitorMutation) AddedFields() []string {
|
||||
if m.addinterval_seconds != nil {
|
||||
fields = append(fields, channelmonitor.FieldIntervalSeconds)
|
||||
}
|
||||
if m.addjitter_seconds != nil {
|
||||
fields = append(fields, channelmonitor.FieldJitterSeconds)
|
||||
}
|
||||
if m.addcreated_by != nil {
|
||||
fields = append(fields, channelmonitor.FieldCreatedBy)
|
||||
}
|
||||
@@ -10229,6 +10304,8 @@ func (m *ChannelMonitorMutation) AddedField(name string) (ent.Value, bool) {
|
||||
switch name {
|
||||
case channelmonitor.FieldIntervalSeconds:
|
||||
return m.AddedIntervalSeconds()
|
||||
case channelmonitor.FieldJitterSeconds:
|
||||
return m.AddedJitterSeconds()
|
||||
case channelmonitor.FieldCreatedBy:
|
||||
return m.AddedCreatedBy()
|
||||
}
|
||||
@@ -10247,6 +10324,13 @@ func (m *ChannelMonitorMutation) AddField(name string, value ent.Value) error {
|
||||
}
|
||||
m.AddIntervalSeconds(v)
|
||||
return nil
|
||||
case channelmonitor.FieldJitterSeconds:
|
||||
v, ok := value.(int)
|
||||
if !ok {
|
||||
return fmt.Errorf("unexpected type %T for field %s", value, name)
|
||||
}
|
||||
m.AddJitterSeconds(v)
|
||||
return nil
|
||||
case channelmonitor.FieldCreatedBy:
|
||||
v, ok := value.(int64)
|
||||
if !ok {
|
||||
@@ -10344,6 +10428,9 @@ func (m *ChannelMonitorMutation) ResetField(name string) error {
|
||||
case channelmonitor.FieldIntervalSeconds:
|
||||
m.ResetIntervalSeconds()
|
||||
return nil
|
||||
case channelmonitor.FieldJitterSeconds:
|
||||
m.ResetJitterSeconds()
|
||||
return nil
|
||||
case channelmonitor.FieldLastCheckedAt:
|
||||
m.ResetLastCheckedAt()
|
||||
return nil
|
||||
|
||||
@@ -529,12 +529,18 @@ func init() {
|
||||
channelmonitorDescIntervalSeconds := channelmonitorFields[9].Descriptor()
|
||||
// channelmonitor.IntervalSecondsValidator is a validator for the "interval_seconds" field. It is called by the builders before save.
|
||||
channelmonitor.IntervalSecondsValidator = channelmonitorDescIntervalSeconds.Validators[0].(func(int) error)
|
||||
// channelmonitorDescJitterSeconds is the schema descriptor for jitter_seconds field.
|
||||
channelmonitorDescJitterSeconds := channelmonitorFields[10].Descriptor()
|
||||
// channelmonitor.DefaultJitterSeconds holds the default value on creation for the jitter_seconds field.
|
||||
channelmonitor.DefaultJitterSeconds = channelmonitorDescJitterSeconds.Default.(int)
|
||||
// channelmonitor.JitterSecondsValidator is a validator for the "jitter_seconds" field. It is called by the builders before save.
|
||||
channelmonitor.JitterSecondsValidator = channelmonitorDescJitterSeconds.Validators[0].(func(int) error)
|
||||
// channelmonitorDescExtraHeaders is the schema descriptor for extra_headers field.
|
||||
channelmonitorDescExtraHeaders := channelmonitorFields[13].Descriptor()
|
||||
channelmonitorDescExtraHeaders := channelmonitorFields[14].Descriptor()
|
||||
// channelmonitor.DefaultExtraHeaders holds the default value on creation for the extra_headers field.
|
||||
channelmonitor.DefaultExtraHeaders = channelmonitorDescExtraHeaders.Default.(map[string]string)
|
||||
// channelmonitorDescBodyOverrideMode is the schema descriptor for body_override_mode field.
|
||||
channelmonitorDescBodyOverrideMode := channelmonitorFields[14].Descriptor()
|
||||
channelmonitorDescBodyOverrideMode := channelmonitorFields[15].Descriptor()
|
||||
// channelmonitor.DefaultBodyOverrideMode holds the default value on creation for the body_override_mode field.
|
||||
channelmonitor.DefaultBodyOverrideMode = channelmonitorDescBodyOverrideMode.Default.(string)
|
||||
// channelmonitor.BodyOverrideModeValidator is a validator for the "body_override_mode" field. It is called by the builders before save.
|
||||
|
||||
@@ -62,6 +62,10 @@ func (ChannelMonitor) Fields() []ent.Field {
|
||||
Default(true),
|
||||
field.Int("interval_seconds").
|
||||
Range(15, 3600),
|
||||
field.Int("jitter_seconds").
|
||||
Default(0).
|
||||
Range(0, 3600).
|
||||
Comment("每次调度在 interval 基础上 ± [0, jitter] 的均匀随机偏移(秒);0 表示固定间隔。service 层另保证 interval - jitter >= 15"),
|
||||
field.Time("last_checked_at").
|
||||
Optional().
|
||||
Nillable(),
|
||||
|
||||
@@ -46,6 +46,7 @@ type channelMonitorCreateRequest struct {
|
||||
GroupName string `json:"group_name" binding:"max=100"`
|
||||
Enabled *bool `json:"enabled"`
|
||||
IntervalSeconds int `json:"interval_seconds" binding:"required,min=15,max=3600"`
|
||||
JitterSeconds int `json:"jitter_seconds" binding:"omitempty,min=0,max=3585"`
|
||||
TemplateID *int64 `json:"template_id"`
|
||||
ExtraHeaders map[string]string `json:"extra_headers"`
|
||||
BodyOverrideMode string `json:"body_override_mode" binding:"omitempty,oneof=off merge replace"`
|
||||
@@ -63,6 +64,7 @@ type channelMonitorUpdateRequest struct {
|
||||
GroupName *string `json:"group_name" binding:"omitempty,max=100"`
|
||||
Enabled *bool `json:"enabled"`
|
||||
IntervalSeconds *int `json:"interval_seconds" binding:"omitempty,min=15,max=3600"`
|
||||
JitterSeconds *int `json:"jitter_seconds" binding:"omitempty,min=0,max=3585"`
|
||||
TemplateID *int64 `json:"template_id"`
|
||||
ClearTemplate bool `json:"clear_template"` // true 时把 template_id 置空,忽略 TemplateID
|
||||
ExtraHeaders *map[string]string `json:"extra_headers"`
|
||||
@@ -83,6 +85,7 @@ type channelMonitorResponse struct {
|
||||
GroupName string `json:"group_name"`
|
||||
Enabled bool `json:"enabled"`
|
||||
IntervalSeconds int `json:"interval_seconds"`
|
||||
JitterSeconds int `json:"jitter_seconds"`
|
||||
LastCheckedAt *string `json:"last_checked_at"`
|
||||
CreatedBy int64 `json:"created_by"`
|
||||
CreatedAt string `json:"created_at"`
|
||||
@@ -150,6 +153,7 @@ func channelMonitorToResponse(m *service.ChannelMonitor) *channelMonitorResponse
|
||||
GroupName: m.GroupName,
|
||||
Enabled: m.Enabled,
|
||||
IntervalSeconds: m.IntervalSeconds,
|
||||
JitterSeconds: m.JitterSeconds,
|
||||
CreatedBy: m.CreatedBy,
|
||||
CreatedAt: m.CreatedAt.UTC().Format(time.RFC3339),
|
||||
UpdatedAt: m.UpdatedAt.UTC().Format(time.RFC3339),
|
||||
@@ -315,6 +319,7 @@ func (h *ChannelMonitorHandler) Create(c *gin.Context) {
|
||||
GroupName: req.GroupName,
|
||||
Enabled: enabled,
|
||||
IntervalSeconds: req.IntervalSeconds,
|
||||
JitterSeconds: req.JitterSeconds,
|
||||
CreatedBy: subject.UserID,
|
||||
TemplateID: req.TemplateID,
|
||||
ExtraHeaders: req.ExtraHeaders,
|
||||
@@ -351,6 +356,7 @@ func (h *ChannelMonitorHandler) Update(c *gin.Context) {
|
||||
GroupName: req.GroupName,
|
||||
Enabled: req.Enabled,
|
||||
IntervalSeconds: req.IntervalSeconds,
|
||||
JitterSeconds: req.JitterSeconds,
|
||||
TemplateID: req.TemplateID,
|
||||
ClearTemplate: req.ClearTemplate,
|
||||
ExtraHeaders: req.ExtraHeaders,
|
||||
|
||||
@@ -45,6 +45,7 @@ func (r *channelMonitorRepository) Create(ctx context.Context, m *service.Channe
|
||||
SetGroupName(m.GroupName).
|
||||
SetEnabled(m.Enabled).
|
||||
SetIntervalSeconds(m.IntervalSeconds).
|
||||
SetJitterSeconds(m.JitterSeconds).
|
||||
SetCreatedBy(m.CreatedBy).
|
||||
SetExtraHeaders(emptyHeadersIfNilRepo(m.ExtraHeaders)).
|
||||
SetBodyOverrideMode(defaultBodyModeRepo(m.BodyOverrideMode))
|
||||
@@ -88,6 +89,7 @@ func (r *channelMonitorRepository) Update(ctx context.Context, m *service.Channe
|
||||
SetGroupName(m.GroupName).
|
||||
SetEnabled(m.Enabled).
|
||||
SetIntervalSeconds(m.IntervalSeconds).
|
||||
SetJitterSeconds(m.JitterSeconds).
|
||||
SetExtraHeaders(emptyHeadersIfNilRepo(m.ExtraHeaders)).
|
||||
SetBodyOverrideMode(defaultBodyModeRepo(m.BodyOverrideMode))
|
||||
if m.TemplateID != nil {
|
||||
@@ -718,6 +720,7 @@ func entToServiceMonitor(row *dbent.ChannelMonitor) *service.ChannelMonitor {
|
||||
GroupName: row.GroupName,
|
||||
Enabled: row.Enabled,
|
||||
IntervalSeconds: row.IntervalSeconds,
|
||||
JitterSeconds: row.JitterSeconds,
|
||||
LastCheckedAt: row.LastCheckedAt,
|
||||
CreatedBy: row.CreatedBy,
|
||||
CreatedAt: row.CreatedAt,
|
||||
|
||||
@@ -123,6 +123,9 @@ var (
|
||||
ErrChannelMonitorInvalidInterval = infraerrors.BadRequest(
|
||||
"CHANNEL_MONITOR_INVALID_INTERVAL", "interval_seconds must be in [15, 3600]",
|
||||
)
|
||||
ErrChannelMonitorInvalidJitter = infraerrors.BadRequest(
|
||||
"CHANNEL_MONITOR_INVALID_JITTER", "jitter_seconds must be >= 0 and interval_seconds - jitter_seconds must be >= 15",
|
||||
)
|
||||
ErrChannelMonitorInvalidEndpoint = infraerrors.BadRequest(
|
||||
"CHANNEL_MONITOR_INVALID_ENDPOINT", "endpoint must be a valid https URL",
|
||||
)
|
||||
|
||||
@@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"math/rand/v2"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
@@ -68,9 +69,25 @@ type scheduledMonitor struct {
|
||||
id int64
|
||||
name string
|
||||
interval time.Duration
|
||||
jitter time.Duration // 每轮 ± [0, jitter] 的均匀随机偏移;0 = 固定间隔
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
// nextDelay 计算下一次触发的等待时长:interval ± [0, jitter] 的均匀随机偏移。
|
||||
// 校验链路已保证 interval - jitter >= monitorMinIntervalSeconds,
|
||||
// 这里仍 clamp 一次下限,兜底数据库中违反约束的脏数据。
|
||||
func (t *scheduledMonitor) nextDelay() time.Duration {
|
||||
if t.jitter <= 0 {
|
||||
return t.interval
|
||||
}
|
||||
offset := time.Duration(rand.Int64N(int64(2*t.jitter) + 1)) // [0, 2*jitter]
|
||||
d := t.interval - t.jitter + offset
|
||||
if floor := monitorMinIntervalSeconds * time.Second; d < floor {
|
||||
d = floor
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
// NewChannelMonitorRunner 构造调度器。Start 在 wire 中调用一次。
|
||||
// settingService 用于在每次 fire 前读取功能开关;传 nil 时视为总是启用(兼容测试)。
|
||||
//
|
||||
@@ -141,6 +158,10 @@ func (r *ChannelMonitorRunner) Schedule(m *ChannelMonitor) {
|
||||
"monitor_id", m.ID, "interval_seconds", m.IntervalSeconds)
|
||||
return
|
||||
}
|
||||
jitter := time.Duration(m.JitterSeconds) * time.Second
|
||||
if jitter < 0 {
|
||||
jitter = 0
|
||||
}
|
||||
|
||||
r.mu.Lock()
|
||||
if r.stopped {
|
||||
@@ -165,6 +186,7 @@ func (r *ChannelMonitorRunner) Schedule(m *ChannelMonitor) {
|
||||
id: m.ID,
|
||||
name: m.Name,
|
||||
interval: interval,
|
||||
jitter: jitter,
|
||||
cancel: cancel,
|
||||
}
|
||||
r.tasks[m.ID] = task
|
||||
@@ -211,20 +233,22 @@ func (r *ChannelMonitorRunner) Stop() {
|
||||
}
|
||||
|
||||
// runScheduled 单个监控的循环:立即触发首次(满足"新建/启用即跑"),
|
||||
// 之后按 interval 周期触发;ctx 取消即退出。
|
||||
// 之后按 interval ± jitter 周期触发;ctx 取消即退出。
|
||||
// 用 timer 而非 ticker:jitter > 0 时每轮等待时长都需要重新随机化。
|
||||
func (r *ChannelMonitorRunner) runScheduled(ctx context.Context, task *scheduledMonitor) {
|
||||
defer r.wg.Done()
|
||||
|
||||
r.fire(ctx, task)
|
||||
|
||||
ticker := time.NewTicker(task.interval)
|
||||
defer ticker.Stop()
|
||||
timer := time.NewTimer(task.nextDelay())
|
||||
defer timer.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-ticker.C:
|
||||
case <-timer.C:
|
||||
r.fire(ctx, task)
|
||||
timer.Reset(task.nextDelay())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,6 +128,7 @@ func (s *ChannelMonitorService) Create(ctx context.Context, p ChannelMonitorCrea
|
||||
GroupName: strings.TrimSpace(p.GroupName),
|
||||
Enabled: p.Enabled,
|
||||
IntervalSeconds: p.IntervalSeconds,
|
||||
JitterSeconds: p.JitterSeconds,
|
||||
CreatedBy: p.CreatedBy,
|
||||
TemplateID: p.TemplateID,
|
||||
ExtraHeaders: emptyHeadersIfNil(p.ExtraHeaders),
|
||||
@@ -157,6 +158,9 @@ func validateCreateParams(p ChannelMonitorCreateParams) error {
|
||||
if err := validateInterval(p.IntervalSeconds); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateJitter(p.JitterSeconds, p.IntervalSeconds); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateEndpoint(p.Endpoint); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -509,6 +513,15 @@ func applyMonitorUpdate(existing *ChannelMonitor, p ChannelMonitorUpdateParams)
|
||||
}
|
||||
existing.IntervalSeconds = *p.IntervalSeconds
|
||||
}
|
||||
if p.JitterSeconds != nil {
|
||||
existing.JitterSeconds = *p.JitterSeconds
|
||||
}
|
||||
if p.IntervalSeconds != nil || p.JitterSeconds != nil {
|
||||
// interval 与 jitter 任一变化都需要重新校验组合约束(interval - jitter >= 下限)。
|
||||
if err := validateJitter(existing.JitterSeconds, existing.IntervalSeconds); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return applyMonitorAdvancedUpdate(existing, p, providerChanged)
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ type ChannelMonitor struct {
|
||||
GroupName string
|
||||
Enabled bool
|
||||
IntervalSeconds int
|
||||
JitterSeconds int // 每次调度 ± [0, jitter] 的随机偏移(秒),0 = 固定间隔
|
||||
LastCheckedAt *time.Time
|
||||
CreatedBy int64
|
||||
CreatedAt time.Time
|
||||
@@ -76,6 +77,7 @@ type ChannelMonitorCreateParams struct {
|
||||
GroupName string
|
||||
Enabled bool
|
||||
IntervalSeconds int
|
||||
JitterSeconds int
|
||||
CreatedBy int64
|
||||
TemplateID *int64
|
||||
ExtraHeaders map[string]string
|
||||
@@ -95,6 +97,7 @@ type ChannelMonitorUpdateParams struct {
|
||||
GroupName *string
|
||||
Enabled *bool
|
||||
IntervalSeconds *int
|
||||
JitterSeconds *int
|
||||
// 自定义快照字段:指针为 nil 表示不更新,非 nil 覆盖
|
||||
// TemplateID *(*int64):用 ** 表达三态:nil=不更新;&nil=清空;&&id=设为 id。
|
||||
// 简化处理:用 ClearTemplate 显式标志 + TemplateID(普通指针)
|
||||
|
||||
@@ -43,6 +43,15 @@ func validateInterval(sec int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateJitter 校验 jitter_seconds(调度 ± 随机抖动):
|
||||
// 非负,且 interval - jitter 不得低于最小检测间隔,防止随机偏移后实际间隔过短打爆上游。
|
||||
func validateJitter(jitterSec, intervalSec int) error {
|
||||
if jitterSec < 0 || intervalSec-jitterSec < monitorMinIntervalSeconds {
|
||||
return ErrChannelMonitorInvalidJitter
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateEndpoint 校验 endpoint:
|
||||
// - scheme 强制 https(拒绝 http,避免明文凭证 + 部分 SSRF 利用面)
|
||||
// - 必须为 origin(无 path/query/fragment),防止用户填 https://api.openai.com/v1
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
-- Migration: 151_channel_monitor_jitter
|
||||
-- 渠道监控新增正负随机抖动配置:每次调度在 interval_seconds 基础上
|
||||
-- ± [0, jitter_seconds] 的均匀随机偏移,避免多个监控固定同步触发。
|
||||
-- 0(默认)表示固定间隔,与历史行为一致。
|
||||
|
||||
ALTER TABLE channel_monitors
|
||||
ADD COLUMN IF NOT EXISTS jitter_seconds INTEGER NOT NULL DEFAULT 0;
|
||||
@@ -28,6 +28,8 @@ export interface ChannelMonitor {
|
||||
group_name: string
|
||||
enabled: boolean
|
||||
interval_seconds: number
|
||||
/** 每次调度在 interval 基础上 ± [0, jitter] 的随机偏移(秒),0 = 固定间隔 */
|
||||
jitter_seconds: number
|
||||
last_checked_at: string | null
|
||||
created_by: number
|
||||
created_at: string
|
||||
@@ -80,6 +82,7 @@ export interface CreateParams {
|
||||
group_name?: string
|
||||
enabled?: boolean
|
||||
interval_seconds: number
|
||||
jitter_seconds?: number
|
||||
template_id?: number | null
|
||||
extra_headers?: Record<string, string>
|
||||
body_override_mode?: BodyOverrideMode
|
||||
|
||||
@@ -109,6 +109,12 @@
|
||||
<p class="mt-1 text-xs text-gray-400">{{ t('admin.channelMonitor.form.intervalSecondsHint') }}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="input-label">{{ t('admin.channelMonitor.form.jitterSeconds') }}</label>
|
||||
<input v-model.number="form.jitter_seconds" type="number" min="0" :max="maxJitterSeconds" class="input" />
|
||||
<p class="mt-1 text-xs text-gray-400">{{ t('admin.channelMonitor.form.jitterSecondsHint') }}</p>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="input-label mb-0">{{ t('admin.channelMonitor.form.enabled') }}</label>
|
||||
<Toggle v-model="form.enabled" />
|
||||
@@ -254,6 +260,7 @@ interface MonitorForm {
|
||||
extra_models: string[]
|
||||
group_name: string
|
||||
interval_seconds: number
|
||||
jitter_seconds: number
|
||||
enabled: boolean
|
||||
// 高级设置快照
|
||||
template_id: number | null
|
||||
@@ -272,6 +279,7 @@ const form = reactive<MonitorForm>({
|
||||
extra_models: [],
|
||||
group_name: '',
|
||||
interval_seconds: systemDefaultInterval.value,
|
||||
jitter_seconds: 0,
|
||||
enabled: true,
|
||||
template_id: null,
|
||||
extra_headers: {},
|
||||
@@ -279,6 +287,9 @@ const form = reactive<MonitorForm>({
|
||||
body_override: null,
|
||||
})
|
||||
|
||||
// jitter 上限与后端校验一致:interval - jitter 不得低于最小检测间隔 15 秒。
|
||||
const maxJitterSeconds = computed<number>(() => Math.max(0, (form.interval_seconds || 0) - 15))
|
||||
|
||||
let suppressFormWatchers = false
|
||||
|
||||
// 可用模板列表(进入 dialog 时一次性拉取 cache;按 provider / api mode 过滤)。
|
||||
@@ -419,6 +430,7 @@ function resetForm() {
|
||||
form.extra_models = []
|
||||
form.group_name = ''
|
||||
form.interval_seconds = systemDefaultInterval.value
|
||||
form.jitter_seconds = 0
|
||||
form.enabled = true
|
||||
form.template_id = null
|
||||
form.extra_headers = {}
|
||||
@@ -438,6 +450,7 @@ function loadFromMonitor(m: ChannelMonitor) {
|
||||
form.extra_models = [...(m.extra_models || [])]
|
||||
form.group_name = m.group_name || ''
|
||||
form.interval_seconds = m.interval_seconds || systemDefaultInterval.value
|
||||
form.jitter_seconds = m.jitter_seconds || 0
|
||||
form.enabled = m.enabled
|
||||
form.template_id = m.template_id ?? null
|
||||
form.extra_headers = { ...(m.extra_headers || {}) }
|
||||
@@ -504,6 +517,7 @@ function buildPayload(): CreateParams {
|
||||
group_name: form.group_name.trim(),
|
||||
enabled: form.enabled,
|
||||
interval_seconds: form.interval_seconds,
|
||||
jitter_seconds: form.jitter_seconds || 0,
|
||||
template_id: form.template_id,
|
||||
extra_headers: form.extra_headers,
|
||||
body_override_mode: form.body_override_mode,
|
||||
|
||||
@@ -2835,6 +2835,8 @@ export default {
|
||||
groupNamePlaceholder: 'Optional, used to group rows in user view',
|
||||
intervalSeconds: 'Interval (seconds)',
|
||||
intervalSecondsHint: 'Range: 15 - 3600 seconds',
|
||||
jitterSeconds: 'Random Jitter (± seconds)',
|
||||
jitterSecondsHint: 'Each check fires at interval ± a random offset within this value; 0 means fixed interval. Interval minus jitter must be ≥ 15s',
|
||||
enabled: 'Enable monitor',
|
||||
kindRequired: 'Please select a provider'
|
||||
},
|
||||
|
||||
@@ -2912,6 +2912,8 @@ export default {
|
||||
groupNamePlaceholder: '可选,用于在用户视图中聚合显示',
|
||||
intervalSeconds: '检测间隔 (秒)',
|
||||
intervalSecondsHint: '范围:15 - 3600 秒',
|
||||
jitterSeconds: '随机抖动 (± 秒)',
|
||||
jitterSecondsHint: '每次检测在间隔基础上正负随机偏移该秒数,0 表示固定间隔;需满足 间隔 - 抖动 ≥ 15 秒',
|
||||
enabled: '启用监控',
|
||||
kindRequired: '请选择供应商'
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user