
主要组件
- Web 浏览器:
- 用户使用 Web 浏览器(如 Chrome、Firefox、Safari 等)发送 HTTP 请求。在这个过程中,用户可能输入 URL、点击链接或提交表单等。
- Web 服务器:
- Web 服务器(如 Apache、Nginx)接收来自浏览器的 HTTP 请求并作出响应。它负责处理静态资源(如 HTML、CSS、JavaScript 文件、图片等)以及将动态请求转发到 Web 框架应用程序。
- Web 框架应用程序:
- Web 框架(如 Flask、Django、FastAPI)处理动态请求。它根据请求的 URL 路由决定调用哪个视图函数。在视图函数中,应用程序的业务逻辑会被执行,比如数据处理、访问数据库等。
- 数据库:
- 应用程序在需要时会访问数据库(如 MySQL、PostgreSQL、MongoDB等)以获取或存储数据。通常通过 ORM (对象关系映射) 技术简化数据库操作。
- 模板:
- 若响应需要动态 HTML,应用程序会使用模板引擎(如 Jinja2、Django Template)将数据渲染成 HTML 页面,并返回给 Web 服务器,然后再发送给 Web 浏览器。
- 静态资源:
- Web 服务器直接提供静态资源(如图片、样式表、JavaScript 文件),用户请求的静态资源可以直接返回,而无需经过应用程序。
流程描述
- 用户请求:
- 用户通过 Web 浏览器发起 HTTP 请求,可能请求一个网页或资源。
- Web 服务器处理:
- Web 服务器接收请求,根据请求的 URL 进行判断。
- 如果是静态资源(如
.html
、.css
、图片等),Web 服务器直接返回该资源。 - 如果是动态内容的请求(如 API 调用或表单提交),Web 服务器将请求转发给 Web 框架应用程序。
- Web 框架应用程序处理:
- Web 框架应用程序接收请求,调用相应的视图函数。
- 视图函数中可能会处理数据逻辑、访问数据库等,并生成需要的动态内容。
- 数据库交互:
- 如果需要获取或存储数据,框架应用程序将与数据库进行交互。
- 使用模板生成响应:
- 视图函数会将获取到的数据与模板结合,渲染成最终的 HTML 页面。
- 返回响应:
- 渲染后的内容通过 Web 框架返回给 Web 服务器,Web 服务器将最终的 HTML 响应发送回 Web 浏览器。
- 用户查看:
- Web 浏览器接收到 HTML 页面并进行渲染,用户看到网页的内容。
项目部署
项目结构
project/
│
├── app.py ← Python 后端
├── templates/
│ └── form.html ← HTML 表单模板
└── static/
├── css/
│ └── styles.css ← 静态 CSS 文件
├── js/
│ └── script.js ← 静态 JavaScript 文件
└── uploads/ ← 用户头像上传的位置
项目说明
- Python后端 (
app.py
)- Flask 是一个轻量级的Python Web框架,用于构建Web应用程序。这个文件包含应用的路由,数据库模型(使用Flask-SQLAlchemy),以及用户数据的处理逻辑。
- 项目使用
PyMySQL
连接到 MySQL 数据库并通过SQLAlchemy管理数据模型。 - 提供了一个API,用于接收表单提交的用户信息(包括用户名、密码、性别、爱好、简介和头像),并将这些信息存储在数据库中。
- HTML 模板 (
templates/
)form.html
是一个用户注册表单的HTML模板。它利用 Jinja2 模板引擎来动态呈现内容(例如,表单的反馈信息)。- 表单中使用了
fetch API
进行异步提交,使得在不刷新页面的情况下将数据发送到后端。
- 静态资源 (
static/
)- CSS 文件夹 (
css/
):styles.css
文件包含了应用的样式定义,使得前端界面美观和用户友好。
- JavaScript 文件夹 (
js/
):script.js
文件包含了用于处理表单提交和与后端通信的JavaScript代码。利用Fetch API
,使得前端能够与后端进行非同步交互,并接收返回的数据。
- 上传文件夹 (
uploads/
):- 用于存储用户上传的头像文件。
- CSS 文件夹 (
数据库设置
首先确保你已经安装了MySQL数据库
创建数据库
在MySQL中创建一个名为user_db
的数据库,并执行如下SQL命令:
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(255) NOT NULL,
password VARCHAR(255) NOT NULL,
gender VARCHAR(20),
hobbies TEXT,
bio TEXT,
avatar_path VARCHAR(255)
);
创建app.py
下面是完整的app.py
后端代码,包括数据库功能和Fetch API的实现:
from flask import Flask, render_template, request, jsonify
from flask_sqlalchemy import SQLAlchemy
import os
import pymysql # 导入 PyMySQL
# 使用 PyMySQL 替代 MySQLdb
pymysql.install_as_MySQLdb()
app = Flask(__name__)
# 配置数据库,使用 PyMySQL
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+pymysql://user:password@localhost/user_db' # 替换为你的数据库用户名和密码
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
# 用户模型
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(255), nullable=False)
password = db.Column(db.String(255), nullable=False)
gender = db.Column(db.String(20))
hobbies = db.Column(db.Text)
bio = db.Column(db.Text)
avatar_path = db.Column(db.String(255))
# 设置头像上传路径
UPLOAD_FOLDER = 'static/uploads'
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
@app.route('/')
def index():
return render_template('form.html')
@app.route('/submit', methods=['POST'])
def submit():
# 获取文本字段
username = request.form.get('username')
password = request.form.get('password')
gender = request.form.get('gender')
hobbies = request.form.getlist('hobby') # 多选
bio = request.form.get('bio')
# 处理上传头像
avatar = request.files.get('avatar')
avatar_path = None
if avatar and avatar.filename:
avatar_path = os.path.join(app.config['UPLOAD_FOLDER'], avatar.filename)
avatar.save(avatar_path)
# 将数据保存到数据库
new_user = User(username=username, password=password, gender=gender,
hobbies=', '.join(hobbies), bio=bio, avatar_path=avatar_path)
db.session.add(new_user)
db.session.commit()
# 返回 JSON 数据
result = {
'username': username,
'gender': gender,
'hobbies': hobbies,
'bio': bio,
'avatar_saved_to': avatar_path
}
return jsonify(result)
if __name__ == '__main__':
with app.app_context(): # 创建应用上下文
db.create_all() # 确保创建数据库和表
app.run(debug=True)
创建form.html
这是HTML用户注册表单模板。
在form.html
中,引入静态资源,添加JavaScript部分使用Fetch API来提交表单数据而不是在页面上直接提交表单。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
</head>
<body>
<div class="form-container">
<h2>用户注册</h2>
<form id="userForm" enctype="multipart/form-data">
<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>
<div class="form-group">
<label>性别</label>
<div class="form-group-inline">
<label><input type="radio" name="gender" value="male" required>男</label>
<label><input type="radio" name="gender" value="female" required>女</label>
</div>
</div>
<div class="form-group">
<label>爱好</label>
<div class="form-group-inline">
<label><input type="checkbox" name="hobby" value="music">音乐</label>
<label><input type="checkbox" name="hobby" value="sports">运动</label>
<label><input type="checkbox" name="hobby" value="reading">阅读</label>
</div>
</div>
<div class="form-group">
<label for="bio">个人简介</label>
<textarea name="bio" id="bio" rows="4"></textarea>
</div>
<div class="form-group">
<label for="avatar">上传头像</label>
<input type="file" id="avatar" name="avatar">
</div>
<div class="form-group">
<button type="submit">提交</button>
</div>
</form>
<div id="result"></div>
</div>
<script src="{{ url_for('static', filename='js/script.js') }}"></script>
</body>
</html>
添加静态CSS和JavaScript文件
创建一个static/css/styles.css
文件并将你的样式代码放入其中
body {
font-family: Arial, sans-serif;
background-color: #f2f2f2;
padding: 40px;
}
.form-container {
background-color: white;
padding: 30px;
max-width: 400px;
margin: auto;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
h2 {
text-align: center;
}
.form-group {
margin-bottom: 15px;
}
label {
display: block;
font-weight: bold;
margin-bottom: 5px;
}
input[type="text"],
input[type="password"],
input[type="file"],
textarea,
select {
width: 100%;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
box-sizing: border-box;
}
.form-group-inline {
display: flex;
gap: 10px;
}
.form-group-inline label {
font-weight: normal;
}
button {
background-color: #4CAF50;
color: white;
padding: 10px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #45a049;
}
.password-strength {
font-size: 0.9em;
color: gray;
}
接下来,我们可以创建一个static/js/script.js
文件,并将Fetch API的JavaScript代码放入其中:
document.getElementById('userForm').addEventListener('submit', function(event) {
event.preventDefault(); // 防止默认提交
const formData = new FormData(this); // 收集表单数据
fetch('/submit', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
document.getElementById('result').innerHTML = `提交成功:<br>用户名:${data.username}<br>性别:${data.gender}<br>爱好:${data.hobbies}<br>简介:${data.bio}<br>头像路径:${data.avatar_saved_to}`;
})
.catch(error => {
console.error('发生错误:', error);
});
});
启动应用
安装依赖后,确保数据库运行,执行:
python app.py
在浏览器中打开 http://127.0.0.1:5000/
,你会看到用户注册表单,填写信息并提交后,数据将通过Fetch API发送到后端,并返回结果。




最后可以将前端代码优化一下:
更新form.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>用户注册</title>
<style>
body {
font-family: Arial, sans-serif; /* 设置字体 */
background-color: #f2f2f2; /* 背景色 */
padding: 40px; /* 内边距 */
display: flex;
justify-content: center;
align-items: center;
height: 100vh; /* 垂直居中 */
margin: 0; /* 去掉默认外边距 */
}
.form-container {
background-color: white; /* 表单背景 */
padding: 30px; /* 内边距 */
max-width: 400px; /* 最大宽度 */
margin: auto; /* 自动外边距以居中 */
border-radius: 8px; /* 圆角效果 */
box-shadow: 0 4px 8px rgba(0,0,0,0.1); /* 阴影效果 */
}
h2 {
text-align: center; /* 标题居中 */
color: #333; /* 标题颜色 */
margin-bottom: 20px; /* 标题间距 */
}
.form-group {
margin-bottom: 15px; /* 每个表单组之间的下间距 */
}
label {
display: block; /* 块级元素 */
font-weight: bold; /* 加粗 */
margin-bottom: 5px; /* 底部间距 */
}
input[type="text"],
input[type="password"],
input[type="file"],
textarea,
select {
width: 100%; /* 宽度撑满容器 */
padding: 10px; /* 内边距 */
border: 1px solid #ccc; /* 边框 */
border-radius: 4px; /* 圆角 */
box-sizing: border-box; /* 包含内边距和边框在宽度内 */
margin-top: 8px; /* 输入框上方间距 */
font-size: 14px; /* 输入框字体大小 */
}
.form-group-inline {
display: flex; /* 使用弹性布局 */
gap: 20px; /* 每项之间的间距 */
flex-wrap: wrap; /* 多行排列 */
}
button {
background-color: #4CAF50; /* 绿色背景 */
color: white; /* 字体颜色 */
padding: 10px 15px; /* 内边距 */
border: none; /* 去掉默认边框 */
border-radius: 4px; /* 圆角 */
font-size: 16px; /* 字体大小 */
cursor: pointer; /* 鼠标悬浮手形 */
transition: background-color 0.3s; /* 动画过渡 */
width: 100%; /* 按钮宽度撑满 */
}
button:hover {
background-color: #45a049; /* 悬停时变深绿色 */
}
</style>
</head>
<body>
<div class="form-container">
<h2>用户注册</h2>
<form id="userForm" enctype="multipart/form-data">
<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>
<div class="form-group">
<label>性别</label>
<div class="form-group-inline">
<label><input type="radio" name="gender" value="male" required>男</label>
<label><input type="radio" name="gender" value="female" required>女</label>
</div>
</div>
<div class="form-group">
<label>爱好</label>
<div class="form-group-inline">
<label><input type="checkbox" name="hobby" value="music">音乐</label>
<label><input type="checkbox" name="hobby" value="sports">运动</label>
<label><input type="checkbox" name="hobby" value="reading">阅读</label>
</div>
</div>
<div class="form-group">
<label for="bio">个人简介</label>
<textarea name="bio" id="bio" rows="4" required></textarea>
</div>
<div class="form-group">
<label for="avatar">上传头像</label>
<input type="file" id="avatar" name="avatar">
</div>
<div class="form-group">
<button type="submit">提交</button>
</div>
</form>
<div id="result"></div>
</div>
<script src="{{ url_for('static', filename='js/script.js') }}"></script>
</body>
</html>

web 服务器构建
目前这个项目没有使用专门的Web服务器(如Nginx或Apache)来部署这个Flask应用。目前使用的是Flask自带的开发服务器,这是适合开发和测试的,但不适合生产环境。
目前推荐WSGI(Web Server Gateway Interface)服务器来运行Flask应用,如Gunicorn或uWSGI,并将其与Nginx或Apache等作为反向代理服务器结合使用,以提高性能和安全性。
安装Gunicorn
pip install gunicorn
安装 Nginx
我这里使用的 MAC,可以使用 Homebrew 来安装 Nginx。打开终端并输入以下命令:
brew install nginx
brew services start nginx
您可以在浏览器中访问 http://localhost:8080
来查看 Nginx 是否成功运行。默认情况下,Nginx 将监听 8080 端口。
配置 Nginx
Nginx 的配置文件位于 /opt/homebrew/etc/nginx/nginx.conf
。您可以使用任何文本编辑器打开该文件进行修改:
vim /opt/homebrew/etc/nginx/nginx.conf
在配置文件中,您需要添加一个 server
块来配置 Nginx 以转发请求到您的 Flask 应用。假设您的 Flask 应用使用 Gunicorn 运行在 localhost:8000
,那么您可以这样配置:
server {
listen 8888;
server_name localhost;
location / {
proxy_pass http://127.0.0.1:8000; # 修改为 Gunicorn 监听的地址和端口
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
启动 Gunicorn
在另一个终端窗口中,启动您的 Flask 应用。例如,如果您的应用在 app.py
中,可以这样启动:
gunicorn -w 4 -b 127.0.0.1:8000 app:app
在这里,-w 4
表示启动4个工作进程,-b
用于指定绑定的地址和端口。
检查并重启 Nginx
在修改 Nginx 配置后,您需要重启 Nginx,以使更改生效。您可以用以下命令重启 Nginx:
nginx -t
brew services restart nginx
测试
在浏览器中访问 http://localhost:8888
,Nginx 将转发请求到的Flask 应用运行在 Gunicorn 上。

动静分离体现
Flask项目通过定义静态目录和动态路由来有效区分和管理动静态数据:
动态资源请求
动态数据的处理主要由Flask路由来管理。在你项目的app.py
中,通过定义不同的路由来处理用户请求,返回动态生成的数据。例如:
路由处理对根路径的GET请求,并返回渲染的HTML模板form.html
@app.route('/')
def index():
return render_template('form.html') # 返回动态HTML内容
处理表单提交的路由/submit
,这里的内容依赖于用户提交的数据,在submit()
中,返回的数据(JSON格式)基于用户输入的数据,体现了动态处理。
@app.route('/submit', methods=['POST'])
def submit():
username = request.form.get('username')
# 其他处理...
return jsonify(result) # 返回动态生成的JSON响应
静态资源管理
对于静态资源(如CSS、JavaScript和图像文件),Flask通过static/
目录管理这些文件,这些文件在服务器上是固定的并可以直接被客户端获取:
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
<script src="{{ url_for('static', filename='js/script.js') }}"></script>
处理静态和动态资源的分离
- 静态内容:不需要进行服务器端计算处理的内容(如固定的CSS、JavaScript和图片文件)直接服务于用户请求。这些资源通过Nginx或Apache等Web服务器提供,加快加载速度和减少服务器负担。
- 动态内容:依赖用户输入或数据库查询的内容,将数据逻辑封装在Flask的视图函数中,根据请求的不同返回相应的内容。
发布者:LJH,转发请注明出处:https://www.ljh.cool/43062.html