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 )