diff --git a/README.md b/README.md
index 83c478721b786e6158e4a7fe4e3667940a9aa0a4..2470c974ca8272ae3c9204dbe1972f5df04e5983 100644
--- a/README.md
+++ b/README.md
@@ -9,4 +9,10 @@
在V1.0的基础上进一步实现CMS用户登录、错误信息返回、登录限制和CSRF保护、CMS用户名渲染和注销、CMS个人页面和模板抽离等功能,进一步丰富后台管理功能。
#### V1.2
-在V1.1的基础上实现后台修改密码布局、通过Ajax实现局部更新修改密码、优化Json数据返回、sweetalert美化提示框、修改邮箱界面搭建功能。
\ No newline at end of file
+在V1.1的基础上实现后台修改密码布局、通过Ajax实现局部更新修改密码、优化Json数据返回、sweetalert美化提示框、修改邮箱界面搭建功能。
+
+#### V1.3
+在V1.2的时候进一步完善,首先实现在Flask中发送邮件,并进一步定义发送验证码,并进一步实现修改邮箱,还对权限和角色模型进行了定义。
+
+注意:
+在配置文件config.md中需要将自己的邮箱信息输入,才能正常实现其功能。
\ No newline at end of file
diff --git a/__pycache__/bbs.cpython-37.pyc b/__pycache__/bbs.cpython-37.pyc
index 1565c985d70c85eefe392a043c03a64f20ee6cdd..c3e0c93d0a61e54f08b1452ad577153489dcbeb3 100644
Binary files a/__pycache__/bbs.cpython-37.pyc and b/__pycache__/bbs.cpython-37.pyc differ
diff --git a/__pycache__/config.cpython-37.pyc b/__pycache__/config.cpython-37.pyc
index 0182dafded4ec66f58274cff5085805a43e7a128..7b267f4e5121105fb21b8b6d07706955f938fe93 100644
Binary files a/__pycache__/config.cpython-37.pyc and b/__pycache__/config.cpython-37.pyc differ
diff --git a/__pycache__/exts.cpython-37.pyc b/__pycache__/exts.cpython-37.pyc
index 152bcd752c884be9e1f54bf2f2ff40469f3df2b7..b344fe0fde0cb11aec8966f9db7f91f476ba3729 100644
Binary files a/__pycache__/exts.cpython-37.pyc and b/__pycache__/exts.cpython-37.pyc differ
diff --git a/apps/cms/__pycache__/forms.cpython-37.pyc b/apps/cms/__pycache__/forms.cpython-37.pyc
index a138161c24758284592bf95956acd4b2b9cc6d1f..6410862a744dcc1003ca2e2df60ef1adb0ec3162 100644
Binary files a/apps/cms/__pycache__/forms.cpython-37.pyc and b/apps/cms/__pycache__/forms.cpython-37.pyc differ
diff --git a/apps/cms/__pycache__/models.cpython-37.pyc b/apps/cms/__pycache__/models.cpython-37.pyc
index f0af941081ee0ba3cbabbf4378ac8cfd265c2ecd..e4ad6e93fe1d448584a0cae4cd74b530e78414b9 100644
Binary files a/apps/cms/__pycache__/models.cpython-37.pyc and b/apps/cms/__pycache__/models.cpython-37.pyc differ
diff --git a/apps/cms/__pycache__/views.cpython-37.pyc b/apps/cms/__pycache__/views.cpython-37.pyc
index 2f049967165481a6e02bbd328a2d0b8022c454f6..c7dfada11ffcdb7e7809d4d27718e18662c2fb02 100644
Binary files a/apps/cms/__pycache__/views.cpython-37.pyc and b/apps/cms/__pycache__/views.cpython-37.pyc differ
diff --git a/apps/cms/forms.py b/apps/cms/forms.py
index de7c6fac47d760bc83ed804edd5d06cd584a3bc4..98477f5b50d962d6f00bb1b347efcc23270b5925 100644
--- a/apps/cms/forms.py
+++ b/apps/cms/forms.py
@@ -1,5 +1,6 @@
-from wtforms import Form, StringField, IntegerField
+from wtforms import Form, StringField, IntegerField, ValidationError
from wtforms.validators import Email, InputRequired, Length, EqualTo
+from utils import clcache
class BaseForm(Form):
@@ -17,4 +18,16 @@ class LoginForm(BaseForm):
class ResetPwdForm(BaseForm):
oldpwd = StringField(validators=[Length(6, 20, message='密码长度应介于6-20')])
newpwd = StringField(validators=[Length(6, 20, message='密码长度应介于6-20')])
- newpwd2 = StringField(validators=[EqualTo("newpwd", message='两次输入密码不一致')])
\ No newline at end of file
+ newpwd2 = StringField(validators=[EqualTo("newpwd", message='两次输入密码不一致')])
+
+
+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
diff --git a/apps/cms/models.py b/apps/cms/models.py
index a5b91406b2a6ce63e22d231243085e80e682be81..64757efbaad8c909ec4db11450cc40ef07e65ef6 100644
--- a/apps/cms/models.py
+++ b/apps/cms/models.py
@@ -30,4 +30,47 @@ class CMSUser(db.Model):
def check_password(self, raw_password):
'''验证密码是否正确'''
result = check_password_hash(self.password, raw_password)
- return result
\ No newline at end of file
+ return result
+
+
+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
diff --git a/apps/cms/views.py b/apps/cms/views.py
index e9590333078f9c67f1505e0b7813a2ee728eb8a6..7b908938c07bc0361f0baa33fcfcf738ace3d8c7 100644
--- a/apps/cms/views.py
+++ b/apps/cms/views.py
@@ -1,8 +1,9 @@
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 exts import db
-from utils import restful
+from exts import db, mail
+from utils import restful, random_captcha, clcache
# from .decorators import login_required # .代表当前路径
@@ -87,9 +88,35 @@ class ResetEmailView(views.MethodView):
return render_template('cms/cms_resetemail.html')
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('/resetpwd/', view_func=ResetPwdView.as_view('resetpwd'))
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'))
diff --git a/bbs.py b/bbs.py
index 75d952a675adc0df8ad052e35a4b6c05899bba21..d96161dd1efb9e11bf560d5190e6bbd44ceeb663 100644
--- a/bbs.py
+++ b/bbs.py
@@ -6,7 +6,7 @@
from flask import Flask
from flask_wtf import CSRFProtect
-from exts import db
+from exts import db, mail
from apps.cms.views import cms_bp
from apps.front.views import front_bp
import config
@@ -15,8 +15,8 @@ import config
app = Flask(__name__)
CSRFProtect(app)
app.config.from_object(config)
-app.config['TEMPLATE_AUTO_RELOAD'] = True
db.init_app(app)
+mail.init_app(app)
app.register_blueprint(cms_bp)
app.register_blueprint(front_bp)
diff --git a/config.py b/config.py
index ec2cd3e4db5bd6b631d53a7abd8f964595ed3be2..034f12ef9b74ef57ee3b5d52f5ffc004bbc70524 100644
--- a/config.py
+++ b/config.py
@@ -8,10 +8,24 @@ PASSWORD = 'root'
DATABASE = 'flask_bbs'
DB_URL = 'mysql+mysqlconnector://{}:{}@{}:{}/{}?charset=utf8'.format(USERNAME, PASSWORD, HOSTNAME, PORT, DATABASE)
+TEMPLATE_AUTO_RELOAD = True
SQLALCHEMY_DATABASE_URI = DB_URL
SQLALCHEMY_TRACK_MODIFICATIONS = False
SQLALCHEMY_POOL_RECYCLE = 280
SQLALCHEMY_POOL_SIZE = 20
# 设置密钥
-SECRET_KEY = os.urandom(15)
\ No newline at end of file
+SECRET_KEY = os.urandom(15)
+
+# 发送邮箱服务地址
+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
diff --git a/exts.py b/exts.py
index 2e1eeb63ff7528d9ae956c7700724bf9989c1fe1..2f87add254338173835f5115c4822b1fb2c4ff41 100644
--- a/exts.py
+++ b/exts.py
@@ -1,3 +1,6 @@
from flask_sqlalchemy import SQLAlchemy
+from flask_mail import Mail
-db = SQLAlchemy()
\ No newline at end of file
+db = SQLAlchemy()
+
+mail = Mail()
\ No newline at end of file
diff --git a/manage.py b/manage.py
index fe652a758f7d11ee1cbfe65d03600ce2fe5113d1..5c32591497bc27c3a7a1369f9135ce758457d1c1 100644
--- a/manage.py
+++ b/manage.py
@@ -2,7 +2,7 @@ from flask_script import Manager
from bbs import app
from flask_migrate import Migrate, MigrateCommand
from exts import db
-from apps.cms.models import CMSUser
+from apps.cms.models import CMSUser, CMSRole
manager = Manager(app)
Migrate(app, db)
diff --git a/migrations/versions/__pycache__/ba80d307aa21_.cpython-37.pyc b/migrations/versions/__pycache__/ba80d307aa21_.cpython-37.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..108b9ec3bcb75c7d59248158beaaefedf21c4ec3
Binary files /dev/null and b/migrations/versions/__pycache__/ba80d307aa21_.cpython-37.pyc differ
diff --git a/migrations/versions/ba80d307aa21_.py b/migrations/versions/ba80d307aa21_.py
new file mode 100644
index 0000000000000000000000000000000000000000..76dfbf5ad7030ecdccb783f6dd5106074b9174b4
--- /dev/null
+++ b/migrations/versions/ba80d307aa21_.py
@@ -0,0 +1,43 @@
+"""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 ###
diff --git a/requirements.txt b/requirements.txt
index 94229556a09b80241dfee36cca4dbf8e07c53480..1362bbceb1756233cf34d116364b70adf788ade7 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -23,6 +23,7 @@ PyMySQL==0.9.3
python-dateutil==2.8.1
python-editor==1.0.4
pytz==2020.1
+redis==3.5.3
six==1.14.0
SQLAlchemy==1.3.16
Werkzeug==1.0.1
diff --git a/static/cms/css/cms_base.css b/static/cms/css/cms_base.css
index e45c29a930af4d71fef5ce01aad4db872688323f..5a176924dca37f5804c001bd5eeba9c9ae4aecd8 100644
--- a/static/cms/css/cms_base.css
+++ b/static/cms/css/cms_base.css
@@ -195,4 +195,8 @@ body {
border-radius: 2px;
background: #ecedf0;
overflow: hidden;
+}
+
+.form-container {
+ width: 300px;
}
\ No newline at end of file
diff --git a/static/cms/js/resetemail.js b/static/cms/js/resetemail.js
index ed4ed45db328d93d17f526887ddb223282863fa8..924d71270be2a9665a7f719b50b86c6fff410980 100644
--- a/static/cms/js/resetemail.js
+++ b/static/cms/js/resetemail.js
@@ -1,4 +1,4 @@
-var lgajax = {
+var clajax = {
'get': function (args) {
args['method'] = 'get';
this.ajax(args);
@@ -28,10 +28,10 @@ $(function () {
event.preventDefault();
var email = $("input[name='email']").val();
if (!email) {
- lgalert.alertInfoToast('请输入邮箱');
+ clalert.alertInfoToast('请输入邮箱');
return;
}
- var lgajax = {
+ var clajax = {
'get': function (args) {
args['method'] = 'get';
this.ajax(args);
@@ -56,20 +56,20 @@ $(function () {
});
}
};
- lgajax.get({
+ clajax.get({
'url': '/cms/email_captcha/',
'data': {
'email': email
},
'success': function (data) {
- if (data['code'] == 200) {
- lgalert.alertSuccessToast('邮件发送成功!请注意查收!');
+ if (data['code'] === 200) {
+ clalert.alertSuccessToast('邮件发送成功!请注意查收!');
} else {
- lgalert.alertInfo(data['message']);
+ clalert.alertInfo(data['message']);
}
},
'fail': function (error) {
- lgalert.alertNetworkError();
+ clalert.alertNetworkError();
}
});
});
@@ -84,23 +84,23 @@ $(function () {
var email = emailE.val();
var captcha = captchaE.val();
- lgajax.post({
+ clajax.post({
'url': '/cms/resetemail/',
'data': {
'email': email,
'captcha': captcha
},
'success': function (data) {
- if (data['code'] == 200) {
+ if (data['code'] === 200) {
emailE.val("");
captchaE.val("");
- lgalert.alertSuccessToast('恭喜!邮箱修改成功!');
+ clalert.alertSuccessToast('恭喜!邮箱修改成功!');
} else {
- lgalert.alertInfo(data['message']);
+ clalert.alertInfo(data['message']);
}
},
'fail': function (error) {
- lgalert.alertNetworkError();
+ clalert.alertNetworkError();
}
});
});
diff --git a/templates/cms/cms_resetemail.html b/templates/cms/cms_resetemail.html
index b7d5a4f8635d77a6416e0d5a759e9325e7d5eac5..17e8d38a7ca75b82fd113329ee69a85a10bf21ff 100644
--- a/templates/cms/cms_resetemail.html
+++ b/templates/cms/cms_resetemail.html
@@ -9,11 +9,7 @@
{% endblock %}
{% block head %}
-
+
{% endblock %}
{% block content %}
diff --git a/templates/cms/cms_resetpwd.html b/templates/cms/cms_resetpwd.html
index 515fc677707b12ad98c92be077f69e599fe1f293..8acfd683115c21de32fce552f4e4c8f8afb574e1 100644
--- a/templates/cms/cms_resetpwd.html
+++ b/templates/cms/cms_resetpwd.html
@@ -9,11 +9,6 @@
{% endblock %}
{% block head %}
-
{% endblock %}
diff --git a/utils/__pycache__/clcache.cpython-37.pyc b/utils/__pycache__/clcache.cpython-37.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0db45f82223afdd6daa0e05c45cea8e2b3a33475
Binary files /dev/null and b/utils/__pycache__/clcache.cpython-37.pyc differ
diff --git a/utils/__pycache__/random_captcha.cpython-37.pyc b/utils/__pycache__/random_captcha.cpython-37.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..197caa29e14d21205b1ab903a7d3e51072361f7c
Binary files /dev/null and b/utils/__pycache__/random_captcha.cpython-37.pyc differ
diff --git a/utils/clcache.py b/utils/clcache.py
new file mode 100644
index 0000000000000000000000000000000000000000..93704d2d736c2300dbe52a4d4ec78d25d3315592
--- /dev/null
+++ b/utils/clcache.py
@@ -0,0 +1,18 @@
+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)
diff --git a/utils/random_captcha.py b/utils/random_captcha.py
new file mode 100644
index 0000000000000000000000000000000000000000..157e6d9451a2dd0a1c35b65c58da5bd348e84789
--- /dev/null
+++ b/utils/random_captcha.py
@@ -0,0 +1,14 @@
+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