表白墙应用后端实现
功能完整的表白墙后端应用,使用Python的Flask框架实现!
设计思路
这个表白墙应用将包含以下核心功能:
- 用户发布表白
- 查看所有表白(分页功能)
- 点赞功能
- 简单的管理员功能(删除表白)
- RESTful API接口
完整代码实现
from flask import Flask, jsonify, request, render_template, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
import os
app = Flask(__name__)
basedir = os.path.abspath(os.path.dirname(__file__))
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'confessions.db')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SECRET_KEY'] = 'your_secret_key_here' # 生产环境中应使用更安全的密钥
db = SQLAlchemy(app)
# 定义表白模型
class Confession(db.Model):
id = db.Column(db.Integer, primary_key=True)
sender = db.Column(db.String(50), nullable=False)
receiver = db.Column(db.String(50), nullable=False)
message = db.Column(db.Text, nullable=False)
likes = db.Column(db.Integer, default=0)
timestamp = db.Column(db.DateTime, default=datetime.utcnow)
is_approved = db.Column(db.Boolean, default=True) # 管理员审核
def to_dict(self):
return {
'id': self.id,
'sender': self.sender,
'receiver': self.receiver,
'message': self.message,
'likes': self.likes,
'timestamp': self.timestamp.strftime('%Y-%m-%d %H:%M:%S')
}
# 创建数据库表
@app.before_first_request
def create_tables():
db.create_all()
# 主页 - 显示所有表白
@app.route('/')
def index():
page = request.args.get('page', 1, type=int)
per_page = 10
confessions = Confession.query.filter_by(is_approved=True).order_by(Confession.timestamp.desc()).paginate(page=page, per_page=per_page)
return render_template('index.html', confessions=confessions)
# 发布表白页面
@app.route('/post', methods=['GET'])
def post_form():
return render_template('post.html')
# 处理表白提交
@app.route('/post', methods=['POST'])
def post_confession():
sender = request.form.get('sender', '匿名').strip()
receiver = request.form.get('receiver', '').strip()
message = request.form.get('message', '').strip()
if not receiver or not message:
return render_template('post.html', error='收件人和内容不能为空')
if len(message) > 500:
return render_template('post.html', error='内容过长,请限制在500字以内')
new_confession = Confession(
sender=sender or '匿名',
receiver=receiver,
message=message
)
db.session.add(new_confession)
db.session.commit()
return redirect(url_for('index'))
# 点赞功能
@app.route('/like/<int:confession_id>', methods=['POST'])
def like_confession(confession_id):
confession = Confession.query.get_or_404(confession_id)
confession.likes += 1
db.session.commit()
return jsonify({'likes': confession.likes})
# API接口 - 获取所有表白
@app.route('/api/confessions', methods=['GET'])
def get_confessions():
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
confessions = Confession.query.filter_by(is_approved=True).order_by(Confession.timestamp.desc()).paginate(page=page, per_page=per_page)
result = {
'confessions': [c.to_dict() for c in confessions.items],
'total': confessions.total,
'pages': confessions.pages,
'current_page': confessions.page
}
return jsonify(result)
# API接口 - 发布表白
@app.route('/api/confessions', methods=['POST'])
def create_confession():
data = request.get_json()
sender = data.get('sender', '匿名').strip()
receiver = data.get('receiver', '').strip()
message = data.get('message', '').strip()
if not receiver or not message:
return jsonify({'error': '收件人和内容不能为空'}), 400
if len(message) > 500:
return jsonify({'error': '内容过长,请限制在500字以内'}), 400
new_confession = Confession(
sender=sender or '匿名',
receiver=receiver,
message=message
)
db.session.add(new_confession)
db.session.commit()
return jsonify(new_confession.to_dict()), 201
# 管理员页面
@app.route('/admin')
def admin():
page = request.args.get('page', 1, type=int)
per_page = 20
confessions = Confession.query.order_by(Confession.timestamp.desc()).paginate(page=page, per_page=per_page)
return render_template('admin.html', confessions=confessions)
# 删除表白
@app.route('/admin/delete/<int:confession_id>', methods=['POST'])
def delete_confession(confession_id):
confession = Confession.query.get_or_404(confession_id)
db.session.delete(confession)
db.session.commit()
return redirect(url_for('admin'))
# 审核表白
@app.route('/admin/approve/<int:confession_id>', methods=['POST'])
def approve_confession(confession_id):
confession = Confession.query.get_or_404(confession_id)
confession.is_approved = not confession.is_approved
db.session.commit()
return redirect(url_for('admin'))
if __name__ == '__main__':
app.run(debug=True)
模板文件
创建 templates
文件夹,添加以下HTML文件:
index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>校园表白墙</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body {
background-color: #f8f9fa;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.header {
background: linear-gradient(135deg, #ff758c 0%, #ff7eb3 100%);
color: white;
padding: 2rem 0;
margin-bottom: 2rem;
text-align: center;
}
.confession-card {
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
margin-bottom: 1.5rem;
transition: transform 0.3s;
background-color: white;
}
.confession-card:hover {
transform: translateY(-5px);
box-shadow: 0 6px 12px rgba(0,0,0,0.15);
}
.card-header {
background-color: #fff0f3;
border-bottom: 1px solid rgba(0,0,0,0.05);
font-weight: 600;
}
.btn-like {
background-color: #ff6b6b;
border-color: #ff6b6b;
color: white;
}
.btn-like:hover {
background-color: #ff5252;
border-color: #ff5252;
}
.btn-post {
background: linear-gradient(135deg, #ff758c 0%, #ff7eb3 100%);
border: none;
padding: 10px 25px;
font-weight: 600;
margin-bottom: 2rem;
}
.pagination {
margin-top: 2rem;
justify-content: center;
}
.footer {
text-align: center;
padding: 2rem 0;
color: #6c757d;
margin-top: 2rem;
background-color: #f1f3f5;
}
</style>
</head>
<body>
<div class="header">
<div class="container">
<h1>❤️ 校园表白墙 ❤️</h1>
<p class="lead">说出你不敢当面说的话</p>
<a href="{{ url_for('post_form') }}" class="btn btn-light btn-lg mt-3">发布表白</a>
</div>
</div>
<div class="container">
{% for confession in confessions.items %}
<div class="confession-card card mb-4">
<div class="card-header">
<div class="d-flex justify-content-between">
<span>表白 #{{ confession.id }}</span>
<small class="text-muted">{{ confession.timestamp.strftime('%Y-%m-%d %H:%M') }}</small>
</div>
</div>
<div class="card-body">
<h5 class="card-title">To: {{ confession.receiver }}</h5>
<p class="card-text">{{ confession.message }}</p>
<div class="d-flex justify-content-between align-items-center">
<span>From: {{ confession.sender }}</span>
<div>
<button class="btn btn-sm btn-like" onclick="likeConfession({{ confession.id }}, this)">
❤️ 点赞 <span class="badge bg-light text-dark">{{ confession.likes }}</span>
</button>
</div>
</div>
</div>
</div>
{% else %}
<div class="text-center py-5">
<h4>还没有表白,快来发布第一条吧!</h4>
<a href="{{ url_for('post_form') }}" class="btn btn-post mt-3">发布表白</a>
</div>
{% endfor %}
<!-- 分页导航 -->
{% if confessions.pages > 1 %}
<nav aria-label="Page navigation">
<ul class="pagination">
{% if confessions.has_prev %}
<li class="page-item"><a class="page-link" href="{{ url_for('index', page=confessions.prev_num) }}">上一页</a></li>
{% endif %}
{% for page_num in confessions.iter_pages() %}
{% if page_num %}
<li class="page-item {% if page_num == confessions.page %}active{% endif %}">
<a class="page-link" href="{{ url_for('index', page=page_num) }}">{{ page_num }}</a>
</li>
{% else %}
<li class="page-item disabled"><span class="page-link">...</span></li>
{% endif %}
{% endfor %}
{% if confessions.has_next %}
<li class="page-item"><a class="page-link" href="{{ url_for('index', page=confessions.next_num) }}">下一页</a></li>
{% endif %}
</ul>
</nav>
{% endif %}
</div>
<div class="footer">
<div class="container">
<p>校园表白墙 © {{ now.year }} - 让爱传递</p>
<p><small>所有表白内容均来自网友投稿,不代表本站观点</small></p>
</div>
</div>
<script>
function likeConfession(confessionId, button) {
fetch(`/like/${confessionId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => {
const badge = button.querySelector('.badge');
badge.textContent = data.likes;
button.disabled = true;
button.classList.add('disabled');
});
}
</script>
</body>
</html>
post.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>发布表白 - 校园表白墙</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body {
background-color: #f8f9fa;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.header {
background: linear-gradient(135deg, #ff758c 0%, #ff7eb3 100%);
color: white;
padding: 2rem 0;
margin-bottom: 2rem;
text-align: center;
}
.form-container {
max-width: 600px;
margin: 0 auto;
background-color: white;
padding: 2rem;
border-radius: 10px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.btn-submit {
background: linear-gradient(135deg, #ff758c 0%, #ff7eb3 100%);
border: none;
padding: 10px 25px;
font-weight: 600;
width: 100%;
margin-top: 1rem;
}
.character-count {
text-align: right;
font-size: 0.9rem;
color: #6c757d;
}
</style>
</head>
<body>
<div class="header">
<div class="container">
<h1>发布表白</h1>
<p class="lead">勇敢说出你的心里话</p>
</div>
</div>
<div class="container">
<div class="form-container">
{% if error %}
<div class="alert alert-danger">
{{ error }}
</div>
{% endif %}
<form method="POST" action="{{ url_for('post_confession') }}">
<div class="mb-3">
<label for="sender" class="form-label">你的名字(可选)</label>
<input type="text" class="form-control" id="sender" name="sender" placeholder="匿名">
</div>
<div class="mb-3">
<label for="receiver" class="form-label">收件人 *</label>
<input type="text" class="form-control" id="receiver" name="receiver" required placeholder="你想表白的人">
</div>
<div class="mb-3">
<label for="message" class="form-label">表白内容 *</label>
<textarea class="form-control" id="message" name="message" rows="5" required placeholder="写下你想说的话..."></textarea>
<div class="character-count">
<span id="charCount">0</span>/500
</div>
</div>
<button type="submit" class="btn btn-submit">发布表白</button>
</form>
</div>
</div>
<script>
const messageTextarea = document.getElementById('message');
const charCount = document.getElementById('charCount');
messageTextarea.addEventListener('input', function() {
const currentLength = this.value.length;
charCount.textContent = currentLength;
if (currentLength > 500) {
charCount.style.color = 'red';
} else {
charCount.style.color = '#6c757d';
}
});
</script>
</body>
</html>
admin.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>表白墙管理后台</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
body {
background-color: #f8f9fa;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}
.header {
background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%);
color: white;
padding: 2rem 0;
margin-bottom: 2rem;
}
.admin-card {
border-radius: 10px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
margin-bottom: 1.5rem;
background-color: white;
}
.btn-delete {
background-color: #dc3545;
color: white;
}
.btn-approve {
background-color: #198754;
color: white;
}
.status-badge {
padding: 0.35em 0.65em;
border-radius: 50rem;
}
</style>
</head>
<body>
<div class="header">
<div class="container">
<h1>表白墙管理后台</h1>
<p class="lead">审核和管理表白内容</p>
</div>
</div>
<div class="container">
{% for confession in confessions.items %}
<div class="admin-card card mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<div>
<span>表白 #{{ confession.id }}</span>
<span class="badge {% if confession.is_approved %}bg-success{% else %}bg-warning{% endif %} status-badge ms-2">
{% if confession.is_approved %}已审核{% else %}待审核{% endif %}
</span>
</div>
<small class="text-muted">{{ confession.timestamp.strftime('%Y-%m-%d %H:%M') }}</small>
</div>
<div class="card-body">
<h5 class="card-title">To: {{ confession.receiver }}</h5>
<p class="card-text">{{ confession.message }}</p>
<div class="d-flex justify-content-between align-items-center">
<span>From: {{ confession.sender }}</span>
<span class="badge bg-primary">❤️ {{ confession.likes }} 赞</span>
</div>
</div>
<div class="card-footer bg-light d-flex justify-content-end">
<form method="POST" action="{{ url_for('approve_confession', confession_id=confession.id) }}" class="me-2">
<button type="submit" class="btn btn-sm btn-approve">
{% if confession.is_approved %}取消审核{% else %}通过审核{% endif %}
</button>
</form>
<form method="POST" action="{{ url_for('delete_confession', confession_id=confession.id) }}">
<button type="submit" class="btn btn-sm btn-delete">删除</button>
</form>
</div>
</div>
{% else %}
<div class="text-center py-5">
<h4>没有表白内容</h4>
</div>
{% endfor %}
<!-- 分页导航 -->
{% if confessions.pages > 1 %}
<nav aria-label="Page navigation">
<ul class="pagination justify-content-center">
{% if confessions.has_prev %}
<li class="page-item"><a class="page-link" href="{{ url_for('admin', page=confessions.prev_num) }}">上一页</a></li>
{% endif %}
{% for page_num in confessions.iter_pages() %}
{% if page_num %}
<li class="page-item {% if page_num == confessions.page %}active{% endif %}">
<a class="page-link" href="{{ url_for('admin', page=page_num) }}">{{ page_num }}</a>
</li>
{% else %}
<li class="page-item disabled"><span class="page-link">...</span></li>
{% endif %}
{% endfor %}
{% if confessions.has_next %}
<li class="page-item"><a class="page-link" href="{{ url_for('admin', page=confessions.next_num) }}">下一页</a></li>
{% endif %}
</ul>
</nav>
{% endif %}
</div>
</body>
</html>
部署指南
- 创建虚拟环境并安装依赖:
python -m venv venv
source venv/bin/activate # Linux/Mac
venv\Scripts\activate # Windows
pip install flask flask-sqlalchemy
- 创建数据库:
python
>>> from app import db
>>> db.create_all()
>>> exit()
- 运行应用:
flask run
- 访问网页:
- 主页:http://127.0.0.1:5000/
- 发布页面:http://127.0.0.1:5000/post
- 管理后台:http://127.0.0.1:5000/admin
功能特点
-
用户界面
- 美观的表白卡片展示
- 响应式设计,适配移动设备
- 分页功能
- 点赞功能(AJAX实现)
-
发布功能
- 表单验证
- 字符数限制
- 匿名发布选项
-
管理功能
- 审核表白内容
- 删除不当内容
- 分页浏览所有表白
-
API接口
- 获取表白列表(支持分页)
- 提交新表白
- 点赞功能