0%

背景

好早前就知道了 Serverless 架构,近日发现阿里云支持了函数计算,便决定将猿文档的后端服务进行迁移。

此前,猿文档的后端服务基于 Node.js,采用 egg.js 框架,运行于 docker 中,购买的是阿里云的云服务器 ECS。之所以决定此次迁移是由于以下几点原因:

  • 按使用收费:目前猿文档的请求量并不大,不存在高频请求的情况,阿里云函数计算的免费额度够用。
  • 功能简单:猿文档只有三个接口,方便迁移。
  • 维护性:使用无服务器架构只需要部署一个个函数,简单易用。

进行

阿里云函数计算可以在这里了解函数计算文档中心

原 3 个接口可以很方便的写成三个函数,基本格式如下:

module.exports.handler = function (request, response, context) {
  const queries = request.queries;
  // ...
  const res = {
    message: 'ok',
    code: 0,
  };
  response.setHeader('Content-Type', 'application/json');
  response.setStatusCode(200);
  response.send(JSON.stringify(res));
};

部署使用 fcli 工具。进入 fcli shell 后基本操作如下:

>>> ls  # 查看服务列表
>>> cd yuanwendang  # 进入服务
>>> ls  # 查看函数列表
>>> mkf doc-entry -h entry.handler -d ./doc-entry -t nodejs8  # 新建函数
>>> upf doc-entry -h entry.handler -d ./doc-entry -t nodejs8  # 更新函数
阅读全文 »

最近翻回了 『JavaScript 框架设计』这本司徒正美编著,在 2014 年发行的书,此书主要讲述了从零开始写一个前端框架所要掌握的内容,每次阅读都会有新的收获。其中内容会在兼容性及性能上做很多考量,本文便是书中的一个关于字符串方法 repeat 实现的例子。

  1. 空数组 join
function repeat(target, n) {
  return (new Array(n + 1)).join(target);
}
  1. 改良方法 1,省去创建数组这一步,提高性能。另,之所以创建一个带 length 属性的对象,是因为要调用数组的原型方法,需要指定 call 第一个参数为类数组对象。
function repeat(target, n) {
  return Array.prototype.join.call({
    length: n + 1
  }, target);
}
  1. 改良方法 2,利用闭包缓存 join,避免重复创建对象、寻找方法。
var repeat = (function () {
  var join = Array.prototype.join, obj = {};
  return function(target, n) {
    obj.length = n + 1;
    return join.call(obj, target);
  };
})();
  1. 使用二分法,减少操作次数
function repeat(target, n) {
  var s = target, total = [];
  while (n > 0) {
    if (n % 2 === 1) {
      total[total.length] = s;
    }
    if (n === 1) {
      break;
    }

    s += s;
    n = n >> 1; // Math.floor(n / 2);
  }
  return total.join('');
}
阅读全文 »

译者说:大前端的概念被提出已经很久了,那么在大前端的背景下,我们前端开发人员使用 Node.js 应该做什么,不应该做什么呢?相信此文能够提供一定的参考。

背景

后台有着各种各样的服务,他们可能会使用不同的语言、数据库、协议及传输层。而客户端对数据也会有不同的要求,比如一个客户端可能需要 XML 格式而其他的需要 JSON。多少情况下,这些你都要支持。另一方面,不同的服务会有一些通用的共享逻辑,例如身份验证,我们不想在所有的服务中重复的去实现。于是就有了 API Gateway 的出现,它可以为不同的服务协议提供一个共享层,满足不同客户端的需求。

什么是 API Gateway?

API Gateway 是一种微服务架构中的一种服务,它可以为客户端与内部服务通信提供一个共享层与 API。API Gateway 能够路由请求、转换协议、数据聚合及实现共享的逻辑(如身份验证、流量限制)。

你可以把 API Gateway 当作微服务世界的入口。根据客户端的需求,我们的系统可以有一个或多个 API Gateway。例如对桌面端和移动端我们可以有不同的 gateway。

API Gateway for Desktop and Mobile

前端团队的 Node.js API Gateway

因为 API Gateway 的功能是提供给客户端的,所以它可以由负责前端应用的团队实现管理。这也意味着它的实现语言应该由负责客户端的团队选择。对应熟悉 JavaScript 的前端开发来说,Node.js 是很好的实现 API Gateway 的语言。

Netflix 成功的使用了 Node.js 的 API Gateway 与 Java 后端为多种客户端提供服务(The “Paved Road” PaaS for Microservices at Netflix)。

Node.js API Gateway with Java in Netflix

Paved PaaS to Microservices

API Gateway 的功能

前面我们讨论了可以把通用共享逻辑放入 API Gateway,接下来将介绍 gateway 常见的职责。

路由和版本控制

使用 API Gateway 作为微服务的入口时,在 gateway 服务中,你可以派发客户端的请求到不同的服务,通过路由控制版本。

阅读全文 »

最近写了个库 promisify-wxmp-util 用来将微信小程序中 callback 形式的方法转换成 Promise 形式。功能实现起来很简单,分别对各个方法调用 promisify 即可。但是实践开发中并不一定会用到所有的 API,所以希望它具有懒初始化的功能,在我调用到对应的 API 时才会做 promisify 处理。

使用 Proxy 可以通过 get handler 控制对象属性的访问:

const lib = new Proxy({}, {
  get(obj, prop) {
    if (Reflect.has(obj, prop)) {
      return Reflect.get(obj, prop);
    } else if (typeof wx[prop] !== 'undefined') {
      const func = promisify(wx[prop]);
      obj[prop] = func;
      return func;
    } else {
      return new Error(`Cannot read property '${prop}' of wx`);
    }
  }
});

Vue、React 这些框架的出现为前端开发带来了很大的便捷,但是享受这些便捷的同时我们不应忘记之前我们掌握的优化方法。

在 Vue 中为列表中每一元素绑定事件非常方便,@eventName="handler" 即可,但是方便的同时带来了性能上的不友好。

如下我们采用两种方式监听 .list .itemclick 事件。

绑定事件到每一个 .item

<template>
  <div id="app">
  <div class="list">
    <div class="item"
      @click="onClickItem"
      v-for="(item, idx) in list"
      :key="idx">{{ idx }}</div>
    </div>
  </div>
</template>

<script>
const getInitialData = () => ({
  list: new Array(100),
});
export default {
  data: getInitialData,
  name: 'App',
  methods: {
    onClickItem() {
      alert('click');
    },
  },
};
</script>

绑定事件到 .list

<template>
  <div id="app">
  <div class="list" @click="onClickList($event)">
    <div class="item"
      v-for="(item, idx) in list"
      :key="idx">{{ idx }}</div>
    </div>
  </div>
</template>

<script>
const getInitialData = () => ({
  list: new Array(100),
});
export default {
  data: getInitialData,
  name: 'App',
  methods: {
    onClickList(event) {
      const target = event.target;
      if (target.className === 'item') {
        alert('click');
      }
    },
  },
};
</script>

以下分别是两种方式的内存快照:

EventListener On Item

EventListener On List

可以看出使用事件委托少了 $121 - 22 = 19$ 个 EventListener 对象。随着 .item 元素的增多,绑定事件到 .item 会使内存消耗呈线性增长。

问题

现在很多项目使用的是 React 或 Vue,而 D3.js 却是直接操作 DOM 的,所以想在这些框架中使用 D3.js 不能使用一般的方式。下文以一个饼图为例介绍两种不错的方案。

PositionsPie

方案 1: D3.js 做计算

一种方案是只使用 D3.js 做计算,使用 React 根据计算值生成 DOM。

import * as React from 'react';
import * as d3 from 'd3';
import { PieArcDatum } from 'd3';

import { str2rgb } from 'utils';

interface PositionData {
  symbol: string;
  val: number;
}

interface PositionsPieProps {
  positions: PositionData[];
  width: number;
  height: number;
}

const PositionsPie = (props: PositionsPieProps) => {
  const { positions, width, height } = props;
  const radius = Math.min(width, height) / 2;

  const pie = d3
    .pie<PositionData>().sort(null)
    .value(d => d.val)(positions);

  const arcPathGen = d3.arc<PieArcDatum<PositionData>>()
    .innerRadius(0)
    .outerRadius(radius - 10);

  return (
    <svg width={width} height={height}>
      <g transform={`translate(${width / 2}, ${height / 2})`}>
        {
          pie.map((val, idx) => {
            const arcPath = arcPathGen(val) as string;

            const labelPath = d3.arc<PieArcDatum<PositionData>>()
              .outerRadius(radius - 40)
              .innerRadius(radius - 40);

            return (
              <g key={idx}>
                <path d={arcPath} fill={str2rgb(val.data.symbol)} />
                <text
                  dy="0.35em"
                  style={{ textAnchor: 'middle' }}
                  transform={`translate(${labelPath.centroid(val)})`}
                >
                  {val.data.symbol}
                </text>
              </g>
            );
          })
        }
      </g>
    </svg>
  );
};

export default PositionsPie;

d3.pie()d3.arc() 都可以用来做计算操作,生成绘制 SVG 所用的数据。

阅读全文 »

在使用 TypeScript 开发的过程中,我们有时会遇到需要做类型减法的情况,例如 react-i18nexttranslate 方法会为组件的 Props 添加 i18n 等属性,而这个属性在通过 translate 生成的高阶组件的 Props 中是不应体现的。这时我们就需要用到类型减法。

具体实现:

// Diff / Omit taken from https://github.com/Microsoft/TypeScript/issues/12215#issuecomment-311923766
type Diff<T extends string, U extends string> = ({ [P in T]: P } & { [P in U]: never } & { [x: string]: never })[T];
type Omit<T, K extends keyof T> = Pick<T, Diff<keyof T, K>>;

interface A {
  a: string;
  b?: string;
  c: number;
}

type B = Omit<A, 'c'>;

const a: A = {
  a: '5',
  c: 5,
};

const b: B = {
  a: '5',
};

// Note: typeof B is (correctly) Pick<A, 'a' | 'b'>

这里主要用到了 Pick 工具类,Pick 可以从一个类型中取出一组属性生成新的类型。


TypeScript 2.8 更新

在 TypeScript 2.8 中,新增了条件类型 Exclude<T, U>,它可以从T中排除那些可分配给U的类型。

所以上面的 Omit<T, K> 类型可以改写为 Pick<T, Exclude<keyof T, K>>

TypeScript 2.8 Release Notes

在学习量化交易过程中,数据源非常重要。之前一段时间使用的是 Quandl 提供的免费数据,然而现在发现它免费的数据里并没有 ETF,而且一些数据也有问题(例如 SHAK 拿不到正确的数据),遂决定寻找替换方案。从 pandas-datareader 了解到 Tiingo 这个数据源,一番测试下来,觉得符合自己目前的需求。

首先需要在 Tiingo 注册账号,得到自己的 API Key。

然后安装开源的 API 客户端 Tiingo Python

$ pip install tiingo

使用方法:

from tiingo import TiingoClient

config = {}
config['session'] = True
config['api_key'] = "MY_SECRET_API_KEY"

client = TiingoClient(config)

historical_prices = client.get_ticker_price("GOOGL",
                                            fmt='json',
                                            startDate='2017-08-01',
                                            endDate='2017-08-31',
                                            frequency='daily')

get_ticker_price 取得的历史数据会是 json 格式的,为了方便使用,我对其进行了简单的封装,将值转换成 DataFrame

def get_df_from_tiingo(symbol, start, end):
    df = pd.DataFrame(client.get_ticker_price(symbol,
                      startDate=start,
                      endDate=end,
                      frequency='daily'
                     ))
    df.set_index('date', inplace=True)
    df.index = pd.to_datetime(df.index)
    del df.index.name
    return df

Zipline 是一个 Python 的算法交易库。它的安装过程比较简单,但是在实际操作过程中可能会由于网络、系统环境等原因遇到各式各样的问题。本文以安装配置完成后能够正常运行 dual_moving_average.py 为目的。


1. 安装 Anaconda

Anaconda 是一个用于科学计算的 Python 发行版,支持 Linux, Mac, Windows,包含了众多流行的科学计算、数据分析的 Python 包。使用 Anaconda 能够方便的安装管理 Zipline 及其他开发过程中会用到的包。

直接从 Anaconda 的官网下载 Anaconda 可能会比较慢,所以这里从清华大学开源软件镜像站下载。

$ wget https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/Anaconda3-5.1.0-Linux-x86_64.sh
$ chmod +x Anaconda3-5.1.0-Linux-x86_64.sh
$ ./Anaconda3-5.1.0-Linux-x86_64.sh

执行完 Anaconda3-5.1.0-Linux-x86_64.sh 这个脚本,会将 Anaconda 安装到 $HOME/anaconda3 如果在安装过程中没有添加 $HOME/anaconda3/bin 到 $PATH 的话需要自己手动在 .bashrc 中添加。

export PATH="/home/hanai/anaconda3/bin:$PATH"

最后还需要添加 TUNA 提供的 Anaconda 仓库的镜像。

$ conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/
$ conda config --add channels https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/main/
$ conda config --set show_channel_urls yes

2. 创建环境

使用 conda 来创建管理环境:

$ conda create -n py35 python=3.5
$ source activate py35

为了方便开发,可以安装 jupyternb_conda

$ conda install jupyter nb_conda
阅读全文 »