且听疯吟如此生活三十年

编译、测试、打包、部署这一系列的操作实在是太麻烦而且容易出错漏,能自动化的东西我们就没必要手动去点。端着咖啡悠哉地等着叮的一声,安装包出现在面前,这才是我们想要的
花了点时间,把正在进行的 React Native 项目的自动部署完善了一下,实现了通过 Travis CI 自动编译测试,并打包成 ipa 发布到 FTP 的整个流程
现在只要 Push 到 Github 上,等到 Travis CI 运行完成,就直接可以拿到 ipa 包安装测试了

准备工作:
首先你需要一个 Github 账户
Travis CI 连接 Gtihub 后,会自动检查根目录下带有 .travis.yml 的项目
关于 Travis CI 的功能和文档,请参考 https://docs.travis-ci.com/

这里给出一个 React Native 项目的 yml 文件示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
language: objective-c
osx_image: xcode7.1
xcode_project: ios/MyApp.xcodeproj
xcode_scheme: MyApp
env:
matrix:
- SPEC=spec1

before_install:
- ./scripts/decrypt_key.sh
- ./scripts/add_key.sh
- brew update

install:
- brew reinstall node flow watchman xctool
- npm install -g react-native-cli

branches:
only:
- master

script:
- ./scripts/release.sh

首先设置项目编译环境:

包括 language 和 要使用的编译镜像 osx_image,并指定项目文件和编译的 scheme

证书加密:

由于 iOS 打包过程需要一些证书密钥,这些是无法公开 Push 到 Github 的
虽然 Travis CI 没有提供 security file 的功能,但是提供了一个 security 的环境变量的功能
因此,我们可以通过在本地加密证书然后上传,线上编译时再解开来实现这一目的

比如:

  • Encrypt
1
2
3
4
# encrypt_key.sh
openssl aes-256-cbc -k ${ENCRYPT_PASS} -in scripts/MyAppdev.mobileprovision -out scripts/MyAppdev.mobileprovision.enc -a
openssl aes-256-cbc -k ${ENCRYPT_PASS} -in scripts/dist.cer -out scripts/dist.cer.enc -a
openssl aes-256-cbc -k ${ENCRYPT_PASS} -in scripts/dist.p12 -out scripts/dist.p12.enc -a
  • Decrypt
1
2
3
4
# decrypt_key.sh
openssl aes-256-cbc -k ${ENCRYPT_PASS} -in scripts/MyAppdev.mobileprovision.enc -out scripts/MyAppdev.mobileprovision -d -a
openssl aes-256-cbc -k ${ENCRYPT_PASS} -in scripts/dist.cer.enc -out scripts/dist.cer -d -a
openssl aes-256-cbc -k ${ENCRYPT_PASS} -in scripts/dist.p12.enc -out scripts/dist.p12 -d -a

注意: 请不要直接上传你的证书和密钥!也不要把密码上传到公开的地方!

导入证书到 Travis 编译环境:

参考下面的步骤:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# add_key.sh

# Create a custom keychain
security create-keychain -p travis ios-build.keychain

# Make the custom keychain default, so xcodebuild will use it for signing
security default-keychain -s ios-build.keychain

# Unlock the keychain
security unlock-keychain -p travis ios-build.keychain

# Set keychain timeout to 1 hour for long builds
# see http://www.egeek.me/2013/02/23/jenkins-and-xcode-user-interaction-is-not-allowed/
security set-keychain-settings -t 3600 -l ~/Library/Keychains/ios-build.keychain

# Add certificates to keychain and allow codesign to access them
security import ./scripts/apple.cer -k ~/Library/Keychains/ios-build.keychain -T /usr/bin/codesign
security import ./scripts/dist.cer -k ~/Library/Keychains/ios-build.keychain -T /usr/bin/codesign
security import ./scripts/dist.p12 -k ~/Library/Keychains/ios-build.keychain -P $KEY_PASS -T /usr/bin/codesign


# Put the provisioning profile in place
mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles
cp "./scripts/MyAppdev.mobileprovision" ~/Library/MobileDevice/Provisioning\ Profiles/

安装环境

安装编译所需要的环境,比如 xctool、node、npm、react-native 等等

编译和打包

举个栗子:

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
# release.sh

npm install

react-native bundle --dev false --platform ios \
--bundle-output "/tmp/main.jsbundle" --entry-file index.ios.js

xctool -project ios/MyApp.xcodeproj -scheme MyApp build -sdk iphoneos \
configuration Release OBJROOT=$PWD/build SYMROOT=$PWD/build

if [[ $? != 0 ]];then
echo ">> build failed"
exit 1;
fi

PROVISIONING_PROFILE="$HOME/Library/MobileDevice/Provisioning Profiles/MyAppdev.mobileprovision"
OUTPUTDIR="$PWD/build/Release-iphoneos"
NAME=Esports_${TRAVIS_COMMIT:0:6}.ipa

xcrun -log -sdk iphoneos PackageApplication "$OUTPUTDIR/MyApp.app" \
-o "$OUTPUTDIR/$NAME"

if [[ $? != 0 ]];then
echo ">> package application failed"
exit 1;
fi

cd $OUTPUTDIR
curl --ftp-create-dirs -T "$NAME" -u $FTP_USER:$FTP_PASSWORD \
ftp://$FTP_SERVER/esports/$NAME

echo ">> {$NAME} uploaded!"
echo ">> all done!"

基本上和本地编译运行 React Native 项目类似,不同之处在于使用了 xctool 命令行来编译

  • 首先安装依赖 package
  • 然后 react-native bundle (如果使用 online 的模式,可以忽略这一步)
  • 使用 xctool 编译和测试项目
  • 打包 App 为 ipa 文件
  • FTP 发布(Travis CI 支持多种发布方式,可以参考文档实现)
  • 完成,邮件或 IM 通知(如果你需要)

ref: https://www.objc.io/issues/6-build-tools/travis-ci/

最近了解了下 react 和 react native,感觉看到了一条成为「Full-stack Developer」的捷径啊(雾

React

  • 消息同步
    不管是 Web 还是 Native 应用,一个很麻烦的问题就是保持某个 Message 在不同 View 之间的状态同步。
    比如,收到一条消息,需要在未读标签显示状态和数字,然后未读列表中插入一条消息记录。
    阅读消息后,需要从未读中去掉这条记录,同时在已读中新增一条记录,还不能忘了将未读数字减去。
    我们是怎么处理这种情况?
    要在接收消息的 handler 中判断各个 View 当前的状态,然后根据对应的状态写不同的处理代码,阅读消息同理。当然高端点的可能自己搞个 Manager 之类的东西去对这些逻辑进行管理。
    但是,并没有什么用。一旦业务复杂,处理和 Debug 起来就很麻烦了。看上去未读是 1,点进去发现是空,这种场景想必见得也不少了。

      还记得很久之前我们怎么写页面的吗?(多久?大概到 ASP 吧=-=
    

    那时候我们没有这么多烦恼。因为我们是这么干的。不管什么操作,ok,刷新页面,可以保证所有状态都是正确的,so easy!
    React + Flux 所做的正和我们很久之前的做法有点像。数据更新了?重新渲染一遍 View 不就好了。
    但是将整个 DOM 重新渲染一遍是开销很大的,当然不可能真的这么做。
    所以就有了 Virtual DOM。
    其实我们对 View 所做的操作大部分都是修改内容,比如修改某个块里面的文字啦,对某个 list 增删改啦。这些都不需要完全重绘。
    React 做到了「智能」去更新 DOM,只改变需要改变的地方。(嗯,就是不相信你能写出高效正确的操作 DOM 的代码,所以我们都帮你写好啦~(雾

  • 组件化
    通常我们的页面上的控件都是带有不同的状态的,比如选框是否选中等等。而不同情况下状态可能会根据一些参数去变化,这让一般情况下的组件复用变得很麻烦。
    而 React 可以做到类似于函数的给定输入参数,输出固定的状态,可以很方便的实现 Web Components,也不会出现组件状态上的冲突。
    而组件化的开发带来的效率提升是非常高的。

  • 其他
    另外经常看到人说 HTML 嵌在 JS 里面感觉很奇怪是不是模板耦合在代码里面之类的,我倒是觉得这种写法挺简洁优雅的。如果把 JSX 里面的 XML 看作是一个 XML 表示的 Object,这样理解可能会好点吧。当然你也可以使用纯 JS 的方式去写。

好的东西往往是看上去简单实现复杂。
Java 本身虽然不怎么样,但是它可以让不同水平的人写出能够达到标准的代码,依然坚挺不是没有理由的。
所以我多少也有些认同 react 可以让不同水平的前端工程师写出符合效用的代码这个说法了。
算是一点并没有深入研究过前端的人的一点想法吧。

React Native

最近公司同时在做某个应用的 iOS 和 Android 版本,虽然没有参与开发,但是从两边的进程来看,大部分时间其实是把同一份逻辑翻译成 Objective-C 和 Java,但是这也是没办法的事情。
毕竟现有的 Hybrid App 方案表现都不太理想, Native 的优势暂时无法替代。如果可以借鉴 React 的优势和开发效率,同时带来 Native 的体验,excited!
所以 React Native 就顺理成章了。
难得的是 React 没有提「Write once, run anywhere」(JAVA:=-=)
而是 「Learn once, write anywhere」。

目前感觉除了基于 React 本身的那些优势外,React Native 在开发 App 方面也是很爽的

  • 效果
    WebView 总是让我们感觉没有 Native 那么顺畅自然,大概是因为触摸反馈之类的原因吧。React Native 在这方面则没有这个问题,可以非常容易的用到 Native 的触摸和动画效果
  • flex 布局
    熟悉 Web 那一套的话用起来还是很方便的。但是 React Native 的 flex 只是 Web 的 Lite 版,虽然尽力去做 Native 兼容,但是表现能力不如 Web 也不如 Native 是必然的,幸好移动端布局通常没有 Web 那么复杂。
  • 组件化
    React Native 提供了基本组件,通过自己组合,可以做出很多意想不到的效果。加上通过 npm 可以引入其他开源的组件,组合出一个 App 更加容易了。
  • 效率
    从安装 Xcode 到 写出一个新闻列表功能只花了两个小时,虽然之前完全没有接触过 iOS 开发。大公司项目这种提供详细文档和 starterkit 的作风真是太赞。
  • 调试方便
    即时刷新,用调试 Web 的方式调 App 真的很爽
  • 集成到现有 App
    可以将某个 React Native 实现的部分集成到现有 App 中,对已有的业务不产生影响

缺点也有:

  • 平台
    除了一些容易抽象的组件比如 ListView,View,Text 之外,目前很多组件是限于平台的。这点也是可以理解的,毕竟不同平台的 API 千差万别,很难做到通用。所以想要一次开发之后小小修改就可以通吃的可以醒醒了 =-=
    这也导致实际上还是无法绕开去,必须要了解 Native 开发的一些组件和细节。比如在 iOS 中,首先你需要放一个 Tabbar,然后每个 Tabbar Item 里面放一个 Navigator,这和 Native 开发其实是差不多的模式。单纯从做 Web 过来的人,还是需要去学习这些东西才能开始。
    但是比起完整使用 Native 开发,这个学习时间要短得多。
  • 表现能力
    虽然 React Native 的表现能力不如 Native ,但是它并不是为了取代 Native 开发存在的。在需要的领域做得足够好,这就够了。
  • 第三方库
    包括 React 和 React-Native 都有这个问题。事实上由于 Virtual DOM 和 React 革命性的架构方式,以往的第三方库很难做到直接可用了。而 React 的可用第三方库还不够多,当然社区还是挺活跃的,相信这个问题会好转。
  • 成熟度
    随着项目变大和引入的 package 变多,你会发现越来越多的莫名奇妙的出错和 bug,这个时候你需要耐心,放狗去搜。最容易找到解决方案的地方还是 Github 上的 issue list。有很多人会碰到同样的问题,也很可能会有人在下面给出解决的方法或是方向。当然,你也可以 subscribe 这个 issue,等待官方哪天大发慈悲解决掉它。
  • 更新速度
    更新太快有好处也有坏处。当你发现新版本解决了一个问题,迫不及待的想要更新的时候,可能发现:你的 App 挂了;第三方组件挂了;你必须升级一堆 package,并解决可能与之相关的问题。当然啦,大部分情况下还是没有那么蛋疼的。

虽然接触的时间不长,也有碰到一些小坑,但是目前看来,对我这种不会 Native 开发的人来说,React Native 是个非常不错的选择。
也许有时间会写写碰到过的坑和一些学习的建议之类的 :-)

这个问题之前很好解决,使用浏览器 plugin 即可。
但是随着 Chrome 和 Firefox 都先后放弃了 NPAPI plugin,这一方法也行不通了,而且很多人是很讨厌 plugin 的。
但是在默认禁止了 NPAPI 的 Chrome 版本,QQ 依然可以实现快速登录(一键登录),是怎么做到的呢?

原理

其实不难猜。既然不存在 plugin,无法以此来实现浏览器内和本地客户端的直接通信,那么排除其他的黑科技,有一种很简单的方法可以实现这个效果。
那就是在客户端开一个 Server,在浏览器里面请求这个地址。
理论上这样是可以实现的,至于 QQ 是不是用的这种方法,稍微验证下好了。

验证

  • 找一个有 QQ 快速登陆的页面,比如 mail.qq.com

  • 登陆 QQ 客户端

  • 打开浏览器的 Developer Tools -> Network

  • 刷新页面,观察所有请求的 domain

  • 好明显,这就是我们要找的了

    • 完整请求 url 是这样的

      1
      https://localhost.ptlogin2.qq.com:4301/pt_get_uins?callback=ptui_getuins_CB&r=0.22949112393586502&pt_local_tk=-2027291081
    • 看看这个请求的 Response Content

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      var var_sso_uin_list = [
      {
      account: "xxxxxx",
      client_type: 65793,
      face_index: 0,
      gender: 1,
      nickname: "xxx",
      uin: "xxx",
      uin_flag: xxxxx,
      },
      ];
      ptui_getuins_CB(var_sso_uin_list);

      很明显是当前登录的用户信息

    • ping 一下这个请求的 domain
      不出所料结果是 127.0.0.1

      1
      2
      ping localhost.ptlogin2.qq.com
      :: Pinging localhost.ptlogin2.qq.com [127.0.0.1] with 32 bytes of data
  • 现在我们验证下是否是 QQ 开了这个 Server

    • 查看哪个程序占用了 4301 端口

      1
      2
      netstat -ano | findstr "4301"
      :: TCP 127.0.0.1:4301 0.0.0.0:0 LISTENING 14516
    • 得到 pid 我们就可以看否是 QQ 在监听这个端口了

      1
      2
      tasklist | findstr "14516"
      :: QQ.exe 14516 Console 2 87,892 K

安全

可能有人担心会不会有安全问题,会不会其他网站访问这个 url 就拿走用户信息?其实挺容易解决,存一个 token 到服务端,获取的时候校验下就好了。
但是归根到底取决于腾讯对这方面安全的重视程度和意愿了,至少之前是确实存在从网页上获取当前登录的 QQ 信息的方法,虽然问题不是出在快速登录这部分。

越来越多的网站开始更加关注安全问题了,比如,Facebook 会在你把密码从 abc123 改为 Abc123 的时候的时候提示你

Your new password is too similar to your current password. Please try another password.

很贴心是不是?但是,他们是怎么做到的?难道 Facebook 保存了用户的明文密码么?

编辑距离

计算两个字符串的相似性,或者说「编辑距离」很容易,我们有很多现成的算法和代码。
但是,显然 Facebook 不会傻到存储明文密码,存储的肯定是 hash("abc123")
而字符串中的差别和 hash 结果并不是一一对应的。两个相近的字符串,其 hash 结果可能差别很大。

simhash

可能你听说过 simhash 算法。Google 就是使用这种算法来做网页查重的。
传统的 hash 算法如 md5,一般尽可能要求结果分布均匀,因此,原始字符串的微小变动也会导致 hash 结果出现很大差异。
而 simhash 是一种局部敏感的 hash 算法,选定位数,提取特征,然后对每一段特征值计算 hash,然后将每一段值处理到 simhash 结果,得到最后的 simhash 值。比较海明距离就可以大概知道两个文档的相似度了。
具体的算法懂得不多就不瞎说了……大概可以推测,对于长文档这种方法是有效的,但是对于短文本,如 password 来说,效果可能不会太好。

Facebook 的做法

事实上这个问题好奇的人也很多,Stack Exchange 上有一个回答 http://security.stackexchange.com/questions/53481/does-facebook-store-plain-text-passwords,下面有一个自称接触过密码验证这部分代码的人肯定了这个猜测。
我觉得这种做法看起来还是挺合理的。

Facebook 用了一种看起来很「土」的方法,操作方法类似这样:

  1. 用户注册,密码 abc123,Facebook 保存了 `hash(“abc123”)
  2. 用户修改密码,提交新密码 Abc123
  3. Facebook 拿到新密码,根据这个密码,生成一堆类似于 ABC123abc123 这样相近的密码,使用同样的 hash 方法,去和 1 中的 hash 比对,一旦发现有相同的,那么可以判定新密码与旧密码是相似的。

很好的反向思维,不是去计算相似性,而是通过生成一堆相似密码来「暴力」尝试。

其他想法

事实上最初想到这个问题的时候,我考虑过这样的做法:

  1. 用户修改密码操作时,提示输入原密码
  2. server 临时记录用户的原密码
  3. 用户输入新密码,server 比对新旧密码
  4. 完成修改或过期后,销毁临时记录

不过即使是临时记录依然可能存在风险,如果需要较高的安全性的话这种方法是不可取的。

无意中翻到 Joe Armstrong 发在 erlang-questions 里的文章,Ways to get started 以及 history of erlang
如果你不知道 Joe Armstrong 是谁,我们给他的另一个称呼是 the father of Erlang :)
大概没有人比他更有资格写这种文章了吧。
在高手眼中大道至简,我们不一定学的来,但是听听还是很有启发的

随便瞎扯几句

忘掉工具

Forget about git/IDEs/rebar etc.
Forget about the tools

如果没有 IDE,没有自动打包工具,我们怎么编写和运行代码?
记住,shell 和文本编辑器对任何语言都是适用的。
当然我并不觉得这意味着需要放弃 IDE 之类的工具,而是在 get started 的时候,对程序怎么工作的有基本的了解是有好处的。
某种意义上来说,过于复杂的工具链意味着,一旦它没有按照你想象的运行,就需要花费更多的时间去解决它。

  • rebar!

    当然,不能忘了 rebar!
    事实上 rebar 已经快要成为 erlang project 的标配了。

Tools like rebar etc are under to automate something but if you don’t
know what it is that you are automating and if the tool doesn’t work
you will just end up being incredible confused.

「像 rebar 这样的工具会自动生成一些东西,但如果你不知道自动生成了什么,如果这些工具无法使用了,你将会变得困惑不已。」
说得好!
但是在更好的解决办法出现之前,也只能这么用了,寄希望于 OTP 组的改进吧,这一块也是我最不喜欢 erlang 的地方。

我一直觉得好的语言应该是某种程度上符合直觉的。这种直觉也许来自逻辑也许来自经验。
比如 erlang。
要举个反例也很容易,比如「世界上最好的语言」。
Joe Armstrong 是这么不经意的黑

Notice there is no quick fix here - if you want a quick fix go buy
“learn PHP in ten minutes”
and spend the next twenty years googling for
“how do I compute the length of a string”

hmm, well done!

the father of Erlang

Joe Armstrong 是个有趣的老头,也是真正的大师,推荐他的 blog,内容远远不止 erlang,可以看他讲讲信手拈来言简意赅的道理,喜欢八卦的话可以看他学 js 顺手黑黑其他语言 :)
以及他的 twitter @joeerl

最后是一段以前放在 erlang.org 上的话,如果学完 erlang 你还不懂这段话,可能你需要从头再来一遍 :)

The world is concurrent.
Things in the world don’t share data.
Things communicate with messages.
Things fail.
                      - Joe Armstrong

btw,Joe Armstrong 的邮箱地址是 erlang#gmail.com,是不是比 「I Wrote Python」 低调多了 :)

升级到 Windows 10 之后,很糟心的是通过 Microsoft Account 登录的时候,默认创建的账户文件夹居然不是 Microsoft Account 的 name,明明是 nick@outlook.com,结果创建出来的却是 nic …… 完全不知道这帮人怎么做到的 =-=
不管有没有强迫症,每次看到这个错的名字都实在不能忍,找到个修改的方法
参考了来自 superuser 的解决方法,原文针对的是 Windows 8,不过对 Windows 10 依然有效
原文地址 http://superuser.com/questions/495290/how-to-rename-user-folder-in-windows-8

步骤如下:

  • 用 nic 代表原来的账户名
  • 用 nick 代表新的账户名
  • 下面的操作中用你自己的需求替换 nic 和 nick

1. 创建一个本地管理员账户

Windows 10 中创建本地账户的选项藏得更深了,可以按如下方法操作

  • 打开 Setting
  • 找到 Family & other users
  • 点击 Add someone else to this PC
  • 弹出添加对话框,选择下面的 the person I want to add doesn't have an email address
  • 继续选择 Add a user without a Microsoft Account
  • 填写用户名密码,创建本地账户
  • 点击创建的用户,Change Account TypeAdministrator

2. 重命名账户名称

  • 退出当前账户,登录刚才创建的本地账户
  • 打开 Computer Management,快捷键 Win + X, G
  • 打开 System Tools
  • 找到 Local Users and Groups
  • 打开 Users,找到需要修改的用户 nic
  • 右键点击,Rename,修改为你想要的 nick

3. 重命名账户文件夹

  • 打开管理员权限的 Cmd,快捷键 Win + X, A
  • 输入 ren C:\Users\nic nick

4. 修改注册表关联

  • 打开注册表编辑器,快捷键 Win + X, R 打开运行,然后输入 regedit 回车
  • 找到 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\
  • 一个个点开所有以 S-1-5 类似的字符开头的键,查看 ProfileImagePath
  • ProfileImagePath 值为 C:\Users\nic 的就是我们需要修改的账户,点击它,修改为 C:\Users\nick

5. 重新登录

  • 使用原来的 Microsoft Acount 登录原先的账号
  • 确定一切正常,账户文件夹也已经修改成功
  • 可以删除创建的本地账户了

hope it’s helpful.