面试之——Web相关

前端

1、 前端三剑客

  HTML、CSS、JS

2、框架和类库(python叫模块)

  jQuery

  BootStrap

  Vue.js

3、响应式布局的原理

@media (min-width: 768px){
    .pg-header{
        background-color: green;
    }
}
@media (min-width: 992px){
    .pg-header{
        background-color: pink;
    }
}

4. jQuery

  jQuery是库,不是框架

  1. jQuery是一个轻量级的、兼容多浏览器的JavaScript库。

  2. jQuery使用户能够更方便地处理HTML Document、Events、实现动画效果、方便地进行Ajax交互,能够极大地简化JavaScript编程。它的宗旨就是:“Write less, do more.“

1.轻量级
   jQuery核心js文件才几十kb,不会影响页面加载速度。
2. 丰富的DOM选择器,jQuery的选择器用起来很方便
    比如要找到某个DOM对象的相邻元素,JS可能要写好几行代码,而jQuery一行代码就搞定了,再比如要将一个表格的隔行变色,jQuery也是一行代码搞定。
3. 链式表达式
    jQuery的链式操作可以把多个操作写在一行代码里,更加简洁。
4. 事件、样式、动画支持。
    jQuery还简化了js操作css的代码,并且代码的可读性也比js要强。
5. Ajax操作支持。
    jQuery简化了AJAX操作,后端只需返回一个JSON格式的字符串就能完成与前端的通信。
6. 跨浏览器兼容。
    jQuery基本兼容了现在主流的浏览器,不用再为浏览器的兼容问题而伤透脑筋。
7. 插件扩展开发。jQuery有着丰富的第三方的插件
    例如:树形菜单、日期控件、图片切换插件、弹出窗口等等基本前端页面上的组件都有对应插件,并且用jQuery插件做出来的效果很炫,并且可以根据自己需要去改写和封装插件,简单实用。

 

5. jQuery Ajax 和 原生Ajax

jQuery ajax: $.ajax(...)

原生 ajax: XMLHttpRequest

6. 跨域

JSONP

  本质: 返回一个函数(传参),前端拿到函数的调用,直接执行函数

CORS

  本质: 设置响应头,使得浏览器允许跨域请求。

    简单请求:一次请求

    非简单请求:两次请求,在发送数据之前会先发一次请求用于做“预检”,只有“预检”通过后才再发送一次请求用于数据传输。

  关于“预检”

  - 请求方式:OPTIONS
  - “预检”其实做检查,检查如果通过则允许传输数据,检查不通过则不再发送真正想要发送的消息
  - 如何“预检”
  => 如果复杂请求是PUT等请求,则服务端需要设置允许某请求,否则“预检”不通过
  Access-Control-Request-Method
  => 如果复杂请求设置了请求头,则服务端需要设置允许某请求头,否则“预检”不通过
  Access-Control-Request-Headers

Web后端

7、 谈谈对PythonWeb框架看法(Django和Flask区别?):

都是通过wsgi协议实现,默认使用模块不同
  Django: wsgiref模块
  flask: werkzeug
Django:
  大而全:
    中间件, 路由系统, 视图(CBV/FBV), 模板, ORM, cookie, Session, Admin, , 缓存, 信号, 序列化..
flask: 
  轻量级(可扩展性强,第三方组件丰富):
    中间件(功能较弱,需要扩展才能等价于Django的中间件), 
    路由系统, 
    视图(CBV/FBV), 
    Session(有,但弱爆了),
    无模板(第三方jinja2模板), 
    无ORM, cookie, 后面的都没有了

还有一个特点: 处理请求的方式不同:

  Django: 请求封装成request,传参进行传递

  Flask: 上下文管理

 

tornado:
  轻量级
  特色: 异步非阻塞 + 内置Websocket
其他:
  web.py
  bottle.py

7.0 你的Django是从哪儿学的?那个版本?

版本: 1.7

7.1   你对Django的哪个组件最熟悉?

ORM概念:
  对象关系映射(Object Relational Mapping,简称ORM)模式

  解决面向对象关系数据库存在的互不匹配的现象的技术。

  简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。

  ORM在业务逻辑层和数据库层之间充当了桥梁的作用。

优点:

  不用写原生sql语句,开发速度快

  移植性好: 公司有钱了,Mysql换Oracle,不需要重写sql语句

缺点:

  牺牲效率(翻译成sql语句)

  用多了,程序员都不会写sql语句了

配置默认数据库:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': "db3",
        'USER': "root",
        'PASSWORD': '123456',
        'HOST': "127.0.0.1",
        'PORT': '3306',
    }
}

使用pymysql连接数据库

app应用中 __init__.py 配置数据库
import pymysql
pymysql.install_as_MySQLdb()

models.py写类

class UserInfo(models.Model):
    nid = models.AutoField(primary_key=True)
    username = models.CharField(max_length=32)
    class Meta:
        # 数据库中生成的表名称 默认 app名称 + 下划线 + 类名
        db_table = "table_name"

        # 联合索引
        index_together = [
            ("pub_date", "deadline"),
        ]

        # 联合唯一索引
        unique_together = (("driver", "restaurant"),)

        # admin中显示的表名称
        verbose_name

问题:orm中你都记得那些方法?

轻描淡写的说:

  基本的: 增删改查

  values/values_list

  F/Q

  性能相关:

    only()

    defer()

    select_related()

    prefetch_related()

  原生SQL

    extra()

    raw() 

ORM基本增删改查

#

        # models.Tb1.objects.create(c1='xx', c2='oo')  增加一条数据,可以接受字典类型数据 **kwargs

        # obj = models.Tb1(c1='xx', c2='oo')
        # obj.save()

     # models.UserInfo.objects.bulk_create([models.UserInfo(name='xx'),models.UserInfo(name='xx')],
batch_size=10) 批量添加,可以指定一次添加的数量
#

        # models.Tb1.objects.get(id=123)         # 获取单条数据,不存在则报错(不建议)
        # models.Tb1.objects.all()               # 获取全部
        # models.Tb1.objects.filter(name='seven') # 获取指定条件的数据
        # models.Tb1.objects.exclude(name='seven') # 获取指定条件的数据


#

        # models.Tb1.objects.filter(name='seven').delete() # 删除指定条件的数据


#
        # models.Tb1.objects.filter(name='seven').update(gender='0')  # 将指定条件的数据更新,均支持 **kwargs
        

     # obj = models.Tb1.objects.get(id=1) # obj.c1 = '111' # obj.save() # 修改单条数据

 

单表查询API汇总

# 返回QuerySet对象

<1> all():    # 所有
<2> filter(**kwargs):    # 包括
<3> exclude(**kwargs):    # 不包括

<4> values(*field字段):    # 字典
<5> values_list(*field字段):    # 元组

<6> order_by(*field):    # 排序
<7> reverse():    # 反向排序
<8> distinct():    # 去重


# 返回一个具体的Object对象

<1> get(**kwargs):    # 没有或超出一个,报错
<2> first():    # 第一条
<3> last():    # 最后一条

# 返回数字
<4> count():    # 返回数据库中匹配查询(QuerySet)的对象数量
 # 返回布尔值
<5> exists():


其他:
anotate() # 实现聚合查询 group by的功能
extra()    # 构造额外的查询条件或者映射,如:子查询
raw()  # 执行原生sql语句

select_related()  # 帮助连表查询(把连表多次查询变成一次查询)
prefetch_related()  # 把连表查询拆分成多个单表查询


using() # 使用数据库

only()  # 只查某些字段
defer()  # 排除某些字段
[1,100]  # 切片 取多少条数据
aggregate  # 聚合函数,获取字典类型聚合结果

# 多对多
add
clear
set  # set两个过程,先clear,后add
remove

 虚拟的外键约束,  但是仍然可以使用ORM的连表操作

class UserType(models.Model):
    title = models.CharField(max_length=32)

class UserInfo(models.Model):
    name = models.CharField(max_length=32)
    email = models.CharField(max_length=32)
    ut = models.ForeignKey(to='UserType',db_constraint=False)

 

 

延伸:

- django组件 

- flask组件,用途 

- 上下文管理

8、Django请求生命周期?

Web客户端发送一个请求,首先经过wsgiref模块(实现socket监听连接,并处理environ原始数据,封装成request),然后经过一层层中间件后,进行路由匹配,匹配成功后,调用相应的视图函数,进行业务处理(比如: 对数据库操作,从模板里面取数据进行渲染),处理完业务后,再经过返回一层层中间件,到达wsgiref模块(此模块对相应内容根据http协议进行处理,发给Web客户

端) 

 

 

9、什么是wsgi?

  是web服务网关接口,是一套协议。以下模块实现了wsgi协议:

    - wsgiref

    - werkzurg(不能并发)

    - uwsgi(部署的时候,可以并发)

  以上模块本质:实现socket:监听请求,请求到来后将数据初次封装(environ),然后交给web框架处理。

wsgiref在Django中完成web框架本质原理:

from wsgiref.simple_server import make_server

def run_server(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return [bytes('<h1>Hello, web!</h1>', encoding='utf-8'), ]
 
 
if __name__ == '__main__':
    httpd = make_server('', 8000, run_server)
    httpd.serve_forever()

werkzeug在Flask中完成web框架本质原理

from werkzeug.wrappers import Request, Response
from werkzeug.serving import run_simple

@Request.application
def hello(request):
    return Response('Hello World!')

if __name__ == '__main__':
    # 请求一旦到来,执行第三个参数    参数()
    run_simple('localhost', 4000, hello)        # ip 端口 函数 ( hello是回调方法 )

Django目录的wsgi入口文件源码:

class WSGIHandler(base.BaseHandler):
    request_class = WSGIRequest

    def __call__(self, environ, start_response):
        # 请求刚进来之后 #
        .....
        signals.request_started.send(sender=self.__class__, environ=environ)
        request = self.request_class(environ)       # 对environ进行二次封装,成request对象
        response = self.get_response(request)
        .....
        return response

10、中间件

中间件的作用?

  对所有的请求到来之前和响应返回之前,批量处理,进行自定义操作。

中间件中方法?5个方法:

process_request
process_view(self, request, callback, callback_args, callback_kwargs)方法介绍:
    #(1)执行完所有中间件的request方法‘
    #(2)url匹配成功
    #(3)拿到 视图函数的名称、参数,(注意不执行) 再执行process_view()方法
    #(4)最后去执行视图函数
process_response
process_exception # 如果视图函数出错,才执行process_exception    
rocess_template_response  # 只有在视图函数的返回对象中有render方法才会执行!
# 并把对象的render方法的返回值返回给用户(注意不返回视图函数的return的结果了,而是返回视图函数 return值(对象)的render方法)

中间件的应用?

  应用场景:

  •   用户登录校验
  •   权限处理

  还有一些内置的:

  •   CSRF
  •   session
  •   全站缓存

  另外,还有一个就是处理:跨域 (前后端分离时,本地测试开发时使用的)

 

1. 登录验证,为什么使用中间件?

  如果不适用就需要为每个函数添加装饰器,太繁琐

2. 权限处理,为什么使用中间件?

  用户登录后,将权限放到session中,然后再每次请求时需要判断当前用户是否有权访问当前url,这检查的东西就可以放到中间件中进行统一处理。

3. session中间件?

  请求到来的时候,传过中间件,创建一个session对象,打包到request对象中

  可以用来用户验证登录,权限验证。。

 

4. 缓存中间件:全站缓存?

  缓存,利用中间件,就可以实现全站缓存

  

11、csrf本质?

防止用户直接向服务端发送POST请求(不能完全防止,比如爬虫先发送GET请求,模拟浏览器行为)

  1、用户先发送GET请求,获取csrf token,页面Form表单有一个隐藏的标签,同时也给客户端cookie里面设置了csrf_token

  2、发起POST请求时,需要携带之前发送给用户的csrf token

  3、如果使用Django,在中间件的process_view方法中进行校验

12、向后台发送请求的几种方法?

form表单提交:

<form method="POST">
    {% csrf_token %}
    <input type='text' name='user' />
    <input type='submit' />
</form

ajax提交(直接写csrf_token,注意没有“%”):

$.ajax({
    url:'/index',
    type:'POST',
    data:{csrfmiddlewaretoken:'{{ csrf_token }}',name:'alex'}
})

ajax提交(利用cookie:X-CSRFToken)

// 前提:引入jquery + 引入jquery.cookie 
$.ajax({
    url: 'xx',
    type:'POST',
    data:{name:'oldboyedu'},
    headers:{
        X-CSRFToken: $.cookie('csrftoken')
    },
    dataType:'json', // arg = JSON.parse('{"k1":123}')
    success:function(arg){
        
    }
})

有个小问题,每次发送ajax请求,都需要写一遍请求头。

ajax优化:为每个ajax请求加上一个请求头

<body>
    <input type="button" onclick="Do1();"  value="Do it"/>
    <input type="button" onclick="Do2();"  value="Do it"/>

    <script src="/static/jquery-3.3.1.min.js"></script>
    <script src="/static/jquery.cookie.js"></script>
    <script>
        $.ajaxSetup({    // 每次发Ajax请求前,都会调用次函数
            beforeSend: function(xhr, settings) {
                xhr.setRequestHeader("X-CSRFToken", $.cookie('csrftoken'));
            }
        });

         function Do1(){
            $.ajax({
                url:"/index/",
                data:{id:1},
                type:'POST',
                success:function(data){
                    console.log(data);
                }
            });
        }

         function Do2(){
            $.ajax({
                url:"/index/",
                data:{id:2},
                type:'POST',
                success:function(data){
                    console.log(data);
                }
            });
        }
        
    </script>
</body>

注意:ajaxSetup 只在本页面有效,如果希望所有页面使用,写个js文件,放到静态文件,在需要的页面引入即可

13、路由系统

(1)基本格式:

urlpatterns = [
url(正则表达式, views视图函数,参数,别名), # 参数:字典形式,给视图函数传参
]

(2)Django路由可以使用正则

#无命名分组
url(r'^index/(\d{4})/(\d{2})',views.index)

#有命名分组
url(r'^index/(?P<year>\d{4})/(?P<month>\d{2})',views.index)

无命名分组:

  使用简单的正则表达式分组匹配(通过圆括号)来捕获URL中的值并以位置参数形式传递给视图。

有命名分组:

  使用分组命名匹配的正则表达式组来捕获URL中的值并以关键字参数形式传递给视图。

  分组命名匹配的方式可以让你的URLconf 更加明晰且不容易产生参数顺序问题的错误

语法:

------(?P<name>pattern) -------- name: 组的名称 pattern: 要匹配的模式(正则表达式)

(3)include进行路由分发

本质: 还是三个参数:。。。(想想二级路由)

urlpatterns = [
url(r'^app01/', include('app01.urls')), # 可以包含其他的URLconfs文件
]

(4)URL命名和反向解析

name='xxx'   #  可以URL反向解析

14、视图

FBV:基于函数的视图

CBV :基于类的视图

FBV和CBV有什么区别?

  其实没啥区别,本质都是调用函数

  CBV的.as_view()返回的view函数,view函数中调用类的dispatch方法,在dispatch方法中通过反射执行get/post/delete/put等方法。

  FBV的GET、POST请求需要自己判断,而CBV的GET、POST请求自动找到对应的方法。

CBV用法:

首先:CBV的URL调用视图的方式

urlpatterns = [
    url(r'login/', views.Login.as_view()),
]

创建方法:创建一个类,继承 views.View

from django.views import View
from functools import wraps
from django.utils.decorators import method_decorator

def wrapper(func):
    @wraps(func)        # 修饰器
    def inner(request, *args, **kwargs):
        return func(request, *args, **kwargs)
    return inner

@method_decorator(wrapper, name='get')    # 类: 装饰get请求
@method_decorator(wrapper, name='post')    # 类: 装饰post请求
class Login(View):
    
    @method_decorator(wrapper)        # dispatch方法: 装饰下面所有的请求
    def dispatch(self, request, *args, **kwargs):
        print('before')
        obj = super(Login,self).dispatch(request, *args, **kwargs)
        print('after')
        return obj

    def get(self, request):
        return render(request, 'login.html')
        
    @method_decorator(wrapper)        # post方法: 装饰post请求
    def post(self, request):
        print(request.POST.get('user'))        
        print(request.method)
        return HttpResponse("这是post")

使用CBV时要注意,请求过来后会先执行dispatch()这个方法,如果需要批量对具体的请求处理方法(如get,post等)做一些操作的时候,这里我们可以手动改写dispatch方法,这

个dispatch方法就和在FBV上加装饰器的效果一样。

@method_decorator(csrf_exempt)        # 如果是csrf装饰器,必须装饰dispatch
def dispatch(self, request, *args, **kwargs):
    print('before')
    obj = super(Login,self).dispatch(request, *args, **kwargs)
    print('after')
    return obj

csrf_protect    # 强制设置防跨站请求伪造功能
csrf_exempt        # 强制取消防跨站请求伪造功能

 15、轮巡和长轮巡

(1)什么是轮巡?

  通过定时器让程序每隔n秒执行一次操作。

(2)什么是长轮训?

  浏览器向后端发起请求,后端会将请求 hang 住,最多hang 30s。

  如果一直不返回数据:则最多等待30s,紧接着用户立即再发送请求。

  如果有数据返回:则操作数据并立即再发送请求。

    PS:后台可以使用队列或redis的列表来hang主请求。

(3)轮训和长轮训目的?

  由于Http请求是无状态、短连接,所以服务端无法向客户端实时推送消息,

  所以,我们就可以使用:轮训和长轮训去服务端获取实时数据。

16、websocket是什么?

websocket是一套类似于http的协议。

http协议:\r\n分割、请求头和请求体\r\n分割、无状态、短连接。
websocket协议:\r\n分割、创建连接后不断开、 验证+数据加密;

 

协议规定:浏览器和服务端连接之后不断开,以此可以完成:服务端向客户端主动推送消息。

websocket协议额外做的一些其他操作:

  1、握手,连接前进行校验

    客户端:

      发送请求,附带Sec-WebSocket-Key: iyRe1KMHi4S4QXzcoboMmw==

    服务器:

      魔法字符串(magic_string)+客户端发送的字符串   拼接

      先sha1加密,再base64加密,构造一个响应头,把结果发给浏览器

    客户端:

      客户端也会对自己发给服务器的字符串进行同样的加密,然后对比两个加密后的结果是否一样

  2、发送数据加密

    加密,payload_len=127/126/<=125 -> mask key

在项目中使用Websocket:

  django: channel

  flask: gevent-websocket

  tornado: 内置

17、实时消息推送,利用什么技术实现?

轮巡:

  优点: 代码简单

  缺点:请求次数多,服务器压力大,消息延迟

长轮巡:

  优点:实时接收数据,兼容性好(浏览器)

  缺点:请求次数只是相对减少(比轮巡),但相对来说也不少

Websocket:

  优点:代码简单,不再反复创建连接(连接不发送数据,只是监听socket(IO多路复用),几乎不消耗资源)

  缺点:兼容性差(浏览器版本低的兼容性差)

18、 关于Form的下拉框(比如choice字段)从数据库取值不能实时更新的问题

方法一: 通过重写构造方法(__init__)重新去数据库获取值。

class UserType(Form):
    title = fields.CharField(max_length=32)

    def __init__(self, *args, **kwargs):
        super(UserType, self).__init__(*args, **kwargs)
        self.title = UserType.objects.all()

方法二: 使用ModelChoiceField字段

from django.forms import Form
from django.forms import fields
from django.forms.models import ModelChoiceField
class UserForm(Form):
    name = fields.CharField(label='用户名',max_length=32)
    email = fields.EmailField(label='邮箱')
    ut_id = ModelChoiceField(queryset=models.UserType.objects.all())    

依赖:
    class UserType(models.Model):
        title = models.CharField(max_length=32)

        def __str__(self):
            return self.title

19、多数据库相关操作: 使用多个数据库

python manage.py makemigraions
python manage.py migrate app名称 --databse=配置文件数据名称的别(比如:default, db1, ...)
    # python manage.py migrate app01 --database=default
    # python manage.py migrate app01 --database=db1


手动操作:(手动选择对哪一个数据库的表进行操作)
    models.UserType.objects.using('db1').create(title='普通用户')
    result = models.UserType.objects.all().using('default')

自动操作:(自动选择对哪一个数据库的表进行操作)
    class Router1:
        def db_for_read(self, model, **hints):
            return 'db1'

        def db_for_write(self, model, **hints):
            return 'default'

    配置:
        DATABASES = {
            'default': {
                'ENGINE': 'django.db.backends.sqlite3',
                'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
            },
            'db1': {
                'ENGINE': 'django.db.backends.sqlite3',
                'NAME': os.path.join(BASE_DIR, 'db1.sqlite3'),
            },
        }
        DATABASE_ROUTERS = ['db_router.Router1',]
        
    使用:
        models.UserType.objects.create(title='VVIP')

        result = models.UserType.objects.all()
        print(result)
View Code

数据库迁移时进行约束:

防止小手一抖,把app和数据库对应关系创建错了

class Router1:
    def allow_migrate(self, db, app_label, model_name=None, **hints):
        """
        All non-auth models end up in this pool.
        """
        if db=='db1' and app_label == 'app02':
            return True
        elif db == 'default' and app_label == 'app01':
            return True
        else:
            return False
        
        # 注意:
            None和False是不一样的 
            如果返回None,那么表示交给后续的router,如果后续没有router,则相当于返回True
        
    def db_for_read(self, model, **hints):
        """
        Attempts to read auth models go to auth_db.
        """
        if model._meta.app_label == 'app01':
            return 'default'
        else:
            return 'db1'

    def db_for_write(self, model, **hints):
        """
        Attempts to write auth models go to auth_db.
        """
        if model._meta.app_label == 'app01':
            return 'default'
        else:
            return 'db1'
View Code

 20、MVC和MTV框架?

MVC: model view controller

MTV: model tempalte view 

对应关系

21、模板?

  • Django自带模板引擎
  • Flask使用jinja2模板引擎
  • tornado自带模板引擎

Django的模板语法:
特色: 点 取值
方法执行不需要加括号: {% for item in dic.items %} {%endfor%}
模板可以继承
自定义方法(标签和数据绑定):

filter 
simple_tag
inclusion_tag

from django import template
register = template.Library()       # 第3步: 创建一个叫register的 Library类的对象, register的名字是固定的,不可改变


@register.filter(name="cut")           # 第5步: 加装饰器      filter只能传2个参数,但能做if条件判断
def cut(value, arg):                        # 第4步: 定义函数
    return value.replace(arg, "")


@register.simple_tag(name="addSB")      # simple_tag参数无限制,但不能做if条件判断
def add_sb(value):
    return "{} SB".at(value)

  使用自定义filter

{# 先导入我们自定义filter那个文件 #}
{% load app01_filters %}

{# 使用我们自定义的filter #}
{{ somevariable|cut:"0" }}
{{ d.name|addSB }} # 前面的d.name当成参数传进去

 

# 相当于把html样式和数据结合到了一起
@register.inclusion_tag("archive.html")             # 接收一个html页面, 次页面会接收下面return的数据
def get_archive_tag(username):
    .....
    return {"category_list": category_list, "tag_list": tag_list, "date_list": date_list}


HTML文件引用自定义标签
    {% load my_tags %}          # 一定要导入自定义标签文件, 先导入,才能使用
    <!DOCTYPE html>
    <html lang="en">
    <head>
    ...............

    {% get_archive_tag username %}       # {% 标签名 参数 %} 

22、Form和ModelForm的作用?区别?应用场景?

作用:

  1. 对用户请求数据的格式进行校验
  2. 自动生成HTML标签

区别:

  Form,字段需要自己手写字段信息。

方法: 局部钩子和全局钩子
class Form(Form):
    xx = fields.CharField(.)
    xx = fields.CharField(.)

# 验证用户名是否注册
    def clean_user(self):               # 局部钩子
        val = self.cleaned_data.get('user')
        is_user = models.User.objects.filter(username=val).first()
        if is_user:
            raise ValidationError("用户名已注册")
        else:
            return val

# 验证密码是否一致
    def clean(self):                # 全局钩子
        val_first = self.cleaned_data.get('pwd')
        val_second = self.cleaned_data.get('repeat_pwd')
        if val_first == val_second:
            return self.cleaned_data        # 全局钩子返回的是cleaned_data字典
        else:
            raise ValidationError("两次输入的密码不一致")

ModelForm,可以通过Meta进行定义
class MForm(ModelForm):
    class Meta:
        fields = "__all__"
        model = UserInfo
    
    def 钩子:pass         # 可以定义钩子

使用:
form = BookModelForm(data=request.POST, instance=edit_book)     # 更新数据,对edit_book更新成request.POST

  应用:只要是客户端向服务端发送表单数据时,都可以进行使用,如:用户登录注册

23、为什么要用缓存?

目的: 减轻数据库的压力

  将常用且不太频繁修改的数据放入缓存。

  以后用户再来访问,先去缓存查看是否存在(命中),如果有就返回

  否则,去数据库中获取并返回给用户(再加入到缓存,以便下次访问)

24、django内部支持哪些缓存?

Django中提供了6种缓存方式:

  1. 开发调试(不加缓存)
  2. 内存
  3. 文件
  4. 数据库
  5. Memcache缓存(python-memcached模块)
  6. Memcache缓存(pylibmc模块)

安装第三方组件支持redis:django-redis组件 

25、Django内部如何设置缓存

粒度一: 视图函数缓存(整个页面)
from django.views.decorators.cache import cache_page

        @cache_page(5)              # 5 是超时时间(缓存的有效期, 单位是:秒)
        def index(request):
            t = time.time()
            return HttpResponse(t)
粒度二: 局部模板缓存(局部页面)
    <body>
        <h1>不缓存: {{ t }}</h1>
        {% cache 5 'name' %}
            <h1>缓存5秒: {{ t }}</h1>
        {% endcache %}
    </body>

粒度三: 全站缓存(中间件级别)
在 MIDDLEWARE_CLASSES 中加入 “update” 和 “fetch” 中间件
MIDDLEWARE_CLASSES = (
    ‘django.middleware.cache.UpdateCacheMiddleware’, #第一
    .......
    ‘django.middleware.cache.FetchFromCacheMiddleware’, #最后
)
“update” 必须配置在第一个
“fetch” 必须配置在最后一个

26、django的信号作用?

django的信号其实就是django内部为开发者预留的一些自定制功能的钩子。

只要在某个信号中注册了函数,那么django内部执行的过程中就会自动触发注册在信号中的函数。

部分信号:
    Model signals:
        pre_init                    # django的modal执行其构造方法前,自动触发
        post_init                   # django的modal执行其构造方法后,自动触发
        pre_save                    # django的modal对象保存前,自动触发
        post_save                   # django的modal对象保存后,自动触发
    Request/response signals:
        request_started             # 请求到来前,自动触发
        request_finished            # 请求结束后,自动触发
        got_request_exception       # 请求异常后,自动触发
    Database Wrappers:
        connection_created          # 创建数据库连接时,自动触发

用信号做过什么?

  在数据库某些表中添加数据时,可以进行日志记录。

27、Django的序列化组件?

内置:
    from django.core import serializers

    #queryset = [obj,obj,obj]
    ret = models.BookType.objects.all()

    data = serializers.serialize("json", ret)

补充json:
    import json

    #ret = models.BookType.objects.all().values('caption')      # 这两个queryset对象转成list可以序列化json
    ret = models.BookType.objects.all().values_list('caption')

    ret=list(ret)                   # 强转成列表

    result = json.dumps(ret)        # json序列化只能是字符串,列表,元组,字典,想序列化对象,可以自定义
    
    补充:
        - json.dumps(ensure_ascii=False)     # 有中文的时候,改成False
        - json.dumps( cls=My_JSONEncoder)      # 可以指定自定义的类

更多: https://www.cnblogs.com/coser/archive/2011/12/14/2287739.html

28、admin组件?

python manage.py createsuperuser  # 创建超级用户(changepassword忘记密码)

方式一:
    class UserAdmin(admin.ModelAdmin):
        list_display = ('name', 'password',)
 
    admin.site.register(models.UserInfo, UserAdmin) # 第一个参数可以是列表
     
 
方式二:
    @admin.register(models.UserInfo)                # 第一个参数可以是列表
    class UserAdmin(admin.ModelAdmin):
        list_display = ('name', 'password',)

        fields=["title", ]   # 增/改页面 显示的字段 
        exclude = ['title']     # 增/改页面,隐藏的字段 和fields相反

29、自己写stark组件?为什么还要写stark组件?

  为公司定制更适用于自己的组件

  # stark组件,见CRM项目

30、ContentType的作用?以及应用场景?

contenttype是django的一个组件app),为我们找到django程序中所有app中的所有表并添加到记录中

可以使用他再加上表中的两个字段实现:一张表N张表创建FK关系。

  • 字段:表名称
  • 字段:数据行ID

应用:路飞表结构优惠券和专题课和学位课关联。

31、谈谈你对restful 规范的认识?

restful其实就是一套编写接口协议,协议规定了如何编写以及如何设置返回值、状态码等信息。

我觉得有两个最显著的特点:url和method

  1. 给用户一个url,根据method不同,在后端做不同的处理,比如:get获取数据,post是创建数据,put和patch是修改数据。

  2. 如果没有restful,每一个请求method都要给一个url,比如:add_user、delete_user、update_user。。。

  体现: 简化了url的数量,便于维护和提高用户的体验

当然还有一些其他的,比如:

  版本:来控制让程序有多个版本共存的情况,版本可以放在 url、请求头(accept/自定义)、GET参数

  状态码:200/300/400/500

  url中尽量使用名词,restful也可以称为“面向资源编程”

  api标示:

    api.luffycity.com

    www.luffycity.com/api/

到这儿就可以了。。

10个规范:

    https://api.example.com/v1/zoos?sortby=name
    协议http       域名       版本号  名词      过滤(条件)

    method                                  # 不同执行不同的函数get/post/put/patch/....

    状态码
    错误信息:{'code':1000, ‘msg’:xxx}

    返回值: 根据路径和请求方式不同,返回不同的结果集或对象
    hyperlink{... type: www.xxxx.com/yy}

把所有的数据都看成资源,是面向资源的一种架构

31.1 你的restful是在哪儿学的?

因为公司要写一个前后端分离项目,要写接口给前端,所以就开始学习restful规范

阮一峰的博客学的restful规范

32、幂等性是什么意思?

幂等性的数学表达:f(f(x)) = f(x)。

幂等性是系统接口对外的一种承诺:

使用相同参数同一资源重复用某个接口的结果与调用一次的结果相同

GET #第一次获取结果、第二次也是获取结果对资源都不会造成影响,幂等。
POST #第一次新增数据,第二次也会再次新增,非幂等。
PUT #第一次更新数据,第二次不会再次更新,幂等。
PATCH #第一次更新数据,第二次不会再次更新,非幂等。(争议)
DELTE #第一次删除数据,第二次不在再删除,幂等。

 

def create_ticket():
    return ticket_id
 
def idempotent_withdraw(ticket_id, account_id, amount)
    return True or False

create_ticket的语义是获取一个服务器端生成的唯一的处理号ticket_id,它将用于标识后续的操作。idempotent_withdraw和withdraw的区别在于关联了一个ticket_id,一个ticket_id表示的操作至多只会被处理一次,每次调用都将返回第一次调用时的处理结果。这样,idempotent_withdraw就符合幂等性了,客户端就可以放心地多次调用。
基于幂等性的解决方案中一个完整的取钱流程被分解成了两个步骤:1.调用create_ticket()获取ticket_id;2.调用idempotent_withdraw(ticket_id, account_id, amount)。虽然create_ticket不是幂等的,但在这种设计下,它对系统状态的影响可以忽略,加上idempotent_withdraw是幂等的,所以任何一步由于网络等原因失败或超时,客户端都可以重试,直到获得结果。
View Code

更多: https://www.cnblogs.com/weidagang2046/archive/2011/06/04/2063696.html

33、远程数据交互(API调用,A服务器应用调用B服务器应用功能):

  • webservice, webservice是在rest api广泛引用之前大家使用的一个跨平台交互的接口。
  • restfull api
  • RPC

  扩展:WCF他是创建了一个双工通道。

webservice天气预报API接口:
    http://www.webxml.com.cn/zh_cn/weather_icon.aspx
    Endpoint: http://www.webxml.com.cn/WebServices/WeatherWebService.asmx
    getSupportCity 
    byProvinceName:北京           调用按钮
View Code

34、HTTPS

端口区别:
  Http: 80端   https: 443端口   nginx可以配置 80端口重定向到443端口,强制使用SSL证书加密了。访问http的时候会自动跳转到https上面。 自定义证书 服务端:创建一对证书 客户端:必须携带证书 购买证书 服务端: 创建一对证书,。。。。 证书机构:服务端把公钥给该机构 客户端: 去机构获取证书,数据加密后发给咱们的服务单

35、用django写接口时,有没有用什么框架?

使用rest framework框架

  # 不用它, 也可以用原生CBV实现

36、rest framework框架优点?或  为什么使用rest framework框架?

内部提供了很多方便的组件,我们通过一些配置就可以完成一些相应的操作,这样可以提高开发效率。

比如:

  序列化器:能把queryset对象序列化成json格式 + 对用户请求的数据进行校验

  解析器: 获取用户的请求数据,如果没有解析器,客户发的是json格式数据,需要我们自己去request.body去取数据,自己进行格式转换,而用rest framework的解析器,自动帮你处理好了,request.data

  分页: 将从数据库获取到的数据在页面进行分页显示。

还有其他:

  认证、权限、访问频率控制。。。

37、rest framework都有哪些组件?

1. 路由

自动帮助开发者快速为一个视图创建4个url
    www.oldboyedu.com/api/v1/student/$
    www.oldboyedu.com/api/v1/student(?P<format>\w+)$
    www.oldboyedu.com/api/v1/student/(?P<pk>\d+)/$
    www.oldboyedu.com/api/v1/student/(?P<pk>\d+)(?P<format>\w+)$
View Code

2. 认证

问题:认证流程?

  1. 写一个类:authentication认证类(或用rest framework内置的认证类)
  2. 我大概记得,视图函数执行的时候,会触发内部的dispatch,dispatch里面会调用认证方法,我记得好像是在request里面执行的,通过request.user,由user触发的认证

  待整理....

返回值:

  • 认证成功:(user,auth)
  • 匿名用户:None
  • 认证失败:异常

3. 权限

  权限是否可以放在中间件中?以及为什么?

    可以放到中间件中, rest_framework的权限是进入到视图里面进行的校验,而放到中间件中,在请求到达视图之前就可以提前校验

4. 访问频率的控制

匿名用户:

  类内部有个get_ident方法,里面可以拿到IP或代理IP

  根据IP代理IP作为标识进行记录,为每一个用户在redis中创建一个列表(ip附带:时间戳)

  根据最后一次访问时间,计算频率

{
    throttle_1.1.1.1:[时间戳1,时间戳2...],
    throttle_1.1.2.2:[时间戳1,时间戳2...],
    throttle_1.1.3.3:[时间戳1,时间戳2...],
}

注册的用户:

  根据用户名(唯一)或邮箱作为标识进行记录,(其他都和IP的一样)

匿名用户可以真正的防止吗?

  •   无法做到真正的访问频率控制,只能把小白拒之门外。
  •   如果要封IP,使用防火墙来做。
  •   登录用户可以通过用户名作为唯一标示进行控制,如果有人注册很多账号,也无法防止。

5. 视图(三部曲)

6. 解析器

  根据Content-Type请求头对请求体中的数据格式进行处理。request.data

7. 分页器

  • PageNumberPagination
  • LimitOffsetPagination         # 索引+切片
  • CursorPagination          # 加密分页

8.   序列化器

    两个功能: 序列化请求数据格式校验

序列化
        # source参数
        publish = serializers.CharField(source="publish.name")      # 一个对象,可以通过参数source="对象.字段"得到一个具体的字段信息
        # 自定义字段方法
        authors = serializers.SerializerMethodField()
        # 上下两个authors是对应的, get_字段(多对多字段)
        def get_authors(self, obj):         # obj 是book_obj对象
            temp = []
            for author in obj.authors.all():
                temp.append(author.name)
            return temp
            
        #字段类型在数据库中为枚举的字段显示问题: get_XXX_display,不加括号
        level = serializers.CharField(source=‘get_level_display‘)

9. 渲染器?

待续。。。

注意:

  如果视图类不设定queryset,默认为None, 当BrowsableAPIRenderer渲染的时候会触发一个断言错误(assert)

  解决办法:

    指定渲染器只用JSON

    设置:queryset

      class UserView(...):

        queryset = []

assert 是的作用?
    条件成立则继续往下,否则抛出异常
        一般用于:满足某个条件之后,才能执行,否则应该抛出异常。

    class GenericAPIView(views.APIView):

        def get_queryset(self):
        
            queryset = None
            
            assert self.queryset is not None, (
                "'%s' should either include a `queryset` attribute, "
                "or override the `get_queryset()` method."
                % self.__class__.__name__
            )

38、视图中都可以继承哪些类?

rest framework 视图你都用过哪些类?

三个基本点:

1. APIView:这个类属于rest framework中的顶层类,内部帮助我们实现了rest framework的一些基本功能:认证、权限、访问频率。但凡是数据库、分页、等操作,需要我们手动去完成,比较原始。

# 示例:提供增加功能
class GenericAPIView(APIView)
    
    def post(...):    # 自己写post
        pass 

2. GenericViewSet(ViewSetMixin, generics.GenericAPIView):

如果继承这个类,首先路由中的as_view()需要填写对应的请求映射关系:.as_view({'get':'list','post':'create'})

类内部也帮助我们提供了一些方便的方法:

  • get_queryset
  • get_object
  • get_serializer

注意: 断言异常

# 只提供增加功能
class TestView(GenericViewSet):
    serializer_class = XXXXXXX

    def create(self,*args,**kwargs):    # 写个create方法
        pass # 获取数据并对数据进行操作

3. ModelViewSet

对数据库的操作、分页等都不需要我们自己写了,只需要继承相应的类即可。

示例:只提供增加功能
class
TestView(mixins.CreateModelMixin,GenericViewSet): serializer_class = XXXXXXX # 这样就把接口写好了,配置序列化后,啥都不用写了

39、redis两种连接方式?

直接连接和链接池

直接连接:
    import redis 
    r = redis.Redis(host='10.211.55.4', port=6379)
    r.set('foo', 'Bar')
    print r.get('foo')
连接池:
    import redis
    pool = redis.ConnectionPool(host='10.211.55.4', port=6379)
     
    r = redis.Redis(connection_pool=pool)
    r.set('foo', 'Bar')
    print r.get('foo')
View Code

推荐链接池

40、redis的五大数据类型?

String、list、hash、set、有序set

set(name, value, ex=None, px=None, nx=False, xx=False) 
    # 设置值,默认,不存在则创建,存在则修改
    # 参数:
        # ex,过期时间(秒)
        # px,过期时间(毫秒)
        # nx,如果设置为True,则只有name不存在时,当前set操作才执行(添加)
        # xx,如果设置为True,则只有name存在时,当前set操作才执行(更新)

mset(*args, **kwargs)   # 批量设置值
get(name)       # 获取值
mget(keys, *args)
incr(self, name, amount=1)
    # 自增 name对应的值,当name不存在时,则创建name=amount,否则,则自增。
    # 参数:
        # name,Redis的name
        # amount,自增数(必须是整数)
    # 注:同incrby
String

r.set('foo', 'Bar',ex=10)

lpush(name,values)
rpush(name,values)

lpop(name)
    # 在name对应的列表的左侧获取第一个元素并在列表中移除,返回值则是第一个元素
rpop(name) # 表示从右向左操作

blpop(keys, timeout)
    # 将多个列表排列,按照从左到右去pop对应列表的元素
r.brpop(keys, timeout)

llen(name)
    # name对应的list元素的个数

lrange(name, start, end)
    # 在name对应的列表分片获取数据
List
hset(name, key, value)
    key不存在: 添加
    key存在: 修改

hmset(name, mapping)
    # 在name对应的hash中批量设置键值对
    maping是个字典
    hmset('n1', {"k1":1, "k2":2, "k3":3})

hget(name,key)
    # 在name对应的hash中获取根据key获取value

hmget(name, keys, *args)
# 在name对应的hash中获取多个key的值

hscan_iter(name, match=None, count=None)
Hash
sadd(name,values)
    # 在name对应的集合中添加元素values

scard(name)
    # 获取name对应的集合中元素个数

sdiff(keys, *args)
    # 在第一个name对应的集合中且不在其他name对应的集合的元素集合

sinter(keys, *args)
    # 获取多个name对应集合的并集

sscan_iter(name, match=None, count=None)
Set
zadd(name, *args, **kwargs)
    # 在name对应的有序集合中添加元素
    如:
        zadd('zz', 'n1', 1, 'n2', 2)
        或
        zadd('zz', n1=11, n2=22)

zcard(name)
    # 获取name对应的有序集合元素的数量

zcount(name, min, max)
    # 获取name对应的有序集合中分数 在 [min,max] 之间的个数

    # 参数:
        # name,redis的name
        # start,有序集合索引起始位置(非分数)
        # end,有序集合索引结束位置(非分数)
        # desc,排序规则,默认按照分数从小到大排序
        # withscores,是否获取元素的分数,默认只获取元素的值
        # score_cast_func,对分数进行数据转换的函数

r.zrange()    # 排序
zrevrange()

zrangebyscore()    # 按分数排序
zrevrangebyscore()
有序Set
delete(*names)
    # 根据name删除redis中的任意数据类型

exists(name)
    # 检测redis的name是否存在

keys(pattern='*')
    # 根据模型获取redis的name
    # 更多:
        # KEYS * 匹配数据库中所有 key 。
        # KEYS h?llo 匹配 hello , hallo 和 hxllo 等。
        # KEYS h*llo 匹配 hllo 和 heeeeello 等。
        # KEYS h[ae]llo 匹配 hello 和 hallo ,但不匹配 hillo

expire(name ,time)
    # 为某个redis的某个name设置超时时间
    # 字符串有自己的超时时间,其他四个没有,需要expire设置
公共操作

41、场景: 线上商城的商品,如何控制剩余的数量不会出问题?

方式一:redis的Watch实现

import redis

conn = redis.Redis(host='192.168.1.41',port=6379)

conn.set('count',1000)      # 先设置商品数量,模拟场景

with conn.pipeline() as pipe:

    # 先监视,自己的值没有被修改过
    conn.watch('count')

    # 事务开始
    pipe.multi()
    old_count = conn.get('count')
    count = int(old_count)
    # time.sleep(5)
    if count > 0:  # 有库存
        pipe.set('count', count - 1)

    # 执行,把所有命令一次性推送过去
    pipe.execute()

# 如果监视的数量是1000, 而等到提交的时候,数量发生了变化(别人买了商品,修改了数量),这个提交就不会执行

    # 注意:
       # 一台redis没问题
       # 多态redis是不支持事务的(集群分布式)
       # 工作中: 问清楚是否支持事务, 如果不支持,只能一次一次提交
事务

方式二:数据库的锁

42、主从复用

优势:

  高可用

  分担主压力:不用都去主服务器查询,可以去从服务器查询,主服务器只写

注意: slave设置只读

从的配置文件添加以下记录,即可:
  slaveof 1.1.1.1 3306

43、sentinel 哨兵 

监控主从服务器,如果主服务挂了,自动切换,这样程序就不再和主服务器连接,直接和sentinel连接

启动主redis:
    redis-server /etc/redis-6379.conf  启动主redis
    redis-server /etc/redis-6380.conf  启动从redis
    
在linux中:
    找到 /etc/redis-sentinel-8001.conf  配置文件,在内部:
        - 哨兵的端口 port = 8001
        - 主redis的IP,1         # 哨兵个数的一半,比如10个哨兵,5个认为主服务挂掉了,才会切换
    
    找到 /etc/redis-sentinel-8002.conf  配置文件,在内部:
        - 哨兵的端口 port = 8002
        - 主redis的IP, 1 

    启动两个哨兵

44、项目如何提高的并发?

目前你们公司项目1000用户,QPS=1000 ,如果用户猛增10000w,怎么解决?

  1. 数据库读写分离

  2. 设置缓存

  3. 负载均衡

  

 

4、Flask使用过的组件? 

内置:
    路由    # 装饰器
    视图    
    模板      # jinja2
    session
    中间件
    blueprint   # 蓝图,组织项目目录
    flash       # 闪现,只用一次就删除,在session的基础上实现的

第三方组件:
    flask-session
    flask-script        # 定制命令
    flask-aqlalchemy    # 类似Django的ORM
    flask-migrate       # 做数据库迁移
   blinker      # 信号

wtforms         # 类似Django的Form

dbutils
gevent-websocket

flask-login自定义组件

 

posted @ 2018-05-12 21:34  静静别跑  阅读(232)  评论(0)    收藏  举报