from flask import Blueprint, request, jsonify, send_from_directory, send_file from flask_jwt_extended import jwt_required, get_jwt, get_jwt_identity from flask import request, Blueprint, current_app, jsonify from ..models import User, File, Download, SystemSettings, Category, OperationLog, CategoryPermission, TeamMember, db import os from datetime import datetime, timedelta import hashlib admin_bp = Blueprint('admin', __name__, url_prefix='/api/admin') from functools import wraps from flask_jwt_extended import jwt_required, get_jwt def admin_required(fn): @wraps(fn) @jwt_required() def wrapper(*args, **kwargs): claims = get_jwt() if not claims.get('is_admin'): return jsonify({'message': '需要管理员权限'}), 403 return fn(*args, **kwargs) return wrapper def team_leader_required(fn): """团队长权限检查装饰器""" @wraps(fn) @jwt_required() def wrapper(*args, **kwargs): current_user_id = get_jwt_identity() user = User.query.get(current_user_id) if not user: return jsonify({'message': '用户不存在'}), 404 # 检查是否是团队长 if not user.is_team_leader: return jsonify({'message': '需要团队长权限'}), 403 return fn(*args, **kwargs) return wrapper # 分类管理 @admin_bp.route('/categories', methods=['GET']) @admin_required def get_categories(): page = request.args.get('page', 1, type=int) per_page = request.args.get('per_page', 10, type=int) search_query = request.args.get('search', type=str) hierarchical = request.args.get('hierarchical', 'true').lower() == 'true' query = Category.query if search_query: query = query.filter(Category.name.like(f'%{search_query}%')) # 如果需要分页,并且不是层次化显示 if not hierarchical: pagination = query.paginate(page=page, per_page=per_page, error_out=False) categories = pagination.items return jsonify({ 'categories': [category.to_dict() for category in categories], 'total_categories': pagination.total, 'pages': pagination.pages, 'current_page': pagination.page, 'has_next': pagination.has_next, 'has_prev': pagination.has_prev }), 200 else: # 获取所有分类 categories = query.all() # 构建分类树结构 category_dict = {} root_categories = [] # 首先将所有分类添加到字典中 for category in categories: category_dict[category.id] = { 'id': category.id, 'name': category.name, 'description': category.description, 'parent_id': category.parent_id, 'level': category.level, 'path': category.path, 'children': [] } # 然后构建树结构 for category_id, category_data in category_dict.items(): if category_data['parent_id'] is None: root_categories.append(category_data) else: parent_id = category_data['parent_id'] if parent_id in category_dict: parent = category_dict[parent_id] parent['children'].append(category_data) return jsonify({ 'categories': root_categories }), 200 @admin_bp.route('/categories', methods=['POST']) @admin_required def create_category(): data = request.get_json() name = data.get('name') description = data.get('description') parent_id = data.get('parent_id') if not name: return jsonify({'message': '分类名称不能为空'}), 400 # 检查同一父级下是否有同名分类 if parent_id: existing_category = Category.query.filter_by(name=name, parent_id=parent_id).first() if existing_category: return jsonify({'message': '该父级下已存在同名分类'}), 400 else: # 检查顶级分类是否有同名 existing_category = Category.query.filter_by(name=name, parent_id=None).first() if existing_category: return jsonify({'message': '顶级分类中已存在同名分类'}), 400 parent_category = None if parent_id: parent_category = Category.query.get(parent_id) if not parent_category: return jsonify({'message': '父分类不存在'}), 404 # 计算层级和路径 level = 1 path = f'/{name}' if parent_category: level = parent_category.level + 1 path = f'{parent_category.path}/{name}' category = Category( name=name, description=description, parent_id=parent_id, level=level, path=path ) db.session.add(category) db.session.commit() # 记录操作日志 admin_user_id = get_jwt_identity() log = OperationLog( user_id=admin_user_id, operation_type='create_category', details=f'创建分类: {category.name} (ID: {category.id})' ) db.session.add(log) db.session.commit() return jsonify(category.to_dict()), 201 @admin_bp.route('/categories/', methods=['PUT']) @admin_required def update_category(category_id): try: category = Category.query.get_or_404(category_id) data = request.get_json() name = data.get('name') description = data.get('description') parent_id = data.get('parent_id') if name and name != category.name: # 检查同一父级下是否有同名分类 if category.parent_id: existing_category = Category.query.filter_by(name=name, parent_id=category.parent_id).first() if existing_category: return jsonify({'message': '该父级下已存在同名分类'}), 400 else: # 检查顶级分类是否有同名 existing_category = Category.query.filter_by(name=name, parent_id=None).first() if existing_category: return jsonify({'message': '顶级分类中已存在同名分类'}), 400 category.name = name # If name changes, path needs to be re-calculated later after parent_id is handled if description is not None: category.description = description # 处理父分类变更 if parent_id is not None and parent_id != category.parent_id: if parent_id == category.id: return jsonify({'message': '不能设置自身为父分类'}), 400 new_parent_category = None if parent_id != 0: # 0 表示设置为顶级分类 new_parent_category = Category.query.get(parent_id) if not new_parent_category: return jsonify({'message': '新的父分类不存在'}), 404 # 检查是否形成循环引用 current = new_parent_category while current: if current.id == category.id: return jsonify({'message': '不能设置子分类为父分类'}), 400 current = current.parent category.parent_id = parent_id if parent_id != 0 else None # 更新层级 if new_parent_category: category.level = new_parent_category.level + 1 else: category.level = 1 # 重新计算path,无论name或parent_id是否改变 if category.parent_id: parent_category = Category.query.get(category.parent_id) if parent_category: category.path = f'{parent_category.path}/{category.name}' else: # This case should not happen if parent_id is set but parent_category is not found # For safety, set to root path category.path = f'/{category.name}' else: category.path = f'/{category.name}' db.session.commit() # 记录操作日志 admin_user_id = get_jwt_identity() log = OperationLog( user_id=admin_user_id, operation_type='update_category', details=f'更新分类 {category.name} (ID: {category.id}) 的信息: {data}' ) db.session.add(log) db.session.commit() return jsonify(category.to_dict()), 200 except Exception as e: db.session.rollback() import sys print(f"Error updating category {category_id}: {e}", file=sys.stderr) return jsonify({'message': f'更新分类失败: {str(e)}'}), 500 @admin_bp.route('/categories/', methods=['DELETE']) @admin_required def delete_category(category_id): try: category = Category.query.get_or_404(category_id) admin_user_id = get_jwt_identity() # 获取默认分类(未分类) default_category = Category.get_default_category() # 不允许删除默认分类 if category.id == default_category.id: return jsonify({'message': '不能删除默认分类'}), 400 # 处理子分类 child_categories = Category.query.filter_by(parent_id=category_id).all() for child in child_categories: # 递归删除子分类 # 先获取子分类下的所有文件 child_files = File.query.filter_by(category_id=child.id).all() for file in child_files: file.category_id = default_category.id # 记录文件迁移日志 log = OperationLog( user_id=admin_user_id, operation_type='move_file', file_id=file.id, category_id=default_category.id, details=f'文件从分类 {child.name} 迁移至 {default_category.name}' ) db.session.add(log) # 删除子分类的权限 CategoryPermission.query.filter_by(category_id=child.id).delete() # 删除子分类相关的操作日志 OperationLog.query.filter_by(category_id=child.id).delete() # 记录子分类删除日志 log = OperationLog( user_id=admin_user_id, operation_type='delete_category', details=f'删除子分类: {child.name} (ID: {child.id})' ) db.session.add(log) # 删除子分类 db.session.delete(child) # 处理当前分类下的文件 files = File.query.filter_by(category_id=category_id).all() for file in files: file.category_id = default_category.id # 记录文件迁移日志 log = OperationLog( user_id=admin_user_id, operation_type='move_file', file_id=file.id, category_id=default_category.id, details=f'文件从分类 {category.name} 迁移至 {default_category.name}' ) db.session.add(log) # 删除分类的权限 CategoryPermission.query.filter_by(category_id=category_id).delete() # 删除相关的操作日志 OperationLog.query.filter_by(category_id=category_id).delete() # 删除分类 db.session.delete(category) # 记录操作日志 log = OperationLog( user_id=admin_user_id, operation_type='delete_category', details=f'删除分类: {category.name} (ID: {category.id})' ) db.session.add(log) db.session.commit() return jsonify({'message': '分类删除成功,相关文件已迁移至未分类'}), 200 except Exception as e: db.session.rollback() import sys print(f"Error deleting category {category_id}: {e}", file=sys.stderr) return jsonify({'message': f'删除分类失败: {str(e)}'}), 500 # 分类权限管理API @admin_bp.route('/category-permissions', methods=['GET']) @admin_required def get_category_permissions(): user_id = request.args.get('user_id', type=int) category_id = request.args.get('category_id', type=int) query = CategoryPermission.query if user_id: query = query.filter_by(user_id=user_id) if category_id: query = query.filter_by(category_id=category_id) permissions = query.all() return jsonify({ 'permissions': [permission.to_dict() for permission in permissions] }), 200 @admin_bp.route('/category-permissions', methods=['POST']) @admin_required def create_category_permission(): data = request.get_json() user_id = data.get('user_id') category_id = data.get('category_id') inherit_to_children = data.get('inherit_to_children', True) if not user_id or not category_id: return jsonify({'message': '用户ID和分类ID不能为空'}), 400 # 检查用户和分类是否存在 user = User.query.get(user_id) category = Category.query.get(category_id) if not user: return jsonify({'message': '用户不存在'}), 404 if not category: return jsonify({'message': '分类不存在'}), 404 # 检查权限是否已存在 existing_permission = CategoryPermission.query.filter_by( user_id=user_id, category_id=category_id ).first() if existing_permission: return jsonify({'message': '该用户已有此分类的权限'}), 400 # 创建新权限 permission = CategoryPermission( user_id=user_id, category_id=category_id, inherit_to_children=inherit_to_children ) db.session.add(permission) # 记录操作日志 admin_user_id = get_jwt_identity() log = OperationLog( user_id=admin_user_id, operation_type='create_permission', category_id=category_id, details=f'为用户 {user.username} 添加分类 {category.name} 的权限' ) db.session.add(log) db.session.commit() return jsonify(permission.to_dict()), 201 @admin_bp.route('/category-permissions/', methods=['PUT']) @admin_required def update_category_permission(permission_id): permission = CategoryPermission.query.get_or_404(permission_id) data = request.get_json() inherit_to_children = data.get('inherit_to_children') if inherit_to_children is not None: permission.inherit_to_children = inherit_to_children db.session.add(permission) # 记录操作日志 admin_user_id = get_jwt_identity() log = OperationLog( user_id=admin_user_id, operation_type='update_permission', category_id=permission.category_id, details=f'更新用户 {permission.user.username} 对分类 {permission.category.name} 的权限' ) db.session.add(log) db.session.commit() return jsonify(permission.to_dict()), 200 @admin_bp.route('/category-permissions/', methods=['DELETE']) @admin_required def delete_category_permission(permission_id): permission = CategoryPermission.query.get_or_404(permission_id) # 记录操作日志 admin_user_id = get_jwt_identity() log = OperationLog( user_id=admin_user_id, operation_type='delete_permission', category_id=permission.category_id, details=f'删除用户 {permission.user.username} 对分类 {permission.category.name} 的权限' ) db.session.delete(permission) db.session.add(log) db.session.commit() return jsonify({'message': '权限删除成功'}), 200 @admin_bp.route('/users//category-permissions/batch', methods=['POST']) @admin_required def batch_assign_category_permissions(user_id): user = User.query.get_or_404(user_id) data = request.get_json() category_ids = data.get('category_ids', []) inherit_to_children = data.get('inherit_to_children', True) if not category_ids: return jsonify({'message': '分类ID列表不能为空'}), 400 admin_user_id = get_jwt_identity() created_permissions = [] for category_id in category_ids: category = Category.query.get(category_id) if not category: continue # 检查权限是否已存在 existing_permission = CategoryPermission.query.filter_by( user_id=user_id, category_id=category_id ).first() if not existing_permission: # 创建新权限 permission = CategoryPermission( user_id=user_id, category_id=category_id, inherit_to_children=inherit_to_children ) db.session.add(permission) created_permissions.append(permission) # 记录操作日志 log = OperationLog( user_id=admin_user_id, operation_type='create_permission', category_id=category_id, details=f'为用户 {user.username} 添加分类 {category.name} 的权限' ) db.session.add(log) db.session.commit() return jsonify({ 'message': f'成功为用户 {user.username} 批量分配 {len(created_permissions)} 个分类权限', 'permissions': [permission.to_dict() for permission in created_permissions] }), 201 # 分类模板系统API @admin_bp.route('/category-templates', methods=['GET']) @admin_required def get_category_templates(): # 预置模板列表 templates = [ { 'id': 'media', 'name': '媒体资源', 'description': '包含视频、音频、图片等媒体资源的分类结构', 'structure': [ { 'name': '媒体资源', 'children': [ {'name': '视频'}, {'name': '音频'}, {'name': '图片'} ] } ] }, { 'id': 'documents', 'name': '文档资料', 'description': '包含PDF、Word、Excel等文档资料的分类结构', 'structure': [ { 'name': '文档资料', 'children': [ {'name': 'PDF文档'}, {'name': 'Word文档'}, {'name': 'Excel表格'}, {'name': '演示文稿'} ] } ] } ] return jsonify({'templates': templates}), 200 @admin_bp.route('/category-templates//apply', methods=['POST']) @admin_required def apply_category_template(template_id): admin_user_id = get_jwt_identity() # 预置模板定义 templates = { 'media': { 'name': '媒体资源', 'structure': [ { 'name': '媒体资源', 'children': [ {'name': '视频'}, {'name': '音频'}, {'name': '图片'} ] } ] }, 'documents': { 'name': '文档资料', 'structure': [ { 'name': '文档资料', 'children': [ {'name': 'PDF文档'}, {'name': 'Word文档'}, {'name': 'Excel表格'}, {'name': '演示文稿'} ] } ] } } if template_id not in templates: return jsonify({'message': '模板不存在'}), 404 template = templates[template_id] created_categories = [] # 递归创建分类结构 def create_category_structure(structure, parent_id=None): for item in structure: # 检查分类是否已存在 existing = Category.query.filter_by(name=item['name'], parent_id=parent_id).first() if existing: parent_category = existing else: # 创建父分类 parent_category = Category(name=item['name'], parent_id=parent_id) db.session.add(parent_category) db.session.flush() # 获取ID created_categories.append(parent_category) # 记录操作日志 log = OperationLog( user_id=admin_user_id, operation_type='create_category', category_id=parent_category.id, details=f'从模板 {template["name"]} 创建分类: {parent_category.name}' ) db.session.add(log) # 如果有子分类,递归创建 if 'children' in item: create_category_structure(item['children'], parent_category.id) try: create_category_structure(template['structure']) db.session.commit() return jsonify({ 'message': f'成功应用模板 {template["name"]},创建了 {len(created_categories)} 个分类', 'categories': [category.to_dict() for category in created_categories] }), 201 except Exception as e: db.session.rollback() return jsonify({'message': f'应用模板失败: {str(e)}'}), 500 # 用户管理 @admin_bp.route('/users', methods=['GET']) @admin_required def get_users(): page = request.args.get('page', 1, type=int) per_page = request.args.get('per_page', 10, type=int) # 限制每页数量只能是10, 20, 50 if per_page not in [10, 20, 50]: per_page = 10 query = User.query # 搜索功能 search_query = request.args.get('search', type=str) if search_query: query = query.filter( (User.username.like(f'%{search_query}%')) | (User.email.like(f'%{search_query}%')) ) # 排序功能 sort_by = request.args.get('sort_by', 'created_at') sort_order = request.args.get('sort_order', 'desc') if sort_by == 'created_at': if sort_order == 'desc': query = query.order_by(User.created_at.desc()) else: query = query.order_by(User.created_at.asc()) elif sort_by == 'username': if sort_order == 'desc': query = query.order_by(User.username.desc()) else: query = query.order_by(User.username.asc()) # 可以添加更多排序选项 pagination = query.paginate(page=page, per_page=per_page, error_out=False) users = pagination.items has_more = pagination.has_next return jsonify({ 'users': [user.to_dict() for user in users], 'has_more': has_more, 'total_users': pagination.total, 'pages': pagination.pages, 'current_page': pagination.page }), 200 @admin_bp.route('/users', methods=['POST']) @admin_required def create_user(): data = request.get_json() if not all(k in data for k in ('username', 'email', 'password')): return jsonify({'message': '缺少必要字段'}), 400 if User.query.filter_by(username=data['username']).first(): return jsonify({'message': '用户名已存在'}), 400 if User.query.filter_by(email=data['email']).first(): return jsonify({'message': '邮箱已被注册'}), 400 user = User( username=data['username'], email=data['email'], is_admin=data.get('is_admin', False), is_active=data.get('is_active', True), daily_quota=data.get('daily_quota', 5) ) user.set_password(data['password']) db.session.add(user) db.session.commit() # 记录操作日志 user_id = get_jwt_identity() log = OperationLog( user_id=user_id, operation_type='update_settings', details=f'更新系统设置: {data}' ) db.session.add(log) db.session.commit() # 记录操作日志 admin_user_id = get_jwt_identity() log = OperationLog( user_id=admin_user_id, operation_type='create_user', details=f'创建用户: {user.username} (ID: {user.id})' ) db.session.add(log) db.session.commit() return jsonify(user.to_dict()), 201 # 操作日志管理 @admin_bp.route('/operation_logs', methods=['GET']) @admin_required def get_operation_logs(): page = request.args.get('page', 1, type=int) per_page = 20 # 每页日志数量 query = OperationLog.query.order_by(OperationLog.timestamp.desc()) # 搜索功能 search_query = request.args.get('search', type=str) if search_query: query = query.filter( (OperationLog.operation_type.like(f'%{search_query}%')) | (OperationLog.details.like(f'%{search_query}%')) ) # 过滤功能 user_id = request.args.get('user_id', type=int) if user_id: query = query.filter_by(user_id=user_id) # 时间范围过滤 start_date_str = request.args.get('start_date', type=str) end_date_str = request.args.get('end_date', type=str) if start_date_str: start_date = datetime.strptime(start_date_str, '%Y-%m-%d') query = query.filter(OperationLog.timestamp >= start_date) if end_date_str: end_date = datetime.strptime(end_date_str, '%Y-%m-%d') + timedelta(days=1) - timedelta(microseconds=1) query = query.filter(OperationLog.timestamp <= end_date) pagination = query.paginate(page=page, per_page=per_page, error_out=False) logs = pagination.items has_more = pagination.has_next return jsonify({ 'logs': [log.to_dict() for log in logs], 'has_more': has_more, 'total_logs': pagination.total, 'pages': pagination.pages, 'current_page': pagination.page }), 200 @admin_bp.route('/users/', methods=['PUT']) @admin_required def update_user(user_id): user = User.query.get_or_404(user_id) data = request.get_json() if 'username' in data and data['username'] != user.username: if User.query.filter_by(username=data['username']).first(): return jsonify({'message': '用户名已存在'}), 400 user.username = data['username'] if 'email' in data and data['email'] != user.email: if User.query.filter_by(email=data['email']).first(): return jsonify({'message': '邮箱已被注册'}), 400 user.email = data['email'] if 'is_admin' in data: user.is_admin = data['is_admin'] if 'is_active' in data: user.is_active = data['is_active'] if 'daily_quota' in data: user.daily_quota = data['daily_quota'] if 'password' in data: user.set_password(data['password']) db.session.commit() # 记录操作日志 admin_user_id = get_jwt_identity() log = OperationLog( user_id=admin_user_id, operation_type='update_user', details=f'更新用户 {user.username} (ID: {user.id}) 的信息: {data}' ) db.session.add(log) db.session.commit() return jsonify(user.to_dict()), 200 @admin_bp.route('/users/', methods=['DELETE']) @admin_required def delete_user(user_id): user = User.query.get_or_404(user_id) # 防止删除自己 current_user_id = get_jwt_identity() if user.id == current_user_id: return jsonify({'message': '不能删除自己的账号'}), 400 # 删除用户上传的文件 for file_to_delete in user.uploaded_files: # 删除实际文件 if os.path.exists(file_to_delete.file_path): try: os.remove(file_to_delete.file_path) # 记录操作日志 log = OperationLog( user_id=current_user_id, operation_type='delete', file_id=file_to_delete.id, details=f'删除用户 {user.username} 的文件: {file_to_delete.original_filename}' ) db.session.add(log) except Exception as e: current_app.logger.error(f"删除文件失败: {file_to_delete.file_path}, 错误: {str(e)}") # 删除相关的下载记录 if file_to_delete.download: db.session.delete(file_to_delete.download) # 删除文件记录 db.session.delete(file_to_delete) # 删除用户的下载记录 Download.query.filter_by(user_id=user.id).delete() # 删除用户领取的文件关联 File.query.filter_by(taken_by=user.id).update({'taken_by': None, 'taken_at': None}) # 记录操作日志(在删除用户之前) admin_user_id = get_jwt_identity() log = OperationLog( user_id=admin_user_id, operation_type='delete_user', details=f'删除用户: {user.username} (ID: {user.id})' ) db.session.add(log) # 删除用户 db.session.delete(user) db.session.commit() return jsonify({'message': '用户已删除'}), 200 # 文件管理 @admin_bp.route('/files', methods=['GET']) @admin_required def get_all_files(): page = request.args.get('page', 1, type=int) per_page = request.args.get('per_page', 20, type=int) # 限制每页数量只能是10, 20, 50 if per_page not in [10, 20, 50]: per_page = 20 query = File.query # 搜索功能 search_query = request.args.get('search', type=str) if search_query: query = query.filter( (File.original_filename.like(f'%{search_query}%')) | (File.description.like(f'%{search_query}%')) ) # 过滤功能 category_id = request.args.get('category_id', type=int) if category_id: query = query.filter_by(category_id=category_id) # 排序功能 sort_by = request.args.get('sort_by', 'created_at') sort_order = request.args.get('sort_order', 'desc') if sort_by == 'created_at': if sort_order == 'desc': query = query.order_by(File.created_at.desc()) else: query = query.order_by(File.created_at.asc()) elif sort_by == 'file_size': if sort_order == 'desc': query = query.order_by(File.file_size.desc()) else: query = query.order_by(File.file_size.asc()) # 可以添加更多排序选项 pagination = query.paginate(page=page, per_page=per_page, error_out=False) files = pagination.items has_more = pagination.has_next return jsonify({ 'files': [file.to_dict(include_path=True) for file in files], 'has_more': has_more, 'total_files': pagination.total, 'pages': pagination.pages, 'current_page': pagination.page }), 200 @admin_bp.route('/files', methods=['POST']) @admin_required def upload_file(): if 'file' not in request.files: return jsonify({'message': '未选择文件'}), 400 file = request.files['file'] if file.filename == '': return jsonify({'message': '未选择文件'}), 400 # 获取系统设置 max_file_size = SystemSettings.get_value('max_file_size', 10485760) # 默认10MB max_files_per_user = SystemSettings.get_value('max_files_per_user', 100) # 检查文件大小 file.seek(0, 2) # 移动到文件末尾 file_size = file.tell() file.seek(0) # 重置文件指针 if file_size > max_file_size: return jsonify({'message': f'文件大小超过限制(最大{max_file_size/1024/1024:.1f}MB)'}), 400 # 检查用户上传文件数量 user_id = get_jwt_identity() user_file_count = File.query.filter_by(uploaded_by=user_id).count() if user_file_count >= max_files_per_user: return jsonify({'message': f'已达到最大上传文件数量限制({max_files_per_user}个)'}), 400 if file: # 计算文件哈希 hasher = hashlib.sha256() for chunk in iter(lambda: file.read(4096), b''): hasher.update(chunk) file_hash = hasher.hexdigest() file.seek(0) # 重置文件指针 # 检查文件是否已存在(通过哈希) existing_file = File.query.filter_by(file_hash=file_hash).first() if existing_file: return jsonify({'message': '文件已存在', 'file': existing_file.to_dict()}), 409 # 获取分类ID category_id = request.form.get('category_id') if not category_id: return jsonify({'message': '缺少分类ID'}), 400 category = Category.query.get(category_id) if not category: return jsonify({'message': '分类不存在'}), 404 # 生成唯一文件名 from uuid import uuid4 filename = f"{uuid4().hex}_{file.filename}" file_path = os.path.join(current_app.config['UPLOAD_FOLDER'], filename) # 确保上传目录存在 os.makedirs(current_app.config['UPLOAD_FOLDER'], exist_ok=True) # 保存文件 file.save(file_path) # 创建文件记录 new_file = File( filename=filename, original_filename=file.filename, file_path=file_path, description=request.form.get('description'), file_size=file_size, file_hash=file_hash, category_id=category_id, uploaded_by=user_id ) db.session.add(new_file) db.session.commit() # 记录操作日志 log = OperationLog( user_id=user_id, operation_type='upload', file_id=new_file.id, details=f'上传文件: {new_file.original_filename}' ) db.session.add(log) db.session.commit() return jsonify(new_file.to_dict()), 201 @admin_bp.route('/files//download', methods=['GET']) @admin_required def download_file(file_id): file_record = File.query.get_or_404(file_id) file_path = file_record.file_path user_id = get_jwt_identity() if not os.path.exists(file_path): return jsonify({'message': '文件不存在'}), 404 try: # 记录操作日志 log = OperationLog( user_id=user_id, operation_type='download', file_id=file_record.id, details=f'下载文件: {file_record.original_filename}' ) db.session.add(log) db.session.commit() return send_file(file_path, as_attachment=True, download_name=file_record.original_filename) except Exception as e: current_app.logger.error(f"文件下载失败: {file_path}, 错误: {str(e)}") return jsonify({'message': '文件下载失败'}), 500 @admin_bp.route('/files/', methods=['PUT']) @admin_required def update_file(file_id): file_record = File.query.get_or_404(file_id) data = request.get_json() if 'description' in data: file_record.description = data['description'] if 'category_id' in data: category_id = data['category_id'] if category_id: category = Category.query.get(category_id) if not category: return jsonify({'message': '分类不存在'}), 404 file_record.category_id = category_id else: file_record.category_id = None db.session.commit() # 记录操作日志 user_id = get_jwt_identity() log = OperationLog( user_id=user_id, operation_type='update', file_id=file_record.id, details=f'更新文件 {file_record.original_filename} 的信息: {data}' ) db.session.add(log) db.session.commit() return jsonify(file_record.to_dict()), 200 @admin_bp.route('/files/', methods=['DELETE']) @admin_required def delete_file(file_id): file_record = File.query.get_or_404(file_id) # 记录文件信息用于日志 original_filename = file_record.original_filename file_path = file_record.file_path # 尝试删除物理文件 file_deleted = False if os.path.exists(file_path): try: os.remove(file_path) file_deleted = True except Exception as e: current_app.logger.error(f"删除文件失败: {file_path}, 错误: {str(e)}") # 即使文件删除失败,也继续删除数据库记录,但记录警告 current_app.logger.warning(f"文件不存在或无法删除,继续删除数据库记录: {file_path}") else: current_app.logger.warning(f"文件不存在: {file_path}") try: # 记录操作日志(在删除文件记录之前) user_id = get_jwt_identity() log = OperationLog( user_id=user_id, operation_type='delete', file_id=file_record.id, details=f'删除文件: {original_filename} (物理文件: {"已删除" if file_deleted else "不存在或删除失败"})' ) db.session.add(log) # 先删除相关的下载记录 if file_record.download: db.session.delete(file_record.download) # 删除数据库记录 db.session.delete(file_record) db.session.commit() return jsonify({ 'message': '文件已删除', 'details': { 'filename': original_filename, 'file_deleted': file_deleted } }), 200 except Exception as e: db.session.rollback() current_app.logger.error(f"删除数据库记录失败: {str(e)}") return jsonify({'message': '删除文件记录失败'}), 500 # 分类管理 # 系统设置 @admin_bp.route('/settings', methods=['GET']) @admin_required def get_settings(): settings = SystemSettings.query.all() settings_dict = [s.to_dict() for s in settings] # 将max_file_size从字节转换为MB返回给前端 for setting in settings_dict: if setting['key'] == 'max_file_size' and isinstance(setting['value'], (int, float)): setting['value'] = round(setting['value'] / (1024 * 1024)) return jsonify(settings_dict), 200 @admin_bp.route('/settings', methods=['PUT']) @admin_required def update_settings(): data = request.get_json() for key, value in data.items(): setting = SystemSettings.query.filter_by(key=key).first() if setting: setting.value = value else: new_setting = SystemSettings(key=key, value=value) db.session.add(new_setting) # 如果更新了每日配额,同步更新所有用户的配额 if 'daily_quota' in data: try: new_quota = int(data['daily_quota']) # 更新所有用户的每日配额 User.query.update({'daily_quota': new_quota}) current_app.logger.info(f"已更新所有用户的每日配额为: {new_quota}") except ValueError: current_app.logger.error(f"每日配额值无效: {data['daily_quota']}") return jsonify({'message': '每日配额值无效'}), 400 db.session.commit() # 记录操作日志 user_id = get_jwt_identity() log = OperationLog( user_id=user_id, operation_type='update_settings', details=f'更新系统设置: {data}' ) db.session.add(log) db.session.commit() return jsonify({'message': '设置已更新'}), 200 # 统计分析 @admin_bp.route('/analytics/users', methods=['GET']) @admin_required def get_user_analytics(): total_users = User.query.count() active_users = User.query.filter_by(is_active=True).count() admin_users = User.query.filter_by(is_admin=True).count() return jsonify({ 'total_users': total_users, 'active_users': active_users, 'admin_users': admin_users }), 200 @admin_bp.route('/analytics/downloads', methods=['GET']) @admin_required def get_download_analytics(): total_downloads = Download.query.count() today = datetime.utcnow().date() today_downloads = Download.query.filter(db.func.date(Download.download_time) == today).count() return jsonify({ 'total_downloads': total_downloads, 'today_downloads': today_downloads }), 200 @admin_bp.route('/analytics/categories', methods=['GET']) @admin_required def get_category_analytics(): categories = Category.query.all() category_data = [] for category in categories: file_count = File.query.filter_by(category_id=category.id).count() category_data.append({ 'id': category.id, 'name': category.name, 'file_count': file_count }) return jsonify(category_data), 200 @admin_bp.route('/analytics/system_health', methods=['GET']) @admin_required def get_system_health(): # 示例:检查数据库连接 try: db.session.execute(db.text('SELECT 1')) db_status = '正常' except Exception as e: db_status = f'异常: {str(e)}' # 示例:检查上传目录是否存在和可写 upload_folder_path = current_app.config['UPLOAD_FOLDER'] upload_folder_status = '正常' if not os.path.exists(upload_folder_path): upload_folder_status = '不存在' elif not os.access(upload_folder_path, os.W_OK): upload_folder_status = '不可写' return jsonify({ 'database_status': db_status, 'upload_folder_status': upload_folder_status, 'timestamp': datetime.utcnow().isoformat() }), 200 @admin_bp.route('/dashboard_summary', methods=['GET']) @admin_required def get_dashboard_summary(): total_users = User.query.count() total_files = File.query.count() total_downloads = Download.query.count() total_file_size = db.session.query(db.func.sum(File.file_size)).scalar() or 0 return jsonify({ 'total_users': total_users, 'total_files': total_files, 'total_downloads': total_downloads, 'total_file_size': total_file_size }), 200 file = File.query.get_or_404(file_id) # 删除文件记录前先删除实际文件 if os.path.exists(file.file_path): try: os.remove(file.file_path) except Exception as e: return jsonify({'message': f'删除文件失败: {str(e)}'}), 500 # 删除相关的下载记录 if file.download: db.session.delete(file.download) db.session.delete(file) db.session.commit() return jsonify({'message': '文件已删除'}), 200 # 数据分析 @admin_bp.route('/analytics/files', methods=['GET']) @admin_required def file_analytics(): # 总文件数 total_files = File.query.count() # 已领取和未领取文件数 taken_files = File.query.filter_by(is_taken=True).count() available_files = total_files - taken_files # 最近7天上传的文件数 seven_days_ago = datetime.utcnow() - timedelta(days=7) recent_uploads = File.query.filter(File.created_at >= seven_days_ago).count() # 按日期统计上传数量 upload_stats = db.session.query( db.func.date(File.created_at), db.func.count(File.id) ).group_by(db.func.date(File.created_at)).all() # 按日期统计领取数量 take_stats = db.session.query( db.func.date(File.taken_at), db.func.count(File.id) ).filter(File.taken_at.isnot(None)).group_by(db.func.date(File.taken_at)).all() return jsonify({ 'total_files': total_files, 'taken_files': taken_files, 'available_files': available_files, 'recent_uploads': recent_uploads, 'upload_stats': {date.strftime('%Y-%m-%d'): count for date, count in upload_stats}, 'take_stats': {date.strftime('%Y-%m-%d'): count for date, count in take_stats} }), 200 @admin_bp.route('/analytics/users', methods=['GET']) @admin_required def user_analytics(): # 总用户数 total_users = User.query.count() # 活跃和非活跃用户数 active_users = User.query.filter_by(is_active=True).count() inactive_users = total_users - active_users # 管理员数量 admin_users = User.query.filter_by(is_admin=True).count() # 用户领取统计 user_download_stats = db.session.query( User.username, db.func.count(Download.id) ).join(Download).group_by(User.username).all() return jsonify({ 'total_users': total_users, 'active_users': active_users, 'inactive_users': inactive_users, 'admin_users': admin_users, 'user_download_stats': {username: count for username, count in user_download_stats} }), 200 @admin_bp.route('/categories', methods=['POST']) def update_file_category(file_id): file = File.query.get_or_404(file_id) data = request.get_json() if 'category_id' not in data: return jsonify({'message': '缺少分类ID'}), 400 if data['category_id'] is not None: category = Category.query.get(data['category_id']) if not category: return jsonify({'message': '分类不存在'}), 404 file.category_id = data['category_id'] db.session.commit() return jsonify(file.to_dict()), 200 # 分类统计 @admin_bp.route('/analytics/categories', methods=['GET']) @admin_required def category_analytics(): # 获取所有分类及其文件数量 categories = db.session.query( Category.id, Category.name, Category.path, db.func.count(File.id) ).outerjoin(File).group_by(Category.id).all() # 获取每个分类的下载次数 downloads = db.session.query( File.category_id, db.func.count(Download.id) ).join(Download).group_by(File.category_id).all() download_stats = {category_id: count for category_id, count in downloads} # 构建结果 result = [] for category_id, name, path, file_count in categories: result.append({ 'id': category_id, 'name': name, 'path': path, 'file_count': file_count, 'download_count': download_stats.get(category_id, 0) }) return jsonify(result), 200 # 团队长管理相关API @admin_bp.route('/team-leaders', methods=['GET']) @admin_required def get_team_leaders(): """获取所有团队长列表""" page = request.args.get('page', 1, type=int) per_page = request.args.get('per_page', 10, type=int) search_query = request.args.get('search', type=str) query = User.query.filter_by(is_team_leader=True) if search_query: query = query.filter(User.username.like(f'%{search_query}%')) pagination = query.paginate(page=page, per_page=per_page, error_out=False) team_leaders = pagination.items result = [] for leader in team_leaders: # 获取团队成员数量 member_count = TeamMember.query.filter_by(leader_id=leader.id).count() leader_data = leader.to_dict() leader_data['member_count'] = member_count result.append(leader_data) return jsonify({ 'team_leaders': result, 'total': pagination.total, 'pages': pagination.pages, 'current_page': pagination.page, 'has_next': pagination.has_next, 'has_prev': pagination.has_prev }), 200 @admin_bp.route('/users//team-leader', methods=['PUT']) @admin_required def set_team_leader(user_id): """设置用户为团队长或取消团队长权限""" data = request.get_json() is_team_leader = data.get('is_team_leader', False) user = User.query.get_or_404(user_id) # 如果取消团队长权限,需要先移除所有团队成员 if not is_team_leader and user.is_team_leader: # 移除该团队长的所有成员 TeamMember.query.filter_by(leader_id=user_id).delete() db.session.commit() user.is_team_leader = is_team_leader db.session.commit() # 记录操作日志 admin_user_id = get_jwt_identity() action = '设置为团队长' if is_team_leader else '取消团队长权限' log = OperationLog( user_id=admin_user_id, operation_type='set_team_leader', details=f'{action}: {user.username} (ID: {user.id})' ) db.session.add(log) db.session.commit() return jsonify({ 'message': f'用户 {user.username} 已成功{action}', 'user': user.to_dict() }), 200 @admin_bp.route('/team-members', methods=['GET']) @admin_required def get_all_team_members(): """获取所有团队成员关系""" page = request.args.get('page', 1, type=int) per_page = request.args.get('per_page', 20, type=int) leader_id = request.args.get('leader_id', type=int) query = TeamMember.query if leader_id: query = query.filter_by(leader_id=leader_id) pagination = query.paginate(page=page, per_page=per_page, error_out=False) members = pagination.items result = [] for member in members: member_data = member.to_dict() result.append(member_data) return jsonify({ 'team_members': result, 'total': pagination.total, 'pages': pagination.pages, 'current_page': pagination.page, 'has_next': pagination.has_next, 'has_prev': pagination.has_prev }), 200 @admin_bp.route('/team-members', methods=['POST']) @admin_required def add_team_member(): """添加团队成员""" data = request.get_json() leader_id = data.get('leader_id') member_id = data.get('member_id') if not leader_id or not member_id: return jsonify({'message': '团队长ID和成员ID不能为空'}), 400 if leader_id == member_id: return jsonify({'message': '团队长不能添加自己为成员'}), 400 # 检查团队长是否存在且有权限 leader = User.query.get(leader_id) if not leader or not leader.is_team_leader: return jsonify({'message': '指定的用户不是团队长'}), 400 # 检查成员是否存在 member = User.query.get(member_id) if not member: return jsonify({'message': '指定的成员用户不存在'}), 404 # 检查成员是否已有团队长 existing_member = TeamMember.query.filter_by(member_id=member_id).first() if existing_member: return jsonify({'message': '该用户已有团队长,请先移除现有团队关系'}), 400 # 创建团队关系 team_member = TeamMember(leader_id=leader_id, member_id=member_id) db.session.add(team_member) db.session.commit() # 记录操作日志 admin_user_id = get_jwt_identity() log = OperationLog( user_id=admin_user_id, operation_type='add_team_member', details=f'添加团队成员: {member.username} (ID: {member.id}) 到团队长 {leader.username} (ID: {leader.id})' ) db.session.add(log) db.session.commit() return jsonify({ 'message': '团队成员添加成功', 'team_member': team_member.to_dict() }), 201 @admin_bp.route('/team-members/', methods=['DELETE']) @admin_required def remove_team_member(member_id): """移除团队成员""" team_member = TeamMember.query.filter_by(member_id=member_id).first() if not team_member: return jsonify({'message': '未找到该团队成员关系'}), 404 member_name = team_member.member.username leader_name = team_member.team_leader.username db.session.delete(team_member) db.session.commit() # 记录操作日志 admin_user_id = get_jwt_identity() log = OperationLog( user_id=admin_user_id, operation_type='remove_team_member', details=f'移除团队成员: {member_name} (ID: {member_id}) 从团队长 {leader_name} (ID: {team_member.leader_id})' ) db.session.add(log) db.session.commit() return jsonify({'message': '团队成员移除成功'}), 200 @admin_bp.route('/users/without-team', methods=['GET']) @admin_required def get_users_without_team(): """获取没有团队的用户列表(用于添加团队成员)""" # 获取已有团队的用户ID existing_member_ids = db.session.query(TeamMember.member_id).all() existing_ids = [id[0] for id in existing_member_ids] # 查询没有团队的用户(排除管理员和已有团队的用户) query = User.query.filter( User.is_admin == False, ~User.id.in_(existing_ids) if existing_ids else True ) users = query.all() return jsonify({ 'users': [{ 'id': user.id, 'username': user.username, 'email': user.email, 'is_active': user.is_active } for user in users] }), 200