三剑客之认证、权限与频率组件

认证组件

局部视图认证

在app01.service.auth.py:

class Authentication(BaseAuthentication):

    def authenticate(self,request):
token=request._request.GET.get("token")
token_obj=UserToken.objects.filter(token=token).first()
if not token_obj:
raise exceptions.AuthenticationFailed("验证失败!")
return (token_obj.user,token_obj)

在views.py:

def get_random_str(user):
import hashlib,time
ctime=str(time.time()) md5=hashlib.md5(bytes(user,encoding="utf8"))
md5.update(bytes(ctime,encoding="utf8")) return md5.hexdigest() from app01.service.auth import * from django.http import JsonResponse
class LoginViewSet(APIView):
authentication_classes = [Authentication,]
def post(self,request,*args,**kwargs):
res={"code":1000,"msg":None}
try:
user=request._request.POST.get("user")
pwd=request._request.POST.get("pwd")
user_obj=UserInfo.objects.filter(user=user,pwd=pwd).first()
print(user,pwd,user_obj)
if not user_obj:
res["code"]=1001
res["msg"]="用户名或者密码错误"
else:
token=get_random_str(user)
UserToken.objects.update_or_create(user=user_obj,defaults={"token":token})
res["token"]=token except Exception as e:
res["code"]=1002
res["msg"]=e return JsonResponse(res,json_dumps_params={"ensure_ascii":False})

备注:一个知识点:update_or_create(参数1,参数2...,defaults={‘字段’:'对应的值'}),这个方法使用于:如果对象存在,则进行更新操作,不存在,则创建数据。使用时会按照前面的参数进行filter,结果为True,则执行update  defaults中的值,否则,创建。

使用方式: 

  使用认证组件时,自定制一个类,在自定制的类下,必须实现方法:authenticate(self,request),必须接收一个request参数,结果必须返回一个含有两个值的元组,认证失败,就抛一个异常rest_framework下的exceptions.APIException。然后在对应的视图类中添加一个属性:authentication_classes=[自定制类名]即可。

备注:from rest_framework.exceptions import AuthenticationFailed   也可以抛出这个异常(这个异常是针对认证的异常)

使用这个异常时,需要在自定制的类中,定制一个方法:def authenticate_header(self,request):pass,每次这样定制一个方法很麻烦,我们可以在自定义类的时候继承一个类: BaseAuthentication

调用方式:from rest_framework.authentication import BaseAuthentication

为什么是这个格式,我们看看这个组件内部的 实现吧!
不管是认证,权限还是频率都发生在分发之前:

class APIView(View):
def dispatch(self, request, *args, **kwargs):
"""
`.dispatch()` is pretty much the same as Django's regular dispatch,
but with extra hooks for startup, finalize, and exception handling.
"""
self.args = args
self.kwargs = kwargs
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers # deprecate? try:
self.initial(request, *args, **kwargs) # Get the appropriate handler method
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed response = handler(request, *args, **kwargs) except Exception as exc:
response = self.handle_exception(exc) self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response

所有的实现都发生在       self.initial(request, *args, **kwargs)        这行代码中

class APIView(View):
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)
      # 用户访问频率的限制
self.check_throttles(request)

我们先看认证相关   self.perform_authentication

class APIView(View):
def perform_authentication(self, request):
"""
Perform authentication on the incoming request. Note that if you override this and simply 'pass', then authentication
will instead be performed lazily, the first time either
`request.user` or `request.auth` is accessed.
"""
request.user

整个这个方法就执行了一句代码:request.user   ,这个request是新的request,是Request()类的实例对象,  .user 肯定在Request类中有一个user的属性方法。

class Request(object):
@property
def user(self):
"""
Returns the user associated with the current request, as authenticated
by the authentication classes provided to the request.
"""
if not hasattr(self, '_user'):
with wrap_attributeerrors():
self._authenticate()
return self._user

这个user方法中,执行了一个 self._authenticate()方法,继续看这个方法中执行了什么:

class Request(object):
def _authenticate(self):
"""
Attempt to authenticate the request using each authentication instance
in turn.
"""
for authenticator in self.authenticators:
try:
user_auth_tuple = authenticator.authenticate(self)
except exceptions.APIException:
self._not_authenticated()
raise if user_auth_tuple is not None:
self._authenticator = authenticator
self.user, self.auth = user_auth_tuple
return self._not_authenticated()

这个方法中,循环一个东西:self.authenticators  ,这个self,是新的request,我们看看这个self.authenticators是什么。

class Request(object):
"""
Wrapper allowing to enhance a standard `HttpRequest` instance. Kwargs:
- request(HttpRequest). The original request instance.
- parsers_classes(list/tuple). The parsers to use for parsing the
request content.
- authentication_classes(list/tuple). The authentications used to try
authenticating the request's user.
""" def __init__(self, request, parsers=None, authenticators=None,
negotiator=None, parser_context=None): self._request = request
self.parsers = parsers or ()
self.authenticators = authenticators or ()

这个self.authenticators是实例化时初始化的一个属性,这个值是实例化新request时传入的参数。

class APIView(View):

     def initialize_request(self, request, *args, **kwargs):
"""
Returns the initial request object.
"""
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
)

实例化传参时,这个参数来自于self.get_authenticators(),这里的self就不是request,是谁调用它既是谁,看看吧:

class APIView(View):
def get_authenticators(self):
"""
Instantiates and returns the list of authenticators that this view can use.
"""
return [auth() for auth in self.authentication_classes]

这个方法,返回了一个列表推导式   [auth() for auth in self.authentication_classes] ,到了这里,是不是很晕,这个self.authentication_classes又是啥,这不是我们在视图函数中定义的属性吗,值是一个列表,里面放着我们自定义认证的类

class APIView(View):

    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES

如果我们不定义这个属性,会走默认的值  api_settings.DEFAULT_AUTHENTICATION_CLASSES

  走了一圈了,我们再走回去呗,首先,这个列表推导式中放着我们自定义类的实例,回到这个方法中def _authenticate(self),当中传的值self指的是新的request,这个我们必须搞清楚,然后循环这个包含所有自定义类实例的列表,得到一个个实例对象。

for authenticator in self.authenticators:
try:
user_auth_tuple = authenticator.authenticate(self)
except exceptions.APIException:
self._not_authenticated()
raise if user_auth_tuple is not None:
self._authenticator = authenticator
self.user, self.auth = user_auth_tuple
return

然后执行每一个实例对象的authenticate(self)方法,也就是我们在自定义类中必须实现的方法,这就是原因,因为源码执行时,会找这个方法,认证成功,返回一个元组 ,认证失败,捕捉一个异常,APIException。认证成功,这个元组会被self.user,self.auth接收值,所以我们要在认证成功时返回含有两个值的元组。这里的self是我们新的request,这样我们在视图函数和模板中,只要在这个request的生命周期,我们都可以通过request.user得到我们返回的第一个值,通过request.auth得到我们返回的第二个值。这就是认证的内部实现,很牛逼。

备注:所以我们认证成功后返回值时,第一个值,最好时当前登录人的名字,第二个值,按需设置,一般是token值(标识身份的随机字符串)

这种方式只是实现了对某一张表的认证,如果我们有100张表,那这个代码我就要写100遍,复用性很差,所以需要在全局中定义。

全局视图认证组件

settings.py配置如下:

1
2
3
REST_FRAMEWORK={
    "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",]
}

为什么要这样配置呢?我们看看内部的实现吧:

  从哪开始呢?在局部中,我们在视图类中加一个属性authentication_classes=[自定义类],那么在内部,肯定有一个默认的值:

class APIView(View):

    authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES

默认值是api_settings中的一个属性,看看这个默认值都实现了啥

api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)

首先,这个api_settings是APISettings类的一个实例化对象,传了三个参数,那这个DEFAULTS是啥,看看:

#rest_framework中的settings.py

DEFAULTS = {
# Base API policies
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
),
'DEFAULT_PARSER_CLASSES': (
'rest_framework.parsers.JSONParser',
'rest_framework.parsers.FormParser',
'rest_framework.parsers.MultiPartParser'
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication'
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.AllowAny',
),
'DEFAULT_THROTTLE_CLASSES': (),
'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'rest_framework.negotiation.DefaultContentNegotiation',
'DEFAULT_METADATA_CLASS': 'rest_framework.metadata.SimpleMetadata',
'DEFAULT_VERSIONING_CLASS': None, # Generic view behavior
'DEFAULT_PAGINATION_CLASS': None,
'DEFAULT_FILTER_BACKENDS': (), # Schema
'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.AutoSchema', # Throttling
'DEFAULT_THROTTLE_RATES': {
'user': None,
'anon': None,
},
'NUM_PROXIES': None, # Pagination
'PAGE_SIZE': None, # Filtering
'SEARCH_PARAM': 'search',
'ORDERING_PARAM': 'ordering', # Versioning
'DEFAULT_VERSION': None,
'ALLOWED_VERSIONS': None,
'VERSION_PARAM': 'version', # Authentication
'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser',
'UNAUTHENTICATED_TOKEN': None, # View configuration
'VIEW_NAME_FUNCTION': 'rest_framework.views.get_view_name',
'VIEW_DESCRIPTION_FUNCTION': 'rest_framework.views.get_view_description', # Exception handling
'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler',
'NON_FIELD_ERRORS_KEY': 'non_field_errors', # Testing
'TEST_REQUEST_RENDERER_CLASSES': (
'rest_framework.renderers.MultiPartRenderer',
'rest_framework.renderers.JSONRenderer'
),
'TEST_REQUEST_DEFAULT_FORMAT': 'multipart', # Hyperlink settings
'URL_FORMAT_OVERRIDE': 'format',
'FORMAT_SUFFIX_KWARG': 'format',
'URL_FIELD_NAME': 'url', # Input and output formats
'DATE_FORMAT': ISO_8601,
'DATE_INPUT_FORMATS': (ISO_8601,), 'DATETIME_FORMAT': ISO_8601,
'DATETIME_INPUT_FORMATS': (ISO_8601,), 'TIME_FORMAT': ISO_8601,
'TIME_INPUT_FORMATS': (ISO_8601,), # Encoding
'UNICODE_JSON': True,
'COMPACT_JSON': True,
'STRICT_JSON': True,
'COERCE_DECIMAL_TO_STRING': True,
'UPLOADED_FILES_USE_URL': True, # Browseable API
'HTML_SELECT_CUTOFF': 1000,
'HTML_SELECT_CUTOFF_TEXT': "More than {count} items...", # Schemas
'SCHEMA_COERCE_PATH_PK': True,
'SCHEMA_COERCE_METHOD_NAMES': {
'retrieve': 'read',
'destroy': 'delete'
},
}

很直观的DEFAULTS是一个字典,包含多组键值,key是一个字符串,value是一个元组,我们需要的数据:

'DEFAULT_AUTHENTICATION_CLASSES': ( 'rest_framework.authentication.SessionAuthentication', 'rest_framework.authentication.BasicAuthentication' ), 这有啥用呢,api_settings是怎么使用这个 参数的呢

class APISettings(object):
"""
A settings object, that allows API settings to be accessed as properties.
For example: from rest_framework.settings import api_settings
print(api_settings.DEFAULT_RENDERER_CLASSES) Any setting with string import paths will be automatically resolved
and return the class, rather than the string literal.
"""
def __init__(self, user_settings=None, defaults=None, import_strings=None):
if user_settings:
self._user_settings = self.__check_user_settings(user_settings)
self.defaults = defaults or DEFAULTS
self.import_strings = import_strings or IMPORT_STRINGS
self._cached_attrs = set()

好像也看不出什么有用的信息,只是一些赋值操作,将DEFAULTS赋给了self.defaults,那我们再回去看,

在视图函数中,我们不定义authentication_classes 就会执行默认的APIView下的authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES,通过这个api_settings对象调用值时,调不到会怎样,我们看看怎么取值吧,首先会在__getattribute__方法中先找,找不到,取自己的属性中找,找不到去本类中找,在找不到就去父类中找,再找不到就去__getattr__中找,最后报错。按照这个逻辑,执行默认 值时最后会走到类中的__getattr__方法中

class APISettings(object):
def __getattr__(self, attr):
if attr not in self.defaults:
raise AttributeError("Invalid API setting: '%s'" % attr) try:
# Check if present in user settings
val = self.user_settings[attr]
except KeyError:
# Fall back to defaults
val = self.defaults[attr] # Coerce import strings into classes
if attr in self.import_strings:
val = perform_import(val, attr) # Cache the result
self._cached_attrs.add(attr)
setattr(self, attr, val)
return val

备注:调用__getattr__方法时,会将取的值,赋值给__getattr__中的参数attr,所有,此时的attr是DEFAULT_AUTHENTICATION_CLASSES,这个值在self.defaluts中,所以,代码会执行到try中,val=self.user_settings[attr],这句代码的意思是,val的值是self.user_settings这个东西中取attr,所以self.user_settings一定是个字典,我们看看这个东西做l什么

class APISettings(object):
@property
def user_settings(self):
if not hasattr(self, '_user_settings'):
self._user_settings = getattr(settings, 'REST_FRAMEWORK', {})
return self._user_settings

这个属性方法,返回self._user_settings,这个值从哪里来,看代码逻辑,通过反射,来取settings中的REST_FRAMEWORK的值,取不到,返回一个空字典。那这个settings是哪的,是我们项目的配置文件。

  综合起来,意思就是如果在api_settings对象中找不到这个默认值,就从全局settings中找一个变量REST_FRAMEWORK,这个变量是个字典,从这个字典中找attr(DEFAULT_AUTHENTICATION_CLASSES)的值,找不到,返回一个空字典。而我们的配置文件settings中并没有这个变量,很明显,我们可以在全局中配置这个变量,从而实现一个全局的认证。怎么配?

  配置格式:REST_FRAMEWORK={“DEFAULT_AUTHENTICATION_CLASSES”:("认证代码所在路径","...")}

代码继续放下执行,执行到if attr in self.import_strings:       val = perform_import(val, attr)   这两行就开始,根据取的值去通过字符串的形式去找路径,最后得到我们配置认证的类,在通过setattr(self, attr, val)   实现,调用这个默认值,返回配置类的执行。

def perform_import(val, setting_name):
"""
If the given setting is a string import notation,
then perform the necessary import or imports.
"""
if val is None:
return None
elif isinstance(val, six.string_types):
return import_from_string(val, setting_name)
elif isinstance(val, (list, tuple)):
return [import_from_string(item, setting_name) for item in val]
return val def import_from_string(val, setting_name):
"""
Attempt to import a class from a string representation.
"""
try:
# Nod to tastypie's use of importlib.
module_path, class_name = val.rsplit('.', 1)
module = import_module(module_path)
return getattr(module, class_name)
except (ImportError, AttributeError) as e:
msg = "Could not import '%s' for API setting '%s'. %s: %s." % (val, setting_name, e.__class__.__name__, e)
raise ImportError(msg)

备注:通过importlib模块的import_module找py文件。

权限组件

局部视图权限

在app01.service.permissions.py中:

from rest_framework.permissions import BasePermission
class SVIPPermission(BasePermission):
message="SVIP才能访问!"
def has_permission(self, request, view):
if request.user.user_type==3:
return True
return False

在views.py:

from app01.service.permissions import *

class BookViewSet(generics.ListCreateAPIView):
permission_classes = [SVIPPermission,]
queryset = Book.objects.all()
serializer_class = BookSerializers

全局视图权限

settings.py配置如下:

1
2
3
4
REST_FRAMEWORK={
    "DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",],
    "DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",]
}

权限组件一样的逻辑,同样的配置,要配置权限组件,就要在视图类中定义一个变量permission_calsses = [自定义类]

同样的在这个自定义类中,要定义一个固定的方法   def has_permission(self,request,view)  必须传两个参数,一个request,一个是

当前视图类的对象。权限通过返回True,不通过返回False,我们可以定制错误信息,在自定义类中配置一个静态属性message="错误信息",也可以继承一个类BasePermission。跟认证一样。

    def check_permissions(self, request):
"""
Check if the request should be permitted.
Raises an appropriate exception if the request is not permitted.
"""
for permission in self.get_permissions():
if not permission.has_permission(request, self):
self.permission_denied(
request, message=getattr(permission, 'message', None)
)

逻辑很简单,循环这个自定义类实例对象列表,从每一个对象中找has_permission,if not  返回值:表示返回False,,就会抛一个异常,这个异常的信息,会先从对象的message属性中找。

  同样的,配置全局的权限,跟认证一样,在settings文件中的REST_FRAMEWORK字典中配一个键值即可。

throttle(访问频率)组件

局部视图throttle

在app01.service.throttles.py中:

from rest_framework.throttling import BaseThrottle

VISIT_RECORD={}
class VisitThrottle(BaseThrottle): def __init__(self):
self.history=None def allow_request(self,request,view):
remote_addr = request.META.get('REMOTE_ADDR')
print(remote_addr)
import time
ctime=time.time() if remote_addr not in VISIT_RECORD:
VISIT_RECORD[remote_addr]=[ctime,]
return True history=VISIT_RECORD.get(remote_addr)
self.history=history while history and history[-1]<ctime-60:
history.pop() if len(history)<3:
history.insert(0,ctime)
return True
else:
return False def wait(self):
import time
ctime=time.time()
return 60-(ctime-self.history[-1])

在views.py中:

from app01.service.throttles import *

class BookViewSet(generics.ListCreateAPIView):
throttle_classes = [VisitThrottle,]
queryset = Book.objects.all()
serializer_class = BookSerializers

全局视图throttle

REST_FRAMEWORK={
"DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",],
"DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",],
"DEFAULT_THROTTLE_CLASSES":["app01.service.throttles.VisitThrottle",]
}

同样的,局部使用访问频率限制组件时,也要在视图类中定义一个变量:throttle_classes = [自定义类],同样在自定义类中也要定义一个固定的方法def allow_request(self,request,view),接收两个参数,里面放我们频率限制的逻辑代码,返回True通过,返回False限制,同时要定义一个def wait(self):pass   放限制的逻辑代码。

内置throttle类

在app01.service.throttles.py修改为:

class VisitThrottle(SimpleRateThrottle):

    scope="visit_rate"
def get_cache_key(self, request, view): return self.get_ident(request)

settings.py设置:

REST_FRAMEWORK={
"DEFAULT_AUTHENTICATION_CLASSES":["app01.service.auth.Authentication",],
"DEFAULT_PERMISSION_CLASSES":["app01.service.permissions.SVIPPermission",],
"DEFAULT_THROTTLE_CLASSES":["app01.service.throttles.VisitThrottle",],
"DEFAULT_THROTTLE_RATES":{
"visit_rate":"5/m",
}
}

总结

  restframework的三大套件中为我们提供了多个粒度的控制。局部的管控和全局的校验,都可以很灵活的控制。下一个系列中,将会带来restframework中的查漏补缺。

深入解析当下大热的前后端分离组件django-rest_framework系列三的更多相关文章

  1. 深入解析当下大热的前后端分离组件django-rest_framework系列一

    前言 Nodejs的逐渐成熟和日趋稳定,使得越来越多的公司开始尝试使用Nodejs来练一下手,尝一尝鲜.在传统的web应用开发中,大多数的程序员会将浏览器作为前后端的分界线.将浏览器中为用户进行页面展 ...

  2. 深入解析当下大热的前后端分离组件django-rest_framework系列四

    查漏补缺系列 解析器 request类 django的request类和rest-framework的request类的源码解析 局部视图 from rest_framework.parsers im ...

  3. 深入解析当下大热的前后端分离组件django-rest_framework系列二

    视图三部曲 一部曲 · 使用混合(mixins) 上一节的视图部分: from rest_framework.views import APIView from rest_framework.resp ...

  4. 海纳百川无所不容,Win10环境下使用Docker容器式部署前后端分离项目Django+Vue.js

    原文转载自「刘悦的技术博客」https://v3u.cn/a_id_179 随着现代化产品研发的不断推进,我们会发现,几乎每个产品线都会包含功能各异的服务,而且服务与服务之间存在也会存在着错综复杂的依 ...

  5. 《Spring Boot 入门及前后端分离项目实践》系列介绍

    课程计划 课程地址点这里 本课程是一个 Spring Boot 技术栈的实战类课程,课程共分为 3 个部分,前面两个部分为基础环境准备和相关概念介绍,第三个部分是 Spring Boot 项目实践开发 ...

  6. SpringBoot+Vue豆宝社区前后端分离项目手把手实战系列教程01---搭建前端工程

    豆宝社区项目实战教程简介 本项目实战教程配有免费视频教程,配套代码完全开源.手把手从零开始搭建一个目前应用最广泛的Springboot+Vue前后端分离多用户社区项目.本项目难度适中,为便于大家学习, ...

  7. 从壹开始前后端分离 [ vue + .netcore 补充教程 ] 三十║ Nuxt实战:动态路由+同构

    上期回顾 说接上文<二九║ Nuxt实战:异步实现数据双端渲染>,昨天咱们通过项目二的首页数据处理,简单了解到了 nuxt 异步数据获取的作用,以及亲身体验了几个重要文件夹的意义,整篇文章 ...

  8. 从壹开始前后端分离 [ vue + .netcore 补程 ] 三十一║ Nuxt终篇:基于Vuex的权限验证探究

    缘起 哈喽大家好,今天周四啦,楼主明天要正式放假了,这里先祝大家节日快乐咯,希望在家里能继续研究点儿东西吧,今天呢是 nuxt 的最后一篇,主要是对权限登录进行研究,这一块咱们之前在说第一个项目的时候 ...

  9. SpringBoot+Vue豆宝社区前后端分离项目手把手实战系列教程02---创建后端工程

    本节代码开源地址 代码地址 项目运行截图 搭建后端工程 0.导入sql 在数据库导入 /* Navicat Premium Data Transfer Source Server : localhos ...

随机推荐

  1. 基础dp例题整理

    背包: 消失之物 先直接做一遍,必然对于i,有 for(j=m;j>=w[i];--j) f[i]+=f[i-w[i]] 所以,如果排除用i做背包的结果,减去这个转移就好了. memcpy(g, ...

  2. jq的$.each遍历数组

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  3. How to speed up insertion performance in PostgreSQL

    Disable any triggers on the table Drop indexes before starting the import, re-create them afterwards ...

  4. ACE反应器(Reactor)模式(3)

    转载于:http://www.cnblogs.com/TianFang/archive/2006/12/18/595938.html 在服务器端使用Reactor框架 使用Reactor框架的服务器端 ...

  5. Divide by Zero 2017 and Codeforces Round #399 (Div. 1 + Div. 2, combined) A B 水 搜索

    A. Oath of the Night's Watch time limit per test 2 seconds memory limit per test 256 megabytes input ...

  6. ACM1004 Let the balloons fly

    These code is for the problem "Let the balloons Fly" in ACM 1004 which need deal with stri ...

  7. RotateAnimation 详解

    RotateAnimation 详解 看看新闻网>看引擎>开源产品 其他构造器的旋转也可参考这副图. RotateAnimation旋转坐标系为以旋转点为坐标系(0,0)点.x轴为0度,顺 ...

  8. Activity及Intent

    1.Activity 在一个Android应用程序中,Activity是为用户操作而展示的可视化界面.比如你要打电话,这个时候的拨号界面就是一个Activity,你要发短信给你的女朋友,这个短信窗口就 ...

  9. poi-对于word的操作(二)

    poi对于word文本的底纹和下划线的样式的展现 package poi.test; import java.io.FileOutputStream; import java.math.BigInte ...

  10. 浏览器报错:unexpected end of input 解决方法

    直接上报错代码: <!DOCTYPE html> <html lang="en"> <head> <meta charset=" ...