""" 技术论坛 - 后台管理系统 (重构版 + 登录验证) """ from flask import Flask, render_template, jsonify, request, redirect, url_for, session, make_response from flask_cors import CORS import jwt import datetime import os from functools import wraps from pathlib import Path # 导入配置和模型 import sys sys.path.insert(0, str(Path(__file__).parent.parent)) from config import SECRET_KEY, ADMIN_USERNAME, ADMIN_PASSWORD, DATABASE_PATH, ADMIN_PORT from models import Database, UserModel, PostModel, ReplyModel, TopicModel app = Flask(__name__) CORS(app) app.secret_key = SECRET_KEY # 初始化数据库 db = Database(DATABASE_PATH) user_model = UserModel(db) post_model = PostModel(db) reply_model = ReplyModel(db) topic_model = TopicModel(db) # ============ 登录验证装饰器 ============ def admin_required(f): @wraps(f) def decorated_function(*args, **kwargs): # 检查 session if not session.get('admin_logged_in'): # 检查 Authorization header token = request.headers.get('Authorization', '').replace('Bearer ', '') if token: try: data = jwt.decode(token, SECRET_KEY, algorithms=['HS256']) if data.get('admin'): return f(*args, **kwargs) except: pass # API请求返回401,页面请求跳转登录 if request.path.startswith('/api/'): return jsonify({'error': '请先登录', 'code': 401}), 401 return redirect('/login') return f(*args, **kwargs) return decorated_function # ============ 登录相关 ============ @app.route('/login') def login_page(): return render_template('login.html') @app.route('/api/login', methods=['POST']) def api_login(): data = request.json username = data.get('username', '').strip() password = data.get('password', '') if not username or not password: return jsonify({'error': '请输入用户名和密码'}), 400 if username != ADMIN_USERNAME or password != ADMIN_PASSWORD: return jsonify({'error': '用户名或密码错误'}), 400 # 设置session session['admin_logged_in'] = True session['admin_username'] = username # 生成token(可选,用于API调用) token = jwt.encode({ 'admin': True, 'username': username, 'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=24) }, SECRET_KEY, algorithm='HS256') return jsonify({ 'success': True, 'token': token, 'message': '登录成功' }) @app.route('/api/logout', methods=['POST']) def api_logout(): session.pop('admin_logged_in', None) session.pop('admin_username', None) return jsonify({'success': True, 'message': '已退出登录'}) @app.route('/api/check-auth') def api_check_auth(): if session.get('admin_logged_in'): return jsonify({ 'logged_in': True, 'username': session.get('admin_username') }) return jsonify({'logged_in': False}) # ============ 页面路由 ============ @app.route('/') @admin_required def index(): return render_template('index.html') @app.route('/users') @admin_required def users_page(): return render_template('users.html') @app.route('/posts') @admin_required def posts_page(): return render_template('posts.html') @app.route('/topics') @admin_required def topics_page(): return render_template('topics.html') # ============ API路由 ============ @app.route('/api/stats') @admin_required def api_stats(): users = user_model.get_all() posts, posts_total = post_model.get_all() topics = topic_model.get_all() # 统计回复数 total_replies = 0 for post in posts: total_replies += len(reply_model.get_by_post(post['id'])) today = datetime.datetime.now().strftime('%Y-%m-%d') today_posts = sum(1 for p in posts if p.get('created_at', '').startswith(today)) today_users = sum(1 for u in users if u.get('created_at', '').startswith(today)) # 帖子类型统计 discussion_count = sum(1 for p in posts if p.get('type') == 'discussion') share_count = sum(1 for p in posts if p.get('type') == 'share') return jsonify({ 'users_count': len(users), 'posts_count': posts_total, 'topics_count': len(topics), 'messages_count': total_replies, 'today_posts': today_posts, 'today_users': today_users, 'discussion_count': discussion_count, 'share_count': share_count, }) @app.route('/api/users') @admin_required def api_users(): users = user_model.get_all() user_list = [] for user in users: user_list.append({ 'id': user['id'], 'username': user.get('username', ''), 'email': user.get('email', ''), 'phone': user.get('phone', ''), 'posts_count': user_model.get_posts_count(user['id']), 'replies_count': user_model.get_replies_count(user['id']), 'created_at': user.get('created_at', ''), }) return jsonify(user_list) @app.route('/api/users/', methods=['DELETE']) @admin_required def api_delete_user(user_id): user_model.delete(user_id) return jsonify({'success': True}) @app.route('/api/posts') @admin_required def api_posts(): post_type = request.args.get('type') posts, total = post_model.get_all(post_type) post_list = [] for post in posts: author = user_model.get_by_id(post['author_id']) or {} post_list.append({ 'id': post['id'], 'title': post['title'], 'type': post['type'], 'author': author.get('username', '未知'), 'author_id': post['author_id'], 'likes': len(post['likes']), 'replies': len(reply_model.get_by_post(post['id'])), 'views': post['views'], 'is_pinned': post['is_pinned'], 'created_at': post['created_at'], }) return jsonify(post_list) @app.route('/api/posts/') @admin_required def api_post_detail(post_id): post = post_model.get_by_id(post_id) if not post: return jsonify({'error': '帖子不存在'}), 404 author = user_model.get_by_id(post['author_id']) or {} # 获取回复 replies = reply_model.get_by_post(post_id) reply_list = [] for reply in replies: reply_author = user_model.get_by_id(reply['author_id']) or {} reply_list.append({ 'id': reply['id'], 'content': reply['content'][:100] + '...' if len(reply['content']) > 100 else reply['content'], 'author': reply_author.get('username', '未知'), 'likes': len(reply['likes']), 'created_at': reply['created_at'], }) return jsonify({ 'id': post_id, 'title': post['title'], 'content': post['content'], 'type': post['type'], 'author': author.get('username', '未知'), 'tags': post['tags'], 'likes': len(post['likes']), 'replies': reply_list, 'views': post['views'], 'is_pinned': post['is_pinned'], 'created_at': post['created_at'], }) @app.route('/api/posts/', methods=['DELETE']) @admin_required def api_delete_post(post_id): post_model.delete(post_id) return jsonify({'success': True}) @app.route('/api/posts//pin', methods=['POST']) @admin_required def api_pin_post(post_id): new_pin = post_model.toggle_pin(post_id) return jsonify({ 'success': True, 'is_pinned': new_pin }) @app.route('/api/topics') @admin_required def api_topics(): topics = topic_model.get_all() topic_list = [] for topic in topics: author = user_model.get_by_id(topic['author_id']) or {} topic_list.append({ 'id': topic['id'], 'name': topic['name'], 'icon': topic.get('icon', '🔧'), 'author': author.get('username', '未知'), 'sub_topics_count': len(topic_model.get_sub_topics(topic['id'])), 'questions_count': len(topic_model.get_questions(topic['id'])), 'followers_count': len(topic['followers']), 'created_at': topic['created_at'], }) return jsonify(topic_list) @app.route('/api/topics/') @admin_required def api_topic_detail(topic_id): topic = topic_model.get_by_id(topic_id) if not topic: return jsonify({'error': '主题不存在'}), 404 author = user_model.get_by_id(topic['author_id']) or {} return jsonify({ 'id': topic_id, 'name': topic['name'], 'description': topic.get('description', ''), 'icon': topic.get('icon', '🔧'), 'author': author.get('username', '未知'), 'sub_topics': topic_model.get_sub_topics(topic_id), 'questions': topic_model.get_questions(topic_id), 'followers_count': len(topic['followers']), 'created_at': topic['created_at'], }) @app.route('/api/topics/', methods=['DELETE']) @admin_required def api_delete_topic(topic_id): topic_model.delete(topic_id) return jsonify({'success': True}) @app.route('/api/tags') @admin_required def api_tags(): tags = post_model.get_tags_stats() return jsonify([{'name': t[0], 'count': t[1]} for t in tags[:20]]) # ============ 健康检查(无需登录) ============ @app.route('/api/health') def api_health(): return jsonify({'status': 'ok', 'service': 'tech-forum-admin', 'port': ADMIN_PORT}) if __name__ == '__main__': print("=" * 50) print("技术论坛 - 后台管理系统") print("=" * 50) print(f"访问地址: http://localhost:{ADMIN_PORT}") print(f"默认账号: {ADMIN_USERNAME}") print(f"默认密码: {ADMIN_PASSWORD}") print("=" * 50) app.run(host='0.0.0.0', port=ADMIN_PORT, debug=True)