魑魅魍魉 发布的文章

2015年9月12日,去了天坛,回来的时候,步行至前门,从前门站入地铁二号线转8号线,奥体中心站下,步行回家,一路随拍了几张。

庆丰包子铺

庆丰包子铺

北京二锅头三大宗师

北京二锅头三大宗师

这是叫什么祥来着,忘记了,不拍招牌,失误啊。

_DSC1769.jpg

SPAO,现代品牌混入北京前门大街

_DSC1771.jpg

奥体中心的那几棵大钉子,至今也不知道叫啥

奥体中心的那几棵大钉子,至今也不知道叫啥

北京天坛位于北京市东城区,是明清两朝帝王祭天、祈谷和祈雨的场所。是现存中国古代规模最大、伦理等级最高的祭祀建筑群。1961年,天坛被中华人民共和国国务院公布为第一批全国重点文物保护单位之一。1998年,“北京皇家祭坛—天坛”被列为世界文化遗产。

_DSC1638.jpg

_DSC1458.jpg

_DSC1618.jpg

_DSC1575.jpg

_DSC1632.jpg

_DSC1526.jpg

_DSC1529.jpg

_DSC1616.jpg

_DSC1594.jpg

_DSC1606.jpg

_DSC1543.jpg

_DSC1599.jpg

_DSC1641.jpg

_DSC1582.jpg

_DSC1580.jpg

_DSC1533.jpg

_DSC1555.jpg

_DSC1558.jpg

_DSC1577.jpg

_DSC1611.jpg

_DSC1551.jpg

_DSC1548.jpg

_DSC1596.jpg

_DSC1587.jpg

2015年9月3日,世界反法西斯战争胜利70周年,全国放假一天,由于公司在长安街沿线,所以,9月2号就已经封内城的北京,我们也没法上班,所以9月2号就出发了,早上 7:05 的高铁到济南,8:44 到达济南西,按计划,走了芙蓉街以及几眼泉水。

芙蓉街(2015.09.02)

芙蓉街

早上起得太早就坐高铁,所以,眼神有点迷离啊。

_DSC0237.jpg

这条街是有点名气,但是实在是没啥好逛的,不长,里面唯一感觉还不错的地方就是 国足臭豆腐 ,味道不错,臭味确实像国足,另外还有几家推荐的鲁菜餐馆,因为时间原因到是没有一一品尝,比如会仙楼等。

走完芙蓉街之后,穿过一条小巷子就直接到了大明湖了,对于游客来说,大明湖实在是没啥看的,但是如果站在济南人的角度去考虑,把它当作是日常休闲的地方,那在各种城市的公园里面,也算得上是很不错的一个地方了。

国足臭豆腐

国足臭豆腐

国足臭豆腐

本来想吃辣味的,就要的香辣,哪知道,香辣的其实和我想的不一样,我想吃的那种在那里叫麻辣,所以在后来让老板又加了一些麻辣,就是下图上面的那一点辣椒。

国足臭豆腐

芙蓉街里面的一些小店

芙蓉街里面的一些小店

芙蓉街里面的一些小店

芙蓉街里面的一些小店

芙蓉街里面的一些小店

走过芙蓉街之后穿过的小巷子

走过芙蓉街之后穿过的小巷子

在去往大明湖的路上还有一排小摊位,景区里面很常见的那种卖手工艺品的。

_DSC0291.jpg

_DSC0290.jpg

大明湖(2015.09.02)

GOPR4796.jpg

好吧,那个牌楼上面的大明湖三个字只拍到一个大字,对于我这种所有拍照都得自己动手的人来说,就原谅我吧。

大明湖

大明湖

大明湖

另外,不得不说一下,大明湖南区是免费的,但是水面和北区(老区)是收费的。

五龙潭(2015.09.02)

午饭在五龙潭外面的一家 老济南药膳把子肉 的小饭店里面吃的,生意很不错,味道也不错,不过没有自己的照片,所以就借同事的一用了。

老济南药膳把子肉

老济南药膳把子肉

这是我们出来的时候拍的,进去的时候忘记拍个门脸了。

五龙潭

五龙潭

五龙潭

五龙潭

五龙潭

五龙潭

五龙潭赏鱼

五龙潭最有名的就是赏鱼了,我也是去了才知道,直接上图。

五龙潭赏鱼

五龙潭赏鱼

五龙潭赏鱼

五龙潭赏鱼

五龙潭赏鱼

五龙潭赏鱼

五龙潭赏鱼

五龙潭赏鱼

五龙潭赏鱼

五龙潭赏鱼

下面这几张是 GoPro 4在水下拍的。

五龙潭赏鱼

五龙潭赏鱼

五龙潭赏鱼

趵突泉(2015.09.02)

五龙潭旁边不远就是趵突泉,从小就知道它,二十多年后才看到它,至少,在它将来的某一天消失后我还知道缅怀它。

趵突泉正门

趵突泉正门

趵突泉泉眼

趵突泉泉眼还有我

趵突泉泉眼

趵突泉泉眼

趵突泉泉眼

趵突泉公园内景

趵突泉公园内景

趵突泉公园内景

趵突泉公园内景

趵突泉公园内景

趵突泉公园内景

趵突泉公园内景

趵突泉公园内景

趵突泉公园内景

趵突泉公园内景

黑虎泉(2015.09.02)

黑虎泉好像就全是打水的人了,沿着河道一直走,有很多口泉水,也有很多很多的人在打水。

黑虎泉

黑虎泉

黑虎泉

黑虎泉

黑虎泉

黑虎泉

黑虎泉

黑虎泉

黑虎泉完了之后,济南开始大暴雨,也没想着再去逛哪里了,所以,直接火车到泰安,准备着休息一晚第二天上泰山了。

泰山(2015.09.03)

好吧,本来前一天晚上早早就睡了,就想着能早点儿出发,从山脚下开始爬,但是结果是,大家十点才起来,我等了四个小时哪,团队活动就是这一点不好,没法儿自由发挥,然后吃饭,打车,然后,本来想着从山脚下开始爬,但是结果却是,咋坐车到中天门吧,再然后,结果是咱再从中天门从索道到山顶吧,好吧,最后的结果就是只剩下我一个人了。

总共花了约一个半小时上的山顶(最高处还差了三四十级台阶,给自己留了点小遗憾,准备下次一个人晚上去爬一下下,顺便看个日出云海啥的),细节就不多说了,直接上图了,下面将会有很多很多滴图片,请注意。

有我的照片

_DSC0626.jpg

_DSC0871.jpg

_DSC1079.jpg

_DSC1089.jpg

G0085103.jpg

GOPR4857.jpg

GOPR4922.jpg

GOPR4999.jpg

GOPR5016.jpg

GOPR5072.jpg

GOPR5076.jpg

GOPR5077.jpg

GOPR5081.jpg

GOPR5098.jpg

GOPR5106.jpg

GOPR5108.jpg

GOPR5112.jpg

GOPR5125.jpg

GOPR5133.jpg

GOPR5180.jpg

泰山上的石碑

_DSC0675.jpg

_DSC0676.jpg

_DSC0677.jpg

_DSC0678.jpg

_DSC0682.jpg

_DSC0683.jpg

_DSC0684.jpg

_DSC0685.jpg

_DSC0688.jpg

_DSC0693.jpg

_DSC0694.jpg

_DSC0700.jpg

GOPR4855.jpg

GOPR4858.jpg

泰山的风景

只有景

_DSC0643.jpg

_DSC0664.jpg

_DSC0668.jpg

_DSC0669.jpg

_DSC0697.jpg

_DSC0706.jpg

_DSC0710.jpg

_DSC0714.jpg

_DSC0716.jpg

_DSC0720.jpg

_DSC0724.jpg

_DSC0731.jpg

_DSC0742.jpg

_DSC0749.jpg

_DSC0751.jpg

_DSC0754.jpg

_DSC0771.jpg

_DSC0781.jpg

_DSC0783.jpg

_DSC0789.jpg

_DSC0790.jpg

_DSC0791.jpg

_DSC0793.jpg

_DSC0799.jpg

_DSC0800.jpg

_DSC0801.jpg

_DSC0812.jpg

_DSC0817.jpg

_DSC0818.jpg

_DSC0819.jpg

_DSC0820.jpg

_DSC0821.jpg

_DSC0824.jpg

_DSC0825.jpg

_DSC0826.jpg

_DSC0828.jpg

_DSC0830.jpg

_DSC0834.jpg

_DSC0840.jpg

_DSC0851.jpg

_DSC0857.jpg

_DSC0865.jpg

_DSC0868.jpg

_DSC0873.jpg

_DSC0874.jpg

_DSC0875.jpg

_DSC0876.jpg

_DSC0878.jpg

_DSC0881.jpg

_DSC0884.jpg

_DSC0894.jpg

_DSC0899.jpg

_DSC0900.jpg

_DSC0901.jpg

_DSC0903.jpg

_DSC0947.jpg

_DSC0958.jpg

_DSC0981.jpg

_DSC0987.jpg

_DSC0988.jpg

_DSC0992.jpg

_DSC1030.jpg

_DSC1032.jpg

_DSC1036.jpg

_DSC1047.jpg

_DSC1053.jpg

_DSC1054.jpg

_DSC1071.jpg

_DSC1083.jpg

_DSC1097.jpg

_DSC1099.jpg

GOPR4996.jpg

GOPR5033.jpg

GOPR5085.jpg

GOPR5105.jpg

GOPR5111.jpg

GOPR5124.jpg

GOPR5127.jpg

遇见一位捡水瓶的老人

_DSC1068.jpg

_DSC1069.jpg

齐鲁菜必吃“煎饼卷大葱”

煎饼卷大葱

煎饼卷大葱

煎饼卷大葱

煎饼卷大葱

当然还少不了泰山啤酒

煎饼卷大葱·泰山啤酒

煎饼卷大葱价格

煎饼卷大葱

煎饼卷大葱

煎饼卷大葱

煎饼卷大葱

煎饼卷大葱

煎饼卷大葱

煎饼卷大葱

煎饼卷大葱

煎饼卷大葱

煎饼卷大葱

煎饼卷大葱

煎饼卷大葱

煎饼卷大葱

煎饼卷大葱

另外,还有第二天我们吃的炒鸡

炒鸡

别我价格,你都是能接受的。如果不能接受,那也是你接受不了它的便宜。

其它

关于住宿,强烈建议,泰安格瑞特商务宾馆,从泰山站或者泰安站打的需要20分钟左右,10公里的距离,远离市区,房间大,干净,有WIFI,手机App开门,便宜,我们住的是最顶级的豪华大床房,也就128一间,两米多的床哪……这要是在泰山顶,2平米的房间就得800+,还是淡季价。

吾本布衣,湘西辰溪人士,姓潘,名韬,农村户口,年方二十有七,家有一妻一女。

鄙人不慧、禀性淳朴、内外兼修、将有志于世。大方无隅、大道无形、行者无疆、优雅无像、上善若水,厚德载物。好行摄于天地,喜深宅于蜗居,相貌奇特但奇而不突,相随心生,心随神动,性随势行,与时俱进,与时偕行。

生来奋斗拼搏于盛世,力求名扬于四海。韶年之时,便立下雄心壮志,三更灯火,闻鸡起舞欲以勤奋之功修身增识;弱冠之年,突发奇志:“麒麟岂是池中物,一遇风云便为龙”。然纵有凌云万丈志,却无通天晓地之才。几经坎坷几多沉浮,自感本领恐慌,时光飞逝不待人,一日难再晨。临近而立之年,却是三十功名尘与土,八千里路云和月。白发悄来临,少年已不在。然雄心未泯,壮志仍在,激情依旧,不减当年。若凭一人之力,微乎其微,聚众人之力,泰山可移。善假于物者,足可纵横捭阖,席卷天下,包举宇内,囊括四海,并吞八荒,可上九天揽明月,在下五洋去捉鳖,扶摇直上九万里,飞流直下三千尺,鹰击长空,鱼翔浅底,极尽善用之奥妙,痛快淋漓,信手捏来,游刃有余!

天行健,君子以自强不息。地势坤,君子以厚德载物!有感于同僚垂青,实乃三生有幸,临书增恩,不知所言。


装B完毕,本人已于2015年8月25日正式加入宜信了,也不知道这公司能要我多久,如果不出意外的话,这次应该是你若不弃,我必生死相依,但这最后的结局是啥样儿的了,Who knows?

继续干前端,公司气氛不错,办公地点位于朗园,记得第一次来这边的时候就被这里面浓浓的酱香味儿给吸引,来了之后发现,确实是一个打酱油的好地方,每天十一点到公司,休息片刻,平复一下自己一天激动的心情之后,去下面吃个中饭,然后回来休息片刻,待午餐消化消化,再回到一楼抽支烟,就可以开始一天的工作了。

大约五点至六点,进点餐系统里面点一个美味的晚餐,七点就送到了,吃完晚餐之后,休息片刻,待晚饭消化消化,八点多,回家睡觉……

其它的,不多说了。

今天Git服务器已经快不行了,空间又要满了,而且常常502,另外两台服务器也快到期了,那两台还是去年创业的时候买的,后来一直也没有怎么使用,所以想想,还是都不要了吧,今天就买了一台新的服务器,配置没有以前的高,但是足够一年的使用了,以前用的都是 CentOS 6.5,CentOS 7 也已经出来很长一段时间了,QCloud也有这个版本的镜像,反正,迟早是要升级到新版本的,所以,这次就索性直接使用了 CentOS 7,但是,以前熟悉的工具、命令似乎都没有用了,所以,一切又得重新来,自己不是一个善于记住事情的人,所以,Get一点新技能,就把这点记下来吧。

更新与升级

每一次登录服务器,尤其是要安装新软件的时候,总是习惯于先更新一下,我是一个一直使用最新版本的软件的人。

yum update

或者使用

yum upgrade

挂载数据盘

这是要做的第二件事情了,系统安装好了之后,默认是只有一个系统盘的,就20G,我们需要先将数据盘挂载至服务器上。

# 创建分区
fdisk /dev/vdb
# 格式化分区
mkfs.ext3 /dev/vdb1
# 挂载分区
echo '/dev/vdb1 /home ext3 defaults 0 0' >> /etc/fstab
mount -a

个性化设置

设置主机名

主机名是肯定需要设置的,要不然,服务器一多,登录上了之后,都不知道哪个是哪个了。

hostnamectl

hostnamectl 命令用于管理系统的主机名,这里面需要知道几个概念,在CentOS或RHEL中,有三种定义的主机名:

  1. 静态的(static):“静态”主机名也称为内核主机名,是系统在启动时从/etc/hostname自动初始化的主机名;
  2. 瞬态的(transient):“瞬态”主机名是在系统运行时临时分配的主机名,例如,通过DHCP或mDNS服务器分配;
  3. 灵活的(pretty)

静态主机名和瞬态主机名都遵从作为互联网域名同样的字符限制规则,“灵活”主机名则允许使用自由形式(包括特殊/空白字符)的主机名,以展示给终端用户(如 Tao's Computer)。

[root@vicpan ~]# hostnamectl status
   Static hostname: onmr.com
         Icon name: computer-vm
           Chassis: vm
        Machine ID: 3f57f163dfaf1ec9ed891518d1d2fafe
           Boot ID: a7316ea3bd284d6da2ecc6cfc3bfe959
    Virtualization: kvm
  Operating System: CentOS Linux 7 (Core)
       CPE OS Name: cpe:/o:centos:centos:7
            Kernel: Linux 3.10.0-123.el7.x86_64
      Architecture: x86_64

hostnamectl status 可以查看系统的主机名状态,如果想只查看静态、瞬态或灵活主机名,分别使用“--static”,“--transient”或“--pretty”选项。

hostnamectl set-hostname onmr.com

hostnamectl set-hostname 命令可以设置主机名,若不特别指定,该命令会修改所有主机名,一旦修改了静态主机名,/etc/hostname 将被自动更新。然而,/etc/hosts 不会更新以保存所做的修改,所以你需要手动更新/etc/hosts。

若要指定某一种主机名,则需要添加相应的参数:

hostnamectl --static set-hostname <host-name>

设置 DNS 服务器

vi /etc/resolv.conf  

nameserver 10.138.224.65
nameserver 10.182.20.26
nameserver 10.182.24.12
options timeout:1 rotate 

安装 Nginx + PHP-FPM 环境

安装 Nginx

安装官方的 Red Hat/CentOS 预编译包,为了追加 nginx 的 yum 仓库,需要创建一个文件 /etc/yum.repos.d/nginx.repo,并将下面的其中一个内容复制进去:

[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=0
priority=10
enabled=1

七月份的下半个月,有幸做了奔驰 Smart 2015年新官网,包括手机端和PC端的宣传页,地址:

这里,为了证明这个是一个事实,我还特意的留存了两张截图:

QQ20150802-1@2x.jpg

QQ20150802-2@2x.jpg

这里只想说明这么几个问题:

  1. 这东西确实是我做了,而且是那种创意95天,开发两天,三天测试,100天的时候就要上线的;
  2. 奥美负责创意,把项目外包,结果就是,丫的居然告诉我,不合格,准备直接把代码转交给另外一个团队,意思就是这项目跟我没半毛钱关系;
  3. 这是纯粹对劳苦码农工作成果的蔑视,本人发誓,老子有生之年,再也不会跟任何外企扯上关系,即使被国人坑,至少不流外人田;
  4. 大家以后跟这两公司打交道,小心……切记

抵制奥美,从我做起

抵制奔驰,人人有责

doraemoney-loan-app.jpg

6月14号,和另外两个同事商量着不能再像最近这几个月这样了,似乎每一个公司的产品经理与码农们都是死对头,我也没有逃出这个怪圈,每天在对产品的“精雕细琢”中,让我对产品越发的反感,不经意间,看了看自己的 Git Commits List,好长啊,每天都有好多,然后就想着看看自己的干了些什么,突然之间,发现这就是一个循环啊,基本上是下面这样的:

for var keepGoing = true; keepGoing  {
    // 4B中
}

不行啊,我们得自己整一个,但是不能在上班时间整,因为这是一个只有我们参与的事情,而且也不希望他人对我们的指指点点,所以,决定每天的空余时间抽出几个小时,计划着一个星期之内整一个新的东西出来,恩,是的,App,最后还是花了我们3个人十天的时间。

这还是一个借款给有需要的人的App,没有风控模型,但是我们有完善的催债模型和真实性模型,我们只做一件事情,让借款给你的人更快的相信你在按时还款,所以,我们选择了通讯录、通话记录、地理位置、手机型号等这些通过一个App能快速获取到的数据。

然后我们开始了规划,简单的设计,接口的定义以及数据结构的定义,这花了一天的时间,我们按着花了三天时间把整个系统开发完了,也就是所有的功能都有了,接着花了两天时间把所有的功能接口串连上,最后再连了四天的时间测试、调试与Bug修复,Ok,一个全新的App就这么出来了。

技术使用的是:

  • Java
  • Ionic
  • Cordova
  • 一些必要的插件

Java

选择Java的原因很简单,也很纯粹,我们的核心业务系统就是Java的,为了能更快速的开发,我们还是直接使用Java,这样很多接口的代码可以直接复制过来改改就能使用,这为我们节省了很多开发的时间。

Ionic

这个不用想,简单的App开发中的神器,有了这个东西,即使我对App开发一无所知,我也能仅使用我自己会的前端技术实现一个完善的App。

Cordova

这为我们的App兼容到各种平台 iOA/Andoird等提供支持。

我是怎么做的

关于本地的数据存储

因为数据量很少,所以直接使用了 LocalStorage,我自己写了一个 AngularJSLocalStorage 的数据绑定的 Angular Module,代码如下:

/**
 * 本地存储
 */
app.factory('$storage', [
  '$rootScope',
  '$window',
  function(
      $rootScope,
      $window
  ){
    // #9: Assign a placeholder object if Web Storage is unavailable to prevent breaking the entire AngularJS app
    var webStorage = $window['localStorage'] || (console.warn('This browser does not support Web Storage!'), {}),
        storage = {
          $default: function(items) {
            for (var k in items) {
              angular.isDefined(storage[k]) || (storage[k] = items[k]);
            }

            return storage;
          },
          $reset: function(items) {
            for (var k in storage) {
              '$' === k[0] || delete storage[k];
            }

            return storage.$default(items);
          }
        },
        _laststorage,
        _debounce;

    for (var i = 0, k; i < webStorage.length; i++) {
      // #8, #10: `webStorage.key(i)` may be an empty string (or throw an exception in IE9 if `webStorage` is empty)

      (k = webStorage.key(i)) && 'storage-' === k.slice(0, 8) && (storage[k.slice(8)] = angular.fromJson(webStorage.getItem(k)));
    }

    _laststorage = angular.copy(storage);

    $rootScope.$watch(function() {
      _debounce || (_debounce = setTimeout(function() {
        _debounce = null;

        if (!angular.equals(storage, _laststorage)) {
          angular.forEach(storage, function(v, k) {
            angular.isDefined(v) && '$' !== k[0] && webStorage.setItem('storage-' + k, angular.toJson(v));

            delete _laststorage[k];
          });

          for (var k in _laststorage) {
            webStorage.removeItem('storage-' + k);
          }

          _laststorage = angular.copy(storage);
        }
      }, 100));
    });

    // #6: Use `$window.addEventListener` instead of `angular.element` to avoid the jQuery-specific `event.originalEvent`
    'localStorage' === 'localStorage' && $window.addEventListener && $window.addEventListener('storage', function(event) {
      if ('storage-' === event.key.slice(0, 10)) {
        event.newValue ? storage[event.key.slice(10)] = angular.fromJson(event.newValue) : delete storage[event.key.slice(10)];

        _laststorage = angular.copy(storage);

        $rootScope.$apply();
      }
    });

    return storage;
  }
]);

使用起来很简单:

$storage.token = 'TOKEN_STRING'; // 这就会在localStorage 中存储一个 `key` 为 `storage-token` 而 `value` 为 `TOKEN_STRING` 的键值对,这是一个单向存储的过程,也就是我们再手工修改 `localStorage` 里面的值是没有用的,`100ms` 之后就会被 `$storage.token` 的值覆盖,这是一个更新存储的时间。

数据请求

因为我们这边的接口走的不是 AngularJS 的默认请求方式,数据结构为类似表单提交,所以,我还修改了 Angular 中的 $http,转换对象为 x-www-form-urlencoded 序列代的字符串:

/**
 * 配置
 */
app.config([
  '$ionicConfigProvider',
  '$logProvider',
  '$httpProvider',
  function(
      $ionicConfigProvider,
      $logProvider,
      $httpProvider
  ) {
    // .. 其它代码
    // 开启日志
    $logProvider.debugEnabled(true);

    /**
     * 服务器接口端要求在发起请求时,同时发送 Content-Type 头信息,且其值必须为: application/x-www-form-urlencoded
     * 可选添加字符编码,在此处我默认将编码设置为 utf-8
     *
     * @type {string}
     */

    $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';
    $httpProvider.defaults.headers.put['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';

    /**
     * 请求只接受服务器端返回 JSON 数据
     * @type {string}
     */
    $httpProvider.defaults.headers.post['Accept'] = 'application/json';

    /**
     * AngularJS 对默认提交的数据结构为 json 格式的,但是我们NiuBilitity的服务器端不能解析 JSON 数据,所以
     * 我们经 x-www-form-urlencoded 的方式提交,此处将对数据进行封装为 foo=bar&bar=other 的方式
     * @type {*[]}
     */
    $httpProvider.defaults.transformRequest = [function(data) {
      /**
       * 转换对象为 x-www-form-urlencoded 序列代的字符串
       * @param {Object} obj
       * @return {String}
       */
      var param = function(obj) {
        var query = '';
        var name, value, fullSubName, subName, subValue, innerObj, i;

        for (name in obj) {
          value = obj[name];

          if (value instanceof Array) {
            for (i = 0; i < value.length; ++i) {
              subValue = value[i];
              fullSubName = name + '[' + i + ']';
              innerObj = {};
              innerObj[fullSubName] = subValue;
              query += param(innerObj) + '&';
            }
          } else if (value instanceof Object) {
            for (subName in value) {
              subValue = value[subName];
              fullSubName = name + '[' + subName + ']';
              innerObj = {};
              innerObj[fullSubName] = subValue;
              query += param(innerObj) + '&';
            }
          } else if (value !== undefined && value !== null) {
            query += encodeURIComponent(name) + '='
                + encodeURIComponent(value) + '&';
          }
        }

        return query.length ? query.substr(0, query.length - 1) : query;
      };

      return angular.isObject(data) && String(data) !== '[object File]'
          ? param(data)
          : data;
    }];

  }
]);

JSON 请求数据结构

我们的数据结构是下面这样的:

Request

{
  "apiVersion" : "0.0.1",
  "token" : "TOKEN_STRING",
  "requestId" : "ID_STRING",
  "data" : {
    // Data goes here
  }
}

Response

{
  "apiVersion" : "0.0.1",
  "data" : {},
  "error" : {
    "code" : ERROR_CODE_NUMBER,
    "message" : "Error Message Here",
    "errors" : [
      {
        "code" : 0,
        "message" : "",
        "location" : ""
      }
    ]
  }
}

说明

在上面的这些数据结构中,请求的很好理解,响应的 json 结构只有三个字段, apiVersion 表示了当前请求的接口版本号, data 就是数据对象, error 则是错误对象,一般情况下,一个 error 只有 codemessage 两个值,但是有一些情况下可能会需要提供一些额外的错误信息,那么都放入了 error.errors 这个数组中。

App前端是下面这样的判断的:

  1. errornull 时,表示请求成功,此时从 data 中取数据;
  2. error 不为 null 时,表示请求失败,此时从 error 中取错误信息,而完全不管 data ,我采取的方式是直接抛弃(其实前后端已经约定了,所以不存在 error 不为 null 时,data 中还有数据的情况出现。

关于 $http

我没有直接将接口的 url 地址、$http 请求等暴露给 Controller,而是做了一层封装,我叫作为 sack(也就是 App 的名称):

app.factory('sack', [
  '$http',
  '$q',
  '$log',
  '$location',
  '$ionicPopup',
  '$storage',
  'API_VERSION',
  'API_PROTOCOL',
  'API_HOSTNAME',
  'API_URI_MAP',
  'util',
  function(
      $http,
      $q,
      $log,
      $location,
      $ionicPopup,
      $storage,
      API_VERSION,
      API_PROTOCOL,
      API_HOSTNAME,
      API_URI_MAP,
      util
  ){
    var HTTPUnknownError = {code: -1, message: '出现未知错误'};
    var HTTPAuthFaildError = {code: -1, message: '授权失败'};
    var APIPanicError = {code: -1, message: '服务器端出现未知错误'};
    var _host = API_PROTOCOL + '://' + API_HOSTNAME + '/',
        _map = API_URI_MAP,
        _apiVersion = API_VERSION,
        _token = (function(){return $storage.token;}()) ;

    setInterval(function(){
      _token = (function(){return $storage.token;}());
      //$log.info("Got Token: " + _token);
    }, 1000);

    var appendTransform = function(defaultFunc, transFunc) {
      // We can't guarantee that the default transformation is an array
      defaultFunc = angular.isArray(defaultFunc) ? defaultFunc : [defaultFunc];

      // Append the new transformation to the defaults
      return defaultFunc.concat(transFunc);
    };

    var _prepareRequestData = function(originData) {
      originData.token = _token;
      originData.apiVersion = _apiVersion;
      originData.requestId = util.getRandomUniqueRequestId();
      return originData;
    };

    var _prepareRequestJson = function(originData) {
      return angular.toJson({
        apiVersion: _apiVersion,
        token: _token,
        requestId: util.getRandomUniqueRequestId(),
        data: originData
      });
    };

    var _getUriObject = function(uon) {
      // 若传入的参数带有 _host 头
      if((typeof uon === 'string' && (uon.indexOf(_host) == 0) ) || uon === '') {
        return {
          uri: uon.replace(_host, ''),
          methods: ['post']
        };
      }

      if(typeof _map === 'undefined') {
        return {
          uri: '',
          methods: ['post']
        };
      }

      var _uon = uon.split('.'),
          _ns,
          _n;

      if(_uon.length == 1) {
        return {
          uri: '',
          methods: ['post']
        };
      }
      _ns = _uon[0];
      _n = _uon[1];

      _mod = _map[_ns];

      if(typeof _mod === 'undefined') {
        return {
          uri: '',
          methods: ['post']
        };
      }

      _uriObject = _mod[_n];

      if(typeof _uriObject === 'undefined') {
        return {
          uri: '',
          methods: ['post']
        };
      }

      return _uriObject;
    };

    var _getUri = function(uon) {
      return _getUriObject(uon).uri;
    };

    var _getUrl = function(uon) {
      return _host + _getUri(uon);
    };

    var _auth = function(uon) {
      var _uo = _getUriObject(uon),
          _authed = false;
      $log.log('Check Auth of : ' + uon);
      $log.log('Is this api need auth: ' + angular.toJson(_uo.needAuth));
      $log.log('Is check passed: ' + angular.toJson(!(!_token && _uo.needAuth)));
      $log.log('Token is: ' + _token);
      if(!_token && _uo.needAuth) {
        
        $ionicPopup.alert({
          title: '提示',
          subTitle: '您当前的登录状态已失效,请重新登录。'
        }).then(function(){
          $location.path('/sign');
        });
        
        $location.path('/sign');
      } else {
        _authed = true;
      }
      return _authed;
    };

    var get = function(uon) {
      return $http.get(_getUrl(uon));
    };

    var post = function(uon, data, headers) {
      var _url = _getUrl(uon),
          _data = _prepareRequestData(data);
      $log.info('========> POST START [ ' + uon + ' ] ========>');
      $log.log('REQUEST URL  : ' + _url);
      $log.log('REQUEST DATA : ' + angular.toJson(_data));

      return $http.post(_url, _data, {
        transformResponse: appendTransform($http.defaults.transformResponse, function(value) {
          $log.log('RECEIVED JSON : ' + angular.toJson(value));
          if(typeof value.ex != 'undefined') {
            return {
              error: APIPanicError
            };
          }
          return value;
        })
      });
    };

    var promise = function(uon, data, headers) {
      var defer = $q.defer();

      if(!_auth(uon)) {
        defer.reject(HTTPAuthFaildError);
        return defer.promise;
      }

      post(uon, data, headers).success(function(res){
        if(res.error) {
          defer.reject(res.error);
        } else {
          defer.resolve(res.data);
        }
      }).error(function(res){
        defer.reject(HTTPUnknownError);
      });
      return defer.promise;
    };

    var postJson = function(uon, data, headers) {
      var _url = _getUrl(uon),
          _json = _prepareRequestJson(data);
      $log.info('========> POST START [ ' + uon + ' ] ========>');
      $log.log('REQUEST URL  : ' + _url);
      $log.log('REQUEST JSON : ' + _json);
      return $http.post(_url, _json, {
        transformResponse: appendTransform($http.defaults.transformResponse, function(value) {
          $log.log('RECEIVED JSON : ' + angular.toJson(value));
          if(typeof value.ex != 'undefined') {
            return {
              error: APIPanicError
            };
          }
          return value;
        })
      });
    };

    var promiseJson = function(uon, data, headers) {
      var defer = $q.defer();

      if(!_auth(uon)) {
        defer.reject(HTTPAuthFaildError);
        return defer.promise;
      }

      postJson(uon, data, headers).success(function(res){
        if(res.error) {
          defer.reject(res.error);
        } else {
          defer.resolve(res.data);
        }
      }).error(function(res){
        defer.reject(HTTPUnknownError);
      });
      return defer.promise;
    };

    return {
      get: get,
      post: post,
      promise: promise,

      postJson: postJson,
      promiseJson: promiseJson,
      _auth: _auth,
      HTTPAuthFaildError: HTTPAuthFaildError
    };
  }
]);

这样里面最主要是使用一个方法: sack.promiseJson,这个方法是以 json 数据向服务器发送请求,然后返回一个 promise 的。

上面的 API_URI_MAP 的数据结构类似于下面这样的:

app.constant('API_URI_MAP', {
  user : {
    sign : {
      needAuth: false,
      uri : 'sack/user/sign.json',
      methods: [
        'post'
      ],
      params: {
        mobile: 'string', // 手机号码
        captcha: 'string' // 验证码
      }
    },
    unsign: {
      needAuth: true,
      uri: 'sack/user/unsign.json',
      methods: [
        'post'
      ],
      params: {
        token: 'string'
      }
    },
    //...
  }
  //...
});

然后,更具体的,在 Controller 中也不直接使用 sack.promiseJson 这个方法,而是使用封装好的服务进行,比如下面这个服务:

app.factory('UserService', function($rootScope, $q, $storage, API_CACHE_TIME, sack) {
  var sign = function(data) {
    return sack.promiseJson('user.sign', data);
  };

  return {
    sign: sign
  }
});

这样的好处是,我可以直接使用类似下面这样发起请求:

UserService.sign({mobile:'xxxxxxxxxxx',captcha:'000000'}).then(function(res){
  // 授权成功
}, function(err){
  // 授权失败
});

但是

好吧,又来但是了,App做完了之后,我们可爱的领导们感觉这个还可以,然后就又要开始发挥他们的各种NB的指导了,还好从一开始我们就没有使用上班时间,这使得我们有理由拒绝领导的指导,但是,公司却说了,不接受指导那就不让上,好吧,那就不上呗,这似乎惹怒了我们的领导们,所以,就直接没有跟我们通气的开始招兵买马要上App了,我瞬间就想问:

我们的战略不是说不做App么?现在怎么看到App比现在的简单就又开始做了

然后我又想到一种可能

  1. 我们把App上了,
  2. 另一个领导带招一些新人把也做了一个App
  3. 如果App还可以的话,把我们的功能直接复制过去,然后让我们的下线
  4. 然后领导又可以邀功了
  5. 如果App不可以的话,那我们是在浪费时间,把我们的下线,然后……

反正,似乎都跟我没半毛钱关系了,除非这个App运营的不好。

Looking for something else? Take a look at the awesome collection of other awesome lists.

安装基本系统与依赖包

安装 Gitlab 依赖的工具

yum -y update
yum -y groupinstall 'Development Tools'
yum -y install readline readline-devel ncurses-devel gdbm-devel glibc-devel tcl-devel openssl-devel curl-devel expat-devel db4-devel byacc sqlite-devel libyaml libyaml-devel libffi libffi-devel libxml2 libxml2-devel libxslt libxslt-devel libicu libicu-devel system-config-firewall-tui git redis ruby sudo wget crontabs logwatch logrotate perl-Time-HiRes

安装 Redis

访问 http://www.redis.io/download,下载 Redis 源代码。

wget http://download.redis.io/releases/redis-3.0.0.tar.gz
tar zxvf redis-3.0.0.tar.gz
cd redis-3.0.0
make

若在编译过程中出错,则可以执行下面的命令:

sudo make test

安装:

sudo make install
sudo ./utils/install_server.sh

配置

创建 /etc/init.d/redis 并使用下面的代码作为启动脚本。

添加如下内容:

###########################
PATH=/usr/local/bin:/sbin:/usr/bin:/bin
 
REDISPORT=6379
EXEC=/usr/local/bin/redis-server
REDIS_CLI=/usr/local/bin/redis-cli
 
PIDFILE=/var/run/redis.pid
CONF="/etc/redis/6379.conf"
 
case "$1" in
    start)
        if [ -f $PIDFILE ]
        then
                echo "$PIDFILE exists, process is already running or crashed"
        else
                echo "Starting Redis server..."
                $EXEC $CONF
        fi
        if [ "$?"="0" ]
        then
              echo "Redis is running..."
        fi
        ;;
    stop)
        if [ ! -f $PIDFILE ]
        then
                echo "$PIDFILE does not exist, process is not running"
        else
                PID=$(cat $PIDFILE)
                echo "Stopping ..."
                $REDIS_CLI -p $REDISPORT SHUTDOWN
                while [ -x ${PIDFILE} ]
               do
                    echo "Waiting for Redis to shutdown ..."
                    sleep 1
                done
                echo "Redis stopped"
        fi
        ;;
   restart|force-reload)
        ${0} stop
        ${0} start
        ;;
  *)
    echo "Usage: /etc/init.d/redis {start|stop|restart|force-reload}" >&2
        exit 1
esac
##############################

保存后,添加可执行权限:

sudo chmod +x /etc/init.d/redis

确保 redis 能随系统启动:

vi /etc/rc.d/rc.local

在文件末尾添加下面这行:

service redis start

然后使用上面同样的命令启动 redis 服务:

service redis start

安装邮件服务器

yum -y install postfix

安装Git

先删除系统中原有的老版本 git

yum -y remove git
yum install zlib-devel perl-CPAN gettext curl-devel expat-devel gettext-devel openssl-devel

从官方网站下载源代码进行:

curl --progress https://www.kernel.org/pub/software/scm/git/git-2.4.0.tar.gz | tar xz
cd git-2.4.0/
./configure
make
make prefix=/usr/local install

然后使用下面这个命令检测安装是否有效:

which git

安装 ruby

如果 ruby 的版本低于 2.0 的话,则需要重新安装 ruby

cd ~
curl --progress ftp://ftp.ruby-lang.org/pub/ruby/2.2/ruby-2.2.2.tar.gz | tar xz
cd ruby-2.2.2
./configure --disable-install-rdoc
make
make prefix=/usr/local install

为 Gitlab 添加系统用户

adduser --system --shell /bin/bash --comment 'GitLab' --create-home --home-dir /home/git/ git

为了包含/usr/local/bin到git用户的$PATH,一个方法是编辑超级用户文件。以管理员身份运行:

visudo

然后搜索:

Defaults    secure_path = /sbin:/bin:/usr/sbin:/usr/bin

将其改成:

Defaults    secure_path = /sbin:/bin:/usr/sbin:/usr/bin:/usr/local/bin

安装数据库

MySQL 已经不再包含在 CentOS 7 的源中,而改用了 MariaDB,先搜索 MariaDB 现有的包:

rpm -qa | grep mariadb

然后全部删除:

rpm -e --nodeps mariadb-*

然后创建 /etc/yum.repos.d/MariaDB.repo

vi /etc/yum.repos.d/MariaDB.repo

将以下内容添加至该文件中:

# MariaDB 10.0 CentOS repository list - created 2015-05-04 19:16 UTC
# http://mariadb.org/mariadb/repositories/
[mariadb]
name = MariaDB
baseurl = http://yum.mariadb.org/10.0/centos7-amd64
gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
gpgcheck=1

然后运行下面命令安装 MariaDB 10.0

sudo yum install MariaDB-server MariaDB-client

然后启动 MariaDB 服务:

service mysql start

接着运行 mysql_secure_installation

mysql_secure_installation

登录 MariaDB 并创建相应的数据库用户与数据库:

mysql -uroot -p
CREATE USER 'git'@'localhost' IDENTIFIED BY '$password';
SET storage_engine=INNODB;
CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_unicode_ci`;
GRANT SELECT, LOCK TABLES, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER ON `gitlabhq_production`.* TO 'git'@'localhost';
\q

尝试使用新用户连接数据库:

sudo -u git -H mysql -u git -p -D gitlabhq_production
\q

安装 Gitlab

克隆源

sudo -u -git cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 7-10-stable gitlab

配置

cd /home/git/gitlab

# Copy the example GitLab config
# 复制GitLab的示例配置文件
sudo -u git -H cp config/gitlab.yml.example config/gitlab.yml

# Make sure to change "localhost" to the fully-qualified domain name of your host serving GitLab where necessary
# 确保修改“localhost”为你的GitLab主机的FQDN
#
# If you want to use https make sure that you set `https` to `true`. See #using-https for all necessary details.
# 如果你想要使用https确保你设置了`https`为`true`。具体必要的细节参见#using-https
#
# If you installed Git from source, change the git bin_path to /usr/local/bin/git
# 如果你从源代码安装了Git,修改git的bin_path为/usr/local/bin/git
sudo -u git -H editor config/gitlab.yml

# Make sure GitLab can write to the log/ and tmp/ directories
# 确保GitLab可以写入log/和temp/目录
chown -R git {log,tmp}
chmod -R u+rwX  {log,tmp}

# Create directory for satellites
# 为卫星(?)创建目录
sudo -u git -H mkdir /home/git/gitlab-satellites
chmod u+rwx,g+rx,o-rwx /home/git/gitlab-satellites

# Make sure GitLab can write to the tmp/pids/ and tmp/sockets/ directories
# 确保GitLab可以写入tmp/pids/和temp/sockets/目录
chmod -R u+rwX  tmp/{pids,sockets}

# Make sure GitLab can write to the public/uploads/ directory
# 确保GitLab可以写入public/uploads/目录
chmod -R u+rwX  public/uploads

# Copy the example Unicorn config
# 复制Unicorn的示例配置文件
sudo -u git -H cp config/unicorn.rb.example config/unicorn.rb

# Enable cluster mode if you expect to have a high load instance
# Ex. change amount of workers to 3 for 2GB RAM server
# 启用集群模式如果你期望拥有一个高负载实例
# 附:修改worker的数量到3用于2GB内存的服务器
sudo -u git -H editor config/unicorn.rb

# Copy the example Rack attack config
# 复制Rack attack的示例配置文件
sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers/rack_attack.rb

# Configure Git global settings for git user, useful when editing via web
# Edit user.email according to what is set in config/gitlab.yml
# 为git用户配置Git全局设定,当通过web修改时有用
# 修改user.email根据config/gitlab.yml中的设定
sudo -u git -H git config --global user.name "GitLab"
sudo -u git -H git config --global user.email "gitlab@localhost"
sudo -u git -H git config --global core.autocrlf input

数据库配置

# MySQL only:
# 仅限MySQL:
sudo -u git cp config/database.yml.mysql config/database.yml
 
# MySQL and remote PostgreSQL only:
# Update username/password in config/database.yml.
# You only need to adapt the production settings (first part).
# If you followed the database guide then please do as follows:
# Change 'secure password' with the value you have given to $password
# You can keep the double quotes around the password
# 仅限MySQL和远程PostgreSQL:
# 在config/database.yml中更新用户名/密码;
# 你只需要适配生产设定(第一部分);
# 如果你跟从数据库向导,请按以下操作:
# 修改'secure password'使用你刚才设定的$password;
# 你可以保留密码两端的双引号。
sudo -u git -H editor config/database.yml
 
# PostgreSQL and MySQL:
# Make config/database.yml readable to git only
# PostgreSQL和MySQL:
# 设置config/database.yml仅对git可读。
sudo -u git -H chmod o-rwx config/database.yml

安装 Gems

cd /home/git/gitlab
 
# For users from China mainland only
# 仅限中国大陆用户
nano /home/git/gitlab/Gemfile
source "http://ruby.taobao.org" // 原始 source "https://rubygems.org/"
 
# For MySQL (note, the option says "without ... postgres")
sudo -u git -H bundle install --deployment --without development test postgres aws

Install GitLab shell

安装GitLab Shell

GitLab Shell是一个专门为GitLab开发的SSH访问和源管理软件。

# Go to the Gitlab installation folder:
# 转到GitLab安装目录:
cd /home/git/gitlab
 
# For users from China mainland only
# 仅限中国大陆用户
nano /home/git/gitlab/Gemfile
source "http://ruby.taobao.org" // 原始 source "https://rubygems.org/"
 
# Run the installation task for gitlab-shell (replace `REDIS_URL` if needed):
# 运行gitlab-shell的安装任务(替换`REDIS_URL`如果有需要的话):
sudo -u git -H bundle exec rake gitlab:shell:install[v1.9.6] REDIS_URL=redis://localhost:6379 RAILS_ENV=production
 
# By default, the gitlab-shell config is generated from your main gitlab config.
# 默认的,gitlab-shell的配置文件是由你的gitlab主配置文件生成的。
#
# Note: When using GitLab with HTTPS please change the following:
# - Provide paths to the certificates under `ca_file` and `ca_path options.
# - The `gitlab_url` option must point to the https endpoint of GitLab.
# - In case you are using self signed certificate set `self_signed_cert` to `true`.
# See #using-https for all necessary details.
# 提示:当通过HTTPS使用GitLab时,请做出如下更改:
# - 提供证书的路径在`ca_file`和`ca_path`选项;
# - `gitlab_url`选项必须指向GitLab的https端点;
# - 如果你使用自签名的证书,设置`self-signed_cert`为`true`。
# 所有必需的具体细节参见#using-https
#
# You can review (and modify) it as follows:
# 你可以检查(并修改该)通过以下方法:
sudo -u git -H editor /home/git/gitlab-shell/config.yml
 
# Ensure the correct SELinux contexts are set
# Read http://wiki.centos.org/HowTos/Network/SecuringSSH
# 确保正确的SELinux上下文被设置
# 阅读http://wiki.centos.org/HowTos/Network/SecuringSSH
restorecon -Rv /home/git/.ssh

初始化数据库和激活高级功能

sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production
# Type 'yes' to create the database tables.
# When done you see 'Administrator account created:'

提示:你可以设置管理员密码通过在环境变量GITLAB_ROOT_PASSWORD中提供,例如:

sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production GITLAB_ROOT_PASSWORD=newpassword

安装初始化脚本

下载初始化脚本(将放在/etc/init.d/gitlab):

sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
chmod +x /etc/init.d/gitlab
chkconfig --add gitlab

设置GitLab开机启动:

chkconfig gitlab on

设置日志翻转

cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab

检查应用状态

sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production

编译静态文件

sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production

启动实例

/etc/init.d/gitlab start

今天逛 jetbrains,本来是想看看 WebStorm 以及 PHPStorm 是否有更新,然后突然发现,居然出了 CLion了,果断下载试用了一翻,由于我只是 C/C++ 的 Hello World 党,所以,也不法太过于深入的使用,下面这个只是一个 Hello World 程序的截图,现在也几乎所有的工作都可以在 Jet Brains 家族产品里完成了,我就在想,如果哪天我有钱了,我一定把这所有的产品都买了,其实这也不失为一种销售方式,先让程序员使用,当程序员有钱了再付费,至少有一个,我现在是不会再考虑其它的产品了。

QQ20150426-1@2x.jpg

重点来了,如果破解

工具还没有找到,但是可以使用未正常工具:

  1. https://github.com/rover12421/JetbrainsPatchKeygen Down到本地,解压
  2. 在解压之后的目录下面执行:

    ./gradlew fatjar
    
  3. 然后:

     java -jar build/libs/JetbrainsPatchKeygen-1.0.jar 
     Jetbrains's Product Patch or Keygen.
     Crack by Rover12421.
     Http://Www.Rover12421.Com
     Please support genuine(https://www.jetbrains.com).
    
     Please select product:
     1. RubyMine
     2. PyCharm
     3. WebStorm
     4. PhpStorm
     5. AppCode
     6. Clion
     7. Idea
     6
     Please enter your username :
     pantao
     Clion need patch
     Please enter your Clion install path or clion.jar path :
     /Applications/CLion.app/Contents/lib/clion.jar
     Find Key. Patch...
     ---------------------------------------
     ===== LICENSE BEGIN =====
     5796-D28740T
     0000088K5JIFTVoEUDCBQv1t1xs4yT
     1wVs7vpdY5qzSL9xH66zj6nD2Z2w64
     qCB1oiIHnpWOKbrjIYIKMW7qpx0fe8
     ===== LICENSE END =====
    

好吧,不好意思了,我把 CLion 的注册码了也给暴露出来了,也看到了没,可以破解 RubyMine、PyCharm、WebStorm、PhpStorm、AppCode、Clion、Idea共计七个产品。

JetbrainsPatchKeygen.zip

基本概念

  • 文档是 MongoDB 中数据的基本单元,非常类似于关系型数据库中的行,但更具有表现力;
  • 集合 Collection 可以看作是一个动态模式(Dynamic Schema)的表;
  • MongoDB 的一个实例可以拥有多个相互独立的数据库 ( Database),每一个数据库都拥有自己的集合;
  • 每一个文档都有一个特殊的键 _id ,这个键在文档所属的集合中是唯一的;
  • MongoDB 自带了一个简单但功能强大的 JavaScript Shell ,可用于管理 MongoDB 的实例或数据操作。

文档

文档是 MongoDB 的核心概念,文档就是键值对的有序集,下面的即是以 JavaScript 语言表现的一份文档:

{
    "greeting" : "Hello, world!"
}

在绝大多数情况下,文档的键是字符串(除了少数例外),键可以使用任意的UTF-8 字符。

  • 键不能含有 \0 (空字符),这个字符用于表示键的结尾;
  • .$ 具有特殊意义,只能在特定环境下使用,通常这两个字符是被保留的,如果使用不当的话,驱动程序会有提示。

MongoDB 不但区分类型,还区分大小写,下面的两个文档是不同的:

{
    "foo" : "bar"
}

与:

{
    "Foo" : "bar"
}

另一个重要的事项是, MongoDB 的文档不能有重复的键,例如:

{
    "greeting" : "Hello, world!",
    "greeting" : "Hello, MongoDB!"
}

文档中的键/值对是有序的:

{
    "x" : 1,
    "y" : 2
}

与:

{
    "y" : 2,
    "x" : 1
}

是不同的。

集合

集合就是一组文档。如果将 MongoDB 中的一个文档比喻为关系型数据库中的一行,那么一个集合就相当于一张表。

动态模式

集合是动态模式的,这意味着一个集合里面的文档可是各式各样的,如何下面两个文档就可以出现一个集合里面:

{
    "greeting" : "Hello, world!"
},
{
    "foo" : 5
}

上面的两个文档,除了值不同外,键也不同,但是不建议像上面那样做,同样的文档,我们一般还是放在一个特定的集合里面更好,不管是速度、效率还是结构上来讲,都要更好。

命名

集合使用名称进行标识,集合名称可以是满足下列条件的任意 UTF-8 字符串:

  • 集合名不能是空字符串("");
  • 集合名称不能包含 \0 字符(空字符),这个字符表示集合名的结束;
  • 集合名不能以 system. 开头,这是为系统集合保留的前缀,例如: system.users 这个集合保存着数据库的用户信息,而 system.namespaces 集合保存着所有数据库集合的信息;
  • 用户创建的集合不能在集合名中包含保留字符 $ ,因为某些系统生成的集合中饮食 $

子集合

组织集合的一种惯例是使用 . 分隔不同命名空间的了集合,比如一个具有博客功能的应用可能包含两个集合,分别是 blog.postsblog.authors

数据库

在 MongoDB 中,多个文档组成集合,而多个集合可以组成数据库,一个 MongoDB 可以承载我个数据库,每个数据库拥有 0 个或者多个集合,每个数据库都有独立的权限,即便是在磁盘上,不同的数据库也放置在不同的文件中。

数据库通过名称来标识,这点与集合类似,数据库名可以是满足以下条件的任意 UTF-8 字符串:

  • 不能是空字符串 ""
  • 不能含有 /\."*<>:|?$(一个空格)、\0(空字符);
  • 数据库名区分大小写,即使在不区分大小写的文件系统中也是如此,简单起见,所有的数据库均为小写;
  • 数据库名最多为64个字节。

在这里我们需要记住一点,数据库名最终会变成文件系统中的文件,而数据库名就是相应的文件名,这是数据库名有如此多限制的原因。

另外,有一些数据库名是保留的,可以直接访问这些有特殊语言的数据库,如下:

  • admin

    从身份验证的角度来讲,这是 root 数据库,如果有一个用户添加到这个数据库,则这个用户将拥有所有数据库的权限;

  • local

    这个数据库永远都不可以复制,且一台服务器上所有的本地集合都可以存储在这个数据训中;

  • config

    MongoDB 用户分片设置时,分片信息会存储在 config 数据库中。

把数据库名称添加到集合名称前,得到集合的完全限定名,即 命名空间 namespace,命名空间的长度不得超过 121 字节,而实际应用中,应该小于100字节。

启动 MongoDB

通常,MongoD 作为网络服务器来运行,客户端可连接到该服务器并执行操作。要安装 MongoDB ,可以从官方网站下载相应的适合你系统的版本(http://www.mongodb.org/downloads)。下载完成之后,解压,并把解压之后得到的文件夹复制或者移到至最终你想安装的目录即可。

我将其安装在了 /Users/pantao/Workspace/MongoDB 这个目录,同时,我将 MongoDBbin 目录加入了PATH变量中:

vi ~/.bash_profile

加入如下一行:

export PATH="/Users/pantao/Workspace/MongoDB/bin:$PATH"

保存之后,运行:

source ~/.bash_profile

这个时候,我就可以直接在任何地方启动 MongoDB 或者进行相关的数据库操作了,使用 mongod 命名启动服务器:

pantaodeMacBook-Pro:MongoDB pantao$ mongod
2015-04-21T10:00:06.302+0800 I STORAGE  [initandlisten] exception in initAndListen: 29 Data directory /data/db not found., terminating
2015-04-21T10:00:06.302+0800 I CONTROL  [initandlisten] dbexit:  rc: 100

出现上面这个错误是因为 /data/db 目录不存在,若启动时,不指定任何参数, MongoDB 会默认使用 /data/db 目录存储数据,我们可以使用 --dbpath 来指定其它的路径,比如我使用的是下面这样的命令启动的:

mongod --dbpath /Users/pantao/Workspace/MongoDB/db

上面这个是我的工作目录,直接将 MongoDB 的程序和数据库放在一起,我方便学习管理。

MongoDB Shell

MongoDB 自带有 JavaScript Shell ,可以Shell 中使用命令行与 MongoDB 实例交互,Shell非常有用,通过它可以执行管理操作,检查运行实例,亦或是做其它尝试。

运行 shell

使用 mongo 命令启动 shell

pantaodeMacBook-Pro:MongoDB pantao$ mongo
MongoDB shell version: 3.0.2
connecting to: test
Welcome to the MongoDB shell.
For interactive help, type "help".
For more comprehensive documentation, see
    http://docs.mongodb.org/
Questions? Try the support group
    http://groups.google.com/group/mongodb-user
Server has startup warnings: 
2015-04-21T10:03:18.997+0800 I CONTROL  [initandlisten] 
2015-04-21T10:03:18.997+0800 I CONTROL  [initandlisten] ** WARNING: soft rlimits too low. Number of files is 256, should be at least 1000
> 

启动时, shell 会打印出当前 shell 的版本号,连接到了哪个库以及一些帮助信息等,这是一个功能完备的 JavaScript 解释器,可以运行任意 JavaScript 程序,比如使用 JavaScript 标准库或者定义以及调用 JavaScript 函数等。

MongoDB 客户端

shell 是一个独立的 MongoDB客户端,启动时, shell 会连到 MongoDB 服务器的 test 数据库,并将数据库连接赋值给合局变量 db ,这个变量是通过 shell访问 MongoDB 的主要入口点,可以使用 db 查看当前指向哪个数据库:

> db
test

除了JavaScript语法外,MongoDB 还提供了一些语法糖,以帮助我们更好的管理数据库,比如:

> use foobar
switched to db foobar

这个时候我们可以看到数据库已经切换到 foobar 数据库了:

> db
foobar

Shell 中的基本操作

shell 中查看查看或操作数据,会用到4个基本操作:创建读取更新删除,即 CRUD 操作;

创建

通过 db.createCollection() 函数可以先创建一个集合:

> db.createCollection("blog")
{ "ok" : 1 }

insert 可以将一个文档添加到集合中:

> post = {"title": "这是一篇文章", "content": "这是文章的内容。","date" : new Date()}
{
    "title" : "这是一篇文章",
    "content" : "这是文章的内容。",
    "date" : ISODate("2015-04-21T02:22:52.899Z")
}

这是一个有效的 MongoDB 文档,所以可以用 insert 方法将其保存到集合中。

> db.blog.insert(post)
WriteResult({ "nInserted" : 1 })

接着可以使用 find 方法查找这篇文章:

> db.blog.find()
{ "_id" : ObjectId("5535b574b705494e688e218a"), "title" : "这是一篇文章", "content" : "这是文章的内容。", "date" : ISODate("2015-04-21T02:22:52.899Z") }

可以看到我们的数据都已经完整的保存下来了,同时,MongoDB 还为我们自动生成了一个 _id 参数。

读取

findfindOne 方法可以用于查询集合里的文档:

> db.blog.findOne()
{
    "_id" : ObjectId("5535b574b705494e688e218a"),
    "title" : "这是一篇文章",
    "content" : "这是文章的内容。",
    "date" : ISODate("2015-04-21T02:22:52.899Z")
}

findfindOne 可以接受一个查询文档作为限定条件,使用 find 时,shell 会自动显示最多 20 个匹配的文档,也可以获取更多文档。

更新

使用 update 修改博客文章,它至少接受两个参数,第一个是限定条件,第二个是新文档,比如我们现在要给 post 加上评论列表:

> post.comments = []
[ ]

然后,用新版本的 post 替换标题为 《这是一篇文章》的文章:

> db.blog.update({"title":"这是一篇文章"},post)
WriteResult({ "nMatched" : 1, "nUpserted" : 0, "nModified" : 1 })
> db.blog.findOne()
{
    "_id" : ObjectId("5535b574b705494e688e218a"),
    "title" : "这是一篇文章",
    "content" : "这是文章的内容。",
    "date" : ISODate("2015-04-21T02:22:52.899Z"),
    "comments" : [ ]
}

可以看到,comments 已经更新到原来的那个 post 中去了。

删除

使用 remove 可以删除集合中的文档,若没有任何限定参数,它将删除集合中的所有数据,也可以像下面这样,删除标题为《这是一篇文章》的文章:

> db.blog.remove({"title":"这是一篇文章"})
WriteResult({ "nRemoved" : 1 })
> db.blog.find()
> 

数据类型

基本的数据类型

MongoDB 的文档与 JavaScript 中的对象相近,因而可以认为类似于 JavaScript 中的 JSONJSON 是一种简单的数据表示方式,仅有 6种数据类型,分别为 nullboolnumberstringarrayobject

MongoDB在保留 JSON 基本键/值对特性的基础上,添加了其它的一些数据类型:

null

用于表示空值或者不存在的字段:

{
    "x" : null
}

bool 布尔型

只有两个值 truefalse

{
    "x" : true,
    "y" : false
}

number 数值

shell 默认使用 64位浮点型数值,对于整型值,可使用 NumberInt 类或 NumberLong 类:

{
    "pi" : 3.14,
    "x" : 3,
    "ni" : NumberInt("3"),
    "nl" : NumberLong("3")
}

string 字符串

UTF-8 类型的字符串都可以表示为字符串类型的数据:

{
    "x" : "this is a string.",
    "y" : "这是一个NB的字符串"
}

date 日期

日期被存储 为自新纪元以来经过的毫秒数,不存储时区

{
    "x" : new Date()
}

regular expression 正则表达式

在进行查询时,我们可以直接使用正则表达式作为值,语法与 javascript 的相同:

{
    "x" : /foobar/i
}

array 数组

数据列表或数据集可以表达为数组:

{
    "a" : ["x", "y" , "z"]
}

object id 对象ID

对象ID是一个 12 字节的ID,它是文档的唯一标识:

{
    "x" : ObjectId()
}

object id 以以下方式生成:

|0|1|2|3|4|5|6|7|8|9|10|11|
|  时间戳  | 机器|PID|   计数器      |
  • 时间戳在前,对于索引效率提高有一定的作用;同时它也带了一定的时间信息,一些驱动可以从 ObjectId 获取这些信息;
  • 时间戳与机器码还有 PID 组合在一起,提供了秒级别的唯一性,机器码是机器主机名的散列值(hash);
  • PID确保了同一台主机不同进程产生的 ObjectId 的唯一性,接下来的字节
  • 最后的计数器,确保同一台机器同一个进程在同一秒内产生的 ObjectId 是不一样的,一秒钟最多允许每个进程拥有 2563 个不同的ObjectId。
自动生成 _id

若插入文档时,没有提供 _id

object 内嵌文档

文档可以嵌套其它文档,被嵌套的文档作为父文档的值:

{
    "o" : {
        "name" : "child object"
    }
}

binary data 二进制数据

任意字节的字符串,它不直直接在 shell 中使用,如果要将非 utf-8 字符保存到数据库中,二进制数据是唯一的实现方式。

javascript 代码

查询和文档中可以包括做生意 JavaScript 代码:

{
    "script" : function() { /* code goes here */ }
}

timestamps 时间戳

不同于 Date 类型的时间戳值,它是一个 64位长度的值,它是:

  • 前面的 32位为 time_t 值,(Unix 时间戳)
  • 后32位是一个在给定的时间内的自增值

使用 MongoDB shell

除了像前面那样使用 shell 连接数据库外,我们还可以在连接数据时指定服务器地址、端口号以及数据名等参数,比如:

mongo mongo-db.onmr.com:30000/ahaInsight

启动时,可以让 mongo shell 不连接任何的 mongod ,可以通过 --nodb 参数:

mongo --nodb

启动之后,我们可以在需要时执行 new Mongo(hostname) 命令就可以连接到想要连接的 mongod 了:

> conn = new Mongo("host.name:30000")
connection to host.name:30000
> db = conn.getDB("dbname")
dbname

在使用 shell 的过程中,还可以随时使用 help 命令查看帮助:

help
    db.help()                    help on db methods
    db.mycoll.help()             help on collection methods
    sh.help()                    sharding helpers
    rs.help()                    replica set helpers
    help admin                   administrative help
    help connect                 connecting to a db help
    help keys                    key shortcuts
    help misc                    misc things to know
    help mr                      mapreduce

    show dbs                     show database names
    show collections             show collections in current database
    show users                   show users in current database
    show profile                 show most recent system.profile entries with time >= 1ms
    show logs                    show the accessible logger names
    show log [name]              prints out the last segment of log in memory, 'global' is default
    use <db_name>                set current database
    db.foo.find()                list objects in collection foo
    db.foo.find( { a : 1 } )     list objects in foo where a == 1
it                           result of the last line evaluated; use to further iterate
    DBQuery.shellBatchSize = x   set default number of items to display on shell
    exit                         quit the mongo shell

使用 shell 执行脚本

除了交互式的使用 shell 外,我们还可以将命令保存在一个文件中,比如 script.js ,然后使用 mongo 命令直接执行它们,一次可以传入多个文件名,mongo shell 会依次执行传入的脚本,然后退出:

mongo script.js script1.js

如果希望指定主机和端口来运行上面的脚本,还可以这样做:

mongo --quiet host.name:30000/dbname script.js script1.js

--quiet 可以让 mongo shell不打印 MongoDB shell version... 这样的提示信息。

在交互式的命令行中,还可以使用 load() 函数加载并运行脚本:

> load("script.js")
i am a string printed by script.js
>

知道这个之后,我们可以把一些能用的函数保存到一个文件里面,然后再将他们加载进 shell 交互界面里面来:

创建一个名为 connectTo.js 的文件,内容如下:

/**
 * 链接到指定的数据库,然后将 db 指向这个链接
 */
var connectTo = function(port, dbname) {
    if (!port) {
        port = 27017;
    }

    if (!dbname) {
        dbname = "test"
    }

    db = connect("localhost:" + port + "/" + dbname)
    return db
}

然后我们进入 shell

> typeof connectTo
undefined
> load("connectTo.js")
true
> typeof connectTo
function
> 

我们可以使用脚本让能用的管理和任务自动化,比如,我们想在每一次 shell 启用时,都加载上面定义的那个函数,这个时候我们可以用到一个名为 .mongorc.js 的文件。

在自己的家目录中(不同的系统都不一样),新建一个名为 .mongorc.js 的文件,然后写入下面这些内容:

print("你好,我来自 .mongorc.js 文件")

然后,重新进入 shell

mongo
MongoDB shell version: 3.0.2
connecting to: test
你好,我来自 .mongorc.js 文件
> 

删除危险的函数

该文件一般用得最多的就是用于删除一些比较危险的 shell 辅助函数,比如删除数据库、索引等,比如下面这样的:

var no = function() {
    print("Not no my watch.");
}

// 禁止删除数据库
db.dropDatabase = DB.prototype.dropDatabase = no;

// 禁止删除集合
DBCollection.prototype.drop = no;

// 禁止删除索引
DBCollection.prototype.dropIndex = no;

定制提示信息

shell 默认的提示是一个 > 符号,我们可以对该符号进行定制,比如最简单的是,在每一个提示符前面加上一个当前时间,这样我们就可以很容易大概的知道,一些需要长时间执行的操作到底用了多久时间了:

prompt = function() {
    return (new Date()) + "> ";
}

再一次进入 shell

mongo
MongoDB shell version: 3.0.2
connecting to: test
Tue Apr 21 2015 13:06:40 GMT+0800 (CST)>
Tue Apr 21 2015 13:06:43 GMT+0800 (CST)>db
test

另一个方便的提示是显示当前正在使用的数据库:

prompt = function() {
    if (typeof db == 'undefined') {
        return '(nodb)>';
    }

    // 检查最后使用的数据库操作
    try {
        db.runCommand({getLastError:1});
    }
    catch (e) {
        print(e);
    }
    return db + "> ";
}

再一次进入 shell

mongo
MongoDB shell version: 3.0.2
connecting to: test
test> 

更好的编辑工具

shell 对多行编辑很有限,若编辑了多行,突然发现前面有一行有错误,这是不能修改的,但是它提供了一种方法,让你可以在 shell 中很访问的使用外部的第三方编辑器,保存并退出编辑器之后,会对你修改的值进行重新解析,并重新加载回 shell 中,同样使用 .mongorc.js,在其中加入下面这一行:

EDITOR = "/usr/bin/vim"

然后进入 shell:编辑一个复杂一点儿的变量:

mongo
MongoDB shell version: 3.0.2
connecting to: test
> var post = {
... title: "这是一篇文章的内容",
... content: "这是一篇文章的标题",
... author: "潘韬"
... }
> post
{ "title" : "这是一篇文章的内容", "content" : "这是一篇文章的标题", "author" : "潘韬" }
> edit post
> post
{ "title" : "这是一篇文章的标题", "content" : "这是一篇文章的内容", "author" : "潘韬" }
> 

一些变态的集合名称的使用

绝大多数时候,我们可以使用 db.collectionName 这种方式访问一个数据库中的集合,但是也有例外,比如 db.version 就不能访问到名称为 version 的集合,因为 versiondb 的一个方法名,可以用来显示当前的数据库版本信息。那么我们就得使用其它的方法了,比如:

> db.getCollection("version");
test.version

有些时候,我们的集合里面还可能使用了很多变态的奇怪的名称,比如 &$*%#,使用 db.&$*%# 是非法的,但是我们却可以使用 db.getCollection("&$*%#") 来获取该集合,同样的,我们还有一种更加直接的方法,就是类似于JavaScript的数组访问语法,像下面这样:

db["&$*%#"].find()

吐槽过程中随便写了点东西,可以很多地方欠周全,不过差不多吧应该。

这么久以来,我听着最恶心的就是H5这两个字符,H5是什么?它是 HTML5 ,也就是 HTML 第五版在中国的IT圈里面的一种缩写,最开始是因为淘宝,腾讯等大公司将自己的以前的 wap 与 3g 网站(也就是3G时代以前手机端的网站,使用的是简化的HTML技术)改成 h5 网站,二级域名使用的是 h5.xxx.com 这种的,人家的意思是”这是一个基于 HTML 5 技术的网站“,就像以前的这是一个基于 WAP 技术的网站是一个意思,但是,中国的IT人总是会创出很多”有意思“的行话来,比如,H5,但是,那我们暂且认同这种说法吧,暂且把 H5 定义为 HTML5 的简称,那我们来看看 HTML5 是什么?

HTML5是HTML最新的修订版本,2014年10月由万维网联盟(W3C)完成标准制定。目标是取代1999年所制定的HTML 4.01和XHTML 1.0标准,以期能在互联网应用迅速发展的时候,使网络标准达到符合当代的网络需求。广义论及HTML5时,实际指的是包括HTML、CSS和JavaScript在内的一套技术组合。它希望能够减少网页浏览器对于需要插件的丰富性网络应用服务(Plug-in-Based Rich Internet Application,RIA),例如:Adobe Flash、Microsoft Silverlight与Oracle JavaFX的需求,并且提供更多能有效加强网络应用的标准集。

具体来说,HTML5添加了许多新的语法特征,其中包括 <video><audio><canvas>元素,同时集成了SVG内容。这些元素是为了更容易的在网页中添加和处理多媒体和图片内容而添加的。其它新的元素如<section><article><header><nav>则是为了丰富文档的数据内容。新的属性的添加也是为了同样的目的。同时也有一些属性和元素被移除掉了。一些元素,像<a><cite><menu>被修改,重新定义或标准化了。同时APIs和DOM已经成为HTML5中的基础部分了。HTML5还定义了处理非法文档的具体细节,使得所有浏览器和客户端程序能够一致地处理语法错误。

上面这两段是引用自维基百科的解释,其实说白了,HTML5就是提供了更多特性的 HTML。那我们再来看 HTML 是什么,还是引用维基百科的解释:

超文本标记语言(英文:HyperText Markup Language,HTML)是为“网页创建和其它可在网页浏览器中看到的信息”设计的一种标记语言。HTML被用来结构化信息——例如标题、段落和列表等等,也可用来在一定程度上描述文档的外观和语义。1982年由蒂姆·伯纳斯-李创建,由IETF用简化的SGML(标准通用标记语言)语法进行进一步发展的HTML,后来成为国际标准,由万维网联盟(W3C)维护。 HTML档案最常用的扩展名(扩展名)为.html,但是有如DOS等的旧操作系统限制扩展名最多为3个文字符号,所以.htm扩展名也允许使用。而如今.htm扩展名的使用较为减少。

编者可以使用任何基本的文本编辑器(例如Notepad等)或所见即所得的HTML编辑器来编辑HTML文件。 早期的HTML语法规则定义较为松散,这有助于不熟悉网络出版的人使用或变更。网页浏览器接受这类的文件,使之可以显示语法不严格的网页。随着时间的流逝,官方标准渐渐趋于严格的语法,但是浏览器继续显示一些仍不合乎标准的HTML。使用XML的严格规则的XHTML(可扩展超文本标记语言)是W3C计划中的HTML的接替者。虽然很多人认为它已经成为当前的HTML标准,但是它实际上是一个独立的、和HTML平行发展的标准。W3C目前建议使用XHTML 1.1、XHTML 1.0或者HTML 4.01标准编写网页,但已有许多网页转用较新的HTML5编码撰写(如Google)。

不知道说到这里各位理解HTML5和HTML了没,如果没有理解,可以想想下面这种场景:

XX写一篇文章,发布到网站上面去,文章嘛,有标题,有摘要,有段落,有列表,还有可能有插图,但是这个该怎么写呢?我得让浏览器知道哪些是标题,哪些又是段落,哪些又是列表嘛,怎么办?这个时候可以先告诉浏览器,我用HTML标准来写文章吧,你就按HTML的标准来理解我给你的数据就可以了,所以,大家可以看到,任何一个网页的源代码的第一行都是声明,我这是一个HTML文档,而且,还申明到了,我这个文档用的是什么版本的HTML,比如,我们以前用得最多的:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

上面这一行告诉我们,我这个文档是HTML文档,遵守的是 XHTML 1.0 版本的协议,而且不需要很严格的执行这个标准备(Transitional说明的),若你(指的是浏览器)不知道我这个版本的HTML的协议(就是规范)是什么样儿的时候,你可以下载http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd这个文件,这个文件会告诉你,我的具体规范是什么。

而当我们用HTML5之后,改变的第一个就是上面这一行,我们不再需要按上面这样写了,就按下面这样写就可以了:

<!DOCTYPE html>

简单吧,就告诉你,我是HTML文档,对于低版本的浏览器,他们会选择一个自己支持的最新的HTML规范去解释这份文档,而对于支持HTML5标准的浏览器,就会直接按HTML5规范来解释了。

然后,不是有一个标题嘛,标题在HTML里面共分为六级,就是 h1,h2,h3,h4,h5,h6,看到没?h5,这个在HTML里面表示的是第五级标题,而如果你在HTML里面没有出现 H1,h2,h3,h4 的话,是不允许直接出现 h5 的,若就只是一篇文章,我们就应该直接使用 h1,所以,吐槽杂志这四个字在HTML里面是这样的:<h1>吐槽杂志</h1>,然后就是段落了,那是 p 干的活,表示,被我包着的文本是一个段落,比如:<p>我是一个段落</p>,同样的,列表有 ul ,即 unordered list,意思为无顺序的列表,ol,即 ordered list,意思是有顺序的列表,比如下面这样的一个示例,要做黄焖鸡要有几步?看下面这几步:

<ol>
    <li>杀鸡</li>
    <li>开火</li>
    <li>焖煮</li>
</ol>

上面这五行,浏览器看到之后,就会知道了,哦,这是一个有序列表,那有序列表该这么显示(说一下,li 不读李,而是 list item,即列表项):

1. 杀鸡
2. 开火
3. 焖煮

看到没,HTML里面其实就是通过各种各样的标签来标记不同意义的内容而已,到现在为止,我还只是说到了 HTML,CSS和JS都还没有涉及,如果没有CSS和JS,HTML的网页在浏览器里面看起像下面这样的:

4693194F-B64E-49A8-94BE-D0B0359BFAC0.png

是不是很丑,其实这个就是HTML在浏览器里面的表现,而为什么有的字体大一点,有的黑一点,有在粗一点,那是因为浏览器通过对HTML结构的解析,然后知道不同的文本都表示的不同的意义的,比如最上面的”中文网页重设与排版:Typo.css“这一段,它是 h1 标签,一级标题,也就是一个网页里面权重最高的文本,所以,它认为应该字体大一点,粗一点,颜色深一点,我们把同样的一份没有CSS和JS的HTML文档放在不同的浏览器里面打开,显示的效果也是不一定一样的,这是因为,我认为 20号字体最能表示 h1,但是可以你却认为24号才行,但是大家基本上的理解也都还差不多,但是虽然差不多,也还是有差异,这个差异,就是所谓的浏览器兼容性(仅仅只是HTML层的,相应的CSS与JS也有兼容性问题),对同样的事物的理解不一样,才是导致兼容性存在的根本原因,但是,兼容性没关系,也都是有解决办法的。

然后,随着互联网的发展,越来越多的人感觉,我们是不是可以让网页文档(HTML文档)更加漂亮一点,这个时候,有一些NB的人就走到了一起,想到了CSS:

层叠样式表(英语:Cascading Style Sheets,简写CSS),又称串样式列表、级联样式表、串接样式表、层叠样式表、階層式樣式表,一种用来为结构化文档(如HTML文档或XML应用)添加样式(字体、间距和颜色等)的计算机语言,由W3C定义和维护。目前最新版本是CSS2.1,为W3C的推荐标准。CSS3现在已被大部分现代浏览器支持,而下一版的CSS4仍在开发过程中。

根据上面的这个解释,应该明白了吧,HTML太丑了,那我就用CSS来样式化他,你把我现在不管你认为H1 应该是多大的字体,反正我告诉你了,我这个文档,H1必须显示成 30号字体,你把你们自己的想法都别体现出来,听我的就成了,我就是想让我的读者看到 30 号字体大号的标题,那我们怎么做了,只要下面这样斥可以了:

h1 {
    font-size: 30pt;
}

这个时候,浏览器就会拿我定义的规则覆盖他们各自自己的规则了,最后字体显示成了 30号字体,同样的,除了字体大小,CSS还提供了很多很多的选择器(用来指定哪一个HTML元素的),用来定义几乎所有的HTML元素,还提供了很多的关于样式的描述,比如 颜色:color,字体:font-family,背景色:background-color等等,通过CSS,让我们可以把一份特别特别丑陋的HTML文档变得更加美观,也正是这个时候,才出现的一个新的工种,网页设计师,因为只有在存在一种技术美化网页的时候,网页设计师才能把自己的想法变成现实,这个时候,那一群工作在网站技术一线的人就分为了网页美工(对网页进行简单的设计和把设计转成HTML+CSS)和后台程序两种,记着,这个时候还没有称之为前端,而我有幸,正好是从那个时代就接触了互联网(04年了解,05年入行)。那是 Web 1.0 时代,再发展,网民越来越多,想法也越来越多,他们不再只是想着看,还想着,我看了之后是不是可以告诉你我的读后的感受?我是不是可以参与到你的话题里面去?慢慢地,这种想法被大家都认同之后,可以让访问者留言的网站出现了,这个时候,网站与读者间有了简单的交互,我写了一篇《怎么做黄焖鸡》的文章,然后有一个读者不认同我的这种做法,就可以在文章最下面的留言框里面写上自己的想法,然后点击提交,然后,他说的话,别的读者也可以看得到,然后别的读者可能又有别的想法,他们也同样可以接着在下面写上自己的想法,这个时候,BBS、博客们就出现了(我这里所说的只是网页上的BBS,其实BBS出现得更早,只是不是以网页的形式出现而已),再接着往下发展,就只是发发评论还是不行啊,那么网站主们为了方便访问者更好的参与交流中来,就想出了各种各样的方法,而最简单的方法就是让死气沉沉的网页动起来,那么,引出了JavaScript了:

JavaScript,一种直译式脚本语言,是一种动态类型、弱类型、基于原型的语言,内置支持类。它的解释器被称为JavaScript引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在HTML网页上使用,用来给HTML网页增加动态功能。然而现在JavaScript也可被用于网络服务器,如Node.js。在1995年时,由网景公司的布兰登·艾克,在网景导航者浏览器上首次设计实现而成。因为网景公司与昇阳公司合作,网景公司管理层次结构希望它外观看起来像Java,因此取名为JavaScript。但实际上它的语义与Self及Scheme较为接近[4]。为了获取技术优势,微软推出了JScript,与JavaScript同样可在浏览器上运行。为了统一规格,1997年,在ECMA(欧洲计算机制造商协会)的协调下,由网景、昇阳、微软和Borland公司组成的工作组确定统一标准:ECMA-262。因为JavaScript兼容于ECMA标准,因此也称为ECMAScript[5]。

上面的定义可能有一些拗口,简单来说,JavaScript可以在浏览器里面运行,运行之后可以产生各种各样的动作,比如让一个标题从左向右走着跑马灯,而不再是定在那里不动等等,这个时候的网页越来越有趣味,以前可能就是在文章的最下面有一个评论框,但是现在却不一样了,文章最下面就一个“点我打开评论框”,然后用户点一下,就在网页里面弹出一个对话框来,用户可以在对话框里面输入内容,然后点击确定,就添加好评论了,这样一来,网页的读者参与感越来越强,再往后发展,才产生一个新的工种 ,用户体验设计师,他们是一群研究与提升读者在你的网页里面的感受的,再往后发展为交互体验设计师(就是我们Ocean的工种),但是交互体验只是在想各种各样的办法和理论来告诉你,怎么做更好,但是他们本身却并不会做,比如,Ocean告诉你:当网页往下翻到第500像素的位置的时候,叮当猫从网页的左边跳出来,然后出来一个气泡,里面一个一个字的出现一句话:大家好,我叫呆小当。

那么,什么人去把这种想法付诸实现呢?前端,那我们应该知道前端干的活儿了吧,前端就是把前端设计师、交互设计师的各种各样的想法变成实际的实现的人,那前端具体要怎么实现上面的那个想法呢?下面这样的:

写一个JS程序,这个JS程序要监听这么几件事情:a) 网页现在在什么位置? b) 若滑动到 500 的话,让叮当猫从左边跳出来,然后出来一个气泡……

但是JS要怎么实现这些动效?它用到了CSS,前面说过CSS是用来样式化HTML文档的,怎么用?先用CSS把叮当猫在网页里面隐藏,比如下面这样的:

.doraemon-cat {
    display: none
}

这个表示,.doraemoney-cat 这个名字的图片,它的展示方式(display)为 不显示(none,即什么也没有)

然后当网页滑动到 500 px 的时候, js 通过 css 把这个图片的 display 属改成 block(以以块显示)

然后再控制其它的如文字出来哪之类的。

知道没,前端需要的技能是HTML+CSS+JavaScript这三种技术的完美配合才能达到最佳效果的,所以,当大家尤其是某某自己在说自己是做前端的的时候,我就只有在心里呵呵一声了,而这个也正是我本来来这里应该做的事情,将交互设计师的创业与想法通过HTML+CSS+JavaScript变成实现。一个不会JS,一个不会CSS,三条腿能稳当,少一条怎么可能站得起来?即使站起来了,随便小风小浪一吹一打就倒了,这也就是为什么我们现在的整个前端架构为什么这么脆弱的原因了。

那么,我们为什么前端又有兼容性呢?其实原因还是因为浏览器对同样的东西的理解不一样导致的,比如,js 里面 alert 这个东西,在 IOS 的浏览器里面显示成半透明的白色,而Android可以显示成半透明的黑色,这就是兼容性,但是并没有谁对谁错,只是理解不一样而已,但是,但是,又但是,对各种不同环境下对同样的东西作出不一样的解释这种现象,说成无法解决的问题,我就只能哈哈一下了。

有兼容性本身并不可怕,我们可以尽可能的少用那些有兼容性问题的特性,或者我们可以很明确的为不同的浏览环境做不同的处理,但是,一般情况下,都不可能出现,IOS 有,Android却没有的特性,也就是说,不管是什么功能,只要是HTML有的特性,那么浏览器里面一般都会有,只要是CSS里面有的特性,各种不同的浏览器里面也一般都会有,JS也一样,只是解释不太一样,我们在完成一个作品(我叫完成一个作品,而不是完全一个任务)的时候,用一个很简单的规避方式就能避免这些问题,但是,现在我们有的人的做法是,自己不知道这里面的异同,然后以自己浅薄的那一点点知识来告诉更加不懂的人:这就是这样的,没有办法,技术是实现不了的,就有点儿让人接受不了了,这让我想起去年,我在学习一个叫作 Ionic 的新技术的时候,在Android里面,导航条就是跑在屏幕的最上面,而在 iOS 的时候,就是我想要的,导航条在下面,我当时在网上各种圈子里面各种吐槽,说一个这么成熟的框架居然还有这种Bug,犯这种SB错误,然而,某一天,当我很仔细的翻看了人家官方的文档之后,我瞬间就想往地缝里钻,人家说得很明白,系统在 Android 环境下,默认导航条在上面,iOS 下,默认导航条在下面,若要在修改默认设置,只需要设置:

navigator.position = "bottom"或者 "top" 即可,

而我现在对于某些人的想法就是这样的,………………

http://segmentfault.com/ 上面看到的问题,回答了之后,人家才告诉我问的不是CSS相关的,所以,实现方式就放在这里了吧:


<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="utf-8"/>
    <title>纯CSS实现的手表</title>
    <style>
        @keyframes tik {
            from {
                transform:rotate(0deg);
                -ms-transform:rotate(0deg);
                -moz-transform:rotate(0deg);
                -webkit-transform:rotate(0deg);
                -o-transform:rotate(0deg);
            }
            to {
                transform:rotate(360deg);
                -ms-transform:rotate(360deg);
                -moz-transform:rotate(360deg);
                -webkit-transform:rotate(360deg);
                -o-transform:rotate(360deg);
            }
        }

        @-moz-keyframes tik {
            from {
                transform:rotate(0deg);
                -ms-transform:rotate(0deg);
                -moz-transform:rotate(0deg);
                -webkit-transform:rotate(0deg);
                -o-transform:rotate(0deg);
            }
            to {
                transform:rotate(360deg);
                -ms-transform:rotate(360deg);
                -moz-transform:rotate(360deg);
                -webkit-transform:rotate(360deg);
                -o-transform:rotate(360deg);
            }
        }

        @-webkit-keyframes tik {
            from {
                transform:rotate(0deg);
                -ms-transform:rotate(0deg);
                -moz-transform:rotate(0deg);
                -webkit-transform:rotate(0deg);
                -o-transform:rotate(0deg);
            }
            to {
                transform:rotate(360deg);
                -ms-transform:rotate(360deg);
                -moz-transform:rotate(360deg);
                -webkit-transform:rotate(360deg);
                -o-transform:rotate(360deg);
            }
        }

        @-o-keyframes tik {
            from {
                transform:rotate(0deg);
                -ms-transform:rotate(0deg);
                -moz-transform:rotate(0deg);
                -webkit-transform:rotate(0deg);
                -o-transform:rotate(0deg);
            }
            to {
                transform:rotate(360deg);
                -ms-transform:rotate(360deg);
                -moz-transform:rotate(360deg);
                -webkit-transform:rotate(360deg);
                -o-transform:rotate(360deg);
            }
        }
        #clock {
            width: 200px;
            height: 200px;
            background: #f0f0f0;
            border: 1px solid #ccc;
            border-radius: 50%;
            position: relative;
        }
        #pointer {
            width: 10px;
            height: 10px;
            background: #e0e0e0;
            border: 1px solid #ccc;
            border-radius: 50%;
            position: absolute;
            top: 50%;
            left: 50%;
            margin-top: -5px;
            margin-bottom: -5px;
            animation: tik 60s;
            -moz-animation: tik 60s;
            -webkit-animation: tik 60s;
            -o-animation: tik 60s;
        }
        #pointer:before {
            content: "";
            display: block;
            height: 95px;
            width: 2px;
            background: #282828;
            position: absolute;
            bottom: 100%;
            left: 50%;
            margin-left: -1px;
        }
    </style>
</head>
<body>
<div id="clock">
    <div id="pointer"></div>
</div>
</body>
</html>

点击下面这个网址还可以看运行效果:

https://pub.ofcrab.com/usr/sandbox/html5/css3-clock/www/index.html

Go语言的语言符号又称记法元素,共包括5类,标签符(identifier)、关键字(keyword)、操作符(operator)、分隔符(delimiter)、字面量(literal),它们是组成Go语言代码和程序的最基本单位。

Go语言的所有源代码都必须由 Unicode 编码规范的 UTF-8 编码格式进行编码。

一、标识符

在Go语言代码中,每一个标识符可以代表一个变更或者一个类型(即标识符可以被看作是变量或者类型的代号或者名称),标识符是由若干字母、下划线(_)和数字组成的字符序列,第一个字符必须为字母。同时,使用一个标识符在使用前都必须先声明。在一个代码块中,不允许重复声明同一个标识。

代码包声明(package PKG_NAME)并不算是一个声明,因为代码包名称并不出现在任何一个作用域中,代码包声明语句的目的只是为了鉴别若干源码文件是否属于同一个代码包,或者指定导入代码包时的默认代码包引用名称。

一个限定标识符代表了对另一个代码包中的某个标识符的访问,这需要两个条件:

 1. 另一个代码包必须事情由Go语言的导入声明 `import` 导入;
 2. 某个标识符在代码包中是可导出的。
标识符可导出的前提条件有下面这两个:
 1. 标识符名称中的第一个字符必须是大写;
 2. 标识必须是被声明在一个代码包中的变量或者类型的名称,或者是属于某个结构体类型的字段名称或者方法名称。

在Go语言中还存在一类特殊的标识符,叫作预定义标识符,这类标识符随Go语言的源码一同出现,主要包括以下几种:

  1. 所有基本数据类型的名称
  2. 接口类型 error
  3. 常量 truefalse 以及 iota
  4. 所有内奸函数的名称,即 appendcapclosecomplexcopydeleteimaglenmakenewpanicprintprintlnrealrecover

由一个下划线表示的标识 _ 为空标识符,它一般被用在一需要引入一个新绑定声明中,如:

import _ "runtime/cgo"

二、关键字

关键字是指被编程语言保留页不让编程人员作为标识符使用的字符序列。因此,关键字也称为保留字。

Go 语言中所有的关键只有25个:

  1. 程序声明:importpackage
  2. 程序实体声明和定义:chanconstfuncinterfacemapstructtypevar
  3. 程序流程控制:goselectbreakcasecontinuedefaultdeferelsefallthroughforgotoifrangereturnswitch

三、字面量

简单来说,字面量就是表示值的一种标记法,但是在Go语言中,字面量的含义要更广一些:

  1. 用于表示基础数据类型值的各种字面量。
  2. 用户构造各种自定义的复合数据类型的类型字面量,如下面这个字面量表示了一个名称为 Person 的自定义结构体类型:

    type Person struct {

      Name string
      Age uint8
      Address string

    }

  3. 用于表示复合数据类型的值的复合字面量,更确切地讲,它会被用来构造类型 Struct(结构体)、Array(数组)、Slice(切片)和Map(字典)的值。如下面的字面量可以表示上面的那个 Person 结构体类型的值:

    Person(Name: "Eric Pan", Age: 28, Address: "Beijing China"}

四、类型

一个类型确定了一类值的集合,以及可以在这些值上施加的操作。类型可以由类型名称或者类型字面量指定,类型分为基本类型和复合类型,基本类型的名称可以代表其自身,比如:

var name string 

string 即为一个基本类型,Go 语言中的基本类型有:bool、byte、int/uint、int8/uint8、int16/uint16、int32/uint32、int64/uint64、float32、float64、complex64、complex128,共18个,基本类型的名称都必须预定义标识符。除了 bool 与 string 外,其它的都称为数值类型。

除了基本类型外,Go语言还有八个复合类型:Array(数组)、Struct(结构体)、Function(函数)、Interface(接口)、Slice(切片)、Map(字典)、Channel(通道)以及Pointer(指针)。

复合类型一般由若干(包括0)个其他已被定义的类型组合而成,如定义一本书的结构体:

type Book struct {
     Name string
     ISBN string
     Press string
     TotalPages uint16
}

Go语言中的类型又可以分为静态类型和动态类型,一个变量的静态类型是指在变量声明中示出的那个类型,绝大多数类型的变量都只拥有静态类型,唯独接口类型的变量例外,它除了拥有静态类型之外,还拥有动态类型,这个动态类型代表了在运行时与该变量绑定在一起的值的实际类型。

  • 每一个类型都会有一个潜在类型,如果这个类型是一个预定义的类型(也就是基本类型),或者是一个由类型字面量构造的复合类型,那么它的潜在类型就是它自身,比如 string 类型的潜在类型就是 string,在上面提到的 Book 的潜在类型就是 Book,但是如果一个类型并不属于上述情况,那么这个类型的潜在类型就是在类型声明中的那个类型的潜在类型,比如我们按以下方式声明一个 MyString 类型:

    type MyString string

MyString 类型的潜在类型就是 string 类型的潜在类型,实际上,我们可以将 MyString 看作是 string 类型的一个别名,在Go 语言中的基本数据类型 rune 类型就是如此,它可以看作是 uint32 类型的一个别名类型,其潜在类型就是 uint32 ,但是一类要注意, MyString 与 string 却并不是一个相同的类型。

潜在类型在声明过程中是具有可传递性的,如下面我们再声明一个 iString 类型:

type iString MyString

iString 类型的潜在类型同样就是 string 类型。

本文章将展示如何安装一个提供 ISPConfig 3 的基于 CentOS 7 的服务器,ISPConfig 3是一个网页服务器控制面板工具,通过它,你可以通过网页浏览器管理以下这些软件:nginx 网页服务器、postfix 邮件服务器、mysql 数据库服务器、BIND 域名解析服务器、pureFTPd 文件服务器、SpamAssassinClamAVMailman等,从 ISPConfig 3 开始,其已经可以完美支持Nginx服务器了。

第一步:下载 CentOS 7 安装镜像

你可以通过下面这两个链接地址下载该镜像:

我你爱我吗的是最小安装版,然后按自己的需要安装。

第二步:安装说明

我是在我的 MacbookPro 上面跑Parallels Desktop 虚拟机安装的 CentOS 7,其IP地址为:10.211.55.6,使用域名 centos7.localserver.com 域作为主机名。

第三步:安装编辑器编辑 /etc/hosts

若你喜欢使用 nano 编辑器,使用以下命令安装:

yum -y install nano

安装 wget 工具:

yum -y install wget

我使用的是 vi ,所以就不安装其它的编辑器了,在以下的所有步骤里面,我也将全部都使用 vi 编辑文件。

设定 /etc/hosts

vi /etc/hosts

内容如下:

127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4
10.211.55.6 centos7.localserver.com
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6

设置 hostnamecentos7.localserver.com

echo 'centos7.localserver.com' > /etc/hostname

第四步:安装并配置一些基本的网络防火墙管理软件

若您只想使用CentOS默认的防火墙软件,则可以直接跳过此步。

关闭并禁用 CentOS 默认的 Firewall 软件:

systemctl stop firewalld.service
systemctl disable firewalld.service

运行结果如下:

[root@localhost ~]# systemctl stop firewalld.service
[root@localhost ~]# systemctl disable firewalld.service
rm '/etc/systemd/system/dbus-org.fedoraproject.FirewallD1.service'
rm '/etc/systemd/system/basic.target.wants/firewalld.service'

使用以下命令查看当前的 Firewall 状态:

firewall-cmd --state

运行结果如下:

[root@localhost ~]# firewall-cmd --state
not running

接着安装新的软件:

yum -y install net-tools NetworkManager-tui

第五步:关闭 SELinux

Selinux 是CentOS的一个安装性扩展,我个人感觉一般都用不着该扩展,启用他,还会给我们系统的使用和管理带来很多麻烦的事儿,所以,我一般都直接关闭该扩展。关闭该扩展只需要将 /etc/selinux/config 中的 SELINUX 项设置为 disabled 即可:

vi /etc/selinux/config

作如下设定:

# This file controls the state of SELinux on the system.
# SELINUX= can take one of these three values:
#     enforcing - SELinux security policy is enforced.
#     permissive - SELinux prints warnings instead of enforcing.
#     disabled - No SELinux policy is loaded.
SELINUX=disabled
# SELINUXTYPE= can take one of three two values:
#     targeted - Targeted processes are protected,
#     minimum - Modification of targeted policy. Only selected processes are protected.
#     mls - Multi Level Security protection.
SELINUXTYPE=targeted

保存修改之后,还必须重启一次系统:

reboot

最近我的一个项目刚刚上线(日PV > 500W),流量突然之间很大,但是由于我们前期仅仅只是想小范围测试,所以没有准备足够的服务器资源,导致整个应用的访问均变得很慢,最主要的是我们的应用需要从服务器端向多个第三方发起多次请求,而每一次请求都会需要很长的时间,所以,整个应用几乎就到了不能使用的地步了。

排查原因之后,我们的服务器完全没有问题,而问题就是出在了当我们向第三方发起请求之后,第三方需要5分钟左右的时间才能给出结果,这个时候我们前端没有做任何的过滤,这使得有了12406效应,就是我们不出结果,导致前端的用户就一直的试这么一个恶性循坏,临时性的想到的一个办法就是我在我们的应用服务器前端再加一台服务器,若在短时间内,请求次数超过某个阈值,我就直接拒绝请求,使用了 Nginxngx_http_limit_req_module 模块。

/etc/nginx/nginx.conf 文件中,加入如下代码:

http {
    limit_req_zone $binary_remote_addr zone=one:10m rate=20r/s;

    ...
}

上面这一行的作用是:

  • 定义一个名为allips的limit_req_zone用来存储session,大小是10M内存,
  • 以$binary_remote_addr 为key,限制平均每秒的请求为20个,
  • 1M能存储16000个状态,rete的值必须为整数,
  • 如果限制两秒钟一个请求,可以设置成30r/m

然后在我们的 server 中添加如下代码:

server {
    limit_req zone=one burst=5;
    ...
}

作用为:

  • 限制每ip每秒不超过20个请求,漏桶数burst为5
  • brust的意思就是,如果第1秒、2,3,4秒请求为19个,
  • 第5秒的请求为25个是被允许的。
  • 但是如果你第1秒就25个请求,第2秒超过20的请求返回503错误。
  • nodelay,如果不设置该选项,严格使用平均速率限制请求数,
  • 第1秒25个请求时,5个请求放到第2秒执行,
  • 设置nodelay,25个请求将在第1秒执行。