mirror of
https://github.com/wangdage12/Snap.Server.git
synced 2026-02-18 02:42:12 +08:00
添加下载资源和下载资源管理功能
This commit is contained in:
@@ -15,12 +15,14 @@ def create_app():
|
||||
from routes.gacha_log import gacha_log_bp
|
||||
from routes.web_api import web_api_bp
|
||||
from routes.misc import misc_bp
|
||||
from routes.download_resource import download_resource_bp
|
||||
|
||||
app.register_blueprint(announcement_bp, url_prefix="/Announcement")
|
||||
app.register_blueprint(auth_bp)
|
||||
app.register_blueprint(gacha_log_bp)
|
||||
app.register_blueprint(web_api_bp)
|
||||
app.register_blueprint(misc_bp)
|
||||
app.register_blueprint(download_resource_bp)
|
||||
|
||||
# CORS
|
||||
@app.after_request
|
||||
|
||||
250
routes/download_resource.py
Normal file
250
routes/download_resource.py
Normal file
@@ -0,0 +1,250 @@
|
||||
from flask import Blueprint, request, jsonify
|
||||
from app.decorators import require_maintainer_permission
|
||||
from app.extensions import logger
|
||||
from services.download_resource_service import (
|
||||
create_download_resource,
|
||||
get_download_resources,
|
||||
get_download_resource_by_id,
|
||||
update_download_resource,
|
||||
delete_download_resource,
|
||||
get_latest_version
|
||||
)
|
||||
|
||||
download_resource_bp = Blueprint("download_resource", __name__)
|
||||
|
||||
|
||||
# 公开API - 获取下载资源列表
|
||||
|
||||
@download_resource_bp.route('/download-resources', methods=['GET'])
|
||||
def get_public_download_resources():
|
||||
"""
|
||||
获取下载资源列表(公开API)
|
||||
可选查询参数:
|
||||
- package_type: 包类型 (msi/msix),不传则返回所有
|
||||
"""
|
||||
package_type = request.args.get('package_type')
|
||||
|
||||
# 验证package_type参数
|
||||
if package_type and package_type not in ['msi', 'msix']:
|
||||
return jsonify({
|
||||
"code": 1,
|
||||
"message": "Invalid package_type, must be 'msi' or 'msix'",
|
||||
"data": None
|
||||
}), 400
|
||||
|
||||
# 只返回激活的资源
|
||||
resources = get_download_resources(package_type=package_type, is_active=True)
|
||||
|
||||
return jsonify({
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": resources
|
||||
})
|
||||
|
||||
|
||||
@download_resource_bp.route('/download-resources/latest', methods=['GET'])
|
||||
def get_latest_download_resource():
|
||||
"""
|
||||
获取最新版本(公开API)
|
||||
可选查询参数:
|
||||
- package_type: 包类型 (msi/msix),不传则返回最新的任意类型
|
||||
"""
|
||||
package_type = request.args.get('package_type')
|
||||
|
||||
# 验证package_type参数
|
||||
if package_type and package_type not in ['msi', 'msix']:
|
||||
return jsonify({
|
||||
"code": 1,
|
||||
"message": "Invalid package_type, must be 'msi' or 'msix'",
|
||||
"data": None
|
||||
}), 400
|
||||
|
||||
resource = get_latest_version(package_type=package_type)
|
||||
|
||||
if resource:
|
||||
return jsonify({
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": resource
|
||||
})
|
||||
else:
|
||||
return jsonify({
|
||||
"code": 1,
|
||||
"message": "No resource found",
|
||||
"data": None
|
||||
}), 404
|
||||
|
||||
|
||||
# Web管理端API - 增删改查
|
||||
|
||||
@download_resource_bp.route('/web-api/download-resources', methods=['POST'])
|
||||
@require_maintainer_permission
|
||||
def web_api_create_download_resource():
|
||||
"""创建下载资源"""
|
||||
data = request.get_json()
|
||||
|
||||
# 验证必需字段
|
||||
required_fields = ['version', 'package_type', 'download_url']
|
||||
if not all(k in data for k in required_fields):
|
||||
return jsonify({
|
||||
"code": 1,
|
||||
"message": f"Missing required fields: {', '.join(required_fields)}",
|
||||
"data": None
|
||||
}), 400
|
||||
|
||||
# 验证package_type
|
||||
if data['package_type'] not in ['msi', 'msix']:
|
||||
return jsonify({
|
||||
"code": 1,
|
||||
"message": "Invalid package_type, must be 'msi' or 'msix'",
|
||||
"data": None
|
||||
}), 400
|
||||
|
||||
# 添加创建者信息
|
||||
data['created_by'] = str(request.current_user['_id'])
|
||||
|
||||
# 创建资源
|
||||
resource_id = create_download_resource(data)
|
||||
|
||||
if resource_id:
|
||||
logger.info(f"Download resource created with ID: {resource_id} by user: {request.current_user['email']}")
|
||||
return jsonify({
|
||||
"code": 0,
|
||||
"message": "Download resource created successfully",
|
||||
"data": {
|
||||
"id": str(resource_id)
|
||||
}
|
||||
})
|
||||
else:
|
||||
logger.error("Failed to create download resource")
|
||||
return jsonify({
|
||||
"code": 2,
|
||||
"message": "Failed to create download resource",
|
||||
"data": None
|
||||
}), 500
|
||||
|
||||
|
||||
@download_resource_bp.route('/web-api/download-resources', methods=['GET'])
|
||||
@require_maintainer_permission
|
||||
def web_api_get_download_resources():
|
||||
"""获取下载资源列表(管理端,包含所有资源,包括未激活的)"""
|
||||
package_type = request.args.get('package_type')
|
||||
is_active_str = request.args.get('is_active')
|
||||
|
||||
# 验证package_type参数
|
||||
if package_type and package_type not in ['msi', 'msix']:
|
||||
return jsonify({
|
||||
"code": 1,
|
||||
"message": "Invalid package_type, must be 'msi' or 'msix'",
|
||||
"data": None
|
||||
}), 400
|
||||
|
||||
# 处理is_active参数
|
||||
is_active = None
|
||||
if is_active_str is not None:
|
||||
is_active = is_active_str.lower() == 'true'
|
||||
|
||||
resources = get_download_resources(package_type=package_type, is_active=is_active)
|
||||
|
||||
return jsonify({
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": resources
|
||||
})
|
||||
|
||||
|
||||
@download_resource_bp.route('/web-api/download-resources/<resource_id>', methods=['GET'])
|
||||
@require_maintainer_permission
|
||||
def web_api_get_download_resource(resource_id):
|
||||
"""获取单个下载资源详情"""
|
||||
resource = get_download_resource_by_id(resource_id)
|
||||
|
||||
if resource:
|
||||
return jsonify({
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": resource
|
||||
})
|
||||
else:
|
||||
return jsonify({
|
||||
"code": 1,
|
||||
"message": "Resource not found",
|
||||
"data": None
|
||||
}), 404
|
||||
|
||||
|
||||
@download_resource_bp.route('/web-api/download-resources/<resource_id>', methods=['PUT'])
|
||||
@require_maintainer_permission
|
||||
def web_api_update_download_resource(resource_id):
|
||||
"""更新下载资源"""
|
||||
data = request.get_json()
|
||||
|
||||
# 检查资源是否存在
|
||||
existing_resource = get_download_resource_by_id(resource_id)
|
||||
if not existing_resource:
|
||||
return jsonify({
|
||||
"code": 1,
|
||||
"message": "Resource not found",
|
||||
"data": None
|
||||
}), 404
|
||||
|
||||
# 验证package_type(如果提供)
|
||||
if 'package_type' in data and data['package_type'] not in ['msi', 'msix']:
|
||||
return jsonify({
|
||||
"code": 1,
|
||||
"message": "Invalid package_type, must be 'msi' or 'msix'",
|
||||
"data": None
|
||||
}), 400
|
||||
|
||||
# 添加更新者信息
|
||||
data['updated_by'] = str(request.current_user['_id'])
|
||||
|
||||
# 更新资源
|
||||
success = update_download_resource(resource_id, data)
|
||||
|
||||
if success:
|
||||
logger.info(f"Download resource {resource_id} updated by user: {request.current_user['email']}")
|
||||
return jsonify({
|
||||
"code": 0,
|
||||
"message": "Download resource updated successfully",
|
||||
"data": None
|
||||
})
|
||||
else:
|
||||
logger.error(f"Failed to update download resource {resource_id}")
|
||||
return jsonify({
|
||||
"code": 2,
|
||||
"message": "Failed to update download resource",
|
||||
"data": None
|
||||
}), 500
|
||||
|
||||
|
||||
@download_resource_bp.route('/web-api/download-resources/<resource_id>', methods=['DELETE'])
|
||||
@require_maintainer_permission
|
||||
def web_api_delete_download_resource(resource_id):
|
||||
"""删除下载资源"""
|
||||
# 检查资源是否存在
|
||||
existing_resource = get_download_resource_by_id(resource_id)
|
||||
if not existing_resource:
|
||||
return jsonify({
|
||||
"code": 1,
|
||||
"message": "Resource not found",
|
||||
"data": None
|
||||
}), 404
|
||||
|
||||
# 删除资源
|
||||
success = delete_download_resource(resource_id)
|
||||
|
||||
if success:
|
||||
logger.info(f"Download resource {resource_id} deleted by user: {request.current_user['email']}")
|
||||
return jsonify({
|
||||
"code": 0,
|
||||
"message": "Download resource deleted successfully",
|
||||
"data": None
|
||||
})
|
||||
else:
|
||||
logger.error(f"Failed to delete download resource {resource_id}")
|
||||
return jsonify({
|
||||
"code": 2,
|
||||
"message": "Failed to delete download resource",
|
||||
"data": None
|
||||
}), 500
|
||||
209
services/download_resource_service.py
Normal file
209
services/download_resource_service.py
Normal file
@@ -0,0 +1,209 @@
|
||||
import datetime
|
||||
from app.extensions import client, logger
|
||||
from app.config import Config
|
||||
|
||||
|
||||
def create_download_resource(data):
|
||||
"""
|
||||
创建下载资源
|
||||
|
||||
:param data: 包含以下字段的数据
|
||||
- version: 版本号
|
||||
- package_type: 包类型 (msi/msix)
|
||||
- download_url: 下载链接
|
||||
- features: 新功能描述
|
||||
- file_size: 文件大小 (可选)
|
||||
- file_hash: 文件哈希 (可选)
|
||||
- is_active: 是否激活 (可选,默认为True)
|
||||
:return: 创建的资源ID或None
|
||||
"""
|
||||
try:
|
||||
resource_doc = {
|
||||
"version": data['version'],
|
||||
"package_type": data['package_type'],
|
||||
"download_url": data['download_url'],
|
||||
"features": data.get('features', ''),
|
||||
"file_size": data.get('file_size'),
|
||||
"file_hash": data.get('file_hash'),
|
||||
"is_active": data.get('is_active', True),
|
||||
"created_at": datetime.datetime.utcnow(),
|
||||
"created_by": data.get('created_by')
|
||||
}
|
||||
|
||||
result = client.ht_server.download_resources.insert_one(resource_doc)
|
||||
logger.info(f"Download resource created with ID: {result.inserted_id}")
|
||||
return result.inserted_id
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to create download resource: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def get_download_resources(package_type=None, is_active=None):
|
||||
"""
|
||||
获取下载资源列表
|
||||
|
||||
:param package_type: 包类型过滤 (msi/msix),None表示获取所有
|
||||
:param is_active: 是否激活过滤,None表示获取所有
|
||||
:return: 资源列表
|
||||
"""
|
||||
try:
|
||||
query = {}
|
||||
if package_type:
|
||||
query['package_type'] = package_type
|
||||
if is_active is not None:
|
||||
query['is_active'] = is_active
|
||||
|
||||
resources = list(client.ht_server.download_resources.find(query, sort=[("created_at", -1)]))
|
||||
|
||||
# 移除 _id 字段并转换日期
|
||||
result = []
|
||||
for r in resources:
|
||||
r = dict(r)
|
||||
# 转换_id为字符串并存为id字段
|
||||
r['id'] = str(r.pop('_id'))
|
||||
# 转换datetime为配置时区的ISO格式字符串
|
||||
if 'created_at' in r and isinstance(r['created_at'], datetime.datetime):
|
||||
dt = r['created_at'].replace(tzinfo=datetime.timezone.utc)
|
||||
dt = dt.astimezone(Config.TIMEZONE)
|
||||
r['created_at'] = dt.isoformat()
|
||||
if 'updated_at' in r and isinstance(r['updated_at'], datetime.datetime):
|
||||
dt = r['updated_at'].replace(tzinfo=datetime.timezone.utc)
|
||||
dt = dt.astimezone(Config.TIMEZONE)
|
||||
r['updated_at'] = dt.isoformat()
|
||||
result.append(r)
|
||||
|
||||
return result
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get download resources: {e}")
|
||||
return []
|
||||
|
||||
|
||||
def get_download_resource_by_id(resource_id):
|
||||
"""
|
||||
根据ID获取下载资源
|
||||
|
||||
:param resource_id: 资源ID
|
||||
:return: 资源对象或None
|
||||
"""
|
||||
try:
|
||||
from bson import ObjectId
|
||||
resource = client.ht_server.download_resources.find_one({"_id": ObjectId(resource_id)})
|
||||
|
||||
if resource:
|
||||
resource = dict(resource)
|
||||
resource.pop('_id', None)
|
||||
# 转换datetime为配置时区的ISO格式字符串
|
||||
if 'created_at' in resource and isinstance(resource['created_at'], datetime.datetime):
|
||||
dt = resource['created_at'].replace(tzinfo=datetime.timezone.utc)
|
||||
dt = dt.astimezone(Config.TIMEZONE)
|
||||
resource['created_at'] = dt.isoformat()
|
||||
if 'updated_at' in resource and isinstance(resource['updated_at'], datetime.datetime):
|
||||
dt = resource['updated_at'].replace(tzinfo=datetime.timezone.utc)
|
||||
dt = dt.astimezone(Config.TIMEZONE)
|
||||
resource['updated_at'] = dt.isoformat()
|
||||
return resource
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get download resource by ID: {e}")
|
||||
return None
|
||||
|
||||
|
||||
def update_download_resource(resource_id, data):
|
||||
"""
|
||||
更新下载资源
|
||||
|
||||
:param resource_id: 资源ID
|
||||
:param data: 要更新的字段
|
||||
:return: 是否成功
|
||||
"""
|
||||
try:
|
||||
from bson import ObjectId
|
||||
|
||||
# 构建更新数据
|
||||
update_data = {"updated_at": datetime.datetime.utcnow()}
|
||||
|
||||
if 'version' in data:
|
||||
update_data['version'] = data['version']
|
||||
if 'package_type' in data:
|
||||
update_data['package_type'] = data['package_type']
|
||||
if 'download_url' in data:
|
||||
update_data['download_url'] = data['download_url']
|
||||
if 'features' in data:
|
||||
update_data['features'] = data['features']
|
||||
if 'file_size' in data:
|
||||
update_data['file_size'] = data['file_size']
|
||||
if 'file_hash' in data:
|
||||
update_data['file_hash'] = data['file_hash']
|
||||
if 'is_active' in data:
|
||||
update_data['is_active'] = data['is_active']
|
||||
if 'updated_by' in data:
|
||||
update_data['updated_by'] = data['updated_by']
|
||||
|
||||
result = client.ht_server.download_resources.update_one(
|
||||
{"_id": ObjectId(resource_id)},
|
||||
{"$set": update_data}
|
||||
)
|
||||
|
||||
if result.modified_count > 0:
|
||||
logger.info(f"Download resource {resource_id} updated successfully")
|
||||
return True
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to update download resource: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def delete_download_resource(resource_id):
|
||||
"""
|
||||
删除下载资源
|
||||
|
||||
:param resource_id: 资源ID
|
||||
:return: 是否成功
|
||||
"""
|
||||
try:
|
||||
from bson import ObjectId
|
||||
result = client.ht_server.download_resources.delete_one({"_id": ObjectId(resource_id)})
|
||||
|
||||
if result.deleted_count > 0:
|
||||
logger.info(f"Download resource {resource_id} deleted successfully")
|
||||
return True
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to delete download resource: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def get_latest_version(package_type=None):
|
||||
"""
|
||||
获取最新版本
|
||||
|
||||
:param package_type: 包类型 (msi/msix),None表示获取所有类型的最新版本
|
||||
:return: 资源对象或None
|
||||
"""
|
||||
try:
|
||||
query = {"is_active": True}
|
||||
if package_type:
|
||||
query['package_type'] = package_type
|
||||
|
||||
resource = client.ht_server.download_resources.find_one(
|
||||
query,
|
||||
sort=[("created_at", -1)]
|
||||
)
|
||||
|
||||
if resource:
|
||||
resource = dict(resource)
|
||||
resource.pop('_id', None)
|
||||
# 转换datetime为配置时区的ISO格式字符串
|
||||
if 'created_at' in resource and isinstance(resource['created_at'], datetime.datetime):
|
||||
dt = resource['created_at'].replace(tzinfo=datetime.timezone.utc)
|
||||
dt = dt.astimezone(Config.TIMEZONE)
|
||||
resource['created_at'] = dt.isoformat()
|
||||
if 'updated_at' in resource and isinstance(resource['updated_at'], datetime.datetime):
|
||||
dt = resource['updated_at'].replace(tzinfo=datetime.timezone.utc)
|
||||
dt = dt.astimezone(Config.TIMEZONE)
|
||||
resource['updated_at'] = dt.isoformat()
|
||||
return resource
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get latest version: {e}")
|
||||
return None
|
||||
Reference in New Issue
Block a user