Top
Top
Top

中国用户如何免费激活Stripe?

中国用户如何免费激活Stripe?

本文会介绍无需开设海外银行账号或者香港账号,并免费的通过注册激活Stripe账号并提现,亲测有效!

主要通过使用万里汇海外账号绑定激活,其中万里汇是蚂蚁金服旗下的产品,值得可靠。(不是给万里汇打广告哦)

什么是Stripe

想象你是一个跨境电商,想要把产品卖到全球,却面临一个问题,商品标价$100,日本客户想直接支付日元,欧洲客户想支付欧元。。。你不可能要求客户说我只支持美元,请兑换后再支付吧?

所以如果你还不知道Stripe,那推荐你去了解下。作为和PayPal一样存在的支付巨头(现在市值 $950亿),在国外早已火得一塌糊涂,使用他作为支付平台的商家和网站数不甚数,消费者渗透率覆盖了全球135个国家。。。

中国商户,NO

遗憾的是,如果你是身在中国,那么是不能激活Stripe 账号的(不激活只是注册账号倒是可以,但是没法收款和体现,只能测试),可以查看现在商户支持的40多个国家/地区列表, 其中香港是可以的。所以你能看到,这个注册公司地址里是没办法选择中国🇨🇳的,That’s the problem!

如果解决?万里汇 或者 TransferWise

问题的瓶颈在于stripe 激活的时候,需要提供你的商业信息以及银行信息,并且保证银行上的名字和账号里的个人姓名一致。但又由于商业信息的国家地区只有上面提到的,所以也就导致中国用户没办法激活。

解决办法的思路就是,使用万里汇注册个香港账号或者其他国家的银行账号(虽说是虚拟的,但和实际没区别),然后再根据账号的信息拿去Stripe激活,看似简单,但还有很多坑需要趟,且听我慢慢道来。


注册万里汇

点击注册地址 https://portal.worldfirst.com.cn/register

  1. 然后按步骤选择支付网关, 这里可以按个人情况多选几个(最好把stripe 勾选上),虽然我也不知道有多大影响

  1. 选择类型,可以是个人, 也可以是公司,这里我选的个人

  2. 填写基本信息,按部就班的填好就行,注册就算成功

认证账号

注册好了,是非认证状态的,这时是不能创建海外账号,还需要提供相应的信息上传去验证(据说可以支付宝快捷验证,但是我没发现有,只能拍照上传)

认证一般会持续一两天验证,等着收邮件就好了,如果有问题,可以联系自己的万里汇客户经理(真是一对一服务啊,这点好)

创建海外货币账户

终于到了关键步骤了,这里可以创建多个账户,每个账户就像自己银行卡一样,有卡号,为了stripe 注册方便,我创建了一个香港账户和一个美国账户

到了这一步,恭喜你,你已经开通了海外账户!关键是这个银行信息对后面的Stripe激活是非常重要。


创建和激活 Stripe 账号

首先创建Stripe 账号,https://dashboard.stripe.com/register, 国家/地区可以选择香港,创建好之后,登录进入主页面, 这个时候如果你暂时不想激活,是完全可以的,可以切换测试模式进行你的支付开发测试,测试模式基本和在线模式一模一样,除了测试的支付账号是假的以外 (PS, 我就是没激活使用了一年多,纯粹作为开发使用)

激活Stripe 账户

点击 激活你的账户 , 这里看起来有很多步骤,不用怕填错,后面都是可以跳回去改的。

公司结构:

选择香港,或者其他上面货币账号国家,地址可以填银行账号地址,类型可以选个人(如果公司的话,据说stripe 会对你账号保护性或者服务更好),点击下一步

公司代表:必须是你自己在上述银行账号的姓名,否则可能会无法体现到你银行

地址信息可以继续用银行地址,电话号码最好是用中国的,可以选CN, 填写自己号码,因为后面可能会用来用来登录短信验证之类的,身份证ID 这个我没记错的话,是随便找的一个ID(只要位数和格式对了就行) ☹️

银行详情:选择在万里汇创建的银行信息就行

然后一直填下去,保存

激活成功了吗?

上述没问题的话,确实激活成功了,你也可以切换到线上模式去收款了,但是却无法提现(转账)到你银行卡里,还有两个重要的未完成步骤警告:

身份信息不匹配(个人信息验证失败,当然了,ID 都是假的) 和 US Tax Form (美国税收表)

两个问题一个一个解决:

  • ID 不匹配,进入提示的配置,只需要上传自己的身份证正反面就好了,一天左右就验证成功(不知道如何验证的,可能是后台人工验证,保证姓名一致就行,所以一定上传自己真实的身份证就,地址用自己中国地址)
  • W-8 form,一定要选非美国居民(勾选No),然后点击提交,会被导航至表单填写页面,基本信息stripe 已经帮你预填了,只需要签上个人姓名就好了

documents-for-identity-and-home-address-verification

w-8-forms-collected-by-stripe

等着这两个错误完成后,这个账号就算真正激活完成,并可以完成提现(每天,并且非常快)。


万里汇提现到人民币(成功)

现在万里汇香港账号已经有收到的港币了,但如果需要的话,需要转为人民币(可转到支付宝),但根据万里汇客户经理说明,这是需要提供相应凭据,也就是stripe 或者 paypal 上的支付记录,表明来历明确。提现过一次,因为金额不大,并且提供了paypay 的收款记录,所以成功提现。

特别提醒的是,这篇文章目的只是为激活stripe指南, 不为跨境转账成功负责,请酌情选择。

特此声明,本文禁止转载至除 troyyang.com, itstripe.com 以外的网站

相关推荐文章:
wordpress stripe插件,支持微信和支付宝
stripe集成 微信和支付宝

Top

Wordpress 插件 Stripe Express 发布啦!


Stripe Express 是什么?

简单来说,Stripe Express 是一款针对wordpress 平台,帮助你使用 stripe 快速,方便完成跨境支付的一款免费插件(扩展功能收费)。其中,包括多种已经创建好的支付组件,包括一次性支付(one-time),电子钱包(支付宝,微信,Apple Pay, Google Pay,下面重点会提到微信和支付宝),表单支付等等组件,需要提及的是,上面的组件都支持常见的各种信用卡,Master card, Visa, 等等等等,以及其他国家地区的主流支付方式比如 Bancontact, FPX, EPS, SEPA, Giropay, Sofort, iDeal。

所以,只有你有一个 Stripe 的账号,那么超过三十多个国家地区的客户都可以向你支付(对于微信和支付宝,你无需申请支付宝或者微信的商家账号,即可免费收款)。

问题: 什么人更需要这个组件?
回答: 现阶段,因为还没集成woocommence, 所以如果你没有一个完整的电子商务网站比如使用 woocommence搭建,只是一个简单的Wordpress 网站,但是你又有自己的产品或者服务需要销售,而你只是想你用户简单的点击购买,付款。

wordpress 插件传送门

https://itstripe.com VS Stripe Express

Stripe Express 是针对Wordpress 平台的一个插件,先说说 itstripe.com, 创办的原因其实还是在我们国内,大部分人肯定知道Paypal,却不知道Stripe,更别说用过,当然也和Stripe 暂不支持中国商家的原因分不开。殊不知,国外Stripe普及程度远大于我们的想象,很多网站都会加上对Stripe 的支持,因为这意味着你的网站可以面向全世界超过30个主要国家的客户收费,包括中国!所以想要做跨境支付的话,Stripe 你必须要熟悉!

不解释太多了,说会正题,itstripe.com 的创办主要是从 wordpress 的 stripe 插件开始,毕竟全世界超过30%的网站都是他创建的,而且现在也依旧火爆。再加上之前很多朋友都在咨询我关于 stripe 在wordpress上的问题,所以主要侧重点会是在 stripe express 这款插件上。其次,网站会包括产品介绍,以及插件的文档,还有和stripe 相关集成服务,如果你有集成这方面的需求的话,或者Web 的支付开发,可以联系我们。

Stripe,微信 和 支付宝

这是一个最重要的原因之一促使我想要做这么一个东西。有这么一部分人:1. 小商家或者个人网站用户想要接入微信或者支付宝,方便国内用户收费,2. 国外的中小网站想要针对中国用户微信和支付宝收费。但是对于他们而言,由其老外,要想接入微信或者支付宝支付接口,门槛还是有点高,需要去申请商家账号

stripe 却在这方面有着天然优势,由于已经和alipay 和 wechat 达成协议,Stripe 完全可以实现上面的收费,其中stripe 会收取3.4% + $0.50每笔的服务费。

之前写过一篇关于 stripe集成 微信和支付宝的文章,反响挺大的,看到很多评论和转载,也收到很多咨询的邮件,但是之前的那篇从技术角度其实有点老了,我会另外抽时间重新写一个更通用的集成方式(已经在这款插件中实践了)

后续

本着布道外加对自己产品的宣传,我计划的是在明年多写写 stripe 相关的技术文章,也许有的也只是翻译而已,咦,那不是还得搭个中文模块,啊,好烦,瞬间有点不想搞了!

回想这半年多的开发时间(包括插件和官网),白天正常上班,晚上继续开发,连周末都不想出门,像打鸡血一样的完善产品,终于迎来了发布的日子(实际发布的日期应该是11.25日,上传至Wordpress 插件库的那天)。无论这款插件将来如何,安装量怎样,有过这么一段为了某个目标而全力以赴的日子也是极好的!

Top

Mobx在项目中的实践 及 与Redux的比较

之前在公司FEE内部做过一次技术分享,主要关于Mobx在项目中的使用一年后的体验以及和Redux 的一些比较(因为我们项目之前的状态管理选型选择的是mobx,而其他项目组的同学选择主要是Redux或者还在纠结如何选)。

以下都是根据查询各种资料后的个人理解概览

Mobx Overview

Mobx looks like a properties tracking and reaction lib.
基础部分就省了,只说结论:Mobx 看起来是属性追踪及作出相应反应的库,和Redux 不一样的是,他的状态是mutable的。

Mobx 4 & 5

  • Mobx 4 Limitations (Observable)
  • Mobx 5 Proxy based (Only ES 6 Browser, no polyfill)

Mobx & Third-Party view lib

  • mobx & mobx-react
  • redux & react-redux
  • mobx & mobx-arch & mobx-backbone 有吗???

Mobx 是可以单独使用的,这点和Redux一样,可以不需要依赖于任何UI 库像,React, Vue,当然如果把他们结合到一起,那才能发挥出最大的作用,所以就理所应当的有mobx-react。

我们公司内部有个UI 库叫arch,很老的了,requirejs时代的,比react, vue, angular还早,没有响应式的更新,核心只有一个render 方法,所以其实可以通过Mobx 简单改造为响应式的,一旦外部属性发生变化,就会触发重新渲染,至于内部状态嘛,呵呵,不考虑了,反正这只是个例子。

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
var { observable, autorun } = require("mobx");
var Entity = require('xx/xxxx/entity');

var todoStore = observable({
todos: [],
get completedCount() {
return this.todos.filter(todo => todo.completed).length
}
})

autorun(function () {
// For Backbone
this.xxxBackBoneComponent = new Entity({
model: todoStore.todos,
editable: true
});

// For Arch
active.render($html, () => {
this.xxArchComponent = arch.getComponent(xxx);
});
})

todoStore.todos[0] = {
title: "Take a walk",
completed: false
}

Mobx Store Design

这是我觉得最难的部分,如何设计好Mobx的Store?官方给出的一个guide 是划分为Domain store 和 UI store。Domain store和Redux的one-single store 可不一样,这里是可以有多个的,像users, books, movies, orders 都可以是一个Domain Store, 至于UI store,暂时我们只是存储一些全局的属性。所以,我们的项目中Store的结构大致如下:

1
2
3
4
5
6
7
stores
--root.ts
--domain
----aaaStore.ts
----bbbStore.ts
--ui
----application.ts

root.ts初始化所有domain和ui store:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export default class RootStore {
@observable
aaStore;

@observable
bbStore;

@observable
applicationUIStore;

constructor() {
// Domain Store Init
this.aaStore = new AStore(this);
this.bbStore = new BStore(this);
...

// UI Store Init
this.applicationUIStore = new ApplicationUIStore(this);
...
}
}

但是在实际的问题中,我们发现大部分的状态其实都是本地UI状态,(也许有人说用setState啊,如果业务复杂,状态很多, 并且基本会依赖其他store,最好抽出来)所以,问题来了,这些ui store我们放在哪里呢?同时,我们需要把Container 组件里的状态隔离开来,为什么隔离,一是因为UT 不好写(因为有inject,所以在UT里需要写很多Provider),二是傻瓜组件更不容易出错,参考Redux的connect用法,我们得到下面的结构:

1
2
3
ContainerAComponent
--ContainerAComponent.tsx
--ContainerAComponentUIStore.ts

ContainerAComponentUIStore.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
export default class ContainerAComponentUIStore {
rootStore;
constructor(rootStore) {
this.rootStore = rootStore;
}

@observable
addHoc = '';

@action.bound
onAdhocChange = (addHocNewValue) => {
....
}

ContainerAComponent.tsx

1
2
3
4
5
6
7
export class ContainerAComponent extends React.Component {
handleAdhocChange = (e) => {
this.props.onAdhocChange(e.target.value);
}
...
}
export default connectComponentStore(ContainerAComponent, ContainerAComponentUIStore);

这样,我们导出了两个组件,一个是ContainerAComponent,就是一个简单组件,我们可以通过传统传props的方式去测试组件核心内容,另一个是HOC组件,其实是不用测试的。

而至于connectComponentStore方法,就是一个很简单的HOC

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
export default (WrapperedComponent, ComponentStore) => {
@inject('appStore')
@observer
class Connect extends React.Component {
@observable componentStore;

constructor(props) {
super(props);
this.componentStore = new ComponentStore(props.appStore);
this.ref = createRef();
}

static displayName = `${WrapperedComponent.displayName || WrapperedComponent.name}-withUIStore`

componentDidMount() {
this.componentStore.mapState(this.props);
}

componentDidUpdate(preProps, preState) {
this.componentStore.mapState(this.props);
}

render() {
return (<WrapperedComponent ref={this.ref} {...this.componentStore.toProps} {...this.props} />);
}
}

return Connect;
};

我们业务里,绝大部分都是用到的这种本地UI Store + 简单组件组合这种方式,也许就是所谓的local state component (忘了哪里听到的了)

Mobx-State-Tree(MST)

也许MST在大型项目中使用是个很好的方式,但我们暂时还没有去尝试。

Project Structure

下面是Mobx的一些项目组织结构参考资料:

https://medium.com/@daniel.bischoff/how-to-structure-your-mobx-react-app-8fd6d9d821a4
https://github.com/gothinkster/react-mobx-realworld-example-app

Mobx-React vs Redux-React

个人简单的一些看法:

  • Workflow
  • Freestyle vs Strict
  • OOP styles vs FP
  • Small vs Large
  • Time-traval problem (Resolved by MST)
  • Container components (Inject vs Connect)
  • redux-crud-example & mobx-crud-example

https://medium.com/@cameronfletcher92/mobdux-combining-the-good-parts-of-mobx-and-redux-61bac90ee448
https://www.sitepoint.com/redux-vs-mobx-which-is-best/

Learning Redux

在一些小项目中用过Redux, 不得不说,Redux的学习成本要比Mobx高得多,比如下面的点,
redux, reducer, action, container component, selectors(reselect), redux-thunk, normalizing, ducks, and more waiting…

Mobx-Best-Practice

Decorator (ES7/TS) vs no-decorators

End

如果你有更好Mobx使用的一些心得,欢迎交流!

Top

纯JS实现按多列排序

重要的事情还是要说的

项目里没引用 lodash (因为和 underscore.js 冲突)

问题

数据结构类似这种:

1
2
3
4
5
6
const testData = [
{ name: '1', primary: true, startDate: '2018-01-01T08:00:00Z', endDate: '2018-05-01T08:00:00Z' },
{ name: 'A', primary: true, startDate: '2018-02-01T08:00:00Z', endDate: '2018-06-01T08:00:00Z' },
{ name: 'a', primary: true, startDate: '2019-02-01T08:00:00Z', endDate: '2019-05-01T08:00:00Z' },
{ name: 'b', primary: false, startDate: '2019-02-01T08:00:00Z', endDate: '2019-02-01T08:00:00Z' },
]

最近项目中有大量的对排序的新需求,由其是按多列来排序, 新需求大致如下:

  • Archived 为true的排列到最后,否则排最前面
  • 然后,按照 StartDate 时间,如果最新,则排前面
  • 然后,如果 StartDate 相同,则按照 EndDate 来排,
  • 然后,如果 EndDate 也相同,则按照 name 的字母表的顺序排

同时呢,之前项目中也有很多类似的需求:

  • 先按照 ModifiedDate 排,
  • 如果相同,则按 name 字母表顺序

或者

  • Primary 为true 的排前面
  • 如果Primary 相同, 按照 name 字母表排序

还有更多的类似需求,我们项目里原来有个 Sort.js 的公共方法来处理这些排序,选取了其中最长的一个 (其实上面需求的每一个实现都和这个差不多)

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
const sortFlattenPrograms = (flattenPrograms) =>
flattenPrograms.sort((a, b) => {
// first sort by archived: unarchived first
if(a.archived && !b.archived) {
return 1;
} else if(!a.archived && b.archived) {
return -1;
}

// sort by start date: latest first
let dateCompareResult = compareDateLatestFirst(a.startDate, b.startDate);
if(dateCompareResult !== 0) {
return dateCompareResult;
}

// sort by end date: latest first
dateCompareResult = compareDateLatestFirst(a.endDate, b.endDate);
if(dateCompareResult !== 0) {
return dateCompareResult;
}

// sort by program name - location name: alphabetically (ignore case)
const nameCompareResult = compareStringAlphabeticallyIgnoreCase(getProgramFullName(a), getProgramFullName(b));
if(nameCompareResult !== 0) {
return nameCompareResult;
}

return 0;
});

是不是很长,很丑,而且这只是一个排序,还有很多这种和0比较,然后再比较,所以继续加下去肯定不可取,维护是个很大的问题,UT 也很难写,要是能抽出中间部分就好了???

解决办法

先贴代码,其实核心就是抽取上面的各种comparator, 并且采用链式的方式执行,这里使用reduce方法来取了个巧,其实,查看了lodash的实现后, 他们采用的是 while 实现。

注意排序的顺序,是按照从右到左,我想的是尽量和 functional programming 的方式来写,并且compose 方法在lodash 里也是这个顺序,如果想改为从左往右,只需要将 reduce 改为 reduceRight 即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Sort by order list from right to left
* For example: we want to order by start date, if date equal, then order by end date, if equal, then name
* composeOrderBy([oderByName, orderByEndDate, orderByStartDate])
* @param {*} comparators
*/
const composeOrderBy = (comparators) => {
const makeChainedComparator = (first, next) => {
return function (a, b) {
var result = first(a, b);
if(result !== 0) return result;
return next(a, b);
};
};
return comparators.reduce(function (chained, first) {
return makeChainedComparator(first, chained);
});
};

所以,上面的需求可以简单改为下面,其实comparators 是一个我预先定义好的各种比较方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 预先定义的方法
comparators = {
compareStringField: (field, ignoreCase = true)=> (a, b) => { ... },
compareBoolField: (field, trueFirst = true) => (a, b) => { ... },
compareDateLatestFirst: (field) => (a, b) => { ... },
}

data.sort(composeOrderBy([
comparators.compareNameIgnoreCase(),
comparators.compareDateLatestFirst('endDate'),
comparators.compareDateLatestFirst('startDate'),
comparators.compareBoolField('archived', false)
]));

data.sort(composeOrderBy([
comparators.compareNameIgnoreCase(),
comparators.compareDateLatestFirst('modifiedDate')
]));

最终还是需要用到 array的sort 方法,但由于这不是纯函数,所以保险的做法就是调用sort前,先在clone一下

Top

神奇的 ES6 继承执行顺序问题

刷推的时候无意间发现一位google 工程师发的一个感叹,感叹发现的一个神奇的JS 6继承顺序问题。。。

仔细看了看,确实好神奇,于是好奇的看了看babel转换出的结果:

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
var SuperClass = function SuperClass() {
_classCallCheck(this, SuperClass);

_defineProperty(this, "foo", function () {
return console.log('foo init in supper class');
}());

console.log('super construtor');
};

var WhatEver =
/*#__PURE__*/
function (_SuperClass) {
_inherits(WhatEver, _SuperClass);

function WhatEver() {
var _this;

_classCallCheck(this, WhatEver);

console.log('before sub class constructor');
_this = _possibleConstructorReturn(this, _getPrototypeOf(WhatEver).call(this));

_defineProperty(_assertThisInitialized(_this), "foo", function () {
return console.log('foo init in sub class');
}());

console.log('after sub class constructor');
return _this;
}

return WhatEver;
}(SuperClass);

new WhatEver();

就和他猜测的一样:没有supper的时候,字段的初始化是早于构造函数执行的,有supper的时候,字段初始化是在构造函数里的super后执行!

个人看法是故意放super之后是为了能在字段里访问到父类的字段?

Top

使用NetlifyCMS在线编辑Github上的博客

Netlify CMS 介绍

使用Netlify CMS我感觉有以下优点:

  • 无缝支持Hexo 等十几种主流静态网站生成器 的 文章后台管理*
  • 可视化在线编辑、新增github 上的markdown
  • 自带图片上传功能
  • 自动部署

支持列表:
Jekyll, GitBook,Hugo, Gatsby, Nuxt, Next, Gridsome, Zola,Hexo, Middleman, Jigsaw,Spike ,Wyam,Pelican,VuePress,Elmstatic,11ty,preact-cli

为什么使用它

对于我的情况:使用Hexo 网站生成器,托管在github上 https://github.com/Troy-Yang/troy-yang.github.io,其中Source branch是存放markdown等生成前分支,Master branch存放的是生成后的静态文件分支。
对于以前,如果要写一篇文章,基本是在source 分支里,新增一个markdown文件(可github上在线添加或者本地新增然后push),然后自动触发github 上配置的travis 自动部署流程,整体感觉已经很不错了。现在配置上Netlify CMS后, 可视化的在线编辑以及图片管理更加方便,可以随时随地发文章。
可惜, Netlify有个致命缺点:需要翻墙访问

HEXO NetlifyCMS 配置

只需要在hexo 的source/ 目录下添加admin 目录,新增下面两个文件:

1
2
config.yml
index.html

config.yml需要根据自己情况进行配置:

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
backend:
name: git-gateway
branch: Source

# This line should *not* be indented
media_folder: "source/images/uploads" # Media files will be stored in the repo under images/uploads
public_folder: "/images/uploads" # The src attribute for uploaded media will begin with /images/uploads

collections:
- name: "blog" # Used in routes, e.g., /admin/collections/blog
label: "Post" # Used in the UI
folder: "source/_posts" # The path to the folder where the documents are stored
create: true # Allow users to create new documents in this collection
slug: "{{slug}}" # Filename template, e.g., YYYY-MM-DD-title.md
fields: # The fields for each document, usually in front matter
- {label: "Layout", name: "layout", widget: "hidden", default: "post"}
- {label: "Title", name: "title", widget: "string"}
- {label: "Publish Date", name: "date", widget: "datetime"}
- {label: "Tags", name: "tags", widget: "list", required: false}
- {label: "Categories", name: "categories", widget: "list", required: false}
- {label: "Photos", name: "photos", widget: "list", required: false}
- {label: "Excerpt", name: "excerpt", widget: "string", required: false}
- {label: "Body", name: "body", widget: "markdown"}
- {label: "Permalink", name: "permalink", widget: "string", required: false}
- {label: "Comments", name: "comments", widget: "boolean", default: true, required: false}

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Content Manager</title>
</head>
<body>
<!-- Include the script that builds the page and powers Netlify CMS -->
<script src="https://unpkg.com/[email protected]^2.0.0/dist/netlify-cms.js"></script>
<script src="https://identity.netlify.com/v1/netlify-identity-widget.js"></script>
</body>
</html>

Netlify 端配置

创建 Netlify website

注意:和大部分人的做法不同的是,我Deploy到的地方并不是托管在Netlify自己的平台上,而是github上,所以这里我选择部署的是Source分支,而不是Master,因为我只是想要Netlify去修改我的Source分支,然后触发Travis自动发布到Master分支。

但是我依旧需要填写Netlify的部署,因为Netlify会自动帮我创建域名为troyyang.netlify.com的网站,任何我Source分支上的修改也会触发这个网站的自动部署

开启Netlify Identity 和 Git Gateway

在Setting 的 Identity选项下:

  1. Enable Identity service
  2. External providers 新增github
  3. Enable Git Gateway

发布测试

打开 https://troyyang.netlify.com/admin/ 然后使用github Oauth登录即可看到:

新增文章后, 你会发现github 上的source目录下的_post 目录的markdown 文件新增了,如果上传了图片,也会看到source目录下多了images/upload目录,同时https://troyyang.netlify.comhttps://troyyang.com 下也自动发布了新的文章, 两者都是因为Source分支里新增了文件导致的自动部署。



问题

当我尝试打开 https://troyyang.com/admin/ 使用github Oauth登录时,结果报错,而https://troyyang.netlify.com/admin/则没有:

1
Failed to load settings from /.netlify/identity

我怀疑是因为https://troyyang.com是托管在github上,而不是netlify上导致的。

Enjoy!

Top

AWS系列之使用无服务器架构你的网站


Serverless 有什么用啊?

Jason最近又出新想法了,想要做一个简单的用户管理系统,好的,没问题,不就是在服务器上安装数据库,部署好网站吗?可答案是no,他不是专业人员,我也不可能永远维护这个服务器,更重要的是服务器开着就要美刀啊,还不能停,怎么办?有没有可以不用服务器的网站,有啊,你自己的静态博客不就是只用到了s3或者github的静态页面托管吗?可是数据库呢,后台api呢?额,这个嘛。。。

好了,成功引出话题,要知道这是21世纪的云时代,只有你想不到,没有做不到的,这不,AWS早就提出了Serverless解决方案:S3 + GateWay API + Lambda + DynamDB,其中举例的一个天气的app架构:

image

其中s3做静态页面托管,用户触发点击事件,调用Gateway API提供到接口,接口映射到Lambda服务端接口,Lambda再负责去处理和数据库相关到操作。整个过程不需要服务器,而且费用是极低的,按量付费,可扩展性也很强,基本做到可配置化。说了这么多,还是得用过才知道好不好。

实现思路

  1. 服务端RestFull: Node express 实现RestFull API
  2. 创建lambda并上传服务端代码
  3. 配置API Gateway映射到lambda函数
  4. 客户端实现: Bootstrap 实现登录 和 管理页面
  5. 修改客户端api接口地址并上传至S3

其中,到第三步的时候我们就已经创建好了一个完整的无服务器的 Restfull API,剩下的就是客户端调用了,客户端调用这个就可以是五花八门的了,这也不是本篇文章的重点。

简单 RestFull 服务端实现

服务端的实现和平时实现一个Node RestFull api的完全没有任何区别, 部分代码如下:
app.js

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
'use strict';

const express = require('express');
const app = express();
const bodyParser = require('body-parser');
const cors = require('cors');

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());

let contacts = require('./data');

app.get('/api/contacts', (request, response) => {
if (!contacts) {
response.status(404).json({ message: 'No contacts found.' });
}
response.json(contacts);
});

const hostname = 'localhost';
const port = 3001;
const server = app.listen(port, hostname, () => {
console.log(`Server running at http://${hostname}:${port}/`);
});

module.exports = app

这一步做完,确保所有接口都能通过访问 localhost:3001/api/contacts

image

aws-serverless-express

要使得上面的服务端代码能在lambda中允许,只需借助 npm 包aws-serverless-express

在目录下新增 lambda.js文件

1
2
3
4
5
6
7
// lambda.js
'use strict'
const awsServerlessExpress = require('aws-serverless-express')
const app = require('./app')
const server = awsServerlessExpress.createServer(app)

exports.handler = (event, context) => awsServerlessExpress.proxy(server, event, context)

这也是为什么我们要在 app.js最后一行exports的原因

1
module.exports = app

此时,将所有文件包括node_module目录全部打包为.zip 文件为后面使用。

创建 lambda

创建 IAM role

创建 Lambda的IAM Role是必须的,他指定了当前lamda能访问到的资源有那些,从我们的列子中,我们需要用到DynamoDB, 同时为了方便debug,我们还需要用到cloudwatch服务 (这个对于查找问题非常有用)。

登录aws console,打开 Service 找到 IAM ,再选择Roles,点击 create role 按钮 后如图,(第三步可选):
image
image
image

创建 lambda 函数

打开Service 找到lambda, 选择 create function:
image

创建后,在代码输入种类中选择上传 .zip 文件:
image

将服务端代码整个打包 (注意一定要包括packages目录下的所有文件)然后上传,大小不能超过10m,如果超过了,可以在代码输入种类选择s3上传。上传完成后,指定入口文件(即在处理程序)为 lambda.handler, 此文件将会映射到 lambda.js文件,一般情况,如果上传的zip包不是很大,aws会自动列出zip项目目录可供在线编辑,但如果大了的化,比如好几兆,则有可能不会列出项目目录,每次修改又只能重新上传。

image

当然,如果node 代码里包括了一些环境变量,你也可以为 lambda 做一些环境变量的设置:

image

一切ok后,就可以测试了,关于lambda的测试,则相对还比较麻烦,我也是最近才稍微懂那么一点。

测试 lambda 函数

在创建好的lambda 函数旁,点击配置测试事件按钮,在弹出对话框创建测试事件中选择创建新测试事件,在事件模板中选择 Amazon API Gateway AWS Proxy, 并给个测试名称,如图:
image

选择Amazon API Gateway AWS Proxy是因为我们的这个lambda函数最终会被API Gateway 触发调用,同时由于默认的事件模板是 post 的请求方式,而我们的这个服务端只有一个api/contacts的get方法,所以我们需要更改事件内容为:

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
{
"resource": "/{proxy+}",
"path": "/api/contacts",
"httpMethod": "get",
"headers": {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Encoding": "gzip, deflate, sdch",
"Accept-Language": "en-US,en;q=0.8",
"Cache-Control": "max-age=0",
"CloudFront-Forwarded-Proto": "https",
"CloudFront-Is-Desktop-Viewer": "true",
"CloudFront-Is-Mobile-Viewer": "false",
"CloudFront-Is-SmartTV-Viewer": "false",
"CloudFront-Is-Tablet-Viewer": "false",
"CloudFront-Viewer-Country": "US",
"Host": "1234567890.execute-api.ap-northeast-1.amazonaws.com",
"Upgrade-Insecure-Requests": "1",
"User-Agent": "Custom User Agent String",
"Via": "1.1 08f323deadbeefa7af34d5feb414ce27.cloudfront.net (CloudFront)",
"X-Amz-Cf-Id": "cDehVQoZnx43VYQb9j2-nvCh-9z396Uhbp027Y2JvkCPNLmGJHqlaA==",
"X-Forwarded-For": "127.0.0.1, 127.0.0.2",
"X-Forwarded-Port": "443",
"X-Forwarded-Proto": "https"
}
}

保存测试事件,并点击执行,如果一切正常,会得到如下:
image

创建 API Gateway

找到Service下到API Gateway,并点击新建 api,

image
新增 api 资源(路径)
image
选择 api 资源,再新增子资源,并选为proxy
image
选择 proxy 资源,创建 集成环境为我们创建好的lambda 函数
image

创建完成之后,在操作选项中,选择部署,弹出对话框并命名为dev阶段:
image

部署完成后,得到如下结果:
image

API Gateway 测试

在部署完成后,我们会在上述结果中得到发布出来的api 地址为

https://ijihnuupmh.execute-api.ap-northeast-1.amazonaws.com/dev

此时如果直接访问,会得到Missing Authentication Token的错误,原因是我们地址不对

1
{"message":"Missing Authentication Token"}

正确地址应该为:

https://ijihnuupmh.execute-api.ap-northeast-1.amazonaws.com/dev/api/contacts

image

由于上述地址是永久的,除非你重新部署,所以我们可以放心的使用用来作为api地址。还有一个就是API Gateway似乎是不收取费用的,只会按照lambda函数的调用次数来收取费用,好像每月前100万次请求是免费的。所以还是相当划算。

OK! 一个无服务器的后端 api 就这样搭建好了,剩下的就是前端静态资源的托管了

前端静态资源

直接上传html,js,css 等静态资源到S3就好了,具体可以参见另一篇博客 AWS系列之S3 + Cloudfront搭建https静态网站

Top