A sample class to clean the input into web pages

本文介绍了一个名为 CleanString 的公共密封类,该类包含一个静态方法 InputText,用于接收字符串输入并进行安全处理。处理过程包括去除空白字符、截断超出最大长度的部分、转义特殊字符以及替换单引号为空格。
    public sealed class CleanString {

        
public static string InputText(string inputString, int maxLength) {

            
            StringBuilder retVal 
= new StringBuilder();

            
// check incoming parameters for null or blank string
            if ((inputString != null&& (inputString != String.Empty)) {
                inputString 
= inputString.Trim();

                
//chop the string incase the client-side max length
                
//fields are bypassed to prevent buffer over-runs
                if (inputString.Length > maxLength)
                    inputString 
= inputString.Substring(0, maxLength);

                
//convert some harmful symbols incase the regular
                
//expression validators are changed
                for (int i = 0; i < inputString.Length; i++{
                    
switch (inputString[i]) {
                        
case '"':
                            retVal.Append(
"&quot;");
                            
break;
                        
case '<':
                            retVal.Append(
"&lt;");
                            
break;
                        
case '>':
                            retVal.Append(
"&gt;");
                            
break;
                        
default:
                            retVal.Append(inputString[i]);
                            
break;
                    }

                }


                
// Replace single quotes with white space
                retVal.Replace("'"" ");
            }


            
return retVal.ToString();
            
        }

    }

 使用:

 

                string itemId = Request["itemId"];

                
if (itemId != null){
                    
// Clean the input string
                    itemId = WebComponents.CleanString.InputText(itemId, 50);
                    myCart.Add(itemId);
                    cartController.StoreCart(myCart);
                    
                }

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))) 打分
12-15
from flask import Flask, render_template, request, jsonify, session, redirect, url_for, flash import re import random import json from datetime import datetime, timedelta import os import logging from functools import wraps from typing import List, Dict, Any, Optional import sqlite3 from contextlib import contextmanager import bleach # 用于HTML清理 from werkzeug.security import check_password_hash, generate_password_hash from flask_wtf import FlaskForm from wtforms import StringField, PasswordField, TextAreaField, SelectField from wtforms.validators import DataRequired, Length, Optional as WTFOptional, Regexp import hashlib import secrets from functools import lru_cache app = Flask(__name__) app.secret_key = secrets.token_hex(32) # 使用安全的随机密钥 # 配置日志 logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) class SecureForm(FlaskForm): """安全表单基类""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def validate(self): # 验证CSRF令牌 if not super().validate(): return False return True class DatabaseManager: """数据库管理器,使用SQLite优化性能""" 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, email TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ''') # 创建收藏表 conn.execute(''' CREATE TABLE IF NOT EXISTS favorites ( 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 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 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_poems_tags ON poems(tags)') conn.execute('CREATE INDEX IF NOT EXISTS idx_favorites_user_poem ON favorites(user_id, poem_id)') conn.commit() # 检查是否有数据 cursor = conn.execute('SELECT COUNT(*) FROM poems') count = cursor.fetchone()[0] if count == 0: # 如果数据库为空,从文件加载数据 self.load_poems_from_file() @contextmanager def get_db_connection(self): """获取数据库连接的上下文管理器""" conn = sqlite3.connect(self.db_path) conn.row_factory = sqlite3.Row # 使结果可以像字典一样访问 conn.execute('PRAGMA foreign_keys = ON') # 启用外键约束 try: yield conn finally: conn.close() def load_poems_from_file(self): """从文件加载诗词到数据库""" poems = [] try: with open('唐诗三百首(cn_en).txt', 'r', encoding='utf-8') as f: content = f.read() # 使用正则表达式分割每首诗 poem_blocks = re.split(r'\n\n+', content) for block in poem_blocks: if block.strip(): lines = block.strip().split('\n') if len(lines) >= 4: # 提取诗名和作者 title_line = lines[0] author_line = lines[1] # 提取中文诗句 chinese_poem = [] for i in range(2, len(lines)): line = lines[i].strip() if line and not line.islower() and not any(c in line for c in ['five', 'seven', 'character', 'ancient', 'verse', 'folk-song', 'regular', 'quatrain']): chinese_poem.append(line) if len(chinese_poem) >= 2: poem_text = '\n'.join(chinese_poem) poem_type = self.extract_poem_type(title_line) tags = self.extract_tags(title_line) poems.append({ 'title': title_line, 'author': author_line, 'content': poem_text, 'type': poem_type, 'tags': ','.join(tags) }) except FileNotFoundError: logger.warning("诗词文件未找到,使用示例数据") poems = self.create_sample_data() except Exception as e: logger.error(f"加载诗词文件时出错: {str(e)}") poems = self.create_sample_data() # 插入数据库 with self.get_db_connection() as conn: conn.executemany(''' INSERT INTO poems (title, author, content, type, tags) VALUES (?, ?, ?, ?, ?) ''', [(p['title'], p['author'], p['content'], p['type'], p['tags']) for p in poems]) conn.commit() def extract_poem_type(self, title: str) -> str: """根据标题判断诗歌类型""" if '五言' in title: if '绝句' in title: return '五言绝句' elif '律诗' in title: return '五言律诗' else: return '五言古诗' elif '七言' in title: if '绝句' in title: return '七言绝句' elif '律诗' in title: return '七言律诗' else: return '七言古诗' elif '乐府' in title: return '乐府' else: return '古诗' def extract_tags(self, title: str) -> List[str]: """从标题中提取关键词标签""" tags = [] if '月' in title: tags.append('月亮') if '春' in title or '夏' in title or '秋' in title or '冬' in title: tags.append('季节') if '山' in title or '水' in title or '江' in title or '河' in title: tags.append('山水') if '思' in title or '念' in title or '怀' in title: tags.append('思念') if '别' in title or '送' in title: tags.append('离别') return tags if tags else ['其他'] def create_sample_data(self) -> List[Dict[str, Any]]: """创建示例数据""" return [ { 'title': '静夜思', 'author': '李白', 'content': '床前明月光,疑是地上霜。\n举头望明月,低头思故乡。', 'type': '五言绝句', 'tags': '月亮,思乡,夜晚' }, { 'title': '春晓', 'author': '孟浩然', 'content': '春眠不觉晓,处处闻啼鸟。\n夜来风雨声,花落知多少。', 'type': '五言绝句', 'tags': '春天,自然,早晨' }, { 'title': '登鹳雀楼', 'author': '王之涣', 'content': '白日依山尽,黄河入海流。\n欲穷千里目,更上一层楼。', 'type': '五言绝句', 'tags': '励志,哲理,景色' }, { 'title': '望庐山瀑布', 'author': '李白', 'content': '日照香炉生紫烟,遥看瀑布挂前川。\n飞流直下三千尺,疑是银河落九天。', 'type': '七言绝句', 'tags': '瀑布,景色,夸张' }, { 'title': '黄鹤楼送孟浩然之广陵', 'author': '李白', 'content': '故人西辞黄鹤楼,烟花三月下扬州。\n孤帆远影碧空尽,唯见长江天际流。', 'type': '七言绝句', 'tags': '送别,友情,景色' } ] def get_all_poems(self, page: int = 1, per_page: int = 12) -> tuple: """获取所有诗词(分页)""" # 验证参数 if page < 1 or per_page < 1: raise ValueError("分页参数必须大于0") offset = (page - 1) * per_page with self.get_db_connection() as conn: cursor = conn.execute(''' SELECT * FROM poems 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) # 验证参数 if page < 1 or per_page < 1: raise ValueError("分页参数必须大于0") # 清理查询字符串以防止SQL注入 clean_query = self.sanitize_input(query) offset = (page - 1) * per_page search_term = f'%{clean_query}%' with self.get_db_connection() as conn: cursor = conn.execute(''' SELECT * FROM poems 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_poem_by_id(self, poem_id: int) -> Optional[Dict[str, Any]]: """根据ID获取诗词""" # 验证ID类型和范围 if not isinstance(poem_id, int) or poem_id < 1: raise ValueError("无效的诗词ID") with self.get_db_connection() as conn: cursor = conn.execute('SELECT * FROM poems WHERE id = ?', (poem_id,)) row = cursor.fetchone() return dict(row) if row else None def get_poems_by_author(self, author: str, page: int = 1, per_page: int = 12) -> tuple: """根据作者获取诗词(分页)""" # 验证参数 if page < 1 or per_page < 1: raise ValueError("分页参数必须大于0") # 清理输入以防止SQL注入 clean_author = self.sanitize_input(author) offset = (page - 1) * per_page with self.get_db_connection() as conn: cursor = conn.execute(''' SELECT * FROM poems WHERE author LIKE ? ORDER BY created_at DESC LIMIT ? OFFSET ? ''', (f'%{clean_author}%', per_page, offset)) poems = [dict(row) for row in cursor.fetchall()] # 获取总数 cursor = conn.execute('SELECT COUNT(*) FROM poems WHERE author LIKE ?', (f'%{clean_author}%',)) total_count = cursor.fetchone()[0] total_pages = (total_count + per_page - 1) // per_page return poems, total_pages def get_poems_by_type(self, poem_type: str, page: int = 1, per_page: int = 12) -> tuple: """根据类型获取诗词(分页)""" # 验证参数 if page < 1 or per_page < 1: raise ValueError("分页参数必须大于0") # 清理输入以防止SQL注入 clean_type = self.sanitize_input(poem_type) offset = (page - 1) * per_page with self.get_db_connection() as conn: cursor = conn.execute(''' SELECT * FROM poems WHERE type = ? ORDER BY created_at DESC LIMIT ? OFFSET ? ''', (clean_type, per_page, offset)) poems = [dict(row) for row in cursor.fetchall()] # 获取总数 cursor = conn.execute('SELECT COUNT(*) FROM poems WHERE type = ?', (clean_type,)) total_count = cursor.fetchone()[0] total_pages = (total_count + per_page - 1) // per_page return poems, total_pages def get_poems_by_tag(self, tag: str, page: int = 1, per_page: int = 12) -> tuple: """根据标签获取诗词(分页)""" # 验证参数 if page < 1 or per_page < 1: raise ValueError("分页参数必须大于0") # 清理输入以防止SQL注入 clean_tag = self.sanitize_input(tag) offset = (page - 1) * per_page with self.get_db_connection() as conn: cursor = conn.execute(''' SELECT * FROM poems WHERE tags LIKE ? ORDER BY created_at DESC LIMIT ? OFFSET ? ''', (f'%{clean_tag}%', per_page, offset)) poems = [dict(row) for row in cursor.fetchall()] # 获取总数 cursor = conn.execute('SELECT COUNT(*) FROM poems WHERE tags LIKE ?', (f'%{clean_tag}%',)) 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 sanitize_input(self, input_str: str) -> str: """清理输入,防止SQL注入""" # 使用正则表达式移除可能的SQL注入字符 if input_str is None: return "" # 移除危险字符 sanitized = re.sub(r'[;\'"\\]', '', input_str) # 限制长度以防止过长输入 return sanitized[:1000].strip() class SessionManager: """会话管理器,负责用户设置的管理""" @staticmethod def init_session(): """初始化会话""" defaults = { 'font_family': '楷体', 'font_size': '16px', 'background_color': '#f8f9fa', 'favorites': [], 'recent_views': [], 'view_history': [], 'last_activity': datetime.now() } for key, default_value in defaults.items(): if key not in session: session[key] = default_value @staticmethod def update_settings(data: Dict[str, Any]) -> bool: """更新用户设置""" try: if 'font_family' in data: # 验证字体名称 allowed_fonts = ['楷体', '宋体', '黑体', '仿宋', '微软雅黑', 'Arial', 'Times New Roman'] if data['font_family'] in allowed_fonts: session['font_family'] = data['font_family'] if 'font_size' in data: # 验证字体大小格式 size_pattern = r'^\d+px$' if re.match(size_pattern, data['font_size']): session['font_size'] = data['font_size'] if 'background_color' in data: # 验证颜色格式 color_pattern = r'^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$' if re.match(color_pattern, data['background_color']): session['background_color'] = data['background_color'] session['last_activity'] = datetime.now() return True except Exception as e: logger.error(f"更新设置时出错: {str(e)}") return False @staticmethod def toggle_favorite(user_id: int, poem_id: int) -> bool: """切换收藏状态""" if not isinstance(user_id, int) or not isinstance(poem_id, int) or user_id < 1 or poem_id < 1: raise ValueError("无效的用户ID或诗词ID") db_manager = DatabaseManager() with db_manager.get_db_connection() as conn: try: cursor = conn.execute('SELECT id FROM favorites WHERE user_id = ? AND poem_id = ?', (user_id, poem_id)) existing = cursor.fetchone() if existing: # 删除收藏 conn.execute('DELETE FROM favorites WHERE user_id = ? AND poem_id = ?', (user_id, poem_id)) else: # 添加收藏 conn.execute('INSERT INTO favorites (user_id, poem_id) VALUES (?, ?)', (user_id, poem_id)) conn.commit() return True except sqlite3.IntegrityError: return False # 如果违反唯一约束则返回False @staticmethod def add_to_recent_views(poem_id: int): """添加到最近浏览""" if not isinstance(poem_id, int) or poem_id < 1: raise ValueError("无效的诗词ID") recent = session.get('recent_views', []) if poem_id not in recent: recent.insert(0, poem_id) if len(recent) > 10: # 只保留最近10个 recent = recent[:10] session['recent_views'] = recent def error_handler(f): """错误处理装饰器""" @wraps(f) def decorated_function(*args, **kwargs): try: # 检查会话是否过期 last_activity = session.get('last_activity') if last_activity: if datetime.now() - last_activity > timedelta(hours=24): # 24小时后会话过期 session.clear() return f(*args, **kwargs) except ValueError as e: logger.error(f"参数错误在函数 {f.__name__}: {str(e)}") return render_template('error.html', error="请求参数错误"), 400 except Exception as e: logger.error(f"函数 {f.__name__} 发生错误: {str(e)}") return render_template('error.html', error="页面加载失败"), 500 return decorated_function def validate_poem_id(f): """验证诗词ID装饰器""" @wraps(f) def decorated_function(poem_id, *args, **kwargs): if not isinstance(poem_id, int) or poem_id < 1: return render_template('error.html', error="无效的诗词ID"), 400 db_manager = DatabaseManager() try: poem = db_manager.get_poem_by_id(poem_id) if not poem: return render_template('error.html', error="诗词不存在"), 404 except ValueError: return render_template('error.html', error="无效的诗词ID"), 400 return f(poem_id, *args, **kwargs) return decorated_function def sanitize_input(input_str: str) -> str: """清理用户输入,防止XSS攻击""" if input_str is None: return "" # 使用bleach库清理HTML标签 clean_input = bleach.clean(input_str, strip=True) return clean_input.strip()[:1000].strip() # 限制长度 @lru_cache(maxsize=128) def get_cached_poems(page: int, per_page: int) -> tuple: """缓存诗词数据以提高性能""" db_manager = DatabaseManager() return db_manager.get_all_poems(page, per_page) # 初始化数据库管理器 db_manager = DatabaseManager() @app.route('/') @error_handler def index(): """首页""" SessionManager.init_session() page = request.args.get('page', 1, type=int) per_page = 12 # 使用缓存提高性能 poems, total_pages = get_cached_poems(page, per_page) return render_template('index.html', poems=poems, page=page, total_pages=total_pages, all_authors=db_manager.get_all_authors(), all_types=db_manager.get_all_types(), all_tags=db_manager.get_all_tags()) @app.route('/search') @error_handler def search(): """搜索页面""" query = request.args.get('q', '') page = request.args.get('page', 1, type=int) per_page = 12 if query: # 清理搜索查询 clean_query = sanitize_input(query) poems, total_pages = db_manager.search_poems(clean_query, page, per_page) else: poems, total_pages = db_manager.get_all_poems(page, per_page) return render_template('search.html', poems=poems, query=query, page=page, total_pages=total_pages) @app.route('/author/<author>') @error_handler def by_author(author): """按作者查看""" # 清理作者名 clean_author = sanitize_input(author) page = request.args.get('page', 1, type=int) per_page = 12 poems, total_pages = db_manager.get_poems_by_author(clean_author, page, per_page) return render_template('author.html', poems=poems, author=author, page=page, total_pages=total_pages) @app.route('/type/<poem_type>') @error_handler def by_type(poem_type): """按类型查看""" # 清理诗歌类型 clean_type = sanitize_input(poem_type) page = request.args.get('page', 1, type=int) per_page = 12 poems, total_pages = db_manager.get_poems_by_type(clean_type, page, per_page) return render_template('type.html', poems=poems, poem_type=poem_type, page=page, total_pages=total_pages) @app.route('/tag/<tag>') @error_handler def by_tag(tag): """按标签查看""" # 清理标签 clean_tag = sanitize_input(tag) page = request.args.get('page', 1, type=int) per_page = 12 poems, total_pages = db_manager.get_poems_by_tag(clean_tag, page, per_page) return render_template('tag.html', poems=poems, tag=tag, page=page, total_pages=total_pages) @app.route('/poem/<int:poem_id>') @validate_poem_id @error_handler def poem_detail(poem_id): """诗词详情页面""" poem = db_manager.get_poem_by_id(poem_id) if not poem: return render_template('error.html', error="诗词不存在"), 404 # 获取上一首和下一首 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 # 添加到最近浏览 SessionManager.add_to_recent_views(poem_id) return render_template('poem.html', poem=poem, poem_id=poem_id, prev_id=prev_id, next_id=next_id) @app.route('/random') @error_handler def random_poem(): """随机诗词""" with db_manager.get_db_connection() as conn: cursor = conn.execute('SELECT id FROM poems ORDER BY RANDOM() LIMIT 1') row = cursor.fetchone() if not row: return render_template('error.html', error="暂无诗词数据"), 500 poem_id = row['id'] poem = db_manager.get_poem_by_id(poem_id) SessionManager.add_to_recent_views(poem_id) # 获取上一首和下一首 with db_manager.get_db_connection() as conn2: cursor = conn2.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 render_template('poem.html', poem=poem, poem_id=poem_id, prev_id=prev_id, next_id=next_id) @app.route('/api/settings', methods=['POST']) @error_handler def update_settings(): """更新用户设置""" data = request.json if not data: return jsonify({'success': False, 'error': '无效的数据'}), 400 # 清理输入数据 if 'font_family' in data: data['font_family'] = sanitize_input(data['font_family']) if 'font_size' in data: data['font_size'] = sanitize_input(data['font_size']) if 'background_color' in data: data['background_color'] = sanitize_input(data['background_color']) success = SessionManager.update_settings(data) if success: return jsonify({'success': True}) else: return jsonify({'success': False, 'error': '设置更新失败'}), 500 @app.route('/api/favorites', methods=['POST']) @error_handler def toggle_favorite(): """切换收藏状态""" data = request.json poem_id = data.get('poem_id') if data else None if poem_id is None: return jsonify({'success': False, 'error': '无效的诗词ID'}), 400 # 验证ID是否存在 poem = db_manager.get_poem_by_id(poem_id) if not poem: return jsonify({'success': False, 'error': '诗词ID不存在'}), 400 # 检查用户是否已登录 user_id = session.get('user_id') if not user_id: return jsonify({'success': False, 'error': '请先登录'}), 401 try: success = SessionManager.toggle_favorite(user_id, poem_id) if success: return jsonify({'success': True}) else: return jsonify({'success': False, 'error': '操作失败'}), 500 except ValueError as e: return jsonify({'success': False, 'error': str(e)}), 400 @app.errorhandler(404) def not_found(error): return render_template('error.html', error="页面未找到"), 404 @app.errorhandler(500) def internal_error(error): return render_template('error.html', error="服务器内部错误"), 500 @app.errorhandler(400) def bad_request(error): return render_template('error.html', error="请求参数错误"), 400 @app.errorhandler(401) def unauthorized(error): return render_template('error.html', error="未授权访问"), 401 if __name__ == '__main__': app.run(debug=True, host='0.0.0.0', port=int(os.environ.get('PORT', 5000))) 打分
最新发布
12-15
from flask import Flask, render_template, request, jsonify, session, redirect, url_for, flash import re import random import json from datetime import datetime import os import logging from functools import wraps from typing import List, Dict, Any, Optional import sqlite3 from contextlib import contextmanager import bleach # 用于HTML清理 from werkzeug.security import check_password_hash, generate_password_hash from flask_wtf import FlaskForm from wtforms import StringField, PasswordField, TextAreaField, SelectField from wtforms.validators import DataRequired, Length, Optional as WTFOptional import hashlib app = Flask(__name__) app.secret_key = 'your-very-secure-secret-key-here' # 生产环境中应使用更复杂的密钥 # 配置日志 logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) class SecureForm(FlaskForm): """安全表单基类""" def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) def validate(self): # 验证CSRF令牌 if not super().validate(): return False return True class DatabaseManager: """数据库管理器,使用SQLite优化性能""" 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, email TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ''') # 创建收藏表 conn.execute(''' CREATE TABLE IF NOT EXISTS favorites ( 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 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.commit() # 检查是否有数据 cursor = conn.execute('SELECT COUNT(*) FROM poems') count = cursor.fetchone()[0] if count == 0: # 如果数据库为空,从文件加载数据 self.load_poems_from_file() @contextmanager def get_db_connection(self): """获取数据库连接的上下文管理器""" conn = sqlite3.connect(self.db_path) conn.row_factory = sqlite3.Row # 使结果可以像字典一样访问 conn.execute('PRAGMA foreign_keys = ON') # 启用外键约束 try: yield conn finally: conn.close() def load_poems_from_file(self): """从文件加载诗词到数据库""" poems = [] try: with open('唐诗三百首(cn_en).txt', 'r', encoding='utf-8') as f: content = f.read() # 使用正则表达式分割每首诗 poem_blocks = re.split(r'\n\n+', content) for block in poem_blocks: if block.strip(): lines = block.strip().split('\n') if len(lines) >= 4: # 提取诗名和作者 title_line = lines[0] author_line = lines[1] # 提取中文诗句 chinese_poem = [] for i in range(2, len(lines)): line = lines[i].strip() if line and not line.islower() and not any(c in line for c in ['five', 'seven', 'character', 'ancient', 'verse', 'folk-song', 'regular', 'quatrain']): chinese_poem.append(line) if len(chinese_poem) >= 2: poem_text = '\n'.join(chinese_poem) poem_type = self.extract_poem_type(title_line) tags = self.extract_tags(title_line) poems.append({ 'title': title_line, 'author': author_line, 'content': poem_text, 'type': poem_type, 'tags': ','.join(tags) }) except FileNotFoundError: logger.warning("诗词文件未找到,使用示例数据") poems = self.create_sample_data() except Exception as e: logger.error(f"加载诗词文件时出错: {str(e)}") poems = self.create_sample_data() # 插入数据库 with self.get_db_connection() as conn: conn.executemany(''' INSERT INTO poems (title, author, content, type, tags) VALUES (?, ?, ?, ?, ?) ''', [(p['title'], p['author'], p['content'], p['type'], p['tags']) for p in poems]) conn.commit() def extract_poem_type(self, title: str) -> str: """根据标题判断诗歌类型""" if '五言' in title: if '绝句' in title: return '五言绝句' elif '律诗' in title: return '五言律诗' else: return '五言古诗' elif '七言' in title: if '绝句' in title: return '七言绝句' elif '律诗' in title: return '七言律诗' else: return '七言古诗' elif '乐府' in title: return '乐府' else: return '古诗' def extract_tags(self, title: str) -> List[str]: """从标题中提取关键词标签""" tags = [] if '月' in title: tags.append('月亮') if '春' in title or '夏' in title or '秋' in title or '冬' in title: tags.append('季节') if '山' in title or '水' in title or '江' in title or '河' in title: tags.append('山水') if '思' in title or '念' in title or '怀' in title: tags.append('思念') if '别' in title or '送' in title: tags.append('离别') return tags if tags else ['其他'] def create_sample_data(self) -> List[Dict[str, Any]]: """创建示例数据""" return [ { 'title': '静夜思', 'author': '李白', 'content': '床前明月光,疑是地上霜。\n举头望明月,低头思故乡。', 'type': '五言绝句', 'tags': '月亮,思乡,夜晚' }, { 'title': '春晓', 'author': '孟浩然', 'content': '春眠不觉晓,处处闻啼鸟。\n夜来风雨声,花落知多少。', 'type': '五言绝句', 'tags': '春天,自然,早晨' }, { 'title': '登鹳雀楼', 'author': '王之涣', 'content': '白日依山尽,黄河入海流。\n欲穷千里目,更上一层楼。', 'type': '五言绝句', 'tags': '励志,哲理,景色' }, { 'title': '望庐山瀑布', 'author': '李白', 'content': '日照香炉生紫烟,遥看瀑布挂前川。\n飞流直下三千尺,疑是银河落九天。', 'type': '七言绝句', 'tags': '瀑布,景色,夸张' }, { 'title': '黄鹤楼送孟浩然之广陵', 'author': '李白', 'content': '故人西辞黄鹤楼,烟花三月下扬州。\n孤帆远影碧空尽,唯见长江天际流。', 'type': '七言绝句', 'tags': '送别,友情,景色' } ] 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 * FROM poems 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'%{self.sanitize_input(query)}%' with self.get_db_connection() as conn: cursor = conn.execute(''' SELECT * FROM poems 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_poem_by_id(self, poem_id: int) -> Optional[Dict[str, Any]]: """根据ID获取诗词""" with self.get_db_connection() as conn: cursor = conn.execute('SELECT * FROM poems WHERE id = ?', (poem_id,)) row = cursor.fetchone() return dict(row) if row else None def get_poems_by_author(self, author: str, page: int = 1, per_page: int = 12) -> tuple: """根据作者获取诗词(分页)""" offset = (page - 1) * per_page with self.get_db_connection() as conn: cursor = conn.execute(''' SELECT * FROM poems WHERE author LIKE ? ORDER BY created_at DESC LIMIT ? OFFSET ? ''', (f'%{self.sanitize_input(author)}%', per_page, offset)) poems = [dict(row) for row in cursor.fetchall()] # 获取总数 cursor = conn.execute('SELECT COUNT(*) FROM poems WHERE author LIKE ?', (f'%{self.sanitize_input(author)}%',)) total_count = cursor.fetchone()[0] total_pages = (total_count + per_page - 1) // per_page return poems, total_pages def get_poems_by_type(self, poem_type: str, page: int = 1, per_page: int = 12) -> tuple: """根据类型获取诗词(分页)""" offset = (page - 1) * per_page with self.get_db_connection() as conn: cursor = conn.execute(''' SELECT * FROM poems WHERE type = ? ORDER BY created_at DESC LIMIT ? OFFSET ? ''', (poem_type, per_page, offset)) poems = [dict(row) for row in cursor.fetchall()] # 获取总数 cursor = conn.execute('SELECT COUNT(*) FROM poems WHERE type = ?', (poem_type,)) total_count = cursor.fetchone()[0] total_pages = (total_count + per_page - 1) // per_page return poems, total_pages def get_poems_by_tag(self, tag: str, page: int = 1, per_page: int = 12) -> tuple: """根据标签获取诗词(分页)""" offset = (page - 1) * per_page with self.get_db_connection() as conn: cursor = conn.execute(''' SELECT * FROM poems WHERE tags LIKE ? ORDER BY created_at DESC LIMIT ? OFFSET ? ''', (f'%{self.sanitize_input(tag)}%', per_page, offset)) poems = [dict(row) for row in cursor.fetchall()] # 获取总数 cursor = conn.execute('SELECT COUNT(*) FROM poems WHERE tags LIKE ?', (f'%{self.sanitize_input(tag)}%',)) 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 sanitize_input(self, input_str: str) -> str: """清理输入,防止SQL注入""" # 使用正则表达式移除可能的SQL注入字符 sanitized = re.sub(r'[;\'"\\]', '', input_str) return sanitized.strip() class SessionManager: """会话管理器,负责用户设置的管理""" @staticmethod def init_session(): """初始化会话""" defaults = { 'font_family': '楷体', 'font_size': '16px', 'background_color': '#f8f9fa', 'favorites': [], 'recent_views': [], 'view_history': [] } for key, default_value in defaults.items(): if key not in session: session[key] = default_value @staticmethod def update_settings(data: Dict[str, Any]) -> bool: """更新用户设置""" try: if 'font_family' in data: # 验证字体名称 allowed_fonts = ['楷体', '宋体', '黑体', '仿宋', '微软雅黑', 'Arial', 'Times New Roman'] if data['font_family'] in allowed_fonts: session['font_family'] = data['font_family'] if 'font_size' in data: # 验证字体大小格式 size_pattern = r'^\d+px$' if re.match(size_pattern, data['font_size']): session['font_size'] = data['font_size'] if 'background_color' in data: # 验证颜色格式 color_pattern = r'^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$' if re.match(color_pattern, data['background_color']): session['background_color'] = data['background_color'] return True except Exception as e: logger.error(f"更新设置时出错: {str(e)}") return False @staticmethod def toggle_favorite(user_id: int, poem_id: int) -> bool: """切换收藏状态""" db_manager = DatabaseManager() with db_manager.get_db_connection() as conn: try: cursor = conn.execute('SELECT id FROM favorites WHERE user_id = ? AND poem_id = ?', (user_id, poem_id)) existing = cursor.fetchone() if existing: # 删除收藏 conn.execute('DELETE FROM favorites WHERE user_id = ? AND poem_id = ?', (user_id, poem_id)) else: # 添加收藏 conn.execute('INSERT INTO favorites (user_id, poem_id) VALUES (?, ?)', (user_id, poem_id)) conn.commit() return True except sqlite3.IntegrityError: return False # 如果违反唯一约束则返回False @staticmethod def add_to_recent_views(poem_id: int): """添加到最近浏览""" recent = session.get('recent_views', []) if poem_id not in recent: recent.insert(0, poem_id) if len(recent) > 10: # 只保留最近10个 recent = recent[:10] session['recent_views'] = recent def error_handler(f): """错误处理装饰器""" @wraps(f) def decorated_function(*args, **kwargs): try: return f(*args, **kwargs) except Exception as e: logger.error(f"函数 {f.__name__} 发生错误: {str(e)}") return render_template('error.html', error="页面加载失败"), 500 return decorated_function def validate_poem_id(f): """验证诗词ID装饰器""" @wraps(f) def decorated_function(poem_id, *args, **kwargs): if not isinstance(poem_id, int) or poem_id < 1: return render_template('error.html', error="无效的诗词ID"), 400 db_manager = DatabaseManager() poem = db_manager.get_poem_by_id(poem_id) if not poem: return render_template('error.html', error="诗词不存在"), 404 return f(poem_id, *args, **kwargs) return decorated_function def sanitize_input(input_str: str) -> str: """清理用户输入,防止XSS攻击""" # 使用bleach库清理HTML标签 clean_input = bleach.clean(input_str, strip=True) return clean_input.strip() # 初始化数据库管理器 db_manager = DatabaseManager() @app.route('/') @error_handler def index(): """首页""" SessionManager.init_session() page = request.args.get('page', 1, type=int) per_page = 12 poems, total_pages = db_manager.get_all_poems(page, per_page) return render_template('index.html', poems=poems, page=page, total_pages=total_pages, all_authors=db_manager.get_all_authors(), all_types=db_manager.get_all_types(), all_tags=db_manager.get_all_tags()) @app.route('/search') @error_handler def search(): """搜索页面""" query = request.args.get('q', '') page = request.args.get('page', 1, type=int) per_page = 12 if query: # 清理搜索查询 clean_query = sanitize_input(query) poems, total_pages = db_manager.search_poems(clean_query, page, per_page) else: poems, total_pages = db_manager.get_all_poems(page, per_page) return render_template('search.html', poems=poems, query=query, page=page, total_pages=total_pages) @app.route('/author/<author>') @error_handler def by_author(author): """按作者查看""" # 清理作者名 clean_author = sanitize_input(author) page = request.args.get('page', 1, type=int) per_page = 12 poems, total_pages = db_manager.get_poems_by_author(clean_author, page, per_page) return render_template('author.html', poems=poems, author=author, page=page, total_pages=total_pages) @app.route('/type/<poem_type>') @error_handler def by_type(poem_type): """按类型查看""" # 清理诗歌类型 clean_type = sanitize_input(poem_type) page = request.args.get('page', 1, type=int) per_page = 12 poems, total_pages = db_manager.get_poems_by_type(clean_type, page, per_page) return render_template('type.html', poems=poems, poem_type=poem_type, page=page, total_pages=total_pages) @app.route('/tag/<tag>') @error_handler def by_tag(tag): """按标签查看""" # 清理标签 clean_tag = sanitize_input(tag) page = request.args.get('page', 1, type=int) per_page = 12 poems, total_pages = db_manager.get_poems_by_tag(clean_tag, page, per_page) return render_template('tag.html', poems=poems, tag=tag, page=page, total_pages=total_pages) @app.route('/poem/<int:poem_id>') @validate_poem_id @error_handler def poem_detail(poem_id): """诗词详情页面""" poem = db_manager.get_poem_by_id(poem_id) if not poem: return render_template('error.html', error="诗词不存在"), 404 # 获取上一首和下一首 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 # 添加到最近浏览 SessionManager.add_to_recent_views(poem_id) return render_template('poem.html', poem=poem, poem_id=poem_id, prev_id=prev_id, next_id=next_id) @app.route('/random') @error_handler def random_poem(): """随机诗词""" with db_manager.get_db_connection() as conn: cursor = conn.execute('SELECT id FROM poems ORDER BY RANDOM() LIMIT 1') row = cursor.fetchone() if not row: return render_template('error.html', error="暂无诗词数据"), 500 poem_id = row['id'] poem = db_manager.get_poem_by_id(poem_id) SessionManager.add_to_recent_views(poem_id) # 获取上一首和下一首 with db_manager.get_db_connection() as conn2: cursor = conn2.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 render_template('poem.html', poem=poem, poem_id=poem_id, prev_id=prev_id, next_id=next_id) @app.route('/api/settings', methods=['POST']) @error_handler def update_settings(): """更新用户设置""" data = request.json if not data: return jsonify({'success': False, 'error': '无效的数据'}), 400 # 清理输入数据 if 'font_family' in data: data['font_family'] = sanitize_input(data['font_family']) if 'font_size' in data: data['font_size'] = sanitize_input(data['font_size']) if 'background_color' in data: data['background_color'] = sanitize_input(data['background_color']) success = SessionManager.update_settings(data) if success: return jsonify({'success': True}) else: return jsonify({'success': False, 'error': '设置更新失败'}), 500 @app.route('/api/favorites', methods=['POST']) @error_handler def toggle_favorite(): """切换收藏状态""" data = request.json poem_id = data.get('poem_id') if data else None if poem_id is None: return jsonify({'success': False, 'error': '无效的诗词ID'}), 400 # 验证ID是否存在 poem = db_manager.get_poem_by_id(poem_id) if not poem: return jsonify({'success': False, 'error': '诗词ID不存在'}), 400 # 检查用户是否已登录 user_id = session.get('user_id') if not user_id: return jsonify({'success': False, 'error': '请先登录'}), 401 success = SessionManager.toggle_favorite(user_id, poem_id) if success: return jsonify({'success': True}) else: return jsonify({'success': False, 'error': '操作失败'}), 500 @app.errorhandler(404) def not_found(error): return render_template('error.html', error="页面未找到"), 404 @app.errorhandler(500) def internal_error(error): return render_template('error.html', error="服务器内部错误"), 500 @app.errorhandler(400) def bad_request(error): return render_template('error.html', error="请求参数错误"), 400 @app.errorhandler(401) def unauthorized(error): return render_template('error.html', error="未授权访问"), 401 if __name__ == '__main__': app.run(debug=True, host='0.0.0.0', port=int(os.environ.get('PORT', 5000))) 评分
12-15
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值