import json import os import uuid from datetime import datetime from flask import Flask, jsonify, request, send_from_directory from flask_cors import CORS app = Flask(__name__, static_folder='../platform', static_url_path='') CORS(app) BASE_DIR = os.path.dirname(os.path.abspath(__file__)) DB_DIR = os.path.join(BASE_DIR, 'db') def load_json(filename): path = os.path.join(DB_DIR, filename) if not os.path.exists(path): return [] with open(path, 'r', encoding='utf-8') as f: return json.load(f) def save_json(filename, data): path = os.path.join(DB_DIR, filename) with open(path, 'w', encoding='utf-8') as f: json.dump(data, f, ensure_ascii=False, indent=2) # ── helpers ── def gen_id(prefix='ORD'): return f"{prefix}-{uuid.uuid4().hex[:8].upper()}" def calc_distance(lat1, lng1, lat2, lng2): from math import radians, cos, sin, asin, sqrt r = 6371 d_lat = radians(lat2 - lat1) d_lng = radians(lng2 - lng1) a = sin(d_lat/2)**2 + cos(radians(lat1)) * cos(radians(lat2)) * sin(d_lng/2)**2 return round(2 * r * asin(sqrt(a)), 1) def calc_price(dist_km, weight_kg, urgency): base = dist_km * 12 + weight_kg * 5 if urgency == 'urgent': base *= 1.3 elif urgency == 'ASAP': base *= 1.5 return max(int(base), 30) # ── static files ── @app.route('/') def index(): return send_from_directory(app.static_folder, 'index.html') # ── Auth ── @app.route('/api/login', methods=['POST']) def login(): data = request.get_json() phone = data.get('phone', '') role = data.get('role', 'demander') users = load_json('users.json') user = next((u for u in users if u['phone'] == phone), None) if not user: user = { 'id': gen_id('U'), 'name': phone[-4:], 'role': role, 'phone': phone, 'company': '', 'avatar': phone[-1], 'credit_score': 800, 'created_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S') } users.append(user) save_json('users.json', users) return jsonify({'code': 0, 'data': user}) # ── Users ── @app.route('/api/users/') def get_user(user_id): users = load_json('users.json') user = next((u for u in users if u['id'] == user_id), None) if not user: return jsonify({'code': 404, 'msg': '用户不存在'}), 404 return jsonify({'code': 0, 'data': user}) @app.route('/api/users/', methods=['PUT']) def update_user(user_id): users = load_json('users.json') idx = next((i for i, u in enumerate(users) if u['id'] == user_id), None) if idx is None: return jsonify({'code': 404, 'msg': '用户不存在'}), 404 data = request.get_json() for k in ['name', 'company', 'phone']: if k in data: users[idx][k] = data[k] save_json('users.json', users) return jsonify({'code': 0, 'data': users[idx]}) # ── Drones ── @app.route('/api/drones') def list_drones(): drones = load_json('drones.json') status = request.args.get('status') user_id = request.args.get('user_id') if status: drones = [d for d in drones if d['status'] == status] if user_id: drones = [d for d in drones if d['user_id'] == user_id] return jsonify({'code': 0, 'data': drones}) @app.route('/api/drones/available') def available_drones(): drones = load_json('drones.json') available = [d for d in drones if d['status'] == 'available'] return jsonify({'code': 0, 'data': available}) @app.route('/api/drones', methods=['POST']) def add_drone(): data = request.get_json() drones = load_json('drones.json') drone = { 'id': gen_id('D'), 'name': data.get('name', '未知机型'), 'model': data.get('model', '通用'), 'max_weight': data.get('max_weight', 10), 'range_km': data.get('range_km', 10), 'status': 'available', 'location': data.get('location', '未设置'), 'total_flights': 0, 'user_id': data.get('user_id'), 'price_per_km': data.get('price_per_km', 15), 'insurance_expiry': data.get('insurance_expiry', '2026-12-31') } drones.append(drone) save_json('drones.json', drones) return jsonify({'code': 0, 'data': drone}), 201 @app.route('/api/drones/', methods=['PUT']) def update_drone(drone_id): drones = load_json('drones.json') idx = next((i for i, d in enumerate(drones) if d['id'] == drone_id), None) if idx is None: return jsonify({'code': 404, 'msg': '无人机不存在'}), 404 data = request.get_json() for k in ['name', 'status', 'location', 'price_per_km']: if k in data: drones[idx][k] = data[k] save_json('drones.json', drones) return jsonify({'code': 0, 'data': drones[idx]}) # ── Orders ── @app.route('/api/orders') def list_orders(): orders = load_json('orders.json') role = request.args.get('role') user_id = request.args.get('user_id') status = request.args.get('status') if role == 'demander' and user_id: orders = [o for o in orders if o['demander_id'] == user_id] elif role == 'provider' and user_id: orders = [o for o in orders if o.get('provider_id') == user_id] if status: orders = [o for o in orders if o['status'] == status] else: ordering = {'pending': 0, 'matching': 1, 'accepted': 2, 'in_transit': 3, 'completed': 4, 'cancelled': 5} orders.sort(key=lambda o: ordering.get(o['status'], 9)) return jsonify({'code': 0, 'data': orders}) @app.route('/api/orders/pending') def pending_orders(): orders = load_json('orders.json') pending = [o for o in orders if o['status'] == 'pending'] return jsonify({'code': 0, 'data': pending}) @app.route('/api/orders', methods=['POST']) def create_order(): data = request.get_json() orders = load_json('orders.json') order = { 'id': gen_id('ORDER'), 'from_addr': data['from_addr'], 'to_addr': data['to_addr'], 'from_lat': data.get('from_lat', 39.904), 'from_lng': data.get('from_lng', 116.407), 'to_lat': data.get('to_lat', 39.904), 'to_lng': data.get('to_lng', 116.407), 'cargo': data.get('cargo', '普通包裹'), 'weight': float(data.get('weight', 1)), 'price': 0, 'distance_km': 0, 'status': 'pending', 'urgency': data.get('urgency', 'normal'), 'insurance': float(data.get('insurance', 0)), 'note': data.get('note', ''), 'demander_id': data.get('user_id', ''), 'provider_id': None, 'drone_id': None, 'created_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), 'updated_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S'), 'completed_at': None, 'rating': None, 'comment': None } dist = calc_distance(order['from_lat'], order['from_lng'], order['to_lat'], order['to_lng']) price = calc_price(dist, order['weight'], order['urgency']) order['distance_km'] = dist order['price'] = price orders.insert(0, order) save_json('orders.json', orders) # auto generate quotes from available providers auto_generate_quotes(order) return jsonify({'code': 0, 'data': order}), 201 def auto_generate_quotes(order): drones = load_json('drones.json') users = load_json('users.json') quotes = load_json('quotes.json') available = [d for d in drones if d['status'] == 'available'] for drone in available[:3]: provider = next((u for u in users if u['id'] == drone['user_id']), None) if not provider: continue base_price = order['price'] variance = 0.8 + (hash(drone['id']) % 40) / 100 quote = { 'id': gen_id('Q'), 'order_id': order['id'], 'provider_id': drone['user_id'], 'drone_id': drone['id'], 'price': int(base_price * variance), 'eta_minutes': max(int(order['distance_km'] / 1.2 * (0.8 + (hash(drone['name']) % 40) / 100)), 5), 'distance_km': order['distance_km'], 'provider_name': provider['name'], 'drone_name': drone['name'], 'provider_rating': provider.get('credit_score', 800) / 100 * 0.5 + 2.5, 'provider_flights': drone['total_flights'], 'status': 'active', 'created_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S') } quote['provider_rating'] = round(min(quote['provider_rating'], 5.0), 1) quotes.append(quote) save_json('quotes.json', quotes) @app.route('/api/orders/') def get_order(order_id): orders = load_json('orders.json') order = next((o for o in orders if o['id'] == order_id), None) if not order: return jsonify({'code': 404, 'msg': '订单不存在'}), 404 return jsonify({'code': 0, 'data': order}) @app.route('/api/orders//accept', methods=['POST']) def accept_order(order_id): orders = load_json('orders.json') idx = next((i for i, o in enumerate(orders) if o['id'] == order_id), None) if idx is None: return jsonify({'code': 404, 'msg': '订单不存在'}), 404 data = request.get_json() orders[idx]['status'] = 'accepted' orders[idx]['provider_id'] = data.get('provider_id') orders[idx]['drone_id'] = data.get('drone_id') orders[idx]['updated_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S') # update drone status drone_id = data.get('drone_id') if drone_id: drones = load_json('drones.json') for d in drones: if d['id'] == drone_id: d['status'] = 'busy' break save_json('drones.json', drones) save_json('orders.json', orders) return jsonify({'code': 0, 'data': orders[idx]}) @app.route('/api/orders//transit', methods=['POST']) def start_transit(order_id): orders = load_json('orders.json') idx = next((i for i, o in enumerate(orders) if o['id'] == order_id), None) if idx is None: return jsonify({'code': 404, 'msg': '订单不存在'}), 404 orders[idx]['status'] = 'in_transit' orders[idx]['updated_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S') save_json('orders.json', orders) return jsonify({'code': 0, 'data': orders[idx]}) @app.route('/api/orders//complete', methods=['POST']) def complete_order(order_id): orders = load_json('orders.json') idx = next((i for i, o in enumerate(orders) if o['id'] == order_id), None) if idx is None: return jsonify({'code': 404, 'msg': '订单不存在'}), 404 orders[idx]['status'] = 'completed' orders[idx]['completed_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S') orders[idx]['updated_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S') # release drone drone_id = orders[idx].get('drone_id') if drone_id: drones = load_json('drones.json') for d in drones: if d['id'] == drone_id: d['status'] = 'available' d['total_flights'] = d.get('total_flights', 0) + 1 break save_json('drones.json', drones) save_json('orders.json', orders) return jsonify({'code': 0, 'data': orders[idx]}) @app.route('/api/orders//cancel', methods=['POST']) def cancel_order(order_id): orders = load_json('orders.json') idx = next((i for i, o in enumerate(orders) if o['id'] == order_id), None) if idx is None: return jsonify({'code': 404, 'msg': '订单不存在'}), 404 orders[idx]['status'] = 'cancelled' orders[idx]['updated_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S') drone_id = orders[idx].get('drone_id') if drone_id: drones = load_json('drones.json') for d in drones: if d['id'] == drone_id: d['status'] = 'available' break save_json('drones.json', drones) save_json('orders.json', orders) return jsonify({'code': 0, 'data': orders[idx]}) @app.route('/api/orders//rate', methods=['POST']) def rate_order(order_id): data = request.get_json() orders = load_json('orders.json') idx = next((i for i, o in enumerate(orders) if o['id'] == order_id), None) if idx is None: return jsonify({'code': 404, 'msg': '订单不存在'}), 404 orders[idx]['rating'] = data.get('rating', 5) orders[idx]['comment'] = data.get('comment', '') save_json('orders.json', orders) return jsonify({'code': 0, 'data': orders[idx]}) # ── Quotes ── @app.route('/api/quotes') def list_quotes(): quotes = load_json('quotes.json') order_id = request.args.get('order_id') if order_id: quotes = [q for q in quotes if q['order_id'] == order_id] return jsonify({'code': 0, 'data': quotes}) @app.route('/api/quotes', methods=['POST']) def create_quote(): data = request.get_json() quotes = load_json('quotes.json') quote = { 'id': gen_id('Q'), 'order_id': data['order_id'], 'provider_id': data['provider_id'], 'drone_id': data['drone_id'], 'price': data.get('price', 100), 'eta_minutes': data.get('eta_minutes', 15), 'distance_km': data.get('distance_km', 5), 'provider_name': data.get('provider_name', '未知'), 'drone_name': data.get('drone_name', '未知机型'), 'provider_rating': data.get('provider_rating', 4.5), 'provider_flights': data.get('provider_flights', 0), 'status': 'active', 'created_at': datetime.now().strftime('%Y-%m-%d %H:%M:%S') } quotes.append(quote) save_json('quotes.json', quotes) return jsonify({'code': 0, 'data': quote}), 201 @app.route('/api/quotes//accept', methods=['POST']) def accept_quote(quote_id): quotes = load_json('quotes.json') q_idx = next((i for i, q in enumerate(quotes) if q['id'] == quote_id), None) if q_idx is None: return jsonify({'code': 404, 'msg': '报价不存在'}), 404 quote = quotes[q_idx] quotes[q_idx]['status'] = 'accepted' save_json('quotes.json', quotes) orders = load_json('orders.json') o_idx = next((i for i, o in enumerate(orders) if o['id'] == quote['order_id']), None) if o_idx is not None: orders[o_idx]['status'] = 'accepted' orders[o_idx]['provider_id'] = quote['provider_id'] orders[o_idx]['drone_id'] = quote['drone_id'] orders[o_idx]['price'] = quote['price'] orders[o_idx]['updated_at'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S') save_json('orders.json', orders) return jsonify({'code': 0, 'data': quote}) # ── Dashboard Stats ── @app.route('/api/dashboard/stats') def dashboard_stats(): role = request.args.get('role', 'demander') user_id = request.args.get('user_id') orders = load_json('orders.json') if user_id: if role == 'demander': orders = [o for o in orders if o['demander_id'] == user_id] else: orders = [o for o in orders if o.get('provider_id') == user_id] total = len(orders) pending = len([o for o in orders if o['status'] == 'pending']) in_transit = len([o for o in orders if o['status'] in ('accepted', 'in_transit')]) completed = len([o for o in orders if o['status'] == 'completed']) cancelled = len([o for o in orders if o['status'] == 'cancelled']) total_revenue = sum(o['price'] for o in orders if o['status'] == 'completed') avg_rating = 0 rated = [o['rating'] for o in orders if o.get('rating')] if rated: avg_rating = round(sum(rated) / len(rated), 1) drones_online = 0 if role == 'provider': user_drones = [d for d in load_json('drones.json') if d.get('user_id') == user_id] drones_online = len([d for d in user_drones if d['status'] == 'available']) else: drones_online = len([d for d in load_json('drones.json') if d['status'] == 'available']) stats = { 'total_orders': total, 'pending_orders': pending, 'in_transit_orders': in_transit, 'completed_orders': completed, 'cancelled_orders': cancelled, 'total_revenue': total_revenue, 'avg_rating': avg_rating, 'online_drones': drones_online, 'completion_rate': round(completed / total * 100, 1) if total > 0 else 0 } return jsonify({'code': 0, 'data': stats}) # ── Agent analysis (AI simulation) ── @app.route('/api/agent/analyze', methods=['POST']) def agent_analyze(): data = request.get_json() action = data.get('action', '') if action == 'credit_check': orders = load_json('orders.json') flags = [] for o in orders: if o['status'] == 'cancelled': flags.append({ 'order_id': o['id'], 'issue': '订单被取消', 'severity': '轻微', 'suggestion': '建议关注取消原因' }) if o.get('rating') and o['rating'] <= 2: flags.append({ 'order_id': o['id'], 'issue': '低分评价', 'severity': '中等', 'suggestion': '建议回访用户了解问题' }) return jsonify({'code': 0, 'data': {'issues': flags, 'summary': f'发现 {len(flags)} 个值得关注的问题'}}) elif action == 'pricing_suggestion': orders = load_json('orders.json') from datetime import datetime now = datetime.now() hour = now.hour if 10 <= hour <= 12 or 17 <= hour <= 19: multiplier = 1.2 reason = '当前为高峰时段,建议加价20%' elif 22 <= hour or hour <= 6: multiplier = 1.3 reason = '当前为夜间时段,建议加价30%' else: multiplier = 1.0 reason = '当前为平峰时段,维持基础费率' return jsonify({ 'code': 0, 'data': { 'multiplier': multiplier, 'reason': reason, 'suggestion': f'建议当前时段基础费率调整为 {multiplier}x' } }) elif action == 'anomaly_detect': orders = load_json('orders.json') anomalies = [] for o in orders: if o['status'] == 'in_transit': duration_hours = (datetime.now() - datetime.strptime(o['created_at'], '%Y-%m-%d %H:%M:%S')).total_seconds() / 3600 if duration_hours > 1: anomalies.append({ 'order_id': o['id'], 'type': '配送超时', 'detail': f'配送已超过{duration_hours:.1f}小时', 'suggestion': '建议联系承运方确认配送状态' }) return jsonify({'code': 0, 'data': {'anomalies': anomalies, 'count': len(anomalies)}}) return jsonify({'code': 400, 'msg': '未知的分析类型'}) # ── Agent management (for future real AI integration) ── @app.route('/api/agent/settings', methods=['GET']) def agent_settings(): path = os.path.join(DB_DIR, 'agent_settings.json') if not os.path.exists(path): default = { 'providers': { 'health_check_enabled': True, 'auto_reconciliation': True, 'cert_reminder_days': 30 }, 'pricing': { 'dynamic_pricing_enabled': True, 'max_surge_multiplier': 2.0, 'peak_hours': ['10:00-12:00', '17:00-20:00'] }, 'anomaly': { 'auto_handle_l1': True, 'auto_handle_l2': False, 'notification_channels': ['sms', 'app'] }, 'credit': { 'fraud_detection_enabled': True, 'min_rating_threshold': 3, 'auto_penalty': True } } save_json('agent_settings.json', default) return jsonify({'code': 0, 'data': default}) return jsonify({'code': 0, 'data': load_json('agent_settings.json')}) @app.route('/api/agent/settings', methods=['PUT']) def update_agent_settings(): data = request.get_json() save_json('agent_settings.json', data) return jsonify({'code': 0, 'msg': 'Agent设置已更新'}) # ── Logging ── @app.route('/api/logs') def get_logs(): path = os.path.join(DB_DIR, 'logs.json') if not os.path.exists(path): return jsonify({'code': 0, 'data': []}) logs = load_json('logs.json') logs.sort(key=lambda x: x.get('timestamp', ''), reverse=True) return jsonify({'code': 0, 'data': logs[:100]}) @app.route('/api/logs', methods=['POST']) def add_log(): data = request.get_json() logs = load_json('logs.json') if os.path.exists(os.path.join(DB_DIR, 'logs.json')) else [] log = { 'id': gen_id('LOG'), 'type': data.get('type', 'info'), 'message': data.get('message', ''), 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S') } logs.append(log) save_json('logs.json', logs) return jsonify({'code': 0, 'data': log}), 201 # ── Health ── @app.route('/api/health') def health(): return jsonify({'status': 'ok', 'timestamp': datetime.now().strftime('%Y-%m-%d %H:%M:%S')}) if __name__ == '__main__': print(""" ╔══════════════════════════════════════════╗ ║ 空运宝 - 后端服务 v1.0 ║ ║ Listening at: http://localhost:5000 ║ ║ API: http://localhost:5000/api/... ║ ╚══════════════════════════════════════════╝ """) app.run(host='0.0.0.0', port=5000, debug=True)