Files
ai_mip/app.py
2026-01-16 22:06:46 +08:00

509 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
)