创建项目仓库&集成前端页面

1.创建码云空白项目

2.克隆到本地&集成前端页面

3.提交版本

4.添加前端文件

1)在meiduo目录中创建前端文件子目录front_end_pc并将前端静态文件复制到此目录下

2)git本地提交

git add front_end_pc
git status
git commit -m '添加静态文件'

3)推送到远端

git push origin master:master

live-server服务器提供静态页面

注意:开发的时候用,部署的时候不用

安装live-server:

1)安装node.js的版本控制工具nvm,在终端中执行如下命令:

curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash

2)重新进入终端,使用nvm安装最新版本的node.js

nvm install node

3)安装live-server

npm install -g live-server

启动:(先进入静态文件目录,再启动live-server服务)

live-server

live-server运行在8080端口下,可以通过127.0.0.1:8080来访问静态页面。

Django目录结构调整

workon django
django-admin startproject meiduo_mall
ls # 查看目录
cd meiduo_mall # 切换到项目目录

内层meiduo_mall目录:
创建apps(存放项目的子应用)
创建libs(用来存放项目的第三方包)
创建utils(用来存放项目的工具包)
创建settings(用来存放项目的配置)
开发阶段配置文件dev.py
生产阶段配置文件prod.py

外层meiduo_mall目录:
创建scripts(开发时使用的脚本,SQL文件等)
创建logs(用于存储日志)
创建docs(用于存放文档)

流程注意点:

1.中间自动生成的文件不用被git管理,点no。

(一)django项目框架搭建

2.runserver记得设置

3.修改manage.py中的配置文件注册
(一)django项目框架搭建

3.空目录中新建.gitkeep文件,用于把空目录提交到远程仓库。

项目配置

apps搜索包目录路径添加

创建子应用

python ../../manage.py startapp users
项目运行搜索包目录:
import sys
sys.path:搜索包的目录列表
['目录1','目录2','目录3',......]

搜索包目录路径添加:

(一)django项目框架搭建

使用sys.path添加<BASE_DIR>/apps目录,即可添加apps应用的导包路径。

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# 添加apps搜索包路径
import sys
sys.path.insert(0, os.path.join(BASE_DIR, 'apps'))
INSTALLED_APPS = [
    ...
    'users.apps.UsersConfig',
]

mysql数据库配置

实际开发中:会给开发人员创建新用户并且给用户分配操作数据库权限。

1.先创建数据库

create database meiduo_sh24 default charset=utf8;
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'HOST': '127.0.0.1',  # 数据库主机
        'PORT': 3306,  # 数据库端口
        'USER': 'meiduo_sh24',  # 数据库用户名
        'PASSWORD': 'meiduo',  # 数据库用户密码
        'NAME': 'meiduo_sh24'  # 数据库名字
    }
}

为本项目创建数据库用户(不再使用root账户)

create user meiduo_sh24 identified by 'meiduo';  meiduo_sh24是用户名,meiduo是密码
grant all on meiduo_sh24.* to 'meiduo_sh24'@'%'; 
flush privileges; # 刷新权限

第一句:创建用户账号 meiduo_sh24, 密码 meiduo (由identified by 指明)
第二句:授权meiduo_sh24数据库下的所有表(meiduo_sh24.*)的所有权限(all)给用户meiduo_sh24在以任何ip访问数据库的时候('meiduo'@'%')
第三句:刷新生效用户权限

*代表数据库的所有表

%代表授权所有IP,替换为IP的话,就是只为某个ip授权

注意点:安装pymysql后注册

(一)django项目框架搭建

django框架的缓存和session储存设置

安装django-redis

pip install django-redis
# Django框架的缓存设置,默认Django框架的缓存为服务器的内存,此处将Django框架的缓存改为了redis
CACHES = {
    "default": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/0",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    },
    "session": {
        "BACKEND": "django_redis.cache.RedisCache",
        "LOCATION": "redis://127.0.0.1:6379/1",
        "OPTIONS": {
            "CLIENT_CLASS": "django_redis.client.DefaultClient",
        }
    }
}
# 设置将session信息存储到缓存中,上面已经将缓存改为了redis,所有session会存放到redis中
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
# 指定session存储到缓存空间的名称
SESSION_CACHE_ALIAS = "session"

查看redis的IP和端口号

ps aux | grep redis

日志存储设置

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,  # 是否禁用已经存在的日志器
    'formatters': {  # 日志信息显示的格式
        'verbose': {
            'format': '%(levelname)s %(asctime)s %(module)s %(lineno)d %(message)s'
        },
        'simple': {
            'format': '%(levelname)s %(module)s %(lineno)d %(message)s'
        },
    },
    'filters': {  # 对日志进行过滤
        'require_debug_true': {  # django在debug模式下才输出日志
            '()': 'django.utils.log.RequireDebugTrue',
        },
    },
    'handlers': {  # 日志处理方法
        'console': {  # 向终端中输出日志
            'level': 'INFO',
            'filters': ['require_debug_true'],
            'class': 'logging.StreamHandler',
            'formatter': 'simple'
        },
        'file': {  # 向文件中输出日志
            'level': 'INFO',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': os.path.join(os.path.dirname(BASE_DIR), "logs/meiduo.log"),  # 日志文件的位置
            'maxBytes': 300 * 1024 * 1024,
            'backupCount': 10,
            'formatter': 'verbose'
        },
    },
    'loggers': {  # 日志器
        'django': {  # 定义了一个名为django的日志器
            'handlers': ['console', 'file'],  # 可以同时向终端与文件中输出日志
            'propagate': True,  # 是否继续传递日志信息
            'level': 'INFO',  # 日志器接收的最低日志级别
        },
    }
}

(一)django项目框架搭建

异常处理设置

修改Django REST framework的默认异常处理方法,补充处理数据库异常和Redis异常。

1)新建utils/exceptions.py

import logging

from rest_framework.views import exception_handler as drf_exception_handler
from rest_framework.response import Response
from rest_framework import status

from django.db import DatabaseError
from redis.exceptions import RedisError

# 获取在配置文件中定义的logger,用来记录日志
logger = logging.getLogger('django')

def exception_handler(exc, context):
    """
    自定义异常处理
    :param exc: 异常
    :param context: 抛出异常的上下文
    :return: Response响应对象
    """
    # 调用drf框架原生的异常处理方法
    response = drf_exception_handler(exc, context)

    if response is None:
        # 获取异常视图对象
        view = context['view']
        if isinstance(exc, DatabaseError) or isinstance(exc, RedisError):
            # 数据库异常
            logger.error('[%s] %s' % (view, type(exc)))
            response = Response({'message': '服务器内部错误'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)

    return response

2)配置文件中添加

REST_FRAMEWORK = {
    # 异常处理
    'EXCEPTION_HANDLER': 'meiduo_mall.utils.exceptions.exception_handler',
}

用户部分

用户数据表的设计

ID 主键

用户名(username)

密码(pssword)

手机号(mobile)

邮箱(email)

标记字段-是否是管理员(is_admin)

逻辑删除(is_delete)

模型类定义

django认证系统:已经提供了一个User模型类

(一)django项目框架搭建

自定义Django认证系统用户模型类(自定义后默认的就不再使用了):

1)自己定义User类,继承于AbstractUser,配置手机号字段

class User(AbstractUser):
	"""用户模型类"""
    mobile = models.CharField(max_length=11,verbose_name='手机号')
    class Meta:
        db_table = 'tb_users'
        verbose_name = '用户'
        verbose_name_plural = verbose_name

2)在配置文件中进行设置

# AUTH_USER_MODEL = '子应用.模型类'
AUTH_USER_MODEL = 'users.User'

3)执行数据库迁移

python manage.py makemigrations
python manage.py migrate

业务接口分析过程

1.拆分自业务(分析业务功能中包含哪些子业务)
2.每个子业务实现一个API接口
	API接口设计:
		a.接口的请求方式和接口地址
		b.接口的请求参数
		c.接口的相应数据和格式
3.进行API接口代码实现

注册业务分析

1.短信验证码获取
2.判断用户名是否存在
3.判断手机号是否存在
4.注册用户信息的保存

短信验证码API接口设计与实现

API接口设计:
	GET /sms_codes/(?P<mobile>1[3-9]\d{9})/
参数:
	通过url地址传递手机号mobile
响应:{
    "message":"ok
	}

基本业务逻辑:
1. 随机生成6位数字作为短信验证码内容
2. 在redis中存储短信验证码的内容,以`sms_<mobile>`作为key,以短信验证码内容为value
3. 使用云通讯给`mobile`发送短信验证码
4. 返回应答,发送成功

创建短信验证码子应用:
切换到apps目录
python ../../manage.py startapp verifications
注册子应用

获取redis链接两种方法:

# 第一种:
	import redis
	# redis_conn = StrictRedis(host=<ip>, port=<port>, db=<db>)
	redis_conn = StrictRedis(host='172.16.179.139', port=6379, db=2)
# 第二种(常用):
	from django_redis import get_redis_connection
	redis_conn = get_redis_connection('verify_codes') # 返回StrictRedis链接对象
    

再配置文件中,再加一个redis存储库命名为:verify_codes

CACHES = {
    ......
    "verify_codes": {
            "BACKEND": "django_redis.cache.RedisCache",
            "LOCATION": "redis://127.0.0.1:6379/2",
            "OPTIONS": {
                "CLIENT_CLASS": "django_redis.client.DefaultClient",
            }
        },
}

在子应用下创建contants.py在里面配置常量

# 短信验证码过期时间s
SMS_CODE_REDIS_EXPIRES = 300
# 短信模板ID
SEND_SMS_TEMP_ID = 1

这里导包有报错,是pycharm的问题:

(一)django项目框架搭建

解决方法:

(一)django项目框架搭建

代码实现:

import random

from django.shortcuts import render
from rest_framework import status
from rest_framework.response import Response
from rest_framework.views import APIView
from django_redis import get_redis_connection


from verifications import constants


# Create your views here.

# GET /sms_codes/(?P<mobile>1[3-9]\d{9})/
from yuntongxun.sms import CCP


class SMSCodeView(APIView):
    def get(self, request,mobile):
        """
        短信验证码获取
        1.随机生成6位数字作为短信验证码内容
        2.在redis中存储短信验证码的内容,以sms_<mobile>作为key,以短信验证码内容作为value
        3.使用云通讯给mobile发送短信验证码
        4.返回应答,发送成功
        """
        # 1.随机生成6位数字作为短信验证码内容
        sms_code = random.randint(100000,999999)
        print("--------------")
        print("短信验证码为:%s"%sms_code)
        # 2.在redis中存储短信验证码的内容,以sms_<mobile>作为key,以短信验证码内容作为value
        redis_conn = get_redis_connection('verify_codes')
        redis_conn.set('sms_%s'%mobile,sms_code,constants.SMS_CODE_REDIS_EXPIRES)
        # 3.使用云通讯给mobile发送短信验证码
        expires = constants.SMS_CODE_REDIS_EXPIRES // 60
        try:
            res = CCP().send_template_sms(mobile,[sms_code,expires],constants.SEND_SMS_TEMP_ID)
        except Exception:
            # 发送短信异常
            return Response({'message':'发送短信异常'},status=status.HTTP_503_SERVICE_UNAVAILABLE)

        if res == -1:
            # 发送短消息失败
            return Response({'message':"发送短消息失败"},status=status.HTTP_503_SERVICE_UNAVAILABLE)
        return Response({'message':'OK'})

同一个手机号发送短信必须间隔60s

实现:
	1.在发短信的时候,发送短信标记send_flag_<mobile>,同时设置有限期为60s
	2.在客户端访问发送短信接口时,提前先判断redis中是否存在标记send_flag_<mobile>

# 创建redis连接
redis_conn = get_redis_connection('verify_codes')
# 查询redis中的'send_flag_%s' % mobile
send_flag = redis_conn.get('send_flag_%s' % mobile)
# 判断存在的话就是还没过期,就返回发送短信过于频繁
if send_flag:
	return Response({'message': "发送短信过于频繁"}, status.HTTP_403_FORBIDDEN)

redis管道使用

可以向redis管道中添加多个redis指令,然后一次性执行所有命令
好处:
一次链接,执行多条指令

# 1.创建管道对象
pl = redis_conn.pipeline()
# 向管道中添加redis指令
pl.set(第一个命令)
pl.set(第二个命令)
...
# 一次性执行管道中的所有命令
pl.execute()

# 实现代码:
pl = redis_conn.pipeline()
pl.set('sms_%s' % mobile, sms_code, constants.SMS_CODE_REDIS_EXPIRES)
pl.set('send_flag_%s' % mobile, 1, constants.SEND_SMS_INTERVAL)
pl.execute()

前后端服务器本地域名设置

通过域名访问网站服务器过程:
	1.根据域名找对应的IP地址
		先到本地/etc/hosts文件中查询ip和域名直接的对应关系
		如果找不到,再进行DNS解析
	2.然后再根据IP访问对应的服务器

静态文件服务器 live-server  127.0.0.1:8080  www.meiduo.site
后端API服务器 python manage.py runserver 127.0.0.1:8000  api.meiduo.site


修改hosts文件/etc/hosts:

(一)django项目框架搭建

如果向通过域名访问django服务器,需要把域名添加到配置文件的ALLOWED_HOSTS中

ALLOWED_HOSTS = ['api.meiduo.site''www.meiduo.site']

VIM扩展

HJKL上下左右光标

I 插入代码
U 撤销

shift+ZZ保存退出

:WQ保存退出

前端vue实现

pycharm支持最新前端语法设置:

(一)django项目框架搭建

注册页前端实现

1)修改register.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
  <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
  <title>美多商城-注册</title>
  <link rel="stylesheet" type="text/css" href="css/reset.css">
  <link rel="stylesheet" type="text/css" href="css/main.css">
  <script type="text/javascript" src="js/host.js"></script>
  <script type="text/javascript" src="js/vue-2.5.16.js"></script>
  <script type="text/javascript" src="js/axios-0.18.0.min.js"></script>
</head>
<body>
  <div class="register_con">
    <div class="l_con fl">
      <a class="reg_logo"><img src="images/logo.png"></a>
      <div class="reg_slogan">商品美 · 种类多 · 欢迎光临</div>
      <div class="reg_banner"></div>
    </div>

    <div class="r_con fr">
      <div class="reg_title clearfix">
        <h1>用户注册</h1>
        <a href="/login.html">登录</a>
      </div>
      <div class="reg_form clearfix" id="app" v-cloak>
        <form id="reg_form" @submit.prevent="on_submit">
        <ul>
          <li>
            <label>用户名:</label>
            <input type="text" v-model="username" @blur="check_username" name="user_name" id="user_name">
            <span v-show="error_name" class="error_tip">{{ error_name_message }}</span>
          </li>
          <li>
            <label>密码:</label>
            <input type="password" v-model="password" @blur="check_pwd" name="pwd" id="pwd">
            <span v-show="error_password" class="error_tip">密码最少8位,最长20位</span>
          </li>
          <li>
            <label>确认密码:</label>
            <input type="password" v-model="password2" @blur="check_cpwd" name="cpwd" id="cpwd">
            <span v-show="error_check_password" class="error_tip">两次输入的密码不一致</span>
          </li>
          <li>
            <label>手机号:</label>
            <input type="text" v-model="mobile" @blur="check_phone" name="phone" id="phone">
            <span v-show="error_phone" class="error_tip">{{ error_phone_message }}</span>
          </li>
          <li>
            <label>短信验证码:</label>
            <input type="text" v-model="sms_code" @blur="check_sms_code" name="msg_code" id="msg_code" class="msg_input">
            <a @click="send_sms_code" class="get_msg_code">{{ sms_code_tip }}</a>
            <span v-show="error_sms_code" class="error_tip">{{ error_sms_code_message }}</span>
          </li>
          <li class="agreement">
            <input type="checkbox" v-model="allow" @change="check_allow" name="allow" id="allow">
            <label>同意”美多商城用户使用协议“</label>
            <span v-show="error_allow" class="error_tip2">请勾选同意</span>
          </li>
          <li class="reg_sub">
            <input type="submit" value="注 册" name="">
          </li>
        </ul>
        </form>
      </div>
    </div>
  </div>

  <div class="footer no-mp">
    <div class="foot_link">
      <a href="#">关于我们</a>
      <span>|</span>
      <a href="#">联系我们</a>
      <span>|</span>
      <a href="#">招聘人才</a>
      <span>|</span>
      <a href="#">友情链接</a>
    </div>
    <p>CopyRight © 2016 北京美多商业股份有限公司 All Rights Reserved</p>
    <p>电话:010-****888    京ICP备*******8号</p>
  </div>
  <script type="text/javascript" src="js/register.js"></script>
</body>
</html>

2)修改register.js

var vm = new Vue({
    el: '#app',
    data: {
        host: host,

        error_name: false,
        error_password: false,
        error_check_password: false,
        error_phone: false,
        error_allow: false,
        error_sms_code: false,
        error_name_message: '',
        error_phone_message: '',
        error_sms_code_message: '',

        sms_code_tip: '获取短信验证码',
        sending_flag: false, // 正在发送短信标志

        username: '',
        password: '',
        password2: '',
        mobile: '',
        sms_code: '',
        allow: false
    },
    methods: {
        check_username: function (){
            var len = this.username.length;
            if(len<5||len>20) {
                this.error_name_message = '请输入5-20个字符的用户名';
                this.error_name = true;
            } else {
                this.error_name = false;
            }

        },
        check_pwd: function (){
            var len = this.password.length;
            if(len<8||len>20){
                this.error_password = true;
            } else {
                this.error_password = false;
            }
        },
        check_cpwd: function (){
            if(this.password!=this.password2) {
                this.error_check_password = true;
            } else {
                this.error_check_password = false;
            }
        },
        check_phone: function (){
            var re = /^1[345789]\d{9}$/;
            if(re.test(this.mobile)) {
                this.error_phone = false;
            } else {
                this.error_phone_message = '您输入的手机号格式不正确';
                this.error_phone = true;
            }

        },
        check_sms_code: function(){
            if(!this.sms_code){
                this.error_sms_code_message = '请填写短信验证码';
                this.error_sms_code = true;
            } else {
                this.error_sms_code = false;
            }
        },
        check_allow: function(){
            if(!this.allow) {
                this.error_allow = true;
            } else {
                this.error_allow = false;
            }
        },
        // 发送手机短信验证码
        send_sms_code: function(){
            if (this.sending_flag == true) {
                return;
            }
            this.sending_flag = true;

            // 校验参数,保证输入框有数据填写
            this.check_phone();

            if (this.error_phone == true) {
                this.sending_flag = false;
                return;
            }

            // 向后端接口发送请求,让后端发送短信验证码
            axios.get(this.host + '/sms_codes/' + this.mobile + '/', {
                    responseType: 'json'
                })
                .then(response => {
                    // 表示后端发送短信成功
                    // 倒计时60秒,60秒后允许用户再次点击发送短信验证码的按钮
                    var num = 60;
                    // 设置一个计时器
                    var t = setInterval(() => {
                        if (num == 1) {
                            // 如果计时器到最后, 清除计时器对象
                            clearInterval(t);
                            // 将点击获取验证码的按钮展示的文本回复成原始文本
                            this.sms_code_tip = '获取短信验证码';
                            // 将点击按钮的onclick事件函数恢复回去
                            this.sending_flag = false;
                        } else {
                            num -= 1;
                            // 展示倒计时信息
                            this.sms_code_tip = num + '秒';
                        }
                    }, 1000, 60)
                })
                .catch(error => {
                    if (error.response.status == 400) {
                        this.error_sms_code_message = error.response.data.message;
                        this.error_sms_code = true;
                    } else {
                        console.log(error.response.data);
                    }
                    this.sending_flag = false;
                })
        },
        // 注册
        on_submit: function(){
            this.check_username();
            this.check_pwd();
            this.check_cpwd();
            this.check_phone();
            this.check_sms_code();
            this.check_allow();

        }
    }
});

CORS跨域请求

1)同源地址:

对于两个不同的url地址,如果其协议、IP和PORT完全一致,这样的地址就叫同源地址,否则就叫非同源地址。

例如:

  1. http://www.meiduo.site:8080/
  2. http://api.meiduo.site:8000/

2)跨域请求

当浏览器发起跨域请求时,如果源请求页面地址被请求地址不是同源地址,那么这个请求就是跨域请求

(一)django项目框架搭建

3)CORS跨域请求限制:

只针对ajax跨域请求

请求头:Origin:请求源地址

响应头:Access-Control-Allow-Origin: 源请求地址

4)跨域请求设置

安装

pip install django-cors-headers

注册应用

INSTALLED_APPS = (
    ...
    'corsheaders',
    ...
)

注册中间件

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    ...
]

添加跨域请求白名单

# CORS设置
CORS_ORIGIN_WHITELIST = (
    '127.0.0.1:8080',
    'www.meiduo.site:8080',
)
CORS_ALLOW_CREDENTIALS = True  # 允许携带cookie
  • 凡是出现在白名单中的域名,都可以访问后端接口
  • CORS_ALLOW_CREDENTIALS 指明在跨域访问中,后端是否支持对cookie的操作。

短信验证码(异步发送短信说明)

(一)django项目框架搭建

云通讯什么时候响应,客户端才会响应

问题:获取验证码如果网速慢的话,可能造成用户长时间的等待。‘

解决(Celery异步发短信):

​ 1.将短信发送的代码封装成一个函数。

​ 2.创建一个新的进程或线程,调用发送短信的函数

相关文章: