Vim global 命令

global 命令结合了 Ex 命令和 Vim 模式匹配的能力,借助该命令,可以在指定模式的所有匹配行上运行 Ex 命令。global 命令是除了点操作和宏命令之外,最强大的 Vim 工具之一。

Basic

global 命令基本形式,可以读作在 range 上,如果行匹配 pattern,那么执行全局命令 cmd

:[range] global[!]/{pattern}/[cmd]

说明:

  • 缺省情况下,global 命令作用范围是整个文件 (%).
  • 除了 global 还有 :vglobal 表示反转,在没有匹配 pattern 的行上执行 cmd
  • pattern 与查找历史相互关联,如果留空,Vim 会自动使用当前的查找模式
  • [cmd] 可以是 :global 之外的任何 Ex 命令,如果不指定 [cmd], 那么缺省是 :print

Use case

删除所有包含模式的行

比如删除所有空行

:g/^$/d

拷贝包含 TODO 的行到文件末尾

代码中经常会写一些 TODO , 如果想要统一处理这些 TODO,可以使用

:g/TODO/t$

将 TODO 行拷贝到文件末尾,来处理。


2019-09-22 vim , vim-global , ex-command , vim-mode , editor , linux

Oracle 提供免费的云服务

前两天在 Twitter 上看到有人分享新闻说,Oracle 发布了新的云服务政策,提供 Always Free 的主机和对象存储还是数据库,所以就看了一下,搜了一下新闻,还真的有 Oracle Offers Always Free Autonomous Database and Cloud Infrastructure,所以就注册一下。但是当天晚上创建 VM 的时候提示我 “out of host capacity”,Google 了一下才发现,原来这条消息早就在羊毛党炸锅了,不亚于 Google Cloud Platform 当年的新闻。不过我个人作为学习使用,并没有违背 Oracle 的政策。所以我就等着 Oracle 解决 “out of host capacity” 的问题。于是等到周末,突然想起这件事情,就登录账号是了一下,确实可以创建了。

这里再整理一下 Oracle 提供的服务内容,根据它官方的博文,Oracle 提供的服务没有像其他云服务提供商一样提供 12 个月的免费体验,而是对于基础服务,比如 Compute VMs, Database, Block and Object Storage, and Load Balancer, 等等只要在用,不超过限额,那么就在账号有效期内免费使用。

甲骨文的永久免费 VPS,可以永久免费使用 2 台 AMD 服务器,外加 4 台 Arm-based 云服务器,还有数据库,对象存储等等服务。

这里引用 Oracle 官方的文章:

The new program enables developers to build applications using any language and framework on top of Oracle Cloud Infrastructure and Autonomous Database. They can get started quickly without waiting for IT to provision and learn new technologies such as artificial intelligence and machine learning. Enterprises can use Free Tier to prototype, prove out new technologies, and do testing before moving production workloads to the cloud. They can sample robust enterprise infrastructure capabilities like load balancing and storage cloning. Additionally, students can learn how to use the latest technologies and become better prepared for their careers.

Oracle 提供的免费服务包括两个部分:

  • Always Free services, which provide access to Oracle Cloud services for an unlimited time
  • Free Trial, which provides $300 in credits for 30 days to try additional services and larger shapes 而 Always Free 项目涵盖了开发,测试应用必要的各种服务,包括 Oracle Autonomous Database, Compute VMs, Block Volumes, Object and Archive Storage, and Load Balancer 等等。具体来说:

  • 2 Autonomous Databases (Autonomous Data Warehouse or Autonomous Transaction Processing), each with 1 OCPU and 20 GB storage
  • 2 Compute VMs, each with 1/8 OCPU and 1 GB memory
  • 2 Block Volumes, 100 GB total, with up to 5 free backups
  • 10 GB Object Storage, 10 GB Archive Storage, and 50,000/month API requests
  • 1 Load Balancer, 10 Mbps bandwidth
  • 10 TB/month Outbound Data Transfer
  • 500 million ingestion Datapoints and 1 billion Datapoints for Monitoring Service
  • 1 million Notification delivery options per month and 1000 emails per month

这里可以看到提供的主机是 1/8 OCPU,什么是 OCPU ? 官方的解释是:OCPU 定义为等同于启用了超线程的 Intel Xeon 处理器一个物理核心的 CPU 容量或者等同于 Orcale SPARC 的一个物理核心。1 对于 Intel Xeon 处理器,每个 OCPU 对应于两个硬件执行线程 ( vCPU )。2

AWS, Microsoft 和 Google 提供的云服务都是以 vCPU 作为计算单元,每一个 vCPU 都表示 Intel Xeon 核心的的一个超线程。一个标准的 Intel 核心如果开启了超线程,有两个线程。

Compute

计算实例配置:

Shape: VM.Standard.E2.1.Micro
Processor: 1/8th of an OCPU with the ability to use additional CPU resources
Memory: 1 GB
Networking: Includes one VNIC with one public IP address and up to 480 Mbps network bandwidth
Operating System: Your choice of one of the following Always Free-eligible operating systems:
Oracle Linux
Canonical Ubuntu Linux
CentOS Linux

Block Volume

存储

100 GB total of combined boot volume and block volume Always Free Block Volume storage.

Five total volume backups (boot volume and block volume combined).

Object Storage

免费账户:

20 GiB of combined Object Storage and Archive Storage
50,000 Object Storage API requests per month

付费账户:

10 GiB of Object Storage
10 GiB of Archive Storage
50,000 Object Storage API requests per month

Server

网站登录地址:

韩国区域后台管理地址:

Server Config

Check this post

ubuntu@instance: $ sudo su - root
root@instance: # passwd # set password of root user
adduser yourname
apt install vim
update-alternatives --config editor   # choose vim
visudo # add  yourname ALL=(ALL:ALL) NOPASSWD:ALL
vi /etc/ssh/sshd_config # AllowUsers yourname
/etc/init.d/ssh reload
# copy ssh pub to ~/.ssh/authorized_keys

then

ssh -p 22 yourname@ip

另外我个人建议可以登陆后修改一下默认的 SSH 端口。

Ping 设置

甲骨文的主机创建之后无法 ping 通,是因为在网络安全组里面禁用了 ICMP ping。

点击子网,在安全列表中,找到入站规则,允许 ICMP (IP 协议选项中)。

UuQp

防火墙设置

实例管理页面,Virtual Cloud Network Details,Security Lists, 在防火墙安全策略里面将需要的端口配上。

另外需要特别注意需要登录机器配置实例上的防火墙。

sudo iptables -P INPUT ACCEPT
sudo iptables -P FORWARD ACCEPT
sudo iptables -P OUTPUT ACCEPT
sudo iptables -F

甲骨文的 Ubuntu 镜像内置了防火墙规则,即使在面板上开通了端口白名单,重启之后端口依然会被防火墙拦住,所以每次都得手动执行上面的命令。主要原因是 Oracle 自带的 Ubuntu 镜像默认设置了 iptable 规则,可以手动关闭:

sudo apt purge netfilter-persistent
sudo reboot

或者可以直接删除 iptabls 的配置文件

rm -rf /etc/iptables && reboot

卸载停止不需要的服务

sudo systemctl stop rpcbind.socket
sudo systemctl disable rpcbind.socket

卸载 Oracle 后台监控

sudo systemctl stop snap.oracle-cloud-agent.oracle-cloud-agent.service
sudo systemctl disable snap.oracle-cloud-agent.oracle-cloud-agent.service

QA

新开的实例,我一般都会直接修改其 ssh 登录端口,而这台机器白天开了之后设置了 sshd 端口,并且 reload 了 sshd 设置,晚上回到家后发现

connect to host port xxx:xxx No route to host

这个时候我想着要不就是端口没有生效,要不就是防火墙问题,所以通过 Oracle 后台远程登录的方式进入到机器上,先关了防火墙,然后去控制后台配置了端口,然后再连接即可。

reference


2019-09-22 linux , oracle , gcp , cloud , vps , server

ModuleNotFoundError: No module named 'marshmallow.compat'

今天重新部署一个服务时发现了一个奇怪的错误,之前用很久都没有错,重新部署一个新环境就发生了这样的问题,问题应该就出现在新依赖的包中。

  File "/usr/local/lib/python3.6/dist-packages/flask_marshmallow/fields.py", line 15, in <module>
	from marshmallow.compat import iteritems
ModuleNotFoundError: No module named 'marshmallow.compat'

所以搜了一圈在原来的老环境中发现依赖的 marshmallow 是 2.15.4 重新安装这个版本之后解决了这个问题。那就可能是 marshmallow 这个包升级到 3.2.0 之后的问题了。

解决办法:

pip install marshmallow==2.15.4

后来查看 marshmallow 的升级说明,和一些 issue 发现 marshmallow 这个包 2.x 和 3.x 没有完全兼容,看来又要改代码了。


2019-09-22 flask , marshmallow , flask-marshmallow

又一 Google 服务停止:Google Translator Toolkit 关闭

前两天收到一份邮件,标题写着 Google Translator Toolkit to be shut down on December 4, 20191,感叹又一款良心服务要终结其生命了。虽然不常用 Google 翻译工具包,甚至很长时间这个翻译工具都偷偷的被隐藏在翻译页面的角落里面,但是不得不说这个工具曾经帮助我翻译过不少文档内容,也非常适合学习。Google 翻译工具包提供上传文档自动翻译,术语翻译等等功能,结合 Google Translate 的帮助能非常快速的辅助完成一篇文档的翻译。在自动翻译完成的基础上可以人工的进行修改润色。更甚至可以添加好友一同翻译,将文档分享给他人。至今为止我也只有在 Google 翻译工具包中体验过如此完整的翻译体验。

很多其他的翻译工具大多只能够提供字符串的辅助翻译,比如之前帮别人翻译过 Android 应用内文本,这些工具都非常的简陋,甚至有些术语都不能自动帮忙翻译,还需要一个字一个字的输入。虽然 Google 在邮件中给出了一些 alternative 但我只想说这些工具要么就是限制平台的,要么就看起来不像是一个完整的产品。体验没有一个能比得上 Google Translator Toolkit.

理想中的翻译工具应该有的功能

  • 导入术语库,自动翻译
  • 自动总结翻译习惯,提取常用翻译
  • 多人协作翻译,提供校对审阅确认等机制

几个社区常用的翻译网站

这些网站多多少少我都有用过,目前 crowdin 做的还不错,有机器翻译自动提示,格式化处理的也比较好,快捷键也很合适。

另外开源版本的 Pootle,也有不少人推荐,用 Python + Django 写的。不过还没有尝试。

另一个开源的本地化工具 Weblate


2019-09-22 google-translator , translator

gpg: keyserver receive failed: Server indicated a failure 解决

sudo add-apt-repository 添加 PPA 时突然遇到 gpg 添加 key 失败,大概知道可能是因为网络问题,但是这个问题在我家里的网络一直存在,非常恼人。

gpg: keyserver receive failed: Server indicated a failure

所以我想从根本上解决这个问题,这个问题的根源可能是因为网络问题导致 gpg key 没有被导入到本地。所以如果能够手动下载 gpg public key 然后手动导入不就可以了?

所以随意打开一个 PPA,比如

在页面中 Technical details about this PPA 下方有 Signing key 点击该链接会跳转到一个签名的 key 列表,在该列表中找到报错内容中的 KEY

W: GPG error: http://ppa.launchpad.net/eosrei/fonts/ubuntu bionic InRelease: The following signatures couldn't be verified because the public key is not available: NO_PUBKEY ADA83EDC62D7EDF8

复制该链接,然后使用下面的命令:

curl -sL https://keyserver.ubuntu.com/pks/lookup\?op\=get\&search\=0xada83edc62d7edf8  | sudo apt-key add -

等出现 OK 即可。注意这个 URL 中的 key 需要在前面加上 0x,否则会找不到该 key.

reference


2019-09-20 gpg , ppa , ubuntu , apt-repository

Linux 下使用 emoji

Ubuntu 或者其他一些 Linux 发行版 (Debian/Ubuntu/Linux Mint) 会内置 Google Noto Color emoji font,如果没有也可以直接通过一个命令直接安装 Noto Color emoji.

安装字体

首先要安装支持 Emoji 的字体,个人比较喜欢 Google Noto Color Emoji,这是 Google 开源的用于 Android 的字体。并且支持力度一直都非常大

Noto color Emoji

直接安装

sudo apt install fonts-noto-color-emoji

或者从这里下载字体文件:

将字体文件放到 ~/.fonts 目录中。

然后运行 sudo fc-cache -f -v

ttf-ancient-fonts

Symbola font 可以将绝大部分 emoji 显示为单色的图案。

Ubuntu 系安装:

sudo apt-get install ttf-ancient-fonts

twemoji

Twitter 的 emoji 方案:

sudo apt-add-repository ppa:eosrei/fonts
sudo apt-get update
sudo apt-get install fonts-twemoji-svginot

Emoji One Font

另一种可选方案:

使用 EmojiOne Picker 来输入 Emoji

Ubuntu 上可以使用 EmojiOne 来输入 Emoji

PPA:

sudo add-apt-repository ppa:ys/emojione-picker
sudo apt-get update
sudo apt-get install emojione-picker

如果在 Ubuntu 18.04 下安装 PPA 有问题,参考这里 解决。

先将 apt source 下的内容改成

sudo vi /etc/apt/sources.list.d/ys-ubuntu-emojione-picker-bionic.list

修改为

deb http://ppa.launchpad.net/ys/emojione-picker/ubuntu xenial main

然后再安装

sudo apt install emojione-picker -t xenial

在 Chrome 中使用 Emoji

在安装了 Noto Color Emoji 之后,记得需要在 Chrome 的设置中将 Chrome 的字体设置成 Noto 字体。

  • rebuild font cache
  • restart Chrome

reference


2019-09-12 linux , ubuntu , linux-mint , mint , emoji

Java 查漏补缺:Java 8 中接口 default 方法

Java 8 新特性:

  • lambda expressions
  • functional interfaces
  • method references
  • streams
  • Optional

还有 interface 中的 staticdefault 方法。

基本使用

Java 8 允许在接口中定义默认方法。

interface Collection {

	void add();

	default void debug(){
		System.out.println("put the key in");
	}
}

为什么要引入 default 方法

和接口中定义的其他方法一样,default 方法默认是 public

接口是用来定义类的行为的,如果要在接口中新添加方法,那么所有实现此接口的类都需要强制的实现新添加的方法。而 default 方法就可以规避该问题。

当多个接口定义了相同 default 方法

当一个类实现的多个接口定义了相同的 default 方法,那么编译时会失败。需要子类 Override 该方法实现。在子类中可以通过 Interface.super.xxx() 方法来调用接口的 default 方法。

@Override
public void turnOnAlarm() {
	Vehicle.super.turnOnAlarm();
	Alarm.super.turnOnAlarm();
}

接口中的 static 方法

除了 default 方法,Java 8 也允许在接口中定义 static 方法。

接口中的静态方法属于类,在接口中定义 static 方法和在类中定义一样。

因为 Java 不支持多继承,所以在遇到一些代码共享的时候,就不能通过多继承来实现,通常的做法是定义一个静态类,包含可能被多个类使用到的共同的方法,比如 Java 中的 Collections 类。

而通过接口中的 static 方法可以提高代码的 cohesion,将相关的逻辑集中到一起,而不用另外定义一个 Object。

同样 Abstract 类也能做到,但是和抽象类的区别在于,抽象类是有 Constructors, state, behavior 的。

总结

理想状态下,接口不应该封装具体的行为,只应该用来定义某类型的公开接口。

但是为了弥补 Java 不能多继承而带来的一些缺点,Java 8 中引入接口的 default 和 static 方法,也肯定是 JDK 工程师仔细考量后的一个权衡。

reference


2019-09-10 java , java8 , interface

理解 GraphQL Schema 结构定义

在上一篇 GraphQL 使用 中了解了 GraphQL 大致用法,如果要更加深入的了解 GraphQL ,那就不得不重新从 Schema 来认识 GraphQL,说到底 GraphQL 还是一个强类型定义,客户端可操作的类型都是需要提前定义好的,这个结构就是这篇文章的重点 – Schema.

因为已经有很多的语言已经实现了 GraphQL,官方不能以某一个语言来具体表达,所以他们自定义了一套表示 GraphQL Schema 的简单表达。1

GraphQL 支持的数据操作

GraphQL 对数据支持的操作:

  • 查询 Query,获取数据的基本查询
  • 变更 Mutation,支持对数据的增删改等操作
  • 订阅 Subscription,用于监听数据变动,并依靠 WebSocket 等协议推送变动消息

内置类型

GraphQL 中的 Type 可以分成,Scalar Type(标量类型),和 Object Type(对象类型)。

GraphQL 自带一些默认类型

  • Int, 32 位有符号整型
  • Float,双精度有符号浮点类型。
  • String, UTF-8 字符串
  • Boolean,true or false
  • ID, 表示唯一标识,通常用来作为主键来获取内容。ID type 和 String 类型使用相同方式序列化,但是如果定义为 ID,那么不可读。

不同的实现,可能会有自己的类型,比如 Date 类型,具体实现要看各个语言。

Enumeration types

枚举类型,将输入固定为几个预定义的值。

Lists and Non-Null

GraphQL 可以表达非空,使用 ! 即可。

type Character {
  name: String!
  appearsIn: [Episode]!
}

或者使用如下来表达非空数组

myField: [String]!

或者可以使用

myField: [String!]

来表达数组可以为空,但是元素不能为 null

Interfaces

GraphQL 支持 Interfaces,直面来看就是接口,GraphQL 可以定义接口,每个接口可以有不同的实现。 [^interface]

[^interface](https://graphql.org/learn/schema/#interfaces)

Type Modifier 类型修饰符

类型修饰符用来修饰类型。

比如定义 User 对象。

type User {
    id: ID!
    name: String!
    articles: [Article]!
    bookNames: [String!]!
}

分别表示了:

  • 列表 [Type]
  • 非空 Type!
  • 列表非空 [Type]!
  • 列表非空,列表内容类型非空 [Type!]!

reference

  1. https://graphql.org/learn/schema/ 


2019-09-08 graphql , schema , graphql-schema , datatype

从 MySQL 迁移到 PostgreSQL 方案调研

之前的文章 PostgreSQL 初识PostgreSQL 数据类型 大致的把 PostgreSQL 了解了一下,那么接下来就是真正地把它用起来。

PGLoader

开源迁移工具,通过一行命令即可做到无缝迁移

PGLoader 原本可以将不同数据源导入到 PostgreSQL 数据库,MySQL 只是它支持的一种。PGLoader 使用 PostgreSQL 的 COPY 命令将数据从源数据库或文件复制到目标 PostgreSQL 数据库中。

安装

Debian 系:

sudo apt-get install pgloader

验证

$ pgloader --version
pgloader version "3.5.2"
compiled with SBCL 1.4.5.debian

或者使用 Docker

docker pull dimitri/pgloader
docker run --rm --name pgloader dimitri/pgloader:latest pgloader --version
docker run --rm --name pgloader dimitri/pgloader:latest pgloader --help

或者参考官方自行编译安装。1

Usage

PGLoader 通过命令来复制,所以必须要配置一个 PGLoader 可以访问 PostgreSQL 的用户来方便执行命令。PostgreSQL 使用角色来管理数据库访问,需要配置该角色与 PGLoader 命令执行用户同一名称。之前提到过 PostgreSQL 普通数据不要轻易使用超级用户来管理,但是 PGLoader 需要使用非常多的权限来管理、访问、加载表中数据,因此需要授予 PGLoader 超级用户权限。

PostgreSQL 用户

创建超级用户 pgloader 使用 -P 来为用户创建密码:

sudo -u postgres createuser --superuser pgloader -P

创建数据库,准备导入该数据库:

sudo -u postgres createdb quotes -O pgloader

MySQL 准备

PostgreSQL 准备工作结束,假设 MySQL 的超级用户和密码都已经设置好,并且该用户拥有要迁移的数据库所有权限。本地执行验证:

mysql -h localhost -u root -p

迁移

注意在任何会影响到数据库数据完整性的操作前,备份数据库,虽然 PGLoader 迁移时并不会修改或者删除数据,但是必要的备份一定不能掉以轻心。使用 mysqldump 来备份数据库。

在本地做一个简单的测试,本地 MySQL 数据库 quotes:

mysql://root@localhost:3306/quotes

如果想要把这个数据库迁移到 PostgreSQL 中。

postgresql://user:pass@localhost:5432/quotes

执行

pgloader mysql://root:password@localhost:3306/quotes postgresql://pgloader:password@localhost:5432/quotes

pgloader 接受两个参数,一个是源数据库连接,一个是目标数据库连接。

➜ build/bin/pgloader mysql://root:password@localhost:3306/quotes postgresql://postgres:password@localhost:5432/quotes
2019-09-07T09:56:38.030000+08:00 LOG pgloader version "3.6.26cc9ca"
2019-09-07T09:56:38.047000+08:00 LOG Migrating from #<MYSQL-CONNECTION mysql://root@localhost:3306/quotes {1005B1DE43}>
2019-09-07T09:56:38.048000+08:00 LOG Migrating into #<PGSQL-CONNECTION pgsql://postgres@localhost:5432/quotes {1005D6D903}>
2019-09-07T09:56:38.335000+08:00 LOG report summary reset
			 table name     errors       rows      bytes      total time
-----------------------  ---------  ---------  ---------  --------------
		fetch meta data          0          1                     0.100s
		 Create Schemas          0          0                     0.004s
	   Create SQL Types          0          0                     0.005s
		  Create tables          0          2                     0.026s
		 Set Table OIDs          0          1                     0.004s
-----------------------  ---------  ---------  ---------  --------------
		  quotes.quotes          0        100    15.9 kB          0.058s
-----------------------  ---------  ---------  ---------  --------------
COPY Threads Completion          0          4                     0.058s
 Index Build Completion          0          0                     0.000s
		Reset Sequences          0          0                     0.015s
		   Primary Keys          0          0                     0.000s
	Create Foreign Keys          0          0                     0.000s
		Create Triggers          0          0                     0.001s
		Set Search Path          0          1                     0.001s
	   Install Comments          0          0                     0.000s
-----------------------  ---------  ---------  ---------  --------------
	  Total import time          ✓        100    15.9 kB          0.075s

校验数据

sudo -u postgres psql
\c quotes
select * from quotes.quotes limit 1;

这里就会发现,导入的数据没有默认到 public Schema 下,而是在自己的 Schema 下。在 PostgreSQL 中,每当我们创建一个数据库,都会自动产生一个 public Schema,当登录数据库查询时,如果没有加特定的 Schema,则会默认使用 public.

在使用的时候有几个问题,Debian 源中的 pgloader 有些老,3.5.2 的版本似乎有些 Bug,我在使用时报错

2019-09-07T02:03:45.044000Z LOG Migrating from #<MYSQL-CONNECTION mysql://root@localhost:3306/imdb {1005805E43}>
2019-09-07T02:03:45.047000Z LOG Migrating into #<PGSQL-CONNECTION pgsql://pgloader@localhost:5432/quotes {1005A56E73}>
KABOOM!
INFO: Control stack guard page unprotected
Control stack guard page temporarily disabled: proceed with caution

What I am doing here?

Control stack exhausted (no more space for function call frames).
This is probably due to heavily nested or infinitely recursive function
calls, or a tail call that SBCL cannot or has not optimized away.

PROCEED WITH CAUTION.

所以最后不得不直接使用源码编译使用最新版。

➜ build/bin/pgloader --version
pgloader version "3.6.26cc9ca"
compiled with SBCL 1.4.5.debian

该版本没有任何问题。

同 pgloader 还有其他一些命令:

pgloader./test/sqlite/sqlite.db postgresql:///newdb

其他使用方式

PGLoader 是一个可以高度配置的工具,除了上面提到的简单命令行迁移之外,PGLoader 还提供了强大的配置文件来帮助迁移。PGLoader 可以使用一个文件来配置告诉 PGLoader 如何迁移文件,该文件可以配置 PGLoader 的运行方式,并且可以执行更加复杂的迁移。

创建文件 vi pgload.load:

LOAD DATABASE
	FROM mysql://root:einverne.@localhost:3306/wordpress
	INTO pgsql://pgloader:einverne.@localhost:5432/quotes

	WITH include drop, create tables, create indexes, workers = 8, concurrency = 1

ALTER SCHEMA 'wordpress' RENAME TO 'public'
;

注意最后的 ; 一定要加。

解释:

  • LOAD DATABASE 指定从数据库加载
  • FROM 源数据库
  • INTO 目标数据库
  • WITH 指定 PGLoader 行为
    • include drop,迁移过程中,PGLoader 会删除目标 PostgreSQL 数据库中在源数据库中同名的任何表。注意备份。
    • create tables, 配置 PGLoader 根据源数据库数据在目标数据库中创建新表,如果使用 create no tables,则需要手动在目标数据库中创建好对应的表。
  • ALTER SCHEMA, 在 WITH 语句之后,配置特定 SQL 来告诉 PGLoader 执行其他操作。

更多更加详细的配置可以参考官方文档

创建完该文件后使用如下命令执行

pgloader pgload.load

其他方案

下面也是可选的方案,不过没有仔细研究

reference


2019-09-07 mysql , postgresql , database , sql , migration-tool , migration , rbdms

GraphQL 使用

很多人都知道 GraphQL 起源于 Facebook,但是似乎很少中文材料提到 GraphQL 出现的契机,我在看完这个纪录片 之后才对 GraphQL 的出现有更加深刻的了解。都知道当年 Facebook 的移动客户端都是网页,随着移动互联网发展,Facebook 网页实现的客户端性能和体验受到非常严重的影响,所以后来不得不去做原生的应用。那么这个时候就遇到了一个问题,原来直接使用网页,那么不同客户端用的接口都是给网页用的,最多做一下屏幕的适配,但是如果使用原生的应用,那么必然会需要设计不同的 API,而 Facebook 的工程师发现,对于复杂的 Feed 流,评论等等,用 RESTful 接口将是一个灾难,所以一帮人开始设计一种查询语言,这就是后来的 GraphQL。也应该纠正一下 Facebook 那帮工程师,只是开源了一份设计和一份实现,但他们万万没有想到开源社区的力量,就像片中 Nick Schrock 说的那样,他低估了社区的力量,在短短的几个月,几年时间中,GraphQL 就已经有了非常多语言的支持,周边应用也非常丰富。原来在脑海里的想法,都没有到生产环境中用过,就这样在所有社区的人的努力下成为了改变这个行业的一部分。

在了解 GraphQL 的过程中,看到一个非常有意思的比喻,经常有人会问起 GraphQL 和 RESTful 接口的区别,如果用去餐厅吃放,自助餐或者点餐来比喻,那么 GraphQL 就像是自助餐,你想要吃什么去餐盘中自己选择,而 RESTful 就像是点菜,需要看菜单,然后根据菜单选择。虽然两者都能吃饱,但是使用感受完全不同。1 当然这篇文章就不再继续讲 GraphQL 是什么,有什么用了,之前的 文章 也列了很多资料,差不多可以了解到它的具体使用场景了。这篇文章重点在于怎么把 GraphQL 用到实际项目中。

GitHub 上的 awesome-graphql 项目列举了太多的开源实现,从 c++, java, 到 python, nodejs, ruby 等等,这里我就选择上手最快的 python 更具体一些是 Django 来体验一下 GraphQL 和 Django 结合的效果。

先放上代码:

原始项目有些时间了,所以更新了一下,加了一些其他特性。

Python

主要借用的是这个项目:

Django 支持 https://github.com/graphql-python/graphene-django/

查询 Query

GraphQL 的查询语法。

Field

最常见的查询,请求方根据 Schema 定义结构,查询:

query {
	allPeople {
		id
		username
	}
}

结果和查询结构相同,包裹在 data 结构中:

{
  "data": {
	"allPeople": [
	  {
		"id": "1",
		"username": "steve"
	  },
	  {
		"id": "2",
		"username": "aholovaty"
	  },
	  {
		"id": "3",
		"username": "swillison"
	  },
	  {
		"id": "4",
		"username": "gvr"
	  },
	  {
		"id": "5",
		"username": "admin"
	  }
	]
  }
}

GraphQL 的接口是有类型定义的,对于上面的查询可以总结出

type RootQuery {
	allPeople: Person
}

type Person {
	id: int
	username: String
}

条件查询 Arguments

官方的说法 叫做 Arguments,参数化请求。

比如查询条件为 id 为 5 的用户信息

query {
  person(id: "5") {
	id
	username
	email
  }
}

结果是

{
  "data": {
	"person": {
	  "id": "5",
	  "username": "admin",
	  "email": "i@gmail.com"
	}
  }
}

结构

type RootQuery {
	person(id: int) : Person
}

GraphQL 可以有不同的参数类型,这取决于你的类型定义,Schema 定义。2 并且你可以自定义你的参数类型,只要你能够序列化该参数,这就使得 GraphQL 扩展性大大增强。

Aliases

没有看到官方的翻译,暂且称之为“别名”好了 [^aliases],某些情况下想要重写结果的 KEY,可以使用改方式来实现。

假如查询的时候要查两个用户的信息

query {
  person(id: "5") {
	id
	username
	email
  },
  person(id: "4") {
	id
	username
	email
  }
}

这么查肯定是会返回两个 person 的 key 的 JSON,那么就会有问题,GraphQL 允许我们重命名

query {
  p5: person(id: "5") {
	id
	username
	email
  },
  p4: person(id: "4") {
	id
	username
	email
  }
}

返回值是这样的:

{
  "data": {
	"p5": {
	  "id": "5",
	  "username": "admin",
	  "email": "i@gmail.com"
	},
	"p4": {
	  "id": "4",
	  "username": "gvr",
	  "email": "gvr@dropbox.com"
	}
  }
}

[^Aliases](http://graphql.org/learn/queries/#aliases)

Fragments

Fragments Android 里面也用了这个名词,我也不知道怎么翻译,暂且叫做“片段”好了,为什么要有 Fragments 呢? 就是因为要重用,看到上面请求两个用户的 Query 语句了吗?其中每个用户都想要请求其 id, username, email,这些参数是不是每次都要手写,假如这个 Person 信息不仅包含着三个,还有很多,假如不止请求两个用户信息,难道还要复制 N 遍,这显然是不合理的,所以 GraphQL 引入了 Fragments 概念,可以定义片段,然后再复用。

{
	p4: person(id: "4") {
	...personFields
  },
  p5: person(id: "5") {
	...personFields
  }
}

fragment personFields on PersonType {
  id
  username
  email
  fullName
  firstName
  lastName
  friends {
	id
	username
  }
}

结果就是对应的

{
  "data": {
	"p4": {
	  "id": "4",
	  "username": "gvr",
	  "email": "gvr@dropbox.com",
	  "fullName": "Guido van Rossum",
	  "firstName": "Guido",
	  "lastName": "van Rossum",
	  "friends": [
		{
		  "id": "2",
		  "username": "aholovaty"
		},
		{
		  "id": "3",
		  "username": "swillison"
		}
	  ]
	},
	"p5": {
	  "id": "5",
	  "username": "admin",
	  "email": "i@gmail.com",
	  "fullName": " ",
	  "firstName": "",
	  "lastName": "",
	  "friends": []
	}
  }
}

Use Variables in Fragments

如果再进一步,既然能够定义 Fragments 了,那么在其中定义变量也是可以的吧。

query person($first: Int = 2) {
	p4: person(id: "4") {
	...personFields
  },
  p5: person(name: "admin") {
	...personFields
  }
}

fragment personFields on PersonType {
  id
  username
  email
  fullName
  friends(first: $first) {
	id
	username
  }
}

结果是这样的

{
  "data": {
	"p4": {
	  "id": "4",
	  "username": "gvr",
	  "email": "gvr@dropbox.com",
	  "fullName": "Guido van Rossum",
	  "friends": [
		{
		  "id": "2",
		  "username": "aholovaty"
		},
		{
		  "id": "3",
		  "username": "swillison"
		}
	  ]
	},
	"p5": {
	  "id": "5",
	  "username": "admin",
	  "email": "i@gmail.com",
	  "fullName": " ",
	  "friends": [
		{
		  "id": "1",
		  "username": "steveluscher"
		},
		{
		  "id": "2",
		  "username": "aholovaty"
		}
	  ]
	}
  }
}

要使用变量,需要有三个步骤:

  • 将静态变量替换为带$ 操作符的 $variableName 变量名
  • 将变量名作为某个查询 operation name 的参数
  • variableName: value 参数以可以传输的格式(通常是 JSON) 传递给查询

变量在定义时需要注意会在后面跟着 : type 来表示该变量的类型。定义的变量只能是这些类型

  • scalars 标量
  • enums 枚举
  • input object types

对于复杂变量,需要到 Schema 中知道该变量的内容。这部分可以参考 Schema.

变量如果是必须的,那么需要在变量类型后面增加 ! 来表示

操作名 Operation name

在上面的例子中也能看到,在 Query 的时候,有些情况下可以省略最前面的 query 关键字,这个 query 关键字在 GraphQL 中叫做 operation type,之所以叫做 type,是因为这只是查询,到后面还有 mutation (修改), subscription 等等。这个操作类型隐含了此次操作的具体动作,比如说是查询,还是修改,还是订阅等等。

接在 query 关键字后面的是 operation name,操作名,该名字定义了此次查询的含义,比如上面的例子就是查询 person 信息。operation name 就像是大部分语言里面的方法,operation name 在获取多个文档时才是必须的,但是非常建议给予每一个查询一个名字,operation name 在调试服务端程序时非常有用,可以快速定位问题。

更改 mutation

插入或者更新数据,和查询类似,使用 mutation operation type, 后面接 operation name,再就是传参,以及定义返回值。

mutation createCategory {
	createCategory(name: "Milk") {
	category {
	  id
	  name
	}
  }
}

class CreateCategory(graphene.Mutation):
	"""
	"""
	class Arguments:
		name = graphene.String(required=True)

	category = graphene.Field(CategoryType)

	def mutate(self, info, **kwargs):
		name = kwargs.get('name')
		category = Category(name=name)
		Category.save(category)
		return CreateCategory(category=category)


class UpdateCategory(graphene.Mutation):
	"""
mutation updateCategory {
	updateCategory(id: "5", name: "MilkV2") {
	category {
	  id
	  name
	}
  }
}
	"""
	class Arguments:
		id = graphene.ID()
		name = graphene.String(required=True)

	category = graphene.Field(CategoryType)

	def mutate(self, info, id, name):
		ca = Category.objects.get(pk=id)
		ca.name = name
		ca.save()
		return UpdateCategory(category=ca)


class Mutation(graphene.ObjectType):
	create_category = CreateCategory.Field()
	update_category = UpdateCategory.Field()

Mutation 和 Query 有一个显著的差异,Query Field 会同时查询,而 Mutation Field 只会顺序,一个接一个查询。

扩展阅读

其他 Python 实现的 GraphQL

用 node 实现第一个 GraphQL

GraphQL with Flask

JSON API

这里是一些已经实现 GraphQL 的公开的 API

reference

  1. https://www.youtube.com/watch?v=X3QM6Ap6u-4 

  2. https://graphql.org/learn/schema/ 


2019-09-07 graphql , apollo , facebook , restful , api

电子书

最近文章

  • 时隔 5 年再安装 NextCloud 时隔多年,我再安装了一次 [[NextCloud]],很早之前 就在我的 QNAP TS 453B mini 上安装并使用了多年,这两年也一直在跟随着官方的版本升级 ,但是 QNAP 毕竟在局域网内,虽然可以使用 Tailscale,ZeroTier 等等工具来组件局域网,但毕竟还是不方便,最近入手一台还不错的 VPS,所以想着再搭建一个公有 IP 的 NextCloud,一方面备份一下自己的相册,另一方面也补足一下我自己使用 Syncthing时没有在线预览的页面,导致常常有些时候想访问自己的笔记而找不到。
  • 使用 SyncTV 异地远程一起看视频 因为和女朋友异地,之前我们都一直使用 [[Plex]] 来同步看视频,基本上可以做到秒级别的共享观看。今天看 GitHub 的时候发现一款叫做 SyncTV 的应用,也是可以通过同步的方式了共享观看视频流,那这样就可以一起看视频,看直播了。原来 Plex 的因为放在了一台欧洲的服务器上,在国内播放的时候总是会出现卡顿,一来是带宽限制,二来是经常发现有转码,所以常常需要我预先转码为低码率的视频才能流畅播放,出现的这个 SyncTV ,我可以搭配一台地理位置比较好的,比如日本的机器,然后通过代理的方式播放,不知道会不会提升一下使用体验。
  • 《被讨厌的勇气》读书笔记 怎么知道的这一本书
  • 修复 macOS 时区和时间错误 今天 macOS 上又发生了一个奇怪的问题,昨天晚上因为没有连接充电线,所以导致 MacBook 晚上自动关机了,看起来是因为没电自动关机了,但是早上看的时候还有一点点电,白天的时候没有任何操作就直接充电到满,但是晚一些使用的时候就发现系统的时间不太对了,在系统设置中强制同步系统时间,但没有用处,每一次同步都比当前的时间慢了一天和几个小时,甚至连分钟都对不上。于是就开始了一系列的修复过程。
  • 为播客爱好者制作的工具 Podwise Podwise 是一个针对播客的笔记软件,过去很长的一段时间里面都在寻找能够给播客记笔记的应用,之前在移动端发现了一个 [[Snipd]],可以在听到的时候,记录下播客中的精彩瞬间,还可以做笔记。但今天发现的这一款 Podwise 是基于网页的,可以在网页中播放,并且 Podwise 提供了语音转写的文字稿。