585 lines
19 KiB
Python
585 lines
19 KiB
Python
from flask import request, jsonify, current_app
|
||
from app import db
|
||
from app.models import Product, License, Ticket, Version
|
||
from . import api_bp
|
||
from app.utils.logger import log_operation
|
||
from app.utils.cors_middleware import handle_preflight, cors_after
|
||
import re
|
||
|
||
# 注册请求和响应处理器
|
||
@api_bp.before_request
|
||
def before_request():
|
||
"""在每个请求前处理"""
|
||
# 处理预检请求
|
||
preflight_response = handle_preflight()
|
||
if preflight_response:
|
||
return preflight_response
|
||
|
||
@api_bp.after_request
|
||
def after_request(response):
|
||
"""在每个请求后添加CORS头部"""
|
||
return cors_after(response)
|
||
|
||
# ==================== 产品相关接口 ====================
|
||
|
||
@api_bp.route('/user/products', methods=['GET'])
|
||
def get_user_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()
|
||
product_type = request.args.get('type') # 产品类型筛选
|
||
is_paid = request.args.get('is_paid', type=int) # 付费筛选:1=付费,0=免费
|
||
|
||
query = Product.query.filter_by(status=1) # 只显示启用的产品
|
||
|
||
# 关键词搜索
|
||
if keyword:
|
||
query = query.filter(
|
||
db.or_(
|
||
Product.product_name.like(f'%{keyword}%'),
|
||
Product.description.like(f'%{keyword}%')
|
||
)
|
||
)
|
||
|
||
# 产品类型筛选
|
||
if product_type:
|
||
query = query.filter(Product.product_type == product_type)
|
||
|
||
query = query.order_by(Product.create_time.desc())
|
||
pagination = query.paginate(page=page, per_page=per_page, error_out=False)
|
||
|
||
products = []
|
||
for product in pagination.items:
|
||
product_dict = product.to_dict()
|
||
# 获取最新版本信息
|
||
latest_version = Version.query.filter_by(
|
||
product_id=product.product_id,
|
||
publish_status=1
|
||
).order_by(Version.create_time.desc()).first()
|
||
|
||
product_dict['latest_version'] = latest_version.version_num if latest_version else None
|
||
# 移除不存在的is_paid字段
|
||
products.append(product_dict)
|
||
|
||
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)}")
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '服务器内部错误,请稍后重试'
|
||
}), 500
|
||
|
||
|
||
@api_bp.route('/user/products/<product_id>', methods=['GET'])
|
||
def get_user_product(product_id):
|
||
"""用户端获取产品详情(无需认证)"""
|
||
try:
|
||
product = Product.query.filter_by(product_id=product_id, status=1).first()
|
||
if not product:
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '产品不存在或已下架'
|
||
}), 404
|
||
|
||
product_dict = product.to_dict(include_stats=True)
|
||
|
||
# 获取最新版本信息
|
||
latest_version = Version.query.filter_by(
|
||
product_id=product_id,
|
||
publish_status=1
|
||
).order_by(Version.create_time.desc()).first()
|
||
|
||
if latest_version:
|
||
product_dict['latest_version'] = latest_version.version_num
|
||
|
||
# 获取最近3条更新日志
|
||
recent_versions = Version.query.filter_by(
|
||
product_id=product_id,
|
||
publish_status=1
|
||
).order_by(Version.create_time.desc()).limit(3).all()
|
||
|
||
product_dict['recent_updates'] = [
|
||
{
|
||
'version_num': v.version_num,
|
||
'update_time': v.create_time.isoformat(),
|
||
'update_log': v.update_log
|
||
} for v in recent_versions
|
||
]
|
||
|
||
# 移除不存在的is_paid字段
|
||
|
||
return jsonify({
|
||
'success': True,
|
||
'data': product_dict
|
||
})
|
||
|
||
except Exception as e:
|
||
current_app.logger.error(f"获取产品详情失败: {str(e)}")
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '服务器内部错误'
|
||
}), 500
|
||
|
||
|
||
# ==================== 卡密相关接口 ====================
|
||
|
||
@api_bp.route('/user/licenses/packages', methods=['GET'])
|
||
def get_license_packages():
|
||
"""用户端获取卡密套餐(按产品筛选,无需认证)"""
|
||
try:
|
||
product_id = request.args.get('product_id')
|
||
if not product_id:
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '缺少产品ID参数'
|
||
}), 400
|
||
|
||
# 验证产品是否存在且启用
|
||
product = Product.query.filter_by(product_id=product_id, status=1).first()
|
||
if not product:
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '产品不存在或已下架'
|
||
}), 404
|
||
|
||
# 从套餐表获取该产品的套餐信息
|
||
from app.models.package import Package
|
||
|
||
# 获取启用的套餐
|
||
packages = Package.query.filter_by(
|
||
product_id=product_id,
|
||
status=1
|
||
).order_by(Package.sort_order).all()
|
||
|
||
# 转换为字典格式
|
||
package_list = [pkg.to_dict() for pkg in packages]
|
||
|
||
return jsonify({
|
||
'success': True,
|
||
'data': {
|
||
'product': product.to_dict(),
|
||
'packages': package_list
|
||
}
|
||
})
|
||
|
||
except Exception as e:
|
||
current_app.logger.error(f"获取卡密套餐失败: {str(e)}")
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '服务器内部错误'
|
||
}), 500
|
||
|
||
|
||
@api_bp.route('/user/licenses/verify', methods=['GET'])
|
||
def user_verify_license():
|
||
"""用户端验证卡密(下载用,无需认证)"""
|
||
try:
|
||
license_key = request.args.get('license_key')
|
||
product_id = request.args.get('product_id')
|
||
|
||
if not all([license_key, product_id]):
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '缺少必要参数'
|
||
}), 400
|
||
|
||
# 查找卡密
|
||
license_obj = License.query.filter_by(
|
||
license_key=license_key,
|
||
product_id=product_id
|
||
).first()
|
||
|
||
if not license_obj:
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '卡密不存在'
|
||
}), 404
|
||
|
||
# 检查卡密状态
|
||
if license_obj.status != 1:
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '卡密未激活或已失效'
|
||
}), 403
|
||
|
||
# 检查是否过期
|
||
if license_obj.is_expired():
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '卡密已过期'
|
||
}), 403
|
||
|
||
# 获取产品信息
|
||
product = Product.query.filter_by(product_id=product_id, status=1).first()
|
||
if not product:
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '产品不存在或已下架'
|
||
}), 404
|
||
|
||
# 获取下载链接(从最新版本获取)
|
||
latest_version = Version.query.filter_by(
|
||
product_id=product_id,
|
||
publish_status=1
|
||
).order_by(Version.create_time.desc()).first()
|
||
|
||
return jsonify({
|
||
'success': True,
|
||
'message': '卡密验证成功',
|
||
'data': {
|
||
'license_key': license_obj.license_key,
|
||
'product_name': product.product_name,
|
||
'expire_time': license_obj.expire_time.isoformat() if license_obj.expire_time else None,
|
||
'download_url': latest_version.download_url if latest_version else None,
|
||
'can_download': True
|
||
}
|
||
})
|
||
|
||
except Exception as e:
|
||
current_app.logger.error(f"卡密验证失败: {str(e)}")
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '服务器内部错误'
|
||
}), 500
|
||
|
||
|
||
# ==================== 工单相关接口 ====================
|
||
|
||
@api_bp.route('/user/tickets', methods=['POST'])
|
||
def create_user_ticket():
|
||
"""用户端创建工单(无需认证)"""
|
||
try:
|
||
data = request.get_json()
|
||
if not data:
|
||
return jsonify({'success': False, 'message': '请求数据为空'}), 400
|
||
|
||
product_id = data.get('product_id')
|
||
contact_person = data.get('contact_person', '').strip()
|
||
phone = data.get('phone', '').strip()
|
||
title = data.get('title', '').strip()
|
||
description = data.get('description', '').strip()
|
||
attachment = data.get('attachment') # 附件信息
|
||
|
||
# 验证必填字段
|
||
if not all([product_id, contact_person, phone, title, description]):
|
||
return jsonify({'success': False, 'message': '缺少必要参数'}), 400
|
||
|
||
# 验证手机号格式
|
||
if not phone or not re.match(r'^1[3-9]\d{9}$', phone):
|
||
return jsonify({'success': False, 'message': '手机号格式不正确'}), 400
|
||
|
||
# 验证产品存在且启用
|
||
product = Product.query.filter_by(product_id=product_id, status=1).first()
|
||
if not product:
|
||
return jsonify({'success': False, 'message': '产品不存在或已下架'}), 404
|
||
|
||
ticket = Ticket(
|
||
title=title,
|
||
product_id=product_id,
|
||
description=description,
|
||
contact_person=contact_person,
|
||
phone=phone,
|
||
attachment=attachment,
|
||
priority=data.get('priority', 1),
|
||
source=1 # 用户提交的工单
|
||
)
|
||
|
||
db.session.add(ticket)
|
||
db.session.commit()
|
||
|
||
# 记录操作日志
|
||
log_operation('CREATE_USER_TICKET', 'TICKET', ticket.ticket_id, {
|
||
'title': ticket.title,
|
||
'product_id': ticket.product_id,
|
||
'contact_person': contact_person,
|
||
'phone': phone
|
||
})
|
||
|
||
return jsonify({
|
||
'success': True,
|
||
'message': '工单提交成功',
|
||
'data': {
|
||
'ticket_id': ticket.ticket_id,
|
||
'ticket_number': ticket.ticket_number
|
||
}
|
||
})
|
||
|
||
except Exception as e:
|
||
db.session.rollback()
|
||
current_app.logger.error(f"创建工单失败: {str(e)}")
|
||
return jsonify({'success': False, 'message': '服务器内部错误'}), 500
|
||
|
||
|
||
@api_bp.route('/user/tickets', methods=['GET'])
|
||
def get_user_tickets():
|
||
"""用户端获取工单列表(通过手机号查询,无需认证)"""
|
||
try:
|
||
phone = request.args.get('phone')
|
||
|
||
if not phone:
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '缺少手机号参数'
|
||
}), 400
|
||
|
||
# 验证手机号格式
|
||
if not re.match(r'^1[3-9]\d{9}$', phone):
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '手机号格式不正确'
|
||
}), 400
|
||
|
||
# 查询该手机号提交的工单
|
||
tickets = Ticket.query.filter_by(phone=phone).order_by(Ticket.create_time.desc()).all()
|
||
|
||
return jsonify({
|
||
'success': True,
|
||
'data': [ticket.to_dict() for ticket in tickets]
|
||
})
|
||
|
||
except Exception as e:
|
||
current_app.logger.error(f"获取工单列表失败: {str(e)}")
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '服务器内部错误'
|
||
}), 500
|
||
|
||
|
||
@api_bp.route('/user/tickets/query', methods=['GET'])
|
||
def query_user_ticket():
|
||
"""用户端查询工单(通过工单编号+手机号,无需认证)"""
|
||
try:
|
||
ticket_number = request.args.get('ticket_number')
|
||
phone = request.args.get('phone')
|
||
|
||
if not all([ticket_number, phone]):
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '缺少必要参数'
|
||
}), 400
|
||
|
||
# 验证手机号格式
|
||
if not phone or not re.match(r'^1[3-9]\d{9}$', phone):
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '手机号格式不正确'
|
||
}), 400
|
||
|
||
# 查找工单
|
||
ticket = Ticket.query.filter_by(
|
||
ticket_number=ticket_number,
|
||
phone=phone
|
||
).first()
|
||
|
||
if not ticket:
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '工单不存在或手机号不匹配'
|
||
}), 404
|
||
|
||
return jsonify({
|
||
'success': True,
|
||
'data': ticket.to_dict()
|
||
})
|
||
|
||
except Exception as e:
|
||
current_app.logger.error(f"查询工单失败: {str(e)}")
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '服务器内部错误'
|
||
}), 500
|
||
|
||
|
||
# ==================== 订单相关接口 ====================
|
||
|
||
@api_bp.route('/user/orders', methods=['POST'])
|
||
def create_user_order():
|
||
"""用户端创建订单(无需认证)"""
|
||
try:
|
||
data = request.get_json()
|
||
if not data:
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '请求数据为空'
|
||
}), 400
|
||
|
||
product_id = data.get('product_id')
|
||
package_id = data.get('package_id')
|
||
contact_person = data.get('contact_person', '').strip()
|
||
phone = data.get('phone', '').strip()
|
||
quantity = data.get('quantity', 1)
|
||
|
||
# 验证必填字段
|
||
if not all([product_id, package_id, contact_person, phone]):
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '缺少必要参数'
|
||
}), 400
|
||
|
||
# 验证手机号格式
|
||
if not phone or not re.match(r'^1[3-9]\d{9}$', phone):
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '手机号格式不正确'
|
||
}), 400
|
||
|
||
# 验证数量
|
||
if not isinstance(quantity, int) or quantity < 1 or quantity > 5:
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '购买数量必须在1-5之间'
|
||
}), 400
|
||
|
||
# 验证产品存在且启用
|
||
product = Product.query.filter_by(product_id=product_id, status=1).first()
|
||
if not product:
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '产品不存在或已下架'
|
||
}), 404
|
||
|
||
# 生成订单号(简化处理,实际应有更复杂的订单号生成规则)
|
||
import time
|
||
import random
|
||
order_number = f"ORD{int(time.time())}{random.randint(100, 999)}"
|
||
|
||
# 简化处理:直接返回支付链接(实际应对接支付系统)
|
||
# 这里模拟支付链接生成
|
||
payment_url = f"/payment?order_number={order_number}&amount=0.01" # 示例金额
|
||
|
||
# 记录操作日志
|
||
log_operation('CREATE_USER_ORDER', 'ORDER', None, {
|
||
'order_number': order_number,
|
||
'product_id': product_id,
|
||
'package_id': package_id,
|
||
'contact_person': contact_person,
|
||
'phone': phone,
|
||
'quantity': quantity
|
||
})
|
||
|
||
return jsonify({
|
||
'success': True,
|
||
'message': '订单创建成功',
|
||
'data': {
|
||
'order_number': order_number,
|
||
'payment_url': payment_url,
|
||
'amount': 0.01 # 示例金额
|
||
}
|
||
})
|
||
|
||
except Exception as e:
|
||
current_app.logger.error(f"创建订单失败: {str(e)}")
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '服务器内部错误'
|
||
}), 500
|
||
|
||
|
||
@api_bp.route('/user/orders/query', methods=['GET'])
|
||
def query_user_order():
|
||
"""用户端查询订单(通过手机号+订单号,无需认证)"""
|
||
try:
|
||
order_number = request.args.get('order_number')
|
||
phone = request.args.get('phone')
|
||
|
||
if not all([order_number, phone]):
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '缺少必要参数'
|
||
}), 400
|
||
|
||
# 验证手机号格式
|
||
if not phone or not re.match(r'^1[3-9]\d{9}$', phone):
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '手机号格式不正确'
|
||
}), 400
|
||
|
||
# 这里简化处理,实际应查询订单表
|
||
# 模拟返回订单信息
|
||
order_info = {
|
||
'order_number': order_number,
|
||
'status': 1, # 1=已支付
|
||
'status_name': '已支付',
|
||
'amount': 0.01,
|
||
'created_time': '2023-01-01T00:00:00',
|
||
'payment_time': '2023-01-01T00:05:00'
|
||
}
|
||
|
||
return jsonify({
|
||
'success': True,
|
||
'data': order_info
|
||
})
|
||
|
||
except Exception as e:
|
||
current_app.logger.error(f"查询订单失败: {str(e)}")
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '服务器内部错误'
|
||
}), 500
|
||
|
||
|
||
# ==================== 下载相关接口 ====================
|
||
|
||
@api_bp.route('/user/downloads/check', methods=['GET'])
|
||
def check_download_permission():
|
||
"""用户端检查下载权限(无需认证)"""
|
||
try:
|
||
product_id = request.args.get('product_id')
|
||
|
||
if not product_id:
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '缺少产品ID参数'
|
||
}), 400
|
||
|
||
# 验证产品存在且启用
|
||
product = Product.query.filter_by(product_id=product_id, status=1).first()
|
||
if not product:
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '产品不存在或已下架'
|
||
}), 404
|
||
|
||
# 获取产品是否为付费产品
|
||
is_paid = getattr(product, 'is_paid', False)
|
||
|
||
# 获取最新版本信息
|
||
latest_version = Version.query.filter_by(
|
||
product_id=product_id,
|
||
publish_status=1
|
||
).order_by(Version.create_time.desc()).first()
|
||
|
||
return jsonify({
|
||
'success': True,
|
||
'data': {
|
||
'product_id': product_id,
|
||
'product_name': product.product_name,
|
||
'is_paid': is_paid,
|
||
'latest_version': latest_version.version_num if latest_version else None,
|
||
'download_url': latest_version.download_url if latest_version else None
|
||
}
|
||
})
|
||
|
||
except Exception as e:
|
||
current_app.logger.error(f"检查下载权限失败: {str(e)}")
|
||
return jsonify({
|
||
'success': False,
|
||
'message': '服务器内部错误'
|
||
}), 500 |