feat(channel-monitor): add feature switch settings + fix extra_models save

Settings:
- New "功能开关" tab between 通用设置 and 安全与认证
- ChannelMonitorEnabled toggle: runner skips scheduling when false,
  user-facing list returns empty
- ChannelMonitorDefaultIntervalSeconds (15-3600): pre-fills interval
  when creating a new monitor; each monitor can still override

Bug fix:
- ModelTagInput now commits pending input on blur, not just Enter/Tab.
  Previously clicking "save" with an un-Enter'd extra model would drop
  the value (DB stored extra_models=[] even when user typed entries).

Backend:
- domain_constants: SettingKeyChannelMonitor{Enabled,DefaultIntervalSeconds}
- SettingService.GetChannelMonitorRuntime: lightweight getter used by
  runner tick + user handler per-request (fail-open on DB error)
- Runner tickDueChecks: bails early when feature disabled
- ChannelMonitorUserHandler: checks feature flag before serving
- Comment on runner doc: scheduler state is implicit (every tick re-reads
  ListEnabled from DB), so CRUD ops on monitors self-maintain the schedule

Bump VERSION to 0.1.114.25
This commit is contained in:
erio
2026-04-21 00:21:29 +08:00
parent a1425b457d
commit 7da5124067
18 changed files with 283 additions and 14 deletions

View File

@@ -14,11 +14,28 @@ import (
// ChannelMonitorUserHandler 渠道监控用户只读 handler。
type ChannelMonitorUserHandler struct {
monitorService *service.ChannelMonitorService
settingService *service.SettingService
}
// NewChannelMonitorUserHandler 创建 handler。
func NewChannelMonitorUserHandler(monitorService *service.ChannelMonitorService) *ChannelMonitorUserHandler {
return &ChannelMonitorUserHandler{monitorService: monitorService}
// settingService 用于每次请求前读取功能开关;关闭时 List/GetStatus 直接返回空/404。
func NewChannelMonitorUserHandler(
monitorService *service.ChannelMonitorService,
settingService *service.SettingService,
) *ChannelMonitorUserHandler {
return &ChannelMonitorUserHandler{
monitorService: monitorService,
settingService: settingService,
}
}
// featureEnabled 返回当前渠道监控功能是否开启。
// settingService 为 nil测试场景视为启用。
func (h *ChannelMonitorUserHandler) featureEnabled(c *gin.Context) bool {
if h.settingService == nil {
return true
}
return h.settingService.GetChannelMonitorRuntime(c.Request.Context()).Enabled
}
// --- Response ---
@@ -123,6 +140,10 @@ func userMonitorDetailToResponse(d *service.UserMonitorDetail) *channelMonitorUs
// List GET /api/v1/channel-monitors
func (h *ChannelMonitorUserHandler) List(c *gin.Context) {
if !h.featureEnabled(c) {
response.Success(c, gin.H{"items": []channelMonitorUserListItem{}})
return
}
views, err := h.monitorService.ListUserView(c.Request.Context())
if err != nil {
response.ErrorFrom(c, err)
@@ -137,6 +158,10 @@ func (h *ChannelMonitorUserHandler) List(c *gin.Context) {
// GetStatus GET /api/v1/channel-monitors/:id/status
func (h *ChannelMonitorUserHandler) GetStatus(c *gin.Context) {
if !h.featureEnabled(c) {
response.ErrorFrom(c, service.ErrChannelMonitorNotFound)
return
}
// 复用 admin.ParseChannelMonitorID 保持错误码与日志一致。
id, ok := admin.ParseChannelMonitorID(c)
if !ok {