Flask,web框架学习_持续更新

《Flask web 开发:基于python》

Always believe that something wonderful is about to happen

第1章 安装

1.1virtualenv模块安装

#在shell窗口
easy_install virtualenv #即可
或者
pip isntall virtualenv 
#查看版本
virtualenv --version

1.2创建虚拟环境venv 及激活####

virtualenv venv#venv为名字,可随意改,但常用venv
#在目录下
venv\scripts\activate #注意是反斜杠,是地址

1.3在虚拟环境中作业

后面的所有安装模块及操作,都在虚拟环境中执行!虚拟环境就是一个独立的环境,将我们所有的文件都集中在一个世界里面,尤其是在有很多不同办法的应用、模块时,你会知道的虚拟环境的友好。

第2章 程序结构

2.1程序初始化####

import flask import Flask
app=Flask(__name__)
#在Flask类中创建一个叫做app的实例,__name__是程序主模块或包的名字,在这里作为变量

2.2路由和视图函数

路由:一个处理url和函数之间映射关系的程序。

装饰器/修饰器:一个用来接受函数的函数,并且返回一个函数,如@app.route(),修饰器的惯用做法,就是将函数注册为事件的处理程序。

@app.route("/") #此处装饰器就是将index()函数绑定一个url
def index():
	return"Hello world!"
#把index()函数注册为根目录的处理程序


@app.route("/user/<name>")
def index():
	return"hello,%s" %name
#“动态路由”

2.3启动程序

实例的.run()方法来启动程序,调试模式的话则设置debug=True:

if __name__="main":
	app.run(debug=True)
	#app.debug=True
	#app.run()
#调试模式下,浏览器在脚本修改代码后并保存的同时,自动重新载入,并且在发生错误的时候,提供一个调节器

以上三步骤就可以构造一个功能最简单的flask程序了,重点是理解修饰器的实质含义

2.5请求响应循环

介绍flask的设计理念

1.程序和请求上下文:flask使用上下文临时将某些对象变成全局可访问——一个线程中的全局访问,不会干扰其他线程的。

from flask import request

@app.route('/')
def index():
user_agent = request.headers.get('User-Agent')
return '<p>Your browser is %s</p>' % user_agent
#request变量,这是一个请求上下文。只有当请求被推送之后,request才会有意义,才可以使用request,否则就会报错,因为缺少上下文。

程序上下文和请求上下文:有种“环境”意味,解释见上面。

2.请求调度:就是找到某个请求的视图函数。

3.请求钩子:为了避开视图函数使用重复代码,加一个“钩子”,这个钩子关联到一个函数,在请求发送到视图函数之前、之后调用

4.响应:响应就是flask调用视图函数后,返回的一个值或者一个页面,也有在重定向响应中,返回的是一个新的地址链接。

第3章 模板

3.1 jinja2模板引擎

在安装jinja2后,将其应用到程序中,jinja2有一套自己的模板和语法,其主要作用是控制HTML响应、显示效果以及程序和页面之间信息交互。

1渲染模板:在通用骨架(即模板)中,传入我们个性的信息,最终展现出来。

from flask import Flask,render_template#导入render_template函数
#...
@app.route("/user/<name>")
def user(name):
	return render_template("user.html",name=name)
#传入name变量实际值到user.html中
#render_template()函数第一个变量第模板名字,第二个是键值对

2变量
模块中占位符{{name}}表示一个变量,告诉模板引擎,这个位置的值从渲染模板时,使用的数据中获取并替代。

3控制结构:即是jinja2提供的,用来改变渲染流程,有以下几种。

1条件控制语句

{% if user %}
	Hello,{{user}}!
{% else %}
	Hello,stranger!
{% endif %}

2将多次重复的代码写成一个单独的文件中,例如common.html,使用包含命令,传到其他文件中,避免重复

{% include "common.html"%}
#...

3继承,也是重复使用代码,先建立基模板,再建立衍生模板,基模板的某些元素可以在衍生模板中更改。

#基模板base.html
#只有block标签定义的元素才可在衍生模板总更改
</html>
<head>
    {% block head %}
    <title>{% block title %}{% endblock%} - my application</title>
    {% endblock%}
</head>
<body>
    {% block body%}
    {% endblock%}
</body>
</html>

#衍生模板
{% extends "base.html"%}#继承基模板
{% block title %}Index{% endblock %}#直接加入需要更改的元素
{% block head %}
    {{super()}} #head元素中仅仅改了title,所以剩下的还是照旧,就super()来获取原来的内容   
{% endblock%}
{% block body %}
    Hello,World!
{% endblock %}

从以上三种控制方法,得出以下结论

  • 控制结构基本形式{% xxx %},条件控制,则是比常见的if从句多一个结尾
  • 控制结构语句都是有始有终,不管是{% endif %}还是
  • 继承方法中,block标签是更改元素的关键。

3.2使用flask-bootstrap集成Twitter-BootStrap

BootStrap是一个Twitter开发的开源框架,为了在程序中集成BootStrap,我们使用一个叫做flask-bootstap的扩展。

通过初始化化bootstrap=BootStrap(app),即可将基模板为我所用,利用模板继承{% extends "bootstrap/base.html"%},就可以定义出统一页面了。

3.3自定义错误页面

常见的错误有路由错误404和未处理的异常500,flask允许我们基于模板自定义错误页面:

@app.errorhandler(404)
	def page_not_found(e):
		return render_template("404.html"),404

@app.errorhandler(500)
	deg internal_serner_error(e):
		return render_template("500.html"),500

Thinking

这里代码与前几章定义index.html代码进行对比,有显著的差别,index.html使用@app.route("/")修饰器注册,而这里是好像是一个“类”,@app.errorhandler(404),代表是访问的路由不存在这类情况,即可响应404.html,不涉及到"路由"功能。

3.4链接

用的最多就是导航条了,链接到的有相对地址绝对地址,常用的是url_for()函数,最简单的用法是将函数名(不带后缀类型)作为参数,生成对应的url。还有,用此函数生成动态地址,将动态部分作为关键字参数传入。

url_for("index")#注意不是index.html,这里不需要后缀
url_for("index",_external=True)#返回绝对地址
url_for("user",name=name)#生成动态url链接

3.5 静态文件

常见静态文件有HTML中使用的图片、JavaScript源代码文件和CSS。

{#定义收藏夹图标#}
{% block head %}
{{ super() }}{#保存定义块的原始内容#}
<link rel="shortcut icon " href="{{ url_for("static",filename = "favicon.ico")}}" type="image/x-icon">
<link rel="icon"  href="{{ url_for("static",filename = "favicon.ico")}}" type="image/x-icon">
{% endblock %}

3.6使用flask-moment本地化时间和日期

网络上有个统一的时间,UTC,universal time coordinated协调世界时间,我们将之转为本机时间。flask-moment依靠着moment.js和jquery.js库来实现功能。二者都是在HTML引入这两个库。

要实现显示时间功能,1引入时间变量datetime,2在基模板中引入moment.js库,3在衍生模板中渲染时间戳。时间渲染详见momentjs介绍

步骤

#安装模块及初始化
pip install flask-moment#安装模块

from flask.ext.moment import Moment
moment=Moment(app)#初始化flask-moment

#template/base.html,引入moment.js库
{# 本地化时间 之引入moment.js,jquery在bootstrap中自动引入了 #}
% block scripts %}
{{ super() }}
{{ moment.include_moment() }}
{% endblock %}

#hello.py	加入datetime变量
from datetime import datetime
@app.route()
	def index():
		return render_template("index.html",current_time=datetime.utcnow())

#template/index.html,使用flask-moment渲染时间戳
<p>The local data and time is {{ moment(current_time).format("LLLL") }}.</p>{#时间显示#}
<p>That was {{ moment(current_time).fromNow(refresh=True) }}</p>
<p>{{ moment.lang("ch") }}</p>#涉及到语言

第4章 web表单

什么叫做表单?每个web表单都由一个继承自Form的类表示。通俗来说,一个表单经过渲染为HTML后,就是我们看到网页上有交互功能、能键入信息的框框。
flask-wtf 扩展非常适用于表单处理

4.1表单类

新建一个表单,WTForms支持的HTML标准字段及內建的验证函数种类很多,见书本列表。

from flask_wtf import Form
from wtforms import StringField,SubmitField,PasswordField
from wtforms.validators import Required,Length,Email#验证函数
def NameForm(Form):
	name=StringField("your name:",validators=[Required])#名字非空,这是一个字段
	password=PasswordField("your password",validators=[Required(),Length(6)])#非空、长度不少于6
	submit=SubmitField("Submit")

4.2把表单渲染为HTML格式

利用Bootstrap中预先定义好的表单样式渲染整个Flask-WTF表单

{% import "bootstrap/wtf.html" as wtf %}{#此处是导入模块来渲染表单,就是将表单渲染为HTML样式#}
{% wtf.quick_form(form)%}

4.3在视图函数中处理表单

表单中的数据,传递给视图函数,显示具有个性的消息

@app.route("/",methods=["GET","POST"])
	def index():
		name=None #自定义变量name,通过赋值None来获得
		form=NameFlaskForm()#form是对象、实例
		if form.valitate_on_submit(): #form.valitate_on_submit()要是填写正确,返回True.
			name=form.name.data #name在前面自定义时,是在if外面
			form.name.data=""
return render_template("index.html",name=name,form=form)#index.html中的变量name,form接受传入的参数值,name=name,前面的name是变量。

4.4重定向和会话

重定向就是为了避免提交表单刷洗,会提醒再次确认的现象发生

会话session主要是用在重定向中,在请求之间,记住数据,session["name"]=form.name.data,name=session.get("name")可以看出来,session好比一个字典,用来储存我们的信息。

4.5Flash消息

请求完成后,有时候需要让用户知道了状态的变化,比如显示确认、警告或者错误from flask import flash。书中例子是在视图函数总添加提醒出来的条件,在模板文件中设置提醒样式即可。

第5章 数据库

使用Flask-SQLAlchemy管理数据库,在学习之余结合《SQL必知必会》终于理解了这一章节,这一张内容大致如下:

  • 关系型数据库的特点,创建模型、关系
  • 在shell中使用flask-SQLAlchemy管理数据库的基本操作,创建表、行及删除行、查询
  • 进阶为在视图函数中操作数据库,将视图函数填入的form.name.data加入数据库,进而针对不对访问对象有不同的欢迎信息
  • 对数据库的迁移migrate、更新和维护等。

5.1常用语法
初始化及简单配置一个SQLite数据库:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app=Flask(__name__)

basedir=os.path.dirname(os.path.abspath(__file__))
#获得当前文件的绝对目录,想一下file为啥不能改为name?_file__ 是用来获得模块所在的路径,固有的“属性”吧
app.config["SQLALCHEMY_DATABASE_URI"]="sqlite:///"+os.path.join(basedir,"shiyanku.sqlite")
#config[]字典配置URI,而不是URL,os.path.join(a,b)用来组合路径的,文件名为shiyanku.sqlite
app.config["SQLALCHEMY_COMMIT_ON_TEARDOWN"]=True
#自动提交数据库
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"]=True
#书中无本此语句,自己根据运行bug添加

db=SQLAlchemy(app)
#SQLAlchemy实例

5.2定义模型:模型就是持久化实体,模型一般是python的类,类中的属性就是数据表的列。

class Role(db.Model):
	__table__="roles" #模型中数据表的名称
	id=db.Column (db.Integer,primary_key=True) #id列,为主键
	name=db.Column(db.String(64),unique=True)#name列,有唯一性
	
	def __repr__(self): #定义一个方法,返回具有可读性的字符串表示模型,具体解释详见下面链接
		return "Role:%r" %self.name
  • __repr__不管有没有print 在前面,都会显示我们想要的内容,而不是内存地
  • 注意列Colume(类型,选项)的类型和选项来定义列属性,比如整数/字符串,主键/唯一性等等

__repr__和__str__方法区别

5.3关系
关系型数据库就是利用关系将不同表中的行联系起来,为我所用。

“一个角色role可以多个用户user,但是一个用户只能是一个角色”一对多的关系如下:

class Role(db.Model):
#...
users = db.relationship("User",backref="role",lazy="dynamic")#lazy指明如何加载相关记录
#backref反向引用,这一语句意思:一个users列表,是一个相同role角色的列表。

class User(db.Model):
#...
role_id=db.Column(db.Integer,db.ForeignKey("roles.id"))、
#建立role_id外键列,用来“一个用户只能一个角色”来映射roles的id列

5.4数据库操作

上面讲数据库完成了配置,下面就是在Pythonshell中操作数据库

创建表

在pythonshell中输入:
>>>from hello import db #hello为hello.py
>>>db.create_all() 
#这个时候就在目录下新建一个xxx.sqlite文件,xxx是在配置中指定的。
#删除表格:db.drop_all()

新建行

>>>from hello import Role,User
>>>admin_role=Role(name="admin")
>>>user_role=Role(name="user")
>>>user_lengqian=User(username="lengqian",role="user_role")
>#...
#role="user_role",role在关系中赋予User模型role属性(backref="role")(小九九:role这个统一赋值的话,在后期就可以容易的列出同一个角色的用户列表...)
#注意,这些数据只是存在python中还未写入数据库
>>>db.session.add(admin_role)
>>>db.session.add(user_lengqian)
#或者简写如下:
>>>db.session.add_all([admin_role,user_lengqian])

再次确认提交:
>>>db.session.commit()

修改、删除行

   #修改为直接修改重命名
>>>admin_role.name="adminstrator"
>>>db.session.add(admin_role)
>>>db.session.commit()
 #删除行
>>>db.session.delete(admin_role)
>>>db.sessiom.commit()

查询行:

Flask-SQLAlchemy为每个模型类都提供了query对象?(方法吧)。配合使用过滤器进行精确筛选

常见过滤器,有filter_by(条件),filter(),order_by(条件)按条件进行排序返回一个新查询

常见查询执行函数,有all(),first(),count(),get()等

User.query.all()
#返回的是一个列表[<User u"lengqian">,<...>]

User.query.filter_by(username="lengqian").all()
#返回的是一个列表[<User u"lengqian">]
#filter为等值过滤器,username="lengqian"就是匹配条件

5.5在视图函数中操作数据库

现在是简单的在视图函数中操作数据库,比如将用户submit的用户名与已有的数据库模型User表users进行对比,最后展示具有个性的页面。

借助书本例子,关系到数据库的操作有添加查询、添加,重点在于理解session["known"],个人认为,session作为会话,权当做一个类,known是一个实例,在这里是给known赋值,session["known"]=True,只是在这么一个视图函数的特殊环境中,才有session参与进来

#书本例子
@app.route("/",methods=["GET","POST"])
def index():    
    form = NameForm()
    if form.validate_on_submit():
        user = User.query.filter_by(username=form.name.data).first()
        if user is None:
            user = User(username=form.name.data)
            db.session.add(user)
            session["known"]=False
        else:
            session["known"]=True
        session["name"]=form.name.data
        form.name.data="" #无此行代码时,submit后刷新总是有提醒,个人理解:post提交一次后,form.name.data就被清空了
        return redirect(url_for("index"))#redirect函数,重定向响应
    return render_template("index.html",form=form,current_time=datetime.utcnow(),name=session.get("name"),known=session.get("known",False))

5.6集成python shell

就是将数据库的数据自动提交给shell中,利用的是flask-script的Shell命令,这个导入只是将我们程序当中的数据库实例、模型注册到shell中,这样的话,就不用我们一个个手动在shell中添加进去了。

以上只是书本描述的一部分作用,更重要的是,将下面文件运行python c:/..../manage.py shell后,会出现如下代码。这些待选项就是我们用来操作的方法:

    ============== RESTART: C:\Users\Administrator\flasky\hello.py ==============
usage: hello.py [-?] {shell,db,runserver} ...

positional arguments:
  {shell,db,runserver}
    shell               Runs a Python shell inside Flask application context.
    db                  Perform database migrations
    runserver           Runs the Flask development server i.e. app.run()

optional arguments:
  -?, --help            show this help message and exit

有以下几种操作:

python c:/../../manage.py shell #启动python shell
python c:/../../manage.py db init #数据库的初始化,可建造xxx.sqlite
python c:/..../manage.py db upgrade #更新数据库
python c:/..../manage.py runserver #启动运行

讲了这么多操作,注意点在于加入路径操作才会有满意的结果,否则出现了not package或者toplevel等等各种错误

若想要把对象添加到列表中,可以为shell命令注册一个make_context函数,

from flask_script import Shell

def make_shell_context():
	return dict(app=app,db=db,User=User,Role=Role)
manager.add_command("shell",Shell(make_context=make_shell_context))#以后再解释。

if __name__== "__main__"
manager.run()	#集成python shell和原来程序有点不同,就是将app.run()改为manager.run()这时候运行如书中一样,运行之后出现一个类似普通shell的界面

如上面所示,make_shell_context()函数注册了程序、数据库实例和模型到shell中,可以如下查看:

>>>python hello.py shell
>>>app
<Flask "app">

>>>python hello.py db
>>>...#数据库库的路径

若没有这样配置的话,要自己手动导入对象才能往下操作

>>>hello
>>>from hello import app
>>>app
>>><Flask "app">

5.7使用Flask-Migrate实现数据库迁移

能够跟踪数据库模式的变化,增量式地 把变化应用到数据中

hello.py:配置flask-migrate

from flask_migrate import Migrate,MigrateCommand
#...
migrate=Migrate(app,db)
manager.add_command("db",MigrateCommand)

步骤:暂时放下来,后期再在项目中补充,挺难的,尤其是脚本的创立(之所以这样,是在前面的pythonshell中失败了)
更新:将上述代码加入程序当中即可

  • 在shell中,利用init子命令建立迁移仓库(venv)...python hello.py db init 即可创建迁移仓库
  • 实际操作,更新数据库(venv)...python hello.py db upgrade 即可

第6章 电子邮件

6.1测试邮件

本章内容见电子邮件QQ邮箱.md(9/17 2017)

单一的邮件测试成功,详见电子邮件QQ邮箱.mdqq.py,问题主要是在于变量的设置和POP3/SMTP授权码的最新属性。

6.2在程序中集成发送邮件
在程序中集中发送、异步发送电子邮件失败。详见hello2.py,暂时放一放吧#0908
更新:上述错误主要在于授权码没有更新,更新后测试成功单独见“QQ邮箱.md”

6.3异步发送电子邮件

第7章 大型程序的结构

本章节最大的问题是“单个文件hello.py转为为大型程序”的描述不足,在结合git checkout的条件下,将作者代码和自己的对比,一点点转化过来。大概能理解和正常运行了。

7.1项目结构

在这里,使用包和模块组织大型程序的方式,其最大作用是,将功能分开,有利于后期的维护等。

7.2配置选项

在实例生产中,我们需要程序在不同的环境中运行,比如测试、开发和生产,这个时候就需要不同的配置了,于是config.py产生了。config字典中,注册不同的配置环境,一般情况还要注册一个默认的配置,如:"default":DevelopmentConfig

7.3程序包

  • 使用工厂函数:就是为了创建,在不同配置环境下的程序实例,用来测试程序运行效果。比如,在开发、生产和测试中。
  • 在蓝本中实现程序功能:让路由先休眠来定义路由简单,使拆分开的可以嵌套一起并运行-使得各个模块可以单独编辑维护

7.4x####

  • 启动脚本:由manage.py来启动脚本
  • 需求文件:需求文件生成,requirements.txt用来记录所有依赖包及其版本号在shellpip freeze >requirements.txt;新生成虚拟环境的完全副本:pip install -r requirements.txt即可。文件名自己定义,一般是常用requirements.txt
  • 单元测试:就是实实在在的将程序可能出现的错误写出来,进行测试。常用标准库中的unittest包编写。

8.4使用flak-login认证用户

第九章 用户角色

用户角色,代表着用户的权限,比如发表文章、评论、关注等,在这里使用的是权限的组合,再根据用户的角色分配不同的权限。具有通用性。10/26/2017 11:24:29 PM

9.1角色在数据库中的表示

在角色模型中,添加permissions字段,其值是一个整数,表示位标志。各操作对应一个位位置,能操作就是1;增加的default列(db.Boolean),则是用来区分是否为管理员。

文中的《程序的权限》表格,并不是flask的设计理念,而是我们的自己添加的属性。我们构建一个8位的二进制数字,是位标志,不同的值代表着不同的权限(数字和权限附加到一起了)。代表不同权限的值相加(权限的相加)结果,就可以代表着不同的用户了,例如,普通用户和管理员肯定不同的。而后,我们构建函数来实现数字和角色的演变。

实际操作:

在models.py中建立"权限常量"???

class Permission:
	FOLLOEW = 0x01
	...

第12章 关注者

12.1 再论数据库关系

  1. 数据库使用关系建立记录之间的联系。一对多关系是最常见的关系类型,实现这种关系时,我们要在"多"这一侧键入一个外键,指向"一"这一侧联结的记录(用户是多,角色才是一,"多个用户配一个角色",每个用户通过外键和唯一的角色联系起来)。
  2. 大部分的其他关系类型都可以从一对多类型中衍生。一对一关系类型是简化版的一对多关系--限制"多"这一侧最多只能有一个记录即可。

12.1.1 多对多关系

  1. 需要添加第三张表-关联表。多对多关系就可以分解成原表和关联表之间的两个一对多关系。例如,在student和class两个模型之间,我们建立一个关联表,表中的每一行都表示一个学生注册的一个课程。学生和关联表是一对多关系(一个学生多个课程选择),课程也是如此(一个课程多个学生选择)。
  2. 于是,我们在,也就是关联表中建立外键,产生联系。注意关联表只是一个table,不是模型。SQLAlchemy会自动接管这个表。
  3. 多对多关系仍旧使用定义一对多关系的db.relationship()方法定义,不同的是,必须把secondary参数设为关联表。多对多关系可以在任何一个类中定义,backref参数会处理好关系的另一端。注意其中的回引backref=db.backref("studetns",lazy='dynamic')。

12.1.2 自引用关系####

问题汇总

1.程序启动问题

程序启动有以下三种方法:

  1. 在shell中交互式输入一条条命令(-Shell就是一个命令行解释器,它的作用是解释执行用户的命令,用户输入一条命令,Shell就解释执行一条,这种方式称为交互式(Interactive))
  2. 在shell中批处理直接执行脚本(批处理(Batch),用户事先写一 个Shell脚本(Script),其中有很多条命令)
  3. 在python自带的IDLE中执行脚本

2.程序小问题

  1. 第九章《用户角色》中,在model.py中更改了角色内容,每次在manage.py shell之后,都要调用Role.insert_roles来更新数据库10/30/2017 3:20:34 PM
  2. 同样的,在很多章节中,修改数据库项目后,都要执行python managy.py db upgrade,进行数据更新

问题扩展:

1.一个脚本文件hello.py在IDLE中执行,报错no module named flask_stript,在shell中输入C:\Python27\Flask\hello.py还是报错(输入地址麻烦的话可以直接将文件丢进shell中执行),报错一样,在shell中键入python hell.py shell成功运行。

2.书本中多次涉及到“命名空间”,例如flask-bootstrap从flask.ext命名空间导入。
结合之前

3.网络请求只是还需要补充GET,POST等

4.蓝本的理解flask蓝本

5.工厂函数的理解:个人理解,工程函数def current_app(config_name):,主要是用来测试程序在不同配置环境下的运行效果。若是普通函数的话,程序实际在运行第一次的时候就随之建立了,但是在工厂函数中,先没有创建实例,而是在配置后,再创建实例了,多个配置config,就可以创建很多程序实例用来测试了。0912

6.current_app以及在异步发送邮件一章中的current_app.get_current_boject()辨识链接1

7.@staticmethod和classmethod的区别:链接2

阿里云部署flask:阿里云部署flask+wsgi+nginx

flask GitHub优秀作品:刘志辉

问题汇总

  1. 关于pip模块的安装:

    1. 电脑系统更换SSD系统也全新安装之后,进行开发环境的安装,python2.7.13
    2. 第一步是安装virtualenv模块,在CMDshell窗口中使用pip isntall xxeasy_install xx,提示没有pip和easy_install命令
    3. 在python/scripts安装包中发现有pip和easy应用程序,将该目录添加至path即可使用。
    4. 提示,我百度的时候,大部分做法是下载pip.exe文件安装后再使用。
  2. 使用pip install -r requirements/common.txt安装依赖包,仅部分安装了,重试还是如此。后面将common.txt中明显不需要的模块删除后放至根目录下,安装成功。这算是一个bug吗?

  3. 基本步骤

    1. python安装及path配置,注意pip程序路径的配置 # 安装python时自带pip和easy_install应用程序,注意也需要添加至path
    2. 安装virtualenv模块 #使用pip install进行安装
    3. 安装虚拟环境并激活
    4. 在虚拟环境中安装所需要的模块 pip install -r requirements.txt即可。
    5. 开发运行环境配置完毕。3/8/2018 10:58:11 PM
posted @ 2018-03-08 23:00  Jarhead  阅读(425)  评论(0编辑  收藏  举报