Files
sub2api/backend/migrations/128_add_channel_monitor_request_templates.sql
erio a296425994 feat(channel-monitor): request templates with snapshot apply + headers/body override
Problem:
Upstream channels can reject monitor probes based on client fingerprint
(e.g. "only Claude Code clients allowed"). The monitor had no way to
customize the outgoing request to bypass such restrictions.

Solution:
Introduce reusable request templates that carry extra_headers plus an
optional body override; monitors reference a template and receive a
snapshot copy on apply. Template edits do NOT auto-propagate — users
must click "apply to associated monitors" to refresh snapshots, so a
bad template edit cannot instantly break all production monitors.

Data model (migration 112):
- channel_monitor_request_templates: id, name, provider, description,
  extra_headers jsonb, body_override_mode ('off'|'merge'|'replace'),
  body_override jsonb. Unique (provider, name).
- channel_monitors: +template_id (FK, ON DELETE SET NULL), +extra_headers,
  +body_override_mode, +body_override (the three runtime snapshot fields).

Checker (channel_monitor_checker.go):
- callProvider + runCheckForModel accept a CheckOptions carrying the
  snapshot fields. mergeHeaders applies user headers on top of adapter
  defaults (forbidden list: Host / Content-Length / Transfer-Encoding /
  Connection / Content-Encoding).
- buildRequestBody:
    off     -> adapter default body
    merge   -> shallow-merge over default; per-provider deny list
               (model/messages/contents) protects the challenge contract
    replace -> user body verbatim
- Replace mode skips challenge validation; instead HTTP 2xx + non-empty
  extracted response text = operational, empty = failed.
- 4 new unit tests cover all three modes + replace/empty-response case.

Admin API:
- /admin/channel-monitor-templates CRUD + /:id/apply (overwrite snapshot
  on all template_id=id monitors, returns affected count).
- channel_monitor request/response DTOs gain the 4 new fields.

Frontend:
- channelMonitorTemplate.ts API client.
- MonitorAdvancedRequestConfig.vue shared component for headers textarea
  + body mode radio + body JSON editor; used by both template and monitor
  forms.
- MonitorTemplateManagerDialog.vue: provider tabs, list/create/edit/
  delete/apply, live "associated monitors" count per row.
- MonitorFiltersBar: new 模板管理 button next to 新增监控.
- MonitorFormDialog: collapsible 高级 section with template dropdown
  (filtered by form.provider, clears on provider change) + embedded
  AdvancedRequestConfig. Picking a template copies its fields into the
  form (snapshot semantics mirrored on the client).
- i18n zh/en entries for all new copy.

chore: bump version to 0.1.114.32
2026-04-21 14:14:49 +08:00

71 lines
3.3 KiB
SQL
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.

-- Migration: 128_add_channel_monitor_request_templates
-- 加请求模板表 + 给 channel_monitors 加 4 个快照字段template_id 关联引用 + extra_headers /
-- body_override_mode / body_override 三个真正运行时使用的快照)。
--
-- 设计要点:
-- 1) 模板与监控之间是「应用即拷贝」的快照语义,运行时 checker 不再回查模板表。
-- 模板 UPDATE 不会自动影响监控;只有用户主动「应用到关联监控」才会刷新快照。
-- 2) ON DELETE SET NULL模板删除不级联清理监控监控保留快照继续工作。
-- 3) extra_headers / body_override 都是 JSONBbody_override_mode 用 varchar不是 enum
-- 便于将来加新模式无需 ALTER TYPE。
-- 4) 同一 provider 内模板 name 唯一(允许 Anthropic + OpenAI 重名 "伪装官方客户端")。
CREATE TABLE IF NOT EXISTS channel_monitor_request_templates (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
provider VARCHAR(20) NOT NULL,
description VARCHAR(500) NOT NULL DEFAULT '',
extra_headers JSONB NOT NULL DEFAULT '{}'::jsonb,
body_override_mode VARCHAR(10) NOT NULL DEFAULT 'off',
body_override JSONB NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT channel_monitor_request_templates_provider_check
CHECK (provider IN ('openai', 'anthropic', 'gemini')),
CONSTRAINT channel_monitor_request_templates_body_mode_check
CHECK (body_override_mode IN ('off', 'merge', 'replace'))
);
CREATE UNIQUE INDEX IF NOT EXISTS channel_monitor_request_templates_provider_name
ON channel_monitor_request_templates (provider, name);
-- channel_monitors 加 4 列ADD COLUMN IF NOT EXISTS 需要 PG 9.6+,生产使用 PG 16
ALTER TABLE channel_monitors
ADD COLUMN IF NOT EXISTS template_id BIGINT NULL;
ALTER TABLE channel_monitors
ADD COLUMN IF NOT EXISTS extra_headers JSONB NOT NULL DEFAULT '{}'::jsonb;
ALTER TABLE channel_monitors
ADD COLUMN IF NOT EXISTS body_override_mode VARCHAR(10) NOT NULL DEFAULT 'off';
ALTER TABLE channel_monitors
ADD COLUMN IF NOT EXISTS body_override JSONB NULL;
-- 约束 + 外键DO 块里 IF NOT EXISTS 判断,保证幂等)
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE constraint_name = 'channel_monitors_body_mode_check'
AND table_name = 'channel_monitors'
) THEN
ALTER TABLE channel_monitors
ADD CONSTRAINT channel_monitors_body_mode_check
CHECK (body_override_mode IN ('off', 'merge', 'replace'));
END IF;
IF NOT EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE constraint_name = 'channel_monitors_template_id_fkey'
AND table_name = 'channel_monitors'
) THEN
ALTER TABLE channel_monitors
ADD CONSTRAINT channel_monitors_template_id_fkey
FOREIGN KEY (template_id)
REFERENCES channel_monitor_request_templates (id)
ON DELETE SET NULL;
END IF;
END $$;
CREATE INDEX IF NOT EXISTS idx_channel_monitors_template_id
ON channel_monitors (template_id)
WHERE template_id IS NOT NULL;