视图家族

视图家族在rest_framework源码位置和学习曲线为:

rest_framework.views: 基本视图(APIView)

rest_framework.generics: 工具视图(GenericAPIView)

rest_framework.mixins: 视图工具集(Create/Destroy/List/Retrieve/Update)

rest_framework.viewsets: 视图集

APIView

前几章代码都是基于APIView写的, 这里就不赘述了, 简单来说APIView做了三件事:

1. 重写了as_view()方法, 在as_view()中主要调用了父类的as_view()方法 view = super().as_view(**initkwargs) , 并在最后返回时, 加上了csrf验证 return csrf_exempt(view)

2. 重写了dispatch()方法, 在父类的as_view()中被调用, 这是DRF的真正核心主函数, 在dispatch()中实现了各大模块

GenericAPIView

源码为: rest_framework.generics.GenericAPIView

class GenericAPIView(views.APIView):
"""
Base class for all other generic views.
"""
# You'll need to either set these attributes,
# or override `get_queryset()`/`get_serializer_class()`.
# If you are overriding a view method, it is important that you call
# `get_queryset()` instead of accessing the `queryset` property directly,
# as `queryset` will get evaluated only once, and those results are cached
# for all subsequent requests.
queryset = None
serializer_class = None # If you want to use object lookups other than pk, set 'lookup_field'.
# For more complex lookup requirements override `get_object()`.
lookup_field = 'pk'
lookup_url_kwarg = None # The filter backend classes to use for queryset filtering
filter_backends = api_settings.DEFAULT_FILTER_BACKENDS # The style to use for queryset pagination.
pagination_class = api_settings.DEFAULT_PAGINATION_CLASS def get_queryset(self):
.......

1. 继承与APIView, 保留了APIView的特性, 并在其基础上定义了一下更加方便易用的属性

2. 定义了很多类属性, 如queryset(ORM查找结果集), serializer_class(序列化类), lookup_field(主键字段变量名),

3. 对于上述属性定义了对应的get_xxx方法, 如get_queryset(获取结果查找集), get_serializer(获取序列化类), get_object(获取模型对象)等等

如何使用GenericAPIView

让我们先回顾一下APIView如何实现查询接口的

class BookAPIView(APIView):
def get(self, request, *args, **kwargs):
pk = kwargs.get('pk')
if pk:
# 单查
books = Book.objects.get(pk=pk)
many = False
else:
# 群查
books = Book.objects.filter(is_delete=False).all()
many = True
serializer = BookSerializer(instance=books, many=many)
return MyResponse(result=serializer.data)

可以看到一般查询接口分为3步:

1. 获取结果查找集books

2. 序列化查询结果集得到序列化对象serializer

3. 返回响应

GenericAPIView就是将这通用的三步, 进行封装, 让使用者只需要编写关键的变量值即可

将上面的代码转化为GenericAPIView就是:

class BookGenericAPIView(GenericAPIView):
queryset = Book.objects.filter(is_delete=False)
serializer_class = BookSerializer def get(self, request, *args, **kwargs):
pk = kwargs.get('pk')
if pk:
# 单取
return self.retrieve(request, *args, **kwargs)
# 群取
return self.list(request, *args, **kwargs) # 单取
def retrieve(self, request, *args, **kwargs):
# books = Book.objects.filter(is_delete=False, pk=pk).first()
books = self.get_object()
# serializer = BookSerializer(instance=books)
serializer = self.get_serializer(instance=books)
return MyResponse(result=serializer.data) # 群取
def list(self, request, *args, **kwargs):
# books = Book.objects.filter(is_delete=False).all()
books = self.get_queryset()
# serializer = BookSerializer(instance=books, many=True)
serializer = self.get_serializer(instance=books, many=True)
return MyResponse(result=serializer.data)

这里将单取和群取做成了两个独立的函数, 且这样看似使用GenericAPIView时代码似乎比使用APIView更多, 但是先不要着急, 我们可以配合后面的mixin一起使用时就不需要写这么多代码了

使用GenericAPIView时, 将原先使用APIView的写法进行了如下替换:

1. 定义了两个变量, queryset用于保存查询结果集, serializer用于保存序列化类

2. 将原获取查询结果集的ORM写法  books = Book.objects.filter(is_delete=False).all() 换成了 books = self.get_queryset() 或 books = self.get_object() , 这里需要注意的一点就是在get_object()内部, 默认是通过参数名'pk'获取具体的主键ID, 而这就需要在url中的参数名也要为'pk', 若url中参数名为'xxx', 则需要重写GenericAPIView中的lookup_field = 'xxx'

3. 将原通过实例化序列化类写法  serializer = BookSerializer(instance=books) 换成了 serializer = self.get_serializer(instance=books)

其中get_queryset()和get_serializer()其实就是去拿到类属性queryset和Serializer_class, 然后再进行实例化操作, 即将原来APIView的写法拆成了两步: 第一步, 定义类属性; 第二步, 获取类属性并实例化.

Mixins工具集

上面手动简单实现了GenericAPIView的单查retrieve和群查list, 而mixins的工具集中就已经封装好了对应的函数, 且功能更加强大, 源码位置为: rest_framework.mixins.py

mixins中主要定义了5个类: CreateModelMixin(用于创建模型类对象), DestroyModelMixin(用于删除模型类对象), ListModelMixin(用于群查模型类对象), RetrieveModelMixin(用于单查模型类对象), UpdateModelMixin(用于单增模型类对象)

Mixins的使用

我们先跟着上面的查询接口, 看一下ListModelMixin用法, 查看ListModelMixin源码可以看到, 其群查的list方法和我们上面手动写的list方法很像, 只是多加了过滤器和分页器的功能

class ListModelMixin:
"""
List a queryset.
"""
def list(self, request, *args, **kwargs):
# 过滤器
queryset = self.filter_queryset(self.get_queryset()) # 分页器
page = self.paginate_queryset(queryset)
if page is not None:
serializer = self.get_serializer(page, many=True)
return self.get_paginated_response(serializer.data) serializer = self.get_serializer(queryset, many=True)
return Response(serializer.data)

具体用法为:

class BookMixinGenericAPIView(ListModelMixin, GenericAPIView):
queryset = Book.objects.filter(is_delete=False)
serializer_class = BookSerializer def get(self, request, *args, **kwargs):
# 群查
# mixins提供的list方法的响应对象是Response, 想将该对象格式化为自定的MyResponse
# response的数据都存放在response.data中
response = self.list(self, request, *args, **kwargs)
return MyResponse(result=response.data)

可以看到mixin工具类和GenericAPIView组合使用时, 实现群查接口非常方便, 只需要三步:

1. 继承ListModelMixin和GenericAPIView

2. 定义类属性queryset和Serializer_class

3. 调用list方法并返回response

同样想要实现单查的话, 再继承一下RetrieveModelMixin, 然后手动分配一下get的方法调用即可, 需要注意的是继承时, 先写mixins的类, 最后写GenericAPIView, 因为是在mixin中调用了GenericAPIView的方法

class BookMixinGenericAPIView(mixins.ListModelMixin, mixins.RetrieveModelMixin,
GenericAPIView):
queryset = Book.objects.filter(is_delete=False)
serializer_class = BookSerializer def get(self, request, *args, **kwargs):
pk = kwargs.get('pk')
if pk:
# 单查
response = self.retrieve(request, *args, **kwargs)
else:
# 群查
response = self.list(self, request, *args, **kwargs)
# mixins提供的list方法的响应对象是Response, 想将该对象格式化为自定的MyResponse
# response的数据都存放在response.data
return MyResponse(result=response.data)

同理, mixins还提供了其他的单增/单删/单整体改/单局部改的接口, 完整案例如下:

from rest_framework.generics import GenericAPIView
from rest_framework import mixins
from utils.response import MyResponse
from books.models import Book
from books.serializers import BookSerializer class BookMixinGenericAPIView(mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.CreateModelMixin,
mixins.DestroyModelMixin, mixins.UpdateModelMixin,
GenericAPIView):
queryset = Book.objects.filter(is_delete=False)
serializer_class = BookSerializer # 查
def get(self, request, *args, **kwargs):
pk = kwargs.get('pk')
if pk:
# 单查
response = self.retrieve(request, *args, **kwargs)
else:
# 群查
response = self.list(self, request, *args, **kwargs)
# mixins提供的list方法的响应对象是Response, 想将该对象格式化为自定的MyResponse
# response的数据都存放在response.data
return MyResponse(result=response.data) # 单增
def post(self, request, *args, **kwargs):
response = self.create(request, *args, **kwargs)
return MyResponse(result=response.data) # 单删
def delete(self, request, *args, **kwargs):
response = self.destroy(request, *args, **kwargs)
return MyResponse(result=response.data) # 单整体改
def put(self, request, *args, **kwargs):
response = self.update(request, *args, **kwargs)
return MyResponse(result=response.data) # 单局部改
def patch(self, request, *args, **kwargs):
response = self.partial_update(request, *args, **kwargs)
return MyResponse(result=response.data)

mixins的钩子函数

在上面我们调用了mixin的self.create/destroy/update方法实现增删改, 但如果我们对增删改操作还有一些附加的操作(比如在新增时触发发送邮件功能)或者不想用其默认的方法(比如它原来的删除确实是把数据直接删除掉了, 而我们想要的删除只是改一下数据的is_dalete状态)时, mixins给我们提供了相应的钩子函数perform_create/perform_destroy/perform_update, 这些函数都在源码相应的类中定义了:

def perform_create(self, serializer):
serializer.save() def perform_destroy(self, instance):
instance.delete() def perform_update(self, serializer):
serializer.save()

现在我们在我们的视图类中重写一下删除的操作perform_destroy, 将数据的is_dalete状态改为True

    # 单删
def perform_destroy(self, instance):
instance.is_delete = True
instance.save() def delete(self, request, *args, **kwargs):
response = self.destroy(request, *args, **kwargs)
return MyResponse(result=response.data)

generics

上面我们把GenericAPIView和mixins的工具集联合使用, 同时继承, 可以实现简单的增删改查接口, 在generics.py中, 除了提供了GenericAPIView外, 还提供了一些其他的类, 如 CreateAPIView/DestroyAPIView/ UpdateAPIView/ ListCreateAPIView/ RetrieveDestroyAPIView 等等, 这些类都是上我们上面同时继承mixin和GenericAPIView一样, 是帮我们封装好的一些工具类, 源码如下:

# 单增
class CreateAPIView(mixins.CreateModelMixin,
GenericAPIView):
"""
Concrete view for creating a model instance.
"""
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs) # 群查
class ListAPIView(mixins.ListModelMixin,
GenericAPIView):
"""
Concrete view for listing a queryset.
"""
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs) # 单查
class RetrieveAPIView(mixins.RetrieveModelMixin,
GenericAPIView):
"""
Concrete view for retrieving a model instance.
"""
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs) # 单删
class DestroyAPIView(mixins.DestroyModelMixin,
GenericAPIView):
"""
Concrete view for deleting a model instance.
"""
def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs) # 单改
class UpdateAPIView(mixins.UpdateModelMixin,
GenericAPIView):
"""
Concrete view for updating a model instance.
"""
def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs) def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs) # 群查和单增
class ListCreateAPIView(mixins.ListModelMixin,
mixins.CreateModelMixin,
GenericAPIView):
"""
Concrete view for listing a queryset or creating a model instance.
"""
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs) def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs) # 单查和单改
class RetrieveUpdateAPIView(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
GenericAPIView):
"""
Concrete view for retrieving, updating a model instance.
"""
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs) def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs) def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs) # 单查和单删
class RetrieveDestroyAPIView(mixins.RetrieveModelMixin,
mixins.DestroyModelMixin,
GenericAPIView):
"""
Concrete view for retrieving or deleting a model instance.
"""
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs) def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs) # 单查和单改和单删
class RetrieveUpdateDestroyAPIView(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
GenericAPIView):
"""
Concrete view for retrieving, updating or deleting a model instance.
"""
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs) def put(self, request, *args, **kwargs):
return self.update(request, *args, **kwargs) def patch(self, request, *args, **kwargs):
return self.partial_update(request, *args, **kwargs) def delete(self, request, *args, **kwargs):
return self.destroy(request, *args, **kwargs)

viewsets

在使用了上面的mixin工具后, 会发现增删改查的函数名(create/destroy/partial_update/update/list/retrieve)与对应的请求方式名(post/delete/patch/put/get)两者的命名并不是一样的, 而是存在一个对应关系或者映射关系.我们把两者结合的方式就是在视图类中通过dispatch的反射而定义与请求方式名一样的函数(post/delete/patch/put/get), 在函数中调用mixin的对应的方法.

ViewSetMixin

那么这种结合方式或者对应关系, 能不能写的更加简单一点呢? 在rest_framework.viewsets.py中, 有一个 ViewSetMixin 类, 这个类的作用就是重写了 as_view() 方法, 让其增加一个字典参数(actions), 把请求方式名与接口函数名的对应的关系直接写在字典参数(actions)中, 源代码分析为:

class ViewSetMixin:
"""
This is the magic. Overrides `.as_view()` so that it takes an `actions` keyword that performs
the binding of HTTP methods to actions on the Resource. For example, to create a concrete view binding the 'GET' and 'POST' methods
to the 'list' and 'create' actions... view = MyViewSet.as_view({'get': 'list', 'post': 'create'})
""" @classonlymethod
def as_view(cls, actions=None, **initkwargs):
....代码省略 # actions must not be empty
if not actions:
raise TypeError("The `actions` argument must be provided when "
"calling `.as_view()` on a ViewSet. For example "
"`.as_view({'get': 'list'})`")
....代码省略
def view(request, *args, **kwargs):
self = cls(**initkwargs)
# We also store the mapping of request methods to actions,
# so that we can later set the action attribute.
# eg. `self.action = 'list'` on an incoming GET request.
self.action_map = actions # Bind methods to actions
# This is the bit that's different to a standard view
# 绑定请求方式method与接口函数action的对应关系
for method, action in actions.items():
handler = getattr(self, action)
setattr(self, method, handler)
....代码省略
return self.dispatch(request, *args, **kwargs)
....代码省略
view.actions = actions
return csrf_exempt(view)

可以看到:

1. 在类的说明文档中, 就告诉了我们说这是重写的as_view()方法, 新增了一个actions参数用来绑定HTTP请求方式和对应的操作, 例如: MyViewSet.as_view({'get': 'list', 'post': 'create'})

2. 在as_view中的view()方法中, 同样通过反射进行上述的绑定

GenericViewSet/ViewSet

上述的ViewSetMixin类只是重写了as_view()方法让其新增一个actions参数而已, 而具体使用时还需要和前面提到的GenericAPIView或者更基础的APIView一起使用, 因为具体的处理逻辑还是需要走dispatch方法.

于是在viewsets.py中还提供了两个类, GenericViewSet(ViewSetMixin, generics.GenericAPIView) 和 ViewSet(ViewSetMixin, views.APIView) , 就像之前我们选择使用基础的APIView还是GenericAPIView一样, DRF也提供了两个对应的ViewSet类, 这里我们使用GenericViewSet实现简单的群查功能:

# 在urls.py中添加配置
from django.urls import path
from books import views urlpatterns = [
...
path('v4/', views.BookGenericViewSet.as_view({'get': 'list'})),
] # 在views.py中添加视图类
from rest_framework.viewsets import GenericViewSet
class BookGenericViewSet(GenericViewSet):
queryset = Book.objects.filter(is_delete=False)
serializer_class = BookSerializer def list(self, request):
books = self.get_queryset()
serializer = self.get_serializer(books, many=True)
return MyResponse(result=serializer.data)

ModelViewSet/ReadOnlyModelViewSet

我们在上一步继承了GenericViewSet, 但是list方法还是我们自己手动写的, 我们可以结合上面的mixin工具一起使用, 例如同时继承mixins的ListModelMixin和GenericViewSet实现群查, 这样list方法在mixin中就定义好了, 我们也不需要手动写list方法, 视图类代码就变得更加简单, 只需要定义类属性queryset和Serializer_class即可:

# 在urls.py中添加配置
from django.urls import path
from books import views urlpatterns = [
...
path('v5/', views.BookListMixinGenericViewSet.as_view({'get': 'list'})),
] # 在views.py中添加视图类
from rest_framework import mixins
from rest_framework.viewsets import GenericViewSet class BookListMixinGenericViewSet(mixins.ListModelMixin, GenericViewSet):
queryset = Book.objects.filter(is_delete=False)
serializer_class = BookSerializer

同理, 我们还可以加上其他接口的mixin工具, 如单查mixins.RetrieveModelMixin, 单增mixins.CreateModelMixin等等, 而这一步DRF也为我们想好了, 在viewsets中定义了一个 ModelViewSet 类, 它继承了mixin的所有接口类

class ModelViewSet(mixins.CreateModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
mixins.ListModelMixin,
GenericViewSet):
"""
A viewset that provides default `create()`, `retrieve()`, `update()`,
`partial_update()`, `destroy()` and `list()` actions.
"""
pass

同时, DRF还定义了一个 ReadOnlyModelViewSet 类, 顾名思义, 这个类只能用来查询, 只继承了 单查mixins.RetrieveModelMixin和 群查 mixins.ListModelMixin两个接口工具类

class ReadOnlyModelViewSet(mixins.RetrieveModelMixin,
mixins.ListModelMixin,
GenericViewSet):
"""
A viewset that provides default `list()` and `retrieve()` actions.
"""
pass

我们可以按需使用ModelViewSet类还是ReadOnlyModelViewSet, 这里我们使用ModelViewSet, 不需要几行代码就能完成简单的单增, 单删, 单整体改, 单局部改, 单查, 群查6个接口, 但是这里需要注意的是由于使用的是mixin的默认接口函数, 我们之前自定义的响应类MyResponse就无法使用了, 除非我们再重写一下这些默认的接口函数:

# 在urls.py中添加配置
from django.urls import path
from books import views urlpatterns = [
...
path('v6/', views.BookModelViewSet.as_view({'get': 'list', 'post': 'create', })),
path('v6/<int:pk>/', views.BookModelViewSet.as_view(
{'get': 'retrieve', 'delete': 'destroy', 'put': 'update', 'patch': 'partial_update'})),
] # 在views.py中添加视图类
from rest_framework import mixins
from rest_framework.viewsets import ModelViewSet class BookModelViewSet(ModelViewSet):
queryset = Book.objects.filter(is_delete=False)
serializer_class = BookSerializer

路由(Router)

有一些像Rails这样的Web框架提供自动生成Urls的功能。但是Django并没有。 REST framework为Django添加了这一功能,以一种简单、快速、一致的方式。

DRF的routers模块没有什么东西,主要包含下面几个类:

BaseRouter:路由的基类

SimpleRouter: 继承了BaseRouter,常用类之一

DefaultRouter:继承了SimpleRouter,常用类之一

APIRootView

我们主要使用的其实就是SimpleRouter和DefaultRouter, 基本用法为, 编辑urls.py:

from rest_framework.routers import DefaultRouter

# 1.创建路由对象
router = DefaultRouter()
# 2.注册视图类
router.register(r'v6', views.BookModelViewSet, basename='v6')
router.register(r'v5', views.BookListMixinGenericViewSet, basename='v5') urlpatterns = [
# path('v5/', views.BookListMixinGenericViewSet.as_view({'get': 'list'})),
# path('v6/', views.BookModelViewSet.as_view({'get': 'list', 'post': 'create', })),
# path('v6/<int:pk>/', views.BookModelViewSet.as_view(
# {'get': 'retrieve', 'delete': 'destroy', 'put': 'update', 'patch': 'partial_update'})), # 3.将路由类的urls添加到urlpatterns中
path('', include(router.urls)),
]

django-rest-framework-源码解析003-视图家族和路由(APIView/GenericAPIView/mixins/generics/viewsets)的更多相关文章

  1. Django Rest Framework源码剖析(八)-----视图与路由

    一.简介 django rest framework 给我们带来了很多组件,除了认证.权限.序列化...其中一个重要组件就是视图,一般视图是和路由配合使用,这种方式给我们提供了更灵活的使用方法,对于使 ...

  2. Django Rest Framework源码剖析(三)-----频率控制

    一.简介 承接上篇文章Django Rest Framework源码剖析(二)-----权限,当服务的接口被频繁调用,导致资源紧张怎么办呢?当然或许有很多解决办法,比如:负载均衡.提高服务器配置.通过 ...

  3. springMVC源码解析--ViewResolver视图解析器执行(三)

    之前两篇博客springMVC源码分析--ViewResolver视图解析器(一)和springMVC源码解析--ViewResolverComposite视图解析器集合(二)中我们已经简单介绍了一些 ...

  4. django之admin源码解析

    解析admin的源码 第一步:项目启动,加载settings文件中的 INSTALLED_APPS 里边有几个app就加载几个,按照注册顺序来执行. 第二步:其中加载的是admin.py,加载每一个a ...

  5. springMVC源码解析--ViewResolverComposite视图解析器集合(二)

    上一篇博客springMVC源码分析--ViewResolver视图解析器(一)中我们介绍了一些springMVC提供的很多视图解析器ViewResolver,在开发的一套springMVC系统中是可 ...

  6. drf框架 - 视图家族 | GenericAPIView | mixins | generics | viewsets

    视图家族 view:视图 generics:工具视图 mixins:视图工具集 viewsets:视图集 学习曲线: APIView => GenericAPIView => mixins ...

  7. Django Rest Framework源码剖析(五)-----解析器

    一.简介 解析器顾名思义就是对请求体进行解析.为什么要有解析器?原因很简单,当后台和前端进行交互的时候数据类型不一定都是表单数据或者json,当然也有其他类型的数据格式,比如xml,所以需要解析这类数 ...

  8. Django Rest Framework源码剖析(七)-----分页

    一.简介 分页对于大多数网站来说是必不可少的,那你使用restful架构时候,你可以从后台获取数据,在前端利用利用框架或自定义分页,这是一种解决方案.当然django rest framework提供 ...

  9. Django Rest Framework源码剖析(六)-----序列化(serializers)

    一.简介 django rest framework 中的序列化组件,可以说是其核心组件,也是我们平时使用最多的组件,它不仅仅有序列化功能,更提供了数据验证的功能(与django中的form类似). ...

随机推荐

  1. 基于TCP与UDP协议的socket通信

    基于TCP与UDP协议的socket通信 C/S架构与初识socket 在开始socket介绍之前,得先知道一个Client端/服务端架构,也就是 C/S 架构,互联网中处处充满了 C/S 架构(Cl ...

  2. python fabric安装

    1 安装epel wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo 2 安装pip yum i ...

  3. 二.drf之使用序列化编写视图

    总结:两功能序列化: a.拿到queryset --->idc = Idc.objects.all() b.将queryset给序列化成类---->serializer = IdcSeri ...

  4. hive 时间戳函数之unix_timestamp,from_unixtime

    一. 日期>>>>时间戳 1.unix_timestamp() 获取当前时间戳 例如:select unix_timestamp() -- 2.unix_timestamp(s ...

  5. DTD约束和Schema约束

    DTD约束 什么是DTD? DTD(Document Type Definition),文档类型定义,用来约束XML文档.规定XML文档中元素的名称,子元素的名称及顺序,元素的属性等. DTD约束长什 ...

  6. C++ 半同步半异步的任务队列

    代码已发布至 HAsyncTaskQueue

  7. PHP实现邮箱验证码验证功能

    *文章来源:https://blog.egsec.cn/archives/623  (我的主站) *本文将主要说明:PHP实现邮箱验证码验证功能,通过注册或登录向用户发送身份确认验证码,并通过判断输入 ...

  8. 资深前端工程师带你认识网页后缀html、htm、shtml、shtm有什么区别?

    每一个网页或者说是web页都有其固定的后缀名,不同的后缀名对应着不同的文件格式和不同的规则.协议.用法,最常见的web页的后缀名是.html和.htm,但这只是web页最基本的两种文件格式,今天我们来 ...

  9. CSS3样式_实现字体发光效果

    text-shadow 属性仅仅是用来设置文本阴影的,似乎并不能实现字体发光效果.其实不然,这正是 text-shadow 属性的精妙之处.当阴影的水平偏移量和垂直偏移量都为0时,阴影就和文本重合了. ...

  10. finally 关键字

    异常处理的时侯 出现的关键字finally 不论在  try  代码块中是否出现  发生了异常时间,  catch语句是否执行,catch语句是否有异常,catch语句中是否return关键字  ,f ...