卡布奇诺

生活心情


  • 首页

  • 分类

  • 归档

  • 标签

  • 关于

Mac-php-version

发表于 2018-10-28

更改 mac 多版本 php 启动方式

1
brew services list
1
2
3
brew services restart php
brew services stop php
brew services start php
1
2
3
brew services restart php@5.6
brew services stop php@5.6
brew services start php@5.6

慕课网Flask高级编程实战-知识点思维导图

发表于 2018-09-11 | 分类于 Python

虚拟环境搭建[二选一即可]

pipenv 安装与使用

Github

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#方式一:
## mac 用户
$ brew install pipenv

## Fedora用户
$ sudo dnf install pipenv

#方式二:

#用 pip 安装 pipenv
pip install pipenv

#进入虚拟环境(需要在你放置项目的地方执行,如果没有文件则会新建文件夹)
pipenv blog

# 退出虚拟环境
pipenv exit

#使用 pipenv 安装类库
pipenv install {package}

# 卸载类库
pipenv uninstall {package}

# 查看安装包依赖
pipenv graph

#查看虚拟环境的执行文件路径
pipenv --env

virtualenvwrapper

virtualenvwrapper是用来管理virtualenv的扩展包,用着很方便。

项目地址

virtualenvwrapper

安装

1
2
3
4
5
#安装virtualenv
pip install virtualenv

#安装virtualenvwrapper
pip install virtualenvwrapper

配置

修改~/.bash_profile或其它环境变量相关文件(如 .bashrc(我的Ubuntu15.10 下的是这个) 或用 ZSH 之后的 .zshrc),添加以下语句:

1
2
3
4
5
6
7
8
#WORKON_HOME,PROJECT_HOME 可以替换成你自己的目录
export WORKON_HOME=$HOME/.virtualenvs
export PROJECT_HOME=$HOME/workspace
source /usr/local/bin/virtualenvwrapper.sh

#然后运行下面代买生效:

source ~/.bash_profile

用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#创建运行环境blog
mkvirtualenv blog

#工作在 blog 环境 或 从其它环境切换到 blog 环境
workon blog

#退出终端环境
deactivate

#删除运行环境ENV
rmvirtualenv ENV

#创建mic项目和运行环境mic
mkproject mic

#创建临时运行环境
mktmpenv

#列出可用的运行环境
lsvirtualenv

#列出当前环境安装了的包
lssitepackages

Flask 的实例化和启动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#导入 Flask 模块包
from flask import Flask

#实例化对象 app 构造函数的参数会作为 Flask app 核心对象的标识。可以用其他的标识符代替。如:项目名称 blog

app = Flask(__name__)

#启动 web app
#host 参数指定可以访问本网站的 IP 地址。“0.0.0.0”表示任何主机都可以访问
#port 参数指定启动端口。如:localhost:8000
#debug 参数表示开启调试模式,开启之后,有以下优势。但是生产环境不建议使用。
# 1.修改文件不需要每次手动重启,服务器会自动重启,
# 2.如果发生错误,可以看到详细的错误信息

app.run(host="0.0.0.0",port=8000,debug=True)

注册路由方式一

1
2
3
4
5
#使用装饰器来诸恶路由,启动的参数为 URL 的路径,对应的会调用 index() 函数
@app.route('/index')
def index():
“““定义视图函数 相当于 MVC 中的 Controller”””
return "首页"

如果直接使用/index

那么不能兼容浏览器不能访问/index/和/index,

解决办法,路径定义改成/index/

1
2
3
4
@app.route('/index/')
def index():
“““基于类的视图,相当于 MVC 中的 Controller”””
return "首页"

在路径xxx后添加斜杠/后就可以访问xxx/ 和 xxx的原理

重定向:当你访问url1的时候,服务器返回状态码302让用户访问url2

访问http://localhost:5000/index,观察浏览器网络请求情况,可以看到发生了重定向,重定向到了http://localhost:5000/index/

为什么这么做呢?

这是因为,如果不做重定向,不带/ 的(index)和带/的(index/)都可以访问到视图函数。

那么就是说同一个视图函数对应着两个不同的路由,没有保证唯一url的原则

唯一url的好处

如果有两个url,那么在搜索引擎中会被索引两次,

这样会浪费性能,影响搜索引擎的优化,没有这个必要。

注册路由方式二

1
2
#通过调用app的add_url_rule函数
app.add_url_rule("/hello",view_func=hello)
  • 实际上方法1装饰器的模式,内部就是调用了add_url_rule函数。下面来看一下源码
1
2
3
4
5
6
7
8
9
10
def route(self, rule, **options):
"""Like :meth:`Flask.route` but for a blueprint. The endpoint for the
:func:`url_for` function is prefixed with the name of the blueprint.
"""
def decorator(f):
endpoint = options.pop("endpoint", f.__name__)
#划重点:实际内部调用的就是 add_url_rule
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator

Flask 配置文件

1
2
3
4
5
#载入配置文件
app.config.from_object("cofing")

#读取配置文件
pritn(app.config["APP_NAME"])

要点

  • 配置文件中的 KEY 必须全部是大写![必须大写!]
  • DEBUG 是 Flask 中的一个默认变量,如果想要覆盖,必须名字一模一样。

if name == “name“:

为什么要添加这句:if __name__ == "__name__"

加入这个判断以后,只有在启动入口文件以后,才会执行。而在被其他模块导入的时候不会执行

加入的优势:

在生产环境中,我们一般是采用 Nginx + uwsgi 来部署我们的 Python 项目,我们使用 uwsgi 来启动项目的 flask 服务的时候,这个时候项目的启动文件(我们这项目文件名为:run.py),只是作为一个模块被调用,这时加上入口判断,在生产环境就就不会执行 app.run()方法,否则就会启动两个 Flask 服务。

1
2
3
# run.py 文件代码
if __name__ == "__name__":
app.run(host=app.config["HOST"], debug=app.config["DEBUG"], port=app.config["PORT"])

慕课网Flask高级编程实战-知识点思维导图

发表于 2018-09-11 | 分类于 Python

PC端github地址

Github

restfult api github 地址

Github

知识点思维导图

慕课网Flask高级编程实战-4.flask核心机制

发表于 2018-09-11 | 分类于 Python

flask中经典错误 working outside application context

在上节我们通过db.create_all(app=app)的方式解决了working outside application context的错误,下面我们来深究,这个错误出现的具体原因是什么。

首先来个测试代码:

1
2
3
4
5
6
7
8
9
10
11
from flask import Flask, current_app

__author__ = "Jason"

app = Flask(__name__)

# 断点调试这里显示current_app=[LocalProxy]<LocalProxy unbound>
a = current_app

# RuntimeError: Working outside of application context.
b = current_app.config["DEBUG"]

我们通过current_app获取配置,看似没有问题的代码,却抛出了同样的异常。

通过断点调试发现current_app并不是Flask对象,而是一个unbound的LocalProxy。

回想我们之前的request对象,其实也是个LocalProxy。

1
2
3
4
5
6
7
# context locals
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, 'request'))
session = LocalProxy(partial(_lookup_req_object, 'session'))
g = LocalProxy(partial(_lookup_app_object, 'g'))

那么这里为什么会抛出这个异常呢,想要回答这个问题,就需要深入理解这个LocalProxy。我们在下一小节进行介绍

AppContext、RequestContext、Flask与Request之间的关系

1.定位AppContext、RequestContext

Flask有两个上下文,应用上下文-AppContext和请求上下文-RequestContext。他们本质都是对象,是一种封装。应用上下文是对Flask的封装,请求上下文是对Request的封装

下面我们来通过源码,了解一下这两个上下文。
Flask源码的全貌,是在External Libraries/site-pacages/flask下

Flask是一个非常好的微框架,里面的源码并不多,大部分都是注释,这给我们可以很方便的阅读源码.

我们要看的两个上下文在ctx.py(context的缩写)中,其中的AppContext就是应用上下文,RequestContext就是请求上下文

阅读AppContext和RequestContext的构造函数,发现他们都将核心对象app作为了他们的一个属性

1
2
3
4
5
6
7
8
def __init__(self, app):
self.app = app
self.url_adapter = app.create_url_adapter(None)
self.g = app.app_ctx_globals_class()

# Like request context, app contexts can be pushed multiple times
# but there a basic "refcount" is enough to track them.
self._refcnt = 0

并且他们都有相同的四个方法

1
2
3
4
5
6
7
8
def push(self):
...
def pop(self, exc=_sentinel):
...
def __enter__(self):
...
def __exit__(self, exc_type, exc_value, tb):
...

2.为什么需要上下文

为什么需要上下文,我们之间操作Flask的核心对象app不可以吗?

这是一个设计思想。有时候呢,我们不光需要这个核心对象app,还需要他外部的一些东西,这个时候,我们可以把他们统一结合封装到一起,组装成一个新的上下文对象,并且在这个对象之上,可以提供一些新的方法,如我们上面所提到的push、pop等

3.对AppContext、RequestContext、Flask与Request的意义做出一个解释

  • Flask:核心对象,核心对象里承载了各种各样的功能,比如保存配置信息,再比如注册路由试图函数等

  • AppContext:对Flask的封装,并且增加了一些额外的参数

  • Request:保存了请求信息,比如url的参数,url的全路径等信息

  • RequestContext:对Request的封装

我们在实际编码过程中,可能是需要Flask或者Request的信息的,但是这并不代表我们应该直接导入这两个对象获取相关信息,

正确的做法是从AppContext,RequestContext中间接的获得我们需要的信息

即使这样,我们也没有必要导入Context去使用上下文,这就回到了current_app和request这些LocalProxy,他们提供了间接操作上下文对象的能力,使用了代理模式

详解flask上下文与出入栈

Flask工作原理

  • 当一个请求进入Flask框架,首先会实例化一个Request Context,这个上下文封装了请求的信息在Request中,并将这个上下文推入到一个栈(_request_ctx_stack/_app_ctx_strack)的结构中,即之前将的push方法

  • RequestContext在入_request_ctx_stack之前,首先会检查_app_ctx_strack是否为空,如果为空,则会把一个AppContext的对象入栈,然后再将这个请求入栈到_request_ctx_stack中

  • 我们的current_app和request对象都是永远指向_app_ctx_strack/_request_ctx_stack的栈顶元素,也就是分别指向了两个上下文,如果这两个值是空的,那么LocalProxy就会出现unbound的状态

  • 当请求结束的时候,这个请求会出栈-pop

回到我们之前的测试代码,如果要想让我们的测试代码正常运行,就需要手动将一个AppContext入栈。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from flask import Flask, current_app

__author__ = "Jason"

app = Flask(__name__)

# 获取AppContext,里面的代码很简单,就是:return AppContext(self)
ctx = app.app_context()
# 将AppContext入栈
ctx.push()
# 断点调试这里显示current_app=[LocalProxy]<LocalProxy unbound>
a = current_app

# RuntimeError: Working outside of application context.
b = current_app.config["DEBUG"]
print(b)

注意

虽然current_app和request指向的是两个上下文,但是他们返回的却是Flask核心独享和Request对象。下面来看下这部分的源码

globals.py

1
2
3
4
5
6
7
8
9
10
# globals.py中实例化LocalProxy获取current_app的代码中,传入了一个_find_app方法
current_app = LocalProxy(_find_app)

def _find_app():
# 取栈顶元素
top = _app_ctx_stack.top
if top is None:
raise RuntimeError(_app_ctx_err_msg)
# 获取ctx中的app对象
return top.app

从源码中可以看到,他获取的是app核心对象。

flask上下文与with语句

我们上一小节通过手动将app推入栈,弹出栈的方式,解决了working outside application context的问题。实际上更经典的做法是使用with语句来完成。

首先使用with语句替换之前的代码

1
2
3
4
5
app = Flask(__name__)

with app.app_context():
a = current_app
b = current_app.config["DEBUG"]

什么时候可以使用with语句:

  • 1.实现了上下文协议的对象,可以使用with语句

  • 2.对于实现了上下文协议的对象,我们通常称为上下文管理员

  • 3.通过实现enter和exit来实现上下文协议

  • 4.上下文表达式必须返回一个上下文管理器

对于上面一段代码来说,AppContext就是上下文管理器;app.app_context()就是上下文表达式。enter中做了push操作,__exit__中做了pop操作。
所以只要进入with语句,current_app就是有值的,一旦离开了with语句,current_app
就会弹出,然后就又没有值了(又变成了unbound)。

1
2
3
4
5
6
7
8
9
def __enter__(self):
self.push()
return self

def __exit__(self, exc_type, exc_value, tb):
self.pop(exc_value)

if BROKEN_PYPY_CTXMGR_EXIT and exc_type is not None:
reraise(exc_type, exc_value, tb)

通过数据库的链接和释放来理解with语句的具体含义

连接数据库的操作步骤:

  • 1.连接数据库
  • 2.sql或者其他的业务逻辑
  • 3.释放资源

如果上面的第二部分出错,那么第三部分的释放资源就不会被执行,资源就会一直被占用。

解决这个问题的通常做法是使用try-except-finally
但是在finally中更优雅的方式就是使用with语句中。

我们可以把连接数据库的操作写在上下文管理器的__enter__方法里面,把业务代码写在with语句的代码块里面,把释放资源的语句写在__exit__里面。

读写文件的具体例子

一般的写法

1
2
3
4
5
try:
f = open(r'/Users/test.txt')
print(f.read())
finally:
f.close()

使用with语句的写法:

1
2
with open(r'/Users/test.txt') as f:
print(f.read())

注意上面的with语句后面的as 返回的并不是上下文管理器,他实际上是enter方法返回的一个值,

上面一段代码我们在enter中返回了一个a,所以下面as 后的obj_A就是1

exit方法详解

注意我们编写的测试代码,运行时会报错的,错误原因是exit方法接受的参数数量不够。
exit方法的作用不只是释放资源,还有处理异常,所以exit方法还要多接受exc_type,exc_value,tb三个参数。这三个参数在没有异常发生的时候回传控制,如果有异常的话,这三个参数分别是异常类型,异常消息,和详细的异常堆栈信息

exit方法还需要返回一个boolean类型的值,如果返回True,那么外部就不会抛出异常,如果返回False,那么还会在外部抛出异常,如果什么都不返回,按照False处理。Flask提供了一种非常灵活的方式,可以让我们选择在with语句内部还是外部处理异常

4.6 阅读源码解决db.create_all的问题

对于Flask来说,文档更适合中高级的开发者,而对于新手不是特别友好。所以以不变应万变。我们可以遇到问题的时候,可以通过阅读源码的时候来解决。

下面我们来看下在第三章的时候,为什么我们的flask_sqlalchemy已经注册了app对象,但是create_all方法还是需要传入app参数,不传就会报错

首先看一下init_app方法的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
def init_app(self, app):
"""This callback can be used to initialize an application for the
use with this database setup. Never use a database in the context
of an application not initialized that way or connections will
leak.
"""
# 首先是尝试获取app中的配置,如果没有找到则发出警告
if (
'SQLALCHEMY_DATABASE_URI' not in app.config and
# 如果有多个数据库,需要配置这个选项
'SQLALCHEMY_BINDS' not in app.config
):
warnings.warn(
'Neither SQLALCHEMY_DATABASE_URI nor SQLALCHEMY_BINDS is set. '
'Defaulting SQLALCHEMY_DATABASE_URI to "sqlite:///:memory:".'
)

# 防御性编程,给dict设置一些默认值
# setdefault是dict的默认值
app.config.setdefault('SQLALCHEMY_DATABASE_URI', 'sqlite:///:memory:')
app.config.setdefault('SQLALCHEMY_BINDS', None)
app.config.setdefault('SQLALCHEMY_NATIVE_UNICODE', None)
app.config.setdefault('SQLALCHEMY_ECHO', False)
app.config.setdefault('SQLALCHEMY_RECORD_QUERIES', None)
app.config.setdefault('SQLALCHEMY_POOL_SIZE', None)
app.config.setdefault('SQLALCHEMY_POOL_TIMEOUT', None)
app.config.setdefault('SQLALCHEMY_POOL_RECYCLE', None)
app.config.setdefault('SQLALCHEMY_MAX_OVERFLOW', None)
app.config.setdefault('SQLALCHEMY_COMMIT_ON_TEARDOWN', False)
track_modifications = app.config.setdefault(
'SQLALCHEMY_TRACK_MODIFICATIONS', None
)

if track_modifications is None:
warnings.warn(FSADeprecationWarning(
'SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and '
'will be disabled by default in the future. Set it to True '
'or False to suppress this warning.'
))

app.extensions['sqlalchemy'] = _SQLAlchemyState(self)

@app.teardown_appcontext
def shutdown_session(response_or_exc):
if app.config['SQLALCHEMY_COMMIT_ON_TEARDOWN']:
if response_or_exc is None:
self.session.commit()

self.session.remove()
return response_or_exc

create_app 方法的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def _execute_for_all_tables(self, app, bind, operation, skip_tables=False):
app = self.get_app(app)

if bind == '__all__':
binds = [None] + list(app.config.get('SQLALCHEMY_BINDS') or ())
elif isinstance(bind, string_types) or bind is None:
binds = [bind]
else:
binds = bind

for bind in binds:
extra = {}
if not skip_tables:
tables = self.get_tables_for_bind(bind)
extra['tables'] = tables
op = getattr(self.Model.metadata, operation)
op(bind=self.get_engine(app, bind), **extra)

def create_all(self, bind='__all__', app=None):
"""Creates all tables.

.. versionchanged:: 0.12
Parameters were added
"""
self._execute_for_all_tables(app, bind, 'create_all')

可以看到create_all方法调用了_execute_for_all_tables私有方法,_execute_for_all_tables里面第一行的get_app方法用来获取一个app核心对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
def get_app(self, reference_app=None):
"""Helper method that implements the logic to look up an
application."""

# 如果关键字参数的app不为空,就返回参数的app
if reference_app is not None:
return reference_app
# 如果current_app不为空,则返回current_app
if current_app:
return current_app._get_current_object()
# 如果对象的app属性不为空,则返回对象的app属性
if self.app is not None:
return self.app

raise RuntimeError(
'No application found. Either work inside a view function or push'
' an application context. See'
' http://flask-sqlalchemy.pocoo.org/contexts/.'
)

所以通过三个判断,我们可以总结出三个不同的方法来解决我们遇到的问题。

1.在create_all 中传入关键字参数app。也就是我们之前用过的。

2.向堆栈中推入一条app_context,使得current_app不为空。

1
2
with app.app_context():
db.create_all()

3.在初始化flask_sqlalchemy对象的时候,传入app参数。

具体选取哪种方式,是根据情况而定的,比如我们当前的情况,就不合适使用第三种方法,因为我们的flask_sqlalchemy对象是在models中的book.py中的,如果用第三种方式,还需要在这里导入app对象。

慕课网Flask高级编程实战-知识点思维导图

发表于 2018-09-11 | 分类于 Python

慕课网Flask高级编程实战-2.搜索书籍路由编写

发表于 2018-09-11 | 分类于 Python

图书数据库的基地址

1
2
3
4
5
6
7
8
# 基地址
http://t.yushu.im
# 关键字搜索
http://t.yushu.im/v2/book/search?q={}&start={}&count={}
# isbn搜索
http://t.yushu.im/v2/book/search/isbn/{isbn}
# 豆瓣api
https://api.douban.com/v2/book

截止2018你那9月11日,以上地址已经无法访问,此地址仅为项目演示地址

定义参数,判断查询参数q是否是isbn号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@app.route("/search/q/<page>")
def search():
"""
搜索书籍路由
:param q: 关键字 OR isbn
:param page: 页码
# isbn isbn13 由13个0-9在数字组成
# isbn10 由10表0-9表数字组组成,中间可能包含' - '
"""
isbn_or_key = 'key'

isbn_or_key = 'key'
if len(q) == 13 and q.isdigit():
isbn_or_key = 'isbn'
short_q = q.replace('-', '')
if '-' in q and len(short_q) == 10 and short_q.isdigit():
isbn_or_key = 'isbn'
pass

要点解析

  • 判断字符串是否是数字,可以使用函数 isdigit()
  • in 关键字可以判断一个字符串是否包含在另外一个字符串中
  • 多个逻辑循环原则:
    • 尽量把判断结构为的假的放在前边,利用 逻辑判断的短路优势,可以减少资源消耗。
    • 需要查询数据库的尽量放在后边。

简单重构

上面代码的缺点

  • 判断代码太多,使得代码很臃肿。而且不利于以后代码的复用
  • 代码可读性查,使后来者不能一眼看出主要功能。

重构后的代码

fisher.py

1
2
3
4
5
6
7
8
@app.route("/search/<q>/<page>")
def search(q, page):
"""
搜索书籍路由
:param q: 关键字 OR isbn
:param page: 页码
"""
isbn_or_key = is_isbn_or_key(q)

helper.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def is_isbn_or_key(word):
"""
判断word是isbn号还是查询关键字key
isbn isbn13 由13个0-9在数字组成
isbn10 由10表0-9表数字组组成,中间可能包含' - '
:param word:
:return: key or isbn
"""
isbn_or_key = 'key'
if len(word) == 13 and word.isdigit():
isbn_or_key = 'isbn'
short_word = word.replace('-', '')
if '-' in word and len(short_word) == 10 and short_word.isdigit():
isbn_or_key = 'isbn'
return isbn_or_key

要点解析:

  • 把判断 isbn 的代码隔离出去,新建 helper.py 文件,可以重复利用。

建议

  • 其他地方的代码多一点还可以接受,视图函数中不可以,因为视图函数是一个web项目的入口。所有人阅读都是从这里入手,应该把细节屏蔽掉,给阅读者一个选择。

  • 看源码的技巧:先通读整体,了解整体过程,再回过头来了解细节,不要从一开始就深究每一个细节

  • 过多的注释会让代码变的臃肿,尽量使用易懂的函数名来代替注释,保持代码的简洁性

requests发送http请求及代码的简化手段

http.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class HTTP:
"""
封装 request 请求类
"""
@staticmethod
def get(url, return_json=True):
"""
发送get请求
:param url: 请求路径
:param return_json: 是否返回json格式的结果
:return:
"""
# r 是对这次HTTP请求调用结果的一个封装,并不是我们直接想要的结果,而是想要返回的内容
r = requests.get(url)
if r.status_code != 200:
return {} if return_json else ''
return r.json() if return_json else r.text

知识点:

  • 简化if-else语句的几种方式
    • 1.使用三元表达式 ;
    • 2.if+return;
    • 3.将if-else里的代码提取成函数
  • if+return的理解:
    • 把最后一句return前的if+return 全都理解为正常流程之外的一种特例情况的处理;
    • 多次if-return,提前结束一些逻辑分支,可以提高代码思维的清晰性
  • requests的一些说明:
    • 1.get()发送get请求;
    • 2.返回结果r.status_code 获取返回状态吗;
    • 3.r.json()将返回结果序列化成json;
    • 4.r.text 将返回结果不做处理直接返回

requests vs urllib

发送http请求的两种方法:

  • 1.使用urllib(python内置)
  • 2.使用requests(需要使用pip3安装)

urllib的用法:

urllib缺点分析

  • 需要对url进行编码,并且有些字符不需要编码还要声明出来
  • 返回的结果是字节码,需要包装成字符串
  • 404的情况是通过异常的形式抛出,现在流行的restful,404已经不是一种异常了

从API获取数据

将具体调用HTTP请求,获取结果的业务代码封装到`YuShuBook中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class YuShuBook:
"""
请求接口的定义类
"""
search_by_isbn_url = "http://t.yushu.im/v2/book/search/isbn/{}"

search_by_key_url = "http://t.yushu.im/v2/book/search?q={}&count={}&start={}"

@classmethod
def search_by_isbn(cls, isbn):
"""
1.组装 search_by_isbn_url 地址
2.返回对地址的请求数据,默认返回 Json 数据
"""
url = cls.search_by_isbn_url.format(isbn)
return HTTP.get(url)

@classmethod
def search_by_key(cls, q, count=15, start=0):
"""
1.组装 search_by_key_url 地址
2.返回对地址的请求数据,默认返回 Json 数据
"""
url = cls.search_by_key_url.format(q, count, start)
return HTTP.get(url)

使用 json.dumps 序列表返回结果,在视图函数中进行返回,并声明状态码和返回类型(一个元组)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@app.route("/book/search/<q>/<page>")
def search(q, page):
"""
搜索书籍路由
:param q: 关键字 OR isbn
:param page: 页码
"""
isbn_or_key = is_isbn_or_key(q)
if isbn_or_key == 'isbn':
result = YuShuBook.search_by_isbn(q)
else:
result = YuShuBook.search_by_key(q)

return json.dumps(result), 200, {'content-type': 'application/json'}
#可以使用 flask 提供的 jsonify 包替代
#return jsonify(result)

可以使用flask提供的jsonify替换麻烦的json.dumps和元组

将视图函数拆分到单独的模块中

1.将试图函数都放在一个文件中有哪些不足:

  • 代码太长,不利于维护

  • 从业务模型抽象的角度,不应该把他们都放在一个文件中。关于书籍相关的API就应该放在书籍模型的视图函数文件中,跟用户相关的API就应该放在用户模型相关的文件中

  • 入口文件的意义比较独特,会启动web服务器以及做很多初始化的操作,就算要放在一个文件也不应该业务的操作放在入口文件中来

2.尝试拆分模块

思路

将试图函数抽离到单独的包中,然后在新的试图文件中引入flask.py来导入app核心对象。

为了新的试图文件中的路由可以成功注册,再在flask.py中引入刚刚抽离出的试图模块

修改后的fisher.py

1
2
3
4
5
6
7
8
9
10
11
from flask import Flask

# 为了可以注册book.py中的路由
from app.web import book

app = Flask(__name__)

app.config.from_object("config")

if __name__ == "__main__":
app.run(host=app.config["HOST"], debug=app.config["DEBUG"], port=app.config["PORT"])

新增的book.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from flask import jsonify

from helper import is_isbn_or_key
from yushu_book import YuShuBook

# 为了让book.py模块可以使用app对象
from fisher import app

__author__ = "Jason"


@app.route("/book/search/<q>/<page>")
def search(q, page):
"""
搜索书籍路由
:param q: 关键字 OR isbn
:param page: 页码
"""
isbn_or_key = is_isbn_or_key(q)
if isbn_or_key == 'isbn':
result = YuShuBook.search_by_isbn(q)
else:
result = YuShuBook.search_by_key(q)

return jsonify(result)

但是这样做并不是正确的做法,结果表明,这样修改以后,访问search api会404

为了知道为什么这样做不行,我们需要先刨铣一下Flask路由机制的原理

3.Flask路由机制

flask路由机制

flask路由机制

flask的基本思想是内部会维护一个字典。

每一个url都会对应一个视图函数,但是不仅仅是这样。

每一个url还会对应一个endpoint端点。用于反向构建URL(后面会讲解)

flask的路由注册app_url_rule(url=,view_func=,endpoint=)会接受三个参数,

前两个我们都知道了,第三个就是上面说的endpoint。他的默认值是view_func的名称。

当然,app.route('url',endpoint=)也可以传入

flask route的部分源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
def route(self, rule, **options):
"""A decorator that is used to register a view function for a
given URL rule. This does the same thing as :meth:`add_url_rule`
but is intended for decorator usage::

@app.route('/')
def index():
return 'Hello World'

For more information refer to :ref:`url-route-registrations`.

:param rule: the URL rule as string
:param endpoint: the endpoint for the registered URL rule. Flask
itself assumes the name of the view function as
endpoint
:param options: the options to be forwarded to the underlying
:class:`~werkzeug.routing.Rule` object. A change
to Werkzeug is handling of method options. methods
is a list of methods this rule should be limited
to (``GET``, ``POST`` etc.). By default a rule
just listens for ``GET`` (and implicitly ``HEAD``).
Starting with Flask 0.6, ``OPTIONS`` is implicitly
added and handled by the standard request handling.
"""
#注册路由的装饰器函数
def decorator(f):
endpoint = options.pop('endpoint', None)
#实际是调用内部的 add_url_rule 函数
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator

通过端点调试可以发现,Flask内部由url_map 维护一个url->endpoint 的指向。

由view_functions 记录 endpoint所指向视图函数的函数,

这样请求进入到Flask内部,才能通过Url找到对应的视图函数

4. 循环引入流程分析

从上面的断点调试中发现,我们的url_map和view_functions中都已经维护了相关的信息。

但是为什么还是会出现404的情况,这是因为fisher.py和book.py出现了循环引入的情况。

下面看下fisher.py和book.py的具体流程图

图中有两种颜色的线:红色的线是fisher主执行文件被执行之后的执行路径;

蓝色的线是book模块被导入之后循环导入的执行路径。

  • 1.主流程开始之后,首先到达导入book的语句。然后进入book模块中执行
  • 2.book模块开始之后,首先到达导入fisher的语句(循环导入),这个时候主流程暂时结束,重新执行fisher中的代码
  • 3.这时候又回到fisher中的导入book的语句,由于book已经被导入一次,所以不会再次导入,进入if语句,这个时候的name是book导入fisher时候的name:fisher,不是主流程main,所以if语句条件为false。蓝色线执行终止,重新回到2. book导入fisher的语句。
  • 4.继续向下执行book 中app.route注册路由的语句。然后book执行完,回到fisher主流程执行中。
  • 5.到达if语句,这个时候name为main。执行run方法,启动服务

回答流程图中的两个问题:

  • 问题1:因为都是由fisher引入book,一个模块只会引入另一个模块一次。所以只执行了一次book
  • 问题2:由于一次是主流程执行fisher文件;一次是由book模块导入 fisher。

5.找不到视图函数的最终解释和证明

整个流程中,出现了两次核心app对象的初始化,注册路由是在蓝色流程中初始化的app注册的。

但是启动服务是红色流程中的app启动的

book中注册路由所使用的app对象,是他自己所导入fisher模块的app对象(蓝色流程中),

而不是红色主流程中所实例化的app对象

加入一些日志出数验证我们的结论。

我们在app实例化,启动,注册路由是哪个地方加入日志信息,来观察一下

book.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
print("id为"+str(id(app))+"的app注册路由")


@app.route("/book/search/<q>/<page>")
def search(q, page):
"""
搜索书籍路由
:param q: 关键字 OR isbn
:param page: 页码
"""
isbn_or_key = is_isbn_or_key(q)
if isbn_or_key == 'isbn':
result = YuShuBook.search_by_isbn(q)
else:
result = YuShuBook.search_by_key(q)

return jsonify(result)
fisher.py
1
2
3
4
5
6
7
8
9
10
11
app = Flask(__name__)
print("id为"+str(id(app))+"的app实例化")

app.config.from_object("config")

# 为了可以注册book.py中的路由
from app.web import book

if __name__ == "__main__":
print("id为" + str(id(app)) + "的app启动")
app.run(host=app.config["HOST"], debug=app.config["DEBUG"], port=app.config["PORT"])
执行结果
1
2
3
4
5
6
7
8
pydev debugger: process 63816 is connecting

id为4350444824的app实例化
id为4355159656的app实例化
id为4355159656的app注册路由
id为4350444824的app启动
* Debugger is active!
* Debugger PIN: 176-669-651

可以看到注册路由的app,和启动服务的app不是同一个app。

并且最后启动的app是最先实例化的app,也就是红色主流程的app;

而注册路由的app是后实例化的app,也就是由book导入fisher模块的蓝色流程的app

慕课网Flask高级编程实战-3.蓝图、模型与CodeFirst

发表于 2018-09-11 | 分类于 Python

应用、蓝图与视图函数

1.Flask的层级关系

  • Flask 最上层是 app 的核心对象
  • 在核心对象上可以新建若干蓝图,这个蓝图不能单独纯在,必须要和 app 关联。
  • 在每个蓝图上还可以注册很多静态文件、视图函数、模板等
  • 一个业务模块可以分为很多个蓝图 比如:user、book。可以把视图函数注册到蓝图上,然后再关联 app ,来达到模块化文件的分隔。
  • 之前的book.py 放到了app/web/路径下,就是考虑到了蓝图。app属于是整个Flask应用层。web属于是蓝图

2.代码规范化

应该讲一些初始化工作,放在对应层级的包的初始化文件 __init__.py 中。比如Flask核心应用app对象初始化应该放在应用层级app包的 __init__.py 中。

蓝图的初始化,应该放在对应蓝图层级web包的__init__.py中,并且所有蓝图对应的试图函数都应该放在web目录下

1
2
3
4
5
6
7
8
9
10
11
# app/__init__.py

from flask import Flask

def create_app():

app = Flask(__name__)

app.config.from_object("config")

return app
1
2
3
4
5
6
7
8
9
10
11
# fisher.py

from app import create_app

__author__ = "Jason"

app = create_app()

if __name__ == "__main__":
print("id为" + str(id(app)) + "的app启动")
app.run(host=app.config["HOST"], debug=app.config["DEBUG"], port=app.config["PORT"])

3 用蓝图注册试图函数

1.在蓝图中注册视图函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 实例化蓝图
# 第一个参数为蓝图所在模块名
# 应用名称
web = Blueprint('web', __name__)


@web.route("/book/search/<q>/<page>")
def search(q, page):
"""
搜索书籍路由
:param q: 关键字 OR isbn
:param page: 页码
"""
isbn_or_key = is_isbn_or_key(q)
if isbn_or_key == 'isbn':
result = YuShuBook.search_by_isbn(q)
else:
result = YuShuBook.search_by_key(q)

return jsonify(result)

2.蓝图是不能替代app应用的,在蓝图中注册了视图函数后,还需要将蓝图插入app

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#app/__init__.py

def create_app():
app = Flask(__name__)

app.config.from_object("config")
#注册 web 蓝图
register_blueprint(app)

return app


def register_blueprint(app):
from app.web.book import web
app.register_blueprint(web)

3.单蓝图多模块拆分视图函数

蓝图,他的出发点,是为了分模块的。

什么是模块级别的呢?

比如一个web系统属于一个web模块;

一个提供给移动端使用的api是一个api模块;

一个内容管理系统是一个CMS。

我们不应该讲book,user这样的不同类别的py文件,做成多个蓝图(这样不是不行,只是小题大做了)

正确的方式是:

在一个模块的初识文件中定义蓝图对象,这个模块的不同文件都引入这个蓝图对象来注册路由函数。

并在模块的初始化文件中引入这些py文件来完成试图函数注册代码的执行

book.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#book.py

from flask import jsonify
from helper import is_isbn_or_key
from yushu_book import YuShuBook

# 引入web模块
from . import web


__author__ = "Jason"


@web.route("/book/search/<q>/<page>")
def search(q, page):
"""
搜索书籍路由
:param q: 关键字 OR isbn
:param page: 页码
"""
isbn_or_key = is_isbn_or_key(q)
if isbn_or_key == 'isbn':
result = YuShuBook.search_by_isbn(q)
else:
result = YuShuBook.search_by_key(q)

return jsonify(result)
后面还会有user模块。我们这里先建立一段伪代码
1
2
3
4
5
6
7
8
#user.py
from . import web

__author__ = "gaowenfeng"

@web.route("/user/login")
def login():
return "success"
web/init.py
1
2
3
4
5
6
7
8
9
10
from flask import Blueprint

__author__ = "Jason"


web = Blueprint('web', __name__)

# 在这里导入不同文件,完成视图函数的注册
from app.web import book
from app.web import user

4. Request对象

之前我们定义的url请求路径是rest风格的 /book/search/<q>/<page>,

Flask会将<>里的值自动映射成视图函数方法的参数。

但是如果需要将方法参数做为请求参数传入进来。

就需要用到Flask内置的Request了。

Request里包含了HTTP请求的详细信息,比如param,method,url,remote ip等。

下面看一下我们之前search函数的改造来了解一下Requset获取请求参数的基本用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from flask import request

...
...
@web.route("/book/search/")
def search():
"""
搜索书籍路由
:param q: 关键字 OR isbn
:param page: 页码
"""
q = request.args['q']
page = request.args['page']
isbn_or_key = is_isbn_or_key(q)
if isbn_or_key == 'isbn':
result = YuShuBook.search_by_isbn(q)
else:
result = YuShuBook.search_by_key(q)

return jsonify(result)

Request 的args属性是一个不可变字典(继承了python内置的dict)immutableDict。里面放的就是http请求的参数。可以使用to_dict()方法获取请求参数的原生可变字典request.args.to_dict()

注意,Flask的request是基于代理模式实现的。想让request正常使用,必须确保是http请求触发的函数或视图函数中使用

5. WTForms参数验证

WTForms 是一款优秀的参数验证框架。可以将参数验证抽离出一个模块。与业务代码解耦。

使用pipenv引入WTForms
1
pipenv install wtforms
使用WTForms需要自定义一个类继承wtforms提供的Form类,然后定义参数校验规则
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from wtforms import Form, StringField, IntegerField
from wtforms.validators import Length, NumberRange

__author__ = "Jason"


class SearchForm(Form):
# 参数校验规则:
# 1.定义的属性名q,page要与要校验的参数同名
# 2.根据要传入的参数类型选择不同的Field类进行实例化
# 3.传入一个数组,作为校验规则validators
# 4.可以设置默认值
q = StringField(validators=[
DataRequired(),
Length(min=1, max=30,message="查询关键字长度必须在1-30之间")
], )
page = IntegerField(validators=[
NumberRange(min=1, max=99)
], default=1)
使用WTForms

book.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@web.route("/book/search/")
def search():
"""
搜索书籍路由
"""
# 实例化我们自定义的SearchForm,需要传入一个字典作为要校验的参数
form = SearchForm(request.args)
# validate()方法返回True/False来标示是否校验公国
if not form.validate():
# errors为错误信息提示(上面定义的message)
return jsonify(form.errors)

# 从form中获取校验后的参数,不从request里拿,因为我们可能会对数据进行预处理或者默认值的给定
q = form.q.data.strip()
page = form.page.data
isbn_or_key = is_isbn_or_key(q)
if isbn_or_key == 'isbn':
result = YuShuBook.search_by_isbn(q)
else:
result = YuShuBook.search_by_key(q)

return jsonify(result)

6. 配置文件拆分

我们之前的YuShuBook访问api分页获取数据的时候,count和start是写死的。现在来完善这一部分操作。

首先看我们之前的代码,接受了count,start两个参数

1
2
3
4
@classmethod
def search_by_key(cls, q, count=15, start=0):
url = cls.search_by_key_url.format(q, count, start)
return HTTP.get(url)
考虑以下几点:

1.我们的试图函数接受的参数是page,考虑到代码的封装性,应该尽可能的隐藏细节,

我们应该把计算count,start的过程放到YuShuBook的search_by_key方法中来做

.

2.虽然计算start的方法很简单。

但是这是一个单独的逻辑过程,不应该将这段过程放在访问api获取数据的方法中。

而应该封装成一个方法,以方法名来代替这段逻辑

.

3.count的值应该放到配置文件中,这样方便修改。

但是考虑到我们之前的配置DEGUG,IP,PORT等都属于私密配置,包括以后会使用的数据库信息等。

而COUNT的值属于可公开的配置,所以应该把配置文件拆分成secure.py和settings.py。

secure.py保存私有配置,在上传git的时候不应该上传此文件,settings.py是共有配置

下面来看修改完后的代码yushu_book.py
1
2
3
4
5
6
7
8
9
10
11
12
13
# flask提供了获取当前app的方法
from flask import current_app
...
...
@classmethod
def search_by_key(cls, q, page=1):
url = cls.search_by_key_url.format(q, current_app.config["PRE_PAGE"],
cls.calculate_start(page))
return HTTP.get(url)

@staticmethod
def calculate_start(page):
return (page-1) * current_app.config["PRE_PAGE"]
app/web/settings.py
1
PRE_PAGE = 15
app/web/secure.py
1
2
3
DEBUG = True
HOST = "0.0.0.0"
PORT = 8090
app/init.py
1
2
3
4
5
6
7
8
9
10
11
def create_app():
app = Flask(__name__)

#加载配置文件
app.config.from_object("app.secure")
app.config.from_object("app.settings")

#注册蓝图
register_blueprint(app)

return app

7. 数据表创建方式

1.模块分成

首先先将项目的层级结构重新调整一下,

将helper,httper这些放在libs目录下,作为常用库函数。

将yushu_book放在spider目录下,因为这里涉及到的访问外部api,或者访问数据库,都更像一个小的爬虫

更新完了目录结构为

  • web flask 的web视图函数蓝图
  • libs 库函数
  • form 参数验证
  • spider 数据爬取

2.数据表创建方式

1.database first

是最普标的。直接在数据库中编写DML语句,建表。

2.model first

使用建模工具,根据绘制的数据模型,生成数据表。DMA最爱

3.code first

在代码中创建业务模型(实体类),自动反向生成数据表。

程序员最爱可以专注业务模型的设计,而不是数据库的设计
不需要关心数据库表以及数据库表是如何创建的,简化思维逻辑

数据库只是用来存储数据的,他的表之间的关系应该有业务来决定

3.ORM与Code first的区别

  • Code first 关注的是相关的数据表是怎么创建的,他解决的是创建数据的问题

  • ORM(Object relation Map 不仅仅是解决数据创建的问题,还包含了数据的查询,更新,添加,删除。ORM希望我们通过操作一个个模型来间接操作数据库,所以说他的范围是更加广阔的。我们后面的所有的数据库操作都是通过ORM来操作的

定义第一个模型类以及反向生成

新建一个模块model,用于存储数据库表对应的业务模型,在编写model层的模型时,

一定要忘记数据库表,重点要放在业务模型的抽象中来

  • sqlalchemy 是一个类库,用于根据定义的model反向生成数据库表

  • Flask_SqlAlchemy 是Flask在sqlalchemy基础上封装的一个组件。提供了更加人性化的API来操作数据库

  • pipenv 按照 pipenv install flask-sqlalchemy

1.编写模型类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from sqlalchemy import Column,Integer,String

__author__ = "Jason"


class Book():
id = Column(Integer, primary_key=True, autoincrement=True)
title = Column(String(50), nullable=True)
author = Column(String(30), default="未名")
binding = Column(String(20))
publisher = Column(String(50))
price = Column(String(20))
pages = Column(Integer)
isbn = Column(String(15), nullable=True, unique=True)
summary = Column(String(1000))
image = Column(String(50))

2.将模型映射到数据库中

1.在模型类中引入Flask_SqlAlchemy,并做相关声明

app/models/book.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from sqlalchemy import Column, Integer, String
from flask_sqlalchemy import SQLAlchemy

__author__ = "Jason"

db = SQLAlchemy()

# 继承db.Model
class Book(db.Model):
id = Column(Integer, primary_key=True, autoincrement=True)
title = Column(String(50), nullable=True)
author = Column(String(30), default="未名")
binding = Column(String(20))
publisher = Column(String(50))
price = Column(String(20))
pages = Column(Integer)
isbn = Column(String(15), nullable=True, unique=True)
summary = Column(String(1000))
image = Column(String(50))
2.在app中插入Flask_SqlAlchemy对象

app/init.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from app.models.book import db

def create_app():
app = Flask(__name__)
app.config.from_object("app.secure")
app.config.from_object("app.settings")
register_blueprint(app)

# 将db插入app
db.init_app(app)

# 创建所有的表
db.create_all()

return app
3.书写配置文件

app/secure.py

1
2
3
4
5
# key-SQLALCHEMY_DATABASE_URI不能随意修改

# URI规则:数据库类型+驱动://账号:密码@host:port/dbname

SQLALCHEMY_DATABASE_URI = "mysql+cymysql://root:root@localhost:3306/fisher"

cysql驱动需要安装

1
pipenv install cymysql

上面的操作完成以后启动项目,会报如下错误

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/Users/gaowenfeng/.local/share/virtualenvs/fisher-4xlkyzha/lib/python3.6/site-packages/flask_sqlalchemy/__init__.py:794: FSADeprecationWarning: SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and will be disabled by default in the future. Set it to True or False to suppress this warning.
'SQLALCHEMY_TRACK_MODIFICATIONS adds significant overhead and '
Traceback (most recent call last):
File "/Users/gaowenfeng/project/pycharm/fisher/fisher/fisher.py", line 10, in <module>
app = create_app()
File "/Users/gaowenfeng/project/pycharm/fisher/fisher/app/__init__.py", line 14, in create_app
db.create_all()
File "/Users/gaowenfeng/.local/share/virtualenvs/fisher-4xlkyzha/lib/python3.6/site-packages/flask_sqlalchemy/__init__.py", line 963, in create_all
self._execute_for_all_tables(app, bind, 'create_all')
File "/Users/gaowenfeng/.local/share/virtualenvs/fisher-4xlkyzha/lib/python3.6/site-packages/flask_sqlalchemy/__init__.py", line 940, in _execute_for_all_tables
app = self.get_app(app)
File "/Users/gaowenfeng/.local/share/virtualenvs/fisher-4xlkyzha/lib/python3.6/site-packages/flask_sqlalchemy/__init__.py", line 912, in get_app
'No application found. Either work inside a view function or push'
RuntimeError: No application found. Either work inside a view function or push an application context. See http://flask-sqlalchemy.pocoo.org/contexts/.

Process finished with exit code 1

这是因为在Flask中,不是实例化了app核心对象,其他的代码就可以直接用到。所以在上面第二部create_all()方法中,应该将app传入

修改后的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
def create_app():
app = Flask(__name__)
app.config.from_object("app.secure")
app.config.from_object("app.settings")
register_blueprint(app)

# 将db插入app
db.init_app(app)

# 创建所有的表
db.create_all(app=app)

return app

从 Windows 过度到 Mac 必备快捷键对照表

发表于 2018-09-07 | 分类于 Mac

从 Windows 过度到 Mac 必备快捷键对照表

Mac 键盘符号说明

  • ⌘ == Command
  • ⇧ == Shift
  • ⇪ == Caps Lock
  • ⌥ == Option
  • ⌃ == Control
  • ↩ == Return/Enter
  • ⌫ == Delete
  • ⌦ == 向前删除键(Fn+Delete)
  • ↑ == 上箭头
  • ↓ == 下箭头
  • ← == 左箭头
  • → == 右箭头
  • ⇞ == Page Up(Fn+↑)
  • ⇟ == Page Down(Fn+↓)
  • Home == Fn + ←
  • End == Fn + →
  • ⇥ == 右制表符(Tab键)
  • ⇤ == 左制表符(Shift+Tab)
  • ⎋ == Escape (Esc)
  • ⏏ == 电源开关键

Ctrl

Win 快捷键 Mac 快捷键 介绍
Ctrl + F Command + F 在当前文件进行文本查找
Ctrl + R Command + R 在当前文件进行文本替换
Ctrl + Z Command + Z 撤销
Ctrl + Y Command + Delete 删除光标所在行 或 删除选中的行
Ctrl + D Command + D 复制光标所在行 或 复制选择内容,并把复制内容插入光标位置下面
Ctrl + W Option + 方向键上 递进式选择代码块。可选中光标所在的单词或段落,连续按会在原有选中的基础上再扩展选中范围
Ctrl + E Command + E 显示最近打开的文件记录列表
Ctrl + N Command + O 根据输入的 类名 查找类文件
Ctrl + J Command + J 插入自定义动态代码模板
Ctrl + P Command + P 方法参数提示显示
Ctrl + U Command + U 前往当前光标所在的方法的父类的方法 / 接口定义
Ctrl + B Command + B 进入光标所在的方法/变量的接口或是定义处,等效于 Ctrl + 左键单击
Ctrl + / Command + / 注释光标所在行代码,会根据当前不同文件类型使用不同的注释符号
Ctrl + F1 Command + F1 在光标所在的错误代码处显示错误信息
Ctrl + F11 Option + F3 选中文件 / 文件夹,使用助记符设定 / 取消书签
Ctrl + F12 Command + F12 弹出当前文件结构层,可以在弹出的层上直接输入,进行筛选
Ctrl + Space Control + Space 基础代码补全,默认在 Windows 系统上被输入法占用,需要进行修改,建议修改为 Ctrl + 逗号
Ctrl + Delete Option + Fn+ Delete 删除光标后面的单词或是中文句
Ctrl + BackSpace Option + Delete 删除光标前面的单词或是中文句
Ctrl + 1,2,3…9 Control + 1,2,3…9 定位到对应数值的书签位置
Ctrl + 加号 Command + 加号 展开代码
Ctrl + 减号 Command + 减号 折叠代码
Ctrl + 左键单击 Control + 左键单击 在打开的文件标题上,弹出该文件路径
Ctrl + 左方向键 Option + 左方向键 光标跳转到当前单词 / 中文句的左侧开头位置
Ctrl + 右方向键 Option + 右方向键 光标跳转到当前单词 / 中文句的右侧开头位置
Ctrl + 前方向键 预设中没有该快捷键 等效于鼠标滚轮向前效果
Ctrl + 后方向键 预设中没有该快捷键 等效于鼠标滚轮向后效果

Alt

Win 快捷键 Mac 快捷键 介绍
Alt + ` Control + V 显示版本控制常用操作菜单弹出层
Alt + F1 Option + F1 显示当前文件选择目标弹出层,弹出层中有很多目标可以进行选择
Alt + F7 Option + F7 查询所选对象/变量被引用
Alt + Enter Option + Enter IntelliJ IDEA 根据光标所在问题,提供快速修复选择,光标放在的位置不同提示的结果也不同
Alt + Insert Command + N 代码自动生成,如生成对象的 set / get 方法,构造函数,toString() 等
Alt + 左方向键 Control + 左方向键 切换当前已打开的窗口中的子视图,比如Debug窗口中有Output、Debugger等子视图,用此快捷键就可以在子视图中切换
Alt + 右方向键 Control + 右方向键 切换当前已打开的窗口中的子视图,比如Debug窗口中有Output、Debugger等子视图,用此快捷键就可以在子视图中切换
Alt + 前方向键 Control + 前方向键 当前光标跳转到当前文件的前一个方法名位置
Alt + 后方向键 Control + 后方向键 当前光标跳转到当前文件的后一个方法名位置
Alt + 1,2,3…9 Command + 1,2,3…9 显示对应数值的选项卡,其中 1 是 Project 用得最多

Shift

Win 快捷键 Mac 快捷键 介绍
Shift + F11 Command + F3 弹出书签显示层
Shift + Tab Shift + Tab 取消缩进
Shift + Enter Shift + Enter 开始新一行。光标所在行下空出一行,光标定位到新行位置
Shift + 左键单击 Shift + 左键单击 在打开的文件名上按此快捷键,可以关闭当前打开文件

Ctrl + Alt

Win 快捷键 Mac 快捷键 介绍
Ctrl + Alt + L Command + Option + L 格式化代码,可以对当前文件和整个包目录使用
Ctrl + Alt + O Control + Option + O 优化导入的类,可以对当前文件和整个包目录使用
Ctrl + Alt + T Command + Option + T 对选中的代码弹出环绕选项弹出层
Ctrl + Alt + S Command + 逗号 打开 IntelliJ IDEA 系统设置
Ctrl + Alt + Enter Command + Option + Enter 光标所在行上空出一行,光标定位到新行
Ctrl + Alt + 左方向键 Command + Option + 左方向键 退回到上一个操作的地方
Ctrl + Alt + 右方向键 Command + Option + 右方向键 前进到上一个操作的地方

Ctrl + Shift

Win 快捷键 Mac 快捷键 介绍
Ctrl + Shift + F Command + Shift + F 根据输入内容查找整个项目 或 指定目录内文件
Ctrl + Shift + R Command + Shift + R 根据输入内容替换对应内容,范围为整个项目 或 指定目录内文件
Ctrl + Shift + J Control + Shift + J 自动将下一行合并到当前行末尾
Ctrl + Shift + Z Command + Shift + Z 取消撤销
Ctrl + Shift + W Option + 方向键下 递进式取消选择代码块。可选中光标所在的单词或段落,连续按会在原有选中的基础上再扩展取消选中范围
Ctrl + Shift + N Command + Shift + O 通过文件名定位 / 打开文件 / 目录,打开目录需要在输入的内容后面多加一个正斜杠
Ctrl + Shift + U Command + Shift + U 对选中的代码进行大 / 小写轮流转换
Ctrl + Shift + T Command + Shift + T 对当前类生成单元测试类,如果已经存在的单元测试类则可以进行选择
Ctrl + Shift + C Command + Shift + C 复制当前文件磁盘路径到剪贴板
Ctrl + Shift + B Control + Shift + B 跳转到类型声明处
Ctrl + Shift + / Command + Option + / 代码块注释
Ctrl + Shift + [ Command + Shift + [ 选中从光标所在位置到它的顶部中括号位置
Ctrl + Shift + ] Command + Shift + ] 选中从光标所在位置到它的底部中括号位置
Ctrl + Shift + 加号 Command + Shift + 加号 展开所有代码
Ctrl + Shift + 减号 Command + Shift + 减号 折叠所有代码
Ctrl + Shift + F7 Command + Shift + F7 高亮显示所有该选中文本,按Esc高亮消失
Ctrl + Shift + F12 Command + Shift + F12 编辑器最大化
Ctrl + Shift + Enter Command + Shift + Enter 自动结束代码,行末自动添加分号
Ctrl + Shift + Backspace Ctrl + Shift + Backspace 退回到上次修改的地方
Ctrl + Shift + 1,2,3…9 Control + Shift + 1,2,3…9 快速添加指定数值的书签
Ctrl + Shift + 左键单击 Command + Shift + 左键单击 把光标放在某个类变量上,按此快捷键可以直接定位到该类中
Ctrl + Shift + 左方向键 Option + Shift + 左方向键 在代码文件上,光标跳转到当前单词 / 中文句的左侧开头位置,同时选中该单词 / 中文句
Ctrl + Shift + 右方向键 Option + Shift + 右方向键 在代码文件上,光标跳转到当前单词 / 中文句的右侧开头位置,同时选中该单词 / 中文句
Ctrl + Shift + 前方向键 Command + Shift + 前方向键 光标放在方法名上,将方法移动到上一个方法前面,调整方法排序
Ctrl + Shift + 后方向键 Command + Shift + 后方向键 光标放在方法名上,将方法移动到下一个方法前面,调整方法排序

Alt + Shift

Win 快捷键 Mac 快捷键 介绍
Alt + Shift + N Option + Shift + B 选择 / 添加 task
Alt + Shift + 左键双击 Option + Shift + 左键双击 选择被双击的单词 / 中文句,按住不放,可以同时选择其他单词 / 中文句
Alt + Shift + 前方向键 Option + Shift + 前方向键 移动光标所在行向上移动
Alt + Shift + 后方向键 Option + Shift + 后方向键 移动光标所在行向下移动

Ctrl + Shift + Alt

Win 快捷键 Mac 快捷键 介绍
Ctrl + Shift + Alt + V Command + Shift + Option + V 无格式黏贴
Ctrl + Shift + Alt + S Command + ; 打开当前项目设置

其他

Win 快捷键 Mac 快捷键 介绍
F2 F2 跳转到下一个高亮错误 或 警告位置
F4 F4 编辑源
F11 F3 添加书签
F12 F12 回到前一个工具窗口
Tab Tab 缩进
ESC ESC 从工具窗口进入代码文件窗口

Google_cloud Centos7搭建科学上网

发表于 2018-09-07 | 分类于 Centos

先切换成 root 账户,使用SSH客户端登录

  • 进入谷歌云实例面板,打开 SSH 窗口
  • 切换到root角色
1
sudo -i
  • 修改SSH配置文件/etc/ssh/sshd_config
1
2
3
4
5
6
7
8
vi /etc/ssh/sshd_config


# Authentication:
PermitRootLogin yes //默认为no,需要开启root用户访问改为yes

# Change to no to disable tunnelled clear text passwords
PasswordAuthentication yes //默认为no,改为yes开启密码登陆
  • 给root用户设置密码
1
passwd root
  • 重启SSH服务使修改生效
1
/etc/init.d/ssh restart

使用一键搭建脚本

1
2
3
wget -N https://raw.githubusercontent.com/ToyoDAdoubi/doubi/master/ssr.sh
chmod +x ssr.sh
bash ssr.sh

bbr加速

1
2
3
4
yum -y install wget
wget --no-check-certificate https://github.com/teddysun/across/raw/master/bbr.sh
chmod +x bbr.sh
./bbr.sh

会提示重启,重启完就可以快乐的玩耍了!

Redis Desktop Manager编译

发表于 2018-09-01 | 分类于 Mac

Redis Desktop Manager(RDM)是一款跨平台的Redis桌面客户端, 其支持SSH隧道连接, 批量删除等特性, 是非常优秀的Redis管理工具.

首先, 如果没有安装brew, 请通过以下脚本安装.

1
$ /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"

安装qt5、openssl、libssh2(不同版本的RDM依赖可能不同, 请根据实际编译时的报错信息安装相应的依赖).

1
2
$ brew update && brew install qt5 openssl libssh2
$ export PATH=/usr/local/opt/qt5/bin:$PATH

克隆RDM版本库, 编译并安装CrashReporter.

1
2
3
4
5
6
7
8
9
10
11
12
13
### 克隆rdm仓库
git clone --recursive https://github.com/uglide/RedisDesktopManager.git rdm && cd rdm

### 编译CrashReporter
cd 3rdparty/crashreporter && qmake DESTDIR=./bin && make -s -j 8

### 编译cr可能报错,切换到7ec6f00版本编译
cd 3rdparty/crashreporter
git checkout 7ec6f00
qmake DESTDIR=./bin && make -s -j 8

### 拷贝编译好的cr到rdm下
mkdir -p ../../bin/osx/release && mv bin/crashreporter ../../bin/osx/release

编译RDM

1
2
$ cd ../../src && ./configure
$ qmake CONFIG-=debug && make -s -j 8 && cd ..

将bin/osx/release文件夹下生成的rdm.app拖到应用程序文件夹中即可完成安装.

12…17
卡布奇诺

卡布奇诺

记录编程中遇到的坑

167 日志
18 分类
102 标签
GitHub E-Mail
© 2015 — 2018 卡布奇诺@心情
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4