功能模块和会员表设计

猫影前台功能

猫影前台功能

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

会员数据表设计

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)

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

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

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

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

使用bootstrap搭建登录注册页面

统一页面

# 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>

注册页面

#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 %}

登录页面

#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 %}

注册功能实现

生成绝对路径的写法

#local_setting.py
DOMAIN = {
    "www":"http://127.0.0.1:5000"
}

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

#getCurrentTime.py

# -*- coding: utf-8 -*-
import  datetime
def getCurrentTime( frm = "%Y-%m-%d %H:%M:%S"):
    dt = datetime.datetime.now()
    return dt.strftime( frm )
#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模块

#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写法

 <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写法

;
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();
});

后端注册功能的写法

# 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 )
# 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) )
# 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写一个链接管理器

# 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的实现

;
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的写法

{% 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 %}

登录功能后端实现


@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

退出功能的实现

退出功能后端实现

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

退出功能前端实现

<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>

判断用户是否登录

# 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