REST

介绍

RESTful API 设计

实现API的两种方式

FBV 视图函数

urlpatterns = [url(r'^user/$', views.user),url(r'^user/add/$', views.user_add),url(r'^user/edit/(\d+)/$', views.user_edit),url(r'^user/del/(\d+)/$', views.user_del),
]

传统的视图函数方式,API接口太多,难以维护。

CBV 视图类

urlpatterns = [url(r'user/$', views.UserView.as_view()),  # GET, POSTurl(r'user/(\d+)$', views.UserView.as_view()),  # PUT, DELETE
]

根据请求方式的不同,执行视图类中对应的方法。同样是实现增删改查,url少一半。这也是面向资源编程的方式,特点是url中都是名词。

CBV相关知识参考:http://blog.csdn.net/ayhan_huang/article/details/78036501#t11

协议

大神说:API与用户的通信协议,总是使用HTTPs协议

域名

  • http://api.example.com 尽量使用专用的二级域名
  • http://www.example.com/api/ 路由分发。如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下。

对应前后端分离的项目,可以这样分配:

前端VUE项目使用域名:http://www.example.com

后端API使用域名:http://api.example.com

版本

应该将API的版本号放入URL。

比如:https://www.example.com/api/v1/ v1是版本信息

路径

路径又称”终点”(endpoint),表示API的具体网址。

在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,而且所用的名词往往与数据库的表格名对应。一般来说,数据库中的表都是同种记录的”集合”(collection),所以API中的名词也应该使用复数。

举例来说,有一个API提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。

  • https://api.example.com/v1/zoos
  • https://api.example.com/v1/animals
  • https://api.example.com/v1/employees

method

  • GET :从服务器取出资源(一项或多项)
  • POST :在服务器新建一个资源
  • PUT :在服务器更新资源(客户端提供改变后的完整资源,全部更新)
  • PATCH :在服务器更新资源(客户端提供改变的属性,局部更新)
  • DELETE :从服务器删除资源
  • HEAD:和GET一样,只是只返回响应首部,不返回响应体,用于确认资源的信息
  • OPTIONS:查询支持的方法,复杂请求的预检会用到。

比如:

  • GET /zoos:列出所有动物园
  • POST /zoos:新建一个动物园
  • GET /zoos/ID:获取某个指定动物园的信息
  • PUT /zoos/ID:更新某个指定动物园的信息(提供该动物园的全部信息)
  • PATCH /zoos/ID:更新某个指定动物园的信息(提供该动物园的部分信息)
  • DELETE /zoos/ID:删除某个动物园
  • GET /zoos/ID/animals:列出某个指定动物园的所有动物
  • DELETE /zoos/ID/animals/ID:删除某个指定动物园的指定动物

状态码

HTTP状态码负责表示客户端HTTP请求的返回结果,标记服务端的处理是否正常,通知出现的错误等工作。

状态码类别

状态码类别原因短语
1XXInformational 信息性状态码接收的请求正在处理
2XXSuccess 成功状态码请求正常处理完毕
3XXRedirection 重定向状态码需要进行附加操作以完成请求
4XXClient Error 客户端错误状态码服务器无法处理请求
5XXServer Error 服务器错误状态码服务器处理请求出错

常用状态码一览

  • 200 OK:客户端发来的请求在服务端被正常处理了。
  • 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。
  • 202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)
  • 204 NO CONTENT :请求已成功处理,但响应中不包含响应体。比如 请求方式为[DELETE]时,表示用户删除数据成功。
  • 206 Partial Content: 服务器成功执行了客户端的范围请求。响应中包含由Content-Range首部字段指定范围的实体内容
  • 301 Moved Permanently: 永久性重定向,请求的资源已被分配了新的URI。应该按Location首部字段提示的URI访问。
  • 302 Found, 303 See Other, 307 Temporary Redirect 都是临时性重定向,请求的资源已被临时分配了新的URI,希望用户本次使用新的URI访问。标准不太统一,每种浏览器可能出现不同的情况,了解即可。
  • 304 Not Modified: 这个比较特殊,和重定向没有关系,表示服务器资源未改变,可直接使用客户端缓存。
  • 400 Bad Request:用户发出的请求报文中存在语法错误,需要修改请求内容后再发送。
  • 401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
  • 403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
  • 404 NOT FOUND - [*]:服务器无法找到请求的资源。或者在服务器拒绝请求且不想说明理由时使用
  • 406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。
  • 410 Gone - [GET]:用户请求的资源被永久删除,且不会再得到的。
  • 422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。
  • 500 INTERNAL SERVER ERROR - [*]:服务器发生错误,可能外web应用存在bug。
  • 503 Service Unavailable: 服务器正忙

状态码有限,可以再约定code,表示更细的状态:

def get(self, request, *args, **kwargs):res = {'code': 1001, 'error': None}try:print('do something...')except Exception as e:res['error'] = str(e)return JsonResponse(res, status=500)

错误处理

如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。

{error: "Invalid API key"
}

提供error key,显示详细错误信息

过滤

如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果

  • ?limit=1:指定返回记录的数量
  • ?offset=10:指定返回记录的开始位置
  • ?page=2$per_page=10:指定第几页,以及每页的记录数
  • ?sortby=name$order=asc:指定返回结果按照哪个属性排序,以及排序顺序
  • ?id=10:指定筛选条件

返回结果

针对不同操作,服务器向用户返回的结果应该符合以下规范。

  • GET /collection:返回资源对象的列表(数组)
  • GET /collection/resource:返回单个资源对象
  • POST /collection:返回新生成的资源对象
  • PUT /collection/resource:返回完整的资源对象
  • PATCH /collection/resource:返回完整的资源对象
  • DELETE /collection/resource:返回一个空文档

Hypermedia

RESTful API最好做到Hypermedia,即返回结果中提供链接,连向其他API方法,使得用户不查文档,也知道下一步应该做什么。

比如,当用户向api.example.com的根目录发出请求,会得到这样一个文档。

{"link": {"rel":   "collection https://www.example.com/zoos","href":  "https://api.example.com/zoos","title": "List of zoos","type":  "application/vnd.yourformat+json"
}}

上面代码表示,文档中有一个link属性,用户读取这个属性就知道下一步该调用什么API了。rel表示这个API与当前网址的关系(collection关系,并给出该collection的网址),href表示API的路径,title表示API的标题,type表示返回类型。
Hypermedia API的设计被称为HATEOAS。Github的API就是这种设计,访问api.github.com会得到一个所有可用API的网址列表:

{"current_user_url": "https://api.github.com/user","authorizations_url": "https://api.github.com/authorizations",# ...
}

从上面可以看到,如果想获取当前用户的信息,应该去访问api.github.com/user,然后就得到了下面结果:

{"message": "Requires authentication","documentation_url": "https://developer.github.com/v3"
}

上面代码表示,服务器给出了提示信息,以及文档的网址。

Django REST framework

通过Django本身也可以实现API设计,只是相对要麻烦些。Django REST framework基于Django进行了丰富,能更方便的实现API设计。

基本使用

settings

INSTALLED_APPS = [# ...'rest_framework',
]

路由

urlpatterns = [url(r'user/$', views.UserView.as_view()),  # GET, POSTurl(r'user/(?P<pk>\d+)/$', views.UserView.as_view()),  # PUT, DELETE
]

视图

from rest_framework.views import APIView
from django.http import JsonResponseclass UsersView(APIView):def dispatch(self, request, *args, **kwargs):"""请求到来之后,首先执行dispatch方法,dispatch方法根据请求方式的不同,反射执行 get/post/put等方法"""return super().dispatch(request, *args, **kwargs)def get(self, request, *args, **kwargs):res = {'code': '10001','data': [],  # 字典元素'error': None}# return HttpResponse(json.dumps(res), status=200, content_type='application/json')# 如果是HttpResponse,需要手动json, 并且指定content_typereturn JsonResponse(res, status=200)def post(self, request, *args, **kwargs):passdef put(self, request, *args, **kwargs):pk = kwargs.get('pk')  # 获取url命名分组传参passdef delete(self, request, *args, **kwargs):pass

生命周期

  • 中间件

  • 路由系统

    • .as_view() 方法:return csrf_exempt(view)
  • CBV视图类

    • 执行dispatch方法

      • 二次封装request

        def initialize_request(self, request, *args, **kwargs):parser_context = self.get_parser_context(request) # return Request(request,parsers=self.get_parsers(),  # 解析器authenticators=self.get_authenticators(),  # 认证negotiator=self.get_content_negotiator(),  # 选择器parser_context=parser_context  # 字典:view和参数)
      • try:

        • 获取版本,认证,权限,节流

          def initial(self, request, *args, **kwargs):"""Runs anything that needs to occur prior to calling the method handler."""self.format_kwarg = self.get_format_suffix(**kwargs)# Perform content negotiation and store the accepted info on the request# 根据用户请求选择neg = self.perform_content_negotiation(request)request.accepted_renderer, request.accepted_media_type = neg# Determine the API version, if versioning is in use.# 获取版本信息,和处理版本的类的对象version, scheme = self.determine_version(request, *args, **kwargs)request.version, request.versioning_scheme = version, scheme# Ensure that the incoming request is permitted# 认证self.perform_authentication(request)# 权限self.check_permissions(request)# 控制访问次数(每天访问10次)self.check_throttles(request)
        • 根据请求方法反射执行 GET/POST/DELETE…

      • except:

        • 处理异常
      • 返回响应

版本

查看源码可知,Django REST framework一共支持5种版本控制方式:

  • AcceptHeaderVersioning
  • URLPathVersioning
  • NamespaceVersioning
  • HostNameVersioning
  • QueryParameterVersioning

导入及使用方式:

from rest_framework.versioning import URLPathVersioningclass TestView(APIView):versioning_class = URLPathVersioning  # 指定版本pass

版本控制中通用的settings全局配置:

REST_FRAMEWORK = {# 'DEFAULT_VERSION': 'v1', # 默认版本# 'ALLOWED_VERSIONS': ['v1', 'v2'], # 允许的版本
}

下面介绍其中两种比较常用获取版本的方式。

基于查询字符串传参

settings配置
REST_FRAMEWORK = {'VERSION_PARM': 'version' # 配置从URL中获取值的key
}
urls配置
urlpatterns = [url(r'test', views.TestView.as_view(), name='test')
]
CBV
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.versioning import QueryParameterVersioningclass TestView(APIView):versioning_class = QueryParameterVersioning  # 指定版本def get(self, request, *args, **kwargs):# 获取版本print(request.version)# 获取版本管理的类print(request.versioning_scheme)# 反向生成urlreverse_url = request.versioning_scheme.reverse('test', request=request)print(reverse_url)return Response('get xxxxxx')"""
浏览器访问:http://127.0.0.1:8866/test/?version=v1
打印结果:
v1
<rest_framework.versioning.QueryParameterVersioning object at 0x0000024779F1A278>
http://127.0.0.1:8866/test?version=v1
"""

url分组传参

urls
urlpatterns = [url(r'test/(?P<version>[v1|v2]+)/$', views.TestView.as_view(), name='test')
]
# 传参必须是 v1 或 v2 
CBV

更换 versioning_class 为 URLPathVersioning 即可

from rest_framework.versioning import URLPathVersioningclass TestView(APIView):versioning_class = URLPathVersioning  # 指定版本def get(self, request, *args, **kwargs):pass"""
浏览器访问:http://127.0.0.1:8866/test/v2/
"""

认证

REST framework自带了认证方式:

  • BasicAuthentication # 基本认证
  • SessionAuthentication # 基于django request 对象的用户session
  • TokenAuthentication # 基于rest自带的Token model,
  • RemoteUserAuthentication # 基于request 请求头中的用户信息

以及它们的基类 BaseAuthentication,通过派生BaseAuthentication 并实现其中的方法,我们可以自定义认证类,下面我们先简单体会一下

from rest_framework.views import APIView
from django.http import JsonResponse
from rest_framework.response import Response
# 认证相关
from rest_framework.authentication import BaseAuthentication
from rest_framework import exceptionsTOKEN_LIST = [  #定义token,稍后我们会随机生成它'hello','world'
]class CustomAuthentication(BaseAuthentication):"""自定义认证类"""def authenticate(self, request):  # 接口约束token = request._request.GET.get('tk')if token in TOKEN_LIST:return ('lena', None)# return None  # 支持匿名用户raise exceptions.AuthenticationFailed('认证失败')  # 不允许匿名用户,交给dispatch中的异常处理class TestView(APIView):versioning_class = URLPathVersioning  # 指定版本authentication_classes = [CustomAuthentication,]  # 指定认证方式;def dispatch(self, request, *args, **kwargs):return super().dispatch(request, *args, **kwargs)def get(self, request, *args, **kwargs):passreturn Response('get xxxxxx')# http://127.0.0.1:8866/test/v2/?tk=hello   --> 认证成功
# http://127.0.0.1:8866/test/v2/?tk=hello888 --> 认证失败 HTTP 403 Forbidden (dispatch中异常处理返回值)

正经使用

和model关联起来,根据用户名实时生成token,用户登录成功后,拿着token访问需要认证的api

创建model表并迁移
from django.db import modelsclass UserInfo(models.Model):username = models.CharField(max_length=32)password = models.CharField(max_length=64)email = models.EmailField()user_type_choices = [(1, '普通用户'),(2, '版主'),(3, '管理员'),]user_type = models.IntegerField(choices=user_type_choices, default=1)class Token(models.Model):user = models.OneToOneField(to=UserInfo)  # 一对一关系token = models.CharField(max_length=64)

说明:

  1. 两张表一对一的关系只能在一个设备上登录。新设备上登录后,服务端生成新的token 会覆盖旧的token值(具体看下面AuthView中的逻辑),导致原先使用旧token的设备无法访问api,除非重新登录,获取新的token。如果设置成一对多关系,那么可以支持多设备登录。(当然O2O 的情况下,在设备间拷贝token过去也可以实现)
新增登陆路由和登录认证
urls
urlpatterns = [url(r'api/(?P<version>[v1|v2]+)/auth/$', views.AuthView.as_view(), name='auth')  # 登录认证 
]
CBV登录认证
def generate_token(username):"""根据用户名和时间,进行MD5值"""import timeimport hashlibmd5 = hashlib.md5(username.encode('utf-8'))md5.update(str(time.time()).encode('utf-8'))return md5.hexdigest()class AuthView(APIView):def post(self, request, *args, **kwargs):res = {'code': 1000,  # code: 1000 登录成功;1001登录失败'msg': None,   # 错误信息'token': None}username = request._request.POST.get('username')  pwd = request._request.POST.get('pwd')print('usernaem:',username)print('pwd:',pwd)user_obj = models.UserInfo.objects.filter(username=username, password=pwd).first()if user_obj:# 如果用户存在,那么生成token并更新token = generate_token(username)models.Token.objects.update_or_create(user=user_obj, defaults={'token': token})res['token'] = tokenelse:res['code'] = 1001res['msg'] = '用户名或密码错误'return JsonResponse(res)

说明:

  • 因为是作为api, 只需要post方法即可,登录页面由前端处理
  • request在dispatch中经过了二测封装,通过request._request获取原来的request对象
  • 封装后的request提供了query_params属性访问request._request.GET,data属性访问request._request.POST
  • 更新/创建 token:update_or_create,user=user_obj是筛选条件,存在则用default更新(比如用户换了登录设备),不存在则创建;
自定义认证类并给api使用
class CustomAuthentication(BaseAuthentication):def authenticate(self, request):token = request.query_params.get('tk')token_obj = models.Token.objects.filter(token=token).first()if token_obj:# 返回(用户对象,token对象)return (token_obj.user, token_obj)# return None  # 支持匿名用户raise exceptions.AuthenticationFailed('认证失败')  # 不允许匿名用户,交给dispatch中的异常处理class TestView(APIView):versioning_class = URLPathVersioning  # 指定版本authentication_classes = [CustomAuthentication, ]  # 指定认证方式def dispatch(self, request, *args, **kwargs):return super().dispatch(request, *args, **kwargs)def get(self, request, *args, **kwargs):# 认证Ok, 打印用户信息print(request.user.username)print(request.user.email)return Response('get xxxxxx')""" 认证成功,打印出用户信息
lena
lena@live.com
"""

说明:TestView中之所以能request.user.username,是因为认证类对象执行authenticate方法返回的元组,被赋值给了Request对象:self.user, self.auth = user_auth_tuple

通过requests模块模拟登录提交
import requestsapi = 'http://127.0.0.1:8866/api/v1/auth/'
response = requests.post(url=api, data={'username': 'lena', 'pwd': '123'})
print(response.text)
"""
{"code": 1000, "msg": null, "token": "117d16c0b1c9397a0573c28b67dad6f8"}
"""

访问api,认证成功,收到服务端返回的信息,其中包括token,以后只需要携带token就可以访问需要认证的api

requests模拟访问api

用之前登录成功返回的token访问目标api

api = 'http://127.0.0.1:8866/api/v1/test/'
response2 = requests.get(url=api, params={'tk': '117d16c0b1c9397a0573c28b67dad6f8'})
print(response2.text)"""
get xxxxxx
"""

认证的几种配置方式

局部配置

在CBV类中通过authentication_classes = [CustomAuthentication, ]指定,比如上面例子中的做法。

多继承 – 推荐

并不是所有的api都需要作认证,比如登录。因此可以通过写一个基类(指定认证类),让需要认证的api首先继承这个基类即可:

# 基类
class Token_auth(APIView):authentication_classes = [CustomAuthentication, ]  # 指定认证方式# 需要认证的api 首先继承基类
class TestView(Token_auth, APIView):versioning_class = URLPathVersioning  # 指定版本pass# 不需要认证的api, 不继承基类
class AuthView(APIView):pass
settings全局

在settings中作全局配置,不需要认证的api指定authentication_classes = []为空即可

REST_FRAMEWORK = {'DEFAULT_AUTHENTICATION_CLASSES': [# 自定义认证类路径'utils.authentication.CustomAuthentication',  ]
}
class AuthView(APIView):authentication_classes = []

认证功能源码剖析

遵循之前的生命周期分析,进入CBV视图后,流程如下:

  • dispatch(self, request, *args, **kwargs)

  • request = self.initialize_request(request, *args, **kwargs) 二次封装request

    return Request(# ...authenticators=self.get_authenticators(), # 为request对象封装认证类
    )
  • def get_authenticators(self):

    return [auth() for auth in self.authentication_classes]  # 循环认证类列表,并实例化对象
  • self.initial(request, *args, **kwargs) 初始化

  • self.perform_authentication(request) 执行认证

  • request.user 调用request对象user方法(@property装饰)(登录之后存在request.user 同django默认设计)

  • self._authenticate()

    def _authenticate(self):"""执行每个认证对象的认证方法:一旦异常raise 全部终止,交由dispatch中的异常处理如果返回元组,赋值给request.user, request.auth, 并return 后续不再执行如果既没有异常,又没有返回,执行_not_authenticated() 匿名用户"""for authenticator in self.authenticators:try:user_auth_tuple = authenticator.authenticate(self)except exceptions.APIException:self._not_authenticated()raiseif user_auth_tuple is not None:self._authenticator = authenticatorself.user, self.auth = user_auth_tuplereturnself._not_authenticated()
  • 执行自带认证类或自定义认证类中authenticate方法

    class CustomAuthentication(BaseAuthentication):def authenticate(self, request):token = request.query_params.get('tk')token_obj = models.Token.objects.filter(token=token).first()if token_obj:# 返回(用户对象,token对象)return (token_obj.user, token_obj)# return None  # 支持匿名用户,将执行 self._not_authenticated()raise exceptions.AuthenticationFailed('认证失败')  # 不允许匿名用户,交给dispatch中的异常处理
  • 匿名用户

    def _not_authenticated(self):"""为未认证的请求设置authenticator, user & authtoken默认值分别是 None, AnonymousUser & None,后两个可以在settings中配置"""self._authenticator = Noneif api_settings.UNAUTHENTICATED_USER: # 默认配置中会使用django内置的AnonymousUser类self.user = api_settings.UNAUTHENTICATED_USER()else:self.user = Noneif api_settings.UNAUTHENTICATED_TOKEN:self.auth = api_settings.UNAUTHENTICATED_TOKEN()else:self.auth = None# 匿名用户settings相关配置REST_FRAMEWORK = {'UNAUTHENTICATED_USER': None, # 取消匿名用户'UNAUTHENTICATED_TOKEN': None,
    }

    如果认证类的authenticate方法执行了returen None,导致user_auth_tuple为空,进而执行self._not_authenticated()方法时,将默认产生一个匿名用户。那么request.user非空,而是一个匿名用户对象。如果希望取消对匿名用户的支持,就需要在settings中指定'UNAUTHENTICATED_USER': None,来覆盖默认的匿名用户配置。

权限

分析了上面的认证后,权限的流程是一摸一样的,下面我们看一下具体用法

自定义权限类

class CustomPermission(BasePermission):message = '无权限'  # 查看源码可知,可以通过message自定义提示信息def has_permission(self, request, view):"""返回True: 有权限;返回False: 无权限"""method = request._request.methodif request.user.user_type == 1 and isinstance(view, TestView) and method == 'POST':  # 限制普通用户通过post方式访问TestViewreturn Falsereturn True

应用

# 方式一:局部视图
class TestView(Token_auth, APIView):versioning_class = URLPathVersioning  # 指定版本permission_classes = [CustomPermission, ]  # 指定权限def dispatch(self, request, *args, **kwargs):return super().dispatch(request, *args, **kwargs)def get(self, request, *args, **kwargs):print(request.user.username)print(request.user.email)return Response('get xxxxxx')def post(self, request, *args, **kwargs):return Response('post xxx')# 方式二:settings全局
REST_FRAMEWORK = {'DEFAULT_PERMISSION_CLASSES': [# path.to.Permissionclass  路径之间用点分割]
}

这样当我们通过get方法访问TestView时,将得到正常响应,如果是post,那么将得到{"detail":"无权限"}的响应。

请求次数限制

配置

REST_FRAMEWORK = {"DEFAULT_THROTTLE_RATES": {'anon': '5/m'# scope: rate 匿名用户: 每分钟5次'user': '10/m'  # 登录用户}}

自定义访问控制类

from rest_framework.throttling import SimpleRateThrottle# 根据request.user 判断匿名不匿名 (在每次进来时认证中赋值了用户或None)class Custom_anno_control(SimpleRateThrottle):"""匿名用户控制,用默认get_ident,获取ip作为标识"""scope = 'anon' # 决定settings中DEFAULT_THROTTLE_RATES 的keydef allow_request(self, request, view):if request.user:  # 如果是登录用户,不限制return Trueself.key = self.get_cache_key(request, view)print('key=====',self.key)self.history = self.cache.get(self.key, [])self.now = self.timer()while self.history and self.history[-1] <= self.now - self.duration:self.history.pop()if len(self.history) >= self.num_requests:return self.throttle_failure()return self.throttle_success()def get_cache_key(self, request, view):return self.cache_format % {'scope': self.scope,'ident': self.get_ident(request)}class Custom_user_control(SimpleRateThrottle):"""登录用户控制,直接用用户名+CBV视图类名作为标识"""scope = 'user'def allow_request(self, request, view):if not request.user:  # 如果是匿名用户,不限制return Trueself.key = request.user.username + view.__class__.__name__  # 如果登录用户,用用户名和类名作为标识if self.key is None:return Trueself.history = self.cache.get(self.key, [])self.now = self.timer()while self.history and self.history[-1] <= self.now - self.duration:self.history.pop()if len(self.history) >= self.num_requests:return self.throttle_failure()return self.throttle_success()

这里参考源码稍作修改:

  • Custom_anno_control中不限制登录用户,Custom_user_control不限制匿名用户,保证CBV在同时应用二者时,不用关心调用顺序。
  • Custom_user_control中的key采用用户名拼接CBV视图类名,确保访问次数限制能精确到具体CBV视图类,而不是所有CBV一共能访问多少次。

自定义权限

class CustomPermission(BasePermission):message = '无权限'  # 查看源码可知,可以通过message自定义提示信息def has_permission(self, request, view):"""返回True: 有权限;返回False: 无权限"""if not request.user:  # 仅允许登录用户,限制匿名用户return Falsereturn True

CBV中应用

class IndexView(APIView):"""控制登录用户访问频次:10/m, 匿名用户访问频次5/m"""authentication_classes = [CustomAuthentication, ]  # 获取登录token和用户;如果不认证限制,那么无法自动获取token,都是匿名访问throttle_classes = [Custom_anno_control, Custom_user_control]# 要同时允许登录用户和匿名用户的访问并作限制,必须同时指定authentication_classes认证类和throttle_classes访问控制类:# 如果用户登录,那么拿着token,可以访问配置中指定的次数 'user': '10/m'# 如果用户未登录,那么没有token或者token错误,可以访问配置中指定的数  'anon': '5/m',def get(self, request, *args, **kwargs):return HttpResponse('欢迎访问首页')class ShoppingView(APIView):versioning_class = URLPathVersioning  # 指定版本authentication_classes = [CustomAuthentication, ]  # 认证(认证可能同时允许登录用户和匿名用户)permission_classes = [CustomPermission, ]  # 指定权限,这里作二次限制,即便匿名用户通过了认证,也过不了权限throttle_classes = [Custom_user_control, ]  # 限制登录用户的访问次数def dispatch(self, request, *args, **kwargs):return super().dispatch(request, *args, **kwargs)def get(self, request, *args, **kwargs):print(request.user.username)print(request.user.email)return HttpResponse('购物车 访问')def post(self, request, *args, **kwargs):return HttpResponse('购物车 提交')

解析器

根据请求头中的content-type,对内容进行解析。在执行request.data时触发。

'DEFAULT_PARSER_CLASSES': ('rest_framework.parsers.JSONParser',  # content-type: application/json'rest_framework.parsers.FormParser',  # content-type: application/x-www-form-urlencoded'rest_framework.parsers.MultiPartParser'  # content-type: multipart/form-data(可以在form中同时上传数据和文件)
),

默认同时支持以上三种解析器(源码中通过for 循环一一匹配请求头的content-type),还有一个FileUploadParser (只能上传文件,鸡肋)。如果想配置的话可以在CBV中指定parser_classes=[],或者在配置中配置,没啥必要,默认都配置上了,除非你闲的蛋疼。。。

序列化

对于序列化,有两种方案,一种是将查询结果通过.value_list('field1', 'field2','xxx')这种方式,返回QuerySet包字典的格式,然后转化为列表:

queryset = models.UserInfo.objects.all().values_list('id', 'name')
res = list(queryset)

这种方式有个弊端,无法处理choice,M2M字段的情况。

第二种方案就是这里的序列化类,REST中内置了三种:

  • Serializer
  • ModelSerializer
  • HyperlinkedModelSerializer
作用
  • 对数据库查询结果进行序列化,返回json数据
  • 验证用户提交,类似Django中的Form / ModelForm

序列化:

from rest_framework.response import Response  # 对于序列化的结果需要用Response对象才能正确返回结果
from rest_framework import serializers
from rest_framework.serializers import Serializer
from rest_framework.serializers import ModelSerializer
from rest_framework.serializers import HyperlinkedModelSerializer# 派生Serializer类
# 两种方式:Serializer 和 ModelSerializer (相当与Django中的Form和ModelForm)
class UserSerializer(Serializer):username = serializers.CharField()password = serializers.CharField()email = serializers.EmailField()user_type = serializers.IntegerField()group = serializers.CharField(source="group.title", required=False)  # 通过source指定FK对象的显示class UserModelSerializer(ModelSerializer):class Meta:model = models.UserInfofields = '__all__'depth = 2# depth = 0 或 1, 只显示FK的PK, 如果=2,可以显示FK对象的字段,比如下面的group外键;# [{"id":1,"username":"Lena",..."group":{"id":1,"title":"A组"}},# 如果外键嵌套很多,depth深度过深可能会影响性能。。# 返回json数据
class SerializerView(APIView):def get(self, request, *args, **kwargs):user_list = models.UserInfo.objects.all()user_obj = models.UserInfo.objects.all().first()ser = UserSerializer(instance=user_list, many=True)  # 返回queryset序列化对象时,many=True# ser = UserModelSerializer(instance=user_list, many=True)# ser = UserSerializer(instance=user_obj, many=False)  # 返回单个序列化对象时,many=Falsereturn Response(ser.data)

定制序列化结果

对于choice字段,外键或者多对多等跨表字段,需要自定制

from rest_framework import serializers# 假设CourseDetail 和 Course 表是一对一关系
class CourseDetailSerializer(serializers.ModelSerializer):"""课程详情"""course_name = serializers.CharField(source='course.name')  # O2O跨表recommend_courses = serializers.SerializerMethodField()  # 写一个函数 def get_field(self, obj),返回的结果就是该字段的结果price_policy = serializers.SerializerMethodField()class Meta:model = models.CourseDetailfields = ['id', 'course_name', 'recommend_courses']def get_recommend_courses(self, obj): # obj指当前表CourseDetail中的一条记录"""获取M2M字段的结果"""ret = []recommend_courses_list = obj.recommend_courses.all()for item in recommend_courses_list:ret.append({'id': item.id, 'name': item.name})return retdef get_price_policy(self, obj):"""获取choice字段的结果"""ret = []price_policy = obj.course.price_policy.all()for item in price_policy:ret.append({'valid_period': item.get_valid_period_display(), 'price': item.price})return ret# 也可以继承派生字段类型,只需要重写get_attribute 和 to_representation 方法即可
class MtoMField(serializers.CharField):def get_attribute(self, instance):return instance.objects.values('name','title')def to_representation(self,value):return list(value)class MyField(serializers.CharField):def get_attribute(self, instance):#instance 是数据库对应的每行数据,即model 实例对象data_list = instance.recommend_courses.all()return data_listdef to_representation(self, value):ret = []for row in value:ret.append({'id': row.id, 'name': row.name})return ret
hypermedia相关

如果希望序列化的结果包括相关的链接关系,那么需要在序列化对象时提供当前的request,这里只需要提供一个上下文参数context即可实现。注意路由需要传id

urlpatterns = [url(r'test/(?P<pk>\d+)/', views.TestView.as_view(), name='test'),
]
class ModelUserSerializer(serializers.ModelSerializer):ut = serializers.HyperlinkedIdentityField(view_name='test')class Meta:model = models.UserInfofields = "__all__"class TestView(APIView):def get(self, request, *args, **kwargs):data_list = models.UserInfo.objects.all()ser = ModelUserSerializer(instance=data_list, many=True, context={'request': request})return Response(ser.data)
from rest_framework import serializers
from rest_framework.response import Responseclass UserSerialize(serializers.HyperlinkedModelSerializer):class Meta:model = models.UserInfofields = ['user','pwd','id','url']class SerializeView(APIView):def get(self, request, *args, **kwargs):user_list = models.UserInfo.objects.all()ser = UserSerialize(instance=user_list,many=True,context={'request': request})return Response(ser.data)def post(self, request, *args, **kwargs):ser = UserSerialize(data=request.data)if ser.is_valid():print(ser.validated_data)print(request.data)return Response(ser.validated_data)else:return Response(ser.errors)

验证用户提交

# 自定义验证类, 在__call__中写验证逻辑
class PasswordValidator:def __init__(self):passdef __call__(self, value):if value != '123':raise serializers.ValidationError('密码必须是123')def set_context(self, serializer_field):pass# 派生Serializer,同样有两种方式
# 两种方式:Serializer 和 ModelSerializer (相当与Django中的Form和ModelForm)
class UserSerializer(Serializer):username = serializers.CharField(min_length=6)password = serializers.CharField(validators=[PasswordValidator(),])email = serializers.EmailField()user_type = serializers.IntegerField()group = serializers.CharField(source="group.title", required=False)  # 通过source指定FK对象的显示class UserModelSerializer(ModelSerializer):# username = serializers.CharField(min_length=6) 相当于下面的extra_kwargs# password = serializers.CharField(validators=[PasswordValidator(), ])class Meta:model = models.UserInfofields = '__all__'extra_kwargs = {'username': {'min_length': 6},'password': {'validators: [PasswordValidator(),]'}}class SerializerView(APIView):def post(self, request, *args, **kwargs):ser = UserSerializer(data=request.data)if ser.is_valid():print(ser.validated_data)else:print(ser.errors)return Response('got post .....') 

post提交数据

api = 'http://127.0.0.1:8899/api/v1/ser/'
response = requests.post(url=api, data={'username':'sebastian', 'password':123, 'email':'asb'})

通过requests模块模拟post提交,CBV中打印结果如下:

{'email': ['Enter a valid email address.'], 'user_type': ['This field is required.']}

分页器

PageNumberPagination 页码分页

分页器类不能直接使用,需要继承它并指定参数

urlpatterns = [  url(r'api/page/$', views.PageTestView.as_view())]
from rest_framework.pagination import PageNumberPagination
from rest_framework import serializersclass CustomPagination(PageNumberPagination):# http://api.example.org/accounts/?page=4&page_size=100# 指定客户端query_param参数:每页数据大小 和 页码page_size_query_param = 'page_size'page_query_param = 'page'# 定制每页显示多少条数据(默认为None, 最终取决于请求中的查询参数) 以及最大值page_size = 10max_page_size = 20class UserSerializer(serializers.ModelSerializer):class Meta:model = models.UserInfofields = "__all__"class PageTestView(APIView):def get(self, request, *args, **kwargs):user_list = models.UserInfo.objects.all()# 实例化分页对象,并根据请求参数,获取分页数据paginator = CustomPagination()page_user_list = paginator.paginate_queryset(user_list, request, view=self)# 序列化分页数据ser = UserSerializer(instance=page_user_list, many=True)# 获取分页响应(可额外生成上一页/下一页链接)response = paginator.get_paginated_response(ser.data)return response

可能会报如下警告,对于无序的数据,分页器生成的分页数据可能不一致:

UnorderedObjectListWarning: Pagination may yield inconsistent results with an unordered object_list

因此在获取数据库数据时,可以做一下排序,这样就不会报警告了:

user_list = models.UserInfo.objects.all().order_by('id')

通过requests模块对该api发起请求,将得到如下结果:

{"count":2,"next":"http://127.0.0.1:8899/api/page/?page=2&page_size=1","previous":null,"results":[{"id":1,"username":"Lena","password":"123","email":"lena@live.com","user_type":1,"group":1}]}

LimitOffsetPagination 位置分页

class CustomPagination(LimitOffsetPagination):# http://api.example.org/accounts/?offset=400&limit=100limit_query_param = 'limit'offset_query_param = 'offset'max_limit = Nonedefault_limit = 10

CursorPagination 游标分页

对于以上两种分页方式,都存在性能问题,页码往后翻的越多,速度越慢。即便是offset,每次也要从头扫描,因此如果每次都能从上一次索引位置继续的话,就可以解决性能下降的问题。看下面的几种情况:

select * from tb where dept = 'it'
select * from tb where dept = 'it' limit 1   # 性能高

加入limit,找到1条就不找了,否则找完整个表,速度自然慢。

select * from tb offset 0 limit 5
select * from tb offset 100 limit 5
select * from tb offset 1000 limit 5
...
select * from tb where id>1000 offset 0 limit 5   # 性能高

通过id筛选,跳过前面的,这样就不用从头扫描。这就是cursor游标分页的原理。cursor分页每一次从上一次索引位置继续,因此只能上一页,下一页,不能直接跳转页码。

class CustomPagination(CursorPagination):# URL传入的游标参数cursor_query_param = 'cursor'# 默认每页显示的数据条数page_size = 1# URL传入的每页显示条数的参数page_size_query_param = 'page_size'# 每页显示数据最大条数max_page_size = 1000# 根据ID从大到小排列ordering = "id"

通过requests模块访问,结果如下

api = 'http://127.0.0.1:8899/api/page/?page_size=1'
response = requests.get(url=api)
print(response.text)
"""
{"next":"http://127.0.0.1:8899/api/page/?cursor=cD0x&page_size=1","previous":null,"results":[{"id":1,"username":"Lena","password":"123","email":"lena@live.com","user_type":1,"group":1}]}
"""

可以看到cursor=cD0x是加密,看不到第几页,只能一页页翻,没办法通过指定cursor的值直接翻页。

路由和视图

如果要支持url带后缀,比如.json,那么可以在路由规则后面加\.(?P<format>\w+)$(具体见后面的渲染器部分)。下面通过实现增删改查视图,来看看几种不同路由方式的区别。

增删改查分别对应几种不同请求方法:

  • GET: 查询列表
  • POST: 增加
  • GET: 查询单条数据(id)
  • PUT: 更新(id)
  • DELETE: 删除(id)

手动路由

需要写两套路由,以分别支持无id和有id传参的情况,每套路由还要支持无url后缀和有url后缀的情况,共计4条路由。推荐手动路由,可定制性强。

urlpatterns = [# http: //127.0.0.1:8000/api/router 无id# GET: 查询(列表)# POST: 增加url(r'api/router/$', views.RouterView.as_view()),url(r'api/router\.(?P<format>\w+)$', views.RouterView.as_view()),  # 支持后缀# http: //127.0.0.1:8000/api/router/1 有id# GET: 查询(单条记录)# PUT: 更新# DELETE: 删除url(r'api/router/(?P<pk>\d+)/$', views.RouterView.as_view()),url(r'api/router/(?P<pk>\d+)\.(?P<format>\w+)$', views.RouterView.as_view()),  # 支持后缀
]

视图中手动实现这几种请求方式:

from rest_framework.serializers import ModelSerializer
from rest_framework.views import APIViewclass RouterSerializer(ModelSerializer):class Meta:model = models.UserInfofields = '__all__'class RouterView(APIView):def get(self, request, *args, **kwargs):pk = kwargs.get('pk')if pk:obj = models.UserInfo.objects.filter(pk=pk).first()ser = RouterSerializer(instance=obj, many=False)else:user_list = models.UserInfo.objects.all()ser = RouterSerializer(instance=user_list, many=True)return Response(ser.data)def post(self, request, *args, **kwargs):passdef put(self, request, *args, **kwargs):pk = kwargs.get('pk')passdef delete(self, request, *args, **kwargs):pk = kwargs.get('pk')pass

半自动路由

视图继承中继承ModelViewSet,其中提供了增删改查方法,不过需要在路由中指定。(视图部分存疑先)

urlpatterns = [url(r'api/router/$', views.RouterView.as_view({'get': 'list', 'post': 'create'})),url(r'api/router/(?P<pk>\d+)/$', views.RouterView.as_view({'get': 'retrieve', 'put': 'update', 'delete': 'destroy'})),
]
from rest_framework.serializers import ModelSerializer
from rest_framework.viewsets import ModelViewSetclass RouterSerializer(ModelSerializer):class Meta:model = models.UserInfofields = '__all__'class RouterView(ModelViewSet):queryset = models.UserInfo.objects.all()serializer_class = RouterSerializer

全自动路由

from django.conf.urls import url,include
from app01 import views
from rest_framework.routers import DefaultRouter  # 自动路由router = DefaultRouter()    #  实例化router对象
router.register(r'/XXX/', views.TargetView1)  # 将目标视图注册到router对象上
router.register(r'/XXY/', views.TargetView2)  # 可以注册多个urlpatterns = [url(r'^', include(router.urls)),    # 自动实现增删改路由
]
from rest_framework.viewsets import ModelViewSet
from rest_framework import serializersclass RouteSerializer(serializers.ModelSerializer):class Meta:model = models.UserInfofields = "__all__"class RouteView(ModelViewSet):queryset = models.UserInfo.objects.all()serializer_class = RouteSerializer

渲染器

根据 用户请求URL 或 用户可接受的类型,筛选出合适的 渲染组件。注意,如果要支持url后缀,路由正则后面必须加\.(?P<format>\w+)。如果同时多个存在时,自动根据URL后缀来选择渲染器。

  urlpatterns = [url(r'test/$', views.RenderTestView.as_view()),url(r'test\.(?P<format>\w+)$', views.RenderTestView.as_view()),  # 支持后缀
]

json

用户请求url
  • http://127.0.0.1:8000/test/?format=json
  • http://127.0.0.1:8000/test.json
  • http://127.0.0.1:8000/test
CBV
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.pagination import PageNumberPagination
from rest_framework import serializers
from rest_framework.renderers import JSONRendererclass CustomPagination(PageNumberPagination):passclass RenderTestSerializer(serializers.ModelSerializer):passclass RenderTestView(APIView):renderer_classes = [JSONRenderer, ]def get(self, request, *args, **kwargs):pass

表格

以table表友好地呈现 ,好看,没多大用。

用户访问url
  • http://127.0.0.1:8000/test/?format=admin
  • http://127.0.0.1:8000/test.admin
  • http://127.0.0.1:8000/test/
CBV
from rest_framework.renderers import HTMLFormRendererclass RenderTestView(APIView):renderer_classes = [HTMLFormRenderer, ]def get(self, request, *args, **kwargs):pass

Form表单

form表单,只能返回单个序列化对象,否则报错,没暖用。

用户访问url
  • http://127.0.0.1:8000/test/?format=admin
  • http://127.0.0.1:8000/test.admin
  • http://127.0.0.1:8000/test/
CBV
from rest_framework.renderers import HTMLFormRendererclass RenderTestView(APIView):renderer_classes = [HTMLFormRenderer, ]def get(self, request, *args, **kwargs):pass

浏览器格式API+JSON

这种是最常用的

用户访问url
  • http://127.0.0.1:8000/test/?format=api
  • http://127.0.0.1:8000/test.json
  • http://127.0.0.1:8000/test/
CBV
from rest_framework.renderers import JSONRenderer
from rest_framework.renderers import BrowsableAPIRendererclass RenderTestView(APIView):renderer_classes = [JSONRenderer, BrowsableAPIRenderer, ]def get(self, request, *args, **kwargs):pass
查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. java压缩zip文件中文乱码问题

    --转自:http://riching.iteye.com/blog/579634 用java来打包文件生成压缩文件,有两个地方会出现乱码 1、内容的中文乱码问题,这个问题网上很多人给出了解决方法,两种:修改sun的源码;使用开源的类库org.apache.tools.zip.ZipOutputStream和org.apache.tools.zip.ZipEntry,…...

    2024/4/19 16:49:26
  2. 安装系统时出现-安装程序无法创建新的系统分区,也无法定位现有的系统分区,解决方法

    使用光盘安装系统,结果格式化C盘后,正准备装系统了,突然弹出“安装程序无法创建新的系统分区,也无法定位现有的系统分区”,就好像下图所示:看到这种情况,解决办法,具体步骤如下:1,准备好一个4G或者更大容量的U盘,用U大师把U盘制作成启动盘,重启电脑设置U盘为第一启…...

    2024/4/30 18:55:57
  3. python2 urlencod urldecode

    import urllib value = {: 三生三世} data = urllib.urlencode(value) print data 打印 %E4%B8%89%E7%94%9F%E4%B8%89%E4%B8%96print urllib.unquote(%E4%B8%89%E7%94%9F%E4%B8%89%E4%B8%96) 打印 三生三世希望能交流更多技术,关注小白的微信公众号吧。...

    2024/4/30 16:40:13
  4. 用ZipInputStream和ZipOutputStream实现文件及文件夹的压缩解压

    主要是运用ZipInputStream和ZipOutputStream实现 当中要注意,在文件解压时,ZipEntry中的名字是相对路径 File的方法createNewFile()只能在存在的目录下创建文件,所以在有多层目录时,先要mkdirs来创建目录,然后创建文件 代码如下 import java.io.*; import java.util.zip.*;…...

    2024/4/20 10:27:46
  5. 树莓派FM广播点歌系统

    简介 前段时间有人发贴说用树莓派可以发射FM收音机信号, 于是便整来玩玩,发现效果还不错,于是想扩充一下功能。 网上实现的FM发射功能是有局限性的: 只能播放wav格式文件,并且wav文件必须是16 bit 22.5kHz Mono格式的。 只支持播放本地音频文件,或者使用-从终端读取音频 …...

    2024/4/30 19:55:22
  6. vue-admin-template笔记(四)

    文章目录Vue学习笔记(四)treetable总结参考 Vue学习笔记(四)学习vue-admin-template https://github.com/PanJiaChen/vue-admin-template上一篇中分析了vue-admin-template的入口逻辑,包括main.js, App.vue, router。这篇根据router来分析下其他的页面。 下面是一部分路由…...

    2024/4/17 12:14:06
  7. AS3 深度拷贝对象的构造函数带参数

    关于AS3的深度拷贝, 网站有不少的的文章介绍. 我在EncryptZip项目中也用到了, 由于ZipEntry 有自己的构造函数, 在copier.readObject()处得到了参数个数不匹配错误(ArgumentError: Error #1063), 所以查找了相关资料, 然而最终也没有找到一个好的解决方案. 资料显示:如果不使…...

    2024/4/26 7:03:11
  8. C# 汉字 转 拼音 G2312编码

    using System;using System.IO;using System.Text;namespace WindowsApplication5{ /// <summary> /// ConvertHzToPz_Gb2312 的摘要说明。 /// </summary> public class ConvertHzToPz_Gb2312 { public ConvertHzToPz_Gb2312() { } #region //gb2312中的…...

    2024/4/13 2:02:57
  9. 二维码解析成链接 二维码解码

    左侧上传二维码就可以进行解析,右边会自动生成网址链接, 点击去解析...

    2024/4/17 12:14:18
  10. 文件夹解压缩问题 出错求解

    源代码如下:求各位大牛帮忙看看哪出错了import java.io.File ;import java.io.OutputStream ;import java.io.InputStream ;import java.util.zip.ZipEntry ;import java.util.zip.ZipFile ;import java.util.zip.ZipInputStream ;import java.io.FileInputStream ;import java…...

    2024/4/17 12:13:12
  11. 为java程序制作exe

    将java程序制作成Windows下的安装需要完成如下步骤: 把程序的class文件打包成Jar文件把jar文件转成exe精简JRE将整个程序(包含jre)打包成安装包使用exe4j将jar文件转成exe exe4j是一个帮助你集成Java应用程序到Windows操作环境的java可执行文件生成工具,无论这些应用是用于…...

    2024/4/17 12:15:25
  12. vueadmin-template应用1:安装入门

    vueadmin-template模板使用目前是比较流行的,它是在elementui基础上再次封装的。github地址:https://github.com/PanJiaChen/vue-admin-template1、安装在GitHub上https://github.com/PanJiaChen/vue-admin-template下载,下载完之后我们把名称修改一下为自己的名称,然后进行…...

    2024/4/18 22:26:20
  13. 小程序入门

    小程序是什么? 小程序是一种全新的连接用户与服务的方式,它可以在微信内被便捷地获取和传播,同时具有出色的使用体验。 小程序并非凭空冒出来的一个概念。当微信中的 WebView 逐渐成为移动 Web 的一个重要入口时,微信就有相关的 JS API 了 小程序与普通网页开发的区别 小程…...

    2024/4/17 12:13:54
  14. bat脚本批量静默安装程序

    1、你最关心的如何静默安装“/q” 用于安装程序时可以静默安装start /wait %%i /q其中“/wait”是等上一个程序执行结束后再接续执行下一个2、正文开始用bat脚本自动安装程序,部署一些环境顺便学习了bat脚本,在这记录一下,方便以后维护、开发@echo off @mode con lines=40 c…...

    2024/4/19 12:53:29
  15. javascript 的URL 编码 和 解码 的几种方法 以及比较

    当url中有中文时为了不出现乱码需要为URL编码方法如下:URL编码: bodyEncode.value = encodeURI(document.all.keyword.value)URL解码: body.value = decodeURI(bodyEncode.value) 不只是这一种方法: javascript 常用的编码格式有:escape(), encodeURL(), encodeURIComponent()…...

    2024/4/17 12:14:06
  16. Django 自动生成api接口文档

    Django 自动生成api接口文档安装第三方包配置接口路径在serializers.py文件设置备注用浏览器打开api接口文档页面: 安装第三方包 pip install coreapi配置接口路径 from rest_framework.documentation import include_docs_urlsurlpatterns = [url(r^admin/, admin.site.urls)…...

    2024/4/20 1:24:35
  17. Java直接遍历并读取zip压缩文件的内容以及错误处理

    有一个项目需要从压缩包中读取数据,然后处理数据。刚开始想的是解压缩然后再读取。后又找到了个直接读取的。主要是用到了ZipEntry。多个压缩包嵌套也可以这样读取,思路是将压缩包里的压缩包解压出来。直接上代码:/** * Licensed to the Apache Software Foundation (ASF) u…...

    2024/4/17 12:14:13
  18. 解决Web安装程序不能选择安装目录问题(1)

    解决Microsoft Visual Studio .NET的WebApplication 安装部署制作的安装程序不能选择安装目录的问题 【问题提出】  Microsoft Visual Studio .NET中的 Web Application 安装部署制作,它可以生成一个安装程序,此安装程序在安装时,会自动在系统盘的…...

    2024/3/31 19:39:06
  19. iOS开发中Objective-C 对 URL的URLEncode(编码)与URLDecode(解码)

    版权归Aaidong所有〜转载需声名〜 欢迎大家访问:http://blog.csdn.net/aaidong废话不多说,直接上代码。[objc] viewplain copy<pre name="code" class="objc"> [objc] viewplain copy// // NSString+URL.h // // Created by aidong on 1…...

    2024/4/20 7:50:24
  20. GB编码,汉字在编译器中数值

    参考http://blog.sina.com.cn/s/blog_8184e033010109ug.html http://blog.csdn.net/oncreate/article/details/1656806?utm_source=jiancool http://www.cnblogs.com/xingyunblog/p/3764002.html基本简介   GB码,全称是GB2312-80《信息交换用汉字编码字符集基本集》,19…...

    2024/4/20 7:54:56

最新文章

  1. 前端面试题大合集2----基础篇

    目录 1、事件模型 2、什么是事件委托/事件代理 3、说一下Commonjs、AMD和CMD 4、Ajax原理 5、说一下XHR和Fetch的区别 6、实现一个once函数&#xff0c;传入函数只执行一次 7、js监听对象属性的改变 8、如何解决跨域问题 9、介绍js有哪些内置对象 10、介绍js有哪些方法…...

    2024/4/30 20:17:17
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. 汽车疲劳测试试验平台技术要求(北重厂家)

    汽车疲劳测试试验平台技术要求通常包括以下几个方面&#xff1a; 车辆加载能力&#xff1a;测试平台需要具备足够的承载能力&#xff0c;能够同时测试多种车型和不同重量的车辆。 动力系统&#xff1a;测试平台需要具备稳定可靠的动力系统&#xff0c;能够提供足够的力和速度来…...

    2024/4/30 1:45:03
  4. xv6项目开源—05

    xv6项目开源—05.md 理论&#xff1a; 1、设备驱动程序在两种环境中执行代码&#xff1a;上半部分在进程的内核线程中运行&#xff0c;下半部分在中断时执行。上半部分通过系统调用进行调用&#xff0c;如希望设备执行I/O操作的read和write。这段代码可能会要求硬件执行操作&…...

    2024/4/30 7:09:10
  5. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/4/29 23:16:47
  6. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/4/30 18:14:14
  7. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/29 2:29:43
  8. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/4/30 18:21:48
  9. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/27 17:58:04
  10. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/27 14:22:49
  11. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/28 1:28:33
  12. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/30 9:43:09
  13. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/27 17:59:30
  14. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/4/25 18:39:16
  15. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/28 1:34:08
  16. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  17. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/29 20:46:55
  18. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/25 18:39:14
  19. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/4/26 23:04:58
  20. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/27 23:24:42
  21. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/28 5:48:52
  22. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/30 9:42:22
  23. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/4/30 9:43:22
  24. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/30 9:42:49
  25. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  26. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  27. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  28. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  29. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  30. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  31. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  32. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  33. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  34. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  35. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  36. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  37. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  38. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  39. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  40. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  41. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  42. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  43. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  44. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57