from flask import Flask, render_template, request, jsonify, session
import re
import random
import json
from datetime import datetime
import os
import logging
from typing import List, Dict, Any, Optional
import sqlite3
from contextlib import contextmanager
import bleach
import secrets
import hashlib
app = Flask(__name__)
app.secret_key = secrets.token_hex(32)
# 日志配置
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class DatabaseManager:
"""数据库管理器"""
def __init__(self, db_path='poems.db'):
self.db_path = db_path
self.init_db()
def init_db(self):
"""初始化数据库"""
with self.get_db_connection() as conn:
# 创建诗词表
conn.execute('''
CREATE TABLE IF NOT EXISTS poems (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
author TEXT NOT NULL,
content TEXT NOT NULL,
type TEXT,
tags TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
# 创建用户表
conn.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
# 创建评论表
conn.execute('''
CREATE TABLE IF NOT EXISTS comments (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
poem_id INTEGER NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users (id),
FOREIGN KEY (poem_id) REFERENCES poems (id)
)
''')
# 创建点赞表
conn.execute('''
CREATE TABLE IF NOT EXISTS likes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
poem_id INTEGER NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users (id),
FOREIGN KEY (poem_id) REFERENCES poems (id),
UNIQUE(user_id, poem_id)
)
''')
# 创建索引
conn.execute('CREATE INDEX IF NOT EXISTS idx_poems_author ON poems(author)')
conn.execute('CREATE INDEX IF NOT EXISTS idx_poems_type ON poems(type)')
conn.execute('CREATE INDEX IF NOT EXISTS idx_comments_poem ON comments(poem_id)')
conn.execute('CREATE INDEX IF NOT EXISTS idx_likes_poem ON likes(poem_id)')
conn.commit()
# 检查是否有数据
cursor = conn.execute('SELECT COUNT(*) FROM poems')
count = cursor.fetchone()[0]
if count == 0:
# 添加示例数据
self.create_sample_data(conn)
@contextmanager
def get_db_connection(self):
"""数据库连接上下文管理器"""
conn = sqlite3.connect(self.db_path)
conn.row_factory = sqlite3.Row
try:
yield conn
finally:
conn.close()
def create_sample_data(self, conn):
"""创建示例数据"""
sample_poems = [
('静夜思', '李白', '床前明月光,疑是地上霜。\n举头望明月,低头思故乡。', '五言绝句', '月亮,思乡'),
('春晓', '孟浩然', '春眠不觉晓,处处闻啼鸟。\n夜来风雨声,花落知多少。', '五言绝句', '春天,自然'),
('登鹳雀楼', '王之涣', '白日依山尽,黄河入海流。\n欲穷千里目,更上一层楼。', '五言绝句', '励志,哲理'),
('望庐山瀑布', '李白', '日照香炉生紫烟,遥看瀑布挂前川。\n飞流直下三千尺,疑是银河落九天。', '七言绝句', '瀑布,景色'),
('黄鹤楼送孟浩然之广陵', '李白', '故人西辞黄鹤楼,烟花三月下扬州。\n孤帆远影碧空尽,唯见长江天际流。', '七言绝句', '送别,友情')
]
conn.executemany('''
INSERT INTO poems (title, author, content, type, tags)
VALUES (?, ?, ?, ?, ?)
''', sample_poems)
# 创建示例用户
conn.execute('''
INSERT INTO users (username, password_hash)
VALUES (?, ?)
''', ('admin', hashlib.sha256('password123'.encode()).hexdigest()))
conn.commit()
def get_poem_by_id(self, poem_id: int) -> Optional[Dict[str, Any]]:
"""根据ID获取诗词"""
with self.get_db_connection() as conn:
cursor = conn.execute('''
SELECT p.*,
(SELECT COUNT(*) FROM comments c WHERE c.poem_id = p.id) AS comment_count,
(SELECT COUNT(*) FROM likes l WHERE l.poem_id = p.id) AS like_count
FROM poems p
WHERE p.id = ?
''', (poem_id,))
row = cursor.fetchone()
return dict(row) if row else None
def get_all_poems(self, page: int = 1, per_page: int = 12) -> tuple:
"""获取所有诗词(分页)"""
offset = (page - 1) * per_page
with self.get_db_connection() as conn:
cursor = conn.execute('''
SELECT p.*,
(SELECT COUNT(*) FROM comments c WHERE c.poem_id = p.id) AS comment_count,
(SELECT COUNT(*) FROM likes l WHERE l.poem_id = p.id) AS like_count
FROM poems p
ORDER BY created_at DESC
LIMIT ? OFFSET ?
''', (per_page, offset))
poems = [dict(row) for row in cursor.fetchall()]
# 总数
cursor = conn.execute('SELECT COUNT(*) FROM poems')
total_count = cursor.fetchone()[0]
total_pages = (total_count + per_page - 1) // per_page
return poems, total_pages
def search_poems(self, query: str, page: int = 1, per_page: int = 12) -> tuple:
"""搜索诗词(分页)"""
if not query:
return self.get_all_poems(page, per_page)
offset = (page - 1) * per_page
search_term = f'%{query}%'
with self.get_db_connection() as conn:
cursor = conn.execute('''
SELECT p.*,
(SELECT COUNT(*) FROM comments c WHERE c.poem_id = p.id) AS comment_count,
(SELECT COUNT(*) FROM likes l WHERE l.poem_id = p.id) AS like_count
FROM poems p
WHERE title LIKE ? OR author LIKE ? OR content LIKE ? OR tags LIKE ?
ORDER BY CASE
WHEN title LIKE ? THEN 1
WHEN author LIKE ? THEN 2
WHEN content LIKE ? THEN 3
ELSE 4
END
LIMIT ? OFFSET ?
''', (search_term, search_term, search_term, search_term,
search_term, search_term, search_term, per_page, offset))
poems = [dict(row) for row in cursor.fetchall()]
# 总数
cursor = conn.execute('''
SELECT COUNT(*) FROM poems
WHERE title LIKE ? OR author LIKE ? OR content LIKE ? OR tags LIKE ?
''', (search_term, search_term, search_term, search_term))
total_count = cursor.fetchone()[0]
total_pages = (total_count + per_page - 1) // per_page
return poems, total_pages
def get_all_authors(self) -> List[str]:
"""获取所有作者"""
with self.get_db_connection() as conn:
cursor = conn.execute('SELECT DISTINCT author FROM poems ORDER BY author')
return [row['author'] for row in cursor.fetchall()]
def get_all_types(self) -> List[str]:
"""获取所有类型"""
with self.get_db_connection() as conn:
cursor = conn.execute('SELECT DISTINCT type FROM poems ORDER BY type')
return [row['type'] for row in cursor.fetchall()]
def get_all_tags(self) -> List[str]:
"""获取所有标签"""
with self.get_db_connection() as conn:
cursor = conn.execute('SELECT tags FROM poems')
all_tags = set()
for row in cursor.fetchall():
if row['tags']:
all_tags.update(tag.strip() for tag in row['tags'].split(','))
return sorted(list(all_tags))
def authenticate_user(self, username: str, password: str) -> Optional[Dict[str, Any]]:
"""验证用户"""
password_hash = hashlib.sha256(password.encode()).hexdigest()
with self.get_db_connection() as conn:
cursor = conn.execute('SELECT * FROM users WHERE username = ? AND password_hash = ?',
(username, password_hash))
user = cursor.fetchone()
return dict(user) if user else None
def register_user(self, username: str, password: str) -> bool:
"""注册用户"""
password_hash = hashlib.sha256(password.encode()).hexdigest()
with self.get_db_connection() as conn:
try:
conn.execute('INSERT INTO users (username, password_hash) VALUES (?, ?)',
(username, password_hash))
conn.commit()
return True
except sqlite3.IntegrityError:
return False # 用户名已存在
def add_comment(self, user_id: int, poem_id: int, content: str) -> bool:
"""添加评论"""
clean_content = bleach.clean(content)[:500]
with self.get_db_connection() as conn:
try:
conn.execute('''
INSERT INTO comments (user_id, poem_id, content)
VALUES (?, ?, ?)
''', (user_id, poem_id, clean_content))
conn.commit()
return True
except sqlite3.Error:
return False
def get_comments_by_poem(self, poem_id: int, limit: int = 10) -> List[Dict[str, Any]]:
"""获取诗词的评论"""
with self.get_db_connection() as conn:
cursor = conn.execute('''
SELECT c.*, u.username, c.created_at
FROM comments c
JOIN users u ON c.user_id = u.id
WHERE c.poem_id = ?
ORDER BY c.created_at DESC
LIMIT ?
''', (poem_id, limit))
return [dict(row) for row in cursor.fetchall()]
def toggle_like(self, user_id: int, poem_id: int) -> bool:
"""切换点赞状态"""
with self.get_db_connection() as conn:
try:
cursor = conn.execute('SELECT id FROM likes WHERE user_id = ? AND poem_id = ?',
(user_id, poem_id))
existing = cursor.fetchone()
if existing:
conn.execute('DELETE FROM likes WHERE user_id = ? AND poem_id = ?',
(user_id, poem_id))
else:
conn.execute('INSERT INTO likes (user_id, poem_id) VALUES (?, ?)',
(user_id, poem_id))
conn.commit()
return True
except sqlite3.Error:
return False
def get_like_status(self, user_id: int, poem_id: int) -> bool:
"""获取点赞状态"""
with self.get_db_connection() as conn:
cursor = conn.execute('SELECT id FROM likes WHERE user_id = ? AND poem_id = ?',
(user_id, poem_id))
return cursor.fetchone() is not None
def validate_input(input_str: str, max_len: int = 1000) -> str:
"""验证和清理输入"""
if not input_str:
return ""
clean_input = bleach.clean(input_str)[:max_len]
return clean_input.strip()
def login_required(f):
"""登录验证装饰器"""
from functools import wraps
@wraps(f)
def decorated_function(*args, **kwargs):
if 'user_id' not in session:
return '<script>alert("请先登录"); window.location="/login";</script>'
return f(*args, **kwargs)
return decorated_function
# 初始化数据库
db_manager = DatabaseManager()
@app.route('/')
def index():
"""首页"""
page = request.args.get('page', 1, type=int)
poems, total_pages = db_manager.get_all_poems(page)
return f'''
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>古诗词鉴赏</title>
<style>
body {{ font-family: '楷体', serif; margin: 0; padding: 20px; background-color: #f8f9fa; }}
.container {{ max-width: 1000px; margin: 0 auto; }}
.header {{ text-align: center; margin-bottom: 30px; }}
.poem-card {{ background: white; border-radius: 8px; padding: 20px; margin-bottom: 20px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }}
.poem-title {{ font-size: 24px; color: #333; margin-bottom: 10px; }}
.poem-author {{ color: #666; margin-bottom: 15px; }}
.poem-content {{ white-space: pre-line; line-height: 1.8; }}
.poem-meta {{ display: flex; gap: 15px; margin-top: 10px; color: #888; font-size: 14px; }}
.pagination {{ display: flex; justify-content: center; margin-top: 30px; gap: 10px; }}
.page-link {{ padding: 8px 15px; background: #007bff; color: white; text-decoration: none; border-radius: 4px; }}
.page-link:hover {{ background: #0056b3; }}
.nav {{ display: flex; justify-content: space-between; margin-bottom: 20px; }}
.nav a {{ padding: 10px 15px; background: #28a745; color: white; text-decoration: none; border-radius: 4px; }}
.nav a:hover {{ background: #218838; }}
.stats {{ display: flex; gap: 15px; margin-top: 5px; font-size: 13px; color: #888; }}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>古诗词鉴赏</h1>
<div class="nav">
<a href="/">首页</a>
<a href="/login">{session.get('username', '登录')}</a>
</div>
</div>
<form method="get" action="/search" style="margin-bottom: 30px;">
<input type="text" name="q" placeholder="搜索诗词..." value="{request.args.get('q', '')}"
style="width: 70%; padding: 10px; border: 1px solid #ddd; border-radius: 4px;">
<button type="submit" style="padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 4px;">搜索</button>
</form>
{''.join([
f'''
<div class="poem-card">
<div class="poem-title">{poem['title']}</div>
<div class="poem-author">{poem['author']}</div>
<div class="poem-content">{poem['content']}</div>
<div class="poem-meta">
<span>{poem['type']}</span>
<span>{poem['tags']}</span>
</div>
<div class="stats">
<span>💬 {poem['comment_count']} 条评论</span>
<span>👍 {poem['like_count']} 个赞</span>
</div>
<a href="/poem/{poem['id']}" style="display: inline-block; margin-top: 10px; padding: 8px 15px; background: #17a2b8; color: white; text-decoration: none; border-radius: 4px;">查看详情</a>
</div>
''' for poem in poems
])}
<div class="pagination">
{''.join([
f'<a class="page-link" href="?page={i}">{i}</a>'
for i in range(1, total_pages + 1)
])}
</div>
</div>
</body>
</html>
'''
@app.route('/search')
def search():
"""搜索页面"""
query = request.args.get('q', '')
page = request.args.get('page', 1, type=int)
if query:
poems, total_pages = db_manager.search_poems(validate_input(query), page)
else:
poems, total_pages = db_manager.get_all_poems(page)
return f'''
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>搜索结果 - 古诗词鉴赏</title>
<style>
body {{ font-family: '楷体', serif; margin: 0; padding: 20px; background-color: #f8f9fa; }}
.container {{ max-width: 1000px; margin: 0 auto; }}
.header {{ text-align: center; margin-bottom: 30px; }}
.poem-card {{ background: white; border-radius: 8px; padding: 20px; margin-bottom: 20px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }}
.poem-title {{ font-size: 24px; color: #333; margin-bottom: 10px; }}
.poem-author {{ color: #666; margin-bottom: 15px; }}
.poem-content {{ white-space: pre-line; line-height: 1.8; }}
.poem-meta {{ display: flex; gap: 15px; margin-top: 10px; color: #888; font-size: 14px; }}
.pagination {{ display: flex; justify-content: center; margin-top: 30px; gap: 10px; }}
.page-link {{ padding: 8px 15px; background: #007bff; color: white; text-decoration: none; border-radius: 4px; }}
.page-link:hover {{ background: #0056b3; }}
.nav {{ display: flex; justify-content: space-between; margin-bottom: 20px; }}
.nav a {{ padding: 10px 15px; background: #28a745; color: white; text-decoration: none; border-radius: 4px; }}
.nav a:hover {{ background: #218838; }}
.stats {{ display: flex; gap: 15px; margin-top: 5px; font-size: 13px; color: #888; }}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>搜索结果 - "{query}"</h1>
<div class="nav">
<a href="/">首页</a>
<a href="/login">{session.get('username', '登录')}</a>
</div>
</div>
<form method="get" action="/search" style="margin-bottom: 30px;">
<input type="text" name="q" placeholder="搜索诗词..." value="{query}"
style="width: 70%; padding: 10px; border: 1px solid #ddd; border-radius: 4px;">
<button type="submit" style="padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 4px;">搜索</button>
</form>
{''.join([
f'''
<div class="poem-card">
<div class="poem-title">{poem['title']}</div>
<div class="poem-author">{poem['author']}</div>
<div class="poem-content">{poem['content']}</div>
<div class="poem-meta">
<span>{poem['type']}</span>
<span>{poem['tags']}</span>
</div>
<div class="stats">
<span>💬 {poem['comment_count']} 条评论</span>
<span>👍 {poem['like_count']} 个赞</span>
</div>
<a href="/poem/{poem['id']}" style="display: inline-block; margin-top: 10px; padding: 8px 15px; background: #17a2b8; color: white; text-decoration: none; border-radius: 4px;">查看详情</a>
</div>
''' for poem in poems
])}
<div class="pagination">
{''.join([
f'<a class="page-link" href="?q={query}&page={i}">{i}</a>'
for i in range(1, total_pages + 1)
])}
</div>
</div>
</body>
</html>
'''
@app.route('/poem/<int:poem_id>')
def poem_detail(poem_id):
"""诗词详情页面"""
poem = db_manager.get_poem_by_id(poem_id)
if not poem:
return '<h1>诗词不存在</h1><a href="/">返回首页</a>'
# 获取评论
comments = db_manager.get_comments_by_poem(poem_id)
# 检查点赞状态
is_liked = False
if 'user_id' in session:
is_liked = db_manager.get_like_status(session['user_id'], poem_id)
# 获取上一首和下一首
with db_manager.get_db_connection() as conn:
cursor = conn.execute('SELECT id FROM poems ORDER BY id')
all_ids = [row['id'] for row in cursor.fetchall()]
try:
current_index = all_ids.index(poem_id)
prev_id = all_ids[current_index - 1] if current_index > 0 else None
next_id = all_ids[current_index + 1] if current_index < len(all_ids) - 1 else None
except ValueError:
prev_id = next_id = None
return f'''
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{poem['title']} - 古诗词鉴赏</title>
<style>
body {{ font-family: '楷体', serif; margin: 0; padding: 20px; background-color: #f8f9fa; }}
.container {{ max-width: 800px; margin: 0 auto; }}
.header {{ text-align: center; margin-bottom: 30px; }}
.poem-detail {{ background: white; border-radius: 8px; padding: 30px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); }}
.poem-title {{ font-size: 28px; color: #333; margin-bottom: 10px; }}
.poem-author {{ font-size: 20px; color: #666; margin-bottom: 20px; }}
.poem-content {{ font-size: 18px; white-space: pre-line; line-height: 2; margin-bottom: 20px; }}
.poem-meta {{ display: flex; gap: 15px; margin-bottom: 20px; color: #888; }}
.nav-buttons {{ display: flex; justify-content: space-between; margin-bottom: 20px; }}
.nav-btn {{ padding: 10px 20px; background: #007bff; color: white; text-decoration: none; border-radius: 4px; }}
.nav-btn:hover {{ background: #0056b3; }}
.nav-btn:disabled {{ background: #ccc; cursor: not-allowed; }}
.social-actions {{ display: flex; gap: 15px; margin-bottom: 20px; }}
.action-btn {{ padding: 10px 15px; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; }}
.like-btn {{ background: #dc3545; color: white; }}
.like-btn.active {{ background: #c82333; }}
.comment-section {{ margin-top: 30px; }}
.comment-form {{ margin-bottom: 20px; }}
.comment-input {{ width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; resize: vertical; }}
.comment-submit {{ margin-top: 10px; padding: 10px 20px; background: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer; }}
.comments-list {{ margin-top: 20px; }}
.comment-item {{ padding: 15px; border-left: 3px solid #007bff; background: #f8f9fa; margin-bottom: 10px; }}
.comment-header {{ display: flex; justify-content: space-between; margin-bottom: 5px; font-weight: bold; }}
.comment-date {{ font-size: 12px; color: #888; }}
.comment-content {{ line-height: 1.6; }}
.login-prompt {{ padding: 15px; background: #fff3cd; border: 1px solid #ffeaa7; border-radius: 4px; text-align: center; }}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>古诗词鉴赏</h1>
<div style="display: flex; justify-content: space-between; margin-top: 10px;">
<a href="/" style="padding: 10px 15px; background: #28a745; color: white; text-decoration: none; border-radius: 4px;">首页</a>
<a href="/login" style="padding: 10px 15px; background: #17a2b8; color: white; text-decoration: none; border-radius: 4px;">{session.get('username', '登录')}</a>
</div>
</div>
<div class="poem-detail">
<div class="poem-title">{poem['title']}</div>
<div class="poem-author">{poem['author']}</div>
<div class="poem-content">{poem['content']}</div>
<div class="poem-meta">
<span>类型: {poem['type']}</span>
<span>标签: {poem['tags']}</span>
</div>
<div class="nav-buttons">
<a href="/poem/{prev_id}" class="nav-btn" {'disabled' if not prev_id else ''}>上一首</a>
<a href="/" class="nav-btn">返回列表</a>
<a href="/poem/{next_id}" class="nav-btn" {'disabled' if not next_id else ''}>下一首</a>
</div>
<div class="social-actions">
<button class="action-btn like-btn {'active' if is_liked else ''}" onclick="toggleLike({poem_id}, this)">
👍 赞 ({poem['like_count']})
</button>
<span>💬 {poem['comment_count']} 条评论</span>
</div>
<div class="comment-section">
<h3>发表评论</h3>
{'<div class="login-prompt">请先 <a href="/login" style="color: #007bff;">登录</a> 再发表评论</div>' if 'user_id' not in session else ''}
<form class="comment-form" onsubmit="addComment(event, {poem_id})" style="{'display:none;' if 'user_id' not in session else ''}">
<textarea id="comment-input-{poem_id}" class="comment-input" rows="3" placeholder="写下你的感想..." required></textarea>
<button type="submit" class="comment-submit">发布评论</button>
</form>
<div class="comments-list">
<h3>评论列表</h3>
{''.join([
f'''
<div class="comment-item">
<div class="comment-header">
<span>{comment['username']}</span>
<span class="comment-date">{comment['created_at']}</span>
</div>
<div class="comment-content">{comment['content']}</div>
</div>
''' for comment in comments
])}
</div>
</div>
</div>
</div>
<script>
async function toggleLike(poemId, button) {{
if (!sessionStorage.getItem('user_id')) {{
alert('请先登录');
window.location.href = '/login';
return;
}}
try {{
const response = await fetch('/api/likes', {{
method: 'POST',
headers: {{ 'Content-Type': 'application/json' }},
body: JSON.stringify({{ poem_id: poemId }})
}});
const result = await response.json();
if (result.success) {{
const count = result.like_count || parseInt(button.textContent.match(/\d+/)[0]) + (button.classList.contains('active') ? -1 : 1);
button.innerHTML = `👍 赞 (${count})`;
button.classList.toggle('active');
}} else {{
alert(result.error || '操作失败');
}}
}} catch (e) {{
console.error(e);
alert('网络错误');
}}
}}
async function addComment(event, poemId) {{
event.preventDefault();
const input = document.getElementById(`comment-input-${{poemId}}`);
const content = input.value.trim();
if (!content) return;
try {{
const response = await fetch('/api/comments', {{
method: 'POST',
headers: {{ 'Content-Type': 'application/json' }},
body: JSON.stringify({{ poem_id: poemId, content: content }})
}});
const result = await response.json();
if (result.success) {{
input.value = '';
location.reload(); // 简单刷新页面
}} else {{
alert(result.error || '评论失败');
}}
}} catch (e) {{
console.error(e);
alert('网络错误');
}}
}}
</script>
</body>
</html>
'''
@app.route('/login', methods=['GET', 'POST'])
def login():
"""用户登录"""
if request.method == 'POST':
username = validate_input(request.form.get('username', ''))
password = request.form.get('password', '')
if not username or not password:
return '<script>alert("请输入用户名和密码"); history.back();</script>'
user = db_manager.authenticate_user(username, password)
if user:
session['user_id'] = user['id']
session['username'] = user['username']
return '<script>alert("登录成功"); window.location="/";</script>'
else:
return '<script>alert("用户名或密码错误"); history.back();</script>'
return '''
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>登录 - 古诗词鉴赏</title>
<style>
body { font-family: '楷体', serif; margin: 0; padding: 20px; background-color: #f8f9fa; display: flex; justify-content: center; align-items: center; height: 100vh; }
.login-container { background: white; padding: 30px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); width: 400px; }
.form-group { margin-bottom: 20px; }
label { display: block; margin-bottom: 5px; font-weight: bold; }
input[type="text"], input[type="password"] { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box; }
button { width: 100%; padding: 12px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; }
button:hover { background: #0056b3; }
.back-link { display: block; text-align: center; margin-top: 15px; color: #007bff; text-decoration: none; }
</style>
</head>
<body>
<div class="login-container">
<h2 style="text-align: center; margin-bottom: 30px;">用户登录</h2>
<form method="post">
<div class="form-group">
<label for="username">用户名</label>
<input type="text" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password">密码</label>
<input type="password" id="password" name="password" required>
</div>
<button type="submit">登录</button>
</form>
<a href="/" class="back-link">返回首页</a>
<p style="text-align: center; margin-top: 20px;">测试账号: admin / password123</p>
</div>
</body>
</html>
'''
@app.route('/api/comments', methods=['POST'])
@login_required
def api_add_comment():
"""API: 添加评论"""
data = request.json
poem_id = data.get('poem_id')
content = data.get('content')
if not poem_id or not content:
return jsonify({'success': False, 'error': '缺少参数'}), 400
success = db_manager.add_comment(session['user_id'], poem_id, content)
return jsonify({'success': success})
@app.route('/api/likes', methods=['POST'])
@login_required
def api_toggle_like():
"""API: 切换点赞"""
data = request.json
poem_id = data.get('poem_id')
if not poem_id:
return jsonify({'success': False, 'error': '缺少参数'}), 400
success = db_manager.toggle_like(session['user_id'], poem_id)
if success:
# 返回更新后的点赞状态和数量
is_liked = db_manager.get_like_status(session['user_id'], poem_id)
poem = db_manager.get_poem_by_id(poem_id)
return jsonify({
'success': True,
'is_liked': is_liked,
'like_count': poem['like_count']
})
return jsonify({'success': False, 'error': '操作失败'}), 500
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=int(os.environ.get('PORT', 5000)))
打分