提交 6bc6f0a0 编写于 作者: C Corley

'V1.5'

上级 955c40fe
......@@ -18,4 +18,7 @@
在配置文件config.md中需要将自己的邮箱信息输入,才能正常实现其功能。
#### V1.4
在V1.3的基础上进一步完善权限验证功能,首先在manage.py中实现添加用户角色,再实现页面修改,最后在客户端和服务端进行双重权限验证,是吸纳了权限验证的基本功能。
\ No newline at end of file
在V1.3的基础上进一步完善权限验证功能,首先在manage.py中实现添加用户角色,再实现页面修改,最后在客户端和服务端进行双重权限验证,是吸纳了权限验证的基本功能。
#### V1.5
这一版本开始进入前台开发阶段,首先定义前台的用户模型,并在此基础上搭建前台注册页面和完成图形验证码类,再实现点击更换图形验证码的功能,接下来实现发送短信验证码的功能,并实现短信验证码接口的MD5加密和JS加密代码的加密。
\ No newline at end of file
from wtforms import Form, StringField
from wtforms.validators import InputRequired, regexp
import hashlib
class SMSCaptchaForm(Form):
telephone = StringField(validators=[regexp(r'1[3-9]\d{9}')])
timestamp = StringField(validators=[regexp(r'\d{13}')])
sign = StringField(validators=[InputRequired()])
def validate_sign(self, field):
'''验证前端发送过来的sign与后端用同样的加密方式生成的sign是否一致'''
telephone = self.telephone.data
timestamp = self.timestamp.data
# 前端传来的sign
sign = self.sign.data
# 后端生成的sign
sign2 = hashlib.md5((timestamp + telephone + "q3423805gdflvbdfvhsdoa`#$%").encode('utf-8')).hexdigest()
print('Client Sign:', sign)
print('Server Sign:', sign2)
if sign == sign2:
return True
else:
return False
\ No newline at end of file
from flask import Blueprint, request
from utils import send_msg, restful
from utils.captcha import Captcha
from .forms import SMSCaptchaForm
common_bp = Blueprint('common', __name__, url_prefix='/c')
@common_bp.route('/sms_captcha/', methods=['POST'])
def sms_captcha():
form = SMSCaptchaForm(request.form)
if form.validate():
return restful.success()
else:
return restful.params_error(message='参数错误')
'''
telephone = request.form.get('telephone')
if not telephone:
return restful.params_error(message='请填写手机号码')
captcha = Captcha.gene_text(4)
if send_msg.send_mobile_msg(telephone, captcha) == 0:
return restful.success()
else:
return restful.params_error(message='发送失败')
'''
\ No newline at end of file
import shortuuid
import enum
from werkzeug.security import generate_password_hash, check_password_hash
from datetime import datetime
from exts import db
class GenderEnum(enum.Enum):
MALE = 1
FAMALE = 2
SECRET = 3
UNKNOWN = 4
class FrontUser(db.Model):
__tablename__ = 'front_user'
id = db.Column(db.String(40), primary_key=True, default=shortuuid.uuid)
telephone = db.Column(db.String(11), nullable=False, unique=True)
username = db.Column(db.String(50), nullable=False)
_password = db.Column(db.String(100), nullable=False)
email = db.Column(db.String(100), unique=False)
realname = db.Column(db.String(30))
avatar = db.Column(db.String(80))
signature = db.Column(db.String(200))
gender = db.Column(db.Enum(GenderEnum), default=GenderEnum.UNKNOWN)
join_time = db.Column(db.DateTime, default=datetime.now)
def __init__(self, *args, **kwargs):
if 'password' in kwargs:
self.password = kwargs.get('password')
kwargs.pop('password')
super().__init__(*args, **kwargs)
@property
def password(self):
'''获取密码'''
return self._password
@password.setter
def password(self, raw_password):
'''设置密码'''
self._password = generate_password_hash(raw_password)
def check_password(self, raw_password):
'''验证密码是否正确'''
result = check_password_hash(self.password, raw_password)
return result
from flask import Blueprint
from flask import Blueprint, views, render_template, make_response
from io import BytesIO
from utils.captcha import Captcha
front_bp = Blueprint('front', __name__)
@front_bp.route('/')
def index():
return '前台首页'
\ No newline at end of file
return '前台首页'
@front_bp.route('/captcha/')
def graph_captcha():
try:
text, image = Captcha.gene_graph_captcha()
# 处理图片二进制流的传输
out = BytesIO()
# 把图片保存到字节流中,并指定格式为png
image.save(out, 'png')
# 指定文件流指针,从文件最开始开始读
out.seek(0)
# 将字节流包装到Response对象中,返回前端
resp = make_response(out.read())
resp.content_type = 'image/png'
except:
return graph_captcha()
return resp
class SignupView(views.MethodView):
def get(self):
return render_template('front/front_signup.html')
front_bp.add_url_rule('/signup/', view_func=SignupView.as_view('signup'))
......@@ -9,6 +9,7 @@ from flask_wtf import CSRFProtect
from exts import db, mail
from apps.cms.views import cms_bp
from apps.front.views import front_bp
from apps.common.views import common_bp
import config
......@@ -20,6 +21,7 @@ mail.init_app(app)
app.register_blueprint(cms_bp)
app.register_blueprint(front_bp)
app.register_blueprint(common_bp)
if __name__ == '__main__':
......
......@@ -26,6 +26,9 @@ MAIL_USE_TLS = True
# 用户名可以为你的邮箱,需要自行添加
MAIL_USERNAME = '379869029@qq.com'
# 邮箱密码,不是邮箱账号密码,而是第三方客户端登录使用的授权码,需要自行获取
MAIL_PASSWORD = 'vrxiqciwjnbcbiaj'
MAIL_PASSWORD = 'vrxiqciwjnbcxxxx'
# 发送者即你的邮箱,需要自行添加
MAIL_DEFAULT_SENDER = '379869029@qq.com'
\ No newline at end of file
MAIL_DEFAULT_SENDER = '379869029@qq.com'
# 云片APIKEY
YP_API = 'edf71361381f31b3957beda37f20xxxx'
\ No newline at end of file
......@@ -3,6 +3,7 @@ from bbs import app
from flask_migrate import Migrate, MigrateCommand
from exts import db
from apps.cms.models import CMSUser, CMSRole, CMSPermission
from apps.front.models import FrontUser
manager = Manager(app)
Migrate(app, db)
......@@ -68,5 +69,15 @@ def add_user_to_role(email, name):
print('邮箱不存在')
@manager.option('-t', '--telephone', dest='telephone')
@manager.option('-u', '--username', dest='username')
@manager.option('-p', '--password', dest='password')
def create_front_user(telephone, username, password):
user = FrontUser(telephone=telephone, username=username, password=password)
db.session.add(user)
db.session.commit()
print('前台用户添加成功')
if __name__ == '__main__':
manager.run()
"""empty message
Revision ID: 101f06ec28fb
Revises: 63b5250011d6
Create Date: 2020-06-23 18:23:01.583773
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '101f06ec28fb'
down_revision = '63b5250011d6'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('front_user',
sa.Column('id', sa.String(length=40), nullable=False),
sa.Column('telephone', sa.String(length=11), nullable=False),
sa.Column('username', sa.String(length=50), nullable=False),
sa.Column('_password', sa.String(length=100), nullable=False),
sa.Column('email', sa.String(length=100), nullable=True),
sa.Column('realname', sa.String(length=30), nullable=True),
sa.Column('avatar', sa.String(length=80), nullable=True),
sa.Column('signature', sa.String(length=200), nullable=True),
sa.Column('gender', sa.Enum('MALE', 'FAMALE', 'SECRET', 'UNKNOWN', name='genderenum'), nullable=True),
sa.Column('join_time', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('telephone')
)
op.create_foreign_key(None, 'cms_role_user', 'cms_user', ['cms_user_id'], ['id'])
op.create_foreign_key(None, 'cms_role_user', 'cms_role', ['cms_role_id'], ['id'])
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint(None, 'cms_role_user', type_='foreignkey')
op.drop_constraint(None, 'cms_role_user', type_='foreignkey')
op.drop_table('front_user')
# ### end Alembic commands ###
alembic==1.4.2
aniso8601==8.0.0
blinker==1.4
certifi==2020.6.20
chardet==3.0.4
click==7.1.1
dnspython==1.16.0
email-validator==1.1.1
......@@ -18,13 +20,18 @@ Mako==1.1.2
MarkupSafe==1.1.1
mysql-connector==2.2.9
mysql-connector-python==8.0.19
Pillow==7.1.2
protobuf==3.6.1
PyMySQL==0.9.3
python-dateutil==2.8.1
python-editor==1.0.4
pytz==2020.1
redis==3.5.3
requests==2.24.0
shortuuid==1.0.1
six==1.14.0
SQLAlchemy==1.3.16
urllib3==1.25.9
Werkzeug==1.0.1
WTForms==2.3.1
yunpian-python-sdk==1.0.0
body {
background: #f3f3f3;
}
.outer-box {
width: 854px;
background: #fff;
margin: 0 auto;
overflow: hidden;
}
.logo-box {
text-align: center;
padding-top: 40px;
}
.logo-box img {
width: 60px;
height: 60px;
}
.page-title {
text-align: center;
}
.sign-box {
width: 300px;
margin: 0 auto;
padding-top: 50px;
}
.captcha-addon {
padding: 0;
overflow: hidden;
}
.captcha-img {
width: 94px;
height: 32px;
cursor: pointer;
}
.captcha-addon {
padding: 0;
/*溢出隐藏*/
overflow: hidden;
}
.captcha-img {
width: 94px;
height: 32px;
cursor: pointer;
}
\ No newline at end of file
var clajax = {
'get': function (args) {
args['method'] = 'get';
this.ajax(args);
},
'post': function (args) {
args['method'] = 'post';
this.ajax(args);
},
'ajax': function (args) {
// 设置csrftoken
this._ajaxSetup();
$.ajax(args);
},
'_ajaxSetup': function () {
$.ajaxSetup({
'beforeSend': function (xhr, settings) {
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
var csrftoken = $('meta[name=csrf-token]').attr('content');
xhr.setRequestHeader("X-CSRFToken", csrftoken)
}
}
});
}
};
$(function () {
$("#submit-btn").click(function (event) {
event.preventDefault();
var telephone_input = $("input[name='telephone']");
var password_input = $("input[name='password']");
var remember_input = $("input[name='remember']");
var telephone = telephone_input.val();
var password = password_input.val();
var remember = remember_input.checked ? 1 : 0;
clajax.post({
'url': '/signin/',
'data': {
'telephone': telephone,
'password': password,
'remember': remember
},
'success': function (data) {
if (data['code'] === 200) {
var return_to = $("#return-to-span").text();
if (return_to) {
window.location = return_to;
} else {
window.location = '/';
}
} else {
clalert.alertInfo(data['message']);
}
}
});
});
});
\ No newline at end of file
var param = {
setParam: function (href, key, value) {
// 重新加载整个页面
var isReplaced = false;
var urlArray = href.split('?');
if (urlArray.length > 1) {
var queryArray = urlArray[1].split('&');
for (var i = 0; i < queryArray.length; i++) {
var paramsArray = queryArray[i].split('=');
if (paramsArray[0] == key) {
paramsArray[1] = value;
queryArray[i] = paramsArray.join('=');
isReplaced = true;
break;
}
}
if (!isReplaced) {
var params = {};
params[key] = value;
if (urlArray.length > 1) {
href = href + '&' + $.param(params);
} else {
href = href + '?' + $.param(params);
}
} else {
var params = queryArray.join('&');
urlArray[1] = params;
href = urlArray.join('?');
}
} else {
var param = {};
param[key] = value;
if (urlArray.length > 1) {
href = href + '&' + $.param(param);
} else {
href = href + '?' + $.param(param);
}
}
return href;
}
};
var clajax = {
'get': function (args) {
args['method'] = 'get';
this.ajax(args);
},
'post': function (args) {
args['method'] = 'post';
this.ajax(args);
},
'ajax': function (args) {
// 设置csrftoken
this._ajaxSetup();
$.ajax(args);
},
'_ajaxSetup': function () {
$.ajaxSetup({
'beforeSend': function (xhr, settings) {
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
var csrftoken = $('meta[name=csrf-token]').attr('content');
xhr.setRequestHeader("X-CSRFToken", csrftoken)
}
}
});
}
};
$(function () {
$('#captcha-img').click(function (event) {
var self = $(this);
var src = self.attr('src');
var newsrc = param.setParam(src, 'cp', Math.random());
self.attr('src', newsrc);
});
});
$(function () {
var __encode ='sojson.com',_a={}, _0xb483=["\x5F\x64\x65\x63\x6F\x64\x65","\x68\x74\x74\x70\x3A\x2F\x2F\x77\x77\x77\x2E\x73\x6F\x6A\x73\x6F\x6E\x2E\x63\x6F\x6D\x2F\x6A\x61\x76\x61\x73\x63\x72\x69\x70\x74\x6F\x62\x66\x75\x73\x63\x61\x74\x6F\x72\x2E\x68\x74\x6D\x6C"];(function(_0xd642x1){_0xd642x1[_0xb483[0]]= _0xb483[1]})(_a);var __Ox89ca5=["\x70\x72\x65\x76\x65\x6E\x74\x44\x65\x66\x61\x75\x6C\x74","\x76\x61\x6C","\x69\x6E\x70\x75\x74\x5B\x6E\x61\x6D\x65\x3D\x27\x74\x65\x6C\x65\x70\x68\x6F\x6E\x65\x27\x5D","\x74\x65\x73\x74","\u8BF7\u8F93\u5165\u6B63\u786E\u7684\u624B\u673A\u53F7\u7801\uFF01","\x61\x6C\x65\x72\x74\x49\x6E\x66\x6F","\x67\x65\x74\x54\x69\x6D\x65","\x71\x33\x34\x32\x33\x38\x30\x35\x67\x64\x66\x6C\x76\x62\x64\x66\x76\x68\x73\x64\x6F\x61\x60\x23\x24\x25","\x2F\x63\x2F\x73\x6D\x73\x5F\x63\x61\x70\x74\x63\x68\x61\x2F","\x63\x6F\x64\x65","\u77ED\u4FE1\u9A8C\u8BC1\u7801\u53D1\u9001\u6210\u529F\uFF01","\x61\x6C\x65\x72\x74\x53\x75\x63\x63\x65\x73\x73\x54\x6F\x61\x73\x74","\x64\x69\x73\x61\x62\x6C\x65\x64","\x61\x74\x74\x72","\x74\x65\x78\x74","\x72\x65\x6D\x6F\x76\x65\x41\x74\x74\x72","\u53D1\u9001\u9A8C\u8BC1\u7801","\x6D\x65\x73\x73\x61\x67\x65","\x61\x6C\x65\x72\x74\x49\x6E\x66\x6F\x54\x6F\x61\x73\x74","\x70\x6F\x73\x74","\x63\x6C\x69\x63\x6B","\x23\x73\x6D\x73\x2D\x63\x61\x70\x74\x63\x68\x61\x2D\x62\x74\x6E","\x75\x6E\x64\x65\x66\x69\x6E\x65\x64","\x6C\x6F\x67","\u5220\u9664","\u7248\u672C\u53F7\uFF0C\x6A\x73\u4F1A\u5B9A\u671F\u5F39\u7A97\uFF0C","\u8FD8\u8BF7\u652F\u6301\u6211\u4EEC\u7684\u5DE5\u4F5C","\x73\x6F\x6A\x73","\x6F\x6E\x2E\x63\x6F\x6D"];$(__Ox89ca5[0x15])[__Ox89ca5[0x14]](function(_0x6286x1){_0x6286x1[__Ox89ca5[0x0]]();var _0x6286x2=$(this);var _0x6286x3=$(__Ox89ca5[0x2])[__Ox89ca5[0x1]]();if(!(/^1[345879]\d{9}$/[__Ox89ca5[0x3]](_0x6286x3))){clalert[__Ox89ca5[0x5]](__Ox89ca5[0x4]);return};var _0x6286x4=( new Date)[__Ox89ca5[0x6]]();var _0x6286x5=md5(_0x6286x4+ _0x6286x3+ __Ox89ca5[0x7]);clajax[__Ox89ca5[0x13]]({'\x75\x72\x6C':__Ox89ca5[0x8],'\x64\x61\x74\x61':{'\x74\x65\x6C\x65\x70\x68\x6F\x6E\x65':_0x6286x3,'\x74\x69\x6D\x65\x73\x74\x61\x6D\x70':_0x6286x4,'\x73\x69\x67\x6E':_0x6286x5},'\x73\x75\x63\x63\x65\x73\x73':function(_0x6286x6){if(_0x6286x6[__Ox89ca5[0x9]]=== 200){clalert[__Ox89ca5[0xb]](__Ox89ca5[0xa]);_0x6286x2[__Ox89ca5[0xd]](__Ox89ca5[0xc],__Ox89ca5[0xc]);var _0x6286x7=60;var _0x6286x8=setInterval(function(){_0x6286x7--;_0x6286x2[__Ox89ca5[0xe]](_0x6286x7);if(_0x6286x7<= 0){_0x6286x2[__Ox89ca5[0xf]](__Ox89ca5[0xc]);clearInterval(_0x6286x8);_0x6286x2[__Ox89ca5[0xe]](__Ox89ca5[0x10])}},1000)}else {clalert[__Ox89ca5[0x12]](_0x6286x6[__Ox89ca5[0x11]])}}})});;;(function(_0x6286x9,_0x6286xa,_0x6286xb,_0x6286xc,_0x6286xd,_0x6286xe){_0x6286xe= __Ox89ca5[0x16];_0x6286xc= function(_0x6286xf){if( typeof alert!== _0x6286xe){alert(_0x6286xf)};if( typeof console!== _0x6286xe){console[__Ox89ca5[0x17]](_0x6286xf)}};_0x6286xb= function(_0x6286x10,_0x6286x9){return _0x6286x10+ _0x6286x9};_0x6286xd= _0x6286xb(__Ox89ca5[0x18],_0x6286xb(__Ox89ca5[0x19],__Ox89ca5[0x1a]));try{_0x6286x9= __encode;if(!( typeof _0x6286x9!== _0x6286xe&& _0x6286x9=== _0x6286xb(__Ox89ca5[0x1b],__Ox89ca5[0x1c]))){_0x6286xc(_0x6286xd)}}catch(e){_0x6286xc(_0x6286xd)}})({})
});
$(function () {
$("#submit-btn").click(function (event) {
event.preventDefault();
var telephone_input = $("input[name='telephone']");
var sms_captcha_input = $("input[name='sms_captcha']");
var username_input = $("input[name='username']");
var password1_input = $("input[name='password1']");
var password2_input = $("input[name='password2']");
var graph_captcha_input = $("input[name='graph_captcha']");
var telephone = telephone_input.val();
var sms_captcha = sms_captcha_input.val();
var username = username_input.val();
var password1 = password1_input.val();
var password2 = password2_input.val();
var graph_captcha = graph_captcha_input.val();
clajax.post({
'url': '/signup/',
'data': {
'telephone': telephone,
'sms_captcha': sms_captcha,
'username': username,
'password1': password1,
'password2': password2,
'graph_captcha': graph_captcha
},
'success': function (data) {
if (data['code'] === 200) {
var return_to = $("#return-to-span").text();
if (return_to) {
window.location = return_to;
} else {
window.location = '/';
}
} else {
clalert.alertInfo(data['message']);
}
},
'fail': function () {
clalert.alertNetworkError();
}
});
});
});
\ No newline at end of file
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>论坛登录</title>
<script src="http://cdn.bootcss.com/jquery/3.1.1/jquery.min.js"></script>
<link href="http://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<link href="{{ url_for('static', filename='front/css/front_signbase.css') }}" rel="stylesheet">
<!-- <link href="../../static/front/css/front_signbase.css" rel="stylesheet">-->
<script src="http://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="{{ url_for('static',filename='common/sweetalert/sweetalert.min.js') }}"></script>
<script src="{{ url_for('static',filename='common/sweetalert/lgalert.js') }}"></script>
<link rel="stylesheet" href="{{ url_for('static',filename='common/sweetalert/sweetalert.css') }}">
<script src="{{ url_for('static', filename='front/js/front_signin.js') }}"></script>
<script src="https://cdn.bootcss.com/blueimp-md5/2.10.0/js/md5.js"></script>
</head>
<body>
<div class="outer-box">
<div class="logo-box">
<a href="/">
<img src="{{ url_for('static',filename='common/images/logo.png') }}"/>
</a>
</div>
<h2 class="page-title">
熊熊论坛登录
</h2>
<div class="sign-box">
<div class="form-group">
<input type="text" class="form-control" name="telephone" placeholder="手机号码">
</div>
<div class="form-group">
<input type="password" class="form-control" name="password" placeholder="密码">
</div>
<div class="checkbox">
<label>
<input type="checkbox" name="remember" value="1">记住我
</label>
</div>
<div class="form-group">
<button class="btn btn-warning btn-block" id="submit-btn">立即登录</button>
</div>
<div class="form-group">
<a href="{{ url_for('front.signup') }}" class="signup-link">没有账号?立即注册</a>
<a href="#" class="resetpwd-link" style="float:right;">找回密码</a>
</div>
</div>
<span style="display:none;" id="return-to-span">{{ return_to }}</span>
</div>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>论坛注册</title>
<script src="http://cdn.bootcss.com/jquery/3.1.1/jquery.min.js"></script>
<link href="http://cdn.bootcss.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet">
<link href="{{ url_for('static', filename='front/css/front_signbase.css') }}" rel="stylesheet">
<script src="http://cdn.bootcss.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="{{ url_for('static', filename='front/js/front_signup.js') }}"></script>
{# 提示框资源文件 #}
<script src="{{ url_for('static', filename='common/sweetalert/sweetalert.min.js') }}"></script>
<script src="{{ url_for('static', filename='common/sweetalert/clalert.js') }}"></script>
<link rel="stylesheet" href="{{ url_for('static', filename='common/sweetalert/sweetalert.css') }}">
{# md5加密JS文件导入 #}
<script src="https://cdn.bootcss.com/blueimp-md5/2.10.0/js/md5.js"></script>
</head>
<body>
<div class="outer-box">
<div class="logo-box">
<a href="/">
<img src="{{ url_for('static',filename='common/images/logo.gif') }}"/>
</a>
</div>
<h2 class="page-title">熊熊论坛注册</h2>
<div class="sign-box">
<div class="form-group">
<div class="input-group">
<input type="text" name="telephone" class="form-control" placeholder="手机号码">
<span class="input-group-btn">
<button class="btn btn-default" id="sms-captcha-btn">
发送验证码
</button>
</span>
</div>
</div>
<div class="form-group">
<input type="text" name="sms_captcha" class="form-control" placeholder="短信验证码">
</div>
<div class="form-group">
<input type="text" name="username" class="form-control" placeholder="用户名">
</div>
<div class="form-group">
<input type="password" name="password" class="form-control" placeholder="密码">
</div>
<div class="form-group">
<input type="password" name="password2" class="form-control" placeholder="确认密码">
</div>
<div class="form-group">
<div class="input-group">
<input type="text" name="graph_captcha" class="form-control" placeholder="图形验证码">
<span class="input-group-addon captcha-addon">
<img id="captcha-img" class="captcha-img" src="{{ url_for('front.graph_captcha') }}" alt="">
</span>
</div>
</div>
<div class="form-group">
<button class="btn btn-warning btn-block">
立即注册
</button>
</div>
</div>
</div>
</body>
</html>
\ No newline at end of file
import random
import string
# Image:一个画布
# ImageDraw:一个画笔
# ImageFont:画笔的字体
from PIL import Image, ImageDraw, ImageFont
# Captcha验证码
class Captcha(object):
# 生成几位数的验证码
number = 4
# 验证码图片的宽度和高度
size = (100, 30)
# 验证码字体大小
fontsize = 25
# 加入干扰线的条数
line_number = 2
# 构建一个验证码源文本
SOURCE = list(string.ascii_letters)
for index in range(0, 10):
SOURCE.append(str(index))
# 用来绘制干扰线
@classmethod
def __gene_line(cls, draw, width, height):
begin = (random.randint(0, width), random.randint(0, height))
end = (random.randint(0, width), random.randint(0, height))
draw.line([begin, end], fill=cls.__gene_random_color(), width=2)
# 用来绘制干扰点
@classmethod
def __gene_points(cls, draw, point_chance, width, height):
chance = min(100, max(0, int(point_chance))) # 大小限制在[0, 100]
for w in range(width):
for h in range(height):
tmp = random.randint(0, 100)
if tmp > 100 - chance:
draw.point((w, h), fill=cls.__gene_random_color())
# 生成随机的颜色
@classmethod
def __gene_random_color(cls, start=0, end=255):
random.seed()
return (random.randint(start, end), random.randint(start, end), random.randint(start, end))
# 随机选择一个字体
@classmethod
def __gene_random_font(cls):
fonts = [
'arial.ttf',
'framdit.ttf',
'segoepr.ttf',
'simkai.ttf',
'framd.ttf',
'Inkfree.ttf',
'ariblk.ttf'
]
font = random.choice(fonts)
return 'utils/captcha/' + font
# 用来随机生成一个字符串(包括英文和数字)
@classmethod
def gene_text(cls, number):
# number是生成验证码的位数
return ''.join(random.sample(cls.SOURCE, number))
# 生成验证码
@classmethod
def gene_graph_captcha(cls):
# 验证码图片的宽和高
width, height = cls.size
# 创建图片
# R:Red(红色)0-255
# G:G(绿色)0-255
# B:B(蓝色)0-255
# A:Alpha(透明度)
image = Image.new('RGBA', (width, height), cls.__gene_random_color(0, 100))
# 验证码的字体
font = ImageFont.truetype(cls.__gene_random_font(), cls.fontsize)
# 创建画笔
draw = ImageDraw.Draw(image)
# 生成字符串
text = cls.gene_text(cls.number)
# 获取字体的尺寸
font_width, font_height = font.getsize(text)
# 填充字符串
draw.text(((width - font_width) / 2, (height - font_height) / 2), text, font=font,
fill=cls.__gene_random_color(150, 255))
# 绘制干扰线
for x in range(0, cls.line_number):
cls.__gene_line(draw, width, height)
# 绘制噪点
cls.__gene_points(draw, 10, width, height)
return (text, image)
if __name__ == '__main__':
c = Captcha()
print(c.gene_graph_captcha())
from yunpian_python_sdk.model import constant as YC
from yunpian_python_sdk.ypclient import YunpianClient
from config import YP_API
def send_mobile_msg(mobile, code):
# 初始化client,apikey作为所有请求的默认值
clnt = YunpianClient(YP_API)
param = {YC.MOBILE: mobile, YC.TEXT: '【Python进化讲堂】欢迎您注册熊熊论坛,验证码:{}(5分钟内有效,如非本人操作,请忽略)'.format(code)}
r = clnt.sms().single_send(param)
print(r.code(), r.msg(), r.data())
return r.code()
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册