码迷,mamicode.com
首页 > 其他好文 > 详细

session and cooike

时间:2019-03-27 17:08:31      阅读:144      评论:0      收藏:0      [点我收藏+]

标签:导致   ddl   组成   安全性   丢失   二进制   standard   ref   mem   

一、理解Cookie

Cooie的作用:

当一个用户通过HTTP访问一个服务器时,这个服务器会将一些Key/Value键值对返回给客户端浏览器,并给这些数据加上一些限制条件,在条件符合时这个用户下次访问这个服务器时,数据又完整地带回给服务器。

Cookie的常用属性:

String name:该Cookie的名称,一旦创建,名称便不可更改

Object value:该Cookie的值,如果值为Unicode字符,需要为字符编码。

如果值为二进制数据,则需要使用BASE64编码

int maxAge 该Cookie失效时间,单位秒。如果为正数,则Cookie在maxAge秒之后失效。

如果为负数,该Cookie为临时Cookie,关闭浏览器即失效,浏览器也不会以任何形式保存Cookie.如果为0,表示删除Cookie。默认是-1

Integer.MAX_VALUE表示永远有效

boolean secure:该Cookie是否仅被使用安全协议传输。安全协议有HTTPS,SSL等,

在网络上传输数据之前先将数据加密。默认为false

secure属性并不能对Cookie内容加密,因而不能保证绝对的安全性。如果

需要高安全性,需要在程序中对Cookie内容加密,解密,以防泄密

String path:该Cookie使用路径。如果设置为”/sessionWeb/”,则

只有contextPath为”/sessionWeb”的程序可以访问该Cookie。如果设置为”/”,则本域名下contextPath都可以访问该Cookie.注意最后一个字符必须为“/”

String domain:可以访问该Cookie的域名。如果设置为”.google.com”,则所有以”google.com”结尾的域名都可以访问该Cookie。注意第一个字符必须为“.”

String comment:该Cookie的用处说明。浏览器显示Cookie信息的时候显示该说明

int version:该Cookie使用的版本号。0表示遵循Netscape的Cookie规范,

1表示遵循W3C的RFC2109规范

注意:浏览器提交Cookie时只会提交NAME与VALUE的值

       修改,删除Cookie时,新建的Cookie除value,maxAge之外的所有属性,例如

       name,path,domain等,都要与原Cookie完全一样。否则,浏览器将视为两个不同的Cookie

       不予覆盖,导致修改,删除失败。

下面给出Cookie的读写流程:

(1)浏览器对于Web服务器应答包头中Cookie的操作步骤:

A.从Web服务器的应答包头中提取所有的cookie。

B.解析这些cookie的组成部分(名称,值,路径等等)。

C.判定主机是否允许设置这些cookie。允许的话,则把这些cookie存储在本地。

(2)浏览器对Web服务器请求包头中所有的cookie进行筛选的步骤:

A.根据请求的url和本地存储cookie的属性,判断那些cookie能被发送给Web服务器。

B.对于多个cookie,判定发送的顺序。

C.把需要发送的cookie加入到请求http包头中一起发送。

通过以上Cookie的读写流程,我们发现Cookie是利用了网页代码中的HTTP头信息进行传递的,但是Cookie 是与Web站点而不是与具体页面关联的,所以无论用户请求浏览某个站点中的哪个页面,浏览器和服务器都将交换该站点中的Cookie信息,浏览器的每一次网页请求,都可伴随Cookie传递。例如:浏览器的打开或刷新网页操作,服务器将Cookie添加到网页的HTTP头信息中,伴随网页数据传回到客户端的浏览器,浏览器根据客户计算机中的Cookie设置来选择是否保存这些数据。如果浏览器不允许保存Cookie,则关掉浏览器后,这些数据就消失。用户访问其他站点时,每个站点都可能会向用户浏览器发送一个 Cookie,而浏览器也会对所有这些 Cookie 做出相应的处理。

 具体工作过程描述如下:

(1)Web客户端通过浏览器向Web服务器发送连接请求,通过HTTP报文请求行中的URL打开某一Web页面。

(2)Web服务器接收到请求后,根据用户端提供的信息产生一个Set-Cookies Header。

(3)将生成的Set-Cookies Header通过Response Header存放在HTTP报文中回传给Web客户端,建立一次会话连接。

(4)Web客户端收到HTTP应答报文后,如果要继续已建立的这次会话,则将Cookies的内容从HTTP报文中取出,形成一个Cookies文本文件储存在客户端计算机的硬盘中或保存在客户端计算机的内存中。

(5)当Web客户端再次向Web服务器发送连接请求时,Web浏览器首先根据要访问站点的URL在本地计算机上寻找对应的Cookies文本文件或在本地计算机的内存中寻找对应的Cookies内容。如果找到,则将此Cookies内容存放在HTTP请求报文中发给Web服务器。

(6)Web服务器接收到包含Cookies内容的HTTP请求后,检索其Cookies中与用户有关的信息,并根据检索结果生成一个客户端所请求的页面应答传递给客户端。

此外,Cookie在客户端计算机上保存的时间是不一样的,这些都是由服务器的设置不同决定的,Cookie中有一个Expires(有效期)属性,这个属性决定了Cookie的保存时间,也可以重新设定来改变它,若不设置该属性,那么Cookie只在浏览网页期间有效,关闭浏览器后,这些Cookie会自动消失。绝大多数网站属于这种情况。通常情况下,Cookie包含Server、Expires、Name、value这几个字段,其中对服务器有用的只是Name和value字段,Expires等字段的内容仅仅是为了告诉浏览器如何处理这些Cookies。

cookie的基本操作

  • cookie增删改查操作
技术图片
技术图片
1、设置Cookies 
 response.set_cookie("cookie_key","value")
2、获取Cookies
 value = request.COOKIES["cookie_key"]
3、删除Cookies
 response.delete_cookie("cookie_key",path="/",domain=name)
4、检测Cookies
 if "cookie_name" is request.COOKIES :
5、rep.set_signed_cookie(key,value,salt=‘加密盐‘,...)
    参数:
        key,              键
        value=‘‘,         值
        max_age=None,     超时时间,表示多少秒数之后失效
        expires=None,     超时时间,表示失效的时间点。支持datetime 和 time.time
        path=‘/‘,         Cookie生效的路径,/ 表示根路径,特殊的:跟路径的cookie可以被任何url的页面访问
        domain=None,      Cookie生效的域名
        secure=False,     https传输
        httponly=False    只能http协议传输,无法被JavaScript获取(不是绝对,底层抓包可以获取到也可以被覆盖)

由于cookie保存在客户端的电脑上,所以,JavaScript和jquery也可以操作cookie。

<script src=‘/static/js/jquery.cookie.js‘></script>
$.cookie("list_pager_num", 30,{ path: ‘/‘ });
技术图片
技术图片

 

  • 基于cookie实现用户回话判断 主页index.html:
技术图片
技术图片
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
    <h1>欢迎:{{ user }}登录</h1>
</body>
</html>
技术图片
技术图片

 

登录页login.html:

技术图片
技术图片
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
    <form action="/login/"  method="POST">
        <input type="text" name="user">
        <input type="text" name="pwd">
        <input type="submit" value="提交" />
    </form>
</body>
</html>
技术图片
技术图片

 

url设置:

urlpatterns = [
    url(r‘^log/‘,views.login),
    url(r‘^index/‘,views.index),
    ]

 

views设置

技术图片
技术图片
from django.shortcuts import render,redirect
from django.shortcuts import HttpResponse
def login(request):
    if request.method == ‘POST‘:
        u = request.POST.get(‘user‘)
        p = request.POST.get(‘pwd‘)
        if u == ‘alex‘ and p == ‘123‘:
            red = redirect(‘/index/‘)  #登录成功,则重定向到index
            red.set_cookie(‘username‘, u) #将用户名插入cookie
            return red
        else:
            return render(request, ‘login.html‘) 
    else:
        return render(request, ‘login.html‘)

def index(request):
    user = request.COOKIES.get(‘username‘) #从cookie中取值
    if user:
        return render(request, ‘index.html‘, {‘user‘:user})
    else:
        return redirect(‘/log/‘)
技术图片
技术图片

 

运行django之后,访问index,会自动跳转到login页面,输入账户密码之后,自动跳转到index,并从cookie中取出username,打印出来

二、理解session

Cookie可以让服务端跟踪每个客户端的访问,但是每次客户端的访问都必须传回这些Cookie,如果Cookie很多,则无形的增加了客户端与服务端的数据传输量,而session的出现正是为了解决这一问题。

Session的工作方式:

1.基于URL Path Parameter,默认支持

2.基于Cookie,如果没有修改Context容器的Cookie标识,则默认也是支持的

3.基于SSL,默认不支持,只有connector.getAttribute("SSLEnabled")为TRUE时才支持

Session如何工作

有了Session ID,服务端就可以创建HttpSession对象了,第一次触发通过request.getSession()方法。如果当前的Session ID还没有对应的HttpSession对象,那么就创建一个新的,并将这个对象加到org.apache.catalina.Manager的sessions容器中保存。Manager类将管理所有Session的声明周期,Session过期将被回收,服务器关闭,Session将被序列化到磁盘等。只要这个HttpSession对象存在,用户就可以根据Session ID来获取这个对象,也就做到了对状态的保持。

从request.getSession中获取的HttpSession对象实际上是StandardSession对象的门面对象,这与Request和Servlet是一样的原理。下图是Session工作的时序图。

从时序图中可以看出,从Request中获取的Session对象保存在org.apache.catalina.Manager类中,他的实现类是org.apache.catalina.session.StandardManager,通过requestSessionId从StandardManager的sessions集合中取出StandardSession对象。由于一个requestedSessionId对应一个访问的客户端,所以一个客户端也就对应一个StandardSession对象,这个对象正是保存我们创建的Session值的。下面我们看一下StandardManager这个类是如何管理StandardSession的生命周期的。

StandardManager类负责Servlet容器中所有的StandardSession对象的生命周期管理。当Servlet容器重启或关闭时,StandardManager负责持久化没有过期的StandardSession对象,他会将所有的StandardSession对象持久化到一个以“SESSIONS.ser”为文件名的文件中。到Servlet容器重启时,也就是StandardManager初始化时,他会重新读取这个文件,解析出所有Session对象,重新保存在StandardManager的sessions集合中。

当Servlet容器关闭StandardManager类会调用unload方法将sessions集合中的StandardSession对象写到“SESSIONS.ser”文件中,然后在启动时再按照上面的状态图重新恢复,注意要持久化保存在Servlet容器中的Session对象,必须调用Servlet容器的stop和start命令,而不能直接结束(kill)Servlet容器的进程。因为直接结束进程,Servlet容器没有机会调用unload方法来持久化这些Session对象。

另外,在StandardManager的sessions集合中的StandardSession对象并不是永远保存的,否则Servlet容器的内存将很容易被消耗尽,所以必须给每个Session对象定义一个有效时间,超过这个时间则Session对象将被清除。在Tomcat中这个有效时间是60s(maxInactiveInterval属性控制),超过60s该Session将会过期。检查每个Session是否失效是在Tomcat的一个后台线程中完成的(backgroundProcess()方法中)。

除了后台进程检查Session是否失效外,当调用request.getSession()时也会检查该Session是否过期。值得注意的是,request.getSession()方法调用的StandardSession永远都会存在,即使与这个客户端关联的Session对象已经过期。如果过期,则又会重新创建一个全新的StandardSession对象,但是以前设置的Session值将会丢失。如果你取到了Session对象,但是通过session.getAttribute取不到前面设置的Session值,请不要奇怪,因为很可能他已经失效了,请检查以下<Manager pathname="" maxInactiveInterval="60" />中maxInactiveInterval配置项的值,如果不想让Session过期则可以设置为-1,但是你要仔细评估一下,网站的访问量和设置的Session的大小,防止将你的Servlet容器内存撑爆。如果不想自动创建Session对象,也可以通过request.getSession(boolean create)方法来判断与该客户端关联的Session对象是否存在。

session的基本操作

Django中默认支持Session,其内部提供了5种类型的Session供开发者使用:

  • 数据库(默认)
  • 缓存
  • 文件
  • 缓存+数据库
  • 加密cookie

数据库session

Django默认支持Session,并且默认是将Session数据存储在数据库中,即:django_session 表中。
技术图片
技术图片
a. 配置 settings.py
 
    SESSION_ENGINE = ‘django.contrib.sessions.backends.db‘   # 引擎(默认)
     
    SESSION_COOKIE_NAME = "sessionid"                       # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串(默认)
    SESSION_COOKIE_PATH = "/"                               # Session的cookie保存的路径(默认)
    SESSION_COOKIE_DOMAIN = None                             # Session的cookie保存的域名(默认)
    SESSION_COOKIE_SECURE = False                            # 是否Https传输cookie(默认)
    SESSION_COOKIE_HTTPONLY = True                           # 是否Session的cookie只支持http传输(默认)
    SESSION_COOKIE_AGE = 1209600                             # Session的cookie失效日期(2周)(默认)
    SESSION_EXPIRE_AT_BROWSER_CLOSE = False                  # 是否关闭浏览器使得Session过期(默认)
    SESSION_SAVE_EVERY_REQUEST = False                       # 是否每次请求都保存Session,默认修改之后才保存(默认)
 
 
 
b. 使用
 
    def index(request):
        # 获取、设置、删除Session中数据
        request.session[‘k1‘]
        request.session.get(‘k1‘,None)
        request.session[‘k1‘] = 123
        request.session.setdefault(‘k1‘,123) # 存在则不设置
        del request.session[‘k1‘]
 
        # 所有 键、值、键值对
        request.session.keys()
        request.session.values()
        request.session.items()
        request.session.iterkeys()
        request.session.itervalues()
        request.session.iteritems()
 
 
        # 用户session的随机字符串
        request.session.session_key
 
        # 将所有Session失效日期小于当前日期的数据删除
        request.session.clear_expired()
 
        # 检查 用户session的随机字符串 在数据库中是否
        request.session.exists("session_key")
 
        # 删除当前用户的所有Session数据
        request.session.delete("session_key")
技术图片
技术图片

 

缓存session

技术图片
技术图片
 SESSION_ENGINE = ‘django.contrib.sessions.backends.cache‘  # 引擎
    SESSION_CACHE_ALIAS = ‘default‘                            # 使用的缓存别名(默认内存缓存,也可以是memcache),此处别名依赖缓存的设置
 
 
    SESSION_COOKIE_NAME = "sessionid"                        # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串
    SESSION_COOKIE_PATH = "/"                                # Session的cookie保存的路径
    SESSION_COOKIE_DOMAIN = None                              # Session的cookie保存的域名
    SESSION_COOKIE_SECURE = False                             # 是否Https传输cookie
    SESSION_COOKIE_HTTPONLY = True                            # 是否Session的cookie只支持http传输
    SESSION_COOKIE_AGE = 1209600                              # Session的cookie失效日期(2周)
    SESSION_EXPIRE_AT_BROWSER_CLOSE = False                   # 是否关闭浏览器使得Session过期
    SESSION_SAVE_EVERY_REQUEST = False                        # 是否每次请求都保存Session,默认修改之后才保存
技术图片
技术图片

 



文件session

技术图片
技术图片
配置 settings.py
 
    SESSION_ENGINE = ‘django.contrib.sessions.backends.file‘    # 引擎
    SESSION_FILE_PATH = None                                    # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址tempfile.gettempdir()                                                            # 如:/var/folders/d3/j9tj0gz93dg06bmwxmhh6_xm0000gn/T
 
 
    SESSION_COOKIE_NAME = "sessionid"                          # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串
    SESSION_COOKIE_PATH = "/"                                  # Session的cookie保存的路径
    SESSION_COOKIE_DOMAIN = None                                # Session的cookie保存的域名
    SESSION_COOKIE_SECURE = False                               # 是否Https传输cookie
    SESSION_COOKIE_HTTPONLY = True                              # 是否Session的cookie只支持http传输
    SESSION_COOKIE_AGE = 1209600                                # Session的cookie失效日期(2周)
    SESSION_EXPIRE_AT_BROWSER_CLOSE = False                     # 是否关闭浏览器使得Session过期
    SESSION_SAVE_EVERY_REQUEST = False                          # 是否每次请求都保存Session,默认修改之后才保存

b. 使用
 
    同上
技术图片
技术图片

 

缓存+数据库Session

数据库用于做持久化,缓存用于提高效率
技术图片
技术图片
 
a. 配置 settings.py
 
    SESSION_ENGINE = ‘django.contrib.sessions.backends.cached_db‘        # 引擎
 
b. 使用
 
    同上
技术图片
技术图片

 

加密cookie Session

技术图片
a. 配置 settings.py
     
    SESSION_ENGINE = ‘django.contrib.sessions.backends.signed_cookies‘   # 引擎
 
b. 使用
 
技术图片

 

使用session实现用户会话判断

views设置

技术图片
技术图片
def session_login(request):
    if request.method == ‘POST‘:
        user = request.POST.get(‘user‘)
        pwd = request.POST.get(‘pwd‘)
        if user == ‘fuzj‘ and pwd == ‘123‘:
            request.session[‘user‘] = user
            return redirect(‘/session_index/‘)

    return render(request,‘session_login.html‘)

@auth
def session_index(request):
    user = request.session.get(‘user‘,None)
    return render(request,‘index.html‘,{‘user‘:user})

def session_logout(request):
    del request.session[‘user‘]
    return redirect(‘/session_login/‘)
技术图片
技术图片

 

补充session的操作

技术图片
技术图片
request.session[‘k1‘]:获取session,如果没有会报错

request.session.get(‘k1‘,None):获取session,如果没有会返回None

request.session[‘k1‘] = 123:设置session,如果k1存在就覆盖

request.session.setdefault(‘k1‘,123):设置session,如果存在则不设置

del request.session["k1"] :只删除k1,随机字符串和其他session值还存在

request.session.session_key:当前用户随机字符串
技术图片
技术图片

 

缓存

由于Django是动态网站,所有每次请求均会去数据进行相应的操作,当程序访问量大时,耗时必然会更加明显,最简单解决方式是使用:缓存,缓存将一个某个views的返回值保存至内存或者memcache中,5分钟内再有人来访问时,则不再去执行view中的操作,而是直接从内存或者Redis中之前缓存的内容拿到,并返回。

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

  • 开发调试
  • 内存
  • 文件
  • 数据库
  • Memcache缓存(python-memcached模块)
  • Memcache缓存(pylibmc模块)

配置

  • 开发调试
技术图片
技术图片
# 此为开始调试用,实际内部不做任何操作
    # 配置:
        CACHES = {
            ‘default‘: {
                ‘BACKEND‘: ‘django.core.cache.backends.dummy.DummyCache‘,     # 引擎
                ‘TIMEOUT‘: 300,                                               # 缓存超时时间(默认300,None表示永不过期,0表示立即过期)
                ‘OPTIONS‘:{
                    ‘MAX_ENTRIES‘: 300,                                       # 最大缓存个数(默认300)
                    ‘CULL_FREQUENCY‘: 3,                                      # 缓存到达最大个数之后,剔除缓存个数的比例,即:1/CULL_FREQUENCY(默认3)
                },
                ‘KEY_PREFIX‘: ‘‘,                                             # 缓存key的前缀(默认空)
                ‘VERSION‘: 1,                                                 # 缓存key的版本(默认1)
                ‘KEY_FUNCTION‘ 函数名                                          # 生成key的函数(默认函数会生成为:【前缀:版本:key】)
            }
        }


    # 自定义key
    def default_key_func(key, key_prefix, version):
        """
        Default function to generate keys.

        Constructs the key used by all other methods. By default it prepends
        the `key_prefix‘. KEY_FUNCTION can be used to specify an alternate
        function with custom key making behavior.
        """
        return ‘%s:%s:%s‘ % (key_prefix, version, key)

    def get_key_func(key_func):
        """
        Function to decide which key function to use.

        Defaults to ``default_key_func``.
        """
        if key_func is not None:
            if callable(key_func):
                return key_func
            else:
                return import_string(key_func)
        return default_key_func
技术图片
技术图片

 

  • 内存
技术图片
技术图片
# 此缓存将内容保存至内存的变量中
    # 配置:
        CACHES = {
            ‘default‘: {
                ‘BACKEND‘: ‘django.core.cache.backends.locmem.LocMemCache‘,
                ‘LOCATION‘: ‘unique-snowflake‘,
            }
        }

    # 注:其他配置同开发调试版本
技术图片
技术图片

 

  • 文件
技术图片
技术图片
# 此缓存将内容保存至文件
    # 配置:

        CACHES = {
            ‘default‘: {
                ‘BACKEND‘: ‘django.core.cache.backends.filebased.FileBasedCache‘,
                ‘LOCATION‘: ‘/var/tmp/django_cache‘,
            }
        }
    # 注:其他配置同开发调试版本
技术图片
技术图片

 

  • 数据库
技术图片
技术图片
# 此缓存将内容保存至数据库

    # 配置:
        CACHES = {
            ‘default‘: {
                ‘BACKEND‘: ‘django.core.cache.backends.db.DatabaseCache‘,
                ‘LOCATION‘: ‘my_cache_table‘, # 数据库表
            }
        }

    # 注:执行创建表命令 python manage.py createcachetable
技术图片
技术图片

 

  • Memcache缓存(python-memcached模块)
技术图片
技术图片
# 此缓存使用python-memcached模块连接memcache

    CACHES = {
        ‘default‘: {
            ‘BACKEND‘: ‘django.core.cache.backends.memcached.MemcachedCache‘,
            ‘LOCATION‘: ‘127.0.0.1:11211‘,
        }
    }

    CACHES = {
        ‘default‘: {
            ‘BACKEND‘: ‘django.core.cache.backends.memcached.MemcachedCache‘,
            ‘LOCATION‘: ‘unix:/tmp/memcached.sock‘,
        }
    }   

    CACHES = {
        ‘default‘: {
            ‘BACKEND‘: ‘django.core.cache.backends.memcached.MemcachedCache‘,
            ‘LOCATION‘: [
                ‘172.19.26.240:11211‘,
                ‘172.19.26.242:11211‘,
            ]
        }
    }
技术图片
技术图片

 

  • memcache
技术图片
技术图片
# 此缓存使用pylibmc模块连接memcache
    
    CACHES = {
        ‘default‘: {
            ‘BACKEND‘: ‘django.core.cache.backends.memcached.PyLibMCCache‘,
            ‘LOCATION‘: ‘127.0.0.1:11211‘,
        }
    }

    CACHES = {
        ‘default‘: {
            ‘BACKEND‘: ‘django.core.cache.backends.memcached.PyLibMCCache‘,
            ‘LOCATION‘: ‘/tmp/memcached.sock‘,
        }
    }   

    CACHES = {
        ‘default‘: {
            ‘BACKEND‘: ‘django.core.cache.backends.memcached.PyLibMCCache‘,
            ‘LOCATION‘: [
                ‘172.19.26.240:11211‘,
                ‘172.19.26.242:11211‘,
            ]
        }
    }
技术图片
技术图片

 

应用

  • 全站使用
使用中间件,经过一系列的认证等操作,如果内容在缓存中存在,则使用FetchFromCacheMiddleware获取内容并返回给用户,当返回给用户之前,判断缓存中是否已经存在,如果不存在则UpdateCacheMiddleware会将缓存保存至缓存,从而实现全站缓存
技术图片
技术图片
  MIDDLEWARE = [
        ‘django.middleware.cache.UpdateCacheMiddleware‘,
        # 其他中间件...
        ‘django.middleware.cache.FetchFromCacheMiddleware‘,
    ]

    CACHE_MIDDLEWARE_ALIAS = ""
    CACHE_MIDDLEWARE_SECONDS = ""
    CACHE_MIDDLEWARE_KEY_PREFIX = ""
技术图片
技术图片

 

  • 单独视图缓存
技术图片
技术图片
方式一:
        from django.views.decorators.cache import cache_page

        @cache_page(60 * 15)
        def my_view(request):
            ...

方式二:
        from django.views.decorators.cache import cache_page

        urlpatterns = [
            url(r‘^foo/([0-9]{1,2})/$‘, cache_page(60 * 15)(my_view)),
        ]
技术图片
技术图片

 

  • 局部视图缓存
技术图片
技术图片
a. 引入TemplateTag

        {% load cache %}

b. 使用缓存

        {% cache 5000 缓存key %}
            缓存内容
        {% endcache %}
技术图片

 

session and cooike

标签:导致   ddl   组成   安全性   丢失   二进制   standard   ref   mem   

原文地址:https://www.cnblogs.com/oner-xd/p/10608585.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!