zoukankan      html  css  js  c++  java
  • Flask-爱家租房项目ihome-02-注册

    图片验证码功能

    导入验证码生成逻辑

    utils中导入验证码逻辑,fonts文件夹和captcha.py文件

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-
    # refer to `https://bitbucket.org/akorn/wheezy.captcha`
    import random
    import string
    import os.path
    # from cStringIO import StringIO py2.x
    # from io import StringIO # py3.x
    from io import BytesIO  # py3.x
    
    from PIL import Image
    from PIL import ImageFilter
    from PIL.ImageDraw import Draw
    from PIL.ImageFont import truetype
    
    class Bezier:
        def __init__(self):
            self.tsequence = tuple([t / 20.0 for t in range(21)])
            self.beziers = {}
    
        def pascal_row(self, n):
            """ Returns n-th row of Pascal's triangle
            """
            result = [1]
            x, numerator = 1, n
            for denominator in range(1, n // 2 + 1):
                x *= numerator
                x /= denominator
                result.append(x)
                numerator -= 1
            if n & 1 == 0:
                result.extend(reversed(result[:-1]))
            else:
                result.extend(reversed(result))
            return result
    
        def make_bezier(self, n):
            """ Bezier curves:
                http://en.wikipedia.org/wiki/B%C3%A9zier_curve#Generalization
            """
            try:
                return self.beziers[n]
            except KeyError:
                combinations = self.pascal_row(n - 1)
                result = []
                for t in self.tsequence:
                    tpowers = (t ** i for i in range(n))
                    upowers = ((1 - t) ** i for i in range(n - 1, -1, -1))
                    coefs = [c * a * b for c, a, b in zip(combinations,
                                                          tpowers, upowers)]
                    result.append(coefs)
                self.beziers[n] = result
                return result
    
    class Captcha(object):
        def __init__(self):
            self._bezier = Bezier()
            self._dir = os.path.dirname(__file__)
            # self._captcha_path = os.path.join(self._dir, '..', 'static', 'captcha')
    
        @staticmethod
        def instance():
            if not hasattr(Captcha, "_instance"):
                Captcha._instance = Captcha()
            return Captcha._instance
    
        def initialize(self, width=200, height=75, color=None, text=None, fonts=None):
            # self.image = Image.new('RGB', (width, height), (255, 255, 255))
            # self._text = text if text else random.sample(string.uppercase + string.uppercase + '3456789', 4)
            self._text = text if text else random.sample(string.ascii_uppercase +
                                                         string.ascii_uppercase +
                                                         '3456789', 4)  # py3.x
            self.fonts = fonts if fonts else 
                [os.path.join(self._dir, 'fonts', font) for font in ['Arial.ttf', 'Georgia.ttf', 'actionj.ttf']]
            self.width = width
            self.height = height
            self._color = color if color else self.random_color(0, 200, random.randint(220, 255))
    
        @staticmethod
        def random_color(start, end, opacity=None):
            red = random.randint(start, end)
            green = random.randint(start, end)
            blue = random.randint(start, end)
            if opacity is None:
                return red, green, blue
            return red, green, blue, opacity
    
        # draw image
    
        def background(self, image):
            Draw(image).rectangle([(0, 0), image.size], fill=self.random_color(238, 255))
            return image
    
        @staticmethod
        def smooth(image):
            return image.filter(ImageFilter.SMOOTH)
    
        def curve(self, image, width=4, number=6, color=None):
            dx, height = image.size
            dx /= number
            path = [(dx * i, random.randint(0, height))
                    # for i in xrange(1, number)]
                    for i in range(1, number)]  # py3.x
            bcoefs = self._bezier.make_bezier(number - 1)
            points = []
            for coefs in bcoefs:
                points.append(tuple(sum([coef * p for coef, p in zip(coefs, ps)])
                                    for ps in zip(*path)))
            Draw(image).line(points, fill=color if color else self._color, width=width)
            return image
    
        def noise(self, image, number=50, level=2, color=None):
            width, height = image.size
            dx = width / 10
            width -= dx
            dy = height / 10
            height -= dy
            draw = Draw(image)
            # for i in xrange(number):
            for i in range(number):  # py3.x
                x = int(random.uniform(dx, width))
                y = int(random.uniform(dy, height))
                draw.line(((x, y), (x + level, y)), fill=color if color else self._color, width=level)
            return image
    
        def text(self, image, fonts, font_sizes=None, drawings=None, squeeze_factor=0.75, color=None):
            color = color if color else self._color
            fonts = tuple([truetype(name, size)
                           for name in fonts
                           for size in font_sizes or (65, 70, 75)])
            draw = Draw(image)
            char_images = []
            for c in self._text:
                font = random.choice(fonts)
                c_width, c_height = draw.textsize(c, font=font)
                char_image = Image.new('RGB', (c_width, c_height), (0, 0, 0))
                char_draw = Draw(char_image)
                char_draw.text((0, 0), c, font=font, fill=color)
                char_image = char_image.crop(char_image.getbbox())
                for drawing in drawings:
                    d = getattr(self, drawing)
                    char_image = d(char_image)
                char_images.append(char_image)
            width, height = image.size
            offset = int((width - sum(int(i.size[0] * squeeze_factor)
                                      for i in char_images[:-1]) -
                          char_images[-1].size[0]) / 2)
            for char_image in char_images:
                c_width, c_height = char_image.size
                mask = char_image.convert('L').point(lambda i: i * 1.97)
                image.paste(char_image,
                            (offset, int((height - c_height) / 2)),
                            mask)
                offset += int(c_width * squeeze_factor)
            return image
    
        # draw text
        @staticmethod
        def warp(image, dx_factor=0.27, dy_factor=0.21):
            width, height = image.size
            dx = width * dx_factor
            dy = height * dy_factor
            x1 = int(random.uniform(-dx, dx))
            y1 = int(random.uniform(-dy, dy))
            x2 = int(random.uniform(-dx, dx))
            y2 = int(random.uniform(-dy, dy))
            image2 = Image.new('RGB',
                               (width + abs(x1) + abs(x2),
                                height + abs(y1) + abs(y2)))
            image2.paste(image, (abs(x1), abs(y1)))
            width2, height2 = image2.size
            return image2.transform(
                (width, height), Image.QUAD,
                (x1, y1,
                 -x1, height2 - y2,
                 width2 + x2, height2 + y2,
                 width2 - x2, -y1))
    
        @staticmethod
        def offset(image, dx_factor=0.1, dy_factor=0.2):
            width, height = image.size
            dx = int(random.random() * width * dx_factor)
            dy = int(random.random() * height * dy_factor)
            image2 = Image.new('RGB', (width + dx, height + dy))
            image2.paste(image, (dx, dy))
            return image2
    
        @staticmethod
        def rotate(image, angle=25):
            return image.rotate(
                random.uniform(-angle, angle), Image.BILINEAR, expand=1)
    
        def captcha(self, path=None, fmt='JPEG'):
            """Create a captcha.
    
            Args:
                path: save path, default None.
                fmt: image format, PNG / JPEG.
            Returns:
                A tuple, (name, text, StringIO.value).
                For example:
                    ('fXZJN4AFxHGoU5mIlcsdOypa', 'JGW9', 'x89PNG
    x1a
    x00x00x00
    ...')
    
            """
            image = Image.new('RGB', (self.width, self.height), (255, 255, 255))
            image = self.background(image)
            image = self.text(image, self.fonts, drawings=['warp', 'rotate', 'offset'])
            image = self.curve(image)
            image = self.noise(image)
            image = self.smooth(image)
            # name = "".join(random.sample(string.lowercase + string.uppercase + '3456789', 24))
            name = "".join(random.sample(string.ascii_lowercase + string.ascii_uppercase + '3456789', 24))  # py3.x
            text = "".join(self._text)
            # out = StringIO()# py2.x
            out = BytesIO()  # py3.x
            image.save(out, format=fmt)
            if path:
                image.save(os.path.join(path, name), fmt)
            return name, text, out.getvalue()
    
        def generate_captcha(self):
            self.initialize()
            return self.captcha("")
    
    captcha = Captcha.instance()
    
    if __name__ == '__main__':
        print(captcha.generate_captcha())
    

    编写图片验证码接口

    逻辑梳理:

    1. 将获取图片验证码的功能做成一个独立的接口,浏览器访问url:http://127.0.0.1:5000/api/v1.0/image_codes/<image_code_key>即可获得一张验证码图片,其中url需要携带一个image_code_key参数,用来标记是谁获取的图片验证码
    2. 浏览器访问注册页面或者点击验证码图片时,就会调用这个验证码图片接口,传入一个全局唯一值image_code_key
    3. 接口中拿到image_code_key后,调用上面的图片验证码生成逻辑,获取到验证码图片image_data和真实的string验证码text,将键值对{image_code_key:text}存入redis数据库中,并设置过期时间,再将image_data返回给前端.
    4. 前端展示返回的验证码图片给用户识别并让用户填写验证码,点击获取验证码按钮时,调用验证验证码是否输入成功的逻辑.

    ihome/api_1_0目录中添加验证码模块verify_code.py

    # /ihome/api_1_0/verify_codes.py
    from flask import make_response, jsonify, current_app
    from ihome.utils.captcha import captcha
    from ihome.utils.constants import IMAGE_CODE_REDIS_EXPIRES
    from ihome.utils.response_codes import RET
    from . import api
    from ihome import redis_connect
    
    @api.route('/image_codes/<image_code_key>')
    def get_image_code(image_code_key):
        # 获取验证码
        name, text, image_data = captcha.generate_captcha()
        print(f'真实验证码:{text}')
        # 保存验证码
        try:
            redis_connect.setex(image_code_key, IMAGE_CODE_REDIS_EXPIRES, text)  # setex(key, 过期时间, value)=set+expire
        except Exception as e:
            # 保存失败, 记录日志
            current_app.logger.error(e)
            return jsonify(error_code=RET.DBERR, error_msg='保存图片验证码失败')
        # 保存成功
        resp = make_response(image_data)
        resp.headers["Content-Type"] = "image/jpg"
        return resp
    

    在上述代码中导入了自定义的常量模块ihome.utils.constants和状态码模块ihome.utils.response_codes,比较好集中管理常量和与前端约定的返回状态码

    # ihome.utils.constants.py
    
    # 图片验证码的redis有效期, 单位:秒
    IMAGE_CODE_REDIS_EXPIRES = 180  
    # 短信验证码的redis有效期, 单位:秒
    SMS_CODE_REDIS_EXPIRES = 300
    # 发送短信验证码的间隔, 单位:秒
    SEND_SMS_CODE_INTERVAL = 60
    
    # ihome.utils.response_codes
    
    class RET:
        OK = "0"             # "成功"
        DBERR = "4001"       # "数据库查询错误"
        NODATA = "4002"      # "无数据"
        DATAEXIST = "4003"   # "数据已存在"
        DATAERR = "4004"     # "数据错误"
        SESSIONERR = "4101"  # "用户未登录"
        LOGINERR = "4102"    # "用户登录失败"
        PARAMERR = "4103"    # "参数错误"
        USERERR = "4104"     # "用户不存在或未激活"
        ROLEERR = "4105"     # "用户身份错误"
        PWDERR = "4106"      # "密码错误"
        REQERR = "4201"      # "非法请求或请求次数受限"
        IPERR = "4202"       # "IP受限"
        THIRDERR = "4301"    # "第三方系统错误"
        IOERR = "4302"       # "文件读写错误"
        SERVERERR = "4500"   # "内部错误"
        UNKOWNERR = "4501"   # "未知错误"
    

    浏览器访问接口http://127.0.0.1:5000/api/v1.0/image_codes/123测试结果

    编写前端获取图片验证码逻辑

    // ihome/static/js/ihome/register.js
    // 进入注册页面自动调用generateImageCode(),获取验证码图片
    $(document).ready(function() {
        generateImageCode();
        ......
    
    // 形成图片验证码的后端地址, 设置到页面中,让浏览请求验证码图片
    function generateImageCode() {
        // 1. 生成图片验证码编号
        imageCodeId = generateUUID();
        // 2. 设置图片url
        var url = "/api/v1.0/image_codes/" + imageCodeId;
        $(".image-code img").attr("src", url);
    }
    
    // 保存图片验证码编号
    var imageCodeId = "";
    // 生成全局唯一值的ID
    function generateUUID() {
        var d = new Date().getTime();
        if(window.performance && typeof window.performance.now === "function"){
            d += performance.now(); //use high-precision timer if available
        }
        var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
            var r = (d + Math.random()*16)%16 | 0;
            d = Math.floor(d/16);
            return (c=='x' ? r : (r&0x3|0x8)).toString(16);
        });
        return uuid;
    }
    

    短信验证码功能

    使用第三方平台容联云通讯提供的短信验证码功能,这里只测试使用短信验证码的功能, 因此只需完成注册登录(无需实名认证等)即可使用其短信验证码免费测试服务, 不过免费测试服务只能给控制台中指定的三个手机号发送短信, 且只能有一个短信模板可以使用.

    安装容联云短信发送模块

    pip install ronglian_sms_sdk
    

    编辑短信发送接口

    编辑verify_codes.py,添加简单的短信发送逻辑,测试是否能成功发送短信

    # /ihome/api_1_0/verify_codes.py
    from ronglian_sms_sdk import SmsSDK
    from . import api
    import random
    
    @api.route('/sms_codes/<phone>')
    def send_sms_code(phone):
        # 创建sdk对象,三个参数为容联云官网账号下的ID,这里设置在了constants.py中
        sdk = SmsSDK(constants.ACCID, constants.ACCTOKEN, constants.APPID)
        # 短信模板编号,该模板为:....您的验证码是{1},请于{2}分钟内正确输入
        tid = '1'
        # 接受短信的手机号,在测试环境中这个手机号需要在容联云的测试号码中维护
        mobile = phone
        # 获取随机6位验证码,容联云不会生成验证码,需要我们自己生成
        sms_code = '%06d' % random.randint(0, 999999)
        # data参数为二元元组,第一个值为模板中的{1},第二个值为模板中的{2}
        data = (sms_code, constants.SMS_CODE_REDIS_EXPIRES//60)
        # 调用发送短信的方法,返回的时json字符串
        resp = sdk.sendMessage(tid, mobile, data)
        print(f'resp: {resp}')
        return 'ok'
    

    浏览器访问http://127.0.0.1:5000/api/v1.0/sms_codes/17621081762

    可以正常接收到短信并且打印出的日志为:

    Sign plaintext:  8aaf07087249953401727fa88e1e1c9d653e8d1fe37d40b589ad85948fefc3ce20200815185159
    Authorization plaintext: 8aaf07087249953401727fa88e1e1c9d:20200815185159
    Request url:  https://app.cloopen.com:8883/2013-12-26/Accounts/8aaf07087249953401727fa88e1e1c9d/SMS/TemplateSMS?sig=0F271A07ED79DF36628DB3DDA2D08F0A
    Request headers:  {'Content-Type': 'application/json;charset=utf-8', 'Accept': 'application/json', 'Accept-Charset': 'UTF-8', 'Authorization': b'OGFhZjA3MDg3MjQ5OTUzNDAxNzI3ZmE4OGUxZTFjOWQ6MjAyMDA4MTUxODUxNTk='}
    Request body:  {"to": "17621081762", "appId": "8aaf07087249953401727fa88f011ca4", "templateId": "1", "datas": ["936497", 5]}
    Response body:  {"statusCode":"000000","templateSMS":{"smsMessageSid":"2e41f82f7adc42e992431cd11637f94d","dateCreated":"20200815185200"}}
    resp: {"statusCode":"000000","templateSMS":{"smsMessageSid":"2e41f82f7adc42e992431cd11637f94d","dateCreated":"20200815185200"}}
    

    完善短信接口功能

    1. 给url中手机号参数phone添加正则转换器

    2. 添加验证图片验证码的逻辑

    3. 添加同一手机号一分钟内不能多次发送短信逻辑

    4. redis中保存短信验证码,给注册接口使用

    @api.route("/sms_codes/<re(r'1[^120]d{9}'):phone>")
    def send_sms_code(phone):
        # 获取url中?参数
        image_code = request.args.get('image_code')
        image_code_key = request.args.get('image_code_key')
        # 判断参数是否有值
        if not all([image_code, image_code_key]):
            return jsonify(errno=RET.PARAMERR, errmsg='图片验证码参数不能为空')
    
        # 判断该手机号是否已经注册过
        try:
            user = Users.query.filter_by(phone=phone).first()
        except Exception as e:
            current_app.logger.error(e)
            return jsonify(errno=RET.DBERR, errmsg='获取用户异常')
        if user:
            return jsonify(errno=RET.DATAEXIST, errmsg='该手机号已注册过用户')
    
        # 判断该手机号一分钟内是否发送过短信
        try:
            his_sms_code_key = f'his_sms_code_{phone}'
            sms_his = redis_connect.get(his_sms_code_key)
        except Exception as e:
            current_app.logger.error(e)
            return jsonify(errno=RET.DBERR, errmsg='redis获取短信验证码异常')
        # 存在短信记录
        if sms_his:
            return jsonify(errno=RET.DATAEXIST, errmsg='该手机号一分钟内已发送过短信,请稍候再试')
    
        # 获取redis中的真实图片验证码
        try:
            real_image_code = redis_connect.get(image_code_key)
        except Exception as e:
            current_app.logger.error(e)
            return jsonify(errno=RET.DBERR, errmsg='读取图片验证码异常')
        # 判断验证码是否存在
        if not real_image_code:
            return jsonify(errno=RET.NODATA, errmsg='图片验证码已失效')
        # 删除redis中的图片验证码,防止同一个验证码被使用多次
        try:
            redis_connect.delete(image_code_key)
        except Exception as e:
            current_app.logger.error(e)
        # 比较用户输入的验证码与真实验证码
        if image_code.lower() != real_image_code.decode().lower():
            # 验证码错误
            return jsonify(errno=RET.DATAERR, errmsg='图片验证码错误')
    
        # 图片验证码正确,则发送短信验证码
        # 获取随机6位验证码
        # sms_code = '%06d' % random.randint(0, 999999)
        sms_code = f'{random.randint(0,999999):06}'
        try:
            sdk = SmsSDK(constants.ACCID, constants.ACCTOKEN, constants.APPID)
            tid = '1'
            mobile = phone
            # 短信模板为:....您的验证码是{1},请于{2}分钟内正确输入
            # data参数为元组,第一个值为模板中的{1},第二个值为模板中的{2}
            data = (sms_code, constants.SMS_CODE_REDIS_EXPIRES//60)
            # 发送短信,接受返回值
            sms_resp_json = sdk.sendMessage(tid, mobile, data)
        except Exception as e:
            current_app.logger.error(e)
            return jsonify(errno=RET.THIRDERR, errmsg='发送短信异常')
        # 处理返回值
        sms_resp_dict = json.loads(sms_resp_json)
        sms_status = sms_resp_dict.get('statusCode')
        if sms_status != '000000':
            # 发送失败
            return jsonify(errno=RET.THIRDERR, errmsg=sms_resp_dict.get('statusMsg'))
    
        # 发送成功
        # redis缓存发送记录,一分钟内同一个手机号不能再发送短信了
        try:
            redis_connect.setex(his_sms_code_key, constants.SEND_SMS_CODE_INTERVAL, sms_code)
        except Exception as e:
            current_app.logger.error(e)
            return jsonify(errno=RET.DBERR, errmsg='redis保存短信验证码记录异常')
    
        # 保存短信验证码
        try:
            sms_code_key = f'sms_code_{phone}'
            redis_connect.setex(sms_code_key, constants.SMS_CODE_REDIS_EXPIRES, sms_code)
        except Exception as e:
            current_app.logger.error(e)
            return jsonify(errno=RET.DBERR, errmsg='redis保存短信验证码异常')
    
        return jsonify(errno=RET.OK)
    

    编写前端短信验证码逻辑

    给注册页面获取验证码按钮添加点击事件sendSMSCode

    // register.py
    function sendSMSCode() {
        // 点击一次之后移除点击功能,防止重复点击
        $(".phonecode-a").removeAttr("onclick");
        // 校验手机号和图片验证码是否填写
        var mobile = $("#mobile").val();
        if (!mobile) {
            $("#mobile-err span").html("请填写正确的手机号!");
            $("#mobile-err").show();
            $(".phonecode-a").attr("onclick", "sendSMSCode();");
            return;
        }
        var imageCode = $("#imagecode").val();
        if (!imageCode) {
            $("#image-code-err span").html("请填写验证码!");
            $("#image-code-err").show();
            $(".phonecode-a").attr("onclick", "sendSMSCode();");
            return;
        }
        // ajax调用后台接口
        $.get("/api/v1.0/sms_codes/" + mobile, {image_code:imageCode, image_code_key:imageCodeId},
            function(data){
                if (0 != data.errno) {
                    // 后台返回错误,则展示错误
                    $("#image-code-err span").html(data.errmsg);
                    $("#image-code-err").show();
                    if (2 == data.errno || 3 == data.errno) {
                        generateImageCode();
                    }
                    // 重新设置点击功能
                    $(".phonecode-a").attr("onclick", "sendSMSCode();");
                }
                else {
                    // 短信发送成功
                    var $time = $(".phonecode-a");
                    var duration = 60;
                    // 设置计时器,60秒内不允许再次点击
                    var intervalid = setInterval(function(){
                        $time.html(duration + "秒");
                        if(duration === 1){
                            clearInterval(intervalid);
                            $time.html('获取验证码');
                            $(".phonecode-a").attr("onclick", "sendSMSCode();");
                        }
                        duration = duration - 1;
                    }, 1000, 60);
                }
        }, 'json');
    }
    

    注册接口逻辑

    ihome/api_1_0下新增用户模块文件users.py,增加用户注册逻辑,并在蓝图中导入该视图文件

    from flask import request, jsonify, current_app, session
    from sqlalchemy.exc import IntegrityError
    from . import api
    from ihome import models, csrf, redis_connect, db
    from ihome.utils.response_codes import RET
    from ihome.models import Users
    import re
    import json
    
    # @csrf.exempt 该装饰器为取消csrf防护
    @api.route('/users', methods=['POST'])
    def register():
        # 获取数据
        dict_data = request.get_json()
        phone = dict_data.get('mobile')
        sms_code = dict_data.get('phoneCode')
        password = dict_data.get('password')
        password2 = dict_data.get('password2')
    
        # 校验数据
        if not all([phone, sms_code, password, password2]):
            return jsonify(errno=RET.PARAMERR, errmsg='参数不完整')
    
        # 两次密码是否一致
        if password2 != password:
            return jsonify(errno=RET.PARAMERR, errmsg='两次密码不一致')
    
        # 校验手机号
        if not re.match(r'1[^120]d{9}', phone):
            return jsonify(errno=RET.PARAMERR, errmsg='手机号格式不正确')
    
        # 验证短信验证码
        sms_code_key = f'sms_code_{phone}'
        # 获取真实短信验证码
        try:
            real_sms_code = redis_connect.get(sms_code_key)
        except Exception as e:
            current_app.logger.error(e)
            return jsonify(errno=RET.DBERR, errmsg='获取redis验证码异常')
        # 校验验证码
        if not real_sms_code:
            return jsonify(errno=RET.NODATA, errmsg='验证码不存在或已过期')
        if sms_code != real_sms_code.decode():
            return jsonify(errno=RET.PARAMERR, errmsg='验证码不正确')
    
        # 创建用户
        user = Users(phone=phone, password=password, name=phone)
        try:
            db.session.add(user)
            db.session.commit()
        except IntegrityError as e:
            current_app.logger.error(e)
            return jsonify(errno=RET.DATAEXIST, errmsg='该手机号已注册过用户')
        except Exception as e:
            current_app.logger.error(e)
            return jsonify(errno=RET.DBERR, errmsg='创建用户异常')
    
        # 注册则认为已经登录, 设置session,记录登录状态
        session['user_id'] = user.id
        session['phone'] = phone
        session['name'] = phone
    
        return jsonify(errno=RET.OK)
    

    注:

    1. @csrf.exempt 该装饰器可以取消视图函数的csrf防护,导入的csrf为创建app时的csrf对象csrf = CSRFProtect(app),这里先不取消防护
    2. flask的request.get_json()可以直接获取到前端传入的json数据,返回值类型为json字符串
    3. 校验该手机号是否已经创建过用户可以利用Users表中phone字段的唯一性约束特性,若已注册过用户,那么再次以该手机号创建用户时,会抛出sqlalchemy.exc.IntegrityError异常,处理该异常即可
    4. 注册成功则认为已经登录, 设置session

    注册的前端逻辑

    由于该项目前后端使用json传输,所以不能使用表单自带的提交功能,需要我们自定义表单的提交和后续的处理,在register.js中添加该部分逻辑

    $(document).ready(function() {
        generateImageCode();
        ......
        // 为表单的提交补充自定义的函数行为,参数e为提交事件
        $(".form-register").submit(function(e){
            //阻止默认的提交表单行为,走下面自定义的行为
            e.preventDefault();
            //判断字段是否填写
            var mobile = $("#mobile").val();
            var phoneCode = $("#phonecode").val();
            var passwd = $("#password").val();
            var passwd2 = $("#password2").val();
            if (!mobile) {
                $("#mobile-err span").html("请填写正确的手机号!");
                $("#mobile-err").show();
                return;
            }
            if (!phoneCode) {
                $("#phone-code-err span").html("请填写短信验证码!");
                $("#phone-code-err").show();
                return;
            }
            if (!passwd) {
                $("#password-err span").html("请填写密码!");
                $("#password-err").show();
                return;
            }
            if (passwd != passwd2) {
                $("#password2-err span").html("两次密码不一致!");
                $("#password2-err").show();
                return;
            }
            //发送ajax请求
            //js对象
            var postData = {
                mobile: mobile,
                phoneCode: phoneCode,
                password: passwd,
                password2: passwd2
            };
            //转换为json格式
            var postJson = JSON.stringify(postData);
            $.ajax({
                url: "api/v1.0/users",
                type: "post",
                contentType: "application/json",
                data: postJson,
                dataType: "json",
                headers: {'X-CSRFToken': getCookie('csrf_token')},
                success: function (resp) {
                    if(resp.errno != '0'){
                        alert(resp.errmsg);
                    }
                    else{
                        //注册成功,跳转到首页
                        location.href='/index.html';
                    }
                }
            })
        });
    })
    

    注:

    1. 在注册页面一加载就阻止默认的提交表单行为e.preventDefault();

    2. 使用JSON.stringify()方法将js对象格式转化为json格式

    3. 因为需要设置请求头等比较多的信息,所以使用ajax的完整写法,而不是用$.post简写

    4. ajax设置发送和接受的数据都是json格式的

    5. 由于后端添加了csrf防护,所以前端需要发送csrf_token,又因为如果在请求体中发送csrf_token的话,必须要请求体的格式为form表单格式,而我们这里是使用的json格式,所以只能在请求头中发送csrf_token,因此在请求头中添加X-CSRFToken属性

    6. X-CSRFToken的值就是之前设置在浏览器cookie中的csrf_token的值,因此需要获取到cookie中的值,这里的getCookie()方法就是用来获取cookie的,原理就是document.cookie获取所有的cookie,再通过正则表达式提取csrf_token的值

      function getCookie(name) {
          var r = document.cookie.match("\b" + name + "=([^;]*)\b");
          return r ? r[1] : undefined;
      }
      
    7. document.cookie.match()的输出为一个列表,第0个位置为完整的cookie信息,第一个位置为cookie值

      >document.cookie.match("\b" + 'csrf_token' + "=([^;]*)\b");
      <(2) ["csrf_token=IjE4ZmU1ZGNkNzNkODU0NDA0YTMzMDQyYmYxNzI…A3NzI4ZTM2YzAi.XztX7g.E_9RCL50tFgogPM9AtAH54KXxZU", "IjE4ZmU1ZGNkNzNkODU0NDA0YTMzMDQyYmYxNzI2NzA3NzI4ZTM2YzAi.XztX7g.E_9RCL50tFgogPM9AtAH54KXxZU", index: 0, input: "csrf_token=IjE4ZmU1ZGNkNzNkODU0NDA0YTMzMDQyYmYxNzI…A3NzI4ZTM2YzAi.XztX7g.E_9RCL50tFgogPM9AtAH54KXxZU", groups: undefined]
      
  • 相关阅读:
    《java.util.concurrent 包源码阅读》10 线程池系列之AbstractExecutorService
    《java.util.concurrent 包源码阅读》09 线程池系列之介绍篇
    《java.util.concurrent 包源码阅读》08 CopyOnWriteArrayList和CopyOnWriteArraySet
    《java.util.concurrent 包源码阅读》07 LinkedBlockingQueue
    《java.util.concurrent 包源码阅读》06 ArrayBlockingQueue
    《java.util.concurrent 包源码阅读》05 BlockingQueue
    NewBluePill源码学习 <一>
    深入理解Windows X64调试
    x64 结构体系下的内存寻址
    Windows PAE 寻址
  • 原文地址:https://www.cnblogs.com/gcxblogs/p/13527709.html
Copyright © 2011-2022 走看看