前言

   drf视图的源码非常的绕,但是实现的功能却非常的神奇。

   它能够帮你快速的解决ORM增删改查的重复代码,非常的方便好用。

   下面是它源码中的一句话:

class ViewSetMixin:
"""
This is the magic.
"""

   好了,屁话不多说,直接看看drf视图中的功能吧。

准备工作

   此次的Django采用3版本,所以相对于1版本来说有一些差异。

模型表

   下面是模型表:

from django.db import models

# Create your models here.
class User(models.Model):
user_id = models.AutoField(primary_key=True)
user_name = models.CharField(max_length=50)
user_gender = models.BooleanField(
[(0,"male"),(1,"female")],
default = 0,
)
user_age = models.IntegerField() def __str__(self):
return self.user_name class Meta:
db_table = ''
managed = True
verbose_name = 'User'
verbose_name_plural = 'Users'

   数据如下:

INSERT INTO app01_user(user_name,user_age,user_gender) VALUES
("用户1",18,0),
("用户2",19,1),
("用户3",19,1);

序列类

   序列类采用ModelSerializer

from rest_framework import serializers
from . import models class UserModelSerializers(serializers.ModelSerializer): class Meta:
model = models.User
fields = "__all__"

url路由

   下面是url路由的设定:

re_path('^api/users/(?P<uid>\d+)?',views.UserAPI.as_view())

封装Rsponse

   由于不需要返回原生的Response,所以我们封装了一个类,用于更加方便的返回Response

class ResponseMeta(type):
# 对Response类做封装
def __call__(cls, *args, **kwargs):
obj = cls.__new__(cls, *args, **kwargs)
cls.__init__(obj, *args, **kwargs)
return Response(data=obj.__dict__) class CommonResponse(object, metaclass=ResponseMeta):
# 返回的信息
def __init__(self, status, data=None, errors=None):
self.status = status
self.data = data
self.errors = errors

APIView

继承关系

   APIView的导入如下:

from rest_framework.views import APIView

   APIView继承了原生的DjangoView,在其之上做了一些封装,使操作更加简单。

  

封装特性

   在APIView中对原生的request对象进行封装,最常用的两个属性如下,它弥补了Django原生ViewJSON请求格式的数据没有处理的缺陷。

   同时,APIView认为对于GET请求的资源参数,不应该使用GET获取,而是应该使用query_params进行获取。

属性 描述
request.data 当请求数据为Json格式时,将以dict形式保存,主要针对request.POST请求
request.query_params 当请求方式为GET时,可获取url中的请求数据

接口书写

   以下是使用APIViewUser表进行增删改查的接口书写。

from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.request import Request
from . import models
from . import ser class ResponseMeta(type):
# 对Response类做封装
def __call__(cls, *args, **kwargs):
obj = cls.__new__(cls, *args, **kwargs)
cls.__init__(obj, *args, **kwargs)
return Response(data=obj.__dict__) class CommonResponse(object, metaclass=ResponseMeta):
# 返回的信息
def __init__(self, status, data=None, errors=None):
self.status = status
self.data = data
self.errors = errors class UserAPI(APIView):
def get(self, request, uid=None):
if not uid:
# 获取所有
user_queryset = models.User.objects.all()
if user_queryset.exists():
serialization = ser.UserModelSerializers(instance=user_queryset, many=True)
return CommonResponse(status=100, data=serialization.data, errors=None)
return CommonResponse(status=200, errors="暂时没有任何学生")
else:
user_obj = models.User.objects.filter(pk=uid).first()
if user_obj:
serialization = ser.UserModelSerializers(instance=user_obj)
return CommonResponse(status=100, data=serialization.data, errors=None)
return CommonResponse(status=200, errors="没有该学生") def post(self, request): serialization = ser.UserModelSerializers(data=request.data)
if serialization.is_valid():
serialization.save()
return CommonResponse(status=100, data=serialization.data, errors=None)
return CommonResponse(status=200, errors=serialization.errors) def patch(self, request, uid):
user_obj = models.User.objects.filter(pk=uid).first()
if user_obj:
serialization = ser.UserModelSerializers(instance=user_obj, data=request.data)
if serialization.is_valid():
serialization.save()
return CommonResponse(status=100, data=serialization.data, errors=None)
else:
return CommonResponse(status=200, errors="修改失败,请检查字段是否一直")
else:
return CommonResponse(status=200, errors="修改失败,请检查该用户是否存在") def delete(self,request,uid):
models.User.objects.get(pk=uid).delete()
return CommonResponse(status=100,data="删除成功",errors=None)

问题发现

   在上述代码中,问题有以下几点:

  1. 重复代码多,在每个接口中都需要书写ORM查询
  2. 每个接口都需要针对同一个序列类做出不同的实例化

GenericAPIView

继承关系

   GenericAPIView的导入如下:

from rest_framework.generics import GenericAPIView

   以下是它的继承关系:

  

   可以发现它是对APIView的继承,所以理论上来说应该又多了一些东西。

源码阅读

   下面来看一下GenericAPIView的源码,首先你可以发现大概有4个类属性:

class GenericAPIView(views.APIView):

    queryset = None  # 要查询的数据表
serializer_class = None # 执行序列化的序列化类 lookup_field = 'pk' # 查询时的查询条件,默认按主键查询
lookup_url_kwarg = None # 如果在视图中,url捕获的查询数据表过滤参数不是pk,你应该进行声明

   接着往下看,其实它的方法很少,对外暴露的方法就更少了。

  

   我们这里就先看最常用的,即对外暴露的方法,首先是get_queryset()

    def get_queryset(self):

        assert self.queryset is not None, (
"'%s' should either include a `queryset` attribute, "
"or override the `get_queryset()` method."
% self.__class__.__name__
) # 做验证,即实例属性queryset不能是空,代表这个类属性你必须要声明,你可以选择将它做成类属性也可以做成实例属性 queryset = self.queryset # 进行赋值,将self.queryset赋值为类属性。先在UserAPI的实例中找,找不到再到UserAPI的类中找
if isinstance(queryset, QuerySet):
queryset = queryset.all() # 如果它是一个QuerySET对象,就获取全部,得到一个QuerySetDict对象
return queryset # 进行返回

   看到这里发现了一个点,即queryset这个属性必须要进行赋值,由于属性查找顺序是先查找实例,而后查找类本身,所以我们直接在UserAPI中声明queryset为类属性即可。

   接下来继续继续看,get_object(),见明知意,它可以从数据表中获取单个对象:

    def get_object(self):
queryset = self.filter_queryset(self.get_queryset()) # 首先会运行get_queryset(),获取一个所有对象的列表,然后进行filter过滤
lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field # 这里是对url中获取到的变量进行映射 assert lookup_url_kwarg in self.kwargs, ( # 比如,url中获取到的名为uid,如果uid没有在kwargs中,即是{uid:4}中,则抛出异常
'Expected view %s to be called with a URL keyword argument '
'named "%s". Fix your URL conf, or set the `.lookup_field` '
'attribute on the view correctly.' %
(self.__class__.__name__, lookup_url_kwarg)
) filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]} # 进行查询替换,默认的self.lookup_field是pk,将uid替换为pk。那么这里就是{pk:4}
obj = get_object_or_404(queryset, **filter_kwargs) # 获取对象
self.check_object_permissions(self.request, obj) # 进行验证权限 return obj # 返回单个对象

   看到这里就发现了,默认查询条件是用pk,也就是说你的url中必须要用pk这个形参名进行分组捕获。否则就需要声明lookup_url_kwarg,即lokup_url_kwarg="uid",然后进行替换组建filter_kwargs。当然如果你的查询条件不是用的pk,就需要修改lookup_field为字段名,如我不是按照pk进行查询,而是按照name,就修改lookup_fieldname

re_path('^api/users/(?P<uid>\d+)?',views.UserAPI.as_view())

   接下来再看另一个方法get_serializer()

   def get_serializer(self, *args, **kwargs):
serializer_class = self.get_serializer_class() # 内部调get_serializer_class
kwargs.setdefault('context', self.get_serializer_context()) # 获取context属性
return serializer_class(*args, **kwargs) # 调用serializer_class并返回

   接下来是get_serializer_class()方法:

    def get_serializer_class(self):

        assert self.serializer_class is not None, (  # 传入的必须不能是None
"'%s' should either include a `serializer_class` attribute, "
"or override the `get_serializer_class()` method."
% self.__class__.__name__
) return self.serializer_class # 返回设置的属性,serializer_class,即序列化类

   OK,其实源代码读到这里就行了。

封装特性

   通过上面的源代码分析,总结出如下方法的使用:

方法/属性 描述
queryset 将要查询的数据表,类型应该是QuerySet
serializer_class 将要执行的序列化类,类型不能为None
lookup_field 查询时的查询条件,默认为pk
lookup_url_kwarg 视图中url捕获的查询条件变量名如果不是pk,则应该进行指定
get_queryset() 查询获取所有记录
get_object() 查询获取单条记录
get_serializer() 执行序列化对象

   关于最常用的调用方法就三个,常用属性四个。

   其他的方法基本上都是内部调用,所以暂时不深究。

接口书写

   接下来使用GenericAPIView进行接口书写。

from rest_framework.response import Response
from rest_framework.generics import GenericAPIView
from . import models
from . import ser class ResponseMeta(type):
# 对Response类做封装
def __call__(cls, *args, **kwargs):
obj = cls.__new__(cls, *args, **kwargs)
cls.__init__(obj, *args, **kwargs)
return Response(data=obj.__dict__) class CommonResponse(object, metaclass=ResponseMeta):
# 返回的信息
def __init__(self, status, data=None, errors=None):
self.status = status
self.data = data
self.errors = errors class UserAPI(GenericAPIView):
queryset = models.User.objects # 传入对象即可
serializer_class = ser.UserModelSerializers # 序列化类
lookup_field = "pk"
lookup_url_kwarg = "uid" # 由于捕获的是uid,需要声明 def get(self, request, uid=None):
if not uid:
# 获取所有
user_queryset = self.get_queryset() # 获取所有
if user_queryset.exists():
serialization = self.get_serializer(instance=user_queryset,many=True) # 获取序列化类,序列化多条
return CommonResponse(status=100, data=serialization.data, errors=None)
return CommonResponse(status=200, errors="暂时没有任何学生") else:
user_obj = self.get_object()
if user_obj:
serialization = self.get_serializer(instance=user_obj)
return CommonResponse(status=100, data=serialization.data, errors=None)
return CommonResponse(status=200, errors="没有该学生") def post(self, request): serialization = self.get_serializer(data=request.data)
if serialization.is_valid():
serialization.save()
return CommonResponse(status=100, data=serialization.data, errors=None)
return CommonResponse(status=200, errors=serialization.errors) def patch(self, request, uid):
user_obj = self.get_object()
if user_obj:
serialization = self.get_serializer(instance=user_obj,data=request.data)
if serialization.is_valid():
serialization.save()
return CommonResponse(status=100, data=serialization.data, errors=None)
else:
return CommonResponse(status=200, errors="修改失败,请检查字段是否一直")
else:
return CommonResponse(status=200, errors="修改失败,请检查该用户是否存在") def delete(self,request,uid):
self.get_object().delete()
return CommonResponse(status=100,data="删除成功",errors=None)

问题发现

   相对于使用APIView来说,它不必再手动去写ORM语句。

   但是对于返回信息、对于验证操作还是要自己写。

mixins中扩展类

五个扩展类

   下面是rest_framework.mixins中的五个扩展类,它们做了更高级别的封装,配合GenericAPIView使用有奇效。

from rest_framework.mixins import ListModelMixin,RetrieveModelMixin,CreateModelMixin,UpdateModelMixin,DestroyModelMixin
描述
ListModelMixin 该类主要负责查询所有记录
RetrieveModelMixin 该类主要负责查询单条记录
CreateModelMixin 该类主要负责创建记录
UpdateModelMixin 该类主要负责对记录做更新操作
DestroyModelMixin 该类主要负责删除记录

继承关系

   这五个类都继承于object,是独立的子类。

  

源码阅读

   下面是ListModelMixin的源码,不难发现,它就是配合GenericAPIView使用的,因为它会使用get_queryset()方法,并且,它会自动的返回Response对象,并把验证结果添加进去:

class ListModelMixin:

    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)

   至于其他几个类,其实都差不多,这里摘出两个比较特别的类来看一下,分别是CreateModelMixinDestroyModelMixin这两个类。

   下面是CreateModelMixin类的源码:

class CreateModelMixin:

    def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer) # 内部进行序列化类保存
headers = self.get_success_headers(serializer.data) # 返回一个location请求头
return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) # 注意返回结果,状态码是201 def perform_create(self, serializer):
serializer.save() def get_success_headers(self, data):
try:
return {'Location': str(data[api_settings.URL_FIELD_NAME])}
except (TypeError, KeyError):
return {}

   下面是DestroyModelMixin类的源码:

class DestroyModelMixin:
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
self.perform_destroy(instance) # 内部执行删除
return Response(status=status.HTTP_204_NO_CONTENT) # 返回状态码204 def perform_destroy(self, instance):
instance.delete()

   那么读这两个类的源码,就是想要让你知道,创建成功后的返回状态码是201,而删除成功的返回状态码是204。这在REST规范中写的很清楚,可以看见这里也是这么做的。

类与方法

   下面是不同的五个扩展类中不同的五个方法,功能与类一样。

方法 描述
ListModelMixin list() 查询所有,并返回Response对象
RetrieveModelMixin retrieve() 查询单条,并返回Response对象
CreateModelMixin create() 创建记录,并返回Response对象
UpdateModelMixin update() 更新记录,并返回Response对象
DestroyModelMixin destroy() 删除记录,并返回Response对象

   由于它会自动进行return Response(),所以我们就不用再对返回对象进行包装了。

接口书写

   下面是利用GenericAPIViewmixins中的五个扩展类进行接口书写。

from . import models
from . import ser
from rest_framework.generics import GenericAPIView
from rest_framework.mixins import ListModelMixin,RetrieveModelMixin,CreateModelMixin,UpdateModelMixin,DestroyModelMixin class UserAPI(GenericAPIView,ListModelMixin,RetrieveModelMixin,CreateModelMixin,UpdateModelMixin,DestroyModelMixin): queryset = models.User.objects # 传入对象即可
serializer_class = ser.UserModelSerializers # 序列化类
lookup_field = "pk"
lookup_url_kwarg = "uid" # 由于捕获的是uid,需要声明 def get(self, request, uid=None):
if not uid:
# 获取所有
return self.list(request) else:
return self.retrieve(request,uid) def post(self, request):
return self.create(request) def patch(self, request, uid):
return self.update(request,uid) def delete(self,request,uid):
return self.destroy(request,uid)

问题发现

   可以看见,代码相比于前两个少了非常非常多。但是还是存在一些问题。

   第一个问题就是这个视图UserAPI继承的类太多了,太长了,其次就是每次都需要在视图中return,它能不能帮我们自己return呢?那这个就非常舒服了。

modelViewSet

基本使用

   modelViewSet是针对GenericAPIViewmixins中扩展类的结合使用做了一些优化,它可以根据不同的请求自动的做出回应。

   同时也不再需要你在视图中进行return。以下是基本使用方法,但是使用它时我们需要对路由做一些改进,具体的情况等下面的源码分析后你就明白了:

urlpatterns = [
path('admin/', admin.site.urls),
path('api/users/', views.UserAPI.as_view(actions={"get":"list","post":"create"})),
re_path('^api/users/(?P<uid>\d+)?',views.UserAPI.as_view(actions={"get":"retrieve","patch":"update","delete":"destroy"}))
]

   那么在views.py中,书写的话很简单:

from . import models
from . import ser
from rest_framework.viewsets import ModelViewSet
class UserAPI(ModelViewSet):
queryset = models.User.objects # 传入对象即可
serializer_class = ser.UserModelSerializers # 序列化类
lookup_field = "pk"
lookup_url_kwarg = "uid" # 由于捕获的是uid,需要声明

继承关系

   ModelViewSet的导入如下:

from rest_framework.viewsets import ModelViewSet

   你可看它的源码,它其实也没什么特别之处,就是针对上面第一个问题做了改进。但是你会发现,它会继承一个新的类,即GenericViewSet这个类。

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

   下面是它的继承图:

  

   那么GenericViewSet中又会有什么新的发现呢?我们先看一看它。

GenericViewSet

   打开GenericViewSet中发现什么都没有。但是它继承了ViewSetMixin

class GenericViewSet(ViewSetMixin, generics.GenericAPIView):
"""
The GenericViewSet class does not provide any actions by default,
but does include the base set of generic view behavior, such as
the `get_object` and `get_queryset` methods.
"""
pass

ViewSetMixin

   我们可以在上面基本使用时对urlas_view()传参发现了一点不一样的地方,我们传递进了一个关键字参数actions,这个参数非常的蹊跷,因为在APIView中的as_view()方法中并没有为该参数预留位置。

def as_view(cls, **initkwargs):

   我们再接着看看GenericAPIView中的as_view()方法有没有为该参数预留位置,非常遗憾的是在GenericAPIView中根本就没有as_view()方法,说明它用了父类也就是APIViewas_view()方法

   那么只有一个可能,就是ViewSetMixin覆写了as_view()方法,那么到底是不是这么回事?我们看一下就知道了:

class ViewSetMixin:

    @classonlymethod
def as_view(cls, actions=None, **initkwargs):

   是的,那么它内部是怎么做的呢?实际上它的核心代码就是那个for循环,它会根据不同的请求方式来执行不同的mixins中五个扩展类的方法,因此我们需要两条url来放入不同的actions。由于modelsViewSet继承了mixins五个扩展类,所以才能够调用扩展类下的方法。

    @classonlymethod
def as_view(cls, actions=None, **initkwargs): # cls即为UserAPI这个类
cls.name = None
cls.description = None
cls.suffix = None
cls.detail = None
cls.basename = None
if not actions: # 必须传入actions,否则抛出异常
raise TypeError("The `actions` argument must be provided when "
"calling `.as_view()` on a ViewSet. For example "
"`.as_view({'get': 'list'})`")
for key in initkwargs: # 构造字典,不用管
if key in cls.http_method_names:
raise TypeError("You tried to pass in the %s method name as a "
"keyword argument to %s(). Don't do that."
% (key, cls.__name__))
if not hasattr(cls, key):
raise TypeError("%s() received an invalid keyword %r" % (
cls.__name__, key)) if 'name' in initkwargs and 'suffix' in initkwargs: # 不用管,这个也是构造字典
raise TypeError("%s() received both `name` and `suffix`, which are "
"mutually exclusive arguments." % (cls.__name__)) def view(request, *args, **kwargs): # 闭包函数view
self = cls(**initkwargs) if 'get' in actions and 'head' not in actions:
actions['head'] = actions['get'] self.action_map = actions for method, action in actions.items(): # 其实这里是核心代码, actions={"get":"retrieve","patch":"update","delete":"destroy"},或者等于{"get":"list","post":"create"}
handler = getattr(self, action) # 根据请求方式,来执行list、create、retrieve、update、destroy这几个方法
setattr(self, method, handler) self.request = request
self.args = args
self.kwargs = kwargs return self.dispatch(request, *args, **kwargs) update_wrapper(view, cls, updated=()) # 传入执行update_wrapper(),不用管 update_wrapper(view, cls.dispatch, assigned=()) # 不用管 view.cls = cls
view.initkwargs = initkwargs
view.actions = actions
return csrf_exempt(view)

   根据不同的请求方式来执行不同的函数方法,可以说这个设计非常的巧妙,所以你可以像下面这样做:

# views.py
from rest_framework.viewsets import ViewSetMixin
class Book6View(ViewSetMixin,APIView): # 一定要放在APIVIew前,因为as_view()的查找顺序一定要先是ViewSetMixin
def get_all_book(self,request):
print("xxxx")
book_list = Book.objects.all()
book_ser = BookSerializer(book_list, many=True)
return Response(book_ser.data) # urls.py
#继承ViewSetMixin的视图类,路由可以改写成这样
path('books6/', views.Book6View.as_view(actions={'get': 'get_all_book'})),

drf 视图使用及源码分析的更多相关文章

  1. DRF框架(一)——restful接口规范、基于规范下使用原生django接口查询和增加、原生Django CBV请求生命周期源码分析、drf请求生命周期源码分析、请求模块request、渲染模块render

    DRF框架    全称:django-rest framework 知识点 1.接口:什么是接口.restful接口规范 2.CBV生命周期源码 - 基于restful规范下的CBV接口 3.请求组件 ...

  2. 探索drf执行流程之APIView源码分析

    Django REST framework 简介 现在新一代web应用都开始采用前后端分离的方式来进行,淘汰了以前的服务器端渲染的方式.而实现前后端分离是通过Django REST framework ...

  3. drf 简介以及部分源码分析

    目录 复习 drf框架 全称:django-rest framework 知识点 接口 restful接口规范 基于restful规范的原生Django接口 主路由:url.py api组件的子路由: ...

  4. drf 认证校验及源码分析

    认证校验 认证校验是十分重要的,如用户如果不登陆就不能访问某些接口. 再比如用户不登陆就不能够对一个接口做哪些操作. drf中认证的写法流程如下: 1.写一个类,继承BaseAuthenticatio ...

  5. DRF中的APIView源码分析

    首先写一个简单的drf接口 from rest_framework.views import APIView from rest_framework.response import Response ...

  6. cvb源码分析,resful规范,drf,drf序列化组件,95

    1 CBV的源码分析 -Class Base View(基于类的视图) -Function Base View(基于函数的视图) -def as_view 类方法 -def view:类方法内部,闭包 ...

  7. DRF之视图类(mixin)源码解析

     同样的增删改查操作,如果我们还像之前序列化组件那样做,代码重复率过多,所以我们用视图表示: 具体源码实现:首先定义一个视图类,然后根据mixin点进去有五个封装好的方法,这五个方法共有的属性就是都需 ...

  8. drf源码分析系列---节流(访问频率限制)

    使用 from rest_framework.throttling import AnonRateThrottle from rest_framework.generics import ListAP ...

  9. Django框架深入了解_01(Django请求生命周期、开发模式、cbv源码分析、restful规范、跨域、drf的安装及源码初识)

    一.Django请求生命周期: 前端发出请求到后端,通过Django处理.响应返回给前端相关结果的过程 先进入实现了wsgi协议的web服务器--->进入django中间件--->路由f分 ...

随机推荐

  1. pwnable.kr-lotto-witeup

    执行分析题目代码,发现是输入值和十进制是1到45的ASCII码系统生成对应字母做比较:而比较方法是遍历输入值的所有位和系统生成字符串的每个位作比较,相同计数为6则爆出flag.漏洞啊,只要押中有一字母 ...

  2. 纹理_贴图_texture

    详细代码可以在我的GitHub上找文末指定的项目.

  3. logging模块培训小结

    Python自动化课程又上了一节课,每一个自动化框架都涉及到日志的使用,logging模块是Python的一个标准库模块,由标准库模块提供日志记录API的关键好处是所有Python模块都可以使用这个日 ...

  4. 中心极限定理(为什么y服从高斯分布)

    因为每一条数据都服从IID原则: 根据中心极限定理,当数据增加的时候,样本均值的分布慢慢变成正态分布 不管分布式什么分布,累加起来都是高斯分布 As sum increases, sum of non ...

  5. 079 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 04 实例化对象

    079 01 Android 零基础入门 02 Java面向对象 01 Java面向对象基础 01 初识面向对象 04 实例化对象 本文知识点:实例化对象 说明:因为时间紧张,本人写博客过程中只是对知 ...

  6. centos配置WordPress(Apache+mysql+php)

    .安装Apache 安装命令:sudo yum install httpd 启动服务:sudo service httpd start 在浏览器输入IP地址,正常应该显示Apache的欢迎页面 如果提 ...

  7. I2C 方式

    转自:http://www.cnblogs.com/lucky-apple/archive/2008/07/03/1234581.html 区别: SPI:高速同步串行口.3-4线接口,收发独立.可同 ...

  8. 什么是ICD文件

    ICD就是IED Capability Description的简称,中文为IED能力描述文件.其中 IED是Intelligent Electronic Device的简称,是智能电子设备 智能电子 ...

  9. 为Android(和其他移动平台)安装MoSync

    为Android(和其他移动平台)安装MoSync Android教程比赛 这是我提交的文章#2:设置你的Android开发环境.它的主要区别在于它描述了如何安装MoSync,这是一种开发环境,它不是 ...

  10. CentOS7 执行 service iptables save 报错 The service command supports only basic LSB actions xxxxxx

    现象描述 在 CentOS 7.6.1810 下执行 service iptables save 命令,出现如下错误: [root@test ~]# service iptables save The ...