1. 项目预览
1.1 登录

1.2 注册

2. 项目目录结构

3. 项目代码展示
blueprint
init.py
from .user import bp as user_bp
forms.py
import wtforms
from wtforms.validators import length, email, EqualTo
from models import EmailCaptchaModel, UserModel
class LoginForm(wtforms.Form):
email = wtforms.StringField(validators=[email()])
password = wtforms.StringField(validators=[length(min=6, max=20)])
class RegisterForm(wtforms.Form):
username = wtforms.StringField(validators=[length(min=3, max=20)])
email = wtforms.StringField(validators=[email()])
captcha = wtforms.StringField(validators=[length(min=4, max=4)])
password = wtforms.StringField(validators=[length(min=6, max=20)])
password_confirm = wtforms.StringField(validators=[EqualTo("password")])
def validate_captcha(self, field):
captcha = field.data
email = self.email.data
captcha_model = EmailCaptchaModel.query.filter_by(email=email).first()
if captcha_model.captcha.lower() != captcha.lower():
raise wtforms.ValidationError("邮箱验证码错误!")
def validate_email(self, field):
email = field.data
user_model = UserModel.query.filter_by(email=email).first()
if user_model:
raise wtforms.ValidationError("邮箱已经存在!")

- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
user.py
from flask import Blueprint, render_template, request, redirect, url_for, jsonify, session, flash
from exts import mail, db
from flask_mail import Message
from models import EmailCaptchaModel, UserModel
from datetime import datetime
from .forms import RegisterForm, LoginForm
from werkzeug.security import generate_password_hash, check_password_hash
import random
import string
bp = Blueprint("user", __name__)
@bp.route("/")
def index():
return render_template("index.html")
@bp.route("/login", methods=['GET', 'POST'])
def login():
if request.method == 'GET':
return render_template("login.html")
else:
form = LoginForm(request.form)
if form.validate():
email = form.email.data
password = form.password.data
user = UserModel.query.filter_by(email=email).first()
if user and check_password_hash(user.password, password):
session['user_id'] = user.id
return redirect("/")
else:
flash("邮箱和密码不匹配!!!")
return redirect(url_for("user.login"))
else:
flash("邮箱或密码错误!!!")
return redirect(url_for("user.login"))
@bp.route("/register", methods=['GET', 'POST'])
def register():
if request.method == 'GET':
return render_template("register.html")
else:
form = RegisterForm(request.form)
if form.validate():
email = form.email.data
username = form.username.data
password = form.password.data
hash_password = generate_password_hash(password)
user = UserModel(email=email, username=username, password=hash_password)
db.session.add(user)
db.session.commit()
return redirect(url_for("user.login"))
else:
return redirect(url_for("user.register"))
@bp.route("/logout")
def logout():
session.clear()
return redirect(url_for('user.login'))
@bp.route("/captcha", methods=['POST'])
def get_captcha():
email = request.form.get("email")
letters = string.ascii_letters + string.digits
captcha = "".join(random.sample(letters, 4))
if email:
message = Message(
subject='【验证码】',
recipients=[email],
body=f"【验证码】您的注册验证码是{captcha},请不要告诉任何人哦!"
)
mail.send(message)
captcha_model = EmailCaptchaModel.query.filter_by(email=email).first()
if captcha_model:
captcha_model.captcha = captcha
captcha_model.create_time = datetime.now()
db.session.commit()
else:
captcha_model = EmailCaptchaModel(email=email, captcha=captcha)
db.session.add(captcha_model)
db.session.commit()
print("captcha:", captcha)
return jsonify({"code": 200})
else:
return jsonify({"code": 400, "message": "请先传递邮箱!"})

- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
static
bootstrap@4.6.min.css
点击下载css代码
jquery.3.6.min.js
点击下载jquery代码
register.js
function bindCaptchaBtnClick(){
$("#captcha-btn").on("click",function (event){
var $this = $(this);
var email = $("input[name='email']").val()
if(!email){
alert("请先输入邮箱");
return;
}
$.ajax({
url: "/user/captcha",
method: "POST",
data: {
"email": email
},
success: function (res){
var code = res['code'];
if (code == 200){
$this.off("click")
var countDown = 60;
var timer = setInterval(function (){
countDown -= 1;
if (countDown > 0){
$this.text(countDown+"秒后重新发送");
}else {
$this.text("获取验证码");
bindCaptchaBtnClick();
clearInterval(timer);
}
},1000)
alert("验证码发送成功!");
}else{
alert(res['message']);
}
}
})
});
}
$(function (){
bindCaptchaBtnClick();
});
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
templates
base.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{% block title %}{% endblock %}</title>
<link rel="stylesheet" href="{{ url_for('static',filename='bootstrap/bootstrap@4.6.min.css') }}">
<link rel="stylesheet" href="{{ url_for('static',filename='css/css.css') }}">
{% block head %}{% endblock %}
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
<div class="container">
<a class="navbar-brand" href="/">
<svg t="1659145665644" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6166" width="50" height="50"><path d="M512 512m-405.333333 0a405.333333 405.333333 0 1 0 810.666666 0 405.333333 405.333333 0 1 0-810.666666 0Z" fill="#4CAF50" p-id="6167"></path><path d="M640 426.666667c47.061333 0 85.333333 38.272 85.333333 85.333333s-38.272 85.333333-85.333333 85.333333-85.333333-38.272-85.333333-85.333333 38.272-85.333333 85.333333-85.333333m0-85.333334a170.666667 170.666667 0 1 0 0 341.333334 170.666667 170.666667 0 0 0 0-341.333334z" fill="#FFFFFF" p-id="6168"></path><path d="M384 426.666667c47.061333 0 85.333333 38.272 85.333333 85.333333s-38.272 85.333333-85.333333 85.333333-85.333333-38.272-85.333333-85.333333 38.272-85.333333 85.333333-85.333333m0-85.333334a170.666667 170.666667 0 1 0 0 341.333334 170.666667 170.666667 0 0 0 0-341.333334z" fill="#FFFFFF" p-id="6169"></path></svg>
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav mr-auto">
<li class="nav-item active">
<a class="nav-link" href="/">首页 <span class="sr-only">(current)</span></a>
</li>
</ul>
<ul class="navbar-nav">
{% if user %}
<li class="nav-item">
<span class="nav-link">{{ user.username }}</span>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('user.logout') }}">退出登录</a>
</li>
{% else %}
<li class="nav-item">
<a class="nav-link" href="{{ url_for('user.login') }}">登录</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url_for('user.register') }}">注册</a>
</li>
{% endif %}
</ul>
</div>
</div>
</nav>
<div class="container">{% block body %}{% endblock %}</div>
</body>
</html>

- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
index.html
{% extends "base.html" %}
{% block title %}首页{% endblock %}
{% block head %}
{% endblock %}
{% block body %}
<h1>文章首页</h1>
{% endblock %}
login.html
{% extends "base.html" %}
{% block title %}登录{% endblock %}
{% block head %}
{% endblock %}
{% block body %}
<div class="row mt-4">
<div class="col"></div>
<div class="col">
<form action="{{ url_for("user.login") }}" method="post">
<div class="form-group">
<label for="exampleInputEmail1">邮箱</label>
<input type="email" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp" name="email">
</div>
<div class="form-group">
<label for="exampleInputPassword1">密码</label>
<input type="password" name="password" class="form-control" id="exampleInputPassword1">
</div>
{% for message in get_flashed_messages() %}
<div class="from-group">
<div class="text-danger">{{ message }}</div>
</div>
{% endfor %}
<div class="from-group">
<button type="submit" class="btn btn-primary btn-block">立即登录</button>
</div>
</form>
</div>
<div class="col"></div>
</div>
{% endblock %}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
register.html
{% extends "base.html" %}
{% block title %}注册{% endblock %}
{% block head %}
<script src="{{ url_for('static',filename='jquery/jquery.3.6.min.js') }}"></script>
<script src="{{ url_for('static',filename='js/register.js') }}"></script>
{% endblock %}
{% block body %}
<div class="row mt-4">
<div class="col"></div>
<div class="col">
<form action="{{ url_for("user.register") }}" method="post">
<div class="form-group">
<label for="exampleInputEmail1">邮箱</label>
<input type="email" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp"
name="email">
<small id="emailHelp" class="form-text text-muted">我们不会把邮箱用于其他用途</small>
</div>
<div class="form-group">
<label for="exampleInputEmail1">验证码</label>
<div class="input-group">
<input type="text" class="form-control" name="captcha">
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="button" id="captcha-btn">获取验证码</button>
</div>
</div>
</div>
<div class="form-group">
<label for="exampleInputEmail1">用户名</label>
<input type="text" class="form-control" name="username">
</div>
<div class="form-group">
<label for="exampleInputPassword1">密码</label>
<input type="password" class="form-control" id="exampleInputPassword1" name="password">
</div>
<div class="form-group">
<label for="exampleInputPassword1">确认密码</label>
<input type="password" class="form-control" name="password_confirm">
</div>
<button type="submit" class="btn btn-primary btn-block">立即注册</button>
</form>
</div>
<div class="col"></div>
</div>
{% endblock %}

- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
app.py
from flask import Flask, session, g
import config
from exts import db, mail
from blueprints import user_bp
from flask_migrate import Migrate
from models import UserModel
app = Flask(__name__)
app.config.from_object(config)
db.init_app(app)
mail.init_app(app)
migrate = Migrate(app, db)
app.register_blueprint(user_bp)
@app.before_request
def before_request():
user_id = session.get("user_id")
if user_id:
try:
user = UserModel.query.get(user_id)
g.user = user
except:
g.user = None
@app.context_processor
def context_processor():
if hasattr(g, "user"):
return {"user": g.user}
else:
return {}
if __name__ == '__main__':
app.run()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
config.py
HOSTNAME = '127.0.0.1'
PORT = '3306'
DATABASE = '数据库名'
USERNAME = '数据库账号'
PASSWORD = '数据库密码'
DB_URI = 'mysql+pymysql://{}:{}@{}:{}/{}?charset=utf8'.format(USERNAME, PASSWORD, HOSTNAME, PORT, DATABASE)
SQLALCHEMY_DATABASE_URI = DB_URI
SQLALCHEMY_TRACK_MODIFICATIONS = True
SECRET_KEY = "sdasd54as56d4a65s4"
MAIL_SERVER = "smtp.qq.com"
MAIL_PORT = 465
MAIL_USE_TLS = False
MAIL_USE_SSL = True
MAIL_DEBUG = True
MAIL_USERNAME = "发件邮箱"
MAIL_PASSWORD = "授权码"
MAIL_DEFAULT_SENDER = "默认发件邮箱"
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
decorators.py
from flask import g, redirect, url_for
from functools import wraps
def login_required(func):
@wraps(func)
def wrapper(*args,**kwargs):
if hasattr(g,'user'):
return func(*args,**kwargs)
else:
return redirect(url_for("user.login"))
return wrapper
exts.py
from flask_sqlalchemy import SQLAlchemy
from flask_mail import Mail
db = SQLAlchemy()
mail = Mail()
models.py
from exts import db
from datetime import datetime
class EmailCaptchaModel(db.Model):
__tablename__ = "email_captcha"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
email = db.Column(db.String(100), nullable=False, unique=True)
captcha = db.Column(db.String(10), nullable=False)
creat_time = db.Column(db.DateTime, default=datetime.now)
class UserModel(db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(200), nullable=False, unique=True)
email = db.Column(db.String(100), nullable=False, unique=True)
password = db.Column(db.String(200), nullable=False)
join_time = db.Column(db.DateTime, default=datetime.now)
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20