添加注册自动审核功能

- 系统配置新增:自动审核开关、每小时注册限制、赠送VIP天数
- 数据库:添加 auto_approve_enabled, auto_approve_hourly_limit, auto_approve_vip_days 字段
- 后端API:支持保存和读取自动审核配置
- 管理后台:新增注册自动审核配置区域(绿色背景)
- 注册逻辑:支持自动审核通过并赠送VIP

功能说明:
1. 启用自动审核后,新用户注册自动通过,无需管理员审批
2. 每小时注册限制防止恶意注册
3. 可配置注册赠送VIP天数(设为0则不赠送)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-10 21:42:28 +08:00
parent 4a72c76c11
commit 696fcfa85d
3 changed files with 161 additions and 6 deletions

41
app.py
View File

@@ -460,9 +460,32 @@ def register():
return jsonify({"error": "验证码错误次数过多,IP已被锁定1小时"}), 429 return jsonify({"error": "验证码错误次数过多,IP已被锁定1小时"}), 429
return jsonify({"error": message}), 400 return jsonify({"error": message}), 400
# 获取自动审核配置
system_config = database.get_system_config()
auto_approve_enabled = system_config.get('auto_approve_enabled', 0) == 1
auto_approve_hourly_limit = system_config.get('auto_approve_hourly_limit', 10)
auto_approve_vip_days = system_config.get('auto_approve_vip_days', 7)
# 检查每小时注册限制
if auto_approve_enabled:
hourly_count = database.get_hourly_registration_count()
if hourly_count >= auto_approve_hourly_limit:
return jsonify({"error": f"注册人数过多,请稍后再试(每小时限制{auto_approve_hourly_limit}人)"}), 429
user_id = database.create_user(username, password, email) user_id = database.create_user(username, password, email)
if user_id: if user_id:
return jsonify({"success": True, "message": "注册成功,请等待管理员审核"}) # 自动审核处理
if auto_approve_enabled:
# 自动审核通过
database.approve_user(user_id)
# 赠送VIP天数
if auto_approve_vip_days > 0:
database.set_user_vip(user_id, auto_approve_vip_days)
return jsonify({"success": True, "message": f"注册成功!已自动审核通过,赠送{auto_approve_vip_days}天VIP"})
else:
return jsonify({"success": True, "message": "注册成功!已自动审核通过"})
else:
return jsonify({"success": True, "message": "注册成功,请等待管理员审核"})
else: else:
return jsonify({"error": "用户名已存在"}), 400 return jsonify({"error": "用户名已存在"}), 400
@@ -2320,6 +2343,9 @@ def update_system_config_api():
schedule_weekdays = data.get('schedule_weekdays') schedule_weekdays = data.get('schedule_weekdays')
new_max_concurrent_per_account = data.get('max_concurrent_per_account') new_max_concurrent_per_account = data.get('max_concurrent_per_account')
new_max_screenshot_concurrent = data.get('max_screenshot_concurrent') new_max_screenshot_concurrent = data.get('max_screenshot_concurrent')
auto_approve_enabled = data.get('auto_approve_enabled')
auto_approve_hourly_limit = data.get('auto_approve_hourly_limit')
auto_approve_vip_days = data.get('auto_approve_vip_days')
# 验证参数 # 验证参数
if max_concurrent is not None: if max_concurrent is not None:
@@ -2353,6 +2379,14 @@ def update_system_config_api():
except (ValueError, AttributeError): except (ValueError, AttributeError):
return jsonify({"error": "星期格式错误"}), 400 return jsonify({"error": "星期格式错误"}), 400
if auto_approve_hourly_limit is not None:
if not isinstance(auto_approve_hourly_limit, int) or auto_approve_hourly_limit < 1:
return jsonify({"error": "每小时注册限制必须大于0"}), 400
if auto_approve_vip_days is not None:
if not isinstance(auto_approve_vip_days, int) or auto_approve_vip_days < 0:
return jsonify({"error": "注册赠送VIP天数不能为负数"}), 400
# 更新数据库 # 更新数据库
if database.update_system_config( if database.update_system_config(
max_concurrent=max_concurrent, max_concurrent=max_concurrent,
@@ -2361,7 +2395,10 @@ def update_system_config_api():
schedule_browse_type=schedule_browse_type, schedule_browse_type=schedule_browse_type,
schedule_weekdays=schedule_weekdays, schedule_weekdays=schedule_weekdays,
max_concurrent_per_account=new_max_concurrent_per_account, max_concurrent_per_account=new_max_concurrent_per_account,
max_screenshot_concurrent=new_max_screenshot_concurrent max_screenshot_concurrent=new_max_screenshot_concurrent,
auto_approve_enabled=auto_approve_enabled,
auto_approve_hourly_limit=auto_approve_hourly_limit,
auto_approve_vip_days=auto_approve_vip_days
): ):
# 如果修改了并发数,更新全局变量和信号量 # 如果修改了并发数,更新全局变量和信号量
if max_concurrent is not None and max_concurrent != max_concurrent_global: if max_concurrent is not None and max_concurrent != max_concurrent_global:

View File

@@ -111,6 +111,9 @@ def init_database():
proxy_api_url TEXT DEFAULT '', proxy_api_url TEXT DEFAULT '',
proxy_expire_minutes INTEGER DEFAULT 3, proxy_expire_minutes INTEGER DEFAULT 3,
enable_screenshot INTEGER DEFAULT 1, enable_screenshot INTEGER DEFAULT 1,
auto_approve_enabled INTEGER DEFAULT 0,
auto_approve_hourly_limit INTEGER DEFAULT 10,
auto_approve_vip_days INTEGER DEFAULT 7,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) )
''') ''')
@@ -236,8 +239,9 @@ def init_database():
INSERT INTO system_config ( INSERT INTO system_config (
id, max_concurrent_global, max_concurrent_per_account, max_screenshot_concurrent, id, max_concurrent_global, max_concurrent_per_account, max_screenshot_concurrent,
schedule_enabled, schedule_time, schedule_browse_type, schedule_weekdays, schedule_enabled, schedule_time, schedule_browse_type, schedule_weekdays,
proxy_enabled, proxy_api_url, proxy_expire_minutes, enable_screenshot proxy_enabled, proxy_api_url, proxy_expire_minutes, enable_screenshot,
) VALUES (1, 2, 1, 3, 0, '02:00', '应读', '1,2,3,4,5,6,7', 0, '', 3, 1) auto_approve_enabled, auto_approve_hourly_limit, auto_approve_vip_days
) VALUES (1, 2, 1, 3, 0, '02:00', '应读', '1,2,3,4,5,6,7', 0, '', 3, 1, 0, 10, 7)
''') ''')
conn.commit() conn.commit()
print("✓ 已创建系统配置(默认并发2,定时任务关闭)") print("✓ 已创建系统配置(默认并发2,定时任务关闭)")
@@ -325,6 +329,15 @@ def _migrate_to_v1(conn):
if 'max_concurrent_per_account' not in columns: if 'max_concurrent_per_account' not in columns:
cursor.execute('ALTER TABLE system_config ADD COLUMN max_concurrent_per_account INTEGER DEFAULT 1') cursor.execute('ALTER TABLE system_config ADD COLUMN max_concurrent_per_account INTEGER DEFAULT 1')
print(" ✓ 添加 max_concurrent_per_account 字段") print(" ✓ 添加 max_concurrent_per_account 字段")
if 'auto_approve_enabled' not in columns:
cursor.execute('ALTER TABLE system_config ADD COLUMN auto_approve_enabled INTEGER DEFAULT 0')
print(" ✓ 添加 auto_approve_enabled 字段")
if 'auto_approve_hourly_limit' not in columns:
cursor.execute('ALTER TABLE system_config ADD COLUMN auto_approve_hourly_limit INTEGER DEFAULT 10')
print(" ✓ 添加 auto_approve_hourly_limit 字段")
if 'auto_approve_vip_days' not in columns:
cursor.execute('ALTER TABLE system_config ADD COLUMN auto_approve_vip_days INTEGER DEFAULT 7')
print(" ✓ 添加 auto_approve_vip_days 字段")
# 检查并添加 duration 字段到 task_logs # 检查并添加 duration 字段到 task_logs
cursor.execute("PRAGMA table_info(task_logs)") cursor.execute("PRAGMA table_info(task_logs)")
@@ -980,14 +993,18 @@ def get_system_config():
'proxy_enabled': 0, 'proxy_enabled': 0,
'proxy_api_url': '', 'proxy_api_url': '',
'proxy_expire_minutes': 3, 'proxy_expire_minutes': 3,
'enable_screenshot': 1 'enable_screenshot': 1,
'auto_approve_enabled': 0,
'auto_approve_hourly_limit': 10,
'auto_approve_vip_days': 7
} }
def update_system_config(max_concurrent=None, schedule_enabled=None, schedule_time=None, def update_system_config(max_concurrent=None, schedule_enabled=None, schedule_time=None,
schedule_browse_type=None, schedule_weekdays=None, schedule_browse_type=None, schedule_weekdays=None,
max_concurrent_per_account=None, max_screenshot_concurrent=None, proxy_enabled=None, max_concurrent_per_account=None, max_screenshot_concurrent=None, proxy_enabled=None,
proxy_api_url=None, proxy_expire_minutes=None): proxy_api_url=None, proxy_expire_minutes=None,
auto_approve_enabled=None, auto_approve_hourly_limit=None, auto_approve_vip_days=None):
"""更新系统配置""" """更新系统配置"""
with db_pool.get_db() as conn: with db_pool.get_db() as conn:
cursor = conn.cursor() cursor = conn.cursor()
@@ -1034,6 +1051,18 @@ def update_system_config(max_concurrent=None, schedule_enabled=None, schedule_ti
updates.append('proxy_expire_minutes = ?') updates.append('proxy_expire_minutes = ?')
params.append(proxy_expire_minutes) params.append(proxy_expire_minutes)
if auto_approve_enabled is not None:
updates.append('auto_approve_enabled = ?')
params.append(auto_approve_enabled)
if auto_approve_hourly_limit is not None:
updates.append('auto_approve_hourly_limit = ?')
params.append(auto_approve_hourly_limit)
if auto_approve_vip_days is not None:
updates.append('auto_approve_vip_days = ?')
params.append(auto_approve_vip_days)
if updates: if updates:
updates.append('updated_at = CURRENT_TIMESTAMP') updates.append('updated_at = CURRENT_TIMESTAMP')
sql = f"UPDATE system_config SET {', '.join(updates)} WHERE id = 1" sql = f"UPDATE system_config SET {', '.join(updates)} WHERE id = 1"
@@ -1044,6 +1073,17 @@ def update_system_config(max_concurrent=None, schedule_enabled=None, schedule_ti
return False return False
def get_hourly_registration_count():
"""获取最近一小时内的注册用户数"""
with db_pool.get_db() as conn:
cursor = conn.cursor()
cursor.execute('''
SELECT COUNT(*) FROM users
WHERE created_at >= datetime('now', '-1 hour')
''')
return cursor.fetchone()[0]
# ==================== 任务日志管理 ==================== # ==================== 任务日志管理 ====================
def create_task_log(user_id, account_id, username, browse_type, status, def create_task_log(user_id, account_id, username, browse_type, status,

View File

@@ -713,6 +713,42 @@
</div> </div>
</div> </div>
<!-- ========== 代理设置结束 ========== --> <!-- ========== 代理设置结束 ========== -->
<!-- ========== 注册自动审核设置 ========== -->
<div style="border-top: 2px solid #f0f0f0; margin-top: 40px; padding-top: 25px; background-color: #e8f5e9; padding: 20px; border-radius: 8px;">
<h3 style="margin-bottom: 15px; font-size: 16px;">✅ 注册自动审核</h3>
<div class="form-group">
<label style="display: flex; align-items: center; gap: 10px;">
<input type="checkbox" id="autoApproveEnabled" style="width: auto; max-width: none;">
启用自动审核
</label>
<div style="font-size: 12px; color: #666; margin-top: 5px;">
开启后,新用户注册将自动通过审核,无需管理员手动审批
</div>
</div>
<div class="form-group">
<label>每小时注册限制</label>
<input type="number" id="autoApproveHourlyLimit" min="1" value="10" style="max-width: 200px; padding: 10px; border: 1px solid #ddd; border-radius: 5px; font-size: 14px;">
<div style="font-size: 12px; color: #666; margin-top: 5px;">
限制每小时内最多允许注册的用户数量,防止恶意注册
</div>
</div>
<div class="form-group">
<label>注册赠送VIP天数</label>
<input type="number" id="autoApproveVipDays" min="0" value="7" style="max-width: 200px; padding: 10px; border: 1px solid #ddd; border-radius: 5px; font-size: 14px;">
<div style="font-size: 12px; color: #666; margin-top: 5px;">
新用户注册成功后自动赠送的VIP天数设为0表示不赠送
</div>
</div>
<div style="margin-top: 15px;">
<button class="btn btn-primary" onclick="saveAutoApproveConfig()">💾 保存自动审核配置</button>
</div>
</div>
<!-- ========== 注册自动审核结束 ========== -->
</div> </div>
</div> </div>
@@ -1398,6 +1434,11 @@
// 显示/隐藏定时任务选项 // 显示/隐藏定时任务选项
toggleSchedule(config.schedule_enabled === 1); toggleSchedule(config.schedule_enabled === 1);
// 加载自动审核配置
document.getElementById('autoApproveEnabled').checked = config.auto_approve_enabled === 1;
document.getElementById('autoApproveHourlyLimit').value = config.auto_approve_hourly_limit || 10;
document.getElementById('autoApproveVipDays').value = config.auto_approve_vip_days || 7;
} }
} catch (error) { } catch (error) {
console.error('加载系统配置失败:', error); console.error('加载系统配置失败:', error);
@@ -1471,6 +1512,43 @@
} }
} }
async function saveAutoApproveConfig() {
const autoApproveEnabled = document.getElementById('autoApproveEnabled').checked ? 1 : 0;
const autoApproveHourlyLimit = parseInt(document.getElementById('autoApproveHourlyLimit').value) || 10;
const autoApproveVipDays = parseInt(document.getElementById('autoApproveVipDays').value) || 0;
if (autoApproveHourlyLimit < 1) {
alert('❌ 每小时注册限制必须大于0');
return;
}
if (autoApproveVipDays < 0) {
alert('❌ VIP天数不能为负数');
return;
}
try {
const response = await fetch('/yuyx/api/system/config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
auto_approve_enabled: autoApproveEnabled,
auto_approve_hourly_limit: autoApproveHourlyLimit,
auto_approve_vip_days: autoApproveVipDays
})
});
const data = await response.json();
if (data.message) {
showNotification('✓ 自动审核配置已保存', 'success');
} else if (data.error) {
showNotification('✗ ' + data.error, 'error');
}
} catch (error) {
showNotification('保存失败: ' + error.message, 'error');
}
}
async function testProxy() { async function testProxy() {
const apiUrl = document.getElementById('proxyApiUrl').value.trim(); const apiUrl = document.getElementById('proxyApiUrl').value.trim();