Flask写一个用户登录的程序,需要用Postgresql数据库来存储数据,用命令来初始化数据库,并刷基础数据,采用分模块的思想来做
下面我将实现一个完整的Flask用户登录系统,采用模块化架构并使用PostgreSQL作为数据库。系统包含以下组件:
flask_login_app/
├── app/
│ ├── __init__.py
│ ├── auth/
│ │ ├── __init__.py
│ │ ├── routes.py
│ │ └── forms.py
│ ├── models.py
│ ├── templates/
│ │ └── auth/
│ │ ├── login.html
│ │ └── register.html
│ └── commands.py
├── config.py
├── requirements.txt
└── run.py
# config.py
import os
from dotenv import load_dotenv
# 加载环境变量
load_dotenv()
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY') or 'your-secret-key'
SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \
'postgresql://your_user:your_password@localhost/your_db'
SQLALCHEMY_TRACK_MODIFICATIONS = False
DEBUG = True
# app/__init__.py
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_login import LoginManager
from config import Config
db = SQLAlchemy()
migrate = Migrate()
login_manager = LoginManager()
login_manager.login_view = 'auth.login'
def create_app(config_class=Config):
app = Flask(__name__)
app.config.from_object(config_class)
# 初始化扩展
db.init_app(app)
migrate.init_app(app, db)
login_manager.init_app(app)
# 注册蓝图
from app.auth import bp as auth_bp
app.register_blueprint(auth_bp, url_prefix='/auth')
# 注册自定义命令
from app import commands
app.cli.add_command(commands.init_db_command)
app.cli.add_command(commands.seed_db_command)
return app
# app/models.py
from datetime import datetime
from app import db
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True, nullable=False)
email = db.Column(db.String(120), index=True, unique=True, nullable=False)
password_hash = db.Column(db.String(128), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
last_login = db.Column(db.DateTime)
is_active = db.Column(db.Boolean, default=True)
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
def __repr__(self):
return f'{self.username}>'
# app/auth/forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, SubmitField, BooleanField
from wtforms.validators import DataRequired, Email, Length, EqualTo
class LoginForm(FlaskForm):
username = StringField('用户名', validators=[DataRequired(), Length(min=4, max=64)])
password = PasswordField('密码', validators=[DataRequired(), Length(min=6)])
remember = BooleanField('记住我')
submit = SubmitField('登录')
class RegistrationForm(FlaskForm):
username = StringField('用户名', validators=[DataRequired(), Length(min=4, max=64)])
email = StringField('邮箱', validators=[DataRequired(), Email()])
password = PasswordField('密码', validators=[DataRequired(), Length(min=6)])
password2 = PasswordField(
'确认密码', validators=[DataRequired(), EqualTo('password')])
submit = SubmitField('注册')
# app/auth/routes.py
from flask import Blueprint, render_template, redirect, url_for, flash, request
from flask_login import login_user, logout_user, current_user, login_required
from app import db
from app.auth.forms import LoginForm, RegistrationForm
from app.models import User
bp = Blueprint('auth', __name__)
@bp.route('/login', methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('main.index'))
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(username=form.username.data).first()
if user and user.check_password(form.password.data):
login_user(user, remember=form.remember.data)
next_page = request.args.get('next')
flash('登录成功!', 'success')
return redirect(next_page or url_for('main.index'))
else:
flash('用户名或密码错误', 'danger')
return render_template('auth/login.html', title='登录', form=form)
@bp.route('/register', methods=['GET', 'POST'])
def register():
if current_user.is_authenticated:
return redirect(url_for('main.index'))
form = RegistrationForm()
if form.validate_on_submit():
user = User(username=form.username.data, email=form.email.data)
user.set_password(form.password.data)
db.session.add(user)
db.session.commit()
flash('注册成功!您现在可以登录了', 'success')
return redirect(url_for('auth.login'))
return render_template('auth/register.html', title='注册', form=form)
@bp.route('/logout')
@login_required
def logout():
logout_user()
flash('您已成功退出登录', 'info')
return redirect(url_for('main.index'))
# app/commands.py
import click
from app import db
from app.models import User
def init_db_command():
"""初始化数据库"""
db.create_all()
click.echo('数据库已初始化')
def seed_db_command():
"""添加基础用户数据"""
users = [
{'username': 'admin', 'email': '[email protected]', 'password': 'admin123'},
{'username': 'user1', 'email': '[email protected]', 'password': 'user123'},
{'username': 'user2', 'email': '[email protected]', 'password': 'user123'}
]
for user_data in users:
user = User.query.filter_by(username=user_data['username']).first()
if not user:
user = User(username=user_data['username'], email=user_data['email'])
user.set_password(user_data['password'])
db.session.add(user)
db.session.commit()
click.echo('基础用户数据已添加')
DOCTYPE html>
<html>
<head>
<title>登录title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
head>
<body>
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<h2 class="mb-4">用户登录h2>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close">button>
div>
{% endfor %}
{% endif %}
{% endwith %}
<form method="POST" action="{{ url_for('auth.login') }}">
{{ form.hidden_tag() }}
<div class="mb-3">
{{ form.username.label(class="form-label") }}
{{ form.username(class="form-control", placeholder="请输入用户名") }}
{% for error in form.username.errors %}
<div class="text-danger">{{ error }}div>
{% endfor %}
div>
<div class="mb-3">
{{ form.password.label(class="form-label") }}
{{ form.password(class="form-control", placeholder="请输入密码") }}
{% for error in form.password.errors %}
<div class="text-danger">{{ error }}div>
{% endfor %}
div>
<div class="mb-3 form-check">
{{ form.remember(class="form-check-input") }}
{{ form.remember.label(class="form-check-label") }}
div>
<button type="submit" class="btn btn-primary">登录button>
<a href="{{ url_for('auth.register') }}" class="btn btn-link">注册新账号a>
form>
div>
div>
div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js">script>
body>
html>
DOCTYPE html>
<html>
<head>
<title>注册title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
head>
<body>
<div class="container mt-5">
<div class="row justify-content-center">
<div class="col-md-6">
<h2 class="mb-4">用户注册h2>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close">button>
div>
{% endfor %}
{% endif %}
{% endwith %}
<form method="POST" action="{{ url_for('auth.register') }}">
{{ form.hidden_tag() }}
<div class="mb-3">
{{ form.username.label(class="form-label") }}
{{ form.username(class="form-control", placeholder="请输入用户名") }}
{% for error in form.username.errors %}
<div class="text-danger">{{ error }}div>
{% endfor %}
div>
<div class="mb-3">
{{ form.email.label(class="form-label") }}
{{ form.email(class="form-control", placeholder="请输入邮箱") }}
{% for error in form.email.errors %}
<div class="text-danger">{{ error }}div>
{% endfor %}
div>
<div class="mb-3">
{{ form.password.label(class="form-label") }}
{{ form.password(class="form-control", placeholder="请输入密码") }}
{% for error in form.password.errors %}
<div class="text-danger">{{ error }}div>
{% endfor %}
div>
<div class="mb-3">
{{ form.password2.label(class="form-label") }}
{{ form.password2(class="form-control", placeholder="请再次输入密码") }}
{% for error in form.password2.errors %}
<div class="text-danger">{{ error }}div>
{% endfor %}
div>
<button type="submit" class="btn btn-primary">注册button>
<a href="{{ url_for('auth.login') }}" class="btn btn-link">已有账号?立即登录a>
form>
div>
div>
div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js">script>
body>
html>
# run.py
from app import create_app
app = create_app()
if __name__ == '__main__':
app.run(debug=True)
python -m venv venv
source venv/bin/activate # Linux/macOS
venv\Scripts\activate # Windows
pip install flask flask-sqlalchemy flask-migrate flask-login flask-wtf psycopg2-binary python-dotenv
# 连接到PostgreSQL
sudo -u postgres psql
# 创建数据库和用户
CREATE DATABASE flask_login;
CREATE USER flask_user WITH PASSWORD 'your_password';
GRANT ALL PRIVILEGES ON DATABASE flask_login TO flask_user;
\q
创建 .env
文件:
SECRET_KEY=your_very_secret_key
DATABASE_URL=postgresql://flask_user:your_password@localhost/flask_login
# 初始化数据库迁移
flask db init
# 创建迁移脚本
flask db migrate -m "Initial migration"
# 应用迁移
flask db upgrade
# 初始化数据库表结构
flask init-db
# 添加基础用户数据
flask seed-db
python run.py
用户认证:
数据库管理:
模块化架构:
安全特性:
http://localhost:5000/auth/login
登录flask init-db
初始化数据库结构flask seed-db
添加基础用户数据