From 040b97914e8d401742d880cacdd00da9219c1f4f Mon Sep 17 00:00:00 2001 From: mediabot-pt <295750538+mediabot-pt@users.noreply.github.com> Date: Mon, 6 Jul 2026 21:11:21 +0800 Subject: [PATCH] refactor: simplify risk rules to KILL/WARN actions only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace complex type system (DEMOTE/TASK) with simple KILL/WARN action model - Each rule now has: type (monitoring category) + action (KILL=封号 / WARN=告警) - Type options: KILL/TRAFFIC/LOGIN/HD/RATIO (no DEMOTE/TASK) - Card left border color by action: red=KILL, orange=WARN - 12 preset templates with alert-based rules (60-day no-traffic warn, 140-day login warn, HnR warn, ratio warn) - Remove requirements sub-table and downloadThreshold fields --- web/src/views/RiskRules.vue | 185 +++++++++++------------------------- 1 file changed, 57 insertions(+), 128 deletions(-) diff --git a/web/src/views/RiskRules.vue b/web/src/views/RiskRules.vue index a220468..880906d 100644 --- a/web/src/views/RiskRules.vue +++ b/web/src/views/RiskRules.vue @@ -62,7 +62,7 @@
- {{ p.name }} + {{ p.name }} {{ typeLabel(p.type) }}
@@ -75,7 +75,7 @@
- - - - - - {{ x.label }} + + + + {{ x.label }} - + + + + + +
#{{ idx + 1 }} @@ -108,7 +111,7 @@
- +
适用条件 @@ -117,7 +120,7 @@ {{ applicableHint(rule.applicable) }}
- +
触发条件 当 @@ -130,7 +133,7 @@ → - {{ levelText(rule.level) }} + {{ actionText(rule.action) }}
@@ -138,33 +141,6 @@ 规则说明
- - -
-
- 考核要求 - + 添加 -
-
- - - - - - - - - - -
-
- - -
- 下载量阈值 - - GB,达到后要求分享率不低于阈值 -
@@ -172,10 +148,10 @@ - +
-

规则优先级:等级豁免 → 新手考核 → 动态分享率 → 存活策略 → 零活跃清理 → H&R 惩罚

-

满足豁免条件的用户不受任何清理规则影响。各参数值可在导入后按需调整。

+

规则体系:等级豁免 → 账号存活 → 流量监控 → H&R 惩罚 → 分享率检查

+

满足豁免条件的用户不受任何规则影响。导入后可调整参数和操作类型(封号/告警)。

{{ cat.title }}
@@ -185,7 +161,7 @@ {{ p.name }} - {{ levelText(p.level) }} + {{ actionText(p.action) }} {{ presetDesc(p) }}
@@ -204,19 +180,16 @@ import { Plus, ArrowDown, ArrowUp, Rank, Delete } from '@element-plus/icons-vue' import api from '../api' // ============ 选项配置 ============ -const levelOptions = [ - { value: 'CRITICAL', label: '严重' }, - { value: 'WARN', label: '关注' }, - { value: 'INFO', label: '提示' }, +const actionOptions = [ + { value: 'KILL', label: '封号' }, + { value: 'WARN', label: '告警' }, ] const typeOptions = [ - { value: 'KILL', label: '封号/删号' }, - { value: 'DEMOTE', label: '降级' }, - { value: 'TASK', label: '考核' }, - { value: 'TRAFFIC',label: '流量' }, - { value: 'LOGIN', label: '登录' }, - { value: 'HD', label: 'H&R' }, - { value: 'RATIO', label: '分享率' }, + { value: 'KILL', label: '封号规则' }, + { value: 'TRAFFIC', label: '流量监控' }, + { value: 'LOGIN', label: '登录监控' }, + { value: 'HD', label: 'H&R' }, + { value: 'RATIO', label: '分享率' }, ] const condOptions = [ { value: 'GT', label: '大于' }, @@ -244,65 +217,46 @@ const metricOptions = [ ] const typeLabel = (t) => typeOptions.find(x => x.value === t)?.label || t -const levelColor = (lv) => ({ CRITICAL: '#f56c6c', WARN: '#e6a23c', INFO: '#409eff' }[lv] || '#909399') -const levelText = (lv) => ({ CRITICAL: '严重', WARN: '关注', INFO: '提示' }[lv] || lv) +const actionColor = (a) => ({ KILL: '#f56c6c', WARN: '#e6a23c' }[a] || '#909399') +const actionText = (a) => ({ KILL: '封号', WARN: '告警' }[a] || a) const applicableHint = (a) => { const m = { '!sealed':'未封存用户', 'sealed':'已封存用户', 'zero_traffic':'上传/下载均为 0', 'new_account_30d':'从未完成种子且无流量增量' } return a ? `— ${m[a] || ''}` : '' } -// ============ 预设规则模板(按 PT 管理规范 6 大类) ============ +// ============ 预设规则模板 ============ +// 每条模板包含 type(监控类型)+ action(KILL/WARN 操作)+ 触发条件 const presets = [ - // 三、动态分享率 - { name:'动态分享率-降级', type:'DEMOTE', level:'WARN', metric:'ratio', threshold:0.95, unit:'倍', condition:'LT', downloadThreshold:100, applicable:'!sealed', description:'下载量达100GB后分享率低于0.95时降级' }, - { name:'动态分享率-严重', type:'DEMOTE', level:'CRITICAL', metric:'ratio', threshold:0.5, unit:'倍', condition:'LT', downloadThreshold:500, applicable:'!sealed', description:'下载量达500GB后分享率低于0.5时严重警告' }, // 四、账号存活策略 - { name:'未封存不登录清理', type:'KILL', level:'CRITICAL', metric:'days_since_last_login', threshold:150, unit:'天', condition:'GT', applicable:'!sealed', description:'未封存账号连续150天不登录删除' }, - { name:'封存账号不登录清理', type:'KILL', level:'CRITICAL', metric:'days_since_last_login', threshold:365, unit:'天', condition:'GT', applicable:'sealed', description:'封存账号连续365天不登录删除' }, - { name:'零流量账号清理', type:'KILL', level:'CRITICAL', metric:'days_since_last_login', threshold:30, unit:'天', condition:'GT', applicable:'zero_traffic', description:'零流量用户连续30天不登录删除' }, + { name:'未封存不登录封号', type:'KILL', action:'KILL', metric:'days_since_last_login', threshold:150, unit:'天', condition:'GT', applicable:'!sealed', description:'未封存账号连续150天不登录即封号' }, + { name:'封存账号不登录封号', type:'KILL', action:'KILL', metric:'days_since_last_login', threshold:365, unit:'天', condition:'GT', applicable:'sealed', description:'封存账号连续365天不登录即封号' }, + { name:'零流量账号清理', type:'KILL', action:'KILL', metric:'days_since_last_login', threshold:30, unit:'天', condition:'GT', applicable:'zero_traffic', description:'零流量用户连续30天不登录即封号' }, // 五、零活跃新用户清理 - { name:'零活跃不登录清理', type:'KILL', level:'CRITICAL', metric:'days_since_last_login', threshold:7, unit:'天', condition:'GT', applicable:'new_account_30d', description:'零活跃新用户连续7天不登录删除' }, - { name:'零活跃注册天数清理', type:'KILL', level:'CRITICAL', metric:'days_since_registration', threshold:30, unit:'天', condition:'GT', applicable:'new_account_30d', description:'零活跃新用户注册满30天删除' }, - // 六、H&R 惩罚 - { name:'H&R 封号', type:'HD', level:'CRITICAL', metric:'hnr_count', threshold:5, unit:'次', condition:'GTE', description:'H&R违规次数达到5次即封号' }, - { name:'H&R 警告', type:'HD', level:'WARN', metric:'hnr_count', threshold:3, unit:'次', condition:'GTE', description:'H&R违规次数达到3次告警' }, - // 补充:流量异常 - { name:'连续无流量封号', type:'TRAFFIC', level:'CRITICAL', metric:'days_since_last_traffic', threshold:90, unit:'天', condition:'GT', description:'连续90天无上传/下载流量封号' }, - { name:'长期未登录提醒', type:'LOGIN', level:'WARN', metric:'days_since_last_login', threshold:30, unit:'天', condition:'GT', description:'超过30天未登录提醒' }, + { name:'零活跃不登录封号', type:'KILL', action:'KILL', metric:'days_since_last_login', threshold:7, unit:'天', condition:'GT', applicable:'new_account_30d', description:'零活跃新用户连续7天不登录即封号' }, + { name:'零活跃注册天数封号', type:'KILL', action:'KILL', metric:'days_since_registration', threshold:30, unit:'天', condition:'GT', applicable:'new_account_30d', description:'零活跃新用户注册满30天即封号' }, + // 流量监控 + { name:'连续无流量封号', type:'TRAFFIC', action:'KILL', metric:'days_since_last_traffic', threshold:90, unit:'天', condition:'GT', description:'连续90天无上传/下载流量即封号' }, + { name:'无流量告警', type:'TRAFFIC', action:'WARN', metric:'days_since_last_traffic', threshold:60, unit:'天', condition:'GT', description:'连续60天无流量时发出告警提醒' }, + // 登录监控 + { name:'长期未登录告警', type:'LOGIN', action:'WARN', metric:'days_since_last_login', threshold:30, unit:'天', condition:'GT', description:'超过30天未登录时发出告警提醒' }, + { name:'即将触发封号提醒', type:'LOGIN', action:'WARN', metric:'days_since_last_login', threshold:140, unit:'天', condition:'GT', applicable:'!sealed', description:'未封存用户即将达到150天封号线时提前告警' }, + // H&R + { name:'H&R 封号', type:'HD', action:'KILL', metric:'hnr_count', threshold:5, unit:'次', condition:'GTE', description:'H&R违规次数达到5次即封号' }, + { name:'H&R 告警', type:'HD', action:'WARN', metric:'hnr_count', threshold:3, unit:'次', condition:'GTE', description:'H&R违规次数达到3次发出告警' }, + // 分享率 + { name:'分享率过低告警', type:'RATIO', action:'WARN', metric:'ratio', threshold:1.0, unit:'倍', condition:'LT', description:'分享率低于1.0时发出告警提醒' }, ] -// 新手考核模板(TASK 类型,含多要求) -const taskPresets = [ - { - name:'新手考核', type:'TASK', level:'CRITICAL', metric:'task', threshold:0, unit:'', condition:'GT', - applicable:'new_account_30d', - description:'新用户需在30天内完成考核:上传≥50GB、下载≥20GB、魔力≥10000、做种积分≥5000、平均做种≥24h', - requirements: [ - { label:'上传量', metric:'upload_gb', threshold:50, condition:'LT', unit:'GB' }, - { label:'下载量', metric:'download_gb', threshold:20, condition:'LT', unit:'GB' }, - { label:'魔力值', metric:'bonus', threshold:10000,condition:'LT', unit:'' }, - { label:'做种积分',metric:'seeding', threshold:5000, condition:'LT', unit:'' }, - { label:'平均做种',metric:'avg_seed_time', threshold:24, condition:'LT', unit:'h' }, - ] - }, -] - -const allPresets = [...presets, ...taskPresets] - const presetCategories = [ - { title:'三、动态分享率', items: presets.filter(p => p.type === 'DEMOTE') }, - { title:'四、账号存活策略', items: presets.filter(p => p.name.includes('不登录清理') || p.name.includes('账号清理')) }, + { title:'四、账号存活策略', items: presets.filter(p => p.name.includes('不登录') || p.name.includes('账号清理')) }, { title:'五、零活跃新用户清理', items: presets.filter(p => p.name.includes('零活跃')) }, - { title:'二、新手考核', items: taskPresets }, - { title:'六、H&R 惩罚', items: presets.filter(p => p.type === 'HD') }, - { title:'补充规则', items: presets.filter(p => !['DEMOTE','HD'].includes(p.type) && !p.name.includes('不登录') && !p.name.includes('零活跃')) }, + { title:'流量监控', items: presets.filter(p => p.type === 'TRAFFIC') }, + { title:'H&R 惩罚', items: presets.filter(p => p.type === 'HD') }, + { title:'登录提醒', items: presets.filter(p => p.type === 'LOGIN') }, + { title:'分享率监控', items: presets.filter(p => p.type === 'RATIO') }, ] const presetDesc = (p) => { - if (p.type === 'TASK' && p.requirements) { - return p.requirements.map(r => `${r.label}≥${r.threshold}${r.unit||''}`).join(',') - } - if (p.type === 'DEMOTE') return `下载≥${p.downloadThreshold}GB后分享率${condOptions.find(c=>c.value===p.condition)?.label||p.condition}${p.threshold}` const condLabel = condOptions.find(c => c.value === p.condition)?.label || p.condition const appLabel = applicableOptions.find(a => a.value === p.applicable)?.label || '' return `${metricOptions.find(m=>m.value===p.metric)?.label||p.metric} ${condLabel} ${p.threshold}${p.unit||''}${appLabel?' ('+appLabel+')':''}` @@ -358,8 +312,8 @@ const sync = () => { editorJson.value = JSON.stringify(parsed.value, null, 2) } const addRule = () => { parsed.value = [...parsed.value, { - type:'KILL', name:'', level:'CRITICAL', metric:'days_since_last_login', threshold:0, unit:'天', condition:'GT', - applicable:null, description:'', requirements:null, _fold:false + type:'KILL', action:'KILL', name:'', metric:'days_since_last_login', threshold:0, unit:'天', condition:'GT', + applicable:null, description:'', _fold:false }] } @@ -368,28 +322,10 @@ const confirmRemove = async (idx) => { const arr = [...parsed.value]; arr.splice(idx, 1); parsed.value = arr } -const onTypeChange = (rule) => { - if (rule.type === 'TASK' && !rule.requirements) rule.requirements = [] - if (rule.type === 'DEMOTE' && rule.downloadThreshold == null) rule.downloadThreshold = 100 - sync() -} - -const addRequirement = (rule) => { - if (!rule.requirements) rule.requirements = [] - rule.requirements.push({ label:'', metric:'upload_gb', threshold:0, condition:'LT', unit:'GB' }) - sync() -} - -const removeRequirement = (rule, ri) => { - rule.requirements.splice(ri, 1) - if (!rule.requirements.length) rule.requirements = null - sync() -} - const addPreset = (p) => { parsed.value = [...parsed.value, { ...p, _fold: false }] } const importPresets = () => { - const items = allPresets.filter(p => selectedPresets.value.includes(p.name)).map(p => ({ ...p, _fold: false })) + const items = presets.filter(p => selectedPresets.value.includes(p.name)).map(p => ({ ...p, _fold: false })) parsed.value = [...parsed.value, ...items] selectedPresets.value = [] showPresets.value = false @@ -435,9 +371,8 @@ onMounted(fetchAll) cursor: default; } .rule-card:hover { box-shadow: 0 2px 8px rgba(0,0,0,.08); } -.rule-card.critical { border-left: 4px solid #f56c6c; } -.rule-card.warn { border-left: 4px solid #e6a23c; } -.rule-card.info { border-left: 4px solid #409eff; } +.rule-card.kill { border-left: 4px solid #f56c6c; } +.rule-card.warn { border-left: 4px solid #e6a23c; } .card-header { display: flex; align-items: center; justify-content: space-between; @@ -456,10 +391,4 @@ onMounted(fetchAll) .field-hint { color: #909399; font-size: 11px; } .condition-text { color: #909399; font-size: 12px; } .then-arrow { color: #409eff; font-size: 16px; font-weight: 700; margin: 0 2px; } - -.task-section { - margin-top: 2px; padding: 8px 10px; - border: 1px dashed #d9ecff; border-radius: 6px; background: #f5f9ff; -} -.req-row { display: flex; align-items: center; gap: 6px; margin-top: 4px; }