509 lines
17 KiB
Python
509 lines
17 KiB
Python
from flask import Flask, request, jsonify, send_from_directory, redirect
|
||
from flask_cors import CORS
|
||
from loguru import logger
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
from config import Config
|
||
from scheduler import ClickScheduler
|
||
|
||
# 配置日志
|
||
Config.ensure_dirs()
|
||
logger.remove()
|
||
logger.add(
|
||
sys.stdout,
|
||
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>",
|
||
level="INFO"
|
||
)
|
||
logger.add(
|
||
Path(Config.LOG_DIR) / "mip_ad_service_{time}.log",
|
||
rotation="500 MB",
|
||
retention="10 days",
|
||
encoding="utf-8",
|
||
level="DEBUG"
|
||
)
|
||
|
||
# 创建Flask应用
|
||
app = Flask(__name__, static_folder='static', static_url_path='')
|
||
CORS(app)
|
||
|
||
# 创建调度器实例
|
||
scheduler = ClickScheduler()
|
||
|
||
|
||
@app.route('/')
|
||
def index():
|
||
"""首页 - 重定向到新的单页应用"""
|
||
return redirect('/app.html')
|
||
|
||
|
||
@app.route('/health', methods=['GET'])
|
||
def health():
|
||
"""健康检查"""
|
||
return jsonify({'status': 'ok', 'message': '服务运行正常'})
|
||
|
||
|
||
@app.route('/api/urls', methods=['POST'])
|
||
def add_urls():
|
||
"""添加URL(支持单个或批量)"""
|
||
try:
|
||
data = request.get_json()
|
||
|
||
if not data:
|
||
return jsonify({'success': False, 'message': '请求数据为空'}), 400
|
||
|
||
# 支持单个URL或URL列表
|
||
if 'url' in data:
|
||
# 单个URL
|
||
url = data['url']
|
||
if not url:
|
||
return jsonify({'success': False, 'message': 'URL不能为空'}), 400
|
||
|
||
success = scheduler.add_url(url)
|
||
if success:
|
||
return jsonify({'success': True, 'message': '添加成功'})
|
||
else:
|
||
return jsonify({'success': False, 'message': 'URL已存在或添加失败'}), 400
|
||
|
||
elif 'urls' in data:
|
||
# 批量URL
|
||
urls = data['urls']
|
||
if not isinstance(urls, list) or not urls:
|
||
return jsonify({'success': False, 'message': 'URLs必须是非空列表'}), 400
|
||
|
||
count = scheduler.add_urls(urls)
|
||
return jsonify({
|
||
'success': True,
|
||
'message': f'成功添加 {count}/{len(urls)} 个URL',
|
||
'added_count': count,
|
||
'total_count': len(urls)
|
||
})
|
||
|
||
else:
|
||
return jsonify({'success': False, 'message': '请提供url或urls参数'}), 400
|
||
|
||
except Exception as e:
|
||
logger.error(f"添加URL异常: {str(e)}")
|
||
return jsonify({'success': False, 'message': f'服务异常: {str(e)}'}), 500
|
||
|
||
|
||
@app.route('/api/urls', methods=['GET'])
|
||
def get_urls():
|
||
"""获取所有URL列表"""
|
||
try:
|
||
urls = scheduler.data_manager.get_all_urls()
|
||
return jsonify({'success': True, 'data': urls})
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取URL列表异常: {str(e)}")
|
||
return jsonify({'success': False, 'message': f'服务异常: {str(e)}'}), 500
|
||
|
||
|
||
@app.route('/api/urls/<path:url>', methods=['GET'])
|
||
def get_url_detail(url: str):
|
||
"""获取URL详细信息"""
|
||
try:
|
||
url_info = scheduler.get_url_detail(url)
|
||
|
||
if url_info:
|
||
return jsonify({'success': True, 'data': url_info})
|
||
else:
|
||
return jsonify({'success': False, 'message': 'URL不存在'}), 404
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取URL详情异常: {str(e)}")
|
||
return jsonify({'success': False, 'message': f'服务异常: {str(e)}'}), 500
|
||
|
||
|
||
@app.route('/api/urls/<path:url>', methods=['DELETE'])
|
||
def delete_url(url: str):
|
||
"""删除URL"""
|
||
try:
|
||
success = scheduler.data_manager.delete_url(url)
|
||
|
||
if success:
|
||
return jsonify({'success': True, 'message': '删除成功'})
|
||
else:
|
||
return jsonify({'success': False, 'message': 'URL不存在'}), 404
|
||
|
||
except Exception as e:
|
||
logger.error(f"删除URL异常: {str(e)}")
|
||
return jsonify({'success': False, 'message': f'服务异常: {str(e)}'}), 500
|
||
|
||
|
||
@app.route('/api/urls/<path:url>/reset', methods=['POST'])
|
||
def reset_url(url: str):
|
||
"""重置URL(重新开始点击)"""
|
||
try:
|
||
success = scheduler.data_manager.reset_url(url)
|
||
|
||
if success:
|
||
return jsonify({'success': True, 'message': '重置成功'})
|
||
else:
|
||
return jsonify({'success': False, 'message': 'URL不存在'}), 404
|
||
|
||
except Exception as e:
|
||
logger.error(f"重置URL异常: {str(e)}")
|
||
return jsonify({'success': False, 'message': f'服务异常: {str(e)}'}), 500
|
||
|
||
|
||
@app.route('/api/statistics', methods=['GET'])
|
||
def get_statistics():
|
||
"""获取统计数据"""
|
||
try:
|
||
stats = scheduler.get_statistics()
|
||
return jsonify({'success': True, 'data': stats})
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取统计数据异常: {str(e)}")
|
||
return jsonify({'success': False, 'message': f'服务异常: {str(e)}'}), 500
|
||
|
||
|
||
@app.route('/api/scheduler/start', methods=['POST'])
|
||
def start_scheduler():
|
||
"""启动调度器"""
|
||
try:
|
||
scheduler.start_scheduler()
|
||
return jsonify({'success': True, 'message': '调度器已启动'})
|
||
|
||
except Exception as e:
|
||
logger.error(f"启动调度器异常: {str(e)}")
|
||
return jsonify({'success': False, 'message': f'服务异常: {str(e)}'}), 500
|
||
|
||
|
||
@app.route('/api/scheduler/stop', methods=['POST'])
|
||
def stop_scheduler():
|
||
"""停止调度器"""
|
||
try:
|
||
scheduler.stop_scheduler()
|
||
return jsonify({'success': True, 'message': '调度器已停止'})
|
||
|
||
except Exception as e:
|
||
logger.error(f"停止调度器异常: {str(e)}")
|
||
return jsonify({'success': False, 'message': f'服务异常: {str(e)}'}), 500
|
||
|
||
|
||
@app.route('/api/scheduler/status', methods=['GET'])
|
||
def get_scheduler_status():
|
||
"""获取调度器状态"""
|
||
try:
|
||
status = 'running' if scheduler.running else 'stopped'
|
||
return jsonify({'success': True, 'data': {'status': status}})
|
||
|
||
except Exception as e:
|
||
logger.error(f"获取调度器状态异常: {str(e)}")
|
||
return jsonify({'success': False, 'message': f'服务异常: {str(e)}'}), 500
|
||
|
||
|
||
# AdsPower 接口调试
|
||
@app.route('/api/adspower/groups', methods=['GET'])
|
||
def adspower_list_groups():
|
||
"""查询分组列表"""
|
||
try:
|
||
from adspower_client import AdsPowerClient
|
||
|
||
group_name = request.args.get('group_name')
|
||
page = request.args.get('page', 1, type=int)
|
||
page_size = request.args.get('page_size', 2000, type=int)
|
||
|
||
client = AdsPowerClient()
|
||
result = client.list_groups(group_name=group_name, page=page, page_size=page_size)
|
||
|
||
if result:
|
||
return jsonify({'success': True, 'data': result})
|
||
else:
|
||
return jsonify({'success': False, 'message': '查询分组列表失败'}), 500
|
||
except Exception as e:
|
||
logger.error(f"AdsPower查询分组异常: {str(e)}")
|
||
return jsonify({'success': False, 'message': str(e)}), 500
|
||
|
||
|
||
@app.route('/api/adspower/group/env', methods=['GET'])
|
||
def adspower_get_group_by_env():
|
||
"""根据当前运行环境获取对应的分组ID"""
|
||
try:
|
||
from adspower_client import AdsPowerClient
|
||
|
||
client = AdsPowerClient()
|
||
group_id = client.get_group_by_env()
|
||
|
||
if group_id:
|
||
return jsonify({'success': True, 'data': {'group_id': group_id, 'env': Config.ENV}})
|
||
else:
|
||
return jsonify({'success': False, 'message': f'未找到对应环境的分组'}), 404
|
||
except Exception as e:
|
||
logger.error(f"AdsPower获取环境分组异常: {str(e)}")
|
||
return jsonify({'success': False, 'message': str(e)}), 500
|
||
|
||
|
||
@app.route('/api/adspower/profiles', methods=['GET'])
|
||
def adspower_list_profiles():
|
||
"""查询Profile列表(支持多个查询参数)"""
|
||
try:
|
||
from adspower_client import AdsPowerClient
|
||
import json as json_module
|
||
|
||
# 获取查询参数
|
||
group_id = request.args.get('group_id')
|
||
page = request.args.get('page', 1, type=int)
|
||
limit = request.args.get('limit', type=int) # 可选
|
||
page_size = request.args.get('page_size', type=int) # 可选
|
||
|
||
# 数组参数(JSON格式)
|
||
profile_id = request.args.get('profile_id')
|
||
profile_no = request.args.get('profile_no')
|
||
|
||
# 解析JSON数组
|
||
if profile_id:
|
||
try:
|
||
profile_id = json_module.loads(profile_id)
|
||
except:
|
||
profile_id = None
|
||
|
||
if profile_no:
|
||
try:
|
||
profile_no = json_module.loads(profile_no)
|
||
except:
|
||
profile_no = None
|
||
|
||
# 排序参数
|
||
sort_type = request.args.get('sort_type')
|
||
sort_order = request.args.get('sort_order')
|
||
|
||
# 如果没有指定group_id,尝试根据环境自动获取
|
||
client = AdsPowerClient()
|
||
if not group_id:
|
||
group_id = client.get_group_by_env()
|
||
if group_id:
|
||
logger.info(f"自动获取到分组ID: {group_id}")
|
||
|
||
# 查询Profile列表
|
||
result = client.list_profiles(
|
||
group_id=group_id,
|
||
page=page,
|
||
page_size=page_size if page_size else 100,
|
||
profile_id=profile_id,
|
||
profile_no=profile_no,
|
||
limit=limit,
|
||
sort_type=sort_type,
|
||
sort_order=sort_order
|
||
)
|
||
|
||
if result:
|
||
return jsonify({'success': True, 'data': result})
|
||
else:
|
||
return jsonify({'success': False, 'message': '查询Profile列表失败'}), 500
|
||
except Exception as e:
|
||
logger.error(f"AdsPower查询Profile异常: {str(e)}")
|
||
return jsonify({'success': False, 'message': str(e)}), 500
|
||
|
||
|
||
@app.route('/api/adspower/browser/start', methods=['POST'])
|
||
def adspower_start_browser():
|
||
"""启动浏览器"""
|
||
try:
|
||
from adspower_client import AdsPowerClient
|
||
data = request.get_json() or {}
|
||
user_id = data.get('user_id')
|
||
|
||
client = AdsPowerClient()
|
||
result = client.start_browser(user_id=user_id)
|
||
return jsonify({'success': True, 'data': result})
|
||
except Exception as e:
|
||
logger.error(f"AdsPower启动浏览器异常: {str(e)}")
|
||
return jsonify({'success': False, 'message': str(e)}), 500
|
||
|
||
|
||
@app.route('/api/adspower/browser/stop', methods=['POST'])
|
||
def adspower_stop_browser():
|
||
"""停止浏览器"""
|
||
try:
|
||
from adspower_client import AdsPowerClient
|
||
data = request.get_json() or {}
|
||
user_id = data.get('user_id')
|
||
|
||
client = AdsPowerClient()
|
||
result = client.stop_browser(user_id=user_id)
|
||
return jsonify({'success': True, 'data': result})
|
||
except Exception as e:
|
||
logger.error(f"AdsPower停止浏览器异常: {str(e)}")
|
||
return jsonify({'success': False, 'message': str(e)}), 500
|
||
|
||
|
||
@app.route('/api/adspower/proxy/damai', methods=['GET'])
|
||
def adspower_get_damai_proxy():
|
||
"""获取大麦IP代理"""
|
||
try:
|
||
from adspower_client import AdsPowerClient
|
||
client = AdsPowerClient()
|
||
result = client.get_damai_proxy()
|
||
return jsonify({'success': True, 'data': result})
|
||
except Exception as e:
|
||
logger.error(f"获取大麦IP异常: {str(e)}")
|
||
return jsonify({'success': False, 'message': str(e)}), 500
|
||
|
||
|
||
@app.route('/api/adspower/proxy/create', methods=['POST'])
|
||
def adspower_create_proxy():
|
||
"""创建代理"""
|
||
try:
|
||
from adspower_client import AdsPowerClient
|
||
data = request.get_json() or {}
|
||
proxy_config = data.get('proxy_config')
|
||
|
||
if not proxy_config:
|
||
return jsonify({'success': False, 'message': '缺少代理配置'}), 400
|
||
|
||
client = AdsPowerClient()
|
||
proxy_id = client.create_proxy(proxy_config)
|
||
return jsonify({'success': True, 'data': {'proxy_id': proxy_id}})
|
||
except Exception as e:
|
||
logger.error(f"AdsPower创建代理异常: {str(e)}")
|
||
return jsonify({'success': False, 'message': str(e)}), 500
|
||
|
||
|
||
@app.route('/api/adspower/proxy/list', methods=['GET'])
|
||
def adspower_list_proxies():
|
||
"""查询代理列表"""
|
||
try:
|
||
from adspower_client import AdsPowerClient
|
||
|
||
page = request.args.get('page', 1, type=int)
|
||
limit = request.args.get('limit', 100, type=int)
|
||
|
||
client = AdsPowerClient()
|
||
result = client.list_proxies(page=page, limit=limit)
|
||
|
||
if result is None:
|
||
return jsonify({'success': False, 'message': '查询代理列表失败,请检查AdsPower是否运行'}), 500
|
||
|
||
return jsonify({'success': True, 'data': result})
|
||
except Exception as e:
|
||
logger.error(f"AdsPower查询代理列表异常: {str(e)}")
|
||
return jsonify({'success': False, 'message': str(e)}), 500
|
||
|
||
|
||
@app.route('/api/adspower/profile/update', methods=['POST'])
|
||
def adspower_update_profile():
|
||
"""更新Profile代理(API v2方式)"""
|
||
try:
|
||
from adspower_client import AdsPowerClient
|
||
data = request.get_json() or {}
|
||
profile_id = data.get('profile_id')
|
||
proxy_id = data.get('proxy_id')
|
||
|
||
if not profile_id or not proxy_id:
|
||
return jsonify({'success': False, 'message': '缺少profile_id或proxy_id'}), 400
|
||
|
||
client = AdsPowerClient()
|
||
result = client.update_profile_proxy(profile_id, proxy_id)
|
||
return jsonify({'success': True, 'data': {'updated': result}})
|
||
except Exception as e:
|
||
logger.error(f"AdsPower更新Profile异常: {str(e)}")
|
||
return jsonify({'success': False, 'message': str(e)}), 500
|
||
|
||
|
||
@app.route('/api/adspower/profile/update-v1', methods=['POST'])
|
||
def adspower_update_profile_v1():
|
||
"""更新Profile代理(API v1方式,直接传入proxy_config)"""
|
||
try:
|
||
from adspower_client import AdsPowerClient
|
||
data = request.get_json() or {}
|
||
profile_id = data.get('profile_id')
|
||
proxy_config = data.get('proxy_config')
|
||
|
||
if not profile_id or not proxy_config:
|
||
return jsonify({'success': False, 'message': '缺少profile_id或proxy_config'}), 400
|
||
|
||
# 验证必要字段
|
||
if 'proxy_host' not in proxy_config or 'proxy_port' not in proxy_config:
|
||
return jsonify({'success': False, 'message': 'proxy_config中缺少proxy_host或proxy_port'}), 400
|
||
|
||
client = AdsPowerClient()
|
||
result = client.update_profile_proxy_v1(profile_id, proxy_config)
|
||
return jsonify({'success': True, 'data': {'updated': result}})
|
||
except Exception as e:
|
||
logger.error(f"AdsPower更新Profile异常 (v1): {str(e)}")
|
||
return jsonify({'success': False, 'message': str(e)}), 500
|
||
|
||
|
||
# 数据库查询接口
|
||
@app.route('/api/clicks', methods=['GET'])
|
||
def get_all_clicks():
|
||
"""获取所有点击记录"""
|
||
try:
|
||
from db_manager import ClickManager
|
||
|
||
site_id = request.args.get('site_id', type=int)
|
||
limit = request.args.get('limit', 100, type=int)
|
||
|
||
click_mgr = ClickManager()
|
||
|
||
if site_id:
|
||
clicks = click_mgr.get_clicks_by_site(site_id, limit=limit)
|
||
else:
|
||
# 获取所有点击记录
|
||
conn = click_mgr.get_connection()
|
||
cursor = conn.cursor()
|
||
cursor.execute(f"""
|
||
SELECT * FROM ai_mip_click
|
||
ORDER BY click_time DESC
|
||
LIMIT ?
|
||
""", (limit,))
|
||
rows = cursor.fetchall()
|
||
conn.close()
|
||
clicks = [click_mgr._dict_from_row(row) for row in rows]
|
||
|
||
return jsonify({'success': True, 'data': clicks})
|
||
except Exception as e:
|
||
logger.error(f"查询点击记录异常: {str(e)}")
|
||
return jsonify({'success': False, 'message': str(e)}), 500
|
||
|
||
|
||
@app.route('/api/interactions', methods=['GET'])
|
||
def get_all_interactions():
|
||
"""获取所有互动记录"""
|
||
try:
|
||
from db_manager import InteractionManager
|
||
|
||
site_id = request.args.get('site_id', type=int)
|
||
limit = request.args.get('limit', 100, type=int)
|
||
|
||
interaction_mgr = InteractionManager()
|
||
|
||
if site_id:
|
||
interactions = interaction_mgr.get_interactions_by_site(site_id, limit=limit)
|
||
else:
|
||
# 获取所有互动记录
|
||
conn = interaction_mgr.get_connection()
|
||
cursor = conn.cursor()
|
||
cursor.execute(f"""
|
||
SELECT * FROM ai_mip_interaction
|
||
ORDER BY interaction_time DESC
|
||
LIMIT ?
|
||
""", (limit,))
|
||
rows = cursor.fetchall()
|
||
conn.close()
|
||
interactions = [interaction_mgr._dict_from_row(row) for row in rows]
|
||
|
||
return jsonify({'success': True, 'data': interactions})
|
||
except Exception as e:
|
||
logger.error(f"查询互动记录异常: {str(e)}")
|
||
return jsonify({'success': False, 'message': str(e)}), 500
|
||
|
||
|
||
if __name__ == '__main__':
|
||
logger.info(f"启动MIP广告点击服务 - 环境: {Config.ENV}")
|
||
logger.info(f"服务地址: http://{Config.SERVER_HOST}:{Config.SERVER_PORT}")
|
||
logger.info(f"调试模式: {Config.DEBUG}")
|
||
|
||
# 自动启动调度器
|
||
scheduler.start_scheduler()
|
||
|
||
# 启动Flask应用
|
||
app.run(
|
||
host=Config.SERVER_HOST,
|
||
port=Config.SERVER_PORT,
|
||
debug=Config.DEBUG
|
||
)
|