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="{time:YYYY-MM-DD HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}",
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/', 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/', 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//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
)