Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
陈曦
sub2api
Commits
3b7a5fff
Commit
3b7a5fff
authored
Apr 27, 2026
by
陈曦
Browse files
补充openai、gemini以及流失请求的采集数据以及nfs落库
parent
8519a8eb
Pipeline
#82284
failed with stage
in 2 minutes and 21 seconds
Changes
180
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
backend/migration_release/123_fix_legacy_auth_source_grant_on_signup_defaults.sql
deleted
100644 → 0
View file @
8519a8eb
-- Auto-backfill untouched migration 110 signup-grant defaults to the corrected false value.
-- Rows still matching the migration-110 default payload and timestamp window are treated as
-- untouched legacy defaults; any remaining legacy true values are reported for manual review.
WITH
migration_110
AS
(
SELECT
applied_at
FROM
schema_migrations
WHERE
filename
=
'110_pending_auth_and_provider_default_grants.sql'
),
providers
AS
(
SELECT
provider_type
FROM
(
VALUES
(
'email'
),
(
'linuxdo'
),
(
'oidc'
),
(
'wechat'
)
)
AS
providers
(
provider_type
)
),
legacy_provider_defaults
AS
(
SELECT
providers
.
provider_type
FROM
providers
CROSS
JOIN
migration_110
JOIN
settings
balance
ON
balance
.
key
=
'auth_source_default_'
||
providers
.
provider_type
||
'_balance'
JOIN
settings
concurrency
ON
concurrency
.
key
=
'auth_source_default_'
||
providers
.
provider_type
||
'_concurrency'
JOIN
settings
subscriptions
ON
subscriptions
.
key
=
'auth_source_default_'
||
providers
.
provider_type
||
'_subscriptions'
JOIN
settings
grant_on_signup
ON
grant_on_signup
.
key
=
'auth_source_default_'
||
providers
.
provider_type
||
'_grant_on_signup'
JOIN
settings
grant_on_first_bind
ON
grant_on_first_bind
.
key
=
'auth_source_default_'
||
providers
.
provider_type
||
'_grant_on_first_bind'
WHERE
balance
.
value
=
'0'
AND
concurrency
.
value
=
'5'
AND
subscriptions
.
value
=
'[]'
AND
grant_on_signup
.
value
=
'true'
AND
grant_on_first_bind
.
value
=
'false'
AND
balance
.
updated_at
BETWEEN
migration_110
.
applied_at
-
INTERVAL
'1 minute'
AND
migration_110
.
applied_at
+
INTERVAL
'1 minute'
AND
concurrency
.
updated_at
BETWEEN
migration_110
.
applied_at
-
INTERVAL
'1 minute'
AND
migration_110
.
applied_at
+
INTERVAL
'1 minute'
AND
subscriptions
.
updated_at
BETWEEN
migration_110
.
applied_at
-
INTERVAL
'1 minute'
AND
migration_110
.
applied_at
+
INTERVAL
'1 minute'
AND
grant_on_signup
.
updated_at
BETWEEN
migration_110
.
applied_at
-
INTERVAL
'1 minute'
AND
migration_110
.
applied_at
+
INTERVAL
'1 minute'
AND
grant_on_first_bind
.
updated_at
BETWEEN
migration_110
.
applied_at
-
INTERVAL
'1 minute'
AND
migration_110
.
applied_at
+
INTERVAL
'1 minute'
),
updated_signup_grants
AS
(
UPDATE
settings
SET
value
=
'false'
,
updated_at
=
NOW
()
FROM
legacy_provider_defaults
WHERE
settings
.
key
=
'auth_source_default_'
||
legacy_provider_defaults
.
provider_type
||
'_grant_on_signup'
AND
settings
.
value
=
'true'
RETURNING
legacy_provider_defaults
.
provider_type
)
INSERT
INTO
auth_identity_migration_reports
(
report_type
,
report_key
,
details
)
SELECT
'legacy_auth_source_signup_grant_review'
,
providers
.
provider_type
,
jsonb_build_object
(
'provider_type'
,
providers
.
provider_type
,
'current_value'
,
grant_on_signup
.
value
,
'auto_backfilled'
,
FALSE
,
'reason'
,
'legacy_true_default_not_auto_backfilled'
)
FROM
providers
JOIN
settings
grant_on_signup
ON
grant_on_signup
.
key
=
'auth_source_default_'
||
providers
.
provider_type
||
'_grant_on_signup'
LEFT
JOIN
updated_signup_grants
ON
updated_signup_grants
.
provider_type
=
providers
.
provider_type
WHERE
grant_on_signup
.
value
=
'true'
AND
updated_signup_grants
.
provider_type
IS
NULL
ON
CONFLICT
(
report_type
,
report_key
)
DO
NOTHING
;
backend/migration_release/124_backfill_legacy_oidc_security_flags.sql
deleted
100644 → 0
View file @
8519a8eb
-- Preserve legacy OIDC behavior for upgraded installs that predate the
-- introduction of secure PKCE/id_token defaults. Fresh installs continue to
-- inherit runtime defaults when these rows are absent.
WITH
legacy_oidc_install
AS
(
SELECT
1
FROM
settings
WHERE
key
IN
(
'oidc_connect_enabled'
,
'oidc_connect_client_id'
,
'oidc_connect_authorize_url'
,
'oidc_connect_token_url'
,
'oidc_connect_issuer_url'
,
'oidc_connect_userinfo_url'
,
'oidc_connect_frontend_redirect_url'
)
LIMIT
1
)
INSERT
INTO
settings
(
key
,
value
)
SELECT
defaults
.
key
,
'false'
FROM
legacy_oidc_install
CROSS
JOIN
(
VALUES
(
'oidc_connect_use_pkce'
),
(
'oidc_connect_validate_id_token'
)
)
AS
defaults
(
key
)
WHERE
NOT
EXISTS
(
SELECT
1
FROM
settings
existing
WHERE
existing
.
key
=
defaults
.
key
)
ON
CONFLICT
(
key
)
DO
NOTHING
;
backend/migration_release/125_add_channel_monitors.sql
deleted
100644 → 0
View file @
8519a8eb
-- Migration: 125_add_channel_monitors
-- 渠道监控 MVP:周期性对外部 provider/endpoint/api_key 做模型心跳测试。
--
-- 表结构说明:
-- - channel_monitors 渠道配置表(一行 = 一个监控对象)
-- - channel_monitor_histories 检测历史明细表(一次检测一个模型 = 一行)
--
-- 设计要点:
-- - api_key_encrypted 列存放 AES-256-GCM 密文(base64),由 service 层加密。
-- - extra_models 用 JSONB 存储字符串数组,便于扩展(后续可加权重等元数据)。
-- - history 表通过 ON DELETE CASCADE 自动清理已删除监控的历史。
-- - (enabled, last_checked_at) 索引服务于调度器扫描“到期需要检测”的监控。
-- - histories 上 (monitor_id, model, checked_at DESC) 服务用户视图聚合查询;
-- 单独的 (checked_at) 索引服务定期清理 30 天前数据的 DELETE。
CREATE
TABLE
IF
NOT
EXISTS
channel_monitors
(
id
BIGSERIAL
PRIMARY
KEY
,
name
VARCHAR
(
100
)
NOT
NULL
,
provider
VARCHAR
(
20
)
NOT
NULL
,
-- openai / anthropic / gemini
endpoint
VARCHAR
(
500
)
NOT
NULL
,
-- base origin
api_key_encrypted
TEXT
NOT
NULL
,
-- AES-256-GCM (base64)
primary_model
VARCHAR
(
200
)
NOT
NULL
,
extra_models
JSONB
NOT
NULL
DEFAULT
'[]'
::
jsonb
,
group_name
VARCHAR
(
100
)
NOT
NULL
DEFAULT
''
,
enabled
BOOLEAN
NOT
NULL
DEFAULT
TRUE
,
interval_seconds
INT
NOT
NULL
,
last_checked_at
TIMESTAMPTZ
,
created_by
BIGINT
NOT
NULL
,
created_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
(),
updated_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
(),
CONSTRAINT
channel_monitors_provider_check
CHECK
(
provider
IN
(
'openai'
,
'anthropic'
,
'gemini'
)),
CONSTRAINT
channel_monitors_interval_check
CHECK
(
interval_seconds
BETWEEN
15
AND
3600
)
);
CREATE
INDEX
IF
NOT
EXISTS
idx_channel_monitors_enabled_last_checked
ON
channel_monitors
(
enabled
,
last_checked_at
);
CREATE
INDEX
IF
NOT
EXISTS
idx_channel_monitors_provider
ON
channel_monitors
(
provider
);
CREATE
INDEX
IF
NOT
EXISTS
idx_channel_monitors_group_name
ON
channel_monitors
(
group_name
);
CREATE
TABLE
IF
NOT
EXISTS
channel_monitor_histories
(
id
BIGSERIAL
PRIMARY
KEY
,
monitor_id
BIGINT
NOT
NULL
REFERENCES
channel_monitors
(
id
)
ON
DELETE
CASCADE
,
model
VARCHAR
(
200
)
NOT
NULL
,
status
VARCHAR
(
20
)
NOT
NULL
,
latency_ms
INT
,
ping_latency_ms
INT
,
message
VARCHAR
(
500
)
NOT
NULL
DEFAULT
''
,
checked_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
(),
CONSTRAINT
channel_monitor_histories_status_check
CHECK
(
status
IN
(
'operational'
,
'degraded'
,
'failed'
,
'error'
))
);
CREATE
INDEX
IF
NOT
EXISTS
idx_channel_monitor_histories_monitor_model_checked
ON
channel_monitor_histories
(
monitor_id
,
model
,
checked_at
DESC
);
CREATE
INDEX
IF
NOT
EXISTS
idx_channel_monitor_histories_checked_at
ON
channel_monitor_histories
(
checked_at
);
backend/migration_release/125_add_group_rpm_limit.sql
deleted
100644 → 0
View file @
8519a8eb
-- Add per-group Requests-Per-Minute limit.
-- rpm_limit: 分组统一 RPM 上限(0 = 不限制)。
-- 一旦配置即接管该用户在该分组的限流,覆盖用户级 users.rpm_limit。
-- 计数键:rpm:ug:{user_id}:{group_id}:{minute}。
ALTER
TABLE
groups
ADD
COLUMN
IF
NOT
EXISTS
rpm_limit
integer
NOT
NULL
DEFAULT
0
;
COMMENT
ON
COLUMN
groups
.
rpm_limit
IS
'分组 RPM 上限;0 表示不限制;设置后接管该分组用户的限流(覆盖用户级 rpm_limit)。'
;
backend/migration_release/126_add_channel_monitor_aggregation.sql
deleted
100644 → 0
View file @
8519a8eb
-- Migration: 126_add_channel_monitor_aggregation
-- 渠道监控日聚合:把 channel_monitor_histories 的明细按天聚合,明细只保留 1 天,
-- 聚合保留 30 天。明细和聚合表都用软删除(deleted_at),由 ops cleanup 任务每天
-- 凌晨随运维监控清理一起跑(共享 cron)。
--
-- 设计要点:
-- - channel_monitor_histories 加 deleted_at 软删除字段(SoftDeleteMixin 全局
-- Hook 会把 DELETE 自动改写成 UPDATE deleted_at = NOW())。
-- - channel_monitor_daily_rollups 按 (monitor_id, model, bucket_date) 唯一,
-- 用 ON CONFLICT DO UPDATE 实现幂等回填,状态分布和延迟分子分母都保留,
-- 方便后续按窗口任意求加权可用率和均值。
-- - watermark 表只有一行(id=1),记录最近一次聚合到达的日期,避免重启后重复
-- 扫全表。
-- - rollup 上 (bucket_date) 索引服务清理任务的 DELETE WHERE bucket_date < cutoff。
-- 1) 给历史明细表加软删除字段
ALTER
TABLE
channel_monitor_histories
ADD
COLUMN
IF
NOT
EXISTS
deleted_at
TIMESTAMPTZ
;
CREATE
INDEX
IF
NOT
EXISTS
idx_channel_monitor_histories_deleted_at
ON
channel_monitor_histories
(
deleted_at
);
-- 2) 创建日聚合表
CREATE
TABLE
IF
NOT
EXISTS
channel_monitor_daily_rollups
(
id
BIGSERIAL
PRIMARY
KEY
,
monitor_id
BIGINT
NOT
NULL
REFERENCES
channel_monitors
(
id
)
ON
DELETE
CASCADE
,
model
VARCHAR
(
200
)
NOT
NULL
,
bucket_date
DATE
NOT
NULL
,
total_checks
INT
NOT
NULL
DEFAULT
0
,
ok_count
INT
NOT
NULL
DEFAULT
0
,
operational_count
INT
NOT
NULL
DEFAULT
0
,
degraded_count
INT
NOT
NULL
DEFAULT
0
,
failed_count
INT
NOT
NULL
DEFAULT
0
,
error_count
INT
NOT
NULL
DEFAULT
0
,
sum_latency_ms
BIGINT
NOT
NULL
DEFAULT
0
,
count_latency
INT
NOT
NULL
DEFAULT
0
,
sum_ping_latency_ms
BIGINT
NOT
NULL
DEFAULT
0
,
count_ping_latency
INT
NOT
NULL
DEFAULT
0
,
computed_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
(),
deleted_at
TIMESTAMPTZ
);
CREATE
UNIQUE
INDEX
IF
NOT
EXISTS
idx_channel_monitor_daily_rollups_unique
ON
channel_monitor_daily_rollups
(
monitor_id
,
model
,
bucket_date
);
CREATE
INDEX
IF
NOT
EXISTS
idx_channel_monitor_daily_rollups_bucket
ON
channel_monitor_daily_rollups
(
bucket_date
);
CREATE
INDEX
IF
NOT
EXISTS
idx_channel_monitor_daily_rollups_deleted_at
ON
channel_monitor_daily_rollups
(
deleted_at
);
-- 3) 创建 watermark 表(单行:id=1)
CREATE
TABLE
IF
NOT
EXISTS
channel_monitor_aggregation_watermark
(
id
INT
PRIMARY
KEY
DEFAULT
1
,
last_aggregated_date
DATE
,
updated_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
(),
CONSTRAINT
channel_monitor_aggregation_watermark_singleton
CHECK
(
id
=
1
)
);
INSERT
INTO
channel_monitor_aggregation_watermark
(
id
,
last_aggregated_date
,
updated_at
)
VALUES
(
1
,
NULL
,
NOW
())
ON
CONFLICT
(
id
)
DO
NOTHING
;
backend/migration_release/126_add_user_rpm_limit.sql
deleted
100644 → 0
View file @
8519a8eb
-- Add per-user Requests-Per-Minute cap.
-- rpm_limit: 用户全局 RPM 兜底(0 = 不限制)。
-- 仅当所访问分组未设置 rpm_limit 且无 user-group rpm_override 时作为兜底生效。
-- 计数键:rpm:u:{user_id}:{minute}。
ALTER
TABLE
users
ADD
COLUMN
IF
NOT
EXISTS
rpm_limit
integer
NOT
NULL
DEFAULT
0
;
COMMENT
ON
COLUMN
users
.
rpm_limit
IS
'用户级 RPM 兜底上限;0 表示不限制;仅当分组未设置 rpm_limit 时生效。'
;
backend/migration_release/127_add_user_group_rpm_override.sql
deleted
100644 → 0
View file @
8519a8eb
-- 在已有的"用户专属分组倍率表"上扩展 rpm_override 列;同时放宽 rate_multiplier 为可空,
-- 使一行记录可以只覆盖 rate、只覆盖 rpm,或同时覆盖两者。
-- 语义:
-- - rate_multiplier NULL → 该用户在此分组使用 groups.rate_multiplier 默认值
-- - rate_multiplier 非 NULL → 覆盖分组默认计费倍率
-- - rpm_override NULL → 该用户在此分组使用 groups.rpm_limit 默认值
-- - rpm_override 非 NULL → 覆盖分组默认 RPM(0 = 不限制)
-- 用户级 users.rpm_limit 仍独立生效(跨分组总配额)。
ALTER
TABLE
user_group_rate_multipliers
ADD
COLUMN
IF
NOT
EXISTS
rpm_override
integer
NULL
;
ALTER
TABLE
user_group_rate_multipliers
ALTER
COLUMN
rate_multiplier
DROP
NOT
NULL
;
COMMENT
ON
COLUMN
user_group_rate_multipliers
.
rate_multiplier
IS
'专属计费倍率;NULL 表示沿用分组默认倍率。'
;
COMMENT
ON
COLUMN
user_group_rate_multipliers
.
rpm_override
IS
'专属 RPM 上限;NULL 表示沿用分组默认;0 表示该用户在此分组不受 RPM 限制。'
;
backend/migration_release/127_drop_channel_monitor_deleted_at.sql
deleted
100644 → 0
View file @
8519a8eb
-- Migration: 127_drop_channel_monitor_deleted_at
-- 纠正 110 引入的 SoftDeleteMixin:日志/聚合表无恢复需求,软删会让行和索引只增不减,
-- 徒增磁盘和查询开销。改回分批物理删(由 OpsCleanupService 每天凌晨统一调度,
-- deleteOldRowsByID 模板,batch=5000)。
--
-- 110 尚未跑过聚合/清理(首次 maintenance 在次日 02:00),所以此处不担心业务数据。
-- 直接 DROP 列 + 索引;对应的 Go 侧 ent schema 已移除 SoftDeleteMixin、repo 的
-- raw SQL 已移除 deleted_at IS NULL 过滤。
DROP
INDEX
IF
EXISTS
idx_channel_monitor_histories_deleted_at
;
ALTER
TABLE
channel_monitor_histories
DROP
COLUMN
IF
EXISTS
deleted_at
;
DROP
INDEX
IF
EXISTS
idx_channel_monitor_daily_rollups_deleted_at
;
ALTER
TABLE
channel_monitor_daily_rollups
DROP
COLUMN
IF
EXISTS
deleted_at
;
backend/migration_release/128_add_channel_monitor_request_templates.sql
deleted
100644 → 0
View file @
8519a8eb
-- 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 都是 JSONB;body_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
;
backend/migration_release/129_seed_claude_code_template.sql
deleted
100644 → 0
View file @
8519a8eb
-- Migration: 129_seed_claude_code_template
-- 内置「Claude Code 伪装」请求模板,覆盖 Anthropic 上游对官方 CLI 客户端的所有验证项:
-- 1) User-Agent / X-App / anthropic-beta / anthropic-version 等头
-- 2) system 数组首项与官方 system prompt 字面一致(Dice >= 0.5)
-- 3) metadata.user_id 满足 ParseMetadataUserID — 这里用 legacy 格式(user_<64hex>_account_<uuid>_session_<36char>)
-- 避免新版 JSON 字符串内嵌 JSON 在编辑器里出现一长串 \" 转义,便于用户阅读。
--
-- ON CONFLICT DO NOTHING:已部署环境(手动建过模板)跑此 migration 不会重复 / 覆盖。
-- 用户可自行编辑后续覆盖此 seed;CC 升大版时再起一条 migration 提供新模板,不动用户的旧模板。
INSERT
INTO
channel_monitor_request_templates
(
name
,
provider
,
description
,
extra_headers
,
body_override_mode
,
body_override
)
VALUES
(
'Claude Code 伪装'
,
'anthropic'
,
'完整模拟 Claude Code 2.1.114 客户端:UA + anthropic-beta + system + metadata.user_id 全部对齐,绕过 Anthropic 上游
''
Claude Code only
''
限制(如 Max 套餐)。'
,
'{
"User-Agent": "claude-cli/2.1.114 (external, sdk-cli)",
"X-App": "cli",
"anthropic-version": "2023-06-01",
"anthropic-beta": "claude-code-20250219,interleaved-thinking-2025-05-14,context-management-2025-06-27,prompt-caching-scope-2026-01-05,advisor-tool-2026-03-01",
"anthropic-dangerous-direct-browser-access": "true"
}'
::
jsonb
,
'merge'
,
'{
"system": [
{
"type": "text",
"text": "You are Claude Code, Anthropic
''
s official CLI for Claude."
}
],
"metadata": {
"user_id": "user_0000000000000000000000000000000000000000000000000000000000000000_account_00000000-0000-0000-0000-000000000000_session_00000000-0000-0000-0000-000000000000"
}
}'
::
jsonb
)
ON
CONFLICT
(
provider
,
name
)
DO
NOTHING
;
backend/migration_release/130_add_user_affiliates.sql
0 → 100644
View file @
3b7a5fff
CREATE
TABLE
IF
NOT
EXISTS
user_affiliates
(
user_id
BIGINT
PRIMARY
KEY
REFERENCES
users
(
id
)
ON
DELETE
CASCADE
,
aff_code
VARCHAR
(
32
)
NOT
NULL
UNIQUE
,
inviter_id
BIGINT
NULL
REFERENCES
users
(
id
)
ON
DELETE
SET
NULL
,
aff_count
INTEGER
NOT
NULL
DEFAULT
0
,
aff_quota
DECIMAL
(
20
,
8
)
NOT
NULL
DEFAULT
0
,
aff_history_quota
DECIMAL
(
20
,
8
)
NOT
NULL
DEFAULT
0
,
created_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
(),
updated_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
()
);
CREATE
INDEX
IF
NOT
EXISTS
idx_user_affiliates_inviter_id
ON
user_affiliates
(
inviter_id
);
CREATE
INDEX
IF
NOT
EXISTS
idx_user_affiliates_aff_quota
ON
user_affiliates
(
aff_quota
);
COMMENT
ON
TABLE
user_affiliates
IS
'用户邀请返利信息'
;
COMMENT
ON
COLUMN
user_affiliates
.
aff_code
IS
'用户邀请代码'
;
COMMENT
ON
COLUMN
user_affiliates
.
inviter_id
IS
'邀请人用户ID'
;
COMMENT
ON
COLUMN
user_affiliates
.
aff_count
IS
'累计邀请人数'
;
COMMENT
ON
COLUMN
user_affiliates
.
aff_quota
IS
'当前可提取返利金额'
;
COMMENT
ON
COLUMN
user_affiliates
.
aff_history_quota
IS
'累计返利历史金额'
;
backend/migration_release/131_affiliate_rebate_hardening.sql
0 → 100644
View file @
3b7a5fff
-- 1) Normalize historical affiliate rebate rate values.
-- Legacy compatibility treated 0<x<=1 as fractional inputs (e.g. 0.2 => 20%).
-- We now use pure percentage semantics, so convert persisted fractional values once.
UPDATE
settings
SET
value
=
to_char
((
value
::
numeric
*
100
),
'FM999999990.########'
),
updated_at
=
NOW
()
WHERE
key
=
'affiliate_rebate_rate'
AND
value
~
'^-?[0-9]+(
\\
.[0-9]+)?$'
AND
value
::
numeric
>
0
AND
value
::
numeric
<=
1
;
-- 2) Affiliate ledger for accrual/transfer traceability.
CREATE
TABLE
IF
NOT
EXISTS
user_affiliate_ledger
(
id
BIGSERIAL
PRIMARY
KEY
,
user_id
BIGINT
NOT
NULL
REFERENCES
users
(
id
)
ON
DELETE
CASCADE
,
action
VARCHAR
(
32
)
NOT
NULL
,
amount
DECIMAL
(
20
,
8
)
NOT
NULL
,
source_user_id
BIGINT
NULL
REFERENCES
users
(
id
)
ON
DELETE
SET
NULL
,
created_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
(),
updated_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
()
);
CREATE
INDEX
IF
NOT
EXISTS
idx_user_affiliate_ledger_user_id
ON
user_affiliate_ledger
(
user_id
);
CREATE
INDEX
IF
NOT
EXISTS
idx_user_affiliate_ledger_action
ON
user_affiliate_ledger
(
action
);
COMMENT
ON
TABLE
user_affiliate_ledger
IS
'邀请返利资金流水(累计/转入)'
;
COMMENT
ON
COLUMN
user_affiliate_ledger
.
action
IS
'accrue|transfer'
;
-- 3) Enforce idempotency at DB layer for payment audit actions.
WITH
ranked
AS
(
SELECT
id
,
ROW_NUMBER
()
OVER
(
PARTITION
BY
order_id
,
action
ORDER
BY
id
)
AS
rn
FROM
payment_audit_logs
)
DELETE
FROM
payment_audit_logs
p
USING
ranked
r
WHERE
p
.
id
=
r
.
id
AND
r
.
rn
>
1
;
CREATE
UNIQUE
INDEX
IF
NOT
EXISTS
idx_payment_audit_logs_order_action_uniq
ON
payment_audit_logs
(
order_id
,
action
);
-- 4) Prevent retroactive affiliate rebate issuance for legacy completed balance orders.
INSERT
INTO
payment_audit_logs
(
order_id
,
action
,
detail
,
operator
,
created_at
)
SELECT
po
.
id
::
text
,
'AFFILIATE_REBATE_SKIPPED'
,
'{"reason":"baseline before affiliate rebate idempotency rollout"}'
,
'system'
,
NOW
()
FROM
payment_orders
po
WHERE
po
.
order_type
=
'balance'
AND
po
.
status
=
'COMPLETED'
AND
NOT
EXISTS
(
SELECT
1
FROM
payment_audit_logs
pal
WHERE
pal
.
order_id
=
po
.
id
::
text
AND
pal
.
action
IN
(
'AFFILIATE_REBATE_APPLIED'
,
'AFFILIATE_REBATE_SKIPPED'
)
);
backend/migration_release/132_affiliate_custom_settings.sql
0 → 100644
View file @
3b7a5fff
-- 邀请返利:用户专属配置增强
-- 1) aff_rebate_rate_percent: 用户作为邀请人时的专属返利比例(百分比,NULL 表示沿用全局比例)
-- 2) aff_code_custom: 标记当前 aff_code 是否被管理员手动改写过(用于"专属用户"列表筛选)
ALTER
TABLE
user_affiliates
ADD
COLUMN
IF
NOT
EXISTS
aff_rebate_rate_percent
DECIMAL
(
5
,
2
);
ALTER
TABLE
user_affiliates
ADD
COLUMN
IF
NOT
EXISTS
aff_code_custom
BOOLEAN
NOT
NULL
DEFAULT
false
;
CREATE
INDEX
IF
NOT
EXISTS
idx_user_affiliates_admin_settings
ON
user_affiliates
(
updated_at
)
WHERE
aff_code_custom
=
true
OR
aff_rebate_rate_percent
IS
NOT
NULL
;
COMMENT
ON
COLUMN
user_affiliates
.
aff_rebate_rate_percent
IS
'专属返利比例(百分比 0-100,NULL 表示沿用全局)'
;
COMMENT
ON
COLUMN
user_affiliates
.
aff_code_custom
IS
'邀请码是否由管理员改写过(用于专属用户筛选)'
;
backend/migration_release/133_affiliate_rebate_freeze.sql
0 → 100644
View file @
3b7a5fff
-- 1) Add frozen quota column to user_affiliates for rebate freeze period.
ALTER
TABLE
user_affiliates
ADD
COLUMN
IF
NOT
EXISTS
aff_frozen_quota
DECIMAL
(
20
,
8
)
NOT
NULL
DEFAULT
0
;
COMMENT
ON
COLUMN
user_affiliates
.
aff_frozen_quota
IS
'Rebate quota currently frozen (pending thaw after freeze period)'
;
-- 2) Add frozen_until column to user_affiliate_ledger for per-entry freeze tracking.
-- NULL = no freeze (or already thawed); non-NULL = frozen until this timestamp.
ALTER
TABLE
user_affiliate_ledger
ADD
COLUMN
IF
NOT
EXISTS
frozen_until
TIMESTAMPTZ
NULL
;
COMMENT
ON
COLUMN
user_affiliate_ledger
.
frozen_until
IS
'Rebate frozen until this time; NULL means already thawed or never frozen'
;
-- 3) Partial index for efficient thaw queries (only rows still frozen).
CREATE
INDEX
IF
NOT
EXISTS
idx_ual_frozen_thaw
ON
user_affiliate_ledger
(
user_id
,
frozen_until
)
WHERE
frozen_until
IS
NOT
NULL
;
backend/migrations/130_add_user_affiliates.sql
0 → 100644
View file @
3b7a5fff
CREATE
TABLE
IF
NOT
EXISTS
user_affiliates
(
user_id
BIGINT
PRIMARY
KEY
REFERENCES
users
(
id
)
ON
DELETE
CASCADE
,
aff_code
VARCHAR
(
32
)
NOT
NULL
UNIQUE
,
inviter_id
BIGINT
NULL
REFERENCES
users
(
id
)
ON
DELETE
SET
NULL
,
aff_count
INTEGER
NOT
NULL
DEFAULT
0
,
aff_quota
DECIMAL
(
20
,
8
)
NOT
NULL
DEFAULT
0
,
aff_history_quota
DECIMAL
(
20
,
8
)
NOT
NULL
DEFAULT
0
,
created_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
(),
updated_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
()
);
CREATE
INDEX
IF
NOT
EXISTS
idx_user_affiliates_inviter_id
ON
user_affiliates
(
inviter_id
);
CREATE
INDEX
IF
NOT
EXISTS
idx_user_affiliates_aff_quota
ON
user_affiliates
(
aff_quota
);
COMMENT
ON
TABLE
user_affiliates
IS
'用户邀请返利信息'
;
COMMENT
ON
COLUMN
user_affiliates
.
aff_code
IS
'用户邀请代码'
;
COMMENT
ON
COLUMN
user_affiliates
.
inviter_id
IS
'邀请人用户ID'
;
COMMENT
ON
COLUMN
user_affiliates
.
aff_count
IS
'累计邀请人数'
;
COMMENT
ON
COLUMN
user_affiliates
.
aff_quota
IS
'当前可提取返利金额'
;
COMMENT
ON
COLUMN
user_affiliates
.
aff_history_quota
IS
'累计返利历史金额'
;
backend/migrations/131_affiliate_rebate_hardening.sql
0 → 100644
View file @
3b7a5fff
-- 1) Normalize historical affiliate rebate rate values.
-- Legacy compatibility treated 0<x<=1 as fractional inputs (e.g. 0.2 => 20%).
-- We now use pure percentage semantics, so convert persisted fractional values once.
UPDATE
settings
SET
value
=
to_char
((
value
::
numeric
*
100
),
'FM999999990.########'
),
updated_at
=
NOW
()
WHERE
key
=
'affiliate_rebate_rate'
AND
value
~
'^-?[0-9]+(
\\
.[0-9]+)?$'
AND
value
::
numeric
>
0
AND
value
::
numeric
<=
1
;
-- 2) Affiliate ledger for accrual/transfer traceability.
CREATE
TABLE
IF
NOT
EXISTS
user_affiliate_ledger
(
id
BIGSERIAL
PRIMARY
KEY
,
user_id
BIGINT
NOT
NULL
REFERENCES
users
(
id
)
ON
DELETE
CASCADE
,
action
VARCHAR
(
32
)
NOT
NULL
,
amount
DECIMAL
(
20
,
8
)
NOT
NULL
,
source_user_id
BIGINT
NULL
REFERENCES
users
(
id
)
ON
DELETE
SET
NULL
,
created_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
(),
updated_at
TIMESTAMPTZ
NOT
NULL
DEFAULT
NOW
()
);
CREATE
INDEX
IF
NOT
EXISTS
idx_user_affiliate_ledger_user_id
ON
user_affiliate_ledger
(
user_id
);
CREATE
INDEX
IF
NOT
EXISTS
idx_user_affiliate_ledger_action
ON
user_affiliate_ledger
(
action
);
COMMENT
ON
TABLE
user_affiliate_ledger
IS
'邀请返利资金流水(累计/转入)'
;
COMMENT
ON
COLUMN
user_affiliate_ledger
.
action
IS
'accrue|transfer'
;
-- 3) Enforce idempotency at DB layer for payment audit actions.
WITH
ranked
AS
(
SELECT
id
,
ROW_NUMBER
()
OVER
(
PARTITION
BY
order_id
,
action
ORDER
BY
id
)
AS
rn
FROM
payment_audit_logs
)
DELETE
FROM
payment_audit_logs
p
USING
ranked
r
WHERE
p
.
id
=
r
.
id
AND
r
.
rn
>
1
;
CREATE
UNIQUE
INDEX
IF
NOT
EXISTS
idx_payment_audit_logs_order_action_uniq
ON
payment_audit_logs
(
order_id
,
action
);
-- 4) Prevent retroactive affiliate rebate issuance for legacy completed balance orders.
INSERT
INTO
payment_audit_logs
(
order_id
,
action
,
detail
,
operator
,
created_at
)
SELECT
po
.
id
::
text
,
'AFFILIATE_REBATE_SKIPPED'
,
'{"reason":"baseline before affiliate rebate idempotency rollout"}'
,
'system'
,
NOW
()
FROM
payment_orders
po
WHERE
po
.
order_type
=
'balance'
AND
po
.
status
=
'COMPLETED'
AND
NOT
EXISTS
(
SELECT
1
FROM
payment_audit_logs
pal
WHERE
pal
.
order_id
=
po
.
id
::
text
AND
pal
.
action
IN
(
'AFFILIATE_REBATE_APPLIED'
,
'AFFILIATE_REBATE_SKIPPED'
)
);
backend/migrations/132_affiliate_custom_settings.sql
0 → 100644
View file @
3b7a5fff
-- 邀请返利:用户专属配置增强
-- 1) aff_rebate_rate_percent: 用户作为邀请人时的专属返利比例(百分比,NULL 表示沿用全局比例)
-- 2) aff_code_custom: 标记当前 aff_code 是否被管理员手动改写过(用于"专属用户"列表筛选)
ALTER
TABLE
user_affiliates
ADD
COLUMN
IF
NOT
EXISTS
aff_rebate_rate_percent
DECIMAL
(
5
,
2
);
ALTER
TABLE
user_affiliates
ADD
COLUMN
IF
NOT
EXISTS
aff_code_custom
BOOLEAN
NOT
NULL
DEFAULT
false
;
CREATE
INDEX
IF
NOT
EXISTS
idx_user_affiliates_admin_settings
ON
user_affiliates
(
updated_at
)
WHERE
aff_code_custom
=
true
OR
aff_rebate_rate_percent
IS
NOT
NULL
;
COMMENT
ON
COLUMN
user_affiliates
.
aff_rebate_rate_percent
IS
'专属返利比例(百分比 0-100,NULL 表示沿用全局)'
;
COMMENT
ON
COLUMN
user_affiliates
.
aff_code_custom
IS
'邀请码是否由管理员改写过(用于专属用户筛选)'
;
backend/migrations/133_affiliate_rebate_freeze.sql
0 → 100644
View file @
3b7a5fff
-- 1) Add frozen quota column to user_affiliates for rebate freeze period.
ALTER
TABLE
user_affiliates
ADD
COLUMN
IF
NOT
EXISTS
aff_frozen_quota
DECIMAL
(
20
,
8
)
NOT
NULL
DEFAULT
0
;
COMMENT
ON
COLUMN
user_affiliates
.
aff_frozen_quota
IS
'Rebate quota currently frozen (pending thaw after freeze period)'
;
-- 2) Add frozen_until column to user_affiliate_ledger for per-entry freeze tracking.
-- NULL = no freeze (or already thawed); non-NULL = frozen until this timestamp.
ALTER
TABLE
user_affiliate_ledger
ADD
COLUMN
IF
NOT
EXISTS
frozen_until
TIMESTAMPTZ
NULL
;
COMMENT
ON
COLUMN
user_affiliate_ledger
.
frozen_until
IS
'Rebate frozen until this time; NULL means already thawed or never frozen'
;
-- 3) Partial index for efficient thaw queries (only rows still frozen).
CREATE
INDEX
IF
NOT
EXISTS
idx_ual_frozen_thaw
ON
user_affiliate_ledger
(
user_id
,
frozen_until
)
WHERE
frozen_until
IS
NOT
NULL
;
deploy/.env.example
View file @
3b7a5fff
...
@@ -376,6 +376,17 @@ GEMINI_QUOTA_POLICY=
...
@@ -376,6 +376,17 @@ GEMINI_QUOTA_POLICY=
# 设置为 false 可在左侧栏隐藏运维监控菜单并禁用所有运维监控功能
# 设置为 false 可在左侧栏隐藏运维监控菜单并禁用所有运维监控功能
OPS_ENABLED=true
OPS_ENABLED=true
# -----------------------------------------------------------------------------
# Request Capture Configuration (Optional)
# 请求捕获配置(可选,按 API Key 开启,用于审计/调试)
# -----------------------------------------------------------------------------
# Local NFS mount path for writing request/response files (leave empty for DB-only mode)
# 本地挂载的 NFS 根目录,留空则跳过文件写入(仅写数据库)
REQUEST_CAPTURE_NFS_PATH=/app/logs/nfs/
# Async write timeout in seconds (default: 5)
# 单次异步写入超时时间(秒,默认 5)
REQUEST_CAPTURE_WORKER_TIMEOUT_SECONDS=5
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
# Update Configuration (在线更新配置)
# Update Configuration (在线更新配置)
# -----------------------------------------------------------------------------
# -----------------------------------------------------------------------------
...
...
frontend/src/api/__tests__/auth-oauth-adoption.spec.ts
View file @
3b7a5fff
...
@@ -74,6 +74,26 @@ describe('oauth adoption auth api', () => {
...
@@ -74,6 +74,26 @@ describe('oauth adoption auth api', () => {
})
})
})
})
it
(
'
posts affiliate code when completing linuxdo oauth registration
'
,
async
()
=>
{
const
{
completeLinuxDoOAuthRegistration
}
=
await
import
(
'
@/api/auth
'
)
await
completeLinuxDoOAuthRegistration
(
'
invite-code
'
,
{
adoptDisplayName
:
true
,
adoptAvatar
:
false
},
'
AFF123
'
)
expect
(
post
).
toHaveBeenCalledWith
(
'
/auth/oauth/linuxdo/complete-registration
'
,
{
invitation_code
:
'
invite-code
'
,
aff_code
:
'
AFF123
'
,
adopt_display_name
:
true
,
adopt_avatar
:
false
})
})
it
(
'
posts oidc invitation completion with adoption decisions
'
,
async
()
=>
{
it
(
'
posts oidc invitation completion with adoption decisions
'
,
async
()
=>
{
const
{
completeOIDCOAuthRegistration
}
=
await
import
(
'
@/api/auth
'
)
const
{
completeOIDCOAuthRegistration
}
=
await
import
(
'
@/api/auth
'
)
...
@@ -134,6 +154,26 @@ describe('oauth adoption auth api', () => {
...
@@ -134,6 +154,26 @@ describe('oauth adoption auth api', () => {
})
})
})
})
it
(
'
posts affiliate code when creating pending wechat oauth account
'
,
async
()
=>
{
const
{
createPendingWeChatOAuthAccount
}
=
await
import
(
'
@/api/auth
'
)
await
createPendingWeChatOAuthAccount
(
'
invite-code
'
,
{
adoptDisplayName
:
false
,
adoptAvatar
:
true
},
'
WXAFF
'
)
expect
(
post
).
toHaveBeenCalledWith
(
'
/auth/oauth/wechat/complete-registration
'
,
{
invitation_code
:
'
invite-code
'
,
aff_code
:
'
WXAFF
'
,
adopt_display_name
:
false
,
adopt_avatar
:
true
})
})
it
(
'
classifies oauth completion results as login or bind
'
,
async
()
=>
{
it
(
'
classifies oauth completion results as login or bind
'
,
async
()
=>
{
const
{
getOAuthCompletionKind
}
=
await
import
(
'
@/api/auth
'
)
const
{
getOAuthCompletionKind
}
=
await
import
(
'
@/api/auth
'
)
...
...
Prev
1
…
3
4
5
6
7
8
9
Next
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment