from flask import Flask, request, jsonify, render_template, session, redirect, url_for from flask_cors import CORS import json import os from datetime import datetime import hashlib from config import * app = Flask(__name__) CORS(app) # 允许跨域请求 app.secret_key = SECRET_KEY # 数据文件路径已在 config.py 中定义 # DATA_FILE = os.path.join(os.path.dirname(__file__), 'data', 'comments.json') def read_comments(): """读取评论数据""" try: if os.path.exists(DATA_FILE): with open(DATA_FILE, 'r', encoding='utf-8') as f: return json.load(f) return {"comments": []} except Exception as e: print(f"读取数据失败: {e}") return {"comments": []} def save_comments(data): """保存评论数据""" os.makedirs(os.path.dirname(DATA_FILE), exist_ok=True) with open(DATA_FILE, 'w', encoding='utf-8') as f: json.dump(data, f, ensure_ascii=False, indent=2) def parse_user_agent(ua): """解析 User Agent 获取浏览器和系统信息""" browser = 'Unknown' os_name = 'Unknown' # 浏览器检测 if 'Chrome' in ua and 'Edge' not in ua: browser = 'Chrome' elif 'Firefox' in ua: browser = 'Firefox' elif 'Safari' in ua and 'Chrome' not in ua: browser = 'Safari' elif 'Edge' in ua: browser = 'Edge' # 系统检测 if 'Windows' in ua: os_name = 'Windows' elif 'Mac' in ua: os_name = 'macOS' elif 'Linux' in ua: os_name = 'Linux' elif 'Android' in ua: os_name = 'Android' elif 'iPhone' in ua or 'iPad' in ua: os_name = 'iOS' return browser, os_name @app.route('/api/comment', methods=['GET']) def get_comments(): """获取评论列表(前端展示用,仅返回已审核)""" try: page = int(request.args.get('page', 1)) page_size = int(request.args.get('pageSize', PAGE_SIZE_FRONTEND)) data = read_comments() # 过滤已审核的评论 filtered = [c for c in data['comments'] if c.get('status') == 'approved'] # 按时间倒序排序 filtered.sort(key=lambda x: x.get('time', 0), reverse=True) total_count = len(filtered) total_pages = (total_count + page_size - 1) // page_size # 分页 start = (page - 1) * page_size end = start + page_size paged_comments = filtered[start:end] return jsonify({ 'errno': 0, 'errmsg': '', 'data': { 'page': page, 'totalPages': total_pages, 'pageSize': page_size, 'count': total_count, 'data': paged_comments } }) except Exception as e: return jsonify({ 'errno': 1, 'errmsg': str(e) }), 500 # 管理员账号配置已在 config.py 中定义 # ADMIN_USERNAME = 'admin' # ADMIN_PASSWORD = 'zhaoyang0902' def login_required(f): """登录验证装饰器""" from functools import wraps @wraps(f) def decorated_function(*args, **kwargs): if not session.get('logged_in'): return redirect(url_for('login_page')) 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 do_login(): """处理登录请求""" data = request.get_json() username = data.get('username') password = data.get('password') if username == ADMIN_USERNAME and password == ADMIN_PASSWORD: session['logged_in'] = True return jsonify({'errno': 0, 'errmsg': '登录成功'}) else: return jsonify({'errno': 1, 'errmsg': '用户名或密码错误'}), 401 @app.route('/logout') def logout(): """退出登录""" session.pop('logged_in', None) return redirect(url_for('login_page')) @app.route('/admin') @login_required def admin_page(): """后台管理页面""" return render_template('admin.html') @app.route('/api/admin/comments', methods=['GET']) @login_required def get_admin_comments(): """获取所有评论(后台管理用)""" try: status_filter = request.args.get('status', '') keyword = request.args.get('keyword', '').lower() page = int(request.args.get('page', 1)) page_size = int(request.args.get('pageSize', PAGE_SIZE_ADMIN)) data = read_comments() comments = data['comments'] # 状态过滤 if status_filter: comments = [c for c in comments if c.get('status') == status_filter] else: # 全部状态下默认不显示已删除的评论 comments = [c for c in comments if c.get('status') != 'deleted'] # 关键词搜索(昵称或内容) if keyword: comments = [c for c in comments if keyword in c.get('nick', '').lower() or keyword in c.get('comment', '').lower()] # 按时间倒序排序 comments.sort(key=lambda x: x.get('time', 0), reverse=True) total_count = len(comments) total_pages = (total_count + page_size - 1) // page_size if total_count > 0 else 0 # 分页 start = (page - 1) * page_size end = start + page_size paged_comments = comments[start:end] return jsonify({ 'errno': 0, 'data': { 'list': paged_comments, 'total': total_count, 'page': page, 'pageSize': page_size, 'totalPages': total_pages } }) except Exception as e: return jsonify({ 'errno': 1, 'errmsg': str(e) }), 500 @app.route('/api/admin/comment/', methods=['PUT']) @login_required def update_comment_status(comment_id): """更新评论状态""" try: update_data = request.get_json() new_status = update_data.get('status') if new_status not in ['approved', 'spam', 'deleted']: return jsonify({'errno': 1, 'errmsg': '无效的状态'}), 400 data = read_comments() found = False for c in data['comments']: if c['id'] == comment_id: c['status'] = new_status found = True break if not found: return jsonify({'errno': 1, 'errmsg': '评论不存在'}), 404 save_comments(data) return jsonify({'errno': 0, 'errmsg': '更新成功'}) except Exception as e: return jsonify({ 'errno': 1, 'errmsg': str(e) }), 500 @app.route('/api/comment', methods=['POST']) def add_comment(): """添加评论""" try: comment_data = request.get_json() if not comment_data: return jsonify({ 'errno': 1, 'errmsg': '无效的数据' }), 400 data = read_comments() # 生成新 ID new_id = max([c.get('id', 0) for c in data['comments']], default=0) + 1 # 解析 User Agent ua = comment_data.get('ua', '') browser, os_name = parse_user_agent(ua) # 生成头像(基于邮箱 MD5) email = comment_data.get('mail', '') avatar_hash = hashlib.md5(email.encode('utf-8')).hexdigest() avatar = f'https://seccdn.libravatar.org/avatar/{avatar_hash}' # 创建新评论 new_comment = { 'id': new_id, 'nick': comment_data.get('nick', '匿名'), 'mail': comment_data.get('mail', ''), 'link': comment_data.get('link', ''), 'comment': f"

{comment_data.get('comment', '')}

\n", 'ua': ua, 'browser': browser, 'os': os_name, 'avatar': avatar, 'time': int(datetime.now().timestamp() * 1000), 'status': 'approved' # 直接审核通过 } # 添加到数据中 data['comments'].append(new_comment) save_comments(data) return jsonify({ 'errno': 0, 'errmsg': '', 'data': new_comment }) except Exception as e: return jsonify({ 'errno': 1, 'errmsg': str(e) }), 500 if __name__ == '__main__': app.run(host=HOST, port=PORT, debug=DEBUG)