d3总结

项目原因,需要使用web绘制图表,比较靠谱的有echart和d3。

echart:应该是百度的项目,首页做的很漂亮,文档也比较全。个人感觉是一个入手较易,主要在学习怎么配参数,地图是echart的一个亮点。缺点就明显了,入手容易的肯定高度封装过,很难根据自己的需求定制化。而且使用了cavas绘图,在移动端性能不如svg。个人感觉echart适合一些时间比较紧迫,设计没那么明确的场合,直接套上echart,就已经很漂亮了。

d3:大名鼎鼎,受欢迎程度超过jquery,首页的绚丽特效让人眼花,教程的话强烈推荐http://www.ourd3js.com/wordpress/,这个教程好像出了书,叫精通D3.js,基本上把教程撸一遍就没啥大问题了。

d3的优点:用过一段d3后,感觉d3就是svg的jquery,把svg的操作封装为更易用的接口,并提供各种数据可视化的接口。所以d3很灵活,因为你可以用d3在svg中添加一个点、线、饼等等,svg性能不错。链式写法。

缺点:入手稍微慢点,不过撸一遍教程也就一下午的时间。svg的接口网上文档比较少,有些用法stackoverflow都找不到。

如果是学习d3的,推荐前面的教程网站,这里只是个人备忘录,想到哪里说到哪里的流水账,记录那些让人眼前一亮或者眼前一黑的点,以及几个开发中反复查询的用法。

好了,出发

1.

var svg = d3.select("body")
    .append("svg")
    .attr("width", svgWidth)
    .attr("height", svgHeight);

首先,你要在一个dom中添加svg,需要两个参数,width和height,一般我是这么给的

this.svgContent = document.getElementById("id");
this.svgWidth = this.svgContent.clientWidth;
this.svgHeight = this.svgContent.clientHeight;

踩坑:需求在指定操作后才绘制该图标,dom display:none的时候,clientWidth和clientHeight是0,所以svg等dom display正常后再添加。

接下来最好设置一下svg的padding,用来应付后面出现的各种文字绘制超出不显示,UI修改等灵细操作。

this.padding = {left: Ruler.size_1920(200), right: Ruler.size_1920(220), top: Ruler.size_1920(66), bottom: Ruler.size_1920(28)};

因为svg直接传的都是px,所以需要根绝屏幕做一下自适应,size_1920函数如下:

let deviceWidth = document.documentElement.clientWidth;
return (px * deviceWidth) / 1920;

坐标轴:

图标中用到最多的是坐标轴,使用坐标轴首先要设定比例尺,目前共用到了4种比例尺

线性比例尺

this.yScale = d3.scale.linear()
   .domain([0, this.threshold])
   .range([this.svgHeight - this.padding.top - this.padding.bottom, 0]);

domain中设置数据的min/max,range中设置svg实际位置。

备注:svg是以左上角为原点的,而常规视角中原点是左下角,所以这里的range起止位置是 svgHeight-0,即起始位置是下边缘y=svgHeight,终止位置是上边缘y=0。这一点需要谨记,后面有许多计算x、y位置的,y的位置总要反着想。其他比例尺也是一样的。

离散比例尺

this.xScale = d3.scale.ordinal()
    .domain(d3.range(data.length))
    .rangeRoundBands([0, this.svgWidth - this.padding.left - this.padding.right]);

or

this.xScale = d3.scale.ordinal()
    .domain(["1","2","3","4"])
    .rangeRoundBands([0, this.svgWidth - this.padding.left - this.padding.right]);

时间比例尺

时间比例尺适用于一些以时间为坐标轴的情况

this.now = new Date();
let nextDay = new Date(this.now.getTime() + 1000 * 3600 * 24);
// 这里要搞成UTC时间
this.nowUTC = this.now.getUTCFullYear() + "-" + (this.now.getUTCMonth() + 1) + "-" + (this.now.getUTCDate() < 10 ? "0" : "") + this.now.getUTCDate() + "T00:00";
this.nextUTC = nextDay.getUTCFullYear() + "-" + (nextDay.getUTCMonth() + 1) + "-" +
    (nextDay.getUTCDate() < 10 ? "0" : "") + nextDay.getUTCDate() + "T00:00";
this.xScale = d3.time.scale()
    .domain([new Date(this.nowUTC), new Date(this.nextUTC)])
    .range([0, this.svgWidth - this.padding.left - this.padding.right]);

备注:比如这里是今天到明天,第二天nextDay的算法,就用秒,其他都不行。UTC时间一定要注意,所有时间都改成UTC时间,所有get一律要加UTC。

颜色比例尺

// 颜色比例尺
var color = d3.scale.category10() // 20  30 都有

这是个比较特殊的比例尺,不是用来画坐标轴的,而是把颜色均匀分割,category10,category20等等都有,填一个index,输出一个色值。在有些d3的版本中,颜色比例尺没用放在scale里面。

坐标轴

可能还有更多种类的比例尺,目前还没有接触,下面就开始生成坐标轴数据。

let gridXAxis = d3.svg.axis()
    .scale(this.xScale)
    .orient("top")
    .innerTickSize(this.svgHeight - this.padding.top - this.padding.bottom)
    .outerTickSize(0)
    .ticks(d3.time.hour, 1)
    .tickFormat(function (d, i) {
        return "";
    })
    .tickPadding(10);

几个常用参数

scale,必要参数,把比例尺填进去

orient,非必填,刻度相对坐标轴的位置 top bottom left right

ticks,非必填,刻度个数 注:ticks只是参考,最终生成刻度个数以数据为准,比如数据需要25个刻度,但ticks填了17,那d3就没办法了,会生成一个d3生成的刻度个数中选跟17最接近的。

innerTickSize,非必填,内刻度高,默认是6。 注:这里设置了innerTickSize是整个svg的高度,和y轴innerTickSize设置整个svg的宽配合行程网格效果,正常的坐标轴就再画一个,注意高度计算时错开

let xAxis = d3.svg.axis()
    .scale(this.xScale)
    .orient("bottom")
    .ticks(d3.time.hour, 1)
    .outerTickSize(0)
    .tickFormat(function (d, i) {
    });

outerTickSize,非必填,外刻度高

tickFormat,非必填,刻度值,默认为空  注:这是一个数据绑定参数,即根据数据生成不同的值

tickPadding,非必填,刻度文字与坐标轴的间距

真正的绘制

this.svg.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(" + this.padding.left + "," + (this.svgHeight - this.padding.top + this.xPadding + this.doubleLinePadding) + ")")
    .call(xAxis);
this.svg.append("g")
    .attr("class", "x axis grid")
    .attr("transform", "translate(" + this.padding.left + "," + (this.svgHeight - this.padding.top + this.xPadding) + ")")
    .call(gridXAxis);

 

给svg中添加g元素,call绑定坐标轴数据,attr就是给g元素添加各种属性,class(是不是很熟悉),transform。 注:svg中没有top left  margin等属性,大的位移使用transform。

曲线

坐标轴绘制完后,就是曲线的绘制,先生成曲线绘制函数

let _line = d3.svg.line()
    .x(function (d) {
        let x = self.xScale(d.time);
        if (x < 0) {
            return 0;
        }
        return self.xScale(d.time);
    })
    .y(function (d) {
        let x = self.xScale(d.time);
        if (x < 0) {
            return 0;
        }
        return self.yScale(d.uv);
    })
    // .interpolate("cardinal");
    .interpolate("monotone");

曲线绘制函数参数

x、y就是根据数据来计算坐标,这里是比例尺大显身手的地方。

interpolate,线段怎么弯曲,讲的最好的是这个地址

http://www.oxxostudio.tw/articles/201411/svg-d3-02-line.html

注:basis曲线保证了优美,没保证曲线与数据的一致,像我这种人是完全无法接受的。

绘制曲线

let paths = this.svg.selectAll(".data-line").data(data);

这一行代码解读一下就是选择所有class是data-line的元素,然后给它绑定数据。data-line哪里来的?这里涉及到d3中一个概念,update  enter  exit

selectAll得到的元素跟数据相比,有三种可能,元素个数 大于/小于/等于数据个数。

数据超出元素个数的部分叫enter,比如第一次绘制时,所有元素都属于paths.enter

paths.enter()
    .append("path")
    .attr("class", "data-line gaussian-shadow")
    .attr("d", function (d) {
            return _line(d.data);
    })
    .attr("transform", "translate(" + this.padding.left + "," + this.padding.top + ")")
    .style("stroke", function (d, i) {
        return d3.scale.category10(i);
    })
    .attr("fill", "none");

paths元素比较简单直接,基本上只需要给一个属性d就能画出来了,d可以用前面的曲线函数直接生成。

stroke,线条颜色,这里可以用颜色比例尺 d3.scale.category10(i)

fill就是填充颜色,paths填充的是一个面。

不超出的部分叫update,即需要更新的部分,paths.update可以看到,也可以直接更新数据,这里是数据驱动的最直观表现

paths.attr("d", function (d) {
    if (d && d.data) {
        return _line(d.data);
    }
});

数据少于元素个数的部分叫exit,一般exit只有一个用法

// 去掉多余的曲线
paths.exit().remove();

到这里,一个坐标轴差不多出来了,但,是不是少了点啥,对,UED最喜欢的灵魂一击,动画。

曲线进入/更新的时候要动画怎么办

d3的动画很好写

paths.transition()
    .duration(2000)
    .ease("sin")
    .attr("d", function (d) {
        if (d && d.data) {
            return _line(d.data);
        }
    });

增加 transition ease 和 delay即可,transition前后是动画的起始和结束状态。

绘制一个圆

let circles = this.svg.selectAll("circle")
    .data(this.axisData);
circles.attr("cx", this.xScale(this.now))
        .attr("cy", function (d) {
            if (d && d.data) {
                return this.yScale(d.data)
            }
        })
}
circles.exit().remove();
circles.enter()
    .append("circle")
    .attr("fill", function (d) {
        return d.color;
    })
    .attr("transform", "translate(" + this.padding.left + "," + this.padding.top + ")")
    // .attr("fill-opacity", "0.4")
    .attr("cx", this.xScale(this.now))
    .attr("cy", function (d) {
        if (d && d.data) {
            return this.yScale(d.data)
        }
    })
    .attr("r", 10);

矩形

let rectContainers = this.svg.selectAll("item-title")
    .data(data)
    .enter()
    .append("g")
    .attr("class", "item-title")
    .attr("transform", "translate(" + this.padding.left + "," + this.padding.top + ")");
rectContainers.append("rect")
    .attr("fill", function (d) {
        return d.color;
    })
    .attr("x", this.xScale(this.now) + 30)
    .attr("y", function (d) {
        if (d && d.data) {
            return yScale(d.data);
        }
    })
    .attr("width", function (d) {
        return 20;
    })
    .attr("height", rectHeight)
    .attr("rx", rectRadius)
    .attr("ry", rectRadius)
    .attr("fill-opacity", 0.8);

半圆角矩形是用path生成的,顶部圆角矩形

var topRoundedRect = function(x, y, width, height, radius) {
    return "M" + x + "," + y
        + "v" + ( radius - height)
        + "a" + radius + "," + radius + " 0 0 1 " + radius + "," + -radius
        + "h" + (width - 2 * radius)
        + "a" + radius + "," + radius + " 0 0 1 " + radius + "," + radius
        + "v" + (height - radius)
        + "z";
};
var rightRoundedRect = function(x, y, width, height, radius) {
    return "M" + x + "," + y
        + "h" + (width - radius)
        + "a" + radius + "," + radius + " 0 0 1 " + radius + "," + radius
        + "v" + (height - 2 * radius)
        + "a" + radius + "," + radius + " 0 0 1 " + -radius + "," + radius
        + "h" + (radius - width)
        + "z";
};
this.svg.selectAll(".data-rect")
 .data(data)
 .enter()
 .append("path")
 .attr("class", "data-rect")
 .attr("transform", "translate(" + this.padding.left + "," + this.padding.top + ")")
 .attr("fill", function(d, i) {
 return this.colors[i];
 }.bind(this))
 .attr("d", function (d, i) {
 let x = this.xScale(i) + this.rectPadding/2;
 let y = this.yScale(this.yScale.domain()[0]);
 let radius = 0;
 let width = this.xScale.rangeBand() - this.rectPadding;
 if (d.albm_cnt != 0) {
 radius = this.rectRadius;
 }
 let height = this.yScale(this.yScale.domain()[0]) - (this.yScale(d.albm_cnt)) + this.extraHeight;
 return topRoundedRect(x, y, width, height, radius);
 }.bind(this));

topRoundRect和rightRoundRect乍一看完全搞不懂,这里讲的比较详细https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths,仔细观察下,就是

M  起始x y坐标

h 纵移

v 横移

a 比较复杂 前两个参数是圆角的x y radius

中间三个参数 001是三个flag,只能是0/1 圆角矩形都是001

第一个代表弧线角度是否要大于180

第二个代表正角度还是负角度

第三个代表正方向还是反方向

z是合拢

最后两个参数是终止的x y位置

当前了,还有更多的L C S等等,这里不研究了

文字

rectContainers.append("text")
    .attr("class", "item-text")
    .attr("fill", "white")
    .attr("x", this.xScale(this.now) + 30 + 14)
    .attr("y", function (d) {
        if (d && d.dataSet) {
            return  self.yScale(AxisView.getPvByTime(d.dataSet, self.now)) + 6;
        }
    })
    .text(function (d) {
        return d.type;
    });

到这里,d3绘制的套路差不多都熟悉了,append data enter transform attr,拿来画个饼图吧!

生成饼图数据

let pie = d3.layout.pie().sort(null);
this.pieData = pie(percentArray);

查看一下this.pieData,你会发现数据增加了 startEngle 和 endEngle

注:饼图默认会按大小排序,如果不需要排序,增加一个sort(null)即可

弧线函数

this.arc = d3.svg.arc()
    .innerRadius(0)
    .outerRadius(self.innerRadius);
let arcs = this.svg.selectAll(".time-pie")
    .data(this.pieData)
    .enter()
    .append("g")
    .attr("class", "time-pie")
    .attr("transform", "translate(" + this.centerX + "," + this.centerY + ")");
arcs.append("path")
    .attr("fill", function (d, i) {
        return this.colors[i];
    }.bind(this))
    .attr('d', function(d) {
        return this.arc(d);
    }.bind(this));

饼图动画不能使用d了,要使用attrTween

简单的饼图动画

arcs.append("path")
    .attr("fill", "#fff")
    .attr("fill-opacity", 0.2)
    .transition()
    .duration(this.fadeInTime)
    .attrTween('d', function(d) {
        var i = d3.interpolate(d.startAngle+0.1, d.endAngle);
        return function(t) {
            d.endAngle = i(t);
            return arc(d);
        };
    }.bind(this));

高级饼图动画,自己计算delay的时间,让饼一点一点出来

arcs.append("path")
    .attr("fill", function (d, i) {
        return this.colors[i];
    }.bind(this))
    .transition()
    .delay(function (d, i) {
        if (data) {
            let _percent = 0;
            for (let j = 0; j < i; j++) {
                try {
                    _percent += parseInt(data[j].percent)
                } catch (e) {
                    _percent += 100 / data.length;
                }
            }
            return this.pieAnimTime * _percent / 100;
        }
    }.bind(this))
    .duration(function (d) {
        let _time = this.pieAnimTime / data.length;
        if (d && d.data) {
            let percent = parseInt(d.data);
            _time = this.pieAnimTime * percent / 100;
        }
        this.lastAnimTime+=_time;
        return _time;
    }.bind(this))
    .ease("linear")
    .attrTween('d', function(d) {
        var i = d3.interpolate(d.startAngle+0.1, d.endAngle);
        return function(t) {
            d.endAngle = i(t);
            return this.arc(d);
        }.bind(this);
    }.bind(this));

往饼图中间添加一个圆点

this.centriodData = Array.apply([0,0], this.pieData).map(function (val, index) {
    // return this.arc.centroid(val);
    return {
        right: (val.startAngle+val.endAngle)/2<Math.PI,
        centroidPos: this.arc.centroid(val)
    };
    return this.arc.centroid(val);
}.bind(this));

注:centriodData和Angle是钝/锐角的计算提出来,是因为在某些低配机子上,偶现绑定计算错误。

this.svg.selectAll(".time-circle")
    .data(this.pieData)
    .enter().append("circle")
    .attr("class", "time-circle")
    .attr("transform", "translate(" + this.centerX + "," + this.centerY + ")")
    .attr("cx", function(d, i) {
        // var pos= this.arc.centroid(d);
        var pos= this.centriodData[i] && this.centriodData[i].centroidPos;
        return pos && pos[0] * 1.65;
    }.bind(this))
    .attr("cy", function(d, i) {
        // var pos= this.arc.centroid(d);
        var pos= this.centriodData[i] && this.centriodData[i].centroidPos;
        return pos && pos[1] * 1.65;
    }.bind(this))
    .attr("fill", "#fff")
    // .attr("fill-opacity", 0.1)
    // .transition()
    // .duration(this.fadeInTime)
    .attr("fill-opacity", 1)
    .attr("r", Ruler.size_1920(6));

画一个折线

let line = this.svg.selectAll(".line")      //添加文字和弧之间的连线
    .data(this.pieData) //返回是pie(data0)
    .enter().append("g")
    .attr("class", "line")
    .attr("transform", "translate(" +  this.centerX + "," + this.centerY + ")")
    .append("polyline")
    .attr('points', function(d, i) {
        // var pos1= this.arc.centroid(d),pos2= this.arc.centroid(d),pos3= this.arc.centroid(d);
        var pos1=[this.centriodData[i].centroidPos[0], this.centriodData[i].centroidPos[1]] ,pos2= [this.centriodData[i].centroidPos[0], this.centriodData[i].centroidPos[1]],pos3= [this.centriodData[i].centroidPos[0], this.centriodData[i].centroidPos[1]];
        pos1[0]*=1.65,pos1[1]*=1.65;
        pos2[0]*=2,pos2[1]= pos2[1] *2;
        pos3[0]=(this.centriodData[i].right?this.lineLen:-this.lineLen);
        pos3[1]= pos3[1]*2;
        //pos1表示圆弧的中心往上,pos2是圆弧边,pos3就是将pos2平移后得到的位置
        //三点链接在一起就成了线段。
        return [pos1,pos2,pos3];
    }.bind(this))
    .style('fill', 'none')
    .style('stroke', "#fff")
    .style('stroke-opacity', 0.6)
    .style('stroke-width', Ruler.size_1920(3) + "px");
    // 这里是动画,直线慢慢生成的动画
    // .attr("stroke-dasharray", totalLen + " " + totalLen)
    // .attr("stroke-dashoffset", totalLen)
    // .transition()
    // .duration(this.lineAnimTime)
    // .ease("linear")
    // .attr("stroke-dashoffset", 0);

到这里,一般的坐标轴、饼图已经难不倒了,而且自己想添加什么就添加什么,无非就是计算x、坐标,d3就是一堆图形函数,帮你计算坐标而已,那还有什么新东西吗?假如UED说想要阴影,光效,这里介绍一下svg里面一个很牛叉的东西,filter

let defs = svg.append("defs");
let filter = defs.append("filter")
    .attr("id", "gaussianShadowFilter");
// append gaussian blur to filter
// filter.append("feMorphology")
//     .attr("operator", "dilate")
//     .attr("radius", 3);
filter.append( 'feGaussianBlur' )
    .attr( 'in', 'SourceAlpha' )
    .attr( 'stdDeviation', 2 ) // !!! important parameter - blur
    .attr( 'result', 'blur' );

// append offset filter to result of gaussion blur filter
filter.append( 'feOffset' )
    .attr( 'in', 'blur' )
    .attr( 'dx', 10 ) // !!! important parameter - x-offset
    .attr( 'dy', 1 ) // !!! important parameter - y-offset
    .attr( 'result', 'offsetBlur' );


// merge result with original image
let feMerge = filter.append( 'feMerge' );

// first layer result of blur and offset
feMerge.append( 'feMergeNode' )
    .attr( 'in", "offsetBlur' );

// original image on top
feMerge.append( 'feMergeNode' )
    .attr( 'in', 'SourceGraphic' );

实话说,这个东西我也只是到抄过来调参的地步,比如dx dy。理解上只知道特效输出为result,下一个特效的in是这个result,其他也是一问三不知,而且看了之后着实没有深入了解下去的想法,简直就是手写Photoshop。

下面是几个介绍filter的地址,留下以备不时之需

1.https://www.w3.org/TR/SVG/filters.html

2.https://www.smashingmagazine.com/2015/05/why-the-svg-filter-is-awesome/

3.https://jorgeatgu.github.io/svg-filters/

 

 

 

 

Ubuntu 14.04服务器Apache禁止某些User Agent抓取网站

最近网站上,被某些爬虫占用了太大的资源,导致访问不畅,网上搜了一下禁止某些爬虫的办法。

下面这些方法需要同时实施才足够稳妥。

1.在网站根目录下修改或创建.htaccess文件

<IfModule mod_rewrite.c>
 RewriteEngine On
 RewriteCond %{HTTP_USER_AGENT} ^YisouSpider* [NC]
 RewriteRule ^(.*)$ - [F,L]
</IfModule>

注意,这个过滤条件需要添加到整个.htaccess文件的头部,否则可能由于其他的过滤条件而跳过了这个过滤条件,导致某些情况下不生效。

2.修改Apache2的配置文件

$ vim /etc/apache2/sites-available/000-default.conf

禁止某些User-Agent的访问

<Directory "/var/www/wordpress">
 SetEnvIfNoCase User-Agent ".*(YisouSpider)" denySpider
 Order allow,deny
 Allow from all
 deny from env=denySpider
</Directory>

注意:

env=denySpider

中间不可用空格,否则无法成功生效。

3.网站根目录下面增加robot.txt,禁止爬虫

#一搜的爬虫访问过于频繁
User-agent:YisouSpider
Disallow:/

4.对于使用ProxyPass,ProxyPassReverse代理转发的情况

使用如下配置进行过滤

<Proxy "*">
 SetEnvIfNoCase User-Agent ".*(YisouSpider)" denySpider
 Order Allow,Deny
 Allow from all
 Deny from env=denySpider
</Proxy>

注意:

env=denySpider

中间不可用空格,否则无法成功生效。

5.验证刚刚的服务器设置是否已生效

刚刚的设置完成后,我们需要修改浏览器的User Agent,来验证一下我们的设置是否已经生效了。
Chrome-55.0为例,Windows下面按下F12,在弹出的窗口中进行如下操作:

参考链接


服务器反爬虫攻略:Apache/Nginx/PHP禁止某些User Agent抓取网站

被优化为无排版和缩进的JavaScript代码,如何调试?

有时候网页上的JavaScript代码被优化器优化过后,会丢失原来的换行,导致整个的代码蜷缩成一行,完全没办法调试,此时我们就需要借助Chrome或者FireFox自带的代码调整功能来实现代码的调试了,如下图:

参考链接


毫无排版和缩进的 JavaScript 代码,怎么阅读?

Mac OSX下VirtualBox直接使用物理硬盘作虚拟机磁盘

目前VirtualBox只能用命令行来建立磁盘才可以使用物理硬盘。

如果是USB磁盘的话,那么需要从"关于本机"->"概览"->"系统报告"->"USB"中找到磁盘的名字,比如"disk2".

我们假定VirtualBox安装在"
/Applications/VirtualBox.app/"这个目录下面,要在"~/VirtualBox\ VMs/Ubuntu/"目录下面生成文件,则执行如下命令:

$ diskutil umountDisk disk2
$ sudo chown `whoami` /dev/disk2
$ mkdir ~/VirtualBox\ VMs
$ mkdir ~/VirtualBox\ VMs/Ubuntu
$ sudo /Applications/VirtualBox.app/Contents/MacOS/VBoxManage internalcommands createrawvmdk -filename ~/VirtualBox\ VMs/Ubuntu/Ubuntu.vmdk -rawdisk /dev/disk2
$ sudo chown `whoami` ~/VirtualBox\ VMs/Ubuntu/Ubuntu.vmdk

/dev/disk2表示机器上的第二块硬盘,每次插入新磁盘后,就会出现类似/dev/disk*的一个路径名。

最后,新建一个虚拟机,然后指定使用刚刚创建的磁盘即可。

参考链接


How do I install Mavericks onto external HD but from inside VirtualBox

macOS Sierra/Catalina/Big Sur支持NTFS/EXT4文件系统

1.安装`HomeBrew`

按照 让Mac也能拥有apt-get类似的功能——Brew 的介绍配置安装`HomeBrew`。

2.安装`osxfuse`/`ext4fuse`/`ntfs-3g`

$ brew install osxfuse

# macOS Big Sur 需要确保 osxfuse 的版本大于等于 3.11.2,暂时不要升级到4.x版本,否则可能无法成功挂载

$ brew reinstall osxfuse

$ sudo mkdir /usr/local/sbin

$ sudo chown -R `whoami` /usr/local/sbin

$ brew reinstall ntfs-3g

$ brew install ext2fuse
 
$ brew install ext4fuse

卸载命令为:

$ sudo bash /Library/Filesystems/osxfuse.fs/Contents/Resources/uninstall_osxfuse.app/Contents/Resources/Scripts/uninstall_osxfuse.sh

3.挂载磁盘设备

如果是USB磁盘的话,那么需要从"关于本机"->"概览"->"系统报告"->"USB"中找到磁盘的名字,比如"disk2".

这个信息也可以通过在终端执行命令看到:

$ diskutil list
/dev/disk0 (internal):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                         500.3 GB   disk0
   1:                        EFI EFI                     314.6 MB   disk0s1
   2:                 Apple_APFS Container disk1         500.0 GB   disk0s2

/dev/disk1 (synthesized):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      APFS Container Scheme -                      +500.0 GB   disk1
                                 Physical Store disk0s2
   1:                APFS Volume Macintosh HD            473.8 GB   disk1s1
   2:                APFS Volume Preboot                 49.2 MB    disk1s2
   3:                APFS Volume Recovery                509.9 MB   disk1s3
   4:                APFS Volume VM                      3.2 GB     disk1s4

/dev/disk2 (external, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:     FDisk_partition_scheme                        *500.1 GB   disk2
   1:                      Linux                         500.1 GB   disk2s1

如果已知是"EXT4"磁盘格式的话,则使用如下命令:

# 只读挂载
$ sudo ext4fuse /dev/disk2s2 ~/Desktop/disk2s2

# 读写挂载
$ sudo ext4fuse /dev/disk2s2 ~/Desktop/disk2s2 -o rw

如果已知是"NTFS"磁盘格式的话,则使用如下命令:

# 先卸载系统的自动挂载
$ sudo diskutil unmount /dev/disk2

# 读写挂载
$ sudo /usr/local/sbin/mount_ntfs /dev/disk2 ~/Desktop/disk2

参考链接


ubuntu 16.04下载Android源代码

由于众所周知的原因,我们是没办法正常下载Android的源代码的,因此只能使用国内的镜像来操作了。

1.安装repo工具

$ sudo apt-get install repo

2.在需要存储代码的地方创建文件夹

$ mkdir ~/Android_Source

$ cd ~/Android_Source

# 国内手工下载 repo 源代码,解决 “fatal: Cannot get https://gerrit.googlesource.com/git-repo/clone.bundle”
 
$ git clone https://gerrit-googlesource.lug.ustc.edu.cn/git-repo .repo/repo

3.使用镜像下载Android源代码
omapzoom.org的镜像

$ repo init -u git://git.omapzoom.org/platform/manifest

清华大学的镜像

$ repo init -u https://aosp.tuna.tsinghua.edu.cn/platform/manifest

上面执行之后是拉取全部的代码。

如果要使用某个特定分支的版本的源代码的话,则则初始化的时候指定分支,比如我想要Android 7.0.0_r21的分支,则执行如下命令

$ repo init -u https://aosp.tuna.tsinghua.edu.cn/platform/manifest -b android-7.0.0_r21

4.同步代码

$ repo sync -j4

5.列出全部分支

$ cd .repo/manifests && git branch -a | cut -d / -f 3

6.切换到指定分支

$ repo start Android_7.0.0_r21 7.0.0_r21 --all

7.查看当前的分支

$ repo branches

8.删除不用的本地分支

$ repo abandon Android_7.0.0_r21

参考链接


WDMyCloud编译TestDisk&PhotoRec 7.0/7.1

1.按照How to successfully build packages for WD My Cloud from source中的介绍,搭建完成WDMyCloud的编译环境

2.下载TestDisk & PhotoRec 7.1的源代码

$ wget https://www.cgsecurity.org/testdisk-7.1-WIP.tar.bz2

3.解压缩源代码

$ tar -xjf testdisk-7.1-WIP.tar.bz2

4.安装依赖库

$ apt-get install libncurses5-dev

$ apt-get install uuid-dev

5.编译源代码

$ cd ~/wdmc-build/testdisk-7.1-WIP

$ chroot build

$ mount -t proc none /proc
$ mount -t devtmpfs none /dev
$ mount -t devpts none /dev/pts

$ export DEBIAN_FRONTEND=noninteractive
$ export DEBCONF_NONINTERACTIVE_SEEN=true
$ export LC_ALL=C
$ export LANGUAGE=C
$ export LANG=C
$ export DEB_CFLAGS_APPEND='-D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE'
$ export DEB_BUILD_OPTIONS=nocheck

$ cd ~/testdisk-7.1-WIP

$ ./configure

$ make

编译好的文件在src目录下面。

上面的编译方法编译出来的没办法生成安装包,如果需要安装包的版本,可以直接从Debian源中下载已经适配过的源代码进行编译,目前已经被适配的版本是testdisk_7.0-2

使用如下方式编译:

$ cd ~/wdmc-build/64k-wheezy

$ chroot build

$ mount -t proc none /proc
$ mount -t devtmpfs none /dev
$ mount -t devpts none /dev/pts

$ export DEBIAN_FRONTEND=noninteractive
$ export DEBCONF_NONINTERACTIVE_SEEN=true
$ export LC_ALL=C
$ export LANGUAGE=C
$ export LANG=C
$ export DEB_CFLAGS_APPEND='-D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE'
$ export DEB_BUILD_OPTIONS=nocheck

$ cd root
$ mkdir testdisk_7.0-2
$ cd testdisk_7.0-2

$ apt-get install ca-certificates
$ apt-get install dh-autoreconf

$ wget http://http.debian.net/debian/pool/main/t/testdisk/testdisk_7.0-2.dsc
$ wget http://http.debian.net/debian/pool/main/t/testdisk/testdisk_7.0.orig.tar.bz2
$ wget http://http.debian.net/debian/pool/main/t/testdisk/testdisk_7.0-2.debian.tar.xz

$ tar -jxvf testdisk_7.0.orig.tar.bz2

$ xz -d testdisk_7.0-2.debian.tar.xz

$ tar -xvf testdisk_7.0-2.debian.tar

$ mv debian/ testdisk-7.0/

$ rm -rf testdisk_7.0-2.debian.tar

$ cp testdisk_7.0-2.dsc testdisk-7.0/testdisk_7.0-2.dsc

$ dpkg-buildpackage -d -b -uc

参考链接


源码包: testdisk (7.0-2)

升级Struts2之后报告HTTP Status 500 - java.lang.ClassNotFoundException: org.apache.jsp.index_jsp以及org.apache.jasper.JasperException: Unable to compile class for JSP

升级Struts22.3.20.1版本升级到2.5.5版本后可能报告如下错误:

HTTP Status 500 - Unable to compile class for JSP:

type Exception report

message Unable to compile class for JSP:

description The server encountered an internal error that prevented it from fulfilling this request.

exception

org.apache.jasper.JasperException: Unable to compile class for JSP:

An error occurred at line: [38] in the generated java file: [/var/lib/tomcat7/work/Catalina/localhost/Tools/org/apache/jsp/index_jsp.java]
The method getJspApplicationContext(ServletContext) is undefined for the type JspFactory

Stacktrace:
	org.apache.jasper.compiler.DefaultErrorHandler.javacError(DefaultErrorHandler.java:103)
	org.apache.jasper.compiler.ErrorDispatcher.javacError(ErrorDispatcher.java:366)
	org.apache.jasper.compiler.JDTCompiler.generateClass(JDTCompiler.java:468)
	org.apache.jasper.compiler.Compiler.compile(Compiler.java:378)
	org.apache.jasper.compiler.Compiler.compile(Compiler.java:353)
	org.apache.jasper.compiler.Compiler.compile(Compiler.java:340)
	org.apache.jasper.JspCompilationContext.compile(JspCompilationContext.java:657)
	org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:357)
	org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:390)
	org.apache.jasper.servlet.JspServlet.service(JspServlet.java:334)
	javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
	org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter.doFilter(StrutsPrepareAndExecuteFilter.java:110)
note The full stack trace of the root cause is available in the Apache Tomcat/7.0.52 (Ubuntu) logs.

也有可能发生如下错误信息:

HTTP Status 500 - java.lang.ClassNotFoundException: org.apache.jsp.index_jsp

具体信息如下图:

1422265899_66497

比较诡异的是,在Tomcat 8的环境下,是可以正常运行的,但是在Tomcat 7环境下却会报错。造成这个现象的原因就是在引入的Jar包中包含了jsp-api.jar这个Jar包,只要在最后生成的war包中排除这个文件即可。

Struts2从2.3.20.1升级到2.5.5版本后报错:ClassNotFoundException: org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter

以前项目一直使用Struts22.3.20.1版本,这个版本是IntelliJ Idea新建项目的时候默认指定的版本,但是这个版本存在漏洞,必须进行升级,干脆一不做二不休,直接升级到最新的2.5.5版本,但是运行的时候报告如下错误信息:

严重: Exception starting filter struts2
java.lang.ClassNotFoundException: org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
	at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1892)
	at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1735)
	at org.apache.catalina.core.DefaultInstanceManager.loadClass(DefaultInstanceManager.java:504)
	at org.apache.catalina.core.DefaultInstanceManager.loadClassMaybePrivileged(DefaultInstanceManager.java:486)
	at org.apache.catalina.core.DefaultInstanceManager.newInstance(DefaultInstanceManager.java:113)
	at org.apache.catalina.core.ApplicationFilterConfig.getFilter(ApplicationFilterConfig.java:258)
	at org.apache.catalina.core.ApplicationFilterConfig.<init>(ApplicationFilterConfig.java:105)
	at org.apache.catalina.core.StandardContext.filterStart(StandardContext.java:4958)
	at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5652)
	at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:145)
	at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:899)
	at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:875)
	at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:652)
	at org.apache.catalina.startup.HostConfig.manageApp(HostConfig.java:1863)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:606)
	at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:301)
	at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:819)
	at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:801)
	at org.apache.catalina.mbeans.MBeanFactory.createStandardContext(MBeanFactory.java:618)
	at org.apache.catalina.mbeans.MBeanFactory.createStandardContext(MBeanFactory.java:565)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:606)
	at org.apache.tomcat.util.modeler.BaseModelMBean.invoke(BaseModelMBean.java:301)
	at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.invoke(DefaultMBeanServerInterceptor.java:819)
	at com.sun.jmx.mbeanserver.JmxMBeanServer.invoke(JmxMBeanServer.java:801)
	at javax.management.remote.rmi.RMIConnectionImpl.doOperation(RMIConnectionImpl.java:1487)
	at javax.management.remote.rmi.RMIConnectionImpl.access$300(RMIConnectionImpl.java:97)
	at javax.management.remote.rmi.RMIConnectionImpl$PrivilegedOperation.run(RMIConnectionImpl.java:1328)
	at javax.management.remote.rmi.RMIConnectionImpl.doPrivilegedOperation(RMIConnectionImpl.java:1420)
	at javax.management.remote.rmi.RMIConnectionImpl.invoke(RMIConnectionImpl.java:848)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:606)
	at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:322)
	at sun.rmi.transport.Transport$2.run(Transport.java:202)
	at sun.rmi.transport.Transport$2.run(Transport.java:199)
	at java.security.AccessController.doPrivileged(Native Method)
	at sun.rmi.transport.Transport.serviceCall(Transport.java:198)
	at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:567)
	at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:828)
	at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.access$400(TCPTransport.java:619)
	at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$1.run(TCPTransport.java:684)
	at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$1.run(TCPTransport.java:681)
	at java.security.AccessController.doPrivileged(Native Method)
	at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:681)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
	at java.lang.Thread.run(Thread.java:745)

分析Struts2-2.5.5的源代码发现

org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter

被更改了目录,变成了

org.apache.struts2.dispatcher.filter.StrutsPrepareAndExecuteFilter

只要如此修改即可。

Ubuntu 16.04下创建IntelliJ IDEA图标快捷方式

一般在安装目录下面或者桌面上创建文件,命名为:idea.desktop
使用vim编辑该文件

$ vim idea.desktop

内容如下:

[Desktop Entry]
Name=IntelliJ IDEA
Comment=IntelliJ IDEA
Exec=/home/longsky/Application/idea-IU-163.7743.44/bin/idea.sh
Icon=/home/longsky/Application/idea-IU-163.7743.44/bin/idea.png
Terminal=false
Type=Application
Categories=Developer;

接着给予这个文件执行权限

$ chmod +x idea.desktop

以后双击这个图标,就可以直接启动IntelliJ IDEA了。