这是本节的多页打印视图。 .
第一个 Web 应用
做一个带登录功能的 Flask 应用,连接 PostgreSQL
目标
这个教程会带你创建一个完整的 Web 应用:
- Flask 后端框架
- PostgreSQL 数据库存储用户
- 用户注册、登录、注销功能
- Session 管理
- 部署上线
完成后,你会有一个真正可用的 Web 应用,不只是 Hello World。
前置条件
- 已完成 快速开始教程
- 已登录 Piglet 环境(VS Code 或 SSH)
第一步:创建项目
cd ~/data
mkdir my-flask-app
cd my-flask-app创建 Python 虚拟环境并安装依赖:
python3 -m venv venv
source venv/bin/activate
pip install flask psycopg[binary] python-dotenv第二步:创建数据库表
连接 PostgreSQL:
psql postgres://postgres:piglet@localhost/postgres创建用户表:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 退出 psql
\q第三步:编写应用代码
创建 app.py:
import os
import hashlib
from flask import Flask, render_template, request, redirect, url_for, session, flash
import psycopg
app = Flask(__name__)
app.secret_key = os.urandom(24)
# 数据库连接
DB_URL = "postgres://postgres:piglet@localhost/postgres"
def get_db():
return psycopg.connect(DB_URL)
def hash_password(password):
return hashlib.sha256(password.encode()).hexdigest()
# 首页
@app.route('/')
def index():
user = session.get('user')
return render_template('index.html', user=user)
# 注册
@app.route('/register', methods=['GET', 'POST'])
def register():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
with get_db() as conn:
with conn.cursor() as cur:
# 检查用户名是否已存在
cur.execute("SELECT id FROM users WHERE username = %s", (username,))
if cur.fetchone():
flash('用户名已存在', 'error')
return redirect(url_for('register'))
# 创建用户
cur.execute(
"INSERT INTO users (username, password_hash) VALUES (%s, %s)",
(username, hash_password(password))
)
conn.commit()
flash('注册成功,请登录', 'success')
return redirect(url_for('login'))
return render_template('register.html')
# 登录
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
with get_db() as conn:
with conn.cursor() as cur:
cur.execute(
"SELECT id, username FROM users WHERE username = %s AND password_hash = %s",
(username, hash_password(password))
)
user = cur.fetchone()
if user:
session['user'] = {'id': user[0], 'username': user[1]}
flash(f'欢迎回来,{user[1]}!', 'success')
return redirect(url_for('index'))
else:
flash('用户名或密码错误', 'error')
return render_template('login.html')
# 注销
@app.route('/logout')
def logout():
session.pop('user', None)
flash('已退出登录', 'success')
return redirect(url_for('index'))
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)第四步:创建模板
创建模板目录:
mkdir templates创建 templates/base.html(基础模板):
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}我的应用{% endblock %}</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f5f5f5;
min-height: 100vh;
}
.navbar {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 1rem 2rem;
display: flex;
justify-content: space-between;
align-items: center;
}
.navbar a { color: white; text-decoration: none; margin-left: 1rem; }
.navbar a:hover { text-decoration: underline; }
.logo { color: white; font-size: 1.5rem; font-weight: bold; }
.container { max-width: 600px; margin: 2rem auto; padding: 0 1rem; }
.card {
background: white;
border-radius: 8px;
padding: 2rem;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.flash { padding: 1rem; border-radius: 4px; margin-bottom: 1rem; }
.flash.success { background: #d4edda; color: #155724; }
.flash.error { background: #f8d7da; color: #721c24; }
input {
width: 100%;
padding: 0.75rem;
margin: 0.5rem 0 1rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
button {
width: 100%;
padding: 0.75rem;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 4px;
font-size: 1rem;
cursor: pointer;
}
button:hover { opacity: 0.9; }
h1, h2 { margin-bottom: 1rem; }
p { color: #666; line-height: 1.6; }
a { color: #667eea; }
</style>
</head>
<body>
<nav class="navbar">
<span class="logo">MyApp</span>
<div>
<a href="{{ url_for('index') }}">首页</a>
{% if session.user %}
<a href="{{ url_for('logout') }}">退出 ({{ session.user.username }})</a>
{% else %}
<a href="{{ url_for('login') }}">登录</a>
<a href="{{ url_for('register') }}">注册</a>
{% endif %}
</div>
</nav>
<div class="container">
{% with messages = get_flashed_messages(with_categories=true) %}
{% for category, message in messages %}
<div class="flash {{ category }}">{{ message }}</div>
{% endfor %}
{% endwith %}
{% block content %}{% endblock %}
</div>
</body>
</html>创建 templates/index.html:
{% extends "base.html" %}
{% block title %}首页{% endblock %}
{% block content %}
<div class="card">
{% if user %}
<h1>欢迎,{{ user.username }}!</h1>
<p>你已成功登录。这是一个由 Piglet 驱动的 Flask 应用。</p>
<p style="margin-top: 1rem;">
<a href="{{ url_for('logout') }}">退出登录</a>
</p>
{% else %}
<h1>欢迎来到 MyApp</h1>
<p>这是一个由 Piglet 驱动的 Flask 应用,使用 PostgreSQL 存储用户数据。</p>
<p style="margin-top: 1rem;">
<a href="{{ url_for('login') }}">登录</a> 或
<a href="{{ url_for('register') }}">注册</a> 开始使用
</p>
{% endif %}
</div>
{% endblock %}创建 templates/login.html:
{% extends "base.html" %}
{% block title %}登录{% endblock %}
{% block content %}
<div class="card">
<h2>登录</h2>
<form method="POST">
<label>用户名</label>
<input type="text" name="username" required>
<label>密码</label>
<input type="password" name="password" required>
<button type="submit">登录</button>
</form>
<p style="margin-top: 1rem; text-align: center;">
还没有账号?<a href="{{ url_for('register') }}">注册</a>
</p>
</div>
{% endblock %}创建 templates/register.html:
{% extends "base.html" %}
{% block title %}注册{% endblock %}
{% block content %}
<div class="card">
<h2>注册</h2>
<form method="POST">
<label>用户名</label>
<input type="text" name="username" required>
<label>密码</label>
<input type="password" name="password" required>
<button type="submit">注册</button>
</form>
<p style="margin-top: 1rem; text-align: center;">
已有账号?<a href="{{ url_for('login') }}">登录</a>
</p>
</div>
{% endblock %}第五步:运行测试
python app.py访问 http://你的服务器IP:5000,试试:
- 点击"注册",创建一个账号
- 用刚才的账号登录
- 看到欢迎页面
- 点击"退出"
第六步:部署上线
开发完成后,用 Gunicorn 部署到生产环境:
# 安装 Gunicorn
pip install gunicorn
# 创建 systemd 服务
sudo tee /etc/systemd/system/myapp.service << EOF
[Unit]
Description=My Flask App
After=network.target
[Service]
User=root
WorkingDirectory=/root/data/my-flask-app
Environment="PATH=/root/data/my-flask-app/venv/bin"
ExecStart=/root/data/my-flask-app/venv/bin/gunicorn -w 2 -b 127.0.0.1:5000 app:app
Restart=always
[Install]
WantedBy=multi-user.target
EOF
# 启动服务
sudo systemctl daemon-reload
sudo systemctl enable myapp
sudo systemctl start myapp配置 Nginx 反向代理:
sudo tee /etc/nginx/conf.d/myapp.conf << EOF
server {
listen 80;
server_name myapp.你的域名.com;
location / {
proxy_pass http://127.0.0.1:5000;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
}
}
EOF
sudo nginx -t && sudo systemctl reload nginx让 Claude 帮你做
上面的步骤看起来很多?试试让 Claude Code 来做:
帮我在 ~/data/my-flask-app 创建一个 Flask 应用:
- 用户注册、登录、注销功能
- 使用 PostgreSQL 存储用户(连接串:postgres://postgres:piglet@localhost/postgres)
- 界面要好看
- 帮我创建数据库表
- 最后帮我部署到 NginxClaude 会自动完成所有步骤。
你学到了什么
- ✅ 创建 Flask 应用
- ✅ 连接 PostgreSQL 数据库
- ✅ 实现用户认证(注册/登录/注销)
- ✅ 使用 Jinja2 模板
- ✅ 部署到生产环境