from flask import request, jsonify, current_app from datetime import datetime from app import db from app.models import Product, License, Device, Version from . import api_bp from .decorators import require_login, require_admin from sqlalchemy import func, case import traceback import sys from app.utils.logger import log_operation @api_bp.route('/products', methods=['GET']) @require_login def get_products(): """获取产品列表""" try: page = request.args.get('page', 1, type=int) per_page = min(request.args.get('per_page', 20, type=int), 100) keyword = request.args.get('keyword', '').strip() include_stats = request.args.get('include_stats', 'true').lower() == 'true' query = Product.query if keyword: query = query.filter( Product.product_name.like(f'%{keyword}%') | Product.description.like(f'%{keyword}%') ) query = query.order_by(Product.create_time.desc()) pagination = query.paginate(page=page, per_page=per_page, error_out=False) products = pagination.items # 优化:批量查询统计信息,避免N+1查询问题 if include_stats and products: product_ids = [p.product_id for p in products] # 批量查询license统计 license_stats = db.session.query( License.product_id, func.count(License.license_id).label('total_licenses'), func.sum(case((License.status == 1, 1), else_=0)).label('active_licenses') ).filter(License.product_id.in_(product_ids)).group_by(License.product_id).all() license_dict = {pid: {'total': 0, 'active': 0} for pid in product_ids} for stat in license_stats: license_dict[stat.product_id] = { 'total': stat.total_licenses or 0, 'active': stat.active_licenses or 0 } # 批量查询device统计 device_stats = db.session.query( Device.product_id, func.count(Device.device_id).label('total_devices') ).filter( Device.product_id.in_(product_ids), Device.status == 1 ).group_by(Device.product_id).all() device_dict = {pid: 0 for pid in product_ids} for stat in device_stats: device_dict[stat.product_id] = stat.total_devices or 0 # 批量查询最新版本 version_stats = db.session.query( Version.product_id, Version.version_num, Version.create_time ).filter( Version.product_id.in_(product_ids), Version.publish_status == 1 ).order_by(Version.create_time.desc()).all() version_dict = {} for v in version_stats: if v.product_id not in version_dict: version_dict[v.product_id] = v.version_num # 为每个产品添加统计信息 for product in products: pid = product.product_id stats = license_dict.get(pid, {'total': 0, 'active': 0}) product._cached_stats = { 'total_licenses': stats['total'], 'active_licenses': stats['active'], 'total_devices': device_dict.get(pid, 0), 'latest_version': version_dict.get(pid) } products = [product.to_dict(include_stats=include_stats) for product in products] return jsonify({ 'success': True, 'data': { 'products': products, 'pagination': { 'page': page, 'per_page': per_page, 'total': pagination.total, 'pages': pagination.pages, 'has_prev': pagination.has_prev, 'has_next': pagination.has_next } } }) except Exception as e: current_app.logger.error(f"获取产品列表失败: {str(e)}") current_app.logger.error(f"错误类型: {type(e)}") current_app.logger.error(f"错误堆栈: {traceback.format_exc()}") return jsonify({ 'success': False, 'message': '服务器内部错误,请稍后重试' }), 500 @api_bp.route('/products', methods=['POST']) @require_login def create_product(): """创建产品""" try: data = request.get_json() if not data: return jsonify({ 'success': False, 'message': '请求数据为空' }), 400 product_name = data.get('product_name', '').strip() description = data.get('description', '').strip() custom_id = data.get('product_id', '').strip() if not product_name: return jsonify({ 'success': False, 'message': '产品名称不能为空' }), 400 # 检查自定义ID是否重复 if custom_id: existing = Product.query.filter_by(product_id=custom_id).first() if existing: return jsonify({ 'success': False, 'message': '产品ID已存在' }), 400 # 创建产品 product = Product( product_id=custom_id if custom_id else None, product_name=product_name, description=description, status=1 ) db.session.add(product) db.session.commit() # 记录操作日志 log_operation('CREATE_PRODUCT', 'PRODUCT', product.product_id, { 'product_name': product.product_name, 'description': product.description }) return jsonify({ 'success': True, 'message': '产品创建成功', 'data': product.to_dict() }) except Exception as e: db.session.rollback() current_app.logger.error(f"创建产品失败: {str(e)}") return jsonify({ 'success': False, 'message': '服务器内部错误' }), 500 @api_bp.route('/products/', methods=['GET']) @require_login def get_product(product_id): """获取产品详情""" try: product = Product.query.filter_by(product_id=product_id).first() if not product: return jsonify({ 'success': False, 'message': '产品不存在' }), 404 return jsonify({ 'success': True, 'data': product.to_dict(include_stats=True) }) except Exception as e: current_app.logger.error(f"获取产品详情失败: {str(e)}") return jsonify({ 'success': False, 'message': '服务器内部错误' }), 500 @api_bp.route('/products/', methods=['PUT']) @require_login def update_product(product_id): """更新产品""" try: product = Product.query.filter_by(product_id=product_id).first() if not product: return jsonify({ 'success': False, 'message': '产品不存在' }), 404 data = request.get_json() if not data: return jsonify({ 'success': False, 'message': '请求数据为空' }), 400 if 'product_name' in data: product.product_name = data['product_name'].strip() if 'description' in data: product.description = data['description'].strip() if 'status' in data: product.status = data['status'] db.session.commit() # 记录操作日志 log_operation('UPDATE_PRODUCT', 'PRODUCT', product.product_id, { 'product_name': product.product_name, 'description': product.description }) return jsonify({ 'success': True, 'message': '产品更新成功', 'data': product.to_dict() }) except Exception as e: db.session.rollback() current_app.logger.error(f"更新产品失败: {str(e)}") return jsonify({ 'success': False, 'message': '服务器内部错误' }), 500 @api_bp.route('/products/', methods=['DELETE']) @require_login def delete_product(product_id): """删除产品""" try: product = Product.query.filter_by(product_id=product_id).first() if not product: return jsonify({ 'success': False, 'message': '产品不存在' }), 404 # 检查是否有关联的卡密 license_count = product.licenses.count() if license_count > 0: return jsonify({ 'success': False, 'message': f'产品下还有 {license_count} 个卡密,无法删除' }), 400 db.session.delete(product) db.session.commit() # 记录操作日志 log_operation('DELETE_PRODUCT', 'PRODUCT', product.product_id, { 'product_name': product.product_name }) return jsonify({ 'success': True, 'message': '产品删除成功' }) except Exception as e: db.session.rollback() current_app.logger.error(f"删除产品失败: {str(e)}") return jsonify({ 'success': False, 'message': '服务器内部错误' }), 500 @api_bp.route('/products/batch', methods=['DELETE']) @require_login def batch_delete_products(): """批量删除产品""" try: data = request.get_json() if not data or 'product_ids' not in data: return jsonify({ 'success': False, 'message': '请求数据为空或缺少product_ids字段' }), 400 product_ids = data['product_ids'] if not isinstance(product_ids, list) or len(product_ids) == 0: return jsonify({ 'success': False, 'message': 'product_ids必须是非空列表' }), 400 # 查找所有要删除的产品 products = Product.query.filter(Product.product_id.in_(product_ids)).all() if len(products) != len(product_ids): found_ids = [p.product_id for p in products] missing_ids = [pid for pid in product_ids if pid not in found_ids] return jsonify({ 'success': False, 'message': f'以下产品不存在: {", ".join(missing_ids)}' }), 404 # 检查是否有产品有关联的卡密 undeletable_products = [] for product in products: license_count = product.licenses.count() if license_count > 0: undeletable_products.append({ 'product_id': product.product_id, 'product_name': product.product_name, 'license_count': license_count }) if undeletable_products: return jsonify({ 'success': False, 'message': '部分产品无法删除,因为有关联的卡密', 'undeletable_products': undeletable_products }), 400 # 批量删除产品 for product in products: db.session.delete(product) db.session.commit() # 记录操作日志 product_ids = [p.product_id for p in products] log_operation('BATCH_DELETE_PRODUCTS', 'PRODUCT', None, { 'product_ids': product_ids, 'count': len(products) }) return jsonify({ 'success': True, 'message': f'成功删除 {len(products)} 个产品' }) except Exception as e: db.session.rollback() current_app.logger.error(f"批量删除产品失败: {str(e)}") return jsonify({ 'success': False, 'message': '服务器内部错误' }), 500 @api_bp.route('/products/batch/status', methods=['PUT']) @require_login def batch_update_product_status(): """批量更新产品状态""" try: data = request.get_json() if not data or 'product_ids' not in data or 'status' not in data: return jsonify({ 'success': False, 'message': '请求数据为空或缺少product_ids/status字段' }), 400 product_ids = data['product_ids'] status = data['status'] if not isinstance(product_ids, list) or len(product_ids) == 0: return jsonify({ 'success': False, 'message': 'product_ids必须是非空列表' }), 400 if status not in [0, 1]: return jsonify({ 'success': False, 'message': 'status必须是0(禁用)或1(启用)' }), 400 # 查找所有要更新的产品 products = Product.query.filter(Product.product_id.in_(product_ids)).all() if len(products) != len(product_ids): found_ids = [p.product_id for p in products] missing_ids = [pid for pid in product_ids if pid not in found_ids] return jsonify({ 'success': False, 'message': f'以下产品不存在: {", ".join(missing_ids)}' }), 404 # 批量更新产品状态 for product in products: product.status = status db.session.commit() # 记录操作日志 product_ids = [p.product_id for p in products] log_operation('BATCH_UPDATE_PRODUCT_STATUS', 'PRODUCT', None, { 'product_ids': product_ids, 'status': status, 'count': len(products) }) status_name = '启用' if status == 1 else '禁用' return jsonify({ 'success': True, 'message': f'成功{status_name} {len(products)} 个产品' }) except Exception as e: db.session.rollback() current_app.logger.error(f"批量更新产品状态失败: {str(e)}") return jsonify({ 'success': False, 'message': '服务器内部错误' }), 500