刘贵学博客

一场人生一场梦

下午眯了一会,不到一小时,但做了一个时间很长的梦,感觉就像一辈子,剧情很操蛋,醒来还能回忆起梗概,记录一下:

开始场景,整个团队都在疯狂加班,何止996,吃住都在一起的那种,无日无夜,不眠不休,跟一群疯子差不多,这种情况已经持续了很长时间了,资金链也快断了。

一个"疯子"突然惊呼: 我们成功了!

大家迅速围过来,原来这家伙研发出一项划时代的技术,:利用量子计算机,精准控制光场、磁场等,把一系列先进技术完美融合,可以在空气里实时渲染出高清画面,真正的裸眼VR(虚拟现实)。还攻破了一个场景化应用,把任意区域的天空当成超清屏幕,真实5D体验,让身临其境的每个人分不清虚拟还是现实。

什么苹果、三星,以后统统弱爆了,真正改变世界的交互方式才刚刚诞生,以后所有的实体屏幕都可以扔进垃圾桶了。

此刻起,虚拟世界开始比真实世界更清晰、更方便。游戏、购物、生活、工作将在新的世界里重新创建,人们更愿意活在我们构建的“完美世界”里,这确实比互联网更伟大的发明。

为了让世人知道这项技术,我们举办了一场盛大的技术发布会,《未来已来: 完美世界,眼见为虚》,发布会现场来了很多人,我们团队特地制作了流星撞地球的特效视频,演示效果很成功,几乎震撼了所有人,观众都以为是真的,有的甚至还吓哭了,吓尿了……

我们在后台恶作剧一般,控制着这一切,从上帝视角,俯瞰下去,有一种造物者的成就感,甚是洋洋得意……

演示快结束时,一颗异样的流星缓慢地飞向人群,爆炸,造成现场的更大的骚乱,太震撼了,太牛逼了!!音响、渲染效果逼真到无法想象,甚至都能闻到灰尘、炸药、焦肉的味道。 做视频的那帮小伙子确实过分,这也太坏了吧,快结束了还放个彩蛋吓唬人。也许前段时间都快憋疯了吧,好好发泄一下吧!

结果“坏小伙”过来汇报说:很奇怪,我们场景中没有这个效果啊,很可能发生了意外。

我们赶紧关闭了虚拟效果,看到的真实的一切:原来现场真的发生了爆炸,死伤极多,所有人争着往外涌去,互相踩踏,推搡,哭泣…… 如果有地狱,可能也不过如此吧。

这事玩大了,我们彻底慌了!初步分析,应该是演示系统早就被被某敌对组织(恐怖分子? 米国??)的黑客提前侵入了,并用定位,引来一颗真实的炸弹!

技术世界里本就道高一尺,魔高一丈。还是不该太狂妄啊,太狂的人往往都没有好结果,天欲其亡,必令其狂。

外面已经被武警包围了,怀疑我们勾结恐怖分子,放弃抵抗,配合调查。

怎么办?不知是谁喊了一句,快跑吧,出了这么大事,即使不是恐怖分子,也肯定得枪毙!

生死面前,必然狂奔啊,我冒充观众,混在人群里,还很心机的往脸上弄了点血迹,总算出了会议中心,大楼背面都是水(莫非就在金鸡湖?), 趁机绕过去,跳水游泳,偷偷潜逃了。

接下来事情开始发酵,我是第一责任人,必须归案,在公安部都挂号了。

公安、武警、国安各种围追堵截,我就藏啊,跑啊,无所不用其极…… 之前看过的一篇技能贴《犯事后如何潜逃》,这下总算有机会用上了,开始一本正经的筹划,各种反侦察,太操蛋……

再后来,我就醒了,因为逃跑动静太大,被子蹬地上冻醒了……

一个小时,我就体验了从人生巅峰,到人生谷底,太他妈刺激了,能醒来,多侥幸啊。

凡人别老想着牛逼,走向人生巅峰,高处往往不胜寒,顶不住就傻逼了,平平淡淡才是真。

再看东哥以前多牛逼,多狂啊,在米国不也被玩了嘛。他是不是也想过,那一切如果是一场梦,该多好啊!

我这个原本就是一场梦,比他幸运些!

以后得低调,低调~~

也谈996,效率也有黑洞

1. 什么是996

2019年3月27日,一个名为996ICU的项目在GitHub上传开。程序员们揭露"996ICU"互联网公司,抵制互联网公司的996工作制度,其中有两个杀手锏:

  • 黑白名单,很多著名的互联网公司都被晾出来了
  • 反 996 许可证,含此许可证的开源项目不给黑名单的公司用了,这个确实厉害了

百度百科给的解释是:

996工作制是指工作日早9点上班,晚上9点下班,中午休息1小时(或不到),总计10小时以上,并且一周工作6天的工作制度。

2. 典型观点

  • 马云,福报说:

如果你年轻的时候不996 你什么时候可以996?

  • 后来马云再谈996:

任何公司不应该,也不能强制员工996;阿里巴巴从来也都提倡,认真生活,快乐工作!但是年轻人自己要明白,幸福是奋斗出来的!不为996辩护,但向奋斗者致敬!

  • 刘强东:混日子的人不是兄弟,他将8116+8;

  • 人民日报发表评论员文章《强制加班不应成为企业文化》

了解更多,建议去微博抖音上翻一翻热点评论

3.我的观点

先说两个基本问题:

  • 公司强制实行996应不应该?

    肯定不应该,确切的讲是违法,即使变相营造潜规则也是耍流氓。
    
  • 员工应该自觉的996吗?

    不一定。
    

996, 将延长员工工作时间常态化,对公司和员工来讲都是弊大于利。

这几年我有机会服务或合作过很多家科技公司,据我观察,大多数员工的工作效率很低,真正聚焦在工作上的时间一半都不到,上淘宝,耍抖音,看微博,聊微信,朋友圈,甚至打游戏……这都是时间黑洞,注意力杀手。日常工作效率不高,就是把24小时全部拿来消耗,又有多大意义呢。

效率黑洞这样的事,在公司太普遍了。除此之外,技术团队常见的黑洞:

  1. 技术能力不够,要边学边做
  2. 任务需求不明确,辛苦完成了不是客户要的,返工重做
  3. 团队培训跟不上
  4. 团队没有节奏感,没有配合,形成不了战斗力
  5. 项目计划,客户承诺拍脑袋定,累死三军,为了老板吹下牛逼
  6. 瞎忙乱忙,救火拆墙
  7. 文山会海,流程繁冗

…….

这些问题背后基本都是管理问题。老板们经常用战术上的勤奋去掩盖战略上的懒惰。看见员工都在忙碌,心里才踏实。

为了让老板心里踏实,员工聪明的配合,假装很忙,形式化的加班,想走不敢走,慢慢演变成潜规则,谁也不去戳破那“老板的新装”。

老板们想过一个问题没有,真的需要加班吗?
加班不是银弹,找到公司问题根源,有针对性解决,是正途。
关注研发团队管理,避免效率黑洞,提高团队战斗力,才是硬实力。

4. 我的体会

我个人是自虐自嗨型,每天工作应该14-16小时。

之前在公司上班,即使当天完成了工作,我还是会留下加班,无偿的,多花些时间在优化、学习和思考上,往往能带来很多“惊喜”,老板也因此给了我很多加薪晋级机会。回头想想,一个刚毕业不久的穷小子,那时除了努力,我还有其他选择吗?在哪里投入就会在哪里产出,初入社会,我切实感觉到了公平。

后来创业,我经常是同时带2-3个项目与团队,研发管理,从成员招聘、培训到业务需求、架构设计,我都是亲手抓,甚至核心模块的代码也不比团队里任何一个成员写得少,还要跑商务,搞业务,做培训……,创业公司一个人当几个人用很正常。一年能真正休息3-5天就相当不错了,这两年更是连春节都没有休息。

有必要这么苦吗?身边的家人和朋友问过,其实我很享受这个过程。

因为我是个有点无聊的人,对各类游戏提不起兴趣,其他爱好也不多,对酒肉财色也不执着。编程或许是我的最大爱好了,可以控制计算机去解决实际世界里的问题让我很有成就感。

有机会把爱好和事业统一是幸运的;能全身心的投入,更是幸福的。

就像《心流》这本书讲的一样:

幸福是你全身心地投入一桩事务,达到忘我的程度,并由此获得内心的秩序和安宁时的状态。

5. 总结

说这么多,我想表达的意思是:

  • 拒绝无意义的加班,延长工时不如先解决效率黑洞
  • 投入决定产出,我们的时间和精力怎么分配,决定了我们有哪些收获。
  • 学习新知识,提高自身战斗力,才有更多选择,也有资格说不

那些持续学习,不断提高,全情投入,把工作干出“惊喜”的人,值得肯定和鼓励,但这是情分不是本分,属于加分项,可以看作优秀标准,可遇不可求。

公司应该明白,员工合格标准只是:按时完成工作。

hello, world 思绪

1. 简单点

大家在学习每个编程语言的时候第一个示例是什么?

hello, world

对吧?应该大多数人都遇到过。大家有没有想过,

为什么是hello, world

很简单,翻译一下:

你好,世界。
  • 你好容易理解,打个招呼嘛,初次见面,请多关注。

  • 世界咋回事? 写的是代码啊,跟世界有啥关系?对程序员来讲,代码就是他的全世界,这下有关系了吧。

除了字面意思,我们更关注的是引申意:

环境一切正常,可以开始了。

学习和使用一门技术,先写个 hello, world ,就简单测试一下,电脑给了个反馈,

通过了!

压根也没想跟谁问好,题主是不是想多了?

看吧,菜鸟永远是菜鸟,想成为大牛,一定要多看、多想、多总结。

2. 前世今生

要了解真正的一个东西,需要从起源、发展着手。
那咱们就扒一扒hello, world的前世今生。

相传古时候有个程序员,对书法十分感兴趣,退休后决定在这方面有所建树。于是花重金购买了上等的文房四宝。一日,饭后突生雅兴,一番磨墨拟纸,并点上了上好的檀香,颇有王羲之风范,又具颜真卿气势,定神片刻,泼墨挥毫,郑重地写下一行字:hello world

当然这是野史,不做数。

据考古资料记载,其实 hello, world 最早出现于B语言,

程序的运行结果,在电脑屏幕上打印出

hello, world 

然后,在C语言中发扬光大了。

因为C语言之父就这样写的,他又在《The C Programming Language》一书中使用它做为第一个演示程序,所以后来的程序员在学习编程或进行设备调试时延续了这一习惯。

郑重说一下C语言之父,丹尼斯·里奇(Dennis M Ritchie),也是UNIX之父。老爷子已经去世,一生立德、立功、立言,开多领域先河,第一个让代码插上翅膀的人,这种大宗师级的人物算得上是程序员界的祖师爷。

我们沿用祖师爷的做法,也是一种致敬和膜拜。

3. 标准与规范

其实,打印 hello, world 这个事,还有个标准规范呢:

  • 全小写字母
  • 有逗号
  • 逗号后空一格
  • 最后是换行,且无感叹号

    printf("hello, world\n");
    

看吧,程序员干啥事都很规范,真讲究,不将就!!!

值得一提的是,为什么标准中要特别指出 无感叹号呢?

这提现地是高手那种 尽在掌握的了然,深藏功名的淡然, 物我两忘的超然。

毕竟,老是一惊一乍,怎有脸担当高手二字

当然,问号也是不允许的。

4. 希望

hello, world 就像一个呱呱落地的婴儿,虽然他还不会说话,不会走路,甚至连基本思考的能力还不具备,然而,当那声啼哭嘹亮地响起,洋溢着的是喜悦,成就,憧憬……

此时此刻,此情此景,如诗云:

剑外忽传收蓟北,初闻涕泪满衣裳。
人逢喜事精神,月到中秋分外明。

声音是哭是笑不重要,想表达得意思也不重要了。

只有经历此刻才重要,才最有纪念意义,因为他的人生之路将全新开启,以后一切才有可能。

就像《流浪地球》里的希望一样,开始流浪……

在 Mac电脑上最优雅地用 Windows: WTG 安装过程

1. 背景

2013年底,我买了一款 15寸的 MacBookPro,已经用了5年多,现在性能配置仍算中上等,再熬2-3年完全没问题。

以前很多朋友想买笔记本让我推荐,对电脑重度用户(程序员、设计师、非游戏),我通常只给一个答案: 高配MBP,2W的价格乍一看很贵,但是可以用 6-8年,完全够本了。
有的东西看着贵,你经常使用且能大幅提高你的生产力,很合算;也有的东西看着便宜,买来就用不了几次,或没多久就坏了,跟花钱买垃圾差不多,那是真贵,比如:某想笔记本。

Mac OS系统确实方便,是Windows + Linux 二合一:界面比Windows好用,命令行跟Linux一样强大,软件生态质量更高,确实是程序员的必备神器,推荐。

我最近需要录制一门在线课程,学员应该Windows系统居多,所以课程最好用Windows环境来演示。

Mac上怎么使用Windows呢?方法不外乎以下几个:

  • 双系统,在SSD上增加安装Windows,浪费空间;
  • 虚拟机,性能太差;
  • Win To Go,将Windows安装到外置存储设备中,即插即用。

录课程,简单的视频编辑,性能要求比较高,所以虚拟机肯定不合适,笔记本的SSD只剩100多G,双系统也不合算。综合下来,最好的选择是 Win To Go。

开始折腾!

2. U盘选择

在京东购买了一款 三星的U盘 ,容量128GB,支持USB3.1读写速度很快, 价格200元左右。

除了这款U盘外,其他的品牌可以参考这个列表中 哪些U盘支持WTG

U盘拿到之后,先格式化为 FAT32。

3. 驱动文件下载【Mac系统中】

1) 插上U盘

2) 打开Boot Camp

Finder -> 应用程序 -> 实用工具 -> 启动转换助理


点击 继续

3) 选择任务

只选择中间的 从Apple下载最新的Windows支持软件

点击 继续

4) 选择存储位置

选择要存储的 U盘,点击 继续 后进入下载页面。

等几分钟后,下载完成,点击退出即可。

最好先将此 U盘中的BootCamp文件夹,备份到其他U盘中,这里面都是驱动,需要系统安装后再重新安装一遍,比如无线网卡驱动。

4. 安装 Windows 系统

下面所有的操作应该在 Windows 10 系统中进行。

1) 下载Win10 ISO

使用迅雷下载 Windows 10 Version 1703 (Updated March 2017) - DVD (Chinese-Simplified):

将下载完成后的 ISO 载入到虚机光驱(如:Daemon Tools) 中。

2) WTG辅助工具

  • 下载 WTG辅助工具 解压到某个目标,路径不应包含中文和空格。
  • 打开 WTGA/wintogo.exe, 界面如下:

  • 浏览选择 sources/install.wim文件;

  • 插入U盘,选择可移动磁盘

  • 选择安装分卷

    高级选项中保持默认配置即可,不需要任何改变。

  • 点击部署即可开始。

完成后,弹出U盘。

5 系统

将 U盘 插入Mac笔记本中,关机。

开机启动Mac,长按住 option 键,进入启动盘选择。

  • 左右建切换 选择中间的 Windows 启动选项,回车。
  • 进入 Windows 10 系统的初始化过程,基本上是 一路下一步即可,完成后自动重启。

    注:其中有 设置网络的步骤可以直接跳过。
    
  • 登入 Windows 10 系统,打开备份的 BootCamp文件夹,双击 setup.exe,即可完成驱动的安装。

  • 驱动更新完,再次重启后,就可以正式使用了。

参考资料:

Wifi 室内定位技术原理

1. 介绍

定位技术最常用的就是 GPS 或者北斗,通常在室外广泛使用。由于建筑物内GPS等信号的严重衰减和多径效应,所以无法有效得使用此类技术在室内定位。

室内定位也有很多方案,如:
* RFID标签锚记
* 蓝牙(iBeacons)标签锚记, 参考https://github.com/megagao/IndoorPos
* 基站定位
* UWB 定位
* 地磁
* 粒子滤波定位,常用与机器人
* Wifi 定位

由于WiFi 设备已经广泛部署在家庭、旅馆、咖啡馆、机场、商场等各类建筑物内,且个人终端(如手机)上WiFi也是标配,其大多数人都默认开启。

这使得WiFi成为室内定位领域中最容易实现的无线技术。

2. 信号强度与距离计算

Wi-Fi定位是不需要终端连接上 AP。
Wi-Fi设备芯片(终端与AP),都会发出一种Probe Request的帧,遍历空间所有信道,等待AP返回Probe Response帧。

根据 AP 到终端的信号强度 RSSI,可以计算出距离,公式如下:

d=10^((abs(RSSI)-A)/(10*n))
  • 其中d为距离,单位是m。
  • RSSI为rssi信号强度,为负数。
  • A为距离探测设备1m时的RSSI 值的绝对值,最佳范围在45-49之间。
  • n为环境衰减因子,需要测试矫正,最佳范围在3.25-4.5之间。

3 定位场景

3.1 一角定位

同一时刻,如果仅有一台 AP 可以侦测到 指定终端RSSI的情况时:

一角是无法定位的,终端的具体位置无法获取,但可以估算出位置范围:

以设备A为圆心,以R1为半径的圆内。

如图中绿色背景部分。

当然此情况下的定位准确度极差,误差通常在 30米(普通AP的信号的最大传输距离)。

3.2 二角定位

当仅有2台 AP 可以侦测到指定终端的时,每个AP都是一个圆圈,有三种情况:

  • 相交
  • 相切
  • 相离

如何判断是 相切,相交 还是 相离呢?

A, B两点的距离 d,如图:

计算公式如下:

比较 d 与 信号距离之和:

  • 相交 d < R1 + R2
  • 相切 d == R1 + R2
  • 相离 d > R1 + R2

3.2.1 相交【重点关注】

两圆相交的2个点都可能是终端的位置。
求 两圆相交的M与N的坐标需要用到 圆的标准方程 和参数圆的参数方程。

  • A 点参数方程

  • B点的标准方程

  • 将 A 点参数方程 带入 B点的标准方程,得到等式公式如下:

  • 展开上面的公式后,得出:

  • 为方便,令:

  • 其中 a, b, c 可解出来均为常数。

又知

可以解出 cosθ 和 sinθ 的值,带入参数方程后可获取到 M, N点的值。

两个点的位置计算成功后,可以通过轨迹或第三个AP来进一步提供向量因子,进一步挑出更准确的一个。

此方案也适用于简单的三角定位计算方式,只是可能误差较大。

3.2.2 相切

两圆相切于1个点,其实也是特殊的相交,这是两个点重合了而已。

简化的计算公式如下:

通过圆的标准方程,可以解出的 (X,Y)坐标就是终端的位置。

3.2.3 相离

理论上一个终端同时被2个AP发现,是不会出现相离的情况,但由于信号距离推算造成的误差,实际也会出现这种情况。

计算方法类似相切。但需要修正两个圆的半径,公式如下:

解出的 (X,Y)坐标就是终端的位置。

3.3 三角定位

3.3.1 方案1:相交点选更近

挑选 信号最强的 2个 AP 为 A和B,计算两个圆的交点M 与 N, 其中距C点的距离,与R3 更接近的值。

如上几图中红点的位置。

计算方法与【二角定位-相交】场景类似。

3.3.2 方案2:三点取中心定位

获取 A, B, C 三个点的中心位置。

三圆相离,取中心。

4. 问题与优化

4.1 需要解决的问题:

  1. 信号强度的波动因素很多,比如墙体遮挡,等,用强度推断出来的半径误差比较大;

  2. 三角定位的场景是平面2D定位,实际情况中,获取到的AP可能不在同一楼层,高度差异会使误差或干扰更大。

  3. AP的位置要提前采集,可以在 CAD上定位坐标,或通过专用的定位仪来采集。AP的坐标是整套解决方案的参考系,所以应尽量准确,而且还要考虑实的操作便捷度。

4.2 优化方案

  • 多传感器(加速度计,陀螺仪,方向传感器等)与WiFi相结合的方法,参考 《位置计算:无线网络定位与可定位性》
  • 结合Wifi 位置指纹来确认位置;
  • 定位完成后的要绘制轨迹,可考虑评估每个坐标的准确权重,按权重拟合出更平滑的轨迹,也可以通过拟合的轨迹将权重低的坐标点进行修正。

参考资料

Go get 无法获取 golang.org 里的资源

因为要翻墙,很麻烦。有比较简单的办法

在 GOPATH 下新建 golang.org/x 目录,cd $GOPATH/golang.org/x


git clone https://github.com/golang/crypto.git
git clone https://github.com/golang/exp.git
git clone https://github.com/golang/image.git
git clone https://github.com/golang/lint.git
git clone https://github.com/golang/net.git
git clone https://github.com/golang/sys.git
git clone https://github.com/golang/text.git
git clone https://github.com/golang/tools.git

Electron 0x01. 开发环境搭建

1. Electron 安装

1.1 node.js 安装

下载地址:

https://nodejs.org/en/download/current/

版本选择最新版即可。

npm install -g npm

1.2 打包工具

开发完成后,需要打包,安装electron-packager工具

npm install -g electron-package

使用方法参考 https://github.com/electron-userland/electron-packager

2. Hello World

git clone https://github.com/electron/electron-quick-start
electron .

可查看运行效果。

3. 代码结构分析

TBD.

Grom 注意事项

## 类型

  • 所有的 ID 都是 uint 类型

make, new 不同

环境

vs code 中 go test -v

可以输出 fmt的信息

单元测试

1、文件名必须以xx_test.go命名
2、函数必须是TestXXX开头
3、函数参数必须 t *testing.T

单元测试中:
* t.Error 输出log,但会继续
* t.Fail 不输出log,但会继续
* t.Fatal 标记错误,输出log,不会继续

Gogs自己搭建Git Server

1. 前言

Gogs 作为比gitlab更简介的git server库,用go语言实现 Git 仓库的管理,团队协作工具等。

新建 Git 用户

Gogs 默认以 git 用户运行。

$ sudo adduser git

新建好 git 用户。

$ su git 
$ cd ~
$ mkdir .ssh

以 git 用户登录,在git 用户的主目录中新建 .ssh 文件夹。

配置 Mysql 数据库

使用的 mysql 数据库,先新建一个 用户 gogs

$ mysql -u root -p
> # (输入密码)
> create user 'gogs'@'localhost' identified by '密码';
> grant all privileges on gogs.* to 'gogs'@'localhost';
> flush privileges;
> exit;

TBD

Gitlab 3. 手动安装

1. 简介

上次介绍了使用Docker自动化安装Gitlab的过程,几个命令非常简单。但是安装一些插件或定制的时候就不太容易了,所以本文将介绍Gitlab的手动安装,操作系统使用 ubuntu 16.04, 先安装依赖的软件:

更新apt:

sudo apt-get update

安装 openssh等 工具:

sudo apt-get install -y curl openssh-server ca-certificates

安装postfix工具,用于发送通知邮件

sudo apt-get install -y postfix

注意:弹出界面选择:Internet Site

2. 下载与安装

gitlab有 ce 与 ee两个版本:

  • CE 是社区版,可免费使用
  • EE 是企业版,要收费的

先更新 gitlab-ce 的apt源 :

curl https://packages.gitlab.com/install/repositories/gitlab/gitlab-ce/script.deb.sh|bash

安装gitlab

EXTERNAL_URL="http://192.168.200.200" apt-get install gitlab-ce

耐心等待,要下载 400M+的安装包

3. 配置

gitlab-ctl reconfigure
gitlab-ctl retart
gitlab-ctl status
gitlab-ctl tail

Gitlab 2. 使用Docker一键安装

1. 安装 Gitlab

gitlab的安装过程还是比较麻烦的,我们先使用docker可以简化安装过程。

2. docker image 下载

下载中文版镜像,命令如下:

docker pull twang2218/gitlab-ce-zh:10.6.2

3. 数据文件夹

初始化数据文件夹,

mkdir -p /data/docker/gitlab/config
mkdir -p /data/docker/gitlab/logs
mkdir -p /data/docker/gitlab/data

4. 运行docker 镜像

sudo docker run --detach \
    --hostname 192.22.200.200 \
    --publish 443:443 --publish 80:80 --publish 22:22 \
    --name gitlab \
    --restart unless-stopped \
    --volume /data/docker/gitlab/config:/etc/gitlab \
    --volume /data/docker/gitlab/logs:/var/log/gitlab \
    --volume /data/docker/gitlab/data:/var/opt/gitlab \
    twang2218/gitlab-ce-zh:10.6.2

如果端 443、80、22 端口已经被占用,可选择其他端口,如下:

--publish 8443:443 --publish 8080:80 --publish 8022:22 

Gitlab 1. 简介

1. 简介

研发流程中需要的管理工具如下:

  • 源码管理(git 或 svn)
  • 任务分配
  • 文档管理
  • 里程碑管理
  • 问题追踪
  • ……

    常用的工具如老牌的 trac,redmine等以任务分配、文档管理为主;能结合源码管理的工具如 github,coding.net等,本文所介绍的gitlab 可以在组织内部大家自己的 开源版github。

2. 运行 Gitlab

2.1 首次登陆

首次登陆gitlab 设置root用户的 密码,其他用户可通过注册后登陆。

root用户可以将其他普通用户设为管理员。

2.2 基本功能介绍

  • 代码管理:

  • 代码提交记录

  • 里程碑管理

  • 任务看板

  • 任务详情

  • Wiki文档管理,支持markdown

  • DevOps支持

ubuntu 使用 阿里云的 source.list

直接购买阿里云ECS,ubuntu系统中默认使用的是阿里云 source.list,详见:

/etc/apt/sources.list.d/sources.list.d

自建服务器如果选用 ubuntu,默认是官方的源,速度慢的很,直接使用阿里云ecs中的源,也不行,因为域名 http://mirrors.cloud.aliyuncs.com 无法在外网访问, 出掉 cloud,批量替换成 http://mirrors.aliyuncs.com 就可以了。

提一个 ubuntu 16.04 64位的 source.list

deb http://mirrors.aliyun.com/ubuntu/ xenial main
deb-src http://mirrors.aliyun.com/ubuntu/ xenial main
deb http://mirrors.aliyun.com/ubuntu/ xenial-updates main
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-updates main
deb http://mirrors.aliyun.com/ubuntu/ xenial universe
deb-src http://mirrors.aliyun.com/ubuntu/ xenial universe
deb http://mirrors.aliyun.com/ubuntu/ xenial-updates universe
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-updates universe
deb http://mirrors.aliyun.com/ubuntu/ xenial-security main
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-security main
deb http://mirrors.aliyun.com/ubuntu/ xenial-security universe
deb-src http://mirrors.aliyun.com/ubuntu/ xenial-security universe

将以上内容覆盖到 /etc/apt/sources.list 文件后即可,更新:

apt clean
apt update

树莓派开发系列1: 入手尝鲜

1. 板子

从淘宝上购买了 2018新款树莓派Raspberry Pi 3B+,先上照片

配置详情:
* CPU: Broadcom BCM 2837B0, Cotex-A53 64-bit Soc 1.4GHz
* RAM: 1GB LPDDR2 SDRAM
* WIFI: 2.4G + 5G IEEE 802.11b/g/n/ac wireless LAN
* Bluetooth: 4.2 BLE
* Ethernet: 300Mbps
* USB: USB2.0 x 4
* GPIO: Extended 40pin Header
* Video: HDMI x 1, MIPI DSI display Port, MIPI CSI camera port, 4 pole stereo output and composite video port
* Multimedia: H.264, MPEG-4 decode(1080p30); H.264 encode(1080p30); OpenGL ES 1.1, 2.0 graphics
* SD Card: Micro SD card. include OS
* Power: 5V/2.5 A DC micro USB; 5V DC GPIO PoE

2. 组装

买的套餐里赠品如下:
* 三个散热片
* 一个风扇
* 塑料外壳
* 16G SanDisk 存储卡
* 电源

2.1 组装散热片

三个散热片

红色是CPU散热片, 蓝色是网卡散热片,剩余的那个的内存散热片在卡片背部。

2.2 组装外壳

外壳有六片,我嫌来回拆装太碍事,只安装了底部外壳防止短路。

风扇在固定顶部外壳上,有散热片,风扇意义不太大。

3. 制作镜像

环境准备:

  • 一张空白的 SD 卡, 我用的是SanDisk 16G
  • 读卡器

我试用的PC 是 Mac OS, Linux 与此类似, Windows请找类似工具。

3.1 下载镜像

先到树莓派的官方网站去下载一个镜像,推荐下载Raspbian,最新版 2018-4-18,格式为 zip,解压得到 img 文件。

ls -hl
-rw-r--r--@  1 liuguixue  staff   4.6G  4 18 02:24 2018-04-18-raspbian-stretch.img

用df命令查看当前存储空间:

Filesystem     Size   Used  Avail Capacity iused               ifree %iused  Mounted on
/dev/disk2s1   15Gi  2.4Mi   15Gi     1%       0                   0  100%   /Volumes/sdcard

3.2 制作镜像卡

使用 diskutil 确认设备信息:

diskutil list
/dev/disk2 (external, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     FDisk_partition_scheme                        *15.9 GB    disk2
   1:             Windows_FAT_32 NO NAME                 15.9 GB    disk2s1

使用 dd 命令来讲img文件 写入(类似烧录)到 U 盘中:

sudo dd bs=4m if=2018-04-18-raspbian-stretch.img of=/dev/disk2

千万注意,of 后面的disk 数据不要搞错!!

注意: 如果遇到 dd: /dev/disk2: Resource busy 的错误,

则需要将 disk2s1 的分区先从系统中卸载掉:

sudo diskutil umount /dev/disk2s1

先后在使用 dd 写入即可。

整个SD镜像卡的写入过程,大概需要 5分钟(img有4.6G)。 提示以下信息说明写入完成。

4. 启动系统

把SD 卡装入树莓派板子,接显示器后,插上电源,启动效果如下:

我没有买屏幕用了一个 24寸的显示器,效果还是非常酷的:

孰乐:新玩法,一个自动合约项目的策划

1. 缘起

前几年,我就想背单词,坚持了多次,下载各种app,都因为各种原因中断了,慢慢再也没有继续了。

2017年底,我想了一个方法,召集几个小伙伴,一起背单词,用的是百词斩完成任务后可以发群里打卡,承诺每天必须完成任务,如失约自愿发 5元钱红包惩罚。

现在过去2个多月了,当然期间有几个人被惩罚发红包了,我本人已经背完4000多个单词,大家整体的完成率在 90%以上,效果算得上非常好,没有想到。

两周前,我们又约定,每周最少跑3次5公里,目前只有一个人发了一次红包,我自己的改变是,作息规律了,每天基本都是7点前准时起床,慢跑1小时 6-10公里,准时吃早饭,两周减掉5斤多,精力也远比之前旺盛了,这个改变让我很开心,比熬夜划算太多了。

总结:现在社会机会、知识、金钱就摆在那里,每个人都懂得很多道理,但是一到执行的时候,很难坚持下来,这是每个人的痛点,也是焦虑原因。

有点追求的人都期望改变自己,做更好的自己。但是靠自己去抵住各种诱惑、战胜惰性、克服拖延症,看起来容易,真正做到确实挺难的。

其实也简单,换一个玩法:把帽子丢过栅栏, 往往能得到意外惊喜。

我认为这种玩法很有价值,可以考虑做个工具,比如:微信小程序 或 类似轻松筹。

产品名称:孰乐, 先来策划策划…

2. 业务场景

2.1 独乐乐

  1. 我要完成一个目标,比如:每天 7点前起床
  2. 创建一个微信群,请大家帮忙监督
  3. 每天7点前打开,如违约,主动在群里发红包

一个人玩,一圈人嗨。

2.2 众乐乐

  1. 找几个志同道合的朋友,大家约定一个目标
  2. 准时打卡,如违约,主动发红包

一起玩,一起嗨。

3. 程序功能描述

  1. 注册:发起者关注孰乐
  2. 创建活动:发起者创建一个活动,建立奖惩机制,并缴纳承诺金。
  3. 分享: 将活动发到群里或朋友圈里,请朋友们参加
  4. 打卡:对接百词斩、运动app之类的接口完成各种活动打卡,按约定规则自动发红包。
  5. 活动的整体报告,支持分享。

4. 盈利点分析

  1. 承诺金资金池
  2. 在活动中,引入公益或广告赞助
  3. 推广费:引入其他App(定向合作),比如百词斩,阅读类,运动类等
  4. 大量粉丝,由于本产品自带游戏、社交属性,类似之前的答题PK,不需要推广。
  5. ….

5. 声明

这个想法源自于2月底,时隔3个月,最近也总算看到了类似的产品,请参考:《坚持早起21天,每月躺赚5000元!》

最近同时带三个团队,实在没时间去尝试。没有实践的想法是一文不值的,如果有人想试试这个想法,不必顾虑,没有任何版权随便用,我只会觉得非常荣幸并表示感谢。

如想深入讨论,欢迎发邮件给我:guixue@outlook.com

Node爬虫:使用爬虫下载小说txt

1. 缘起

浏览微博时被一个小说片段勾起了兴趣,如果要继续看,必须加某个微信公众号,回复关键字等,哎,都是套路啊,这种营销方法侮辱智商,阅读体验也非常差。

在百度中搜索小说的开头一句,可以找到一些资源,但是没有txt或epub格式,很多在线小说阅读有广告,没有适配手机端,反正阅读体验也不好。

怎样提高读小说的体验呢?本文将尝试解决这个问题。

其实手机或平板上读书的很多app体验是不错的,比如多看。假如我们可以将小说转成txt传到 多看里,体验问题就可以解决了。

如何获得txt版本的小说呢?江湖盛传三种绝技:
1 最直接的方法:下载。通过云盘、文库、小说站点等直接下载txt的小说格式,这个主要看运气。
2 最笨的方法:复制粘贴。从小说网,打开每个链接复制粘贴到txt文本中,保存可得,现在网络小说章节动不动就几百上千章,能坚持下来用这个方法操作的,毅力肯定不错,当然也傻的可以。
3 真正必杀绝技:使用脚本自动爬取,这个才靠技术。

2. 解决方案

2.1 业务分析

网络小说由多个章节组成,通常每个章节一个页面,如下图:

所以我们的爬虫逻辑分四步:

  1. 获取目录网页
  2. 解析目录网页,获取章节列表
  3. 下载所有章节页面
  4. 解析章节页面,抽取信息合成 txt

2.2 技术分析

实现业务涉及的技术点如下:

  • 获取某个网页内容,这个可以使用 node-fetch 库

    const response = await fetch(url)
    const content = await response.text()
    
  • 编码转换,js中只处理utf8的中文,小说网通常为gbk

    const buff = await response.buffer()
    // 要将GBK 编码转换为 UTF-8
    const iconv = new Iconv('GBK', 'UTF-8//TRANSLIT//IGNORE');
    let str = iconv.convert(buff).toString()
    
    // 将网页中的编码gbk 改为UTF-8
    str = str.replace('charset=gbk','charset=UTF-8')
    
  • 使用async库 并发爬取,如:每次获取10个

    const ASYNC = require("async");
    ASYNC.mapLimit(LIST,10, async (item) => {
        fetchPage(item)
    }, (err, results) => {
        if (err) {
            console.log('error', err)
        }
    });
    
  • 抽取网页信息 cheerio + 正则表达式

    const cheerio = require('cheerio')
    
    const $ = cheerio.load(html);
    let content = $('#content').text()
    
    // 去除行首多余空格、空行
    content = content.replace(/\n\s+/g, "\n");
    

3. 代码实现

请参考: https://coding.net/u/guixue/p/novel2txt/git/tree/level1?public=true

4. 使用方法与效果预览

4.1 下载目录页面

npm run fetch.index

将目录页面保存到 data/index.html

4.2 解析章节列表

npm run parse.index

章节目录列表为json格式,一个大数组,包含标题和链接。

4.3 下载所有章节页面

npm run fetch.list  

将所有的章节html下载到本地 data目录下:

4.4 解析章节页面合成txt文件

npm run parse.chapter   

解析每个章节的html文件后,产生一个2.8m的 data/all.txt,妥了。

将此txt文件通过QQ 或微信发送到移动设备,右上角选择【用其他应用打开】,选择多看或其他阅读器即可。

  • 效果预览,可以自动生成目录

img_0002

  • 效果预览,阅读效果:

img_0003

5. 免责声明

本文以技术学习为目的,如侵犯了您的权益,请来信告知: guixue@outlook.com

使用Docker 部署 MongoDB

1. 下载镜像

docker pull mongo

下载官方默认镜像,mongodb v3.6.2

查看本机镜像:

docker images

2. 运行容器

2.1 默认无密码(不安全)

docker run -p 27017:27017 -v $PWD/db:/data/db -d mongo
  • 注:Mac版的 Docker 需要将 $PWD/db地址设置到 Preferences…

window下没有尝试

2.2 开启认证 (安全,推荐)

docker run -p 27017:27017 \
-e MONGO_INITDB_ROOT_USERNAME=admin \
-e MONGO_INITDB_ROOT_PASSWORD=pass123 \
-v $PWD/db:/data/db -d mongo

开启用户名和密码, 使用mongo客户端连接命令如下:

mongo --host=127.0.0.1 --port=27017 \
 --username=admin --password=pass123 \
 --authenticationDatabase=admin

3. 查看 运行情况

docker ps

注:如需要查看容器IP地址,可使用如下命令

docker inspect --format='{{.NetworkSettings.IPAddress}}' e5e1a6efa7ae
  • 停止数据库:

    docker stop e5e1a6efa7ae

SSH 秘钥部署常见场景

本文涵盖两个应用场景:

  1. 通过公钥访问git代码;
  2. Linux服务器自动登录;

首先安装 ssh的客户端与服务器端:

 apt-get install openssh-server openssh-client

1) 场景1:Git账户关联

例如 github.com 或者 coding.net 上传本地 的 pub key。

ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
cd ~/.ssh
chmod  -R 700 ./
chmod 600 *

把产生的 pub key 配置到 SSH 秘钥设置界面即可。

2) 场景2: Linux服务器无密码自动登陆

客户端可以无密码登陆服务器。

  • (1). 需要在 客户端产生一对秘钥(参考上节)
ssh-keygen -t rsa -b 4096
cd ~/.ssh
chmod -R 700 ./
chmod 600 *
  • (2). 把公钥放到服务器配置SSHD;

~/.ssh/id_dsa.pub

  • (3). 配置sshd服务并启动;

配置sshd允许RSA登陆:

vi  /etc/ssh/sshd_config

将客户端上传的 pub key 放入可认证列表:

cat ~/.ssh/id_dsa.pub >> ~/.ssh/authorized_keys 

最后,启动服务器:

/etc/init.d/ssh [start/stop/restart/status]
或者
service sshd start
  • (4). 客户端直接透过 ssh config 里的配置,连接到服务器;
Host myserver
    HostName 139.129.208.188
    User root
    PreferredAuthentications publickey
    identityfile ~/.ssh/private-key
    Port 22

执行 ssh myserver 即可自动连接到远端服务器。

阿里云ECS 与 MongoDB 服务器的部署

1. 购买服务

购买ECS 与 MongoDB服务,需要在同一区域购买,比如都是华北1。

先购买ECS,网络类型选择 专有网络 VPC.

购买 mongoDB时,选择专有网络类型,选择对应的VPC 名称。

2. ESC 基本配置

参考 SSH 秘钥部署常见场景 场景二。

3. 互通链接配置

3.1 MongoDB 网络代理设置

1) 阿里云的MongoDB默认是不能直接通过外网访问的,如果需要访问,可以通过 ECS 作代理。


自动加载内网 IP设置即可。
2). 测试,在ECS机器中,通过mongo shell 手动连接到mongodb

mongo --host dds-XXXX.mongodb.rds.aliyuncs.com:3717 --authenticationDatabase admin -u root -p

注意,如果测试失败,请检查mongo的版本,必须要升级ECS中提到的 mongo版本 v3.0以上。
ubuntu16.04 下安装 mongo 3.0 以上的方法:

echo "deb http://repo.mongodb.org/apt/ubuntu xenial/mongodb-org/3.2 multiverse" | tee /etc/apt/sources.list.d/mongodb-org-3.2.list
apt-get update
apt-get install -y mongodb-org  --allow-unauthenticated

3). 通过ECS的内网连接MongoDB

其中 /etc/rinetd.conf 的内容如下:

0.0.0.0 27098   dds-XXXX.mongodb.rds.aliyuncs.com  3717
logfile /var/log/rinetd.log

如遇到以下错误

请将 /etc/rinetd.conf 配置文件中的空格替换为 Tab

启动 rinetd 服务:

pkill rinetd  //如果已经开启则先停止
rinetd

3.2 通过ECS访问数据库

在本地 使用ECS 的公网IP连接到数据库:

 mongo --host 1.1.1.1:3717 -u root -p 密码 --authenticationDatabase admin

注意,可能会遇到如下问题:

原因是 申请ECS时,没有开通相关端口, 配置方法如下:

Weex 入门 3 主页面修改

大多数主页面都是 基于 tabbar来制作的。

https://alibaba.github.io/weex-ui/#/cn/packages/wxc-tab-bar/

接下来所有的基础UI 除了weex本身的,还会引入 weex-ui

1. 安装 weex-ui

npm install --save-dev weex-ui

参考 https://alibaba.github.io/weex-ui/#/cn/packages/wxc-tab-bar/

2. index.vue

<template>
<div>
  <wxc-minibar title="title"
    background-color="#009ff0"
                  :use-default-return   = "false"
                   text-color="#FFFFFF"
                   right-text="more">
  </wxc-minibar>

  <wxc-tab-bar :tab-titles="tabTitles"
               :tab-styles="tabStyles"
               title-type="icon"
               :tab-page-height="tabPageHeight"
               @wxcTabBarCurrentTabSelected="wxcTabBarCurrentTabSelected">
     <!-- 第一个页面内容-->
    <div class="item-container" :style="contentStyle"><text>首页</text></div>
    <!-- 第二个页面内容-->
    <div class="item-container" :style="contentStyle"><text>特别推荐</text></div>
    <!-- 第三个页面内容-->
    <div class="item-container" :style="contentStyle"><text>消息中心</text></div>
    <!-- 第四个页面内容-->
    <div class="item-container" :style="contentStyle"><text>我的主页</text></div>
  </wxc-tab-bar>
</div>
  
</template>

<style scoped>
  .item-container {
    width: 750px;
    background-color: #f2f3f4;
    align-items: center;
    justify-content: center;
  }
</style>
<script>
  import { WxcTabBar, WxcMinibar, Utils } from 'weex-ui';
  import Config from './config'

  export default {
    components: { WxcTabBar, WxcMinibar},
    data: () => ({
      tabTitles: Config.tabTitles,
      tabStyles: Config.tabStyles
    }),
    created () {
      this.tabPageHeight = Utils.env.getPageHeight();
      const { tabPageHeight, tabStyles } = this;
      this.contentStyle = { height: (tabPageHeight - tabStyles.height) + 'px' };
    },
    methods: {
      wxcTabBarCurrentTabSelected (e) {
        const index = e.page;
      }
    },
    mounted(){
      console.log("tabPageHeight=", this.tabPageHeight);
    }
  };
</script>

3. config.js


export default {
    
      tabTitles: [
        {
          title: '首页',
          icon: 'https://gw.alicdn.com/tfs/TB1MWXdSpXXXXcmXXXXXXXXXXXX-72-72.png',
          activeIcon: 'https://gw.alicdn.com/tfs/TB1kCk2SXXXXXXFXFXXXXXXXXXX-72-72.png',
        },
        {
          title: '特别推荐',
          icon: 'https://gw.alicdn.com/tfs/TB1ARoKSXXXXXc9XVXXXXXXXXXX-72-72.png',
          activeIcon: 'https://gw.alicdn.com/tfs/TB19Z72SXXXXXamXFXXXXXXXXXX-72-72.png'
        },
        {
          title: '消息中心',
          icon: 'https://gw.alicdn.com/tfs/TB1VKMISXXXXXbyaXXXXXXXXXXX-72-72.png',
          activeIcon: 'https://gw.alicdn.com/tfs/TB1aTgZSXXXXXazXFXXXXXXXXXX-72-72.png',
          badge: 5
        },
        {
          title: '我的主页',
          icon: 'https://gw.alicdn.com/tfs/TB1Do3tSXXXXXXMaFXXXXXXXXXX-72-72.png',
          activeIcon: 'https://gw.alicdn.com/tfs/TB1LiNhSpXXXXaWXXXXXXXXXXXX-72-72.png',
          dot: true
        }
      ],
      tabStyles: {
        bgColor: '#FFFFFF',
        titleColor: '#666666',
        activeTitleColor: '#3D3D3D',
        activeBgColor: '#FFFFFF',
        isActiveTitleBold: true,
        iconWidth: 70,
        iconHeight: 70,
        width: 160,
        height: 120,
        fontSize: 24,
        textPaddingLeft: 10,
        textPaddingRight: 10
      }
    }

4. 执行后的预览效果

100X100

Weex 入门 2 修改Splash页面

Splash页面指的是App的首次启动页面,而常见的App界面应该只需要一个 图片即可。原有的Weex启动页,是个动画效果,动画执行完成后(固定的时间)自动加载主页面。所以,这并不是真正意义上的 等待页面。

所以,我们的需求是: 在主页面加载完成后,等待页面消失。

App的启动顺序是:

WXApplication -> SplashActivity -> WXPageActivity

  • WXApplication 初始化配置、加载插件;
  • SplashActivity App等待页
  • WXPageActivity App主页面

1. SplashActivity

功能需求:
1. 如果加载layout/activity_splash.xml, 耗时太多了。 SplashActivity的资源文件应该越简单约好。
2. 初始化加载主页功能,完成后,直接跳转到主页。

    package com.alibaba.weex;
    
    import android.content.Intent;
    import android.net.Uri;
    import android.os.Bundle;
    import android.support.v7.app.AppCompatActivity;
    import android.text.TextUtils;
    import com.alibaba.weex.commons.util.AppConfig;
    
    public class SplashActivity extends AppCompatActivity {
    
    
      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    
        gotoMainPage();
        finish();
    
      }
    
    
      private void gotoMainPage(){
        String url = AppConfig.getLaunchUrl();
        if (!TextUtils.isEmpty(url)) {
          Intent intent = new Intent(Intent.ACTION_VIEW);
          String scheme = Uri.parse(url).getScheme();
          StringBuilder builder = new StringBuilder();
          if (TextUtils.equals("file", scheme)) {
            intent.putExtra("isLocal", true);
          } else if (!TextUtils.equals("http", scheme) && !TextUtils.equals("https", scheme)) {
            builder.append("http:");
          }
          builder.append(url);
    
          Uri uri = Uri.parse(builder.toString());
          intent.setData(uri);
          intent.addCategory("com.taobao.android.intent.category.WEEX");
          intent.setPackage(getPackageName());
          startActivity(intent);
          finish();
        }
      }
    }

2. AndroidManifest.xml

2.1 设置 SplashActivity 的 theme 为SplashTheme

 <activity
                android:name=".SplashActivity"
                android:configChanges="orientation|keyboardHidden|screenSize"
                android:label="@string/app_name"
                android:screenOrientation="portrait"
                android:theme="@style/SplashTheme">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

2.2 在 res/values/theme.xml 目录下:

 <style name="SplashTheme" parent="FullscreenTheme">
    <item name="android:windowBackground">@drawable/splash_page</item>
    <item name="android:windowFullscreen">true</item>
  </style>

2.3 在 res/drawable/splash_page.xml 目录下:

 <?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@color/colorWhite" />

    <item>
        <bitmap
            android:gravity="fill"
            android:src="@drawable/splash_pic" />
    </item>

</layer-list>

把等待页面移到 res/drawable/splash_pic.png 即可。

Weex 入门 1 环境安装

1. 安装

weexpack 是面向整个weex项目的管理工具,容易跟 weex init混淆,weex init 适用更简单的情景。

npm install -g weexpack

创建 Weex Hello例子:

weexpack create helloWeex
cd helloWeex
npm install
weexpack platform add android

2. 运行程序

2.1 在 web 端运行

npm run build
npm run serve

2.2 在 android 端运行

使用USB 链接Android手机后,执行命令:

weex run android

3. 用 Android Studio 3.0 打开 weex项目

3.1 坑1: 使用 AS 3.0 提示 从 Gradle 3.0 升级 Gradle 4.1 后,会有一个错误:

Error:(24, 0) Cannot set the value of read-only property ‘outputFile’ for ApkVariantOutputImpl_Decorated{apkData=Main{type=MAIN, fullName=debug, filters=[]}} of type com.android.build.gradle.internal.api.ApkVariantOutputImpl.
Open File

解决方案:
把 build.gradle 的

applicationVariants.all { variant ->
    variant.outputs.each { output ->
        def outputFile = output.outputFile
        if (outputFile != null && outputFile.name.equals('app-debug.apk')) {
            def fileName = outputFile.name.replace("app-debug.apk", "playground.apk")
            output.outputFile = new File(outputFile.parent, fileName)
        }
    }
}

修改为:

static def releaseTime() {
return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))
}

android.applicationVariants.all { variant ->
    variant.outputs.all { output ->
        def outputFile = output.outputFile
        if (outputFile != null && outputFile.name.endsWith('.apk')) {
            def fileName = outputFile.name.replace("app", 
                    "${defaultConfig.applicationId}_${defaultConfig.versionName}_${releaseTime()}")
            outputFileName = fileName
        }
    }
}

}

3.3 坑3:

执行 weex run android 时,提示找不到 gradle 3,

3.2 坑2:没有找到 android-23

Error:Missing Android platform(s) detected: 'android-23'
Install missing platform(s) and sync project

因为在AS3.0 下默认使用的 是 SDK 26

compileSdkVersion 26
buildToolsVersion '26.0.2'

compile 'com.android.support:support-v4:26.0.2'
compile 'com.android.support:appcompat-v7:26.0.2'

3.3 替换App ID, App Logo 与 App Name

  1. App Id 的修改在 platforms/android/app/build.gradle

  1. 将不同清晰度的Logo图片,命名为ic_launcher.png 替换到 platforms/android/app/src/main/res,下对应的目录里:

    mipmap-mdpi     160dpi
    mipmap-hdpi     240dpi
    mipmap-xhdpi    320dpi
    mipmap-xxhdpi   480dpi
    mipmap-xxhdpi   720dpi
    
  2. 修改App名称

修改 platforms/android/app/src/main/res/values/strings.xml (也有可能是 values-zh-rCN/strings.xml) app_name 标记。

MongoDB 连接数耗尽的解决方法

问题

今天突然发现数据库的连接特别迟缓,本地Robo 3T 客户端都连接不上,登陆阿里云后台发现连接数已经到达 500 达到最大值。

原因分析

由于阿里云的MongoDB服务器不能直接暴露给外网访问,开发机器如要访问MongoDB,需要通过一台ECS 路由中转访问。中转服务器使用 rinetd 工具做端口映射,连接没有释放的原因应该是 中转服务器与Mongodb服务器的连接被 rinetd 缓存住了。

解决方案

重启ECS 上的 rinetd 服务即可,执行命令:

rinetd restart

结果提示错误:

rinetd: couldn't bind to address 0.0.0.0 port 27099

看下 27099 这个端口被谁占用呢?

netstat -ltnp | grep ':27099'
tcp        0      0 0.0.0.0:27099           0.0.0.0:*               LISTEN      8633/rinetd

把 8633 这个进程 kill 掉就可以

pkill rinetd
rinetd restart

一份号称最健康的作息时间表

上午

  • 7:00 起床, 喝杯温水
  • 7:20 ~ 8:00 吃早饭
  • 8:30 ~ 9:00 避免剧烈运动,可以走路上班
  • 9:00 ~ 10:00 做困难性工作,此时大脑最清晰
  • 10:30 让眼睛休息一下
  • 11:00 吃点水果
  • 12:00 ~ 12:30 午餐

下午

  • 13:00 ~ 14:00 午睡
  • 14:00 ~ 16:00 做创意性工作,思维最活跃
  • 16:00 喝杯酸奶
  • 16:10 ~ 19:00 做细致性工作,思维最活跃
  • 19:10 总结:
    • 今天收获了哪些好创意
    • 积累了哪些经验
    • 学习了什么新工具
    • 验证了什么想法

晚上

  • 19:10 最佳运动时间
  • 20:00 看书
  • 22:00 洗个热水澡,舒缓身体
  • 22:30 上床睡觉
  • 23:00 身体开始恢复

参考资料

【效率】Mac 下常用快捷键

Mac下丢掉鼠标,提高效率

  • 编辑器 VSCODE

1. 切换环境

  • 多个桌面切换: 三手指左右滑动,触摸板;
  • 切换工作窗口: CMD + TAB
  • VSC中切换Tab:CMD+W

团队Git 协作环境的使用规范

本文的目的是让新成员快速熟悉团队协作工具和流程。

源码管理拓扑如下:

本文将涵盖如下内容:

  1. 客户端 Git for Windows 安装
  2. Coding.net的注册;
  3. SSH秘钥制作与上传;
  4. 下载(项目)源码;
  5. VSC的安装与Git插件管理源码;
    • 更新代码;
    • 提交代码;

本文将涉及的工具与环境如下:

  • Git 客户端使用: VSC 插件
  • 服务器端使用: Coding.net
  • 操作系统假定 Window 7

1. 客户端 Git for Windows

客户端 Git for Windows 下载地址:

https://git-for-windows.github.io/

安装完成过程参考:

完成后,记得注册到系统环境变量。

输入 git 命令可以测试环境是否OK。

2. Coding.net的注册

注册地址:

https://coding.net/

注册完成后,将 昵称发送给 项目管理人员, 他会将你加入项目。

注意: 本文不涉及项目的初始化,创建,添加项目成员。

加入项目后,所有项目链接下看到项目情况:

https://coding.net/user/projects

进入一个项目后,右侧选择源码

建议要选择 SSH方式,不会每次提交弹出输入密码。

git@git.coding.net:guixue/XXXApp.git

上面这个路径,就是我们项目源码的地址, 后面会用到。

3. SSH秘钥制作与上传

我们跟 git 服务器通讯使用的使用 SSH 秘钥,不用每次都输入密码,方便。

在 CMD 框中输入 :

cd C:\Users\liuguixue\.ssh
ssh-keygen

新建一个 config文件,内容如下:

host git.coding.net
 user YOURNAME
 hostname git.coding.net
 identityfile /c/Users/liuguixue/.ssh/coding
 PreferredAuthentications publickey

其中 YOURNAME 换成 coding.net 上登陆的用户名

将 coding.pub 里的内容复制,在 coding.net 里增加秘钥。

右上角用户头像, 用户设置,

输入登陆密码即可。

4. 下载项目源码

使用命令 git clone 下载源码:

5. VSC 安装与插件

5.1 安装 VSC

VSC 指的是 VS Code,由微软开源的一款现代化的代码编辑器,支持插件,性能块,非常好用。

点击如下链接下载VSC工具

https://code.visualstudio.com/

下载完成后,双击安装即可。

注意:安装目录尽量不要有中文和空格,例如:C:\soft\VSCode

5.2 安装 Git Easy 插件

步骤如下:

安装完成后,重载窗口。

5.3 更新代码

用 VSC 打开 已经clone 的代码路径,

1.先暂存本地修改:

2.拉取服务器最新代码

5.4 提交代码

提交代码之前,也需要 暂存本地代码,拉取最新代码,参考5.3.

1.提交代码

2.推送到服务器

一些程序员才懂的笑话

关于List的括号

据说,一个黑客冒死偷到了美国用于导弹控制的lisp代码的最后一页,却发现那一页上全是右括号")“。

注释

几个程序员去吃饭,有人点了一道菜,麻辣牛蛙,然后其中有一个人说自己不吃牛蛙,于是负责点菜的直接在麻辣牛蛙前面划了两道斜杠。

就像这样://麻辣牛蛙

现场没有人觉得那里不对,
直到服务员上了11盘牛蛙………..

起名

某程序员结婚第一年有了一个女儿

他想了一下,起名叫玲玲,
后来第二个女儿,起名叫玲依,
第三个女儿,起名叫依玲。
第四个女儿,起名叫依依。
然后。。他有了个儿子,起名的时候难住他了。
于是他想了很久,最后决定叫逸初。

耿直的程序员

Gif 图片

  • 刚修复了Bug,我给老板演示的时候: ​ ​​​​

演示

  • 发布新版本

0

  • 第一次做程序员

frist

Vue 中的疑难杂症整理

1. 数组不支持监听

1). $set 元素赋值

    this.arr[2] =  "modify"

替换:

    this.$set(this.arr,  2,  "modify");

2). 使用 splice也可替换

    this.arr.splice(2, 1, "modify")

2. 打包Cordova资源加载问题

注册一个全局 $static 函数,用于绝对路径的转换:

Vue.prototype.$static = function(path) {
        let isCordova = window.location.protocol.match(/^file/);

    let staticPath = path;
    if(path.match( /^\/static/) ){
      if(isCordova){
        staticPath = "/android_asset/www/"+path;
      }
    }
    console.log("staticPath=",staticPath);
    return staticPath;
}

使用方法:

  computed: {
        thumbnail(){
          return this.$static(this.project.Thumbnail);
        }
  },

背景的使用方法:

<div class="home-page" :style="bgStyle">


computed:{
       bgStyle(){
           return {
             backgroundImage: `url(${this.$static("/static/index.png")})`
          }
       }
  },

3. Cordova 编译apk

参考: https://7449.github.io/2017/08/07/Android_Cordova_apk/

如何写 RESTful API 文档

本文使用 apidoc 工具自动生成 RESTfull API 格式的文档

安装

npm install apidoc -g

cd rest/doc
./doc.sh

产生的 手册文件存放在 doc/gen 目录下,打开index.html 即可使用。

使用方法

基于注释,按标记 自动提取文档内容, 类似写法如下:

 /**
 * @api  {get}  /v1/focus/:id/:focus
 * @apiGroup Projecting
 * @apiDescription  关注或取消关注项目。 注:用于App
*/

参数说明:


更多明细,请参考: http://apidocjs.com/#param-api

生成的文档如下图:

RESTful 测试工具

  1. 命令行工具可以使用 curl 工具:

  • get 方法

    curl \
    localhost:7999/v1/projecting/5964ba8c34be4904b70394bf
    
  • post 方法

    curl -l -H "Content-type: application/json" -X POST \
    localhost:7999/v1/projecting/5964ba8c34be4904b70394bf  -d \
    '["59644de76ab75d074f300a5b","5966472e79478d0e2547c7a3"]'
    
  1. 图形化的建议使用 PostMan, 使用方法:

Mongodb 安全加密认证

1. 安装

  1. Ubuntu:

    apt-get install mongodb
    
  2. Mac OS

    brew install mongodb
    

2. 服务器认证配置

2.1 添加用户

use admin
db.createUser({user: 'root', pwd: 'password', roles: ['root']})

db.system.users.remove({user:"haha"})

2.2 加密配置

  • ubuntu 下的配置: /etc/mongodb.conf,修改后的内容如下:

    bind_ip = 0.0.0.0
    port = 27098
    auth=true
    
  • Mac 下的配置, /usr/local/etc/mongod.conf

    net:
        bindIp: 0.0.0.0
        port: 27098
    security:
        authorization: enabled
    

注:

  • 建议更换一个其他的端口,来保证安全;

重启服务

  1. Ubuntu下:

    /etc/init.d/mongodb restart
    // 或:
    service  mongodb restart
    
  2. Mac OS下 重启服务

    ps -A|grep mongo
    sudo kill XXXX
    sudo mongod --config /usr/local/etc/mongod.conf --fork
    

3. 客户端认证连接

  1. 使用客户端 mongo 认证连接:

    mongo --port=27098
    >use admin
    >db.auth("root", "password")
    1
    >use your-other-db
    
  2. 使用客户端 robomongo 认证连接:

  1. nodejs 中 mongoose 的重新连接字段:

    mongodb : "mongodb://root:password@YOUR-IP:27098/FireDB?authSource=admin"
    

4. 参考文档

https://scarletsky.github.io/2014/10/25/mongodb-security-and-auth/

Vue 用户管理

  • 关于注册;
  • 关于登陆;
  • 如何保持session
  • 如何验证接口的安全性;
  • 如何控制测试账号

Vue

5. 如何控制测试账号

方案一: 给Email 账户添加后缀,比如 liuguxiue@qq.com,范式注册未 liuguixue@qq.com.test 这种默认都过。

方案二: 后台用户审核,加入允许测试的用户列表

夯实Vue系列 Demo 10 综合实例 TodoList(数据库)

至此,我们的例子已经查不到了,最后我们再完成一个综合实例,将 Demo 6 结合 Demo 9,完成一个带数据库的TodoList。

代码分析(获取初始数据)

1. 整体流程

2. 组件监听items更新,并且调用 InitTodoItem Action

  • items 设为计算属性,如果变更自动 更新;
  • mounted 函数里 dispatch 一个 InitTodoItem

3. Action 里的逻辑

  • Action里执行 Axios 的get函数;
  • Axios.get成功后,将获取数据使用 commit 传入 mutation 的 initTodoItems;

运行结果

其他的动作 增加、删除、更新 的逻辑 请参考:

https://coding.net/u/guixue/p/vue-starter/git/tree/master/src/pages/10-todo-db

夯实Vue系列 Demo 9 数据库:MongoDB

安装

1. Mac下安装

brew install mongodb
mongod --config /usr/local/etc/mongod.conf

2. Windows 下安装

注意:记得 D:/mongo/db 要存在,数据库文件都存储在这里。

常用工具

mongo 客户端常用命令

 show dbs           //显示数据库;
 use test               //使用某个数据库
 db.test.insert({‘name’:’byc’}) //插入一条记录
 db.test.find()         //查找所有记录
 db.test.findone()  //查找一条记录
 db.dropDatabase() //删除数据库

RoboMongo

https://robomongo.org/

Mongoose 的API

nodejs 中使用 mongoDB 用的是 Mongoose 库安装方法:

npm install mongoose --save-dev

1. Connect

connect(“mongodb://127.0.0.1:27017/yourdb");

2. Schema

 mongoose.Schema({XXXX})

3. model 模型

mongoose.model("Todo", TodoSchema)  //设置模型
mongoose.model("Todo")  //获取模型

4. 增、删、改、查

 Model.save()
 Model.remove()
 Model.update()
 Model.find()

代码分析

后台相关代码:

1. build/dev-server.js

这里会引入 /db/todo 的rest 服务调用入口:

2. server/config.js, mongoose.js

这里对 mongoose的 配置文件,在 build/dev-server.js 里盗用

3. model的定义与使用

  • model 的定义,/server/todo/model.js

  • model 的使用 /server/todo/api.js

结合客户端的测试效果

本例所有源码,请参考:
https://coding.net/u/guixue/p/vue-starter/git/commit/f49274908f4be37b

夯实Vue系列 Demo 8 RESTful Client:Axios

Demo 7 实现了一个简单的 RESTful 服务器,我们测试接口使用的是 Postman工具,实际代码中,我们也需要实现 客户端工具,Vue中有个 vue-resources 库,但大牛们已经建议使用 Axios。

Axios介绍

Axios 优势:

其中 Promise API 指的是什么?

如下:

  axios.get('/api/todo/2')
      .then(  function (response) { rest.ok(response); } )
      .catch( function (error)   { rest.err(error);  }   );

来替换 万恶的 callback 大坑。

代码实现

1. 配置 axios 的默认值:

其中 第 6行,将接口的header默认类型定位: json格式, 我们Demo 7中Rest服务器中需要补充 对 json格式的支持:

2. 编写通用接口服务:

3. 在nodejs 中测试服务

执行 ./nodejs.js

参考代码:
https://coding.net/u/guixue/p/vue-starter/git/blob/master/src/pages/8-rest-client/nodejs.js

4. 在 浏览器中

参考代码:

https://coding.net/u/guixue/p/vue-starter/git/tree/master/src/pages/8-rest-client

夯实Vue系列 Demo 7 RESTful Server

从Demo1 到 Demo6 主要讲了使用Vue构建Web程序的前端实现方法,后端没有任何涉及。

从本例开始,我们将介绍 现代web应用程序的 后端编程方法。

API 接口方式

目前比较常用的API 接口方式有三种:

  • SOAP
  • JSON-RPC
  • RESTful

    三种协议各有优势,但 RESTful 更简单通用,本例将采用 Express 来实现一个 RESTful 服务程序。

    关于RESTful详情介绍本文将不再展开,请参考: RESTful 架构详解

环境安装

Express 中有个模块 express-rest :

npm install --save-dev express-rest

express-rest 使用方法 详见: npm express-rest

实现

1. 增加rest功能到 Express

/build/dev-server.js 中已经新建了一个 Express的实例app,我们只需要把 rest服务增加给app即可。

2. restful-server.js 代码架构

3. get 实例

  rest.get('/api/todo', function(req, rest) {
    rest.ok(records);
  });

使用 Postman工具 测试 服务

代码参考

本文中的所有代码,请参考:

https://coding.net/u/guixue/p/vue-starter/git/commit/b12c0ed4936427073fd9a

夯实Vue系列 Demo 6 综合实例 TodoList(Vuex 实现)

上文Demo5里已经实现了 todolist,功能如下:

  • 新增:在输入框中输入内容,回车可新增;
  • 修改:单击条目可设置为完成;
  • 删除:双击条目可删除;

Demo5里的 DateStore.js 是利用浏览器的localStorage保存数据的,Vue中有专门的组件来处理数据存储: Vuex。

本例将使用Vuex来处理 TodoList里的 存储功能。

Vuex 简介

Vuex 里的一些核心概念:

这里有一个经典的概念流程的图,如下:

  1. Vue 的组件是通过数据 【state】来渲染的, 从Store中获取指定的 State:

    • Getter 的调用方法如下:

    • Getter 实现如下:

    todoItems: (state) => {
    return state.todoItems
    }
    
  2. Vue 的组件的修改会设置 State 流程如下:

    • 组件通过 store.dispatch 将设置 传给 Action

    • Action 做一些处理之后,使用 commit 方法,调用 mutation,调用方法如下:

    • mutation 修改State存入 Store, 实现方法如下:

    setTodoItems(state, todoItems) {
    state.todoItems = todoItems
    }
    

运行效果

本文中的所有代码,请参考:
https://coding.net/u/guixue/p/vue-starter/git/commit/84e6a3127e5a1a4dd4

夯实Vue系列 Demo **3 父子组件双向通讯:V-model

我们在Demo1和 Demo2 分别介绍了父子间的通讯原理:
* 父 -> 子:Props
* 子 -> 父:Emit

本例将二者综合在一起,实现双向通讯,也就是其他框架所说的【双向绑定】,当然通讯原理还是和我们上面 Demo1 和 Demo2一致的。

业务场景

我们需要实现一个 折叠面板,如下图所示:

  1. 如不勾选,此模块的详情将隐藏;
  2. 如勾选,此模块的详情将显示;
  3. 每个模块的勾选状态需要父组件传入,并允许更改;

实现(父->子)

代码解析:

  1. 左侧是 父组件(Parent),两次调用 Panel 展示 模块A 与模块B
  2. 红色圈中是 Panel中 添加一个 isShow的属性;
  3. Parent中分别 将变量 showA, showB 传入 Panel

已经能实现【业务场景】中的所有效果,但是,当切换勾选时,浏览器会抛出警告:

意思是,不要在子组件内修改 父组件传入的props。

那我们修改一下:

实现(子->父)

代码解析:

  1. 右侧的 子组件 Panel 新加了 一个计算属性my_show,当子组件触发其修改的时候,会调用 set 函数,$emit 一个input事件给父组件;

注: 为了方便,Panel组件的 isshow 属性 修改为 value了。

  1. 父组件内的 Panel A 与 B 分别使用了两种方式来接受 input 事件。其实v-model只是一个语法糖,将input事件固定关联 value属性的修改:

v-model是否可以绑定多个元素呢?

可以的,传入对象即可,如下:

1. Parent.vue

2. 子组件 Panel.vue

检测到obj内的成员变化,发送input事件给父组件。

代码实现

参考 https://coding.net/u/guixue/p/vue-starter/git

![](media/14952939575744/14957046058782.jpg)

夯实Vue系列 Demo 5 综合示例:TodoList

这里我们给出一个简单的 Todo List使用示例,提供的操作如下:

  • 新增:在输入框中输入内容,回车可新增;
  • 修改:单击条目可设置为完成;
  • 删除:双击条目可删除;

代码实现

1. index.vue 模板

模板里用3个事件来实现基本需求:

  • addItem 新增,在输入框中输入内容,回车可新增
  • toggleFinish,修改,单击条目可设置为完成;
  • deleteItem, 删除,双击条目可删除;

    <template>
    <div id="todo-app">
    <h1 v-text="title"></h1>
    
    <p class="note" >操作提醒:
      <br />* 新增:在输入框中输入内容,回车可新增
      <br />* 修改:单击条目可设置为完成;
      <br />* 删除:双击条目可删除;
      <br /> <br />
    </p>
    
    <input type="text" v-model="newItem" v-on:keyup.enter="addItem">
    
    <ul>
      <li v-for="item in items" v-bind:class="{'cls-finished': item.isFinished}"
          @click="toggleFinish(item)"  @dblclick="deleteItem(item)">
        {{ item.label }}
      </li>
    </ul>
    
    </div>
    </template>
    
    

    2. index.vue 脚本

  • methods实现3个事件: addItem、toggleFinish & deleteItem;

  • items 数据的管理:

    • DataStore.fetch() 获取当前所有的记录;
    • 在watch里,如果 items任何改变,将调用 DataStore.save(items) 保存数据
    <script>
    import DataStore from './DataStore.js'
    
    Array.prototype.remove = function(val) {
    var index = this.indexOf(val);
    if (index > -1) {
      this.splice(index, 1);
    }
    };
    
    export default {
    data(){
      return {
        title:"Todo List",
        items:  DataStore.fetch(),
        newItem:""
      }
    },
    methods: {
      toggleFinish(item){
        item.isFinished = !item.isFinished;
      },
      addItem(){
        this.items.push({label:this.newItem, isFinished:false});
        this.newItem  = "";
      },
    
      deleteItem(item){
        this.items.remove(item);
      }
    },
    
    watch:{
      items : {
        handler: function (items) {
          DataStore.save(items);
        },
        deep: true
      }
    }
    }
    </script>
    
    

3. DataStore.js

fetch 和 save的实现:

  • fetch, 从本地存储(localStorage)中获取记录;
  • save, 将记录保存到localStorage里;

DataStore相当于一个本地数据库。

const ITEMS_KEY ='todo_items';

export default {
  fetch(){
    return JSON.parse(
      window.localStorage.getItem(ITEMS_KEY) ||'[]')
  },

  save(items){
    window.localStorage.setItem(
      ITEMS_KEY,JSON.stringify(items));
  }
}

运行效果

本文中的所有代码,请参考:
https://coding.net/u/guixue/p/vue-starter/git/commit/8490ab2afe5443badc

注: 本例参考自视频教程: vue.js入门基础 推荐各位观看。

夯实Vue系列 Demo 4 组件通讯:Global Bus

上文中的 Demo1、Demo2 与 Demo3 分别讲述了 父子组件通讯方法:
* 父 -> 子,属性;
* 子 -> 父,事件;
* 双向绑定, v-model

那普通组件的通讯是以什么方式进行的呢?

其实跟 Emit方式类似,只是我们用了一个全局Vue对象最为事件Bus来传递和接受消息。

示例代码如下:

Demo3 代码

组件通讯:Global Bus

源码可下载:

https://coding.net/u/guixue/p/vue-starter/git/commit/b19c249b927b393ef73de7

运行结果

夯实Vue系列 Demo 1 父子组件通讯:Props

Vue 组件通讯概述

父子组件

  • 父 -> 子:属性,即:父组件传值给子组件使用Props属性;【本文将介绍】
  • 子 -> 父,事件;子组件 传值给父组件使用Emit事件;【Demo2 介绍】

普通组件

邻居组件或兄弟组件如何传值?

两种方式
* Global Event Bus 【Demo4 会介绍】
* Vuex 【Demo6 会介绍】

Demo1 代码:

父子组件通讯:Props 实现如下:

实例代码详情可参考

https://coding.net/u/guixue/p/vue-starter/git/commit/70f792b4c0d7966c

运行结果

输入框里输入例子,子组件会收到改变的效果。

但是反过来, 子组件如果想改变 传入的属性值,浏览器会抛出 警告。

因为组件设计初衷是单向数据流,只能由父->子单向传递。

夯实Vue系列 0 环境搭建

背景

本系列是我为公司编写的内部培训资料,目标是:快速让Vue理论知识落地,2天时间培训让工程师上手干活。

我本人也是十多年的老程序员了,也非常反感 《21天入门XXX》《7天精通XXX》速食类系列: 以Demo为主的学习方法。但不得不承认,站在公司的角度,这种效果确实很好,只用2-3天左右时间,就能让工程师上手干活,当然知识体系和深度肯定不够,所以,大家切记自己要去多查手册、翻源码。

夯实Vue系列文章几点约定:

  • 系列中能用代码说明的东西,尽量不废话!

    Talk is cheap, show me code.  // 弄点英文,貌似很吊的样子
    
  • 如果涉及背景知识如果陌生,请自行查阅相关教程后再结合代码理解;

  • 每个例子的实现尽量 控制在 100行代码以内;

  • 所有代码请参考: https://coding.net/u/guixue/p/vue-starter/git 建议下载后在本地参阅;

    墙裂、墙裂建议,里面的10个例子一定要自己理解后亲自敲过!!

代码使用:

1 下载代码

git clone https://git.coding.net/guixue/vue-starter.git

例子中 使用的 vue init 新建的项目模板,命令如下:(下载的代码已经包含在内,不用再执行下面的命令了)

vue init webpack vue-starter 

2. 查看每个例子:

每个例子都已经打tag了, 链接:https://coding.net/u/guixue/p/vue-starter/git/tags

运行例子

cd vue-starter
npm install  //首次运行需要
npm run dev

浏览器将自动弹出页面 localhost:8080

参考资料:

系列教程会提供 10个经典实例代码来学习 Vue,我会假设您已经学习过Vue基础,如果没有请先学习:

如有批评建议,请发Mail给我: guixue@outlook.com

Ant Design Mobile 渐进系列教程 5:Index主页面

1. 目标

略。

2. 原型

2.1 Index页面的组件

组件结构如下图:

包含三个组件:
* TodoAdd 添加待办事;
* TodoList 展示待办事列表;
* TodoItem 展示待办事项,属于 TodoList的子组件;

2.2 Index页面功能

需要完成的功能:

  1. 点击 TodoAdd可以添加待办事;
  2. 左滑动 TodoItem 可以删除待办事;
  3. 勾选 待办事可以 设置待办事状态为 已完成;

3. 实现

Ant Design Mobile 渐进系列教程 4:路由嵌套 Main页面实现

1. 目标

上一章介绍 React-Router的简单实用,本文将继续介绍路由嵌套 ,React父子组件的数据传递方式。

本文中的页面组件关系如下:

其中 Main 是父页面,Index、List 与Setting页面继承自 Main,如下:

2. 原型功能

本文需要实现的功能如下:

  1. 点击Tab按钮,切换页面;
  2. 设置当前页面Tab按钮 selected;
  3. 子页面可以通过属性传入 NavBar 标题;

3. 实现

3.1 Tab 事件

我们接下来,要做的任务是:

  1. 点击Tab按钮,切换页面;

思路:Tab 点击事件触发后,将浏览器的 URL 设置为对应的锚标记。

Tab的点击事件为 onPress,如下官方例子所示:

<TabBar.Item 
...
     onPress={() => {
       this.setState({
         selectedTab: m.url,
       });
     }} 
...

此例子的 this.setState 只是为了切换 TabBar Item的 选中状态(selected),我们可以修改为:

<TabBar.Item 
...
     onPress={() => {
       this.linkTo(m.url);
     }} 
...
linkTo(link) {
     this.context.router.push(link);
  },

this.context.router 需要先声明router为context的对象:

contextTypes: {
        router: React.PropTypes.object
  },

此功能的 预览效果如下:(开始时请等待2秒)
1

总结一下:

  1. 完成目标:点击Tab按钮,切换页面;
  2. 未解问题: 点击底部 Tab后,按钮没有被选中;
  3. 引入Bug:连续点击中间的Tab会产生警告:

    Warning: You cannot PUSH the same path using hash history

意思是: 要跳转的链接跟当前链接不能相同。

不用担心,#2 和 #3 其实是一个问题,将#2解决后,Tab状态为当前链接的Tab为selected,就不会响应 onPress事件了,所以#3也就不存在了。

3.2 Tab selected

上一节,点击 【清单】按钮时,状态没有修改为 selected,我们参考官方例子知道设置为选中其实很简单,对onPress里设置 selectedTab 即可:

 linkTo(link) {
     this.context.router.push(link);
     this.setState({selectedTab: link});
  },

但是,这样也不是最完美的,加入我直接输入

http://localhost:8989/#/list

就不会执行 onPress 函数,所以解决方案应该还要初始化
selectedTab ,实现在 getInitialState 函数开始:

  getInitialState() {

    let link=this.getLink()||'index';

    return {
      selectedTab: link,
    };
  },

  getLink(){
      let links = window.location.hash.match(/(\w+)/g);
      if (!links)
          return null;
      return links[0].toLowerCase();
    },

注:其中 getLink函数里的 window.location.hash.match(/(\w+)/g);
意思是:获取最后一个 / 之后的单词。

上面代码的预览效果如下:
2

3.3 NavBar Title

上面两个例子,我们已经完成切换了 TabBar 与router 的配合使用。

我们可以发现Main页面的 标题区域还没有根据不同的子页面切换。

Main 和 List 的关系是父子组件,如下:

NavBar Title, title 定义在子组件List内, 要传递给 父组件Main,并设置到父组件的方法中。

React的通信是单向的,即从父组件到子组件,总结组件间的通讯场景如下:

  1. 父组件->子组件:props
  2. 子组件->父组件:callback
  3. 子组件->子组件:子组件通过回调改变父组件中的状态,通过props再修改另一个组件的状态

    所以,我们这个例子符合场景2,通过 callback 子组件的数据送给父组件 。

3.3.1 List组件中新建 Prop

  getDefaultProps : function () {
      return {
        title : '历史列表'
      };
    },

3.3.2 Main组件定义 callback函数

增加一个 state: title;

 getInitialState: function() {
    return {
      title: "没有传入标题"
    };
  },
  
   setTitle: function(title) {
    this.setState({title: title});
  },
  
  
  render() {
  <NavBar ref="myNavBar" mode="dark" iconName="">
            {this.state.title}
          </NavBar>
},

将 setTitle 传递给 子组件 List:

 render() {
      ....
            <ContextBox>
              {this.props.children && React.cloneElement(this.props.children, {
                  "setTitle":this.setTitle
                })}
            </ContextBox>
           
           ....
},

注:由于咱们使用了react-router,父子组件的嵌套方式是借助 this.props.children,导致我们不能直接将 属性增加到子组件的标签内,需要React.cloneElement来增加子组件的属性。详情参考 React router and this.props.children - how to pass state to this.props.children

3.3.3 子组件使用callback

   componentDidMount(){
      let title = this.props.title;
      
      //callback: setTitle
      this.props.setTitle(title);
  },

3.3.4 效果预览

Index 与 Setting 页面 参考 List 页面修改后:

3

4. 小结

本文主要实现了三个功能:

  1. 点击Tab按钮,切换页面;
  2. 设置当前页面Tab按钮 selected;
  3. 子页面可以通过属性传入 NavBar 标题;

本文所有代码可以下载:

git clone git@git.coding.net:guixue/antdm-demo.git
cd antdm-demo
git checkout demo4

5. 更新

本文提到的功能,除了使用路由的嵌套外,直接将重复的内容封装为父控件,更更简单。

git clone git@git.coding.net:guixue/antdm-demo.git
cd antdm-demo
git checkout demo4-1

为了长期交流,antdm-demo 仓库被为私有,注册 coding.net 后, 加 guixue 为好友后,会添加进来。

npm 安装与使用

1 安装 nodejs

下载

https://nodejs.org/en/download/

直接安装

注意:ubuntu通过 apt-get install nodejs 安装的版本比较低。

node -v 

查看nodejs 的安装结果

2 npm 安装

直接通过命令可安装:

curl http://npmjs.org/install.sh | sudo sh 

通过

node -v 

查看npm 的安装结果

3 配置 npm registry

npm config set registry "http://r.cnpmjs.org"

也可以 使用 nrm 工具 来切换 npm 镜像库站点,

npm install -g nrm 

国内建议使用 淘宝的库:

4 nvm 配置 nodejs 的版本号

npm install -g nvm

node -v

nvm version

nvm install v6.10.3 
v8.0 v6.10.3

如果 安装 nvm 出问题 可以参考 https://github.com/creationix/nvm/blob/master/README.md , ubuntu下建议安装方法

curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.2/install.sh | bash

建议使用v6.10.3版本,因为 webpack等一些工具必须要求 6.X,而v6.10.3是LTS(长期技术支持)。

Ant Design Mobile 渐进系列教程 3:路由

1. 目标

本文会介绍 ADM框架中如何使用 React-Router, 在开始本章之前,请先阅读 阮一峰的博文 React Router 使用教程

首先,我们要问一个问题:为什么要使用 Router ?

由于我们用React开发的App基本都属于SPA(单页面应用)架构,SPA应用的复杂功能,还是会抽成多个虚拟页面来降低复杂度。

  • 实体页面之间的切换,基本是透过不同的 URL来区分;
  • 但虚拟页面之间的切换,最简单的方法是关联到 URL锚标记 ,比如:

React-Router 就是通过管理 URL,实现 React 组件的切换和状态的变化,复杂的应用都会用到路由,本文会将 React-Router 引入 我们的App。

2. 原型图

实现的功能如下:

注意:Index、List 与 Setting 这三个页面都会共用 Main页面的内容。

3. 实现

3.1 环境安装

npm install --save react-router

3.2 index.web.js 在入口处 填写 Router映射

import React from 'react';
import ReactDOM from 'react-dom';

import Main       from './pages/Main';
....


import { 
  Router, 
  Route, 
  IndexRedirect, 
  hashHistory 
} from 'react-router';


let RouterMap =(
  <Router history={hashHistory}>

    <Route path="/login"    component={Login} />
    <Route path="/register" component={Register} />
  
      <Route path="/"       component={Main}>
        <IndexRedirect to="index"/>
        <Route path="/index"    component={Index}/>
        <Route path="/list"     component={List}/>
        <Route path="/setting"  component={Setting}/>
      </Route>
  </Router>
);

ReactDOM.render(RouterMap, document.getElementById('root'));

3.3 pages/Main.js 抽成共用组件

<ContextBox>
    {this.props.children}
</ContextBox>

3.4 pages/Index.js 页面

import React from 'react';

export default React.createClass({
  render() {
    return (
      <p>TODO 主页</p>
    );
  },
});

其他几个页面类似:

  • pages/List.js
  • pages/Setting.js
  • pages/Login.js
  • pages/Register.js

4 测试结果

切换 URL 的锚标记 可以完成 页面组件的切换。

本文所有代码可以下载:

git clone git@git.coding.net:guixue/antdm-demo.git
cd antdm-demo
git checkout demo3

为了长期交流,antdm-demo 仓库被为私有,注册 coding.net 后, 加 guixue 为好友后,会添加进来。

Ant Design Mobile 渐进系列教程 2:组件

1. 目标

整个系列,我们会完成一个 TODO 的可用程序, 本文主要针对 ADM的 tab 组件。

2. 原型图

页面的组件的组成方式如下:。

注: 蓝色分支为ADM已有控件。

3. 实现

3.1 NavBar 组件

NavBar 本身在 ADM中已经存在了, 直接引入即可,如下:

import { NavBar } from 'antd-mobile';
export default React.createClass({
  render() {
    return (
      <div>
          <NavBar mode="light" iconName="">
            任务列表
          </NavBar>

          //....
      </div> 
    );
  },
});

效果如下:

3.2 ContentBox 组件

  • components/ContentBox.js 的代码如下:
import React from 'react';

import  '../style/ContextBox.less';

export default React.createClass({

  render() {

    return (
      <div className="contextBox">

      {this.props.children}

      </div>
    );
  },
});

  • 其中 style/ContentBox.less 的代码如下:

@tabHeight:120px;

.contextBox{
  width: 100%;
  height: 60%;
  margin-bottom: @tabHeight; 
  background-color:#eee;
  position: absolute;
  overflow: scroll;
}

3.3 TabListBar 组件

  • components/TabListBar 组件 源码 如下:

import React from 'react';


import { TabBar } from 'antd-mobile';
import  {Menu} from '../data/Menu';

export default React.createClass({
  getInitialState() {
    return {
      selectedTab: 'todo',
    };
  },

  renderContent() {
      return (
        <div>{this.props.children}</div>
      );
  },

  renderMenu() {
      let menus = [];
      let i =0;

      for(i=0;i<3; i++)
      {
        let m = Menu[i];
        menus.push(
        <TabBar.Item
          icon={{ uri: 'https://zos.alipayobjects.com/rmsportal/'+m.icon+'.png' }}
          selectedIcon={{ uri: 'https://zos.alipayobjects.com/rmsportal/'+m.selected+'.png' }}
          title={m.title}
          key={m.url}
          selected={this.state.selectedTab===m.url}
          onPress={() => {
            this.setState({
              selectedTab: m.url,
            });
          }}
        >
          {this.renderContent()}
        </TabBar.Item>
        );

      }
      return menus;
  },


  render() {

    let menus = this.renderMenu();

    return (
      <TabBar
        unselectedTintColor="#949494"
        tintColor="#33A3F4"
        barTintColor="white"
      >


      {menus}
        
       
      </TabBar>
    );
  },
});

  • data/Menu 数据源码 如下:
import React from 'react'
const Menu =[
    {
      title:"代办事", 
      url: "todo",
      icon:"UNQhIatjpNZHjVf",
      selected:"HLkBvJOKnmOfBPO",
    },
    
    {
      title:"清单", 
      url: "list",
      icon:"YWpPVCVOnJoCYhs",
      selected:"WadBBxOIZtDzsgP",
    },
  
    {
      title:"我的", 
      url: "mine",
      icon:"EljxLrJEShWZObW",
      selected:"LWNaMdwAFSmYBFw",
    }
  
  ];

export {Menu};

3.4 pages/Main 页面

  • pages/Main.js 的源码如下:
import React from 'react';


import { NavBar } from 'antd-mobile';

import ContextBox from '../components/ContextBox';
import TabListBar from '../components/TabListBar';

export default React.createClass({
  render() {
    return (
      <div>
          <NavBar mode="dark" iconName="">
            任务列表
          </NavBar>

          <TabListBar>

            <ContextBox>
              <p>任务内容</p>
              <p>....</p>
            </ContextBox>

          </TabListBar>

      </div> 
    );
  },
});

4. 运行结果

至此, 我们app的页面框架已经实现基本实现了,但是有个问题,点击下面的Tab时, ContextBox里的内容无法变化,下一章节我们会引入 Route 来解决这个问题。

本文所有代码可以下载:

git clone git@git.coding.net:guixue/antdm-demo.git
cd antdm-demo
git checkout demo2

为了长期交流,antdm-demo 仓库被为私有,注册 coding.net 后, 加 guixue 为好友后,会添加进来。

Ant Design Mobile 渐进系列教程 1:入门

1. 前言

Ant Design Mobile 是阿里蚂蚁金服团队维护的高质量移动应用的前端框架,除了40多个通用组件,更有意义的提出一套类似Ant Design的设计指南和场景模式。 框架的体验确如其slogin一样:微小·确定·幸福。

Ant Design Mobile框架是基于 react native,扩展支持Web组件,不仅可以编译成native的 iOS 与 android App,而且还提供了高性能的HTML5支持(微网站、微信公众号等)。

用框架解决移动应用跨端问题:

为了描述方便,下文将 Ant Design Mobile 简称为 ADM

2. 环境

目前 Ant Design Mobile 还没发布 1.0版本,入门(类似Getting Start)的文档暂时还没有。 但是,开发团队在 问题#56 中给出了一个简单的入门实例:

git clone https://github.com/ant-design/antd-init.git
cd boilerplates/MobileDemo

也可以从 coding.net上下载:

git clone git@git.coding.net:guixue/antdm-demo.git
cd antdm-demo
git checkout demo4

参考 Readme 文档中的安装命令:

npm install npm@3 -g
npm install react-native-cli -g
npm install

3. 第一个例子

环境安装成功后,无需修改一行代码:

npm start

在浏览器中 localhost:8989 可以看到效果:

好了,对于Ant desgin mobile 框架,我们已经算入门了,接下来我们将继续开启【微小·确定·幸福】的Ant desgin mobile之旅…

注: 本文所有代码可以下载:

git clone git@git.coding.net:guixue/antdm-demo.git
cd antdm-demo
git checkout demo1

为了长期交流,antdm-demo 仓库被为私有,注册 coding.net 后, 加 guixue 为好友后,会添加进来。

前端开发入门参考资料单

更新日期: 2016-01-12

1. 基础语法篇

1.1. HTML&CSS 教程

1.1.1 W3C HTML 教程

1.1.2 W3C CSS 教程

1.1.3 慕课网 HTML+CSS基础课程 练习

1.1.4 Web配色:色彩设计方法

完成1.1所有课程,建议完成作业,实现一个博客的页面,例子可以参考:
http://www.diandian.com/
http://www.ghostchina.com/

1.2. JavaScript 教程

1.2.1 W3C JavaScript教程

1.2.2 慕课网 JavaScript 入门篇 练习

2. 进阶框架篇

2.1. Bootstrap

2.1.1 Bootstrap官方文档

2.1.2 慕课网 玩转Bootstrap 基础 视频

2.2. CSS 框架

2.2.1 Less框架

2.2.2 慕课网 Less基础 视频

2.3. jQuery 框架

2.3.1 W3C jQuery教程

2.3.2 慕课网 jQuery基础课程 练习

2.4. AngularJS 框架

2.4.1 AngularJS 中文社区系列 教程

2.4.2 慕课网 AngularJS实战 视频

2.4.3 AngularJS API手册 英文

Ubuntu 下PHP开发环境配置脚本

1. 系统更新

apt-get update

2. 安装git

apt-get install libcurl4-openssl-dev  libexpat1-dev git

3. 安装 lnxcfg 脚本 ( bookmark, git command alias, vim and plugins.)

git clone https://github.com/guixue/lnxcfg.git
cd lnxcfg
make
vi ~/.bashrc 
添加
if [ -f ~/.mybashrc ]; then
    . ~/.mybashrc
fi
source ~/.bashrc

4. 安装 HTTP 服务器: nginx

apt-get install nginx

测试: hello.html 可以显示

5. 安装数据库: mysql-server

apt-get install mysql-server

mysql -u root -p 测试通过

6. 安装PHP:

6.1 PHP5.X

ubuntu 16.04 无法安装 php5.6

sudo apt-get purge `dpkg -l | grep php| awk '{print $2}' |tr "\n" " "`
sudo add-apt-repository ppa:ondrej/php
sudo apt-get install software-properties-common
sudo apt-get update
sudo apt-get install php5.6
apt-get install php5.6-cli php5.6-cgi mysql-server php5.6-mysql 
apt-get install php5.6-xml php5.6-gd
apt-get install php5.6-fpm
apt-get install spawn-fcgi
cd /usr/sbin
ln -s php-fpm5.6 php-fpm

6.2 PHP 7.x

sudo apt-get install php
apt-get install php-cli php-cgi php-mysql php-xml php-gd

7. 测试PHP 环境:

1) . 新增vhost 站点:

例如: /etc/nginx/sites-enabled/default.conf

server {
    listen 80;
    server_name [IP or domain Name];

    root  /var/www/sites/default/;

    location / {
        index index.html index.php;
        autoindex on;
    }
    
    include enable-php.conf;
    
    access_log logs/[domain Name].log;

}

注: /etc/nginx/enable-php.conf ,针对通用的所有的PHP解析:

location ~ \.php$ {
        fastcgi_intercept_errors on;
        fastcgi_pass unix:/var/run/php5-fpm.sock;
        
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  fastcgi_script_name;
        include fastcgi_params;

}

测试: info.php 简单探针程序可以运行;

<?php

phpinfo();

info.php 测试通过

apt-get install phpmyadmin
# whereis phpmyadmin
# ln -s /usr/share/phpmyadmin phpadmin

SSH 配置

将静态资源压缩10倍+

背景

目前JS框架很多,发展很快,但是使用 Angularjs(ionic) 或 React 框架,合并的js源码动辄 几兆,浏览器首次加载太吃力,使用较多的是使用 uglify,minify 等工具后,静态文件能通常压缩2-3倍,很多情况下这还是不够,如何进一步压缩呢?

服务器Nginx原生支持 gzip on/off 的功能,此功能的意思是,自动为浏览器的所有文件实时压缩,可以节省传输带宽,提供传输速度。但对每次请求实时压缩,同时会增加了服务CPU的负荷,有时,反而得不偿失,所谓鱼和熊掌不可兼得。

有没有方案既吃鱼又啃熊掌吗?即:利用gzip节省带宽,又不多消耗CPU。

答案是: static gzip!

方案

实时压缩对于请求大于 1K 动态资源还是很有用处的。

对于静态资源来讲,我们需要的只是压缩,不要针对每次一请求实时处理。

如果我们需要压缩的文件(例如 all.js), 在部署的时候就产生了一份压缩好的文件,(如 all.js.gz),只需要在服务器请求 *.gz 文件的时候加上, http响应头:

Accept-Encoding:gzip, deflate

告诉浏览器,*.gz 是压缩文件,要解压缩,就可以啦。

这就是,static gzip,所以问题的解决在于,需要解决两个小步骤:

  • Step1:部署前使用工具将大的资源文件 gzip,添加.gz后缀;
  • Step2:服务器查找文件时,先查找是否有同名gz文件,如有优先读取gz文件。

Gzip Step1 部署前统一压缩

gulp

安装

npm install -save-dev gulp-gzip
gzip = require('gulp-gzip');


gulp.task('gzip', function() {
    //js gzip
    gulp.src('./www/lib/ionic/js/*.min.js')
      .pipe(gzip())
      .pipe(rename({extname: '.gz'}));

      //css gzip
      gulp.src('./www/lib/ionic/css/ionic.css')
        .pipe(gzip())
        .pipe(rename({extname: '.gz'}));
});

执行 gulp gzip 命令, 查看结果,可以节省 70%左右的网络带宽:

其他工具

例如webpack等,这里就省略了,按照 gulp的思路,去找对应 plugin 吧。

Gzip Step2 Web服务器添加Header

Nginx

nginx 去除了支持 gzip 的实时 压缩,也支持静态压缩,添加如下配置即可。

gzip_static on;

开启nginx_static后,对于任何文件都会先查找是否有对应的gz文件。

Apache

通过 deflate_Module和headers_Module 来实现,详情不标。

总结

以 ionic为例,在压缩前, 2.6M左右:

压缩后, 只有不到200K:

整个压缩比例 10倍以上。

后记

其实,nginx_static 还是会有一些I/O性能损耗,即,会对所有的文件都先查询一下是否存在同名gz文件。

如果修改为:
* 站点目录添加 .gzip_statics 配置文件列表;
* 根据站点根目录下的一个配置文件(server 启动时将配置文件加载到内存)的规则,来确定是否查找 gz文件。

附脚本:用gulp 压缩js和css

var gulp = require('gulp');

var     uglify = require('gulp-uglify');
var     rename = require('gulp-rename');
var     mincss = require('gulp-minify-css');
var     concat = require('gulp-concat');
var     gzip = require('gulp-gzip');

var dist_path = 'Public/static/dist/';

gulp.task('gzjs', function() {

    gulp.src([
          'Public/static/vendor/modernizr/modernizr.js',
     //........待压缩的列表,注意顺序
        ])
        .pipe(concat('common.js'))
        .pipe(gulp.dest(dist_path))
        .pipe(rename('common.min.js'))
        .pipe(uglify())
        .pipe(gulp.dest(dist_path))
        .pipe(rename('common.min.js'))
        .pipe(gzip())
        .pipe(gulp.dest(dist_path));

});

 gulp.task('gzcss', function() {
     gulp.src([
         'Public/static/css/font-awesome.css',
         //........待压缩的 css 列表,注意顺序
        ])
         .pipe(concat('common.css'))
         .pipe(gulp.dest(dist_path))
         .pipe(rename('common.min.css'))
         .pipe(mincss())
         .pipe(gulp.dest(dist_path))
         .pipe(rename('common.min.css'))
         .pipe(gzip())
         .pipe(gulp.dest(dist_path));

 });

gulp.task('default', ['gzjs', 'gzcss']);

React 边写边学 3 使用Webpack自动化管理

缘由

《React 边写边学 2:使用Babel》最后的章节里,很多执行命令,非常麻烦。本人将介绍Webpack自动化管理部署 React 项目。

Webpack介绍

npm install --save-dev webpack webpack-dev-server
npm install --save-dev css-loader style-loader

webpack教程参考:
* Webpack 中文指南
* React结合Webpack使用

配置文件介绍

此次项目是在 《React 边写边学 2:使用Babel》 基础上展开的。

  • webpack.config.js 文件
var path = require('path');
var webpack = require('webpack');
var CompressionPlugin = require("compression-webpack-plugin");

module.exports = {
  entry: './src/index.jsx', // 入口文件,单入口 index.jsx 文件
  output: { path: __dirname, filename: 'bundle.js' }, // 编译到的文件
  module: {
    loaders: [ // 使用特定的加载器 loader 处理特定的文件
      {
        test: /.jsx?$/, // 文件过滤规则
        loader: 'babel-loader',
        exclude: /node_modules/,
        query: {
          presets: ['es2015', 'react'] // es2015 处理 ES6 语法,react 处理 jsx 语法
        }
      },
      
      {test: /\.css$/, loader: 'style!css'}
    ]
  },
  plugins:[
      new webpack.optimize.UglifyJsPlugin({
        compress: {
          warnings: false
        }
      }),
      
      new CompressionPlugin({
            asset: "[path].gz[query]",
            algorithm: "gzip",
            test: /\.js$|\.html$/,
            threshold: 10240,
            minRatio: 0.8
        })
  ]
};

文件格式与结构与说明:

  • package.json 文件
{
  "name": "3_webpack",
  "version": "1.0.0",
  "description": "",
  "main": "./src/index.jsx",
  "scripts": {
    "start": "webpack-dev-server -p --hot --progress --colors"
  },
  "author": "Guixue",
  "license": "ISC",
  "devDependencies": {},
  "dependencies": {
    "babel": "^6.5.2",
    "babel-loader": "^6.2.4",
    "babel-preset-es2015": "^6.9.0",
    "babel-preset-react": "^6.5.0",
    "compression-webpack-plugin": "^0.3.1",
    "css-loader": "^0.23.1",
    "extract-text-webpack-plugin": "^1.0.1",
    "react": "^15.1.0",
    "react-dom": "^15.1.0",
    "style-loader": "^0.13.1",
    "webpack": "^1.13.1",
    "webpack-dev-server": "^1.14.1"
  }
}

将 package.json文件放根目录后,安装依赖的npm库。

npm  install

执行并运行

执行webpack

npm  start

浏览器 输入 http://localhost:8080/ 即可查看效果。

总结

本文通过引入webpack 达到了 对React 项目自动化管理的目的,当然这只是简单的管理,更复杂的配置和应用可以参考 github webpack/react-starter, 到此为止,我们才完成React项目开发环境的搭建。请继续关注《React边做边学》系列其他内容。

React 边写边学 2:使用Babel

缘由

《React 边写边学 1:Hello World》用的是 CDN 上的库和转换器,正式的React项目环境我们需要使用其他的转换编译工具。

JSX 到 ES5 的编译/转换器工具有很多,比如:

  • React-tools jsx 命令
  • Jsxtransformer
  • reactify
  • Traceur
  • Babel

目前使用最多的是Babel ,Babel支持的功能也比较:

  • ES6/ES2015 转 ES5 (3个 stage)
  • JSX 转 ES5
  • ES7 转 ES5
  • CoffeeScript 转 ES5

《圣经·创世纪》第11章中说,人类最早的时候都住在一个地方,讲一种语言。他们决定造一座通天的塔,所有的人住在里面,人类再也不会分散。上帝不同意,他将人类拆散到世界各地,让人类讲不同的语言,从此难于沟通。
因此,"巴别塔"就成了混乱和语言不通的代名词,是《圣经》中最广为人知的故事之一。

Babel 的使用方法

babel src/input.jsx -o dist/output.js

将 input.jsx 格式的内容 编译为 ES5的 output.js

实例项目文件

所有项目文件

├── package.json  //npm 项目配置文件
├── .babelrc        //babel配置文件
├── index.html      //HTML 入口文件
└── src
     ├── Hello.jsx   //Hello 组件
     └── index.jsx   //入口

1 package.json

npm 项目配置文件可以手动新建

npm init            //全部回车默认
npm install --save react react-dom babel babel-cli babel-core
npm install --save   babel-preset-es2015 babel-preset-react 
npm install --save  browserify react-hot-loader webpack

新建完成后的文件如下:

{
  "name": "2_babel",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "build": "babel src/index.jsx -o dist/index.js&&babel src/Hello.jsx -o dist/Hello.js",
    "pack": "webpack -d dist/index.js dist/all.js",
    "start": "http-server",
    "restart":"npm run build && npm run pack && npm run start"
  },
  "author": "Guixue",
  "license": "ISC",
  "devDependencies": {
    
  },
  "dependencies": {
    "react": "^15.0.2",
    "react-dom": "^15.1.0",
    "babel": "^6.5.2",
    "babel-cli": "^6.9.0",
    "babel-core": "^6.9.1",
    "babel-preset-es2015": "^6.9.0",
    "babel-preset-react": "^6.5.0",
    "browserify": "^13.0.1",
    "react-hot-loader": "^1.3.0",
    "webpack": "^1.13.1"
  }
}

2 .babelrc

babel 编译配置文件,默认的编译参数

{
  "presets": ["es2015", "react"]
}

3 index.html

HTML页面:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>React Use Babel tool</title>
   
</head>

<body>
  

    <div id="container"></div>
    <script src="dist/all.js"></script>
</body>
</html>

4 src/Hello.jsx

Hello 组件:

import React from 'react';


export default class Hello extends React.Component{
    render() {
        return (<div>Hello {this.props.name}</div>);
    }
}

5 src/index.jsx

Index入口:

import React from 'react';
import ReactDOM from 'react-dom';


import Hello from './Hello';

ReactDOM.render(
    <Hello name="Guixue"></Hello>,
    document.getElementById("container")
);

编译并执行

  1. 安装 npm 包;
npm install
  1. 使用Babel编译 src 下的代码

或者直接执行 npm run build

babel src/index.jsx -o dist/index.js
babel src/Hello.jsx -o dist/Hello.js
  1. 使用 Webpack 打包代码

或者直接执行 npm run pack

webpack -d dist/index.js dist/all.js
  1. 启动Web Server

或者直接执行 npm run server

http-server

注: 以上内容 2-4 步骤,可以使用 npm run restart

总结

本文使用Babel编译JSX格式成功部署本地开发环境。但是,同时也带来了一个问题,步骤2的编译,如果项目中有很多文件,每个文件一条命令,肯定非常麻烦,有没有自动化的开发部署方案? 请关注系列文章《React 边写边学 3 使用Webpack自动化管理》

附:本文的参考的文档

React 边写边学 1:Hello World

背景介绍

React是Facebook出品的,目前最热门的(没有之一)前端框架,

React主页 中提到, 只三方面的内容:

  1. 只关注View层,UI的操作与展示,引入JSX自定义格式,使组件化更近简洁;
  2. 虚拟DOM,通过对DOM的模拟表现,最大限度地减少与DOM的交互。独特的diff算法,操作或渲染虚拟DOM性能非常高;
  3. 单向数据流,沿着组件树从上到下单向流动的。

第一个例子 Hello World

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>React hello world</title>

  <script src="http://cdn.bootcss.com/react/15.1.0/react.min.js"></script>
  <script src="http://cdn.bootcss.com/react/15.1.0/react-dom.min.js"></script>
  <script src="http://cdn.bootcss.com/react/0.14.0-beta3/JSXTransformer.js"></script>

</head>

<body>
  
    <div id="example"></div>

    <script type="text/jsx">
      var HelloMessage = React.createClass({
        render: function() {
          return <h1>Hello {this.props.name}</h1>;
        }
      });

      ReactDOM.render(
        <HelloMessage name="World" />,  
        document.getElementById('example')
      );
    </script>
    
</body>
</html>

代码运行结果:

注: 如果要在浏览器中查看 VDOM 标签,推荐 React Devtools 插件, Chrome 和 Firefox 都支持。

代码解析


第一个例子,要最简单,所以本地不需要安装任何库,直接用 CDN的。

  • react React的库
  • react-dom React Dom库
  • JSXTransformer 将JSX语法转为 JS 格式,正常的生产环境中,不会使用此类的库,会用在部署前先用 Babel 工具转换后再部署。

总结

本文通过一个页面,成功运行React的第一个实例。
但是,使用CDN上的库以及JSXTransformer.js的做法,写的学习React的小例子还行,真正做项目肯定不行,那怎么做呢? 请阅读系列第二章 《React 边写边学 2:使用Babel》

React 组件库 Antd入门:写一个 Blog 例子

1. 需求分析

最简单的博客展示要有两个页面组成:

  1. 博客列表
  2. 博客详情页

1. 1 博客列表页面 Blog Home Page

博客首页列表

1. 2 博客详情页 Blog Detail Page

博客详情页

2. 路由与布局

2.1 安装环境

npm install antd-init -g
mkdir blog && cd blog
antd-init
npm install

3. 子组件实现

TBD…

4. 后台数据实现

TBD…

React 组件库 Antd 的使用入门

环境准备

安装 antd项目模板:

git clone https://github.com/YuhangGe/react-antd-template.git
cd react-antd-template
npm install 

测试例子:

npm run dev

在浏览器中输入 http://localhost:8000 即可访问

源码分析

代码结构:

App 组件的基本层次:

WeUI 开发示例

1. 开发环境

1.1 下载 weui 库

安装开发环境

mkdir weiixn-demo
npm init
npm install --save-dev react-weui

react 等依赖库都会自动下载到 node_modules 目录下。

另外,jsx 的解析工具需要添加,如下:

npm install --save-dev babel

1.2 webpack 使用

Express 学习实例

1. Express框架

1.1 运行原理

1.2 Express API

1.3 router的使用

app.route('/login')
    .get(function(req, res) {
        res.send('this is the login form');
    })
    .post(function(req, res) {
        console.log('processing');
        res.send('processing the login form!');
    });

2. Express 实例 Hello world

2.1. 环境安装

 mkdir demo
 npm init       //一路回车
 npm install --save express@4

2.2. 写一个例子

var express = require('express');
var app = express();

app.get('/hello', function(req, res){
  res.send('Hello World');
});


var server = app.listen(3000, function() {
    console.log('Listening on port %d', server.address().port);
});

2.3. 测试

浏览器用 http://localhost:3000/hello 会输出

Hello World

3. 使用 generator 创建实例

npm install -g express-generator
express --sessions --css stylus --ejs myapp
cd myapp
npm install
node app

浏览器用 http://localhost:3000/ 会输出 index 内容。

nginx 配置反向代理

站点配置如下:

/etc/nginx/sites-enabled# vi XXXX.conf
server {
    listen 80;
    server_name YOUR_DOMAIN.com;


    location / {
        proxy_pass http://127.0.0.1:8080;
    }
}

常用命令收集

Yxld0iiRyjm4ZXrq

MYSQL压缩备份

filename="backfile_prefix_`date +%y%m%d`.sql.gz"
database="database name"
username="username"
password="password"
mysqldump -hlocalhost -u$username -p$password $database|gzip >$filename

文件夹压缩


tar -zcvf ./test.tar.gz ./test

//解压
 tar -zxvf  ./etc.tar.gz

分卷压缩

  1. 将目录分卷压缩
zip -s 100m -x "*.DS_Store" -r split-foo.zip foo/

• -s 切分单元的大小,可选的单位有k(kB), m(MB), g(GB), t(TB),默认为m
• -r 或者 –recurse-paths 递归目录
• -x 或者 –exclude 忽略文件

  1. 切分已有zip文件
zip existing.zip --out new.zip -s 50m
  1. 解压分卷:
//-s 0 表示合并
zip -s 0 split.zip --out single.zip

//解压
unzip single.zip 

上传文件到服务器

scp -r file.txt username@192.168.0.1:/home/username/

chrome清除 dns

chrome://net-internals

//Mac 清除缓存
sudo dscacheutil -flushcache;sudo killall -HUP mDNSResponder; sudo dscacheutil -flushcache;sudo killall -HUP mDNSResponder; 

//最高版本
sudo dscacheutil -flushcache


# 

## Mac 下 vmware 无法打开虚拟机
先后显示两个对话框:

* 打不开 /dev/vmmon:无此文件或目录。
* 初始化显示器设备失败。

在命令行中输入:

    sudo kextunload /Library/Extensions/intelhaxm.kext




重复添加数据,SQL筛选

1. 问题

由于网络缓慢,或用户点击太快,造成重复添加记录。

2. 解决方案

  • Root Case 方案:

    • 用户提交后,将 form 里的 button disabled,
    • 后台每次新增数据时,验证是否已经存在。
  • 补救方案:

筛选重复记录,手动删除:

  Select * From TABNAME  Where tid 
  In (Select tid From TABNAME Group By tid Having Count(*)>1)

Thinkphp 中 json RPC 的跨域方案

1. 跨域支持

Web server 的配置(nginx)

   location ~ \.php$ {

    add_header Access-Control-Allow-Origin *;
    add_header Access-Control-Allow-Headers Origin,DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,If-Modified-Since,Cache-Control,X-Requested-With,Content-Type,Accept;
    add_header Access-Control-Allow-Methods POST;

    fastcgi_intercept_errors on; 
    fastcgi_pass   127.0.0.1:9000;
    fastcgi_index  index.php;
    fastcgi_param  SCRIPT_FILENAME  fastcgi_script_name;
    include /usr/local/etc/nginx/fastcgi.conf;
}

Thinkphp中的配置 ThinkPHP/Library/Vendor/jsonRPC/jsonRPCServer.php

if( $_SERVER['Access-Control-Request-Method'] != 'POST' &&   // support nginx cross-domain.
    $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_METHOD'] != 'POST') // support apache CORS cross-domain.
    {

        if (
            $_SERVER['REQUEST_METHOD'] != 'POST' || 
            empty($_SERVER['CONTENT_TYPE']) ||
                (   $_SERVER['CONTENT_TYPE'] != 'application/json' &&
                    $_SERVER['CONTENT_TYPE'] != 'application/json; charset=UTF-8'
                )
            ) 
        {
            // This is not a JSON-RPC request
            $dbg = json_encode($_SERVER);
            Log::err("Not a json rpc client_SERVER=$dbg");
            return false;
        }

    }

2 用 PHP 作为代理实现跨域

以上配置 nginx的 CORS,但是对 IE8以下的浏览器兼容不好,可以直接使用一个简单的php脚步作为代理
在调用者的根目录新建 API.php。

<?php 

function curl_post($url, $post_data, $timeout=3)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);

curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);


$headers = array("Content-type: application/json; charset=UTF-8",
                "Accept: application/json",
                "Cache-Control: no-cache",
                "Pragma: no-cache");

curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); 
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
//curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
$output = curl_exec($ch);

curl_close($ch);

return $output;
}



$host = "http://remote_server/API.php?c=".$_GET['c'];
$req = file_get_contents('php://input');

$out = curl_post($host, $req);

echo($out);

 ?>

TODO:

Thinkphp 只支持 JSON-RPC 1.0 规格,不支持 批量操作。
* http://www.kancloud.cn/thinkphp/json-rpc-2/43544

常用代码: 日期 与 转义

1 时间和日期

  • 使用 int 在数据库中,例如 create_time PHP 中填入时 用 time函数
$info['create_time'] = time();
  • 前台显示时 ,格式化显示
date("Y-m-d H:i:m", $val) 
  • 前端输入 2016-02-12 13:03 , 提交到数据库的要用strtotime代码为:
$info['datetime'] = strtotime($info['datetime']);

2 转义字符 (换行符转换)

  • br 换成 换行符
    function br2line ($string)
    {
        $string = preg_replace("/(\r\n|\n|\r)/", "", $string);
        return preg_replace("=<br */?>=i", "\n", $string);
    }
  • 换行符 换成 br, 注: 在输出json中常用,因为不能有换行。
    function line2br ($string)
    {
        return preg_replace("/(\r\n|\n|\r)/", "", nl2br($string));
    }

上传头像并压缩裁剪

1. 问题概述

我们做的一个报名系统,有个上传头像的功能,一开始前端同学忘记使用 lrz.js的压缩上传了。造成的结果是,客户上传的头像大多是 1M以上,造成空间与网络的浪费,体验也很长。

2. 解决方案

此问题的解决分两个方面:

  • 一个是 root case,即:开启前端压缩,利用canvas的 base64格式字节码上传到服务器。
  • 另一方面,之前已经上传的头像补救。

2.1 lrz.js图片压缩方案

lrz使用方法

2.2 利用命令行批量裁剪图片

这里用的工具是 imagemagick 里的 convert 命令, shell脚本如下, reszie.sh:

filepath="$1"
filelist=`ls $filepath`
for file in $filelist
do 
 echo  "$filepath/$file"
 convert -resize 300 "$filepath/$file" "$filepath/$file"
done

使用方法:

reszie.sh ./Upload