generated from dellevin/template
288 lines
8.5 KiB
Python
288 lines
8.5 KiB
Python
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/<int:comment_id>', 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"<p>{comment_data.get('comment', '')}</p>\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)
|