赞
踩
一旦试通了Rust通用逻辑功能和FinClip小程序的结合,可以玩的东西就很多了。我们先试试用Rust生成二维码,并以inline SVG方式提供给小程序渲染展示
前面一番操作,从小程序前端到Rust跨平台通用算法逻辑library,基本搞通。再增加其他功能的话,照虎画猫也不难。
就算是Paper wallet,也还得有两个二维码才能用(总不能每次手敲几十位无法记忆的数字吧?) - 一个是钱包地址,接受支付或者查自己的资产时用;另一个是私钥,在交易时使用。
感谢Rust开源世界的大神们,要给我们的钱包加个生成二维码的功能,真是唾手可得。再把我们在前面已经玩熟了的FinClip小程序+Rust的套路拿出来弄一次,顺便还能额外试试SVG在小程序里面怎么搞:
有了上述“指导思想”,我们选择了以下的方案:
下面我们把之前介绍的从Rust到FinClip小程序的流程又“机械”的走一次。
首先,改一下Rust部分的代码:
- finclip-rust
- |---- ios
- |---- mini-app
- |---- rust <==== 修订对象
在rust子项目的根目录下,对Cargo.toml编辑,增加两个库:
- [dependencies]
- ...
- qrcodegen = "1.8.0"
- base64 = "0.13.0"
然后编辑修订src/lib.rs,增加一个QR Code的生成函数,因为是打算给小程序调用的,所以也需要输出为C函数。QR code生成部分特别简单,'QrCode::encode_text(&str, ECC-level)'即可生成,其中ECC为Error correction,即当二维码损毁的时候,视乎损毁程度,二维码中浓缩的数据的程度(四级)以支持容错。我们选个medium即可:
- #[no_mangle]
- pub unsafe extern "C" fn generate_qrcode_svg(input: *const c_char) -> *const c_char {
- let s = CStr::from_ptr(input);
- let sp = s.to_str().unwrap();
-
- let qr = QrCode::encode_text(sp, QrCodeEcc::Medium).unwrap();
- let svg = to_svg_string_base64(&qr, 4);
-
- let result = CString::new(svg).unwrap();
- let c_result: *mut c_char = result.into_raw();
-
- c_result
- }
注意这里比较重要的,是这个'to_svg_string_base64()'函数的实现,它实际上是“手工”生成一个XML文本。一个SVG的“图片”,例如以下这个star.svg,用文本编辑器打开,实际上它是这个样子:
- <svg height="210" width="500">
- <polygon points="100,10 40,198 190,78 10,78 160,198" style="fill:lime;stroke:purple;stroke-width:5;fill-rule:nonzero;"/>
- Sorry, your browser does not support inline SVG.
- </svg>
to_svg_string_base64()的代码也没几行,详情可在项目代码库中查看,基本原理就是生成上述XML格式的数据,并调用'base64:encode'函数把它变成一串在小程序中可用的数据。SVG原始数据,例如是这样:
- <svg xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 38 38" stroke="none">
- <rect width="100%" height="100%" fill="#FFFFFF"/>
- <path d="M4,4h1v1h-1z M5,4h1v1h-1z M6,4h1v1h-1z M7,4h1v1h-1z M8,
- 4h1v1h-1z M9,4h1v1h-1z M10,4h1v1h-1z M14,4h1v1h-1z M15,
- ......
- fill="#000000"/></svg>
再经过base64的转换后,会变成这样(注:以下例子只是一个二维码SVG转换后很长的字符串的部分节选,实在太长无法完整粘贴在此):
PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZlcnNpb249IjEuMSIgdmlld0JveD0iMCAwIDM3IDM3IiBzdHJva2U9Im5vbmUiPgogICAgICAgIDxyZWN0IHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiIGZpbGw9IiNGRkZGRkYiLz4KICAgICAgICA8cGF0aCBkPSJNNCw0aDF2MWgtMXogTTUsNGgxdjFoLTF6IE02LDRoMXYxaC0xeiBNNyw0aDF2MWgtMXogTTgsNGgxdjFoLTF6IE05LDRoMXYxaC0xeiBNMTAsNGgxdjFoLTF6IE0xNCw0aDF2MWgtMXogT60F6IE0yOCwzMmgxdjFoLTF6IE0zMCwzMmgxdjFoLTF6IiBmaWxsPSIjMDAwMDAwIi8+Cjwvc3ZnPgo=
按照老规矩,我们写在/src/lib.rs的代码,到/examples下修订一下test.rs:
- //test.rs
- //
- // 测试在Rust侧生成钱包密钥对,转换成C侧的数据结构。
- // 测试钱包在C侧调用接口存储和重新读出钱包密钥
- //
-
- use std::ffi::{CStr};
- //use rustylib::gen::{CWallet};
- use rustywallet::{
- CWallet,
- generate_cwallet,
- free_cwallet,
- save_wallet,
- fetch_cwallet,
- generate_qrcode_svg};
-
- fn main() {
-
- unsafe {
- let wallet: CWallet = generate_cwallet();
- println!("---- generated a wallet to be used on C-side ----");
- print_wallet(&wallet);
-
- println!("---- saving the wallet to wallet.json ----");
- save_wallet(&wallet);
- println!("---- saved! ----");
-
- println!("---- fetching the saved wallet to be exposed to C-side ----");
- let fetched = fetch_cwallet();
- print_wallet(&fetched);
-
- free_cwallet(wallet); // 对应 generate_cwallet()
- free_cwallet(fetched); // 对应 fetch_wallet()
- }
- }
-
- unsafe fn print_wallet(wallet: &CWallet) {
- let a = CStr::from_ptr(wallet.public_addr);
- let pa = a.to_str().unwrap();
- println!("public address=> {}", pa);
- let qrcode_pa = generate_qrcode_svg(wallet.public_addr);
- let c_qrcode_pa = CStr::from_ptr(qrcode_pa);
- println!("QR Code:\n {}", c_qrcode_pa.to_str().unwrap());
-
- let pk = CStr::from_ptr(wallet.public_key);
- let ppk = pk.to_str().unwrap();
- println!("public key=> {}", ppk);
-
- let sk = CStr::from_ptr(wallet.private_key);
- let psk = sk.to_str().unwrap();
- println!("private key=> {}", psk);
-
- }

运行测试一下无碍,就按老办法打包生成iOS universal library:
- cargo run --example test
- cargo lipo --release (或者直接用scripts/ios_build.sh,输出头文件和库到ios项目目录中,更加便利)
Rust部分宣告结束。
接下来到了宿主部分,要把Rust的新功能给集成进去,也是非常的机械、简单。
- finclip-rust
- |---- ios
- | |---- clip.xcodeproj
- | |---- clip
- | |---- FinClipExt.m <==== 修改一下
- |---- mini-app
- |---- rust (done)
也就是在FinClipExt,这个所有准备向小程序输出的自定义API的“挂靠单位”(一个singleton),修改一下'generate_wallet'这个函数,在钱包中生成钱包地址后,把这个地址的字符串作为输入,让Rust部分的代码生成对应的SVG数据并转换成base64编码,然后再把结果放到一个叫"public_address_qrcode"的字段中,随着'generate_wallet'返回的NSDictionary返回。
终于来到了小程序的展现部分,修改也是几分钟的事情:
- finclip-rust
- |---- ios (done)
- |---- mini-app
- | |---- pages
- | |---- generate
- | |---- generate.fxml <==== 增加两行代码
- | |---- generate.js <==== 增加三行代码
- |---- rust (done)
首先,自然是要在界面增加一个地方来展示二维码,我们采用最简单粗暴的办法,插入这么两行:
- <view style="width:148px;height:148px;background:url('{{ wallet.public_address_qrcode }}') no-repeat center">
- </view>
这是干嘛的呢?就是以inline方式,把本地生成的SVG通过CSS设为background image。为什么不直接用HTML5所支持的SVG标签?因为... 这是当前互联网上小程序平台的通用做法,考虑到大家都这么干... FinClip首先还是选择了兼容互联网主流技术,希望后续对SVG有更强大的支持吧。
然后,就是上面这个'{{wallet.public_address_qrcode}}'的变量的数据生成了。这是比较tricky的部分,涉及所谓“内联”的情况。
“内联”,就是把在当前网页内的数据(可能是静态,但更可能是动态生成)当作一个外部资源供页面渲染时使用。以图片为例,通常我们在网页里通过img的标签,让它的src指向图片所在的某个URL地址,从而网页渲染时会把图片资源加载进来。但如果该图片是一种当前页面内的数据呢?我们怎样告诉浏览器去加载它、渲染它?解决方案是通过一种叫做Data URI scheme的格式,例如:
- <img src="data:image/png;base64,iVBORw0KGgoAAA
- ANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4
- //8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU
- 5ErkJggg==" alt="Red dot" />
Data URI scheme的语法是这样:
data:[<media type>][;base64],<data>
我们要内联式生成SVG,它的data URI scheme是:
"data:image/svg+xml;base64,"
所以,我们现在来到mini-app/pages/generate/generate.js,在这里我们要生成一串数据是符合这个规范的,把它返回到generate.fxml的'{{wallet.public_address_qrcode}}'中。老办法,改一改小程序的onShow函数:
- onShow: function (options) {
- const wallet = ft.generate_wallet();
- const qrcode_svg = "data:image/svg+xml;base64," + wallet.public_address_qrcode;
- console.log("qrcode");
- console.log(qrcode_svg);
-
- this.setData({
- ['wallet.public_address']: wallet.public_address,
- ['wallet.public_address_qrcode']: qrcode_svg,
- ['wallet.public_key']: wallet.public_key,
- ['wallet.private_key']: wallet.private_key
- })
- }
也是足够的简单直观了。Rust侧把脏活累活都干好了,到了小程序侧,也就是给数据加个明文的data URI scheme前缀,结束。
当然,要最后联调我们还得用FinClip社区版的企业端、运营端,自己带上开发者、管理者的帽子,自己提交一下新版本的小程序,再自己审批上架。然后用重新在xcode编译的宿主App刷一刷 - 带二维码的加密钱包马上呈现。
从 Rust 到达FinClip 小程序,开发过程实质上是层层的跨语言的接口转换、数据转换。
只要把中间的转换机制弄明白弄熟练,其实技术上的实现是比较机械的。一旦成为“熟手技工”,熟练掌握写转换代码的技能,我们就可以把注意力、创新焦点放在 Rust 侧和小程序侧,一侧让我们实现算法型、逻辑型的跨平台通用的代码,另一侧让我们赋能其他前端开发者去创造应用。
如之前的几篇文章所介绍,我们需要经过三种语言、数据结构在各语言之间的适配:
代码层层转换,是 Polyglot Programming(多语言混合编程)所难以避免 - 我们用最适合的语言去解决最对口的问题,但是最终每一个局部方案都需要互相连贯起来。实现了第一次,第二、第三次就难度按指数级别下降,也许最后就是“唯手熟尔”。
受时间与篇幅限制,还有更多内容未及试验。以下是一些可以在现有代码上继续扩展的功能,是熟悉 FinClip+Rust 的很好练习:
欢迎读者对这个 demo 继续作出更多尝试,寻找实际应用场景、探索实用价值。
本文首发于凡泰极客博客,作者:F1n0Geek
Source code on Github: GitHub - kornhill/finclip-rust-demo
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。