注册和登录功能

功能模块和会员表设计

猫影前台功能

猫影前台功能

  • 账户功能
    • 注册
    • 登录
      • 展示功能
      • 列表
      • 详情

会员数据表设计

timestamp有两个属性,分别是CURRENT_TIMESTAMPON UPDATE CURRENT_TIMESTAMP两种,使用情况分别如下:

  • CURRENT_TIMESTAMP
    当要向数据库执行insert操作时,如果有个timestamp字段属性设为 CURRENT_TIMESTAMP,则无论这个字段有没有set值都插入当前系统时间

  • ON UPDATE CURRENT_TIMESTAMP
    当执行update操作是,并且字段有ON UPDATE CURRENT_TIMESTAMP属性。则字段无论值有没有变化,它的值也会跟着更新为当前UPDATE操作时的时间。

MySQL数据库唯一性设置(unique index)
使用navicat操作mysql数据库更加方便,设计表时:如图选择索引,第一项‘名’,随便自己起一个有意义的名字就行了。第二项是你要设置唯一性的字段,然后索引类型选择unique。
MySQL数据库唯一性设置(unique index)

1
2
3
4
5
6
7
8
9
10
11
12
CREATE TABLE `user` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`nickname` varchar(30) NOT NULL DEFAULT '' COMMENT '昵称',
`login_name` varchar(20) NOT NULL DEFAULT '' COMMENT '登录用户名',
`login_pwd` varchar(32) NOT NULL DEFAULT '' COMMENT '登录用户密码',
`login_salt` varchar(32) NOT NULL DEFAULT '' COMMENT '登录密码随机字符串',
`status` tinyint(3) NOT NULL DEFAULT '1' COMMENT '状态 0:无效 1:有效',
`updated_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后一次更新时间',
`created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '插入时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_login_name` (`login_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

安装相关扩展包:pymysqlflask-sqlacodegen

1
2
pip install pymysql -i https://pypi.tuna.tsinghua.edu.cn/simple
pip install flask-sqlacodegen -i https://pypi.tuna.tsinghua.edu.cn/simple

自动生成model的语法如下,注意导出的路径是否正确:

1
flask-sqlacodegen "mysql://root:123456@127.0.0.1/movie_cat" --tables user --outfile "common/models/user.py"  --flask

使用bootstrap搭建登录注册页面

统一页面

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
# layout.py
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>这是统一模板</title>
<!--bootstrap v3的核心样式-->
<link rel="stylesheet" href="{{ buildStaticUrl('/plugins/bootstrap_v3/css/bootstrap.min.css') }}">
{% block css %}{% endblock %}
</head>
<body>

<nav class="navbar navbar-inverse" style="border-radius: 0px;">
<div class="container-fluid">
<!-- Brand and toggle get grouped for better mobile display -->
<div class="navbar-header">
<button type="button" class="navbar-toggle collapsed" data-toggle="collapse"
data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="{{ buildUrl('/') }}">猫影</a>
</div>

<!-- Collect the nav links, forms, and other content for toggling -->
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
<ul class="nav navbar-nav">
<li class="active"><a href="{{ buildUrl('/') }}">影视</a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
{% if current_user %}
<li><a href="javascript:void(0);">{{ current_user.nickname }}</a></li>
<li><a href="{{ buildUrl('/member/logout') }}">退出</a></li>
{% else %}
<li><a href="{{ buildUrl('/member/reg') }}">注册</a></li>
<li><a href="{{ buildUrl('/member/login') }}">登录</a></li>
{% endif %}
</ul>
</div><!-- /.navbar-collapse -->
</div><!-- /.container-fluid -->
</nav>
<div class="container" style="min-height: 600px;">
{% block content %} {% endblock %}
</div>
<footer class="text-center">
Kevin @2020 www.javami.com
</footer>

<script src="{{buildStaticUrl('/plugins/jquery.min.js')}}"></script>
<script src="{{buildStaticUrl('/plugins/bootstrap_v3/js/bootstrap.min.js')}}"></script>
<script src="{{buildStaticUrl('/plugins/layer/layer.js')}}"></script>
<script src="{{buildStaticUrl('/js/common.js')}}"></script>
{% block js %} {% endblock %}
</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
50
51
52
#reg.html
{% extends "common/layout.html"%}
{% block content %}
<div class="row">
<div class="col-lg-8 col-lg-offset-2">
<div class="panel panel-default">
<div class="panel-heading">会员注册</div>
<div class="panel-body">
<div class="form-horizontal reg_wrap">
<div class="form-group">
<label for="nickname" class="col-sm-2 control-label">昵称</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="nickname" name="nickname" placeholder="请输入昵称~~">
</div>
</div>

<div class="form-group">
<label for="login_name" class="col-sm-2 control-label">登录用户名</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="login_name" name="login_name" placeholder="请输入登录用户名~~">
</div>
</div>
<div class="form-group">
<label for="login_pwd" class="col-sm-2 control-label">登录密码</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="login_pwd" name="login_pwd" placeholder="请输入登录密码~~">
</div>
</div>

<div class="form-group">
<label for="login_pwd2" class="col-sm-2 control-label">确认登录密码</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="login_pwd2" name="login_pwd2" placeholder="请输入确认登录密码~~">
</div>
</div>

<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-success btn-block do-reg">确定</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

{% endblock %}

{% block js %}
<script src="{{buildStaticUrl('/js/member/reg.js')}}"></script>
{% 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
#login.html

{% extends "common/layout.html"%}
{% block content %}
<div class="row">
<div class="col-lg-8 col-lg-offset-2">
<div class="panel panel-default">
<div class="panel-heading">会员登录</div>
<div class="panel-body">
<form class="form-horizontal">
<div class="form-group">
<label for="login_name" class="col-sm-2 control-label">登录用户名</label>
<div class="col-sm-10">
<input type="email" class="form-control" id="login_name" placeholder="请输入登录用户名~~">
</div>
</div>
<div class="form-group">
<label for="login_pwd" class="col-sm-2 control-label">登录密码</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="login_pwd" placeholder="请输入登录密码~~">
</div>
</div>

<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-success btn-block">登录</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>

{% endblock %}

注册功能实现

生成绝对路径的写法

1
2
3
4
#local_setting.py
DOMAIN = {
"www":"http://127.0.0.1:5000"
}

在common-libs文件夹下创建UrlManager.py,有版本管理学习。

1
2
3
4
5
6
7
#getCurrentTime.py

# -*- coding: utf-8 -*-
import datetime
def getCurrentTime( frm = "%Y-%m-%d %H:%M:%S"):
dt = datetime.datetime.now()
return dt.strftime( frm )
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
#UrlManager.py
from application import app
from common.libs.DataHelper import getCurrentTime
import os
class UrlManager(object):
@staticmethod
def buildUrl( path ):
config_domain = app.config['DOMAIN']
return "%s%s"%( config_domain['www'],path )

@staticmethod
def buildStaticUrl( path ):
path = "/static" + path + "?ver=" + UrlManager.getReleaseVersion()
return UrlManager.buildUrl( path )

@staticmethod
def getReleaseVersion():
'''
版本管理
开发模式 使用时间作为我们的版本号
生产环境 使用版本文件进行管理,覆盖开发模式的值
:return:
'''
ver = "%s" %( getCurrentTime("%Y%m%d%H%M%S%f") )
release_path = app.config.get( 'RELEASE_PATH' )
if release_path and os.path.exists( release_path ):
with open( release_path,'r') as f:
ver = f.readline()
return ver

写入www.py模块

1
2
3
4
5
6
7
8
9
10
11
#www.py

# -*- coding: utf-8 -*-
from application import app

'''
模板函数
'''
from common.libs.UrlManager import UrlManager
app.add_template_global( UrlManager.buildStaticUrl,'buildStaticUrl' )
app.add_template_global( UrlManager.buildUrl,'buildUrl' )

修改layout.html页面的url写法

1
2
3
4
5
6
7
 <a class="navbar-brand" href="{{ buildUrl('/') }}">猫影</a>
<li><a href="{{ buildUrl('/member/reg') }}">注册</a></li>
<li><a href="{{ buildUrl('/member/login') }}">登录</a></li>

{% block js %}
<script src="{{buildStaticUrl('/js/member/reg.js')}}"></script>
{% endblock %}

注册页面的reg.js写法

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
;
var member_reg_ops = {
//初始化页面
init:function(){
//绑定的执行函数
this.eventBind();
},
eventBind:function(){
$(".reg_wrap .do-reg").click( function(){
var btn_target = $(this);
if( btn_target.hasClass("disabled") ){
common_ops.alert( "正在处理!!请不要重复点击~~" );
return;
}
var nickname = $(".reg_wrap input[name=nickname]").val();
var login_name = $(".reg_wrap input[name=login_name]").val();
var login_pwd = $(".reg_wrap input[name=login_pwd]").val();
var login_pwd2 = $(".reg_wrap input[name=login_pwd2]").val();
if( login_name == undefined || login_name.length < 1 ){
common_ops.alert( "请输入正确的登录用户名~~" );
return ;
}

if( login_pwd == undefined || login_pwd.length < 6 ){
common_ops.alert( "请输入正确的登录密码,并且不能小于6个字符~~" );
return ;
}

if( login_pwd2 == undefined || login_pwd2 !=login_pwd ){
common_ops.alert( "请输入正确的确认登录密码~~" );
return ;
}
btn_target.addClass("disabled");
$.ajax({
url: common_ops.buildUrl( "/member/reg" ),
type: "POST",
data:{
nickname:nickname,
login_name:login_name,
login_pwd:login_pwd,
login_pwd2:login_pwd2,
},
dataType:'json',
success:function( res ){
btn_target.removeClass("disabled");
//异步执行
var callback = null;
if( res.code == 200 ){
callback = function(){
window.location.href = common_ops.buildUrl( "/" );
};
}
common_ops.alert( res.msg,callback );
}
});

} );
}
};

$(document).ready( function(){
member_reg_ops.init();
});

后端注册功能的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Helper.py
from flask import jsonify,g,render_template

def ops_render( template,context = {} ):
if 'current_user' in g:
context['current_user'] = g.current_user
return render_template( template, **context )

def ops_renderJSON( code = 200,msg = "操作成功~~",data = {} ):
resp = { "code":code,"msg":msg,"data":data }
return jsonify( resp )

def ops_renderErrJSON( msg = "系统繁忙,请稍后再试~~",data = {} ):
return ops_renderJSON( code = -1,msg = msg,data = data )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# UserService.py

import random,string,hashlib,base64
class UserService():

@staticmethod
def geneAuthCode( user_info = None ):
m = hashlib.md5()
str = "%s-%s-%s-%s-%s"%( user_info.id,user_info.login_name,user_info.login_pwd,
user_info.login_salt,user_info.status )
m.update( str.encode("utf-8") )
return m.hexdigest()

@staticmethod
def genePwd(pwd,salt):
m = hashlib.md5()
str = "%s-%s"%( base64.encodebytes( pwd.encode("utf-8") ),salt )
m.update( str.encode("utf-8") )
return m.hexdigest()

@staticmethod
def geneSalt( length = 16 ):
keylist = [ random.choice( (string.ascii_letters + string.digits ) ) for i in range(length ) ]
return ( "".join( keylist) )
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
# controllers文件夹下的member.py

@member_page.route("/reg",methods = [ "GET","POST" ])
def reg():
if request.method == "GET":
return ops_render("member/reg.html")

req = request.values
nickname = req['nickname'] if "nickname" in req else ""
login_name = req['login_name'] if "login_name" in req else ""
login_pwd = req['login_pwd'] if "login_pwd" in req else ""
login_pwd2 = req['login_pwd2'] if "login_pwd2" in req else ""

if login_name is None or len( login_name ) < 1:
return ops_renderErrJSON( msg = "请输入正确的登录用户名~~" )

if login_pwd is None or len( login_pwd ) < 6:
return ops_renderErrJSON( msg ="请输入正确的登录密码,并且不能小于6个字符~~")

if login_pwd != login_pwd2:
return ops_renderErrJSON(msg="请输入正确的确认登录密码~~")

user_info = User.query.filter_by( login_name = login_name ).first()
if user_info:
return ops_renderErrJSON( msg ="登录用户名已被注册,请换一个~~")

model_user = User()
model_user.login_name = login_name
model_user.nickname = nickname if nickname is not None else login_name
model_user.login_salt = UserService.geneSalt( 8 )
model_user.login_pwd = UserService.genePwd( login_pwd,model_user.login_salt )
model_user.created_time = model_user.updated_time = getCurrentTime()
db.session.add( model_user )
db.session.commit()
return ops_renderJSON( msg = "注册成功~~" )

为JS写一个链接管理器

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
# common.js
var common_ops = {
buildUrl:function( path ,params ){
//params = { "test":"abc","sort":"asc" };
// ?test=abc&sort=asc
var url = "" + path;
var _param_url = "";
if( params ) {
_param_url = Object.keys(params).map(function (k) {
return [ encodeURIComponent(k),encodeURIComponent( params[k] ) ].join("=")
}).join("&");
_param_url = "?" + _param_url;
}

return url + _param_url;
},
//封装一个layer组件的使用
alert:function( msg ,cb){
layer.alert( msg,{
yes:function( index ){
if( typeof cb == "function" ){
cb();
}
layer.close( index );
}
} );
}
};

登录功能实现

登录功能前端实现

login.js的实现

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
;
var member_login_ops = {
init: function () {
this.eventBind();
},
eventBind: function () {
$(".login_wrap .do-login").click(function () {
var btn_target = $(this);
if (btn_target.hasClass("disabled")) {
common_ops.alert("正在处理!!请不要重复点击~~");
return;
}
var login_name = $(".login_wrap input[name=login_name]").val();
var login_pwd = $(".login_wrap input[name=login_pwd]").val();
if( login_name == undefined || login_name.length < 1 ){
common_ops.alert("请输入正确的登录用户名~~");
return;
}

if( login_pwd == undefined || login_pwd.length < 6 ){
common_ops.alert("请输入正确的登录密码~~");
return;
}

btn_target.addClass("disabled");
$.ajax({
url: common_ops.buildUrl("/member/login"),
type: "POST",
data: {
login_name: login_name,
login_pwd: login_pwd
},
dataType: "json",
success: function (res) {
btn_target.removeClass("disabled");
var callback = null;
if (res.code == 200) {
callback = function () {
window.location.href = common_ops.buildUrl("/");
};
}
common_ops.alert(res.msg, callback);
}
});
});
}
};

$(document).ready(function () {
member_login_ops.init();
});

login.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
{% extends "common/layout.html"%}
{% block content %}
<div class="row">
<div class="col-lg-8 col-lg-offset-2">
<div class="panel panel-default">
<div class="panel-heading">会员登录</div>
<div class="panel-body">
<div class="form-horizontal login_wrap">
<div class="form-group">
<label for="login_name" class="col-sm-2 control-label">登录用户名</label>
<div class="col-sm-10">
<input type="email" class="form-control" id="login_name" name="login_name" placeholder="请输入登录用户名~~">
</div>
</div>
<div class="form-group">
<label for="login_pwd" class="col-sm-2 control-label">登录密码</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="login_pwd" name="login_pwd" placeholder="请输入登录密码~~">
</div>
</div>

<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-success btn-block do-login">登录</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>

{% endblock %}

{% block js %}
<script src="{{ buildStaticUrl('/js/member/login.js') }}"></script>
{% 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

@member_page.route("/login",methods = [ "GET","POST" ])
def login():
if request.method == "GET":
return ops_render("member/login.html")

req = request.values
login_name = req['login_name'] if 'login_name' in req else ''
login_pwd = req['login_pwd'] if 'login_pwd' in req else ''
if login_name is None or len( login_name ) < 1:
return ops_renderErrJSON( "请输入正确的登录用户名~~" )

if login_pwd is None or len( login_pwd ) < 6:
return ops_renderErrJSON("请输入正确的登录密码~~")
user_info = User.query.filter_by( login_name = login_name ).first()
if not user_info:
return ops_renderErrJSON("请输入正确的登录用户名和密码 -1~~")

if user_info.login_pwd != UserService.genePwd( login_pwd,user_info.login_salt ):
return ops_renderErrJSON("请输入正确的登录用户名和密码 -2 ~~")

if user_info.status != 1:
return ops_renderErrJSON( "账号被禁用,请联系管理员处理~~" )

#session['uid'] = user_info.id
response = make_response( ops_renderJSON( msg="登录成功~~" ) )
response.set_cookie(app.config['AUTH_COOKIE_NAME'],
"%s#%s"%( UserService.geneAuthCode( user_info ),user_info.id ),60 * 60 *24 *120 )
return response

退出功能的实现

退出功能后端实现

1
2
3
4
5
@member_page.route("/logout")
def logOut():
response = make_response( redirect( UrlManager.buildUrl("/") ) )
response.delete_cookie( app.config['AUTH_COOKIE_NAME'] )
return response

退出功能前端实现

1
2
3
4
5
6
7
8
9
<ul class="nav navbar-nav navbar-right">
{% if current_user %}
<li><a href="javascript:void(0);">{{ current_user.nickname }}</a></li>
<li><a href="{{ buildUrl('/member/logout') }}">退出</a></li>
{% else %}
<li><a href="{{ buildUrl('/member/reg') }}">注册</a></li>
<li><a href="{{ buildUrl('/member/login') }}">登录</a></li>
{% endif %}
</ul>

判断用户是否登录

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
# Auth.py
from application import app
from flask import request,g
from common.models.user import User
from common.libs.UserService import UserService

@app.before_request
def before_request():
app.logger.info( "--------before_request--------" )
user_info = check_login()
app.logger.info( user_info )
g.current_user = None
if user_info:
g.current_user = user_info
return

@app.after_request
def after_request( response ):
app.logger.info("--------after_request--------")
return response

'''
判断用户是否登录
'''
def check_login():
cookies = request.cookies
cookie_name = app.config['AUTH_COOKIE_NAME']
auth_cookie = cookies[cookie_name] if cookie_name in cookies else None
if auth_cookie is None:
return False

auth_info = auth_cookie.split("#")
if len( auth_info ) != 2:
return False

try:
user_info = User.query.filter_by( id = auth_info[1] ).first()
except Exception :
return False

if user_info is None:
return False

if auth_info[0] != UserService.geneAuthCode( user_info ):
return False

return user_info