zxldev

zxldev 查看完整档案

北京编辑  |  填写毕业院校  |  填写所在公司/组织 www.souii.com 编辑
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

zxldev 赞了回答 · 2018-02-06

【官方比赛】社区 1111 秀代码,让你来秀让你飞!

先用下面的代码占个坑,一会儿再答(这是一段神奇的代码):

document.querySelector("button[data-id=\"1020000000767695\"][class=\"like\"]").click();

脱光不现实,让这段代码陪陪你:

/* via https://github.com/pa7/nude.js */
nude.scan(callback)

不知道作者为什么这么羞涩,没有提供文档,也没有写明详细的注释。

这是一个很神秘的库,提供了一个神秘的函数,实现了一个神秘的功能——

扫描网页的图片,判断是不是裸体(摔,看你们谁还敢脱光)

今年光棍节,可以用这段代码陪陪你。可以去看看官方的 demo。

记住这段神秘的代码 nude.scan(callback)

--------- ↓↓↓↓下面代码废了↓↓↓↓ ------------

字符码那个去年已经玩了2次了,今天来个简单的,就不炫技了。

将你QQ号中间插入 + - * /,计算结果和 1+1+1+1 比较。

没有B格,没有技巧,没有眼花缭乱,只有一个变量 op_diaobaolity,多读几遍就知道什么意思了。

现在问题来了,op_diaobaolity = parseInt(i).toString(4) 这段代码什么意思呢?

var no = "123456789";   // 此处填写你的QQ号码
var ops = ["+", "-", "*", "/"];

var expression = [];

var total = 0;

for (var i=0; i<Math.pow(4, no.length-1); i++) {
    var op_diaobaolity = parseInt(i).toString(4);
    var exp = no[0];
    for (var j=1; j<no.length; j++) {
        var op_index = op_diaobaolity[no.length-j] | 0;
        exp += ops[op_index] + no[j];
    }

    if (eval(exp) == 4) {
        total ++;
        console.log(exp + "=1+1+1+1");
    }
}

console.log("今年双十一的脱光概率:" + (total/365*100).toFixed(2) + "%");

关注 70 回答 48

zxldev 赞了回答 · 2018-02-06

【官方比赛】社区 1111 秀代码,让你来秀让你飞!

// language: javascript
// 打开控制台运行,亲
Function("‍‍‌‍‍‌‍‍‍‍‌‍‌‍‍‍‍‍‌‍‍‍‌‍‍‍‌‍‌‌‌‍‍‌‌‌‍‌‌‌‍‌‌‍‌‍‍‌‍‌‌‍‍‌‍‍‍‌‌‍‍‌‌‌‍‌‌‍‍‌‍‌‍‌‌‌‍‌‍‍‍‍‌‍‌‌‍‌‍‌‌‌‍‌‌‍‍‌‌‍‌‌‌‌‍‌‌‌‍‌‍‍‍‌‌‍‍‌‍‌‍‍‌‍‍‍‍‍‍‍‌‍‌‌‌‍‍‌‌‍‌‌‍‍‍‌‌‍‌‍‍‌‍‌‌‍‌‍‌‌‍‌‌‍‍‌‍‌‍‌‍‌‌‍‌‌‍‌‌‍‍‌‍‍‍‌‌‍‍‍‍‌‍‌‌‌‍‌‍‍‍‌‌‍‍‍‍‌‍‍‌‍‌‌‍‌‍‌‌‍‌‍‍‌‍‌‌‍‍‌‍‍‍‍‌‌‌‌‍‌‍‌‍‌‌‌‍‍‍‍‌‍‍‍‌‍‍‍‌‌‍‍‍‌‍‍‌‌‍‍‍‍‍‍‌‌‍‍‌‍‍‍‌‌‍‍‍‍‍‍‌‌‍‍‍‍‍‍‌‌‍‍‍‍‍‍‌‌‍‍‍‍‍‍‌‌‍‍‍‍‍‍‌‌‍‍‍‍‍‍‌‌‍‍‍‍‍‍‌‌‍‌‌‌‍‍‌‌‍‌‌‍‍‍‌‌‍‌‌‌‍‍‌‌‍‌‌‍‍‍‌‌‍‍‌‍‍‍‌‌‍‍‍‌‍‌‍‌‌‌‍‍‍‍‌‍‍‍‌‍‍‌‍‌‌‌‍‌‍‍‌‍‍‍‌‍‍‍‌‍‌‍‍‌‍‍‌‍‌‌‌‍‍‌‌‍‍‍‌‌‍‌‌‍‌‌‍‍‍‌‌‍‌‍‍‌‍‌‌‍‍‍‌‌‍‌‌‍‌‍‌‌‍‍‌‍‌‍‍‍‍‍‌‍‌‍‍‌‍‍‌‍‌‌‍‍‍‌‌‍‍‍‌‌‍‌‌‍‌‌‌‌‍‌‌‍‌‌‌‍‍‌‌‌‍‍‌‌‍‌‌‍‌‌‌‌‍‌‌‍‌‌‍‍‍‌‌‍‍‌‍‌‍‍‌‍‌‌‌‍‍‌‌‍‌‌‍‍‍‌‌‍‌‌‌‌‍‌‌‍‍‌‌‌‍‍‌‍‌‍‍‍‍‍‌‍‍‍‌‍‍‌‍‌‌‌‍‍‍‌‌‌‍‌‍‌‍‍‌‌‍‌‍‌‍‍‌‌‌‍‍‌‍‍‌‌‍‌‌‌‍‍‌‌‍‍‌‌‍‌‍‌‌‌‍‍‍‌‌‌‍‌‍‌‍‍‌‌‍‌‌‌‍‍‌‌‌‍‍‌‍‍‌‌‍‌‍‌‍‌‌‍‍‌‍‌‍‌‍‌‌‌‍‍‍‌‌‌‍‌‍‌‍‍‌‌‌‍‍‍‍‌‌‍‍‌‌‍‍‌‌‍‍‌‍‍‍‍‌‌‌‍‍‍‍‌‍‌‌‌‍‍‍‌‌‌‍‌‍‌‍‍‌‌‍‌‌‍‍‍‌‌‍‌‌‍‍‍‌‌‍‍‌‍‍‌‌‍‍‌‌‍‍‌‍‌‌‌‍‍‍‌‌‌‍‌‍‌‍‍‌‌‌‍‍‍‍‍‌‌‌‍‍‌‍‍‌‌‌‍‍‍‍‍‌‌‍‍‍‌‍‌‍‌‌‌‍‍‍‌‌‌‍‌‍‌‍‍‌‌‌‍‍‍‍‌‌‍‍‌‌‍‍‌‌‍‍‌‌‍‍‌‌‍‍‌‍‍‍‌‍‌‌‌‍‍‍‌‌‌‍‌‍‌‍‍‌‌‍‌‌‌‍‍‌‌‍‌‌‍‍‍‌‌‌‍‍‍‍‍‌‌‍‌‍‍‍‌‍‌‌‌‍‍‍‌‌‌‍‌‍‌‍‌‌‍‍‌‌‍‍‌‌‍‍‌‌‍‍‍‌‌‍‍‍‍‍‌‌‍‍‍‌‌‍‌‍‌‌‌‍‍‍‌‌‌‍‌‍‌‍‍‌‌‍‌‍‍‍‌‌‍‍‌‍‌‍‍‌‌‍‍‍‍‍‍‌‌‍‌‌‌‍‌‍‌‌‌‍‍‍‌‌‌‍‌‍‌‍‍‌‌‍‌‍‍‍‌‌‍‍‌‍‌‍‍‌‌‍‍‍‍‍‍‌‌‍‍‍‍‍‌‍‌‌‌‍‍‍‌‌‌‍‌‍‌‍‍‌‌‍‌‍‌‍‍‌‌‌‍‍‌‍‍‌‌‍‌‌‌‍‍‌‌‌‍‍‌‍‌‍‌‌‌‍‍‍‌‌‌‍‌‍‌‍‍‌‌‍‌‌‌‍‍‌‌‍‌‌‌‍‍‌‌‌‍‍‍‍‌‌‍‍‌‍‌‍‌‍‌‌‌‍‍‍‌‌‌‍‌‍‌‍‍‌‌‍‌‍‍‍‌‌‍‍‌‍‌‍‍‌‌‌‍‍‍‍‍‌‌‍‌‌‍‍‌‍‌‌‌‍‍‍‌‌‌‍‌‍‌‍‍‌‌‍‌‍‌‍‍‌‌‍‌‍‍‍‍‌‌‍‌‌‍‍‍‌‌‍‍‌‍‍‌‍‌‌‌‍‍‍‌‌‌‍‌‍‌‍‌‌‍‍‌‌‍‍‌‌‍‍‌‌‍‍‍‌‌‍‍‍‍‍‌‌‍‍‍‌‌‍‍‌‍‍‍‌‍‍‍‌‍‌‍‍‌‍‍‌‌‌‍‌‌".replace(/.{8}/g,function(u){return String.fromCharCode(parseInt(u.replace(/\u200c/g,1).replace(/\u200d/g,0),2))}))();

关注 70 回答 48

zxldev 赞了文章 · 2017-12-12

HTML5,不只是看上去很美(第二弹:打造最美3D机房)

前言

最近项目开发任务告一段落,刚好有时间整理这大半年的一些成果。使用html5时间还不久,对js的认识还不够深入。没办法,以前一直搞java,对js的一些语言特性和概念一时还转换不过来。

上一篇第一弹介绍了项目中做的一个彩虹爆炸图,主要用了 html5的canvas的2d绘制技术。这一回我想介绍一下项目中的一个亮点技术:html5的3D,以及如何用它打造精美的3D机房监控系统。

------------------------10月27日更新-----------------------------
没想到这篇的点击率这么高,感谢大家给予的厚爱。
受到大家的启发和鼓励,这个3D机房系列已经有了长足的进步。
补上后续两篇的传送门:
第二季:场景和功能的丰富,包括机柜、设备、走线、路径规划等功能
第三季:功能的进一步丰富,包括资产管理,动环监控等的可视化管理

另外,录了两个视频,懒得看长文的朋友可以直接戳:
http://v.youku.com/v_show/id_...
http://v.youku.com/v_show/id_...

插播一则广告(长期有效)

MONO哥需要在武汉招JavaScript工程师若干
要求:对前端技术(JavasScript、HTML、CSS),对可视化技术(Canvas、WebGL)有浓厚的兴趣
基础不好的可培养,基础好的可共谋大事
感兴趣的给我发邮件:hr@servasoft.com

目标效果图

下图是领导给找的一张的效果参考图,客户希望机房至少能达到下面的3D效果。
图片描述
懂的人都知道,这可是一张设计公司出的装修效果图啊,就算是用max建模,也需要大量的工作,何况咱可是程序员在做数据中心的可视化项目啊。。。强忍心中奔腾的万千头**马,静下心来思考,那就先从搭建一个webGL的场景开始吧。

WebGL基本场景搭建

在html5里面使用3D已经不是什么高深技术,它的基础是WebGL,一个OpenGL的浏览器子集,支持大部分主要3D功能接口。目前最新的浏览器都有比较好的支持,IE需要到11(是的,你没有看错)。

要检测你的浏览器是否支持webGL,可直接访问网页http://get.webgl.org/ 看是否能看到一个旋转的立方体。如果能看到,说明你的浏览器支持webgGL,否则,可以下一个最新的chrome试试吧。相对来说chrome对webGL的支持最好,效率也很优秀。

要在浏览器里面使用webGL,就要研究webGL相关的技术和用法。做3D应用并不是一件轻松的事。就算最简单的搭建一下webGL场景,也需要下面这些代码:

var width = window.innerWidth;  
var height= window.innerHeight;  
var container = document.createElement( 'div' );  
document.body.appendChild( container );  
var webglcanvas = document.createElement('canvas');               
container.appendChild(webglcanvas);   
var gl = webglcanvas.getContext("experimental-webgl");                

function updateFrame () {             
  gl.viewport ( 0, 0, width, height );  
        gl.clearColor(0.4, 0.4, 0.7, 1);  
        gl.clear ( gl.COLOR_BUFFER_BIT );       
         setTimeout(   
    function(){updateFrame()},  
            20);  
     }  

setTimeout(   
  function(){
    updateFrame();
  },  
20);  

和html一样,需要先创建一个canvas元素,并获得其webgl上下文:

var gl = webglcanvas.getContext("experimental-webgl");

然后在一个updateFrame的函数中,像html5的2D context一样,去绘制3D的内容。

另外,要再起一个死循环,每隔**毫秒调用一次这个updateFrame函数来重绘场景。和2D不同,3D场景里面的变化是随时随地的,所以需要不停刷新,就像播放电影或视频,静止不动的画面基本没有,所以死循环刷新基本是必要的。不过实际项目使用中会有很多优化,尽量做到“按需刷新”,节省cpu和移动设备电量。有感兴趣的同学,哥可以单独写文章介绍。这段程序基本上什么也没做,就画了一个静止不动的区域,如下图:

图片描述

虽然看不见任何3D的内容,不过它已经是一个最简单的webgl程序了。我们的3D机房,也就是在这上面不断丰富而已。

对象封装

要做项目,搭建下去工作量太大了,时间周期也不允许。使用第三方辅助工具是不可避免的,像Three.js, twaver.js都是选择。这些工具都可以提供3D的基本对象和各种特效,当然这都不是最主要的,主要是如何利用它做出我想要的效果:好看。为了避免大量修改代码,在项目里做了一些封装,即把原始3D的立方体等对象进行进一步封装,让一个json数据就可以提供这些对象的定义。这样使用起来就比较方便了。json大致结构如下:

var json={    
objects: [{
    name: '地板',
    …
},{
…
}],
}

下面我们逐一来看这些3D对象是怎么进行美化的,过程可能稍显啰嗦,跬步千里,这次的基础打好了,以后的项目就手到擒来了。

地板和斜坡

第一个要做的,也是应该比较简单的,就是地板对象。3D中,地板应该是一个有些厚度、带上格子贴图的薄薄立方体平面。因此我对经过封装的立方体对象,用一段json对象定义如下:

{
    name: '地板',
    type: 'cube',
    width: 1600,
    height: 10,
    depth: 1300,
        
    style: {
        'm.color': '#BEC9BE',
        'm.ambient': '#BEC9BE',
    }
}

通过定义,创建了一个13米*16米的地板块,这也是客户小型机房的实际尺寸:
图片描述

看起来有那么点意思,就是颜色还不够,需要找一个地板砖纹理图。需要注意的是,纹理图的尺寸都需要是宽和高都是2的幂,例如128x128、256*256等,这样出来效果才会好。这也是3D软件一般所要求的。另外纹理要能连续拼接不露破绽,这样才好。例如下面我google出来的图:
图片描述

在style里面添加:

   'top.m.texture.image': 'images/floor.png',
   'top.m.texture.repeat': new mono.Vec2(10,10),

效果如下:
图片描述

有图片材质纹理,效果果然好多了。突然想到客户说,他们机房底面有一个方便运送设备的斜坡,必须要画出来。这……(╯-_-)╯

后来想到twaver里面的对象可以支持运算,比如可以定义一个斜的立方体,让地板剪掉立方体,就可以做到。于是继续定义json:

{
    name: '地板切坡',
    type: 'cube',
    width: 200,
    height: 20,
    depth: 260,
    translate: [-348,0,530],
    rotate: [Math.PI/180*3, 0, 0],
    op: '-',
    style: {
        …,
    }
}

这里定义的一个倾斜的立方体,通过translate定义位置,rotate定义旋转角度,然后再通过op定义运算符,这里是“减去”,就用“-”表示。被剪掉的立方体也可以设置材质、纹理、贴图、颜色…等等,和地板一样。看看效果:
图片描述

第一步总算是有惊无险地搞定了。

走廊桌

下一步找了个简单的对象,按要求走廊要放一个接待桌。为了简单,我决定就偷懒做一个立方体表示。

{
    name: '走廊板凳',
    type: 'cube',
    width: 300,
    height: 50,
    depth: 100,
    translate: [350, 0, -500],
}

效果如下:
图片描述

这里偷懒其实是有原因的。在3D里,最重视的就是效率,千万不要放一些很复杂的模型,尤其是这类非业务对象。就像这个桌子,尽管只是个简单的立方体,但只要和整体风格协调一致,再增加一点配色并启动阴影效果后,看着就好多了:
图片描述

墙体

墙体是机房里很重要的一个部分,有好的光照、阴影的效果才能看起来更加逼真。由于墙体是不规则的路径,一段一段去生成还真挺麻烦的,还好引擎支持这种物体,甚至曲线路径都可以。这里只要在json里面定义一组数字的坐标,让这些数字依次连接,组成一个墙体,最后生成3D对象放入场景中就行啦。

json定义如下:

{
    name: '主墙体',
    type: 'path',
    width: 20,
    height: 200,
    translate: [-500, 0, -500],
    data:[
        [0, 0],
        [1000, 0],
        [1000, 500],
        [500, 500],
        [500, 1000],
        [0, 1000],
        [0,0],
    ],
}

注意这里的类型变成了pathdata中定义了一个二维坐标数组来描述墙体。由于墙都是从底面开始的,所以只定义它的平面的x、y坐标就行了。看看效果:

图片描述

不过如前文所说,还是需要上色、上阴影,才能有更好的效果。这里我们启用阴影并咨询设计师美眉几个颜色值,加上去后再看下效果:
图片描述

以及一些细节:
图片描述

看着雪白的墙,是不是觉得少了点什么?对,就是门。在3D机房的监控系统里,门禁是很重要的一块,客户要求门应该与实际位置相对应,并且要有开门关门的动画效果。这样,实际的门禁信息采集上来后,就能在界面实时看到门的状态了。

这里,考虑到门如果直接放上去,会被墙盖住;如果比墙厚,又难看不符合实际。还是应该先定义一个门洞立方体,把门所在的位置挖掉:

{
    name: '门洞',
    type: 'cube',
    width: 195,
    height: 170,
    depth: 30,
    op: '-',
    translate:[-350,2,500],
}

刚好挖在斜坡的位置,这样设备进屋就方便了:

图片描述

不过这门没有一个门框,感觉不太生动。多一个门框会感觉立体感强一些。门框可以是一个比门洞略大的立方体,在挖门洞之前添加:

{
    name: '门框',
    type: 'cube',
    width: 205,
    height: 180,
    depth: 26,
    translate: [-350, 0, 500],
    op: '+',
}

加上阴影和光线等综合效果后,还不错,挺有档次的。

图片描述

来张全景图看看:
图片描述

接着,只要把门安上去就行了。门的定义比较简单,就是一个薄的立方体。不过为了做到玻璃效果,需要设置透明度,让它看上去更像一个玻璃,再让设计师美眉弄一张好看一点的门的图,贴上去。尽管有了webGL,复杂的建模工作可以省略了,不过设计师美眉的配合仍然很重要。
先做左边的门:

{
    name: '左门',
    type: 'cube',
    width: 93,
    height: 165,
    depth: 2,
    translate:[-397,4,500],
    style:{
        'm.transparent': true,
        'm.texture.image': 'images/door_left.png',                    
    }

上面增加的style主要透明和贴图两项。看看效果:

图片描述

同样的方法,再把右侧门贴上就搞定了。为了增加体验,也是用户的要求,门上面设置了动画:双击可以自动打开,再双击可以直接关闭。动画功能引擎做好了封装,在json中直接指定动画类型就行了。不过要注意左右门的动画旋转方向要相反,要不然一个向里开一个向外开感觉比较怪异。

图片描述

项目中,窗本身不需要有任何业务属性,但是美观度的要求可一点都不能少。方法和门类似,先放窗框后挖窗体。不过为了有点变化,这里不做窗框了,做一个窗台,方法和道理与门相同。

{
    name: '主窗户洞',
    type: 'cube',
    width: 420,
    height: 150,
    depth: 50, 
    translate: [200, 30, 500],
    op: '-',
},{
    name: '主窗户台',
    type: 'cube',
    width: 420,
    height: 10,
    depth: 40, 
    translate: [200, 30, 510],
    op: '+',
}

定义了一个窗洞(挖掉)、一个窗台(添加)。一个大窗户就做好了:

图片描述

再添加一个略带颜色的透明玻璃。玻璃设置点高光和反射,增加“玻璃”感觉:

{
    name: '主窗户玻璃',
    type: 'cube',
    width: 420,
    height: 150,
    depth: 2,
    translate: [200, 30, 500],
    op: '+',
    style: {
        'm.transparent': true,
        'm.opacity':0.4,
        'm.color':'#58ACFA',
    },            
}

json中玻璃设置了透明度和颜色。这样一个半透明的茶色玻璃就好了:
图片描述

到这里突然在想:盖房子如果像写程序一样简单就好了,所有的程序猿就不会是无房一族单身狗了。当然写程序和盖房子一样:该封装好的要封装好,最后就是搭积木组装就行了。如果盖房子都是从挖土活泥巴开始,那就杯具了。写程序也是一样,如果从webGL的main开始写……这3D机房的系统要几个月甚至几年才能做出来呢?

外侧墙

按照项目实际要求,继续增加更多建筑物墙体。主要是房间外侧有两道走廊隔墙。这是一个中间有大片透明玻璃的走廊隔墙,需要做的好看一点。由于是直线墙,没有复杂走向,直接用立方体定义:

{
    name: '左外墙',
    type: 'cube',
    width: 20,
    height: 200,
    depth: 1300,
    translate: [-790, 0, 0],
    op: '+',
}

效果如下:

图片描述

再继续挖掉中间的窗户部分:

{
    name: '左外墙洞',
    type: 'cube',
    width: 30,
    height: 110,
    depth: 1300,
    translate: [-790, 60, 0],
    op: '-',
}

图片描述

空白显得很奇怪,加上玻璃试试:

{
    name: '左外墙玻璃',
    type: 'cube',
    width: 4,
    height: 110,
    depth: 1300,
    translate: [-790, 60, 0],
    op: '+',
    style: {
        'm.transparent': true,
        'm.opacity':0.6,
    },
}

图片描述

有了半透明和高光的效果,看起来就有质感了,右边也如法炮制:

图片描述

这样,整个建筑的外观就基本完成了。最后,放一些绿植,增加些生气吧。

植物

做一盆植物,需要有一个空的花盆,花盆里面有泥土,上面有一株植物。这些东西用3D做起来都有点啰嗦。不过也不难。花盆用一个大圆柱剪掉中间的小圆柱,做成空心花盆;植物用贴图+透明模拟一下就行,不用真的去做植物的3D模型,否则要累死了。

根据上面的思路,在项目中通过仔细调整,把创建花盆的代码封装好,然后在json中定义花盆位置就行了。下面定义一个:

{
    name: '花1',
    type: 'plant',
    translate: [560, 0, 400],
}

程序中解析如果type是plant则创建植物对象并添加场景。

图片描述

在房间、走廊、甚至窗台上都可以放几盆,窗台上的可以通过设置scale缩小一些,并提升其高度到窗台位置即可。
图片描述

看看下整体效果,还不赖吧。
图片描述

机柜和设备

写了那么一大篇,才终于把3D机房的外观装修完成,咱也算是个设计师程序员的混合型人才了呢。其实机房最核心的资源——机柜,还没找落呢,没办法,形象工程也是项目建设的一大亮点。

机柜

机柜,以及其中的服务器设备。这才是3D机房里面最终要管理的内容。在我们的实际项目中,这些资产都是在数据库中存储,并通过json接口加载到浏览器中显示。这里为了演示方便,直接写几个机柜的片段,看一下显示效果。
图片描述

机柜对象在项目中是这样封装的:用一个立方体来表示机柜,并加上贴图。项目中,为了提高显示速度,机柜一开始并不加载内部服务器内容,而是只显示自身一个立方体。当用户双击后,会触发一个延迟加载器,从服务器端加载机柜内部服务器,并加载到对应的位置上。此时,机柜会被挖空成一个空心的立方体,以便视觉上更像一个机柜。

定义机柜的json如下:

{
    name: '机柜',
    type: 'rack',
    lazy: true,
    width: 70,
    depth: 100,
    height: 220,
    translate: [-370, 0, -250],
    severity: CRITICAL,
}

上面的机柜定义中,有一个lazy标记,标记它是否延迟加载其内容。如果延迟加载,则双击触发,否则程序显示时直接加载其内容。Severity是定义了机柜的告警信息,它是否有业务告警。如果有告警,会用一个气泡显示在机柜的上方,同时机柜也会被染色成告警对应的颜色。

加入更多的机柜看看效果:

图片描述

设备

简单起见,这里管理的设备假设都是机架设备,尺寸规格比较规整,因此比较容易在机柜中组织。一个设备的外观确定后,在数据库中定义好模板,加载时根据其所在机柜的位置放置即可。

图片描述

这里只是随机生成了几个服务器设备,并按位置摆放。在实际应用中,可以通过手工录入或者智能机架报送的信息来确定服务器的类型和位置。

图片描述

如果需要监控到端口级别,还可以在服务器弹出后,再进一步延迟加载设备商的板卡、端口对象,并点击后进一步进行配置、监控等操作。当然加载的数据越细,对3D引擎和浏览器的压力会越大。可以通过动态延迟加载/卸载策略,获取一些平衡折中。

电视机

纯属无聊,再做一个电视机挂在墙上。依旧,定义一个立方体、挖空屏幕,放上透明玻璃,再贴上我们喜欢的电视节目画面,就ok了。

{
    name: '电视机体',
    type: 'cube',
    width: 150,
    height: 80,
    depth: 5,
    translate: [80, 100, 13],
    op: '+',        
},{
    name: '电视机挖空',
    type: 'cube',
    width: 130,
    height: 75,
    depth: 5,
    translate: [80, 102.5, 17],
    op: '-',
},{
    name: '电视机屏幕',
    type: 'cube',
    width: 130,
    height: 75,
    depth: 1,
    translate: [80, 102.5, 14.6],
    op: '+',
    style: {
        'front.m.texture.image': 'images/screen.jpg',
    },
}

当然,实际项目中,可以换上监控大屏幕的效果:
图片描述

总结

整个场景写到最后,我也已经脑洞大开游刃有余了。3D场景,尤其是这类业务系统,并不一定要死抠模型的仿真度,才能做到“好看”的效果。先来一张全景看一下:
图片描述

怎么样,还算精美吧?基本不输前面看到的广告公司的效果图。但和效果图一张死图片不一样,我们这是一个能操作、能漫游、能缩放、有动画、显示流畅、浏览器无需插件就能直接打开的3D机房小程序,就一个json文件和一百多行代码和一天的时间就搞定了,还是让人有点惊讶的。

图片描述

不用插件、不用3Dmax,不用模型库,干干净净纯粹的小程序,手机和平板也能用哦,而且还很流畅!上一张我的Nexus5截图瞅瞅:

图片描述

经过优化,场景加载已经控制在600毫秒以内,缩放漫游也很流畅。当然,技术和美化永无止境,用户的需求也千变万化精益求精。但只要我们选择好了技术和工具,就能事半功倍。Html5就是极佳的一个选择。

Html5,也许它还不是银弹,但它确实是很好的一个炮弹。本文这一弹,你还喜欢吗?欢迎来信留言索取代码、技术交流:tw-service@servasoft.com

查看原文

赞 103 收藏 239 评论 413

zxldev 赞了文章 · 2017-07-11

跟我一起写shell补全脚本(Bash篇)

上一篇里我们定下了给pandoc写补全脚本的计划:

  1. 支持主选项(General options)
  2. 支持子选项(Reader options/General writer options)
  3. 支持给选项提供参数值来源。比如在敲pandoc -f之后,能够补全FORMAT的内容。

支持主选项

先列出实现了第一阶段目标的程序:

bash# 以pandoc的名字保存下面的程序
_pandoc() {
    local pre cur opts

    COMPREPLY=()
    #pre="$3"
    #cur="$2"
    pre=${COMP_WORDS[COMP_CWORD-1]}
    cur=${COMP_WORDS[COMP_CWORD]}
    opts="-f -r -t -w -o --output -v --version -h --help"
    case "$cur" in
    -* )
        COMPREPLY=( $( compgen -W "$opts" -- $cur ) )
    esac
}
complete -F _pandoc -A file pandoc

运行程序的方式:

shell$ . ./pandoc # 加载上面的程序
$ pandoc -[Tab][Tab] # 试一下补全能用不

现在我来解释下这个程序。

bashcomplete -F _pandoc -A file pandoc

是这段代码中最为关键的一行。其实该程序起什么名字都不重要,重要的是要有上面这一行。上面这一行指定bash在遇到pandoc这个词时,调用_pandoc这个函数生成补全内容。(叫_pandoc其实只是出于惯例,并不一定要在前面加下划线)。complete -F后面接一个函数,该函数将输入三个参数:要补全的命令名、当前光标所在的词、当前光标所在的词的前一个词,生成的补全结果需要存储到COMPREPLY变量中,以待bash获取。-A file表示默认的动作是补全文件名,也即是如果bash找不到补全的内容,就会默认以文件名进行补全。

假设你在键入pandoc -o sth后,连击两下Tab触发了补全,_pandoc会被执行,其中:

  1. $1的值为pandoc
  2. $2的值为sth
  3. $3的值为-o
  4. 由于COMPREPLY为空(只有cur-开头时,COMPREPLY才会被填充),所以补全的内容是当前路径下的文件名。

你应该看到了,这里我把$2$3都注释掉了。其实

bashpre="$3"
cur="$2"

bashpre=${COMP_WORDS[COMP_CWORD-1]} # COMP_WORDS变量是一个数组,存储着当前输入所有的词
cur=${COMP_WORDS[COMP_CWORD]}

是等价的。不过后者的可读性更好罢了。

最后解释下COMPREPLY=( $( compgen -W "$opts" -- $cur ) )这一行。
opts就是pandoc的主选项列表。
compgen接受的参数和complete差不多。这里它接受一个以IFS分割的字符串"$opts"作为补全的候选项(IFS即shell里面表示分割符的变量,默认是空格或者Tab、换行)。假如没有一项跟当前光标所在的词匹配,那么它返回当前光标所在的词作为结果。(也即是不补全)

实现第一个目标用到的东西就是这么多。接下来就是第二个目标了。
在继续之前,你需要把Bash文档看一遍。若能把其中的一些选项尝试一下就更好了。

支持子选项

接下来的目标是支持Reader options/General writer options。想判断是否需要补全Reader options/General writer options,先要确认输入的词里面是否有-r-f(读),以及-w-t(写)。前面提到的COMP_WORDS就派上用场了。只需要将它迭代一下,查找里面有没有我们需要确认的词。

假设我们已经确认了需要补全子选项,接下来就应该往原来的补全项中添加子选项的内容。需要补全读选项的添加读方面的选项,需要补全写选项的添加写方面的选项。既然补全选项是一个字符串,那么把要添加的字符串接到原来的opts后面就好了。这里要注意一点,假如前面的操作里面已经把某类子选项添加到opts了,那么就需要避免重复添加。

目前的实现代码如下:

bash_pandoc() {
    local pre cur

    COMPREPLY=()
    #pre="$3"
    #cur="$2"
    pre=${COMP_WORDS[COMP_CWORD-1]}
    cur=${COMP_WORDS[COMP_CWORD]}
    complete_options() {
        local opts i
        opts="-f -r -t -w -o --output -v --version -h --help"
        for i in "${COMP_WORDS[@]}"
        do
            if [ "$i" == "-f" -o "$i" == "-r" ]
            then
                opts="$opts"" -R -S --filter -p"
                break
            fi
        done

        for i in "${COMP_WORDS[@]}"
        do
            if [ "$i" == "-t" -o "$i" == "-w" ]
            then
                opts="$opts"" -s --template --toc"
                break
            fi
        done
        echo "$opts"
    }

    case "$cur" in
    -* )
        COMPREPLY=( $( compgen -W "$(complete_options)" -- $cur ) )
    esac
}
complete -F _pandoc -A file pandoc

注意跟上一个版本相比,这里把原来的opts变量替换成了complete_options这个函数的输出。通过使用函数,我们可以动态地提供补全的来源。比如我们可以在函数里列出符合特定条件的文件名,作为补全的候选词。

支持给选项提供参数值来源

好了,现在是最后一个子任务。大致浏览一下pandoc的文档,基本上就两类参数:FORMATFILE。(其它琐碎的我们就不管了,嘿嘿)

FILE好办,默认就可以补全路径嘛。那就看看FORMATFORMAT分两种,一种是读的时候支持的FORMAT,另一种是写的时候支持的FORMAT,这个把文档里面的复制一份,改改就能用了。我们把读操作支持的FORMAT叫做READ_FORMAT,相对的,写操作支持的FORMAT叫做WRITE_FORMAT

补全的来源有了,想想什么时候把它放到COMPREPLY里去。前面补全选项的时候,是通过case语句中-*来匹配的。但是这里的FORMAT参数,只在特定选项后面才有意义。所以前面一直坐冷板凳的pre变量可以上场了。

pre中存储着光标前一个词。我们就用一个case语句判断前面是否是-f-r,还是-t-w。如果符合前面两个组合之一,用compgen配合READ_FORMATWRITE_FORMAT生成补全候选词列表,一切就跟处理opts时一样。由于此时继续参与下一个判断cur的case语句已经没有意义了,这里直接让它退出函数:

bashREAD_FORMAT="native json markdown markdown_strict markdown_phpextra 
    markdown_github textile rst html docbook opml mediawiki haddock latex"
WRITE_FORMAT="native json plain markdown markdown_strict 
    markdown_phpextra markdown_github rst html html5 latex beamer context 
    man mediawiki textileorg textinfo opml docbook opendocument odt docx 
    rtf epub epub3 fb2 asciidoc slidy slideous dzslides revealjs s5"

case "$pre" in
-f|-r )
    COMPREPLY=( $( compgen -W "$READ_FORMAT" -- $cur ) )
    return 0
    ;;
-t|-w )
COMPREPLY=( $( compgen -W "$WRITE_FORMAT" -- $cur ) )
    return 0
esac

. ./pandoc一下,试试看,是不是一切都ok?

诶呀,还有个问题!这次在尝试补全FORMAT的时候,还会把当前路径下的文件名补全出来。然而这并没有什么意义。所以在补全FORMAT的时候,得把路径补全关掉才行。

问题在于最后一句:complete -F _pandoc -A file pandoc。目前不管是什么情况,都会补全文件名。所以接下来得限定某些情况下才补全文件名。

第一步是移除最后一行的-A file,下一步是修改最底下的case语句,变成这样子:

bashcase "$cur" in
-* )
    COMPREPLY=( $( compgen -W "$(complete_options)" -- $cur ) );;
* )
    COMPREPLY=( $( compgen -A file ))
esac

只有在没有找到对应的补全时,才会调用对路径的补全。

最终版本:

bash_pandoc() {
    local pre cur

    COMPREPLY=()
    #pre="$3"
    #cur="$2"
    pre=${COMP_WORDS[COMP_CWORD-1]}
    cur=${COMP_WORDS[COMP_CWORD]}
    READ_FORMAT="native json markdown markdown_strict markdown_phpextra 
    markdown_github textile rst html docbook opml mediawiki haddock latex"
    WRITE_FORMAT="native json plain markdown markdown_strict 
    markdown_phpextra markdown_github rst html html5 latex beamer context 
    man mediawiki textileorg textinfo opml docbook opendocument odt docx 
    rtf epub epub3 fb2 asciidoc slidy slideous dzslides revealjs s5"

    case "$pre" in
    -f|-r )
        COMPREPLY=( $( compgen -W "$READ_FORMAT" -- $cur ) )
        return 0
        ;;
    -t|-w )
        COMPREPLY=( $( compgen -W "$WRITE_FORMAT" -- $cur ) )
        return 0
    esac

    complete_options() {
        local opts i
        opts="-f -r -t -w -o --output -v --version -h --help"
        for i in "${COMP_WORDS[@]}"
        do
            if [ "$i" == "-f" -o "$i" == "-r" ]
            then
                opts="$opts"" -R -S --filter -p"
                break
            fi
        done

        for i in "${COMP_WORDS[@]}"
        do
            if [ "$i" == "-t" -o "$i" == "-w" ]
            then
                opts="$opts"" -s --template --toc"
                break
            fi
        done
        echo "$opts"
    }

    case "$cur" in
    -* )
        COMPREPLY=( $( compgen -W "$(complete_options)" -- $cur) )
        ;;
    * )
        COMPREPLY=( $( compgen -A file ))
    esac
}
complete -F _pandoc pandoc

最后的问题

现在补全脚本已经写好了,不过把它放哪里呢?我们需要找到这样的地方,每次启动bash的时候都会自动加载里面的脚本,不然每次都要手动加载,那可吃不消。

.bashrc是一个(不推荐的)选择,不过好在bash自己就提供了在启动时加载补全脚本的机制。

如果你的系统有这样的文件夹:/etc/bash_completion.d,那么你可以把补全脚本放到那。这样每次bash启动的时候就会加载你写的文件。

如果你的系统里没有这个文件夹,你需要查看下/etc/bash_completion这个文件。bash启动的时候,会执行. /etc/bash_completion,你可以把你的补全脚本放在这个地方。

正如许多配置文件一样,凡是有/etc版本的也对应的~/.版本。有/etc/bash_completion,自然也有~/.bash_completion。如果你只想让自己使用这个补全脚本,或者没有root权限,可以放在~/.bash_completion

Bash补全脚本的内容就是这么多……请期待下一篇的Zsh补全脚本。

查看原文

赞 10 收藏 31 评论 0

zxldev 回答了问题 · 2017-07-04

解决求一套有关于IP地址的JS正则!

你可以直接在网上找到一个匹配IP的正则,但是网上的也不一定对,也不好做验证,那么我们就自己来写一个。

有个数字范围生成正则的工具。【工具可以网上找】
ip规则
0-255.0-255.0-255.0-255/0-32

0-255

([0-9]|[1-8][0-9]|9[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])

0-255.

([0-9]|[1-8][0-9]|9[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.

重复3次

(([0-9]|[1-8][0-9]|9[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}

再拼接一次0-255

(([0-9]|[1-8][0-9]|9[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-8][0-9]|9[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])

0-32

([0-9]|[12][0-9]|3[0-2])

全部拼接

(([0-9]|[1-8][0-9]|9[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-8][0-9]|9[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/([0-9]|[12][0-9]|3[0-2])

添加首位分隔符

^(([0-9]|[1-8][0-9]|9[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-8][0-9]|9[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/([0-9]|[12][0-9]|3[0-2])$
或者
(?^|\s)(([0-9]|[1-8][0-9]|9[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-8][0-9]|9[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])/([0-9]|[12][0-9]|3[0-2])(?:\s|$)

  • 分割符号根据实际情况更换

  • 如果不添加分隔符,则33.33.33.1/24会拼匹配成33.33.33.1/2或者 99933.33.33.1/24也会被匹配

关注 5 回答 4

zxldev 关注了问题 · 2017-07-03

解决关于最短路径算法在业务情景中实现的改进,与技术选型决策探讨

背景介绍

业务模型:

有这么一个场景。
数据库中存储了一些点元素【A,B,C...,Z】以及构建与点元素之上的线元素【AB,BC,...,YZ】。

比较特别的一点是,计算最短路径时,两点之间的线段只有满足某些特定要求(随请求传入)的时候才算做可达,可达的线元素权重均为1。

需求

任意选择两个网元,规定好特定需求,然后找出其中满足特定需求且跳数最少的线列表作为最短路径。

目前的解决办法

采用 Dijkstra 算法,根据使用场景做了个变种,可达路径的权值均设为1。

过程分为两个步骤,

  1. 从起始点开始,检索相关联的线段。

  2. 依据特定需求排除掉不可达线段,缓存起来。

  3. 将可达线段的另一端点作为下一跳起始点,重复1,2步骤。

  4. 找到第一次找到终点时终止。

由于该特定需求的可达判定需要动态的进行数据库查询,所以为了减小过程中的网络开销,第一版代码是由前人用存储过程写在数据库的执行的。这也导致了如下几个显著的缺点:

  • 维护成本高昂,这个算法的主体写了小1k行(其他支持性函数加起来另外有1k行),任何后来人对这个算法做任何改动,都得先花几天时间对存过逻辑进行梳理。(这点其实有更深层次的原因,相较java,存过更不容易拥有良好的封装,所以coder们稍不自觉,业务逻辑和算法主体也就紧紧的耦合在一起了)

  • 难以拓展,业务中相似的最短路径搜索需求其实挺多的,但是因为算法方案与特定需求的可达判定耦合比较紧密,所以每类需求的实现都有不同程度的重复代码,当经历了不同人的几轮维护之后,到我手中每处重复的代码都总有微小的不同,重构难度较大。

  • 计算速度偏慢,客户反馈过多次,跳数较多的情况下计算偏慢。其实也好理解,遍历过程中总有些核心点与多个点有线段链接,只要查询过程多经过几个核心节点,计算量都是爆炸。

可能的改造思路

改造思路

宏观来说,将主体算法移植进代码层并考虑替换掉 Dijkstra 算法

细节来说,逻辑上采用模板方法模式对算法主体进行封装,暴露出遍历下一跳的以及可达判定方法作为钩子方法。以完成算法主体逻辑与业务细节的解耦合。

面临的问题:

算法不熟:因为我对图论相关算法并不熟悉(其实别的算法积累也比较薄弱),所以目前并不知道什么样的算法更加适合这个可达权值均为1的场景,不知道有没有过来人能给点建议?

网络开销:将算法主体移植到代码层,固然提升了功能的可维护性以及可拓展性,但是由于查找下一跳以及判断是否可达,都需要动态查询。在原有算法思路下,过程中必然伴随着大量 sql 读请求的出现,相较于之前放在存储过程中,网络开销的增加也不可忽视,所以我很懵逼,有什么办法可以优化掉这层网络开销吗?建表进行数据缓存是一种可行的思路吗?

这个问题是在日常工作中,自己觉得还是挺典型的一类问题,它的解与算法有关又不局限与算法,还涉及了代码或者数据库层面的选型。作为一个新手,难免有自己的知识盲点,所以想拿出来与大家做一些探讨,如果社区大大们有什么好的建议,不论是您看过相关算法好的文章链接,还是关于代码解耦或者提效有什么好的建议/经验,还望不吝赐教,碰到好的回答我会及时采纳的

关注 9 回答 6

zxldev 赞了回答 · 2017-06-16

关于github项目怎么嵌入另外一个github项目的代码。

可以使用Git的submodule功能,在你的issaxite.github.io项目里运行git submodule add https://issaxite.github.io/lib/toggleAnimate/就行了,Git上传的时候只会传一个子模块的配置,而不会传整个toggleAnimate文件内容。

关注 3 回答 1

zxldev 提出了问题 · 2017-06-13

sqlalchemy 批量插入的数据,数据列不相等

# 初始化数据库连接:
engine = create_engine("xxxxx")
# 创建DBSession类型:
DBSession = sessionmaker(bind=engine)
session = DBSession()
# 测试没有问题的数据
rows_ok = [
    {"name":"aaa","otherdata":"exist_col_aaa"},
    {"name":"bbb","otherdata":"exist_col"},
]
# 测试出问题的数据
rows = [
    {"name":"aaa"},
    {"name":"bbb","otherdata":"exist_col"},
]
# User中有name,otherdata字段
session.execute(User.__table__.insert(),rows)
session.commit()
session.close()

如果批量插入数据中所有字典的key一致,则数据能保存

只要List中字典缺失一个key,会导致整个列被忽略

真实的情况列数很多,而且缺失数据也很多.有没有解决办法,或者使用其它方法?

或者将

rows = [
    {"name":"aaa"},
    {"name":"aaa"},
    {"name":"aaa"},
    {"name":"aaa"},
    {"name":"bbb","otherdata":"exist_col",....},
]

转化为

rows = [
    {"name":"aaa","otherdata":"",....},
    {"name":"aaa","otherdata":"",....},
    {"name":"aaa","otherdata":"",....},
    {"name":"aaa","otherdata":"",....},
    {"name":"bbb","otherdata":"exist_col",....},
]

关注 1 回答 1

zxldev 赞了回答 · 2017-06-12

解决输入一段不减的整数,如11111223333,怎么快速计算重复次数最多的那个数

这题不用对所有数字计数,那样很慢。
如果增长,则increment = 1
把数字当作字符串来处理,只要找出所有增长的点的位置就可以了,然后把相邻的点的位置相减,即可得到长度。
因为不一定是从1开始,所有可能的增长的点为:"12","23","34",。。。"78","89"

例如:
将11111223333转为字符串,查找增长点
结果:12-位置4,23-位置6
增长点补正(加1):12-位置5,23-位置7
初始位置补0,末尾补字符串长度len,得到
[0,5,7,11]
取相邻两点之差
[5,2,4]
所以,出现次数最多的是第一个元素,5次


以下为6/14日更新

@刀之魂 和 @tedBear 的想法很棒,比我原来的回答好多了。

受大家的启发,我来更新一个不需要遍历的算法:
由于increment = 1,所以看开头和结尾2个数,即可算出出现数字有几种
(如11111223333,开头为1,结尾为3,则出现的数为1到3,共3种)
那么,考虑出现N种数的情况
N=1就不用说了,等于白送答案。
先从简单的N=2说起吧,因为有些细节在N=2的情况说了,后面N>2的情况类似的细节就省略了。

N=2

2分采样
若len为奇数,取len/2处的数,这个数就是出现次数最多的数。
若len为偶数,len/2处是间隙,所以取该间隙前后2个数,这2个数相同的话,答案就是这个数,如果不同的话,答案是这两个数出现次数一样,并列最多。

N>2

N分采样,取0、len/N、2len/N、...、(N-1)len/N、len处的(N+1)个点,(由于间隙的情况太复杂,这里省略,请参考N=2的处理方法)
然后统计这(N+1)个点里面,出现次数最多的数,把出现次数最多的数叫做M1,出现第二多的叫做M2,依次类推
若 (M1的次数 - M2次数) 大于等于2,则答案为M1
若 (M1的次数 - M2次数) 小于2,那么
2N分采样(若2N>len,则len分采样),取len/2N、3len/2N、5len/2N、。。。。、处的N个点,与前面N分法时候取的(N+1)个点组合,重新求M1,M2,。。。。
若 (M1的次数 - M2次数) 大于等于2,则答案为M1
若 (M1的次数 - M2次数) 小于2,
则4N分采样(若4N>len,则len分采样)
。。。。依次类推,8N,16N。。。。
直至找到 (M1的次数 - M2次数) 大于等于2为止。其中若变为len分采样,结束条件不需要(M1的次数 - M2次数)大于等于2,M1就是答案。

小结

这个算法大部分时候是比较快的,最差的情况是N个数的各个次数都相差很小,或者都相同,那就会进入最后的len分采样,等于遍历,那还不如一开始就直接遍历比较快。
但是数据的分布越是不均匀,这个算法就越快。

杂谈

写完这个算法之后,我觉得有似曾相识的感觉,大家了解通信原理的有没有一样的感觉?
如果把这段不减整数映射到频域,那么这就是递增阶梯的方波,问题就是确定其最长一段方波,如果这个方波太长,数据量太大,我们可以对其特征点采样来压缩数据,根据奈奎斯特采样定理(或者叫香农采样定理):

当采样频率fs.max大于信号中最高频率fmax的2倍时(fs.max>2fmax),采样之后的数字信号完整地保留了原始信号中的信息。

上文的2N分采样与此正好相似,是不是很有趣~

(描述的比较乱,大家如果有建议请指正)

关注 14 回答 8

zxldev 关注了用户 · 2017-06-12

zhuangzebo @zhuangzebo

Pythonista
/* stackoverflow */
https://stackoverflow.com/use...

关注 8

认证与成就

  • 获得 31 次点赞
  • 获得 13 枚徽章 获得 0 枚金徽章, 获得 3 枚银徽章, 获得 10 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2015-10-09
个人主页被 877 人浏览