zoukankan      html  css  js  c++  java
  • Flask框架(二)

    一、请求扩展

    1.1befor_request

    1.app.befor_request装饰过得函数在真正的响应函数之前执行,可以有多个,当有多个的时候,执行顺序是谁在前面谁先执行。

    2.只要一个befor_request有返回值,那后面所有的befor_request都不会执行,真正的响应函数也不会执行。

    3.被befor_request装饰的函数可以没有return。

    from  flask import Flask,render_template
    
    app = Flask(__name__)
    
    @app.before_request
    def befor_process():
        print("befor_process0")
        return "tttt"
    
    @app.before_request
    def befor_process():
        print("befor_process1")
    
    
    if __name__ == '__main__':
        #app()--->app.__call__
        app.__call__
        app.run()
    

    1.2after_request

    1.after_request是在真正响应函数之后执行的。

    2.它可以有多个,当为多个的时候执行顺序为,谁在前面谁后执行。

    3.befor_request有没有返回值都不会影响after_resquest的执行。

    4.after_resquest装饰的函数必须接收参数,还要把参数返回否则报错

    @app.after_request
    def after_process(response):
    
        print("我是请求之后1")
        return  response
    
    @app.after_request
    def after_process1(response):
        print("我是请求之后2")
        return  response
    

    1.3before_first_request

    before_first_request在整个项目启动后第一次接收到请求时会执行,以后不会执行

    #这个是整个项目启动后第一次接收到请求,就会执行这个方法。以后不会执行
    @app.before_first_request
    def first():
        print(123)
    
    

    1.4teardown_request

    不管有没有错误,都会执行teardown_request,如果没有错误,错误值为None;如果有错误,直接建报错信息传递给改被装饰的函数。

    @app.teardown_request
    def tre(e):
        print("e",e)
    

    1.5errhandler(错误码)

    如果没有该错误码的错误出现,则被装饰的函数不会执行,如果有错误码的错误出现
    就会执行被装饰的函数,不会让错误暴露给用户,相当于Django的报错页面。

    @app.errorhandler(500)
    def error_handler(*args,**kwargs):
        print(*args,**kwargs)
        return "500错误"
    
    @app.errorhandler(404)
    def errrrrr(e):
        return render_template("index404.html")#此处可以返回一个页面
    

    1.6template_global()

    全局模板标签,被装饰的函数可以在模板中作为全局的标签使用,在模板中可以直接调用。

    @app.template_global()
    def sb(a1,a2,a3):
        return a1+a2+a3
    

    调用方式:

    {{sb(1,2,3)}}#与Django类似
    

    1.7template_filter()

    全局模板过滤器,可以在模板中作为全局过滤器使用,在模板中可以直接调用。

    @app.template_filter()
    def sb1(a1,a2,a3):
        return a1+a2+a3
    

    调用方式(注意同template_global的区别) :

    {{1|add_filter(2,3,4)}}
    

    全局模板标签和全局模板过滤器简化了需要手动传一个函数给模板调用。

    二、flask中间件

    flask的中间件的性质,就是可以理解为在整个请求的过程的前后定制一些个性化的功能。

    flask的中间件的实现案例:

    from flask import Flask
    
    app = Flask(__name__)
    
    @app.route('/')
    def index():
        print('视图函数中')
        return 'hello world'
    
    class my_middle:
    
        def __init__(self,wsgi_app):
            self.wsgi_app = wsgi_app
    
        def __call__(self, *args, **kwargs):
            print('中间件的代码上')
            obj = self.wsgi_app( *args, **kwargs)
            print('中间件的代码下')
    
            return obj
    
    if __name__ == '__main__':
       
        app.wsgi_app = my_middle(app.wsgi_app)
         # app.wsgi_app(environ, start_response)
        app.run()
        # 梳理一下 根据werkzeug我们可以知道 每次请求必然经历了app()
        # 所以我们要查看Flask的源码找到__call__方法
        # 找到了__call__方法后发现执行了return self.wsgi_app(environ, start_response)
        # 然后flask里面所有的内容调度都是基于这个self.wsgi_app(environ, start_response),这就是就是flask的入口
        # 如何实现中间件呢? 原理上就是重写app.wsgi_app,然后在里面添加上一些自己想要实现的功能。
        # 首先分析  app.wsgi_app需要加括号执行  所以我们把app.wsgi_app做成一个对象,并且这个对象需要加括号运行
        # 也就是会触发这个对象的类的__call__()方法
        # 1 那么就是app.wsgi_app=对象=自己重写的类(app.wsgi_app) ,我们需要在自己重写的类里面实现flask源码中的app.wsgi_app,在实例化的过程把原来的app.wsgi_app变成对象的属性
        # 2         app.wsgi_app() =对象() = 自己重写的类.call()方法
        # 3         那么上面的代码就可以理解了,在自己重写的类中实现了原有的__call__方法
    

    梳理

    • 根据werkzeug我们可以知道 每次请求必然经历了app()
    • 所以我们要查看Flask的源码找到__call__方法
    • 找到了Flask的__call__方法后发现执行了return self.wsgi_app(environ, start_response)
    • flask里面所有的内容调度都是基于这个self.wsgi_app(environ, start_response),这就是就是flask的入口,也就是self是app,也就是app.wsgi_app(environ, start_response)为程序的入口。
    • 如何实现中间件呢? 原理上就是重写app.wsgi_app,然后在里面添加上一些自己想要实现的功能。
    • 首先分析 app.wsgi_app需要加括号执行 所以我们把app.wsgi_app做成一个对象,并且这个对象需要加括号运行。
    • 也就是会触发这个对象的类的__call__()方法。

    实操理解

    1. app.wsgi_app=对象=自己重写的类(app.wsgi_app)

      提示:我们需要在自己重写的类里面实现flask源码中的app.wsgi_app,在实例化的过程把原来的 app.wsgi_app变成对象的属性

    2. app.wsgi_app() =对象() = 自己重写的类.call()方法

      app.wsgi_app(实参) =对象(实参) = 自己重写的类.call(实参)方法

    3. 那么上面的代码就可以理解了,在自己重写的类中实现了原有的call方法,并且重新调用了原有的app.wsgi_app

    三、flask蓝图

    3.1蓝图的介绍与使用

    蓝图用来对程序的目录进行划分,比如下面的代码很乱就需要蓝图进行管理:

    from flask import Flask
    
    app = Flask(__name__)
    
    @app.route('/login/')
    def login():
        return "login"
    
    @app.route('/logout/')
    def logout():
        return "logout"
    
    @app.route('/add_order/')
    def add_order():
        return "add_order"
    
    @app.route('modify_order')
    def modify_order():
        return "modify_order"
    
    
    if __name__ == '__main__':
        app.run()
    

    项目目录:

    -templates
    -static
    -views
        -user.py
        -order.py
    -app.py
    

    views/user.py

    from flask import Blueprint
    
    # 1 创建蓝图
    user_bp = Blueprint('user',__name__)
    
    # 2 利用蓝图创建路由关系
    @user_bp.route('/login/')
    def login():
        return "login"
    
    @user_bp.route('/logout/')
    def logout():
        return "logout"
    

    views/order.py

    from flask import Blueprint
    
    order_bp = Blueprint('order',__name__)
    
    @order_bp.route('/add_order/')
    def add_order():
        return "add_order"
    
    @order_bp.route('/modify_order/')
    def modify_order():
        return "modify_order"
    

    app.py

    from flask import Flask
    from views.user import user_bp
    from views.order import order_bp
    
    app = Flask(__name__)
    # 3 注册蓝图
    app.register_blueprint(user_bp)
    app.register_blueprint(order_bp)
    
    
    if __name__ == '__main__':
        app.run()
    

    访问:

    1552424766233

    观察views/user.py

    • 我们可以把所有的视图函数抽出来多个文件。

    • 在这里我们通过user_bp = Blueprint('user',__name__)创建一个蓝图对象

      参数讲解:

      • user_bp :是用于指向创建出的蓝图对象,可以自由命名。
      • Blueprint的第一个参数自定义命名的‘user’用于url_for翻转url时使用。
      • __name__用于寻找蓝图自定义的模板和静态文件使用。
    • 蓝图对象的用法和之前实例化出来的app对象用法很像,可以进行注册路由。

    观察app.py

    • 这里我们需要手动的去注册一下蓝图,才会建立上url和视图函数的映射关系。

    关键词:

    1. 创建蓝图

      user_bp = Blueprint('user',__name__)

    2. 利用蓝图创建路由关系

      @bp.route('/login/')
      def login():
      return "login"

    3. 注册蓝图
      app.register_blueprint(bp)

    3.2蓝图的高级使用(重点内容)

    3.2.1蓝图中实现path部分的url前缀

    创建蓝图的时候填写url_prefix可以为增加url的path部分的前缀,可以更方便的去管理访问视图函数。

    from flask import Blueprint
    
    # 1 创建蓝图
    user_bp = Blueprint('user',__name__,url_prefix='/user')
    # 注意斜杠跟视图函数的url连起来时候不要重复了。
    

    1552425130691

    注意:

    1. 斜杠跟视图函数的url连起来时候不要重复了。

      图解:

      1552426158515

    2.url加前缀的时候也可以再注册蓝图的时候加上,更推荐这么做,因为代码的可读性更强。

    app.register_blueprint(user_bp,url_prefix='/order')
    

    3.2.2蓝图中自定义模板路径

    创建蓝图的时候填写template_folder可以指定自定义模板路径

    # 1 创建蓝图                                           #所对应的参数路径是相对于蓝图文件的
    user_bp = Blueprint('user',__name__,url_prefix='/user',template_folder='views_templates')
    

    注意

    1. 蓝图虽然指定了自定义的模板查找路径,但是查找顺序还是会先找主app规定的模板路径(templates),找不到再找蓝图自定义的模板路径。

    2. Blueprinttemplate_folder参数指定的自定义模板路径是相对于蓝图文件的路径。

      图解:

      (01)

      1552425776786

      (02)

      1552425616132

    3.2.3蓝图中自定义静态文件路径

    创建蓝图的时候填写static_folder可以指定自定义静态文件的路径

    user_bp = Blueprint('user',__name__,url_prefix='/user',template_folder='views_templates',
                        static_folder='views_static')
    

    注意:

    1. 在模板中使用自定义的静态文件路径需要依赖url_for()
    2. 下节讲解如何在模板中应用蓝图自定义的静态文件。

    3.2.4url_for()翻转蓝图

    视图中翻转url:
    url_for('创建蓝图时第一个参数.蓝图下的函数名')
    # 如:
    url_for('user.login')
    
    模板中翻转url:
    {{ url_for('创建蓝图时第一个参数.蓝图下的函数名') }}
    # 如:
    {{ url_for('user.login') }}
    
    模板中应用蓝图自定义路径的静态文件:
    {{ url_for('创建蓝图时第一个参数.static',filename='蓝图自定义静态文件路径下的文件') }}
    # 如:
    {{ url_for('user.static',filename='login.css') }}
    

    3.2.5蓝图子域名的实现

    创建蓝图的时候填写subdomain可以指定子域名,可以参考之前注册路由中实现子域名。

    (1) 配置C:WindowsSystem32driversetchosts

    127.0.0.1 bookmanage.com
    127.0.0.1 admin.bookmanage.com
    

    (2)给app增加配置

    app.config['SERVER_NAME'] = 'bookmanage.com:5000'
    

    (3)创建蓝图的时候添加子域名 subdomain='admin'

    # 1 创建蓝图                                           
    user_bp = Blueprint('user',__name__,url_prefix='/user',template_folder='views_templates',
                        static_folder='views_static',subdomain='admin')
    
    
    # 2 利用蓝图创建路由关系
    @user_bp.route('/login/')
    def login():
        return render_template('login_master.html')
    

    (4) 访问admin.bookmanage.com:5000/user/login/

    1552428071447

    3.2.6蓝图中使用自己请求扩展

    在蓝图中我们可以利用创建好的蓝图对象,设置访问蓝图的视图函数的时候触发蓝图独有的请求扩展。

    例如:

    order_bp = Blueprint('order',__name__)
    
    
    @order_bp.route('/add_order/')
    def add_order():
        return "add_order"
    
    @order_bp.before_request
    def order_bp_before_request():
        return '请登录'
    

    注意:

    • 只有访问该蓝图下的视图函数时候才会触发该蓝图的请求扩展。
    • 可以这么理解:相当app的请求扩展是全局的,而蓝图的请求扩展是局部的只对本蓝图下的视图函数有效。

    3.3使用蓝图之中小型系统

    目录结构:

    -flask_small_pro
        -app01
            -__init__.py
            -static
            -templates
            -views
                -order.py
                -user.py
         -manage.py 
            
    __init__.py
    from flask import Flask
    from app01.views.user import user_bp
    from app01.views.order import order_bp
    
    
    app = Flask(__name__)
    
    app.register_blueprint(user_bp,url_prefix='/user')
    app.register_blueprint(order_bp)
    

    user.py

    from flask import Blueprint
    
    user_bp = Blueprint('user',__name__)
    
    
    @user_bp.route('/login/')
    def login():
        return 'login'
    
    @user_bp.route('/logout/')
    def logout():
        return 'logout'
    

    order.py

    from flask import Blueprint
    
    order_bp = Blueprint('order',__name__)
    
    @order_bp.route('/add_order/')
    def add_order():
        return 'buy_order'
    
    
    @order_bp.route('/modify_order/')
    def modify_order():
        return 'modify_order'
    

    manage.py

    from app01 import app
    
    
    if __name__ == '__main__':
        app.run()
    

    3.4使用蓝图之使用大型系统

    这里所谓的大型系统并不是绝对的大型系统,而是相对规整的大型系统,相当于提供了一个参考,在真实的生成环境中会根据公司的项目以及需求,规划自己的目录结构。

    文件路径:

    │  run.py  
    │
    │
    └─pro_flask  # 文件夹
        │  __init__.py 
        │
        ├─admin  # 文件夹
        │  │  views.py
        │  │  __init__.py
        │  │
        │  ├─static # 文件夹
        │  └─templates  # 文件夹
        │
        └─web   # 文件夹
           │  views.py
           │  __init__.py
           │
           ├─static  # 文件夹
           └─templates # 文件夹
        
        
    

    run.py 启动app

    from pro_flask import app
    
    if __name__ == '__main__':
        app.run()
    

    __init__.py 实例化核心类,导入蓝图对象,注册蓝图。

    from flask import Flask
    from .admin import admin
    from .web import web
    
    app = Flask(__name__)
    app.debug = True
    
    app.register_blueprint(admin, url_prefix='/admin')
    app.register_blueprint(web)
    

    admin.views.py 完成注册路由以及视图函数

    from . import admin
    
    
    @admin.route('/index')
    def index():
        return 'Admin.Index'
    

    admin.__init__.py 生成蓝图对象导入views,使得views的代码运行完成注册路由

    from flask import Blueprint
    
    admin = Blueprint(
        'admin',
        __name__,
        template_folder='templates',
        static_folder='static'
    )
    from . import views
    

    web文件夹下和admin文件夹下目录构成完全一致,这里就不举例子了。

    四、flask之协程和线程

    假设每一个请求进来的时候都开一个进程肯定不合理,那么如果每一个请求进来都是串行的根本实现不了并发,所以我们假定每一个请求进来使用的是线程。但是线程中数据互相不隔离,存在修改数据的时候数据不安全的问题。

    假定我们的需求是,每个线程都要设置值,并且该线程打印该线程修改的值。

    from threading import Thread,current_thread
    import time
    
    class Foo(object):
        def __init__(self):
            self.name = 0
    
    locals_values = Foo()
    
    def func(num):
        locals_values.name = num
        time.sleep(2)             # 取出该线程的名字
        print(locals_values.name, current_thread().name)
    
    for i in range(10):
                                        # 设置该线程的名字
        t = Thread(target=func,args=(i,),name='线程%s'%i)
        t.start()
    

    很明显阻塞了2秒的时间所有的线程都完成了修改值,而2秒后所有的线程打印出来的时候都是9了,这就产生了数据不安全的问题。

    1552975111079

    所以我们要解决这种线程不安全的问题,有如下两种解决方案。

    • 方案一:是加锁

    • 方案二:使用threading.local对象把要修改的数据复制一份,使得每个数据互不影响。

      我们要实现的并发是多个请求实现并发,而不是纯粹的只是修改一个数据,所以第二种思路更适合做我们每个请求的并发,把每个请求对象的内容都复制一份让其互相不影响。

      详解:为什么不用加锁的思路?加锁的思路是多个线程要真正实现共用一个数据,并且该线程修改了数据之后会影响到其他线程,更适合类似于12306抢票的应用场景,而我们是要做请求对象的并发,想要实现的是该线程对于请求对象这部分内容有任何修改并不影响其他线程。所以使用方案二

    4.1threading.local

    多个线程修改同一个数据,复制多份数据给每个线程用,为每个线程开辟一块空间进行数据存储

    实例:

    from threading import Thread,current_thread,local
    import time
    
    locals_values = local()
    # 可以简单理解为,识别到新的线程的时候,都会开辟一片新的内存空间,相当于每个线程对该值进行了拷贝。
    
    def func(num):
        locals_values.name = num
        time.sleep(2)
        print(locals_values.name, current_thread().name)
    
    for i in range(10):
        t = Thread(target=func,args=(i,),name='线程%s'%i)
        t.start()
    

    1552976228090

    如上通过threading.local实例化的对象,实现了多线程修改同一个数据,每个线程都复制了一份数据,并且修改的也都是自己的数据。达到了我们想要的效果。

    4.2通过字典自定义threading.local

    实例:

    from threading import get_ident,Thread,current_thread
    # get_ident()可以获取每个线程的唯一标记,
    import time
    
    class Local(object):
        storage = {}# 初始化一个字典
        get_ident = get_ident # 拿到get_ident的地址
        def set(self,k,v):
            ident =self.get_ident()# 获取当前线程的唯一标记
            origin = self.storage.get(ident)
            if not origin:
                origin={}
            origin[k] = v
            self.storage[ident] = origin
        def get(self,k):
            ident = self.get_ident() # 获取当前线程的唯一标记
            v= self.storage[ident].get(k)
            return v
    
    locals_values = Local()
    def func(num):
        # get_ident() 获取当前线程的唯一标记
        locals_values.set('KEY',num)
        time.sleep(2)
        print(locals_values.get('KEY'),current_thread().name)
    
    for i in range(10):
        t = Thread(target=func,args=(i,),name='线程%s'%i)
        t.start()
    

    利用get_ident()获取每个线程的唯一标记作为键,然后组织一个字典storage。

    :{线程1的唯一标记:{k:v},线程2的唯一标记:{k:v}.......}

     {
        15088: {'KEY': 0}, 
        8856: {'KEY': 1},
        17052: {'KEY': 2}, 
        8836: {'KEY': 3}, 
        13832: {'KEY': 4}, 
        15504: {'KEY': 5}, 
        16588: {'KEY': 6}, 
        5164: {'KEY': 7}, 
        560: {'KEY': 8}, 
        1812: {'KEY': 9}
                        }
    

    运行效果

    1552981453617

    4.3通过setattr和getattr实现自定义threthreading.local

    实例

    from threading import get_ident,Thread,current_thread
    # get_ident()可以获取每个线程的唯一标记,
    import time
    
    class Local(object):
        storage = {}# 初始化一个字典
        get_ident = get_ident # 拿到get_ident的地址
    
        def __setattr__(self, k, v):
            ident =self.get_ident()# 获取当前线程的唯一标记
            origin = self.storage.get(ident)
            if not origin:
                origin={}
            origin[k] = v
            self.storage[ident] = origin
        def __getattr__(self, k):
            ident = self.get_ident() # 获取当前线程的唯一标记
            v= self.storage[ident].get(k)
            return v
    
    locals_values = Local()
    def func(num):
        # get_ident() 获取当前线程的唯一标记
        locals_values.KEY=num
        time.sleep(2)
        print(locals_values.KEY,current_thread().name)
    
    for i in range(10):
        t = Thread(target=func,args=(i,),name='线程%s'%i)
        t.start()
    

    提示:

    4.4每个对象有自己的存储空间(字典)

    我们可以自定义实现了threading.local的功能,但是现在存在一个问题,如果我们想生成多个Local对象,但是会导致多个Local对象所管理的线程设置的内容都放到了类属性storage = {}里面,所以我们如果想实现每一个Local对象所对应的线程设置的内容都放到自己的storage里面,就需要重新设计代码。

    实例:

    from threading import get_ident,Thread,current_thread
    # get_ident()可以获取每个线程的唯一标记,
    import time
    
    class Local(object):
        def __init__(self):
            # 千万不要按照注释里这么写,否则会造成递归死循环,死循环在__getattr__中,不理解的话可以全程使用debug测试。
            # self.storage = {}
            # self.get_ident =get_ident
            object.__setattr__(self,"storage",{})
            object.__setattr__(self,"get_ident",get_ident) #借用父类设置对象的属性,避免递归死循环。
    
        def __setattr__(self, k, v):
            ident =self.get_ident()# 获取当前线程的唯一标记
            origin = self.storage.get(ident)
            if not origin:
                origin={}
            origin[k] = v
            self.storage[ident] = origin
        def __getattr__(self, k):
            ident = self.get_ident() # 获取当前线程的唯一标记
            v= self.storage[ident].get(k)
            return v
    
    locals_values = Local()
    locals_values2 = Local()
    def func(num):
        # get_ident() 获取当前线程的唯一标记
        # locals_values.set('KEY',num)
        locals_values.KEY=num
        time.sleep(2)
        print(locals_values.KEY,current_thread().name)
        # print('locals_values2.storage:',locals_values2.storage) #查看locals_values2.storage的私有的storage
    
    for i in range(10):
        t = Thread(target=func,args=(i,),name='线程%s'%i)
        t.start()
    

    显示效果我们就不做演示了,和前几个案例演示效果一样。

    4.5如何设计flask的请求并发?

    • 情况一:单进程单线程,基于全局变量就可以做

    • 情况二:单进程多线程,基于threading.local对象做

    • 情况三:单进程多线程多协程,如何做?

      提示:协程属于应用级别的,协程会替代操作系统自动切换遇到 IO的任务或者运行级别低的任务,而应用级别的切换速度远高于操作系统的切换

      当然如果是自己来设计框架,为了提升程序的并发性能,一定是上诉的情况三,不光考虑多线程并且要多协程,那么该如何设计呢?

      在我们的flask中为了这种并发需求,依赖于底层的werkzeug外部包,werkzeug实现了保证多线程和多携程的安全,werkzeug基本的设计理念和上一个案例一致,唯一的区别就是在导入的时候做了一步处理,且看werkzeug源码。

      werkzeug.local.py部分源码

      ...
      
      try:
          from greenlet import getcurrent as get_ident # 拿到携程的唯一标识
      except ImportError:
          try:
              from thread import get_ident #线程的唯一标识
          except ImportError:
              from _thread import get_ident
      
      class Local(object):
          ...
      
          def __init__(self):
              object.__setattr__(self, '__storage__', {})
              object.__setattr__(self, '__ident_func__', get_ident)
      
            ...
      
          def __getattr__(self, name):
              try:
                  return self.__storage__[self.__ident_func__()][name]
              except KeyError:
                  raise AttributeError(name)
      
          def __setattr__(self, name, value):
              ident = self.__ident_func__()
              storage = self.__storage__
              try:
                  storage[ident][name] = value
              except KeyError:
                  storage[ident] = {name: value}
      

      讲解:

      原理就是在最开始导入线程和协程的唯一标识的时候统一命名为get_ident,并且先导入协程模块的时候如果报错说明不支持协程,就会去导入线程的get_ident,这样无论是只有线程运行还是协程运行都可以获取唯一标识,并且把这个标识的线程或协程需要设置的内容都分类存放于__storage__字典中。

    五、偏函数

    当函数的参数个数太多,需要简化时,使用functools.partial可以创建一个新的函数,这个新函数可以固定住原函数的部分参数,从而在调用时更简单。

    from functools import partial
    def func(a1,a2,a3):
        print(a1,a2,a3)
    
    
    new_func1 = partial(func,a1=1,a2=2)
    new_func1(a3=3)
    
    new_func2 = partial(func,1,2)
    new_func2(3)
    
    new_func3 = partial(func,a1=1)
    new_func3(a2=2,a3=3)
    

    注意:partial括号内第一个参数是原函数,其余参数是需要固定的参数

    效果图

    1553002020199

  • 相关阅读:
    Asp.net弹出浏览器客户端确认对话框代码 Carlwave
    VS 2005 与SQL Server 2005整合优势在哪里?(from csdn.net) Carlwave
    如何让搜索引擎收录我的站点 Carlwave
    超强扩展性的DNNDotNetNuke模块功能分类列表(from 中国DNN) Carlwave
    DotNetNuke命名空间概述 Carlwave
    Most Popular Questions and Answers on ASP.NET Whidbey(from asp.net forums,write by ASP.NET Team) Carlwave
    火箭官方宣告麦蒂缺阵五周 季后赛前景蒙上阴影 Carlwave
    asp.net有效使用缓存(转) Carlwave
    《Business Rules Engine Overview》《业务规则引擎概述》write by Mark Kamoski Carlwave
    中国详细省市县自治区名称列表(含access数据库和sql2000备份数据库) Carlwave
  • 原文地址:https://www.cnblogs.com/ghylpb/p/12390980.html
Copyright © 2011-2022 走看看