提交 65e72ce0 编写于 作者: C Corley

V1.3

上级 40ff2bc7
...@@ -9,4 +9,10 @@ ...@@ -9,4 +9,10 @@
在V1.0的基础上进一步实现CMS用户登录、错误信息返回、登录限制和CSRF保护、CMS用户名渲染和注销、CMS个人页面和模板抽离等功能,进一步丰富后台管理功能。 在V1.0的基础上进一步实现CMS用户登录、错误信息返回、登录限制和CSRF保护、CMS用户名渲染和注销、CMS个人页面和模板抽离等功能,进一步丰富后台管理功能。
#### V1.2 #### V1.2
在V1.1的基础上实现后台修改密码布局、通过Ajax实现局部更新修改密码、优化Json数据返回、sweetalert美化提示框、修改邮箱界面搭建功能。 在V1.1的基础上实现后台修改密码布局、通过Ajax实现局部更新修改密码、优化Json数据返回、sweetalert美化提示框、修改邮箱界面搭建功能。
\ No newline at end of file
#### V1.3
在V1.2的时候进一步完善,首先实现在Flask中发送邮件,并进一步定义发送验证码,并进一步实现修改邮箱,还对权限和角色模型进行了定义。
注意:
在配置文件config.md中需要将自己的邮箱信息输入,才能正常实现其功能。
\ No newline at end of file
from wtforms import Form, StringField, IntegerField from wtforms import Form, StringField, IntegerField, ValidationError
from wtforms.validators import Email, InputRequired, Length, EqualTo from wtforms.validators import Email, InputRequired, Length, EqualTo
from utils import clcache
class BaseForm(Form): class BaseForm(Form):
...@@ -17,4 +18,16 @@ class LoginForm(BaseForm): ...@@ -17,4 +18,16 @@ class LoginForm(BaseForm):
class ResetPwdForm(BaseForm): class ResetPwdForm(BaseForm):
oldpwd = StringField(validators=[Length(6, 20, message='密码长度应介于6-20')]) oldpwd = StringField(validators=[Length(6, 20, message='密码长度应介于6-20')])
newpwd = StringField(validators=[Length(6, 20, message='密码长度应介于6-20')]) newpwd = StringField(validators=[Length(6, 20, message='密码长度应介于6-20')])
newpwd2 = StringField(validators=[EqualTo("newpwd", message='两次输入密码不一致')]) newpwd2 = StringField(validators=[EqualTo("newpwd", message='两次输入密码不一致')])
\ No newline at end of file
class ResetEmailForm(BaseForm):
email = StringField(validators=[Email(message='您的邮箱格式有误,请重新输入')])
captcha = StringField(validators=[Length(4, 4, message='请输入4位验证码')])
def validate_captcha(self, form):
captcha = self.captcha.data
email = self.email.data
redis_captcha = clcache.get_captcha(email)
if not redis_captcha or captcha.lower() != redis_captcha.lower():
raise ValidationError('邮箱验证码错误')
\ No newline at end of file
...@@ -30,4 +30,47 @@ class CMSUser(db.Model): ...@@ -30,4 +30,47 @@ class CMSUser(db.Model):
def check_password(self, raw_password): def check_password(self, raw_password):
'''验证密码是否正确''' '''验证密码是否正确'''
result = check_password_hash(self.password, raw_password) result = check_password_hash(self.password, raw_password)
return result return result
\ No newline at end of file
class CMSPermission(object):
# 255二进制表示所有权限
ALL_PERMISSION = 0b11111111
# 访问权限
VISITOR = 0b00000001
# 管理帖子权限
POSTER = 0b00000010
# 管理评论权限
COMMENTER = 0b00000100
# 管理板块
BOARDER = 0b00001000
# 管理前台用户
FRONTUSER = 0b00010000
# 管理前台用户
CMSUSER = 0b00100000
# 管理前台用户
ADMINER = 0b01000000
cms_role_user = db.Table(
'cms_role_user',
db.Column('cms_role_id', db.Integer, db.ForeignKey('cms_role.id'), primary_key=True),
db.Column('cms_user_id', db.Integer, db.ForeignKey('cms_user.id'), primary_key=True)
)
class CMSRole(db.Model):
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(50), nullable=False)
desc = db.Column(db.String(200), nullable=False)
create_time = db.Column(db.DateTime, default=datetime.now())
permissions = db.Column(db.Integer, default=CMSPermission.VISITOR)
users = db.relationship('CMSUser', secondary=cms_role_user, backref='roles')
\ No newline at end of file
from flask import Blueprint, render_template, views, request, redirect, url_for, session, g from flask import Blueprint, render_template, views, request, redirect, url_for, session, g
from apps.cms.forms import LoginForm, ResetPwdForm from flask_mail import Message
from apps.cms.forms import LoginForm, ResetPwdForm, ResetEmailForm
from apps.cms.models import CMSUser from apps.cms.models import CMSUser
from exts import db from exts import db, mail
from utils import restful from utils import restful, random_captcha, clcache
# from .decorators import login_required # .代表当前路径 # from .decorators import login_required # .代表当前路径
...@@ -87,9 +88,35 @@ class ResetEmailView(views.MethodView): ...@@ -87,9 +88,35 @@ class ResetEmailView(views.MethodView):
return render_template('cms/cms_resetemail.html') return render_template('cms/cms_resetemail.html')
def post(self): def post(self):
pass form = ResetEmailForm(request.form)
if form.validate():
email = form.email.data
# 修改用户邮箱
g.cms_user.email = email
db.session.commit()
return restful.success()
else:
return restful.params_error(form.get_error())
class EmailCaptchaView(views.MethodView):
def get(self):
email = request.args.get('email')
if not email:
return restful.params_error('请传递邮箱参数')
# 发送邮件验证码,可以是4位或6位的数字与英文组合
captcha = random_captcha.get_random_captcha(4)
try:
message = Message('熊熊论坛验证码', recipients=[email], body='您正在进行更改邮箱验证,验证码是%s,5分钟内有效,请及时输入、注意保密。' % captcha)
mail.send(message)
except Exception as e:
print(e.args[0])
return restful.server_error('邮件发送异常,请检查重试')
clcache.save_captcha(email, captcha)
return restful.success(message='邮件发送成功,请注意接收验证码')
cms_bp.add_url_rule('/login/', view_func=LoginView.as_view('login')) cms_bp.add_url_rule('/login/', view_func=LoginView.as_view('login'))
cms_bp.add_url_rule('/resetpwd/', view_func=ResetPwdView.as_view('resetpwd')) cms_bp.add_url_rule('/resetpwd/', view_func=ResetPwdView.as_view('resetpwd'))
cms_bp.add_url_rule('/resetemail/', view_func=ResetEmailView.as_view('resetemail')) cms_bp.add_url_rule('/resetemail/', view_func=ResetEmailView.as_view('resetemail'))
cms_bp.add_url_rule('/email_captcha/', view_func=EmailCaptchaView.as_view('email_captcha'))
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
from flask import Flask from flask import Flask
from flask_wtf import CSRFProtect from flask_wtf import CSRFProtect
from exts import db from exts import db, mail
from apps.cms.views import cms_bp from apps.cms.views import cms_bp
from apps.front.views import front_bp from apps.front.views import front_bp
import config import config
...@@ -15,8 +15,8 @@ import config ...@@ -15,8 +15,8 @@ import config
app = Flask(__name__) app = Flask(__name__)
CSRFProtect(app) CSRFProtect(app)
app.config.from_object(config) app.config.from_object(config)
app.config['TEMPLATE_AUTO_RELOAD'] = True
db.init_app(app) db.init_app(app)
mail.init_app(app)
app.register_blueprint(cms_bp) app.register_blueprint(cms_bp)
app.register_blueprint(front_bp) app.register_blueprint(front_bp)
......
...@@ -8,10 +8,24 @@ PASSWORD = 'root' ...@@ -8,10 +8,24 @@ PASSWORD = 'root'
DATABASE = 'flask_bbs' DATABASE = 'flask_bbs'
DB_URL = 'mysql+mysqlconnector://{}:{}@{}:{}/{}?charset=utf8'.format(USERNAME, PASSWORD, HOSTNAME, PORT, DATABASE) DB_URL = 'mysql+mysqlconnector://{}:{}@{}:{}/{}?charset=utf8'.format(USERNAME, PASSWORD, HOSTNAME, PORT, DATABASE)
TEMPLATE_AUTO_RELOAD = True
SQLALCHEMY_DATABASE_URI = DB_URL SQLALCHEMY_DATABASE_URI = DB_URL
SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_POOL_RECYCLE = 280 SQLALCHEMY_POOL_RECYCLE = 280
SQLALCHEMY_POOL_SIZE = 20 SQLALCHEMY_POOL_SIZE = 20
# 设置密钥 # 设置密钥
SECRET_KEY = os.urandom(15) SECRET_KEY = os.urandom(15)
\ No newline at end of file
# 发送邮箱服务地址
MAIL_SERVER = 'smtp.qq.com'
# 邮箱端口,为587或465,为587时TLS设置为True,为465时SSL设置为True
MAIL_PORT = 587
MAIL_USE_TLS = True
# MAIL_USE_SSL = True # MAIL_PORT为465时设置此项
# 用户名可以为你的邮箱,需要自行添加
MAIL_USERNAME = '123456789@qq.com'
# 邮箱密码,不是邮箱账号密码,而是第三方客户端登录使用的授权码,需要自行获取
MAIL_PASSWORD = 'xxxxxxxxxxxxxxxx'
# 发送者即你的邮箱,需要自行添加
MAIL_DEFAULT_SENDER = '123456789@qq.com'
\ No newline at end of file
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from flask_mail import Mail
db = SQLAlchemy() db = SQLAlchemy()
\ No newline at end of file
mail = Mail()
\ No newline at end of file
...@@ -2,7 +2,7 @@ from flask_script import Manager ...@@ -2,7 +2,7 @@ from flask_script import Manager
from bbs import app from bbs import app
from flask_migrate import Migrate, MigrateCommand from flask_migrate import Migrate, MigrateCommand
from exts import db from exts import db
from apps.cms.models import CMSUser from apps.cms.models import CMSUser, CMSRole
manager = Manager(app) manager = Manager(app)
Migrate(app, db) Migrate(app, db)
......
"""empty message
Revision ID: ba80d307aa21
Revises: f7a47bdbaa71
Create Date: 2020-06-05 20:35:29.305779
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'ba80d307aa21'
down_revision = 'f7a47bdbaa71'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('cms_role',
sa.Column('id', sa.Integer(), autoincrement=True, nullable=False),
sa.Column('name', sa.String(length=50), nullable=False),
sa.Column('desc', sa.String(length=200), nullable=False),
sa.Column('create_time', sa.DateTime(), nullable=True),
sa.Column('permissions', sa.Integer(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_table('cms_role_user',
sa.Column('cms_role_id', sa.Integer(), nullable=False),
sa.Column('cms_user_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['cms_role_id'], ['cms_role.id'], ),
sa.ForeignKeyConstraint(['cms_user_id'], ['cms_user.id'], ),
sa.PrimaryKeyConstraint('cms_role_id', 'cms_user_id')
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('cms_role_user')
op.drop_table('cms_role')
# ### end Alembic commands ###
...@@ -23,6 +23,7 @@ PyMySQL==0.9.3 ...@@ -23,6 +23,7 @@ PyMySQL==0.9.3
python-dateutil==2.8.1 python-dateutil==2.8.1
python-editor==1.0.4 python-editor==1.0.4
pytz==2020.1 pytz==2020.1
redis==3.5.3
six==1.14.0 six==1.14.0
SQLAlchemy==1.3.16 SQLAlchemy==1.3.16
Werkzeug==1.0.1 Werkzeug==1.0.1
......
...@@ -195,4 +195,8 @@ body { ...@@ -195,4 +195,8 @@ body {
border-radius: 2px; border-radius: 2px;
background: #ecedf0; background: #ecedf0;
overflow: hidden; overflow: hidden;
}
.form-container {
width: 300px;
} }
\ No newline at end of file
var lgajax = { var clajax = {
'get': function (args) { 'get': function (args) {
args['method'] = 'get'; args['method'] = 'get';
this.ajax(args); this.ajax(args);
...@@ -28,10 +28,10 @@ $(function () { ...@@ -28,10 +28,10 @@ $(function () {
event.preventDefault(); event.preventDefault();
var email = $("input[name='email']").val(); var email = $("input[name='email']").val();
if (!email) { if (!email) {
lgalert.alertInfoToast('请输入邮箱'); clalert.alertInfoToast('请输入邮箱');
return; return;
} }
var lgajax = { var clajax = {
'get': function (args) { 'get': function (args) {
args['method'] = 'get'; args['method'] = 'get';
this.ajax(args); this.ajax(args);
...@@ -56,20 +56,20 @@ $(function () { ...@@ -56,20 +56,20 @@ $(function () {
}); });
} }
}; };
lgajax.get({ clajax.get({
'url': '/cms/email_captcha/', 'url': '/cms/email_captcha/',
'data': { 'data': {
'email': email 'email': email
}, },
'success': function (data) { 'success': function (data) {
if (data['code'] == 200) { if (data['code'] === 200) {
lgalert.alertSuccessToast('邮件发送成功!请注意查收!'); clalert.alertSuccessToast('邮件发送成功!请注意查收!');
} else { } else {
lgalert.alertInfo(data['message']); clalert.alertInfo(data['message']);
} }
}, },
'fail': function (error) { 'fail': function (error) {
lgalert.alertNetworkError(); clalert.alertNetworkError();
} }
}); });
}); });
...@@ -84,23 +84,23 @@ $(function () { ...@@ -84,23 +84,23 @@ $(function () {
var email = emailE.val(); var email = emailE.val();
var captcha = captchaE.val(); var captcha = captchaE.val();
lgajax.post({ clajax.post({
'url': '/cms/resetemail/', 'url': '/cms/resetemail/',
'data': { 'data': {
'email': email, 'email': email,
'captcha': captcha 'captcha': captcha
}, },
'success': function (data) { 'success': function (data) {
if (data['code'] == 200) { if (data['code'] === 200) {
emailE.val(""); emailE.val("");
captchaE.val(""); captchaE.val("");
lgalert.alertSuccessToast('恭喜!邮箱修改成功!'); clalert.alertSuccessToast('恭喜!邮箱修改成功!');
} else { } else {
lgalert.alertInfo(data['message']); clalert.alertInfo(data['message']);
} }
}, },
'fail': function (error) { 'fail': function (error) {
lgalert.alertNetworkError(); clalert.alertNetworkError();
} }
}); });
}); });
......
...@@ -9,11 +9,7 @@ ...@@ -9,11 +9,7 @@
{% endblock %} {% endblock %}
{% block head %} {% block head %}
<style> <script src="{{ url_for('static', filename='cms/js/resetemail.js') }}"></script>
.form-container {
width: 300px;
}
</style>
{% endblock %} {% endblock %}
{% block content %} {% block content %}
......
...@@ -9,11 +9,6 @@ ...@@ -9,11 +9,6 @@
{% endblock %} {% endblock %}
{% block head %} {% block head %}
<style>
.form-container {
width: 300px;
}
</style>
<script src="{{ url_for('static', filename='cms/js/resetpwd.js') }}"></script> <script src="{{ url_for('static', filename='cms/js/resetpwd.js') }}"></script>
{% endblock %} {% endblock %}
......
import redis
# 连接Redis数据库
r = redis.StrictRedis(host='localhost', port=6379, db=0, decode_responses=True)
def save_captcha(key, value, timeout=300):
'''把验证码存到Redis'''
return r.set(key, value, timeout)
def get_captcha(key):
'''从Redis中取验证码'''
return r.get(key)
def delete_captcha(key):
return r.delete(key)
import random
def get_random_captcha(num):
'''生成随机验证码'''
code = ''
for i in range(num):
num = str(random.randint(0, 9))
upper = chr(random.randint(65, 90))
lower = chr(random.randint(97, 122))
lst = [num, upper, lower]
ret = random.choice(lst)
code = ''.join([code, ret])
return code
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册