Kamixitong/app/api/product.py

432 lines
14 KiB
Python
Raw Normal View History

2025-11-11 21:39:12 +08:00
from flask import request, jsonify, current_app
from datetime import datetime
from app import db
2025-11-15 23:57:05 +08:00
from app.models import Product, License, Device, Version
2025-11-11 21:39:12 +08:00
from . import api_bp
2025-11-15 23:57:05 +08:00
from .decorators import require_login, require_admin
from sqlalchemy import func, case
2025-11-11 21:39:12 +08:00
import traceback
import sys
2025-11-15 23:57:05 +08:00
from app.utils.logger import log_operation
2025-11-11 21:39:12 +08:00
@api_bp.route('/products', methods=['GET'])
2025-11-15 23:57:05 +08:00
@require_login
2025-11-11 21:39:12 +08:00
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()
2025-11-15 23:57:05 +08:00
include_stats = request.args.get('include_stats', 'true').lower() == 'true'
2025-11-11 21:39:12 +08:00
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)
2025-11-15 23:57:05 +08:00
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]
2025-11-11 21:39:12 +08:00
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()}")
2025-11-13 16:51:51 +08:00
2025-11-11 21:39:12 +08:00
return jsonify({
'success': False,
2025-11-13 16:51:51 +08:00
'message': '服务器内部错误,请稍后重试'
2025-11-11 21:39:12 +08:00
}), 500
@api_bp.route('/products', methods=['POST'])
2025-11-15 23:57:05 +08:00
@require_login
2025-11-11 21:39:12 +08:00
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()
2025-11-15 23:57:05 +08:00
# 记录操作日志
log_operation('CREATE_PRODUCT', 'PRODUCT', product.product_id, {
'product_name': product.product_name,
'description': product.description
})
2025-11-11 21:39:12 +08:00
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/<product_id>', methods=['GET'])
2025-11-15 23:57:05 +08:00
@require_login
2025-11-11 21:39:12 +08:00
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/<product_id>', methods=['PUT'])
2025-11-15 23:57:05 +08:00
@require_login
2025-11-11 21:39:12 +08:00
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()
2025-11-15 23:57:05 +08:00
# 记录操作日志
log_operation('UPDATE_PRODUCT', 'PRODUCT', product.product_id, {
'product_name': product.product_name,
'description': product.description
})
2025-11-11 21:39:12 +08:00
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/<product_id>', methods=['DELETE'])
2025-11-15 23:57:05 +08:00
@require_login
2025-11-11 21:39:12 +08:00
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()
2025-11-15 23:57:05 +08:00
# 记录操作日志
log_operation('DELETE_PRODUCT', 'PRODUCT', product.product_id, {
'product_name': product.product_name
})
2025-11-11 21:39:12 +08:00
return jsonify({
'success': True,
'message': '产品删除成功'
})
except Exception as e:
db.session.rollback()
current_app.logger.error(f"删除产品失败: {str(e)}")
2025-11-12 15:11:05 +08:00
return jsonify({
'success': False,
'message': '服务器内部错误'
}), 500
@api_bp.route('/products/batch', methods=['DELETE'])
2025-11-15 23:57:05 +08:00
@require_login
2025-11-12 15:11:05 +08:00
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()
2025-11-15 23:57:05 +08:00
# 记录操作日志
product_ids = [p.product_id for p in products]
log_operation('BATCH_DELETE_PRODUCTS', 'PRODUCT', None, {
'product_ids': product_ids,
'count': len(products)
})
2025-11-12 15:11:05 +08:00
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'])
2025-11-15 23:57:05 +08:00
@require_login
2025-11-12 15:11:05 +08:00
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()
2025-11-15 23:57:05 +08:00
# 记录操作日志
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)
})
2025-11-12 15:11:05 +08:00
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)}")
2025-11-11 21:39:12 +08:00
return jsonify({
'success': False,
'message': '服务器内部错误'
}), 500