如何用Erlang构建Mixin Network小程序

小程序相对于原生App,有无可比拟的优点

  • 不需要Apple Store或Play Store的审核。
  • 跨平台 小程序可实现安卓苹果手机同时上线使用。
  • 免下载 比如在超市买单,在没有Wifi的情况下,用小程序就省去了下载的麻烦。

 Erlang与rebar3的安装

macOS

brew install erlang

Ubuntu

apt update
apt upgrade
apt install erlang

注意:安装好erlang后才能安装rebar3

git clone https://github.com/erlang/rebar3.git
cd rebar3
./bootstrap
./rebar3 local install

本地化安装后,rebar3创建了自己的安装目录,需要在配置文件中将目录加入到$PATH,这样每次打开终端,才能直接运行rebar3了,方法如下:
macOS (注意,将{user}替换成你的用户名)

echo 'export PATH=/Users/{user}/.cache/rebar3/bin:$PATH' >> ~/.bash_profile

Ubuntu (注意,将{user}替换成你的用户名)

echo 'export PATH=/{user}/.cache/rebar3/bin:$PATH' >> ~/.bashrc

至此,erlang与rebar3都安装好了!

接下来,我们用rebar3构建一个wk_bot程序!

第一步  到mixin.one申请创建一个bot程序

目前,Mixin Network不需审核,就可以轻易创建一个App,申请步骤请移步到下面的链接!

Mixin App注册教程    注册入口地址

第二步  创建第一个erlang程序

wenewzha:tmp wenewzhang$ rebar3 new release wk_bot
===> Writing wk_bot/apps/wk_bot/src/wk_bot_app.erl
===> Writing wk_bot/apps/wk_bot/src/wk_bot_sup.erl
===> Writing wk_bot/apps/wk_bot/src/wk_bot.app.src
===> Writing wk_bot/rebar.config
===> Writing wk_bot/config/sys.config
===> Writing wk_bot/config/vm.args
===> Writing wk_bot/.gitignore
===> Writing wk_bot/LICENSE
===> Writing wk_bot/README.md

在这对创建的文件进行一下解释,

erl  erlang源代码
src erlang虚拟机启动的程序,比如基本的stdlib,ssl,inets
rebar.config 包管理文件,相当于nodejs的package.json
sys.config 配置程序的参数
vm.args 配置erlang虚拟机,如节点名字,CPU内核使用等
rebar.lock 不需要修改

配置rebar.config,在deps里加入三个包,分别是jwt(json web token),gun(websocket),uuid

{deps, [
  {jwt, ".*", {git, "https://github.com/wenewzhang/jwt",{tag,rsa512}}},
  {gun, "1.3.0"},
  {uuid, "1.7.4", {pkg, uuid_erl}}
]}.

erlang的包管理在hex.pm可以查询,这里jwt是我做了一点修改,原来的包没有rsa512的加密,而Mixin的Token是用了rsa512签名的,上面的包加入了rsa512的签名算法。

下面,我们可以编译新创建的App

wenewzha:wk_bot wenewzhang$ rebar3 compile
===> Verifying dependencies...
===> Fetching gun ({pkg,<<"gun">>,<<"1.3.0">>})
===> Version cached at /Users/wenewzhang/.cache/rebar3/hex/hexpm/packages/gun-1.3.0.tar is up to date, reusing it
===> Fetching jwt ({git,"https://github.com/wenewzhang/jwt",
                               {tag,rsa512}})
===> Fetching rebar3_hex ({pkg,<<"rebar3_hex">>,<<"6.1.0">>})

这里我介绍几个rebar3指令,可以复制到Makefile文件里,

all:
	rebar3 compile
debug:
	rebar3 as test shell
tar:
	rebar3 as prod release
	rebar3 as prod tar
run:
	rebar3 tar
	/Users/wenewzhang/Documents/sl/wk_bot/_build/default/rel/wk_bot/bin/wk_bot console
rebar3 compile 编译
rebar3 as test shell 在console里debug

rebar3 as prod release

rebar3 as prod tar

生成发布包,生成的tar.gz包可以直接在相同的系统里运行,

自带erlang虚拟机与运行环境

rebar3 tar 重新生成tar.gz安装包

第三步 写一个Hello,World程序

修改wk_bot_sup.erl,在init函数加入

init([]) ->
    io:format("Hello,World~n"),
    {ok, {{one_for_all, 0, 1}, []}}.

依照上面的Makefile,第一次运行程序,只需要make & make debug就可以运行了,效果如下

wenewzha:wk_bot wenewzhang$ make debug
rebar3 as test shell
===> Verifying dependencies...
===> Compiling wk_bot
Erlang/OTP 21 [erts-10.1.3] [source] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe] [dtrace]

Eshell V10.1.3  (abort with ^G)
1> ===> The rebar3 shell is a development tool; to deploy applications in production, consider using releases (http://www.rebar3.org/docs/releases)
Hello,World
=PROGRESS REPORT==== 10-Dec-2018::22:05:49.848839 ===
    application: wk_bot
    started_at: [email protected]

你可能会奇怪,为什么没有像其它语言,需要include一个头文件才可以用io:format?在erlang里,每一个库都有一个app的实例在运行,我们通过wk_bot.app.src进行配置,applications.stdlib就引入了io:format

下面我们进入构建wk_bot的关键环节

第一步  先准备好上一步我们注册的app资料,包含appid,secret id,private key等,将此文件写入hrl头文件

-define(CLIENT_ID,"21042518-85c7-4903-bb19-f311813d1f51").
-define(CLIENT_SECRET,"8cc112e77c25457e287b39c786b4e29edd2035a9deb2f658e17c99d56fdfb13a").
-define(SCOPE_PROFILE,<<"PROFILE:READ">>).
-define(MXN_OAUTH_STEP_ONE_URL,<<"https://mixin.one/oauth/authorize?client_id=~s&scope=~s">>).
-define(MXN_OAUTH_CLIENT_URL,<<"https://mixin.one/oauth/oauth/token">>).
-define(PIN,232913).
-define(SESSION_ID,"4c6bda11-3460-4bc9-9673-996ac34b7907").
-define(PIN_TOKEN,<<"
IMnZTGJmqdA8Lax81+10ltmVQVRAug+u/Qsv3VRB7QEJgFYJ9WL8PQG1e4wdZwym2bCYheHkUTaLbSWVI6o2FEXoHBfftnOYzN1et9JCJwYfPCfDmBCQ2FWKXjhGXy6huqsUMRTErROpsSkRkQskeSudFwJrurnRsdgQyvzdVHw=
">>).
-define(PRIVATE_KEY,<<"
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQCfaMTR0HBsRr3MVMTACVCYWCx6cSdC9c4q2H3kUD7KjS5VuCXR
eT4aBY1fk5s4icurxutEaVt1V2M1DyDqy7EJO7nNL7hUz6iCZ0REpr6D6FviE5BD
TCwX1unNF0QLHi3wbHRDHt+0evlawujopM/H+BXkxNdVaHpqeunj7C1sXwIDAQAB
AoGAPufTM5DzrGbGI0oYUkfavCOfeboJak0hzJqeI2jfPoM0E7OViPI1ZYNnZJ4V
FNybuO/Ii7if1NBlX9zWepFjDMamDoVpL5bTUL8eQ4lfr80boWrF1AOnYBM4Xp5Q
n0pmtp5nFRCwL1lnRF702eXxwmn6hURTnYCcb9VybMLftWECQQDpL4Wc2uJgGsux
8oBBL41K64j4rxWeOARsrS56bTseqUT16hByfV7PVPMc8kpupY+P3iuQhCBOSJTa
CW5h417ZAkEArwFonuyuXMHD6KnwMS6oP8NklV+d1joza+U3OVM+MzWMoKHNO9z6
pWtzNS9ypkLYQPhR5KvhsZX8LNJrtTSR9wJBAIvM5OMMS2noxrRxubjbBG+lVGIb
ve80kFqDXXkioa4ZN3HjmWa6iSvuNy7kiAFcGvza6u1ieWfVlgA+ZUIkqckCQA1u
iD8aX0+TN5wV3u+HazZposCsNAsLMIMpdpGZx/5aL87sXDop/brQgmkkmSIVo09p
P6/TWWEt58rw439m54UCQQDN0F6oGJzR/RX6FhEpt6zge+8Kpv6IaeHbUpQ0uaEp
TTi81xsOsJoFhTUOyzsQVA2doGV7D9ptfekMCeJrbabM
-----END RSA PRIVATE KEY-----
">>).

第二步  用create-genserver shell脚本,生成一个wk_websocket.erl文件,create-genserver在github源代码里有

然后在wk_bot_sup里,加入下面代码:

init([]) ->
    io:format("Hello,World~n"),
    Specs = [
    spec(wk_websocket)
    ],
    {ok, {{one_for_all, 0, 1}, Specs}}.

%%-------------------------------------

spec(M) -> {M, {M, start_link, []}, permanent, brutal_kill, worker, [M]}.

在erlang/otp里,这里的wk_bot_sup是supervisor即管理员,wk_websocket是一个worker,一个supervisor可以管理多个worker,这里的Specs可以加入多个worker,这样,我们新建的wk_websocket就随之启动了。

第三步  生成signAuthenticationToken

介绍一下Mixin的Token,Token由三部分组成,

base64(alg).base64(client_info).signature(base64(alg)+base64(client_info))
分别是加密算法名称+客户端信息+算法名称与客户端信息生成的签名,如果我们在websocket里用ws进行通讯,这里的加密算法名称与客户端信息只是进行base64编码,并没有加密,但我们用私钥进行的签名,是无法伪造的。
JWT代码

encode(Alg, ClaimsSet, Key) ->
    Claims = base64url:encode(jsx:encode(ClaimsSet)),
    Header = base64url:encode(jsx:encode(jwt_header(Alg))),
    Payload = <<Header/binary, ".", Claims/binary>>,
    case jwt_sign(Alg, Payload, Key) of
        undefined -> {error, algorithm_not_supported};
        Signature -> {ok, <<Payload/binary, ".", Signature/binary>>}
    end.

signAuthenticationToken代码

signAuthenticationToken(Uid,Sid,PrvKey,Method,Uri) ->
  Iat = os:system_time(seconds),
  % three hour
  Exp = os:system_time(seconds) + (3*60*60),
  Token = [
   {jti,list_to_binary(uuid:uuid_to_string(uuid:get_v5(uuid:get_v4_urandom())))},
   {uid,list_to_binary(Uid)},
   {sig,list_to_binary(to_hex(crypto:hash(sha256,Method ++ Uri)))},
   {exp,Exp},
   {sid,list_to_binary(Sid)},
   {iat,Iat}
   ],
  [Entry] = public_key:pem_decode(PrvKey),
  Key = public_key:pem_entry_decode(Entry),
  {ok,SignDt} = jwt:encode(<<"RS512">>,Token,Key),
  Dt = jwt:decode(SignDt,Key),
  SignDt.

to_hex([]) ->
    [];
to_hex(Bin) when is_binary(Bin) ->
    to_hex(binary_to_list(Bin));
to_hex([H|T]) ->
    [to_digit(H div 16), to_digit(H rem 16) | to_hex(T)].

to_digit(N) when N < 10 -> $0 + N;
to_digit(N)             -> $a + N-10.

下一步,我将讲用gun包写websocket与mixin.one进行通迅(敬请期待)!

github源代码

添加新评论

Restricted HTML

  • 允许的HTML标签:<a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • 自动断行和分段。
  • 网页和电子邮件地址自动转换为链接。