from flask import request, jsonify, current_app from datetime import datetime, timedelta from app import db from app.models import Product, License, Device, Version from app.utils.crypto import generate_hash, verify_hash, generate_signature from . import api_bp @api_bp.route('/auth/verify', methods=['POST']) def verify_license(): """验证卡密接口""" try: # 检查Content-Type content_type = request.content_type if content_type and 'application/json' not in content_type: current_app.logger.warning(f"验证请求:Content-Type不正确 - {content_type}") data = request.get_json(force=True) # 强制解析JSON,即使Content-Type不正确 if not data: # 尝试获取原始数据用于调试 raw_data = request.get_data(as_text=True) current_app.logger.warning(f"验证请求:请求数据为空或无法解析 - Content-Type: {content_type}, 原始数据: {raw_data[:200] if raw_data else 'None'}") return jsonify({ 'success': False, 'message': '请求数据为空或格式错误' }), 400 # 获取请求参数 software_id = data.get('software_id') license_key = data.get('license_key') machine_code = data.get('machine_code') timestamp = data.get('timestamp') signature = data.get('signature') # 验证必填参数,并记录缺失的参数 missing_params = [] if not software_id: missing_params.append('software_id') if not license_key: missing_params.append('license_key') if not machine_code: missing_params.append('machine_code') if timestamp is None: missing_params.append('timestamp') if not signature: missing_params.append('signature') if missing_params: current_app.logger.warning(f"验证请求:缺少必要参数 - {', '.join(missing_params)}") return jsonify({ 'success': False, 'message': f'缺少必要参数: {", ".join(missing_params)}' }), 400 # 验证时间戳(防止重放攻击) try: # 确保timestamp是数字类型 if isinstance(timestamp, str): timestamp = int(timestamp) # 使用utcfromtimestamp确保时间戳被解析为UTC时间(与time.time()生成的UTC时间戳一致) request_time = datetime.utcfromtimestamp(timestamp) # 使用UTC时间进行比较 current_time = datetime.utcnow() time_diff = abs((current_time - request_time).total_seconds()) if time_diff > 300: # 5分钟有效期 current_app.logger.warning(f"验证请求:请求已过期 - 时间差: {time_diff}秒, 当前时间: {current_time}, 请求时间: {request_time}") return jsonify({ 'success': False, 'message': '请求已过期' }), 400 except (ValueError, TypeError) as e: current_app.logger.warning(f"验证请求:时间戳格式错误 - timestamp: {timestamp}, 错误: {str(e)}") return jsonify({ 'success': False, 'message': f'时间戳格式错误: {str(e)}' }), 400 # 验证签名 secret_key = current_app.config.get('AUTH_SECRET_KEY', 'default-secret-key') signature_data = f"{software_id}{license_key}{machine_code}{timestamp}" expected_signature = generate_signature(signature_data, secret_key) # 记录请求信息(不记录敏感信息) current_app.logger.info(f"验证请求 - software_id: {software_id}, machine_code: {machine_code[:8]}..., timestamp: {timestamp}") # 调试信息(仅在调试模式记录,且不输出密钥) if current_app.debug: current_app.logger.debug(f"签名数据: {signature_data}") current_app.logger.debug(f"客户端签名: {signature}") current_app.logger.debug(f"服务端签名匹配: {signature == expected_signature}") if signature != expected_signature: current_app.logger.warning(f"验证请求:签名验证失败 - software_id: {software_id}") return jsonify({ 'success': False, 'message': '签名验证失败' }), 401 # 查找产品 product = Product.query.filter_by(product_id=software_id).first() if not product: return jsonify({ 'success': False, 'message': '产品不存在' }), 404 if not product.is_enabled(): return jsonify({ 'success': False, 'message': '产品已禁用' }), 403 # 查找卡密 license_obj = License.query.filter_by( license_key=license_key, product_id=software_id ).first() if not license_obj: return jsonify({ 'success': False, 'message': '卡密不存在' }), 404 # 验证卡密 software_version = request.json.get('software_version', '1.0.0') if request.json else '1.0.0' success, message = license_obj.verify(machine_code, software_version) if not success: return jsonify({ 'success': False, 'message': message }), 403 # 处理首次激活 if license_obj.status == 0: # 未激活 software_version = request.json.get('software_version', '1.0.0') if request.json else '1.0.0' success, message = license_obj.activate( machine_code, software_version ) if not success: return jsonify({ 'success': False, 'message': message }), 403 # 更新设备验证时间 device = Device.query.filter_by(machine_code=machine_code).first() if device: device.update_verify_time() # 获取最新版本信息 latest_version = Version.query.filter_by( product_id=software_id, publish_status=1 ).order_by(Version.update_time.desc(), Version.create_time.desc()).first() response_data = { 'license_key': license_obj.license_key, 'type': license_obj.type, 'type_name': '试用' if license_obj.is_trial() else '正式', 'expire_time': license_obj.expire_time.isoformat() if license_obj.expire_time else None, 'remaining_days': license_obj.get_remaining_days(), 'product_name': product.product_name, 'activate_time': license_obj.activate_time.isoformat() if license_obj.activate_time else None } # 添加版本信息 if latest_version: current_version = request.json.get('software_version', '1.0.0') if request.json else '1.0.0' response_data.update({ 'new_version': latest_version.version_num, 'download_url': latest_version.download_url, 'force_update': latest_version.is_force_update(), 'update_log': latest_version.update_log, 'need_update': latest_version.version_num != current_version }) return jsonify({ 'success': True, 'message': '验证成功', 'data': response_data }) except Exception as e: # 记录详细的错误信息,包括堆栈跟踪 import traceback error_trace = traceback.format_exc() current_app.logger.error(f"卡密验证失败: {str(e)}\n{error_trace}") # 尝试回滚数据库事务 try: db.session.rollback() except: pass # 检查是否是数据库连接错误 error_str = str(e).lower() error_type = type(e).__name__ if 'operationalerror' in error_str or 'connection' in error_str or 'database' in error_str or 'OperationalError' in error_type: return jsonify({ 'success': False, 'message': '数据库连接失败,请稍后重试' }), 503 elif 'timeout' in error_str or 'Timeout' in error_type: return jsonify({ 'success': False, 'message': '请求处理超时,请稍后重试' }), 503 else: return jsonify({ 'success': False, 'message': '服务器内部错误' }), 500 @api_bp.route('/auth/activate', methods=['POST']) def activate_license(): """激活卡密接口""" try: data = request.get_json() if not data: return jsonify({ 'success': False, 'message': '请求数据为空' }), 400 software_id = data.get('software_id') license_key = data.get('license_key') machine_code = data.get('machine_code') software_version = data.get('software_version', '1.0.0') # 验证必填参数 if not all([software_id, license_key, machine_code]): return jsonify({ 'success': False, 'message': '缺少必要参数' }), 400 # 查找产品 product = Product.query.filter_by(product_id=software_id).first() if not product: return jsonify({ 'success': False, 'message': '产品不存在' }), 404 # 查找卡密 license_obj = License.query.filter_by( license_key=license_key, product_id=software_id ).first() if not license_obj: return jsonify({ 'success': False, 'message': '卡密不存在' }), 404 # 激活卡密 success, message = license_obj.activate(machine_code, software_version) if not success: return jsonify({ 'success': False, 'message': message }), 400 return jsonify({ 'success': True, 'message': '激活成功', 'data': license_obj.to_dict() }) except Exception as e: current_app.logger.error(f"卡密激活失败: {str(e)}") return jsonify({ 'success': False, 'message': '服务器内部错误' }), 500 @api_bp.route('/auth/info', methods=['GET']) def get_auth_info(): """获取授权信息接口""" try: software_id = request.args.get('software_id') machine_code = request.args.get('machine_code') if not software_id or not machine_code: return jsonify({ 'success': False, 'message': '缺少必要参数' }), 400 # 查找设备 device = Device.query.filter_by( machine_code=machine_code, status=1 ).first() if not device: return jsonify({ 'success': False, 'message': '设备未激活' }), 404 # 查找关联的卡密 license_obj = device.license if not license_obj or license_obj.product_id != software_id: return jsonify({ 'success': False, 'message': '授权信息不匹配' }), 403 # 检查授权状态 if license_obj.is_expired(): return jsonify({ 'success': False, 'message': '授权已过期' }), 403 # 返回授权信息 return jsonify({ 'success': True, 'data': { 'license_key': license_obj.license_key, 'type': license_obj.type, 'type_name': '试用' if license_obj.is_trial() else '正式', 'expire_time': license_obj.expire_time.isoformat() if license_obj.expire_time else None, 'remaining_days': license_obj.get_remaining_days(), 'activate_time': license_obj.activate_time.isoformat() if license_obj.activate_time else None, 'last_verify_time': license_obj.last_verify_time.isoformat() if license_obj.last_verify_time else None } }) except Exception as e: current_app.logger.error(f"获取授权信息失败: {str(e)}") return jsonify({ 'success': False, 'message': '服务器内部错误' }), 500 @api_bp.route('/auth/heartbeat', methods=['POST']) def heartbeat(): """心跳接口(用于在线验证)""" try: data = request.get_json() if not data: return jsonify({'success': False}), 400 software_id = data.get('software_id') machine_code = data.get('machine_code') if not software_id or not machine_code: return jsonify({'success': False}), 400 # 更新设备最后验证时间 device = Device.query.filter_by( machine_code=machine_code, status=1 ).first() if device: device.update_verify_time() return jsonify({'success': True}) except Exception as e: current_app.logger.error(f"心跳处理失败: {str(e)}") return jsonify({'success': False}), 500 @api_bp.route('/auth/unbind_device', methods=['POST']) def unbind_device(): """用户端解绑当前设备接口""" try: data = request.get_json() if not data: return jsonify({ 'success': False, 'message': '请求数据为空' }), 400 software_id = data.get('software_id') machine_code = data.get('machine_code') timestamp = data.get('timestamp') signature = data.get('signature') # 验证必填参数 if not all([software_id, machine_code, timestamp, signature]): return jsonify({ 'success': False, 'message': '缺少必要参数' }), 400 # 验证签名 secret_key = current_app.config.get('AUTH_SECRET_KEY', 'default-secret-key') signature_data = f"{software_id}{machine_code}{timestamp}" expected_signature = generate_signature(signature_data, secret_key) if signature != expected_signature: return jsonify({ 'success': False, 'message': '签名验证失败' }), 401 # 查找产品 product = Product.query.filter_by(product_id=software_id).first() if not product: return jsonify({ 'success': False, 'message': '产品不存在' }), 404 # 查找设备 device = Device.query.filter_by( machine_code=machine_code, product_id=software_id ).first() if not device: return jsonify({ 'success': False, 'message': '设备不存在' }), 404 # 解绑设备与卡密的关联 device.unbind_license() return jsonify({ 'success': True, 'message': '设备解绑成功' }) except Exception as e: current_app.logger.error(f"设备解绑失败: {str(e)}") return jsonify({ 'success': False, 'message': '服务器内部错误' }), 500 @api_bp.route('/auth/unbind_license_user', methods=['POST']) def unbind_license_user(): """用户端解绑卡密接口""" try: data = request.get_json() if not data: return jsonify({ 'success': False, 'message': '请求数据为空' }), 400 software_id = data.get('software_id') license_key = data.get('license_key') machine_code = data.get('machine_code') timestamp = data.get('timestamp') signature = data.get('signature') # 验证必填参数 if not all([software_id, license_key, machine_code, timestamp, signature]): return jsonify({ 'success': False, 'message': '缺少必要参数' }), 400 # 验证签名 secret_key = current_app.config.get('AUTH_SECRET_KEY', 'default-secret-key') signature_data = f"{software_id}{license_key}{machine_code}{timestamp}" expected_signature = generate_signature(signature_data, secret_key) if signature != expected_signature: return jsonify({ 'success': False, 'message': '签名验证失败' }), 401 # 查找产品 product = Product.query.filter_by(product_id=software_id).first() if not product: return jsonify({ 'success': False, 'message': '产品不存在' }), 404 # 查找卡密 license_obj = License.query.filter_by( license_key=license_key, product_id=software_id ).first() if not license_obj: return jsonify({ 'success': False, 'message': '卡密不存在' }), 404 # 检查卡密是否绑定到当前机器码 if license_obj.bind_machine_code != machine_code: return jsonify({ 'success': False, 'message': '卡密未绑定到当前设备' }), 400 # 执行解绑操作 success, message = license_obj.unbind() if not success: return jsonify({ 'success': False, 'message': message }), 400 return jsonify({ 'success': True, 'message': '解绑成功' }) except Exception as e: current_app.logger.error(f"卡密解绑失败: {str(e)}") return jsonify({ 'success': False, 'message': '服务器内部错误' }), 500