sing-box 用户流量控制与限制
本文介绍如何为 sing-box 代理服务实现用户流量控制、配额管理和自动限制功能。
核心架构
流量控制系统包含以下组件:
- 流量统计模块:实时记录每个用户的流量使用
- 配额管理系统:设置和管理用户流量限制
- 自动控制器:达到限制时自动禁用账户
- Web 管理面板:可视化管理界面
方案一:使用 sing-box + 流量统计脚本
1. 服务器配置(支持多用户)
{
"log": {
"level": "info",
"timestamp": true
},
"inbounds": [
{
"type": "trojan",
"tag": "trojan-in",
"listen": "::",
"listen_port": 443,
"users": [
{
"name": "user1",
"password": "password1"
},
{
"name": "user2",
"password": "password2"
},
{
"name": "user3",
"password": "password3"
}
],
"tls": {
"enabled": true,
"server_name": "your-domain.com",
"certificate_path": "/etc/letsencrypt/live/your-domain.com/fullchain.pem",
"key_path": "/etc/letsencrypt/live/your-domain.com/privkey.pem"
}
}
],
"outbounds": [
{
"type": "direct",
"tag": "direct"
}
],
"stats": {
"enabled": true,
"inbounds": ["trojan-in"],
"outbounds": ["direct"],
"users": ["user1", "user2", "user3"]
}
}2. 流量监控脚本
创建 /opt/singbox-traffic/traffic_monitor.py:
#!/usr/bin/env python3
import json
import sqlite3
import subprocess
import time
from datetime import datetime, timedelta
import os
import sys
class TrafficMonitor:
def __init__(self):
self.db_path = "/opt/singbox-traffic/traffic.db"
self.config_path = "/etc/sing-box/config.json"
self.init_database()
def init_database(self):
"""初始化数据库"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# 用户表
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
username TEXT PRIMARY KEY,
password TEXT NOT NULL,
email TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
status TEXT DEFAULT 'active',
monthly_quota INTEGER DEFAULT 100, -- GB
total_quota INTEGER DEFAULT 0, -- GB, 0 means unlimited
reset_day INTEGER DEFAULT 1 -- 每月重置日期
)
''')
# 流量记录表
cursor.execute('''
CREATE TABLE IF NOT EXISTS traffic_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL,
upload_bytes INTEGER DEFAULT 0,
download_bytes INTEGER DEFAULT 0,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (username) REFERENCES users(username)
)
''')
# 月度统计表
cursor.execute('''
CREATE TABLE IF NOT EXISTS monthly_stats (
username TEXT NOT NULL,
month TEXT NOT NULL,
upload_bytes INTEGER DEFAULT 0,
download_bytes INTEGER DEFAULT 0,
PRIMARY KEY (username, month),
FOREIGN KEY (username) REFERENCES users(username)
)
''')
conn.commit()
conn.close()
def get_traffic_stats(self):
"""从 sing-box 获取流量统计"""
try:
# 使用 sing-box API 获取统计信息
result = subprocess.run(
["curl", "-s", "http://127.0.0.1:9090/stats"],
capture_output=True,
text=True
)
if result.returncode == 0:
return json.loads(result.stdout)
return None
except Exception as e:
print(f"获取流量统计失败: {e}")
return None
def update_traffic(self, username, upload, download):
"""更新用户流量"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# 记录流量日志
cursor.execute('''
INSERT INTO traffic_logs (username, upload_bytes, download_bytes)
VALUES (?, ?, ?)
''', (username, upload, download))
# 更新月度统计
current_month = datetime.now().strftime('%Y-%m')
cursor.execute('''
INSERT INTO monthly_stats (username, month, upload_bytes, download_bytes)
VALUES (?, ?, ?, ?)
ON CONFLICT(username, month) DO UPDATE SET
upload_bytes = upload_bytes + ?,
download_bytes = download_bytes + ?
''', (username, current_month, upload, download, upload, download))
conn.commit()
conn.close()
def check_quota(self, username):
"""检查用户配额"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# 获取用户配额设置
cursor.execute('''
SELECT monthly_quota, total_quota, reset_day, status
FROM users WHERE username = ?
''', (username,))
user = cursor.fetchone()
if not user:
conn.close()
return True # 用户不存在,允许使用
monthly_quota, total_quota, reset_day, status = user
if status != 'active':
conn.close()
return False # 用户已被禁用
# 检查月度流量
current_month = datetime.now().strftime('%Y-%m')
cursor.execute('''
SELECT upload_bytes + download_bytes as total
FROM monthly_stats
WHERE username = ? AND month = ?
''', (username, current_month))
result = cursor.fetchone()
monthly_used = result[0] if result else 0
monthly_used_gb = monthly_used / (1024**3)
# 检查总流量(如果设置了)
if total_quota > 0:
cursor.execute('''
SELECT SUM(upload_bytes + download_bytes) as total
FROM traffic_logs
WHERE username = ?
''', (username,))
total_used = cursor.fetchone()[0] or 0
total_used_gb = total_used / (1024**3)
if total_used_gb >= total_quota:
self.disable_user(username, "总流量超限")
conn.close()
return False
# 检查月度流量
if monthly_used_gb >= monthly_quota:
self.disable_user(username, "月流量超限")
conn.close()
return False
conn.close()
return True
def disable_user(self, username, reason=""):
"""禁用用户"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute('''
UPDATE users SET status = 'disabled'
WHERE username = ?
''', (username,))
conn.commit()
conn.close()
# 更新 sing-box 配置
self.update_singbox_config()
# 记录日志
print(f"用户 {username} 已被禁用: {reason}")
def enable_user(self, username):
"""启用用户"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute('''
UPDATE users SET status = 'active'
WHERE username = ?
''', (username,))
conn.commit()
conn.close()
# 更新 sing-box 配置
self.update_singbox_config()
def update_singbox_config(self):
"""更新 sing-box 配置文件"""
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# 获取所有活跃用户
cursor.execute('''
SELECT username, password
FROM users
WHERE status = 'active'
''')
active_users = cursor.fetchall()
# 读取当前配置
with open(self.config_path, 'r') as f:
config = json.load(f)
# 更新用户列表
users_list = []
for username, password in active_users:
users_list.append({
"name": username,
"password": password
})
# 更新配置
for inbound in config.get('inbounds', []):
if inbound.get('type') in ['trojan', 'vmess']:
inbound['users'] = users_list
# 写回配置文件
with open(self.config_path, 'w') as f:
json.dump(config, f, indent=2)
# 重启 sing-box
subprocess.run(["systemctl", "restart", "sing-box"])
conn.close()
def reset_monthly_quota(self):
"""重置月度配额"""
today = datetime.now().day
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
# 查找需要重置的用户
cursor.execute('''
SELECT username FROM users
WHERE reset_day = ? AND status = 'disabled'
''', (today,))
users_to_reset = cursor.fetchall()
for (username,) in users_to_reset:
# 检查是否因为月流量超限被禁用
cursor.execute('''
SELECT monthly_quota FROM users WHERE username = ?
''', (username,))
monthly_quota = cursor.fetchone()[0]
current_month = datetime.now().strftime('%Y-%m')
cursor.execute('''
SELECT upload_bytes + download_bytes as total
FROM monthly_stats
WHERE username = ? AND month = ?
''', (username, current_month))
result = cursor.fetchone()
if result:
monthly_used_gb = result[0] / (1024**3)
if monthly_used_gb >= monthly_quota:
# 重新启用用户
self.enable_user(username)
print(f"用户 {username} 月度配额已重置并重新启用")
conn.close()
def monitor_loop(self):
"""主监控循环"""
while True:
try:
# 获取流量统计
stats = self.get_traffic_stats()
if stats:
for user_stat in stats.get('users', []):
username = user_stat['name']
upload = user_stat.get('upload', 0)
download = user_stat.get('download', 0)
# 更新流量记录
self.update_traffic(username, upload, download)
# 检查配额
self.check_quota(username)
# 检查是否需要重置月度配额
self.reset_monthly_quota()
# 每5分钟检查一次
time.sleep(300)
except KeyboardInterrupt:
print("监控程序已停止")
break
except Exception as e:
print(f"监控出错: {e}")
time.sleep(60)
if __name__ == "__main__":
monitor = TrafficMonitor()
monitor.monitor_loop()3. Web 管理面板
创建 /opt/singbox-traffic/web_panel.py:
#!/usr/bin/env python3
from flask import Flask, render_template, request, jsonify, redirect, url_for, session
import sqlite3
import json
import hashlib
import secrets
from datetime import datetime
import os
app = Flask(__name__)
app.secret_key = secrets.token_hex(32)
DB_PATH = "/opt/singbox-traffic/traffic.db"
ADMIN_PASSWORD_HASH = hashlib.sha256("your-admin-password".encode()).hexdigest()
def get_db():
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
return conn
@app.route('/')
def index():
if 'logged_in' not in session:
return redirect(url_for('login'))
conn = get_db()
cursor = conn.cursor()
# 获取所有用户统计
cursor.execute('''
SELECT
u.username,
u.email,
u.status,
u.monthly_quota,
u.total_quota,
COALESCE(ms.upload_bytes + ms.download_bytes, 0) as monthly_used,
COALESCE(total.total_bytes, 0) as total_used
FROM users u
LEFT JOIN monthly_stats ms ON u.username = ms.username
AND ms.month = strftime('%Y-%m', 'now')
LEFT JOIN (
SELECT username, SUM(upload_bytes + download_bytes) as total_bytes
FROM traffic_logs
GROUP BY username
) total ON u.username = total.username
ORDER BY u.username
''')
users = cursor.fetchall()
conn.close()
return render_template('dashboard.html', users=users)
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
password = request.form.get('password')
if hashlib.sha256(password.encode()).hexdigest() == ADMIN_PASSWORD_HASH:
session['logged_in'] = True
return redirect(url_for('index'))
else:
return render_template('login.html', error='密码错误')
return render_template('login.html')
@app.route('/logout')
def logout():
session.pop('logged_in', None)
return redirect(url_for('login'))
@app.route('/api/user/<username>', methods=['GET', 'PUT', 'DELETE'])
def manage_user(username):
if 'logged_in' not in session:
return jsonify({'error': 'Unauthorized'}), 401
conn = get_db()
cursor = conn.cursor()
if request.method == 'GET':
cursor.execute('SELECT * FROM users WHERE username = ?', (username,))
user = cursor.fetchone()
if user:
return jsonify(dict(user))
return jsonify({'error': 'User not found'}), 404
elif request.method == 'PUT':
data = request.json
cursor.execute('''
UPDATE users
SET monthly_quota = ?, total_quota = ?, status = ?, reset_day = ?
WHERE username = ?
''', (
data.get('monthly_quota', 100),
data.get('total_quota', 0),
data.get('status', 'active'),
data.get('reset_day', 1),
username
))
conn.commit()
# 如果状态改变,更新 sing-box 配置
if 'status' in data:
os.system('python3 /opt/singbox-traffic/update_config.py')
return jsonify({'success': True})
elif request.method == 'DELETE':
cursor.execute('DELETE FROM users WHERE username = ?', (username,))
conn.commit()
os.system('python3 /opt/singbox-traffic/update_config.py')
return jsonify({'success': True})
conn.close()
@app.route('/api/add_user', methods=['POST'])
def add_user():
if 'logged_in' not in session:
return jsonify({'error': 'Unauthorized'}), 401
data = request.json
conn = get_db()
cursor = conn.cursor()
# 生成随机密码
password = secrets.token_urlsafe(16)
cursor.execute('''
INSERT INTO users (username, password, email, monthly_quota, total_quota)
VALUES (?, ?, ?, ?, ?)
''', (
data['username'],
password,
data.get('email', ''),
data.get('monthly_quota', 100),
data.get('total_quota', 0)
))
conn.commit()
conn.close()
# 更新 sing-box 配置
os.system('python3 /opt/singbox-traffic/update_config.py')
return jsonify({'success': True, 'password': password})
@app.route('/api/reset_traffic/<username>', methods=['POST'])
def reset_traffic(username):
if 'logged_in' not in session:
return jsonify({'error': 'Unauthorized'}), 401
conn = get_db()
cursor = conn.cursor()
current_month = datetime.now().strftime('%Y-%m')
cursor.execute('''
DELETE FROM monthly_stats
WHERE username = ? AND month = ?
''', (username, current_month))
conn.commit()
conn.close()
return jsonify({'success': True})
@app.route('/api/stats')
def get_stats():
if 'logged_in' not in session:
return jsonify({'error': 'Unauthorized'}), 401
conn = get_db()
cursor = conn.cursor()
# 获取总体统计
cursor.execute('''
SELECT
COUNT(*) as total_users,
SUM(CASE WHEN status = 'active' THEN 1 ELSE 0 END) as active_users,
SUM(upload_bytes + download_bytes) as total_traffic
FROM users u
LEFT JOIN traffic_logs t ON u.username = t.username
''')
stats = dict(cursor.fetchone())
conn.close()
return jsonify(stats)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080, debug=False)4. HTML 模板
创建 /opt/singbox-traffic/templates/dashboard.html:
<!DOCTYPE html>
<html>
<head>
<title>流量管理面板</title>
<meta charset="utf-8">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
background: #f5f5f5;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
.header {
background: white;
padding: 20px;
border-radius: 10px;
margin-bottom: 20px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.stat-card {
background: white;
padding: 20px;
border-radius: 10px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.stat-value {
font-size: 2em;
font-weight: bold;
color: #333;
}
.stat-label {
color: #666;
margin-top: 5px;
}
.users-table {
background: white;
border-radius: 10px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
overflow: hidden;
}
table {
width: 100%;
border-collapse: collapse;
}
th {
background: #f8f9fa;
padding: 15px;
text-align: left;
font-weight: 600;
color: #333;
border-bottom: 2px solid #dee2e6;
}
td {
padding: 15px;
border-bottom: 1px solid #dee2e6;
}
.status-active {
color: #28a745;
font-weight: 600;
}
.status-disabled {
color: #dc3545;
font-weight: 600;
}
.progress-bar {
width: 100%;
height: 20px;
background: #e9ecef;
border-radius: 10px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #007bff, #0056b3);
transition: width 0.3s;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
transition: opacity 0.3s;
}
.btn:hover {
opacity: 0.8;
}
.btn-primary {
background: #007bff;
color: white;
}
.btn-danger {
background: #dc3545;
color: white;
}
.btn-success {
background: #28a745;
color: white;
}
.actions {
display: flex;
gap: 10px;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>流量管理面板</h1>
<div style="float: right;">
<button class="btn btn-primary" onclick="addUser()">添加用户</button>
<a href="/logout" class="btn btn-danger">退出</a>
</div>
</div>
<div class="stats-grid">
<div class="stat-card">
<div class="stat-value" id="total-users">-</div>
<div class="stat-label">总用户数</div>
</div>
<div class="stat-card">
<div class="stat-value" id="active-users">-</div>
<div class="stat-label">活跃用户</div>
</div>
<div class="stat-card">
<div class="stat-value" id="total-traffic">-</div>
<div class="stat-label">总流量</div>
</div>
</div>
<div class="users-table">
<table>
<thead>
<tr>
<th>用户名</th>
<th>状态</th>
<th>月流量使用</th>
<th>总流量使用</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{% for user in users %}
<tr>
<td>{{ user.username }}</td>
<td>
<span class="status-{{ user.status }}">
{{ '活跃' if user.status == 'active' else '禁用' }}
</span>
</td>
<td>
<div class="progress-bar">
<div class="progress-fill" style="width: {{ (user.monthly_used / (user.monthly_quota * 1024**3) * 100) if user.monthly_quota > 0 else 0 }}%"></div>
</div>
{{ '%.2f' | format(user.monthly_used / 1024**3) }} / {{ user.monthly_quota }} GB
</td>
<td>
{{ '%.2f' | format(user.total_used / 1024**3) }} GB
</td>
<td>
<div class="actions">
{% if user.status == 'active' %}
<button class="btn btn-danger" onclick="toggleUser('{{ user.username }}', 'disable')">禁用</button>
{% else %}
<button class="btn btn-success" onclick="toggleUser('{{ user.username }}', 'enable')">启用</button>
{% endif %}
<button class="btn btn-primary" onclick="resetTraffic('{{ user.username }}')">重置流量</button>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<script>
// 加载统计数据
fetch('/api/stats')
.then(r => r.json())
.then(data => {
document.getElementById('total-users').textContent = data.total_users || 0;
document.getElementById('active-users').textContent = data.active_users || 0;
document.getElementById('total-traffic').textContent =
((data.total_traffic || 0) / (1024**3)).toFixed(2) + ' GB';
});
function addUser() {
const username = prompt('输入用户名:');
if (!username) return;
const monthly = prompt('月流量限制 (GB):', '100');
const total = prompt('总流量限制 (GB, 0=无限):', '0');
fetch('/api/add_user', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
username: username,
monthly_quota: parseInt(monthly),
total_quota: parseInt(total)
})
})
.then(r => r.json())
.then(data => {
if (data.success) {
alert(`用户已创建\n密码: ${data.password}`);
location.reload();
}
});
}
function toggleUser(username, action) {
const status = action === 'enable' ? 'active' : 'disabled';
fetch(`/api/user/${username}`, {
method: 'PUT',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({status: status})
})
.then(() => location.reload());
}
function resetTraffic(username) {
if (!confirm(`确定要重置 ${username} 的流量统计吗?`)) return;
fetch(`/api/reset_traffic/${username}`, {
method: 'POST'
})
.then(() => location.reload());
}
</script>
</body>
</html>5. 安装和部署脚本
创建 /opt/singbox-traffic/install.sh:
#!/bin/bash
# 安装依赖
apt update
apt install -y python3 python3-pip
# 安装 Python 包
pip3 install flask
# 创建目录
mkdir -p /opt/singbox-traffic/templates
# 创建 systemd 服务 - 流量监控
cat > /etc/systemd/system/singbox-traffic-monitor.service << EOF
[Unit]
Description=sing-box Traffic Monitor
After=network.target sing-box.service
[Service]
Type=simple
User=root
WorkingDirectory=/opt/singbox-traffic
ExecStart=/usr/bin/python3 /opt/singbox-traffic/traffic_monitor.py
Restart=always
[Install]
WantedBy=multi-user.target
EOF
# 创建 systemd 服务 - Web 面板
cat > /etc/systemd/system/singbox-traffic-web.service << EOF
[Unit]
Description=sing-box Traffic Web Panel
After=network.target
[Service]
Type=simple
User=root
WorkingDirectory=/opt/singbox-traffic
ExecStart=/usr/bin/python3 /opt/singbox-traffic/web_panel.py
Restart=always
[Install]
WantedBy=multi-user.target
EOF
# 重载 systemd
systemctl daemon-reload
# 启动服务
systemctl enable singbox-traffic-monitor
systemctl enable singbox-traffic-web
systemctl start singbox-traffic-monitor
systemctl start singbox-traffic-web
echo "流量控制系统已安装"
echo "Web 面板地址: http://your-server:8080"
echo "默认管理员密码: your-admin-password"方案二:使用 V2Ray-Core + V2Ray-Stats
如果你需要更精细的流量控制,可以使用 V2Ray-Core 的统计功能:
V2Ray 配置示例
{
"stats": {},
"api": {
"tag": "api",
"services": ["StatsService"]
},
"policy": {
"levels": {
"0": {
"statsUserUplink": true,
"statsUserDownlink": true
}
},
"system": {
"statsInboundUplink": true,
"statsInboundDownlink": true,
"statsOutboundUplink": true,
"statsOutboundDownlink": true
}
},
"inbounds": [
{
"tag": "api",
"port": 10085,
"protocol": "dokodemo-door",
"settings": {
"address": "127.0.0.1"
}
},
{
"tag": "trojan",
"port": 443,
"protocol": "trojan",
"settings": {
"clients": [
{
"password": "password1",
"email": "user1@example.com",
"level": 0
}
]
}
}
],
"routing": {
"rules": [
{
"type": "field",
"inboundTag": ["api"],
"outboundTag": "api"
}
]
}
}方案三:使用 Nginx 流量统计模块
对于基于 Nginx 的代理,可以使用 nginx-module-vts:
http {
vhost_traffic_status_zone;
server {
listen 8080;
location /status {
vhost_traffic_status_display;
vhost_traffic_status_display_format json;
}
}
server {
listen 443 ssl;
server_name your-domain.com;
# 为每个用户设置独立的 location
location /user1/ {
vhost_traffic_status_filter_by_set_key $uri user1::$server_name;
proxy_pass http://backend;
}
}
}用户配额管理最佳实践
1. 配额策略设置
# 配额策略示例
QUOTA_POLICIES = {
'free': {
'monthly_quota': 10, # 10GB/月
'speed_limit': 1024, # 1MB/s
'concurrent_connections': 2
},
'basic': {
'monthly_quota': 100, # 100GB/月
'speed_limit': 5120, # 5MB/s
'concurrent_connections': 5
},
'premium': {
'monthly_quota': 500, # 500GB/月
'speed_limit': 10240, # 10MB/s
'concurrent_connections': 10
},
'unlimited': {
'monthly_quota': 0, # 无限
'speed_limit': 0, # 无限
'concurrent_connections': 0 # 无限
}
}2. 自动化管理脚本
#!/bin/bash
# 每日运行的维护脚本
# 检查并禁用超额用户
python3 /opt/singbox-traffic/check_quotas.py
# 清理过期日志
find /opt/singbox-traffic/logs -type f -mtime +30 -delete
# 备份数据库
cp /opt/singbox-traffic/traffic.db /backup/traffic-$(date +%Y%m%d).db
# 发送日报
python3 /opt/singbox-traffic/send_report.py3. 监控告警
# 告警脚本
def check_alerts():
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
# 检查即将超额的用户(80%)
cursor.execute('''
SELECT username, email,
(monthly_used * 100.0 / (monthly_quota * 1024^3)) as usage_percent
FROM user_stats
WHERE usage_percent > 80 AND usage_percent < 100
''')
for user in cursor.fetchall():
send_email(user['email'],
f"流量警告:您已使用 {user['usage_percent']:.1f}% 的月度流量")
conn.close()常见问题
1. 如何实现速度限制?
在 sing-box 配置中添加:
{
"inbounds": [
{
"type": "trojan",
"settings": {
"clients": [{
"password": "password",
"level": 1
}]
}
}
],
"policy": {
"levels": {
"1": {
"connIdle": 300,
"downlinkOnly": 1024, // KB/s
"uplinkOnly": 1024 // KB/s
}
}
}
}2. 如何防止用户共享账号?
def check_concurrent_connections(username):
"""检查并发连接数"""
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute('''
SELECT COUNT(DISTINCT ip_address) as unique_ips
FROM active_connections
WHERE username = ?
AND last_seen > datetime('now', '-5 minutes')
''', (username,))
unique_ips = cursor.fetchone()[0]
if unique_ips > 3: # 超过3个不同IP
log_suspicious_activity(username, f"检测到 {unique_ips} 个不同IP同时使用")
# 可选:自动禁用账号
disable_user(username, "疑似共享账号")
conn.close()3. 如何实现流量包购买?
def purchase_traffic_package(username, package_gb):
"""购买流量包"""
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
# 添加到用户的额外流量
cursor.execute('''
UPDATE users
SET bonus_traffic = bonus_traffic + ?
WHERE username = ?
''', (package_gb * 1024**3, username))
# 记录购买历史
cursor.execute('''
INSERT INTO purchase_history (username, package_gb, purchase_date)
VALUES (?, ?, datetime('now'))
''', (username, package_gb))
conn.commit()
conn.close()总结
通过以上方案,你可以实现:
- 精确的流量统计 - 记录每个用户的上传/下载流量
- 灵活的配额管理 - 设置月度、总量限制
- 自动化控制 - 超额自动禁用,到期自动重置
- 可视化管理 - Web 面板实时查看和管理
- 告警通知 - 流量预警和异常检测
选择合适的方案取决于你的具体需求和技术栈。建议从简单的脚本开始,逐步增加功能。
Last updated on