搭建一个博客并实现文章的发送功能,涉及到前后端开发和数据库操作。以下是一个简单的示例,使用Python的Flask框架作为后端,HTML和JavaScript作为前端,SQLite作为数据库。
1. 项目结构
my_blog/
│
├── app.py
├── templates/
│ ├── index.html
│ ├── profile.html
│ └── base.html
├── static/
│ ├── style.css
│ └── script.js
└── blog.db
2. 后端代码 (app.py
)
以下数据库使用的是sqlite3(后文介绍MySQL数据库和数组的实现):
from flask import Flask, render_template, request, redirect, url_for, session
import sqlite3
import secrets
app = Flask(__name__)
app.secret_key = secrets.token_hex(16) # 生成一个 32 字节的随机十六进制字符串
# 初始化数据库
def init_db():
db = sqlite3.connect('blog.db')
cursor = db.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL,
password TEXT NOT NULL
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
title TEXT NOT NULL,
content TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id)
)
''')
db.commit()
db.close()
init_db()
@app.route('/')
def index():
db = sqlite3.connect('blog.db')
cursor = db.cursor()
cursor.execute('SELECT * FROM posts')
posts = cursor.fetchall()
db.close()
return render_template('index.html', posts=posts)
@app.route('/profile', methods=['GET', 'POST'])
def profile():
if 'username' not in session:
return redirect(url_for('index'))
if request.method == 'POST':
title = request.form['title']
content = request.form['content']
db = sqlite3.connect('blog.db')
cursor = db.cursor()
cursor.execute('INSERT INTO posts (user_id, title, content) VALUES (?, ?, ?)', (session['user_id'], title, content))
db.commit()
db.close()
return redirect(url_for('profile'))
db = sqlite3.connect('blog.db')
cursor = db.cursor()
cursor.execute('SELECT * FROM posts WHERE user_id = ?', (session['user_id'],))
posts = cursor.fetchall()
db.close()
return render_template('profile.html', posts=posts)
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
db = sqlite3.connect('blog.db')
cursor = db.cursor()
cursor.execute('SELECT * FROM users WHERE username = ? AND password = ?', (username, password))
user = cursor.fetchone()
db.close()
if user:
session['username'] = user[1]
session['user_id'] = user[0]
return redirect(url_for('profile'))
else:
return "Invalid credentials"
return render_template('login.html')
@app.route('/logout')
def logout():
session.pop('username', None)
session.pop('user_id', None)
return redirect(url_for('index'))
if __name__ == '__main__':
app.run(debug=True)
如果使用的是MySQL数据库,请注意:
使用MySQL作为数据库时,需要对代码进行一些调整。首先,确保你已经安装了所需的Python库:
pip install Flask mysql-connector-python
并将app.py文件的内容用以下代码代替
from flask import Flask, render_template, request, redirect, url_for, session
import mysql.connector
import secrets
app = Flask(__name__)
app.secret_key = secrets.token_hex(16) # 生成一个 32 字节的随机十六进制字符串
# MySQL database configuration
db_config = {
'host': 'localhost',
'user': 'your_username',
'password': 'your_password',
'database': 'your_database'
}
# Database initialization
def init_db():
db = mysql.connector.connect(**db_config)
cursor = db.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(255) NOT NULL,
password VARCHAR(255) NOT NULL
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS posts (
id INT AUTO_INCREMENT PRIMARY KEY,
user_id INT NOT NULL,
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id)
)
''')
db.commit()
db.close()
init_db()
@app.route('/')
def index():
db = mysql.connector.connect(**db_config)
cursor = db.cursor()
cursor.execute('SELECT * FROM posts')
posts = cursor.fetchall()
db.close()
return render_template('index.html', posts=posts)
@app.route('/profile', methods=['GET', 'POST'])
def profile():
if 'username' not in session:
return redirect(url_for('index'))
if request.method == 'POST':
title = request.form['title']
content = request.form['content']
db = mysql.connector.connect(**db_config)
cursor = db.cursor()
cursor.execute('INSERT INTO posts (user_id, title, content) VALUES (%s, %s, %s)', (session['user_id'], title, content))
db.commit()
db.close()
return redirect(url_for('profile'))
db = mysql.connector.connect(**db_config)
cursor = db.cursor()
cursor.execute('SELECT * FROM posts WHERE user_id = %s', (session['user_id'],))
posts = cursor.fetchall()
db.close()
return render_template('profile.html', posts=posts)
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
db = mysql.connector.connect(**db_config)
cursor = db.cursor()
cursor.execute('SELECT * FROM users WHERE username = %s AND password = %s', (username, password))
user = cursor.fetchone()
db.close()
if user:
session['username'] = user[1]
session['user_id'] = user[0]
return redirect(url_for('profile'))
else:
return "Invalid credentials"
return render_template('login.html')
@app.route('/logout')
def logout():
session.pop('username', None)
session.pop('user_id', None)
return redirect(url_for('index'))
if __name__ == '__main__':
app.run(debug=True)
当然也可以不使用数据库,而用数组储存模拟的数据:
我们用posts数组来存储我们模拟的数据,请将以下代码写入app.py文件
from flask import Flask, render_template, session, redirect, url_for, request
import secrets
app = Flask(__name__)
app.secret_key = secrets.token_hex(16) # 生成一个 32 字节的随机十六进制字符串
# 模拟数据
posts = [
(1, 'Author 1', 'First Post', 'This is the content for the first post.'),
(2, 'Author 2', 'Second Post', 'This is the content for the second post.'),
]
# 主页
@app.route('/')
def index():
return render_template('index.html', posts=posts)
# 登录视图(示例用简单的登录方式)
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
session['username'] = request.form.get('username')
return redirect(url_for('index'))
return render_template('login.html')
# 个人中心
@app.route('/profile', methods=['GET', 'POST'])
def profile():
if 'username' not in session:
return redirect(url_for('login'))
if request.method == 'POST':
title = request.form.get('title')
content = request.form.get('content')
# 在此处可以添加代码将新帖子保存到数据库或列表中
posts.append((len(posts) + 1, session['username'], title, content))
return render_template('profile.html', posts=posts)
# 登出视图
@app.route('/logout')
def logout():
session.pop('username', None)
return redirect(url_for('index'))
if __name__ == '__main__':
app.run(debug=True)
- 设置密钥 :
secret_key
是用于加密 Flask 应用中的某些数据的一个字符串,特别是与用户会话相关的数据。Flask 使用这个密钥来进行数据签名(比如处理 Flask 的session
对象时)。 - 用户会话保护:该密钥有助于防止伪造用户会话。使用时,Flask 会对存储在会话中的数据进行签名,以防止攻击者修改会话内容。
你并不需要知道这个密钥的具体值 ,但你有方法可以获取这个随机生成的密钥,感兴趣自己多了解了解。
3. 前端代码
基础模板 (templates/base.html
)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}My Blog{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
<nav>
<a href="{{ url_for('index') }}">Home</a>
{% if 'username' in session %}
<a href="{{ url_for('profile') }}">Profile</a>
<a href="{{ url_for('logout') }}">Logout</a>
{% else %}
<a href="{{ url_for('login') }}">Login</a>
{% endif %}
</nav>
<div class="content">
{% block content %}{% endblock %}
</div>
</body>
</html>
首页 (templates/index.html
)
{% extends "base.html" %}
{% block title %}Home{% endblock %}
{% block content %}
<h1>Welcome to My Blog</h1>
<ul>
{% for post in posts %}
<li>
<h2>{{ post[2] }}</h2>
<p>{{ post[3] }}</p>
</li>
{% endfor %}
</ul>
{% endblock %}
这段代码是一个使用 Jinja2 模板引擎的 HTML 模板文件。它继承了一个名为 base.html
的基础模板,并在其中定义了两个块(title
和 content
)。以下是代码的详细解释:
1. {% extends "base.html" %}
- 作用 : 这行代码表示当前模板继承自
base.html
文件。base.html
通常是一个包含网站通用结构的基础模板,比如头部、导航栏、页脚等。 - 继承 : 通过继承,当前模板可以使用
base.html
中定义的结构,并在特定位置插入自己的内容。
2. {% block title %}Home{% endblock %}
- 作用 : 这行代码定义了一个名为
title
的块,并将其内容设置为Home
。 - 用途 : 通常,
title
块用于定义页面的标题,这个标题会显示在浏览器的标签栏中。
3. {% block content %}
和 {% endblock %}
- 作用 : 这行代码定义了一个名为
content
的块,并在其中插入了页面的主要内容。 - 用途 :
content
块通常用于放置页面的主要内容,比如文章列表、表单等。
4. <h1>Welcome to My Blog</h1>
- 作用: 这是一个一级标题,显示在页面的主要内容区域。
5. <ul>
和 {% for post in posts %}
- 作用 : 这是一个无序列表,用于显示文章列表。
{% for post in posts %}
是一个循环语句,遍历posts
列表中的每一项。 - 用途 : 在循环中,每一项
post
都会生成一个列表项<li>
,并显示文章的标题和内容。
6. {``{ post[2] }}
和 {``{ post[3] }}
- 作用 : 这些是变量插值,用于显示
post
对象的特定属性。post[2]
通常表示文章的标题。post[3]
通常表示文章的内容。
个人中心 (templates/profile.html
)
{% extends "base.html" %}
{% block title %}Profile{% endblock %}
{% block content %}
<h1>Welcome, {{ session['username'] }}!</h1>
<form action="{{ url_for('profile') }}" method="post">
<label for="title">Title:</label>
<input type="text" id="title" name="title" required>
<label for="content">Content:</label>
<textarea id="content" name="content" required></textarea>
<button type="submit">Post</button>
</form>
<ul>
{% for post in posts %}
<li>
<h2>{{ post[2] }}</h2>
<p>{{ post[3] }}</p>
</li>
{% endfor %}
</ul>
{% endblock %}
4. 静态文件 (static/style.css
)
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f4f4f4;
}
nav {
background-color: #333;
overflow: hidden;
}
nav a {
float: left;
display: block;
color: #f2f2f2;
text-align: center;
padding: 14px 16px;
text-decoration: none;
}
nav a:hover {
background-color: #ddd;
color: black;
}
.content {
padding: 20px;
margin: 20px;
}
form {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
}
input, textarea {
width: 100%;
padding: 8px;
margin-bottom: 10px;
box-sizing: border-box;
}
button {
padding: 10px 20px;
background-color: #333;
color: white;
border: none;
cursor: pointer;
}
button:hover {
background-color: #555;
}
我暂时还没有编写login和logout的html文件,如果感兴趣可以自己先编写。不过在首页你可以直接点击profile进入个人中心,这并不影响。至于如何运行flask项目,可以在终端使用以下代码。
flask --app app run --host=0.0.0.0
5. 运行应用
- 确保你已经安装了Flask。如果没有,可以通过
pip install flask
安装。 - 运行
app.py
,访问http://127.0.0.1:5000
即可看到博客首页。 - 登录后可以进入个人中心,发表文章。