黑月的BLooooog

  • 首页
  • 归档
  • 标签

Web Audo API advance

发表于 2017-03-01

Web Audio API Advance

一个简单的、典型的web audio工作流是这样的:

  1. 创建音频环境对象(AudioContext)。
  2. 在音频环境对象AudioContext中,创建音源。例如<audio>,振荡器(oscillator),源。
  3. 创建效果节点(effectNode),例如分析、增益、混响、双二阶滤波器、平移、压缩等。
  4. 选择最终的音源目的地,例如你的系统扬声器。
  5. 连接源到效果节点,以及效果节点到输出终端。

简单来说,Web Audio API提供了一个简单强大的机制来实现控制web应用程序的音频内容,但是Web Audio API并不会取代<audio>,而可以把它看做是<audio>的补充,就好像<img>和<canvas>的共存关系。你用来实现音频的方式取决于你的需求的复杂程度,如果只是简单的音轨播放,那么<audio>足够了。

这里首先来明确两个概念。

1. AudioContext – 音频环境对象

W3C描述如下:

This interface represents a set of AudioNode objects and their connections. It allows for arbitrary routing of signals to the AudioDestinationNode (what the user ultimately hears). Nodes are created from the context and are then connected together. In most use cases, only a single AudioContext is used per document.

大致意思是这个接口是音频节点和节点之间连接关系的集合。他允许信号经过任意的路由连接到目的节点(音频播放设备节点)上。节点都是通过音频环境(AudioContext)创建的,然后连接在一起。在大多数情况下,一个文档只需要一个音频环境。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 1.创建音频环境对象AudioContext;
var audioCtx = new AudioContext();
// 2.创建音源节点
var sourceNode = audioCtx.createBufferSource();
// 3.创建效果节点
// 创建分析节点
var analyser = audioCtx.createAnalyser();
// 创建低阶滤波节点
var biquadFilter = audioCtx.createBiquadFilter();
// 创建增益节点
var gainNode = audioCtx.createGain();

可以看出AudioContext相当于一个容器,各类音频节点,及节点间的连接方式都需要AudioContext的实例对象来创建。

2. AudioNode – 音频节点

W3C中描述如下:

AudioNodes are the building blocks of an AudioContext. This interface represents audio sources, the audio destination, and intermediate processing modules.

音频节点是一个音频环境的基础模块。这些节点可以是音频源节点,音频播放设备节点,也可以是中间处理模块。

3. Demo构建流程

先来看一个简单的音频模型,音频直接连接到播放设备节点播放。

再来看一套专业的音效合成处理图:

是不是很厉害,好,那么接下来我们讲点简单的……

3.1 构建AudioContext对象

首先,需要构建一个AudioContext实例,来创建一个音频环境容器。

1
2
3
4
5
// 1. 一个文档可以存在多个实例,但是没有必要,通常只需要一个。
// 2. 对于webkit/blink内核的浏览器需要加webkit前缀。
// 3. 在Safari浏览器中,如果不加window对象,会无效。
// 所以构建音频环境对象如下:
var audioCtx = new (window.AudioContext || window.webkitAudioContext)();

3.2 创建音源AudioSource

现在有了环境的实例对象,前面也提到这个对象非常有用,接下来就是用这个实例整一个音源。音源可以用以下几种方式创建/获取:

  • 从PCM(Pulse Code Modulation,脉冲编码调制)数据构建:这个PCM数据是啥玩意,怎么来?简单的说:首先可以通过XMLHttpRequest获取被支持的音频格式的文件(比如x.mp3),然后利用AudioContext对象中的方法对文件进行解码后获得的数据。下文中提到的自产demo ,就是利用这种方式获取音源的。AudioContext提供了解密被支持的音频格式的多种方法: AudioContext.createBuffer(), AudioContext.createBufferSource(), 以及 AudioContext.decodeAudioData().
  • 利用AudioContext对象直接生产音频节点,比如振荡器oscillator。具体API:AudioContext.createOscillator()
1
var oscillator = audioCtx.createOscillator();
  • 来自HTML音频元素,如我们熟悉的<video>或者<audio>.具体API: AudioContext.createMediaElementSource()
1
var source = audioCtx.createMediaElementSource(myMediaElement);
  • 直接来自于WebRTC,MediaStream。如摄像头、麦克风。具体API: AudioContext.createMediaStreamSource()。这种方式可以看下MDN提供的Demo:the Voice-change-O-matic live及Demo源码

3.3 连接输入输出

接下来需要创建改变音效的节点,并将这些节点连接起来,最终通过默认输出设备(通常是是设备扬声器)实质输出声音。AudioContext.destination就是最后需要连接的输出设备节点。

1
2
3
4
5
6
// 以振荡器为例
var oscillator = audioCtx.createOscillator();
var gainNode = audioCtx.createGain();
oscillator.connect(gainNode);
gainNode.connect(audioCtx.destination);

当然还有很多增益相关的节点,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var analyser = audioCtx.createAnalyser(); //该节点可以从音频里提取时间、频率或者其它数据。
var distortion = audioCtx.createWaveShaper(); //接口表示一个非线性的扭曲常被用来添加温暖的感觉。
var gainNode = audioCtx.createGain(); //接口表示音量变更
var biquadFilter = audioCtx.createBiquadFilter(); //表示一个简单的低阶滤波器
var convolver = audioCtx.createConvolver(); //对给定的 AudioBuffer 执行线性卷积,通常用于实现混响效果。
source = audioCtx.createMediaStreamSource(stream);
source.connect(analyser);
analyser.connect(distortion);
distortion.connect(biquadFilter);
biquadFilter.connect(convolver);
convolver.connect(gainNode);
gainNode.connect(audioCtx.destination);

如果你一股脑添加了如上的节点,就会得到下面的音频节点图。这些节点都可以做对应的设置,来达到复杂的音频调节效果,这里就不做展开了,因为我也展不开……

音频图

3.4 设置音调、声音大小并播放

终于到最后了,下面简单的做个设置就开始放吧:

1
2
3
4
5
oscillator.type = 'sine'; // sine 表示正弦波形 — 其他的波形值可以是 'square', 'sawtooth', 'triangle' and 'custom'
oscillator.frequency.value = 2500; // 单位是赫兹
gainNode.gain.value = 2; // 默认值是1
oscillator.start(); // 开始播放

具体接口请参考Web_Audio_API

3.5 自产demo

下面就上一个简单的demo,过程是利用XHR获取一个mp3文件,解码后播放,加了调节音量的按钮。

demo

PS:

  • AudioSampleLoader这个库可以帮你简化XHR/buffer的操作。
  • Web Audio API的具体资料可以参考:Web_Audio_API

Web Audio API history

发表于 2017-03-01
  1. <bgsound>:

    早在1996年,IE3.0定义了标签,这应该是web最早的一个能播放音频的标签,但是它没有成为标准,始终只有IE支持。提供的功能比较有限,就是自动播放,支持.wav|.mid|.ua格式音频。
    当时最早的个人blog中用来播放背景音乐的代码就是用<bgsound>实现的。

  2. <embed>

    OK,这个时候当时的浏览器厂商大哥NetScape坐不住了,没多久就退出了类似的功能标签<embed>。
    其相对于<bgsound>的特色:

    • 添加了一点交互,可以暂停/播放(可选)。
    • 不仅仅支持音频格式文件,还支持当时比较高端的VRML Live3D的图形动画。

      大哥发话,很快小弟safari,opare,firefox就纷纷跟进支持<embed>。

  3. <object>

    1994年W3C成立。1997年,随着HTML4的到来,W3C引入了 <object>标签,包括了图片、音频、视频等格式文件。可以说是第一款跨浏览器的音频播放标签。但是这个标签也有自己的弊端,例如标签臃肿,依赖插件,SEO困难等。

  4. <audio>

    2008年,第一份正式的HTML5草案发布,引入了新的富媒体元素<video>, <audio>,<canvas>,这些标签的引入最大目的还是为了减少web富媒体应用对插件的依赖。从标签名就能区分功能,这点也非常有利于搜索引擎去索引资源。相比<object>,其有一下特点:

    • 标签语义化,结构简单;
    • 脱离插件;
    • 简单的js内置方法以及事件交互。

      同样,也有缺陷:

    • 缺少对音频数据的访问权限,在需要更动感的交互和更复杂的音效需求面前就显得力不从心。

  5. [Audio Data API]

    为了满足更复杂的需求,Mozilla社区又开始搞事,提出了Audio Data API,对<audio>标签进行了js能力方面的扩展,这套API主要是以提供读取写入音频数据接口为主。不过到现在基本已经废弃了,因为很多音频的专业效果处理需要涉及大量波形相关处理算法,对于普通开发者来说,成本太高,这也是为什么最后W3C推荐WEB AUDIO API了。

  6. [Web Audio API]

    这套API最早是由Chrome社区提供并支持的,这是一套全新的相对独立的接口系统。对音频文件拥有更高的处理权限以及内置相关的音频专业效果处理(这一点很关键),可以完全独立于<audio>标签而存在。
    Web Audio API的特点:

    1. 更精确的时间控制;
    2. 可以完全独立<audio>,允许更多音频文件同事播放,用于游戏或者复杂音频应用场景;
    3. 模块化路由链接方式,让音频操作更加灵活形象;
    4. 实时的频域,时域数据访问/操作;
    5. 更多专业的音频处理方法

      1. 音道分离/合并;
      2. 音频延时效果;
      3. 内置频率滤波器;
      4. 音频空间感效果以及多普勒效应模拟;
      5. 音频卷积运算(用于声场环境模拟);
      6. 自定义波形生成器;
      7. 波形非线性失真处理。

ios和android使用同一个二维码实现跳转下载链接

发表于 2017-02-28

需求:扫描一个二维码,根据手机系统跳转到对应的下载页。ios扫描跳转到appstore,android扫描跳转到对应的浏览器或者市场下载。

注意点:

  • 如果在微信中扫描二维码,需要手动跳转到手机的浏览器才能跳到下载页面。因为微信浏览器是不支持直接打开App Store页面的,以前可以通过微信中“查看原文“的function来跳转,微信升了几个版本又不行了,所以写了一个alert引导用户打开浏览器,蠢是蠢了点,但是靠谱。
  • 如果要copy使用,改一下的url就好。

下面直接上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title></title>
</head>
<body>
<div id="txt"></div>
<script>
var iosUrl = 'https://itunes.apple.com/fr/app/wang-yi-dong-jianar-dong-cha/id1142482530?mt=8'; //AppStore下载地址
var anUrl = 'http://www.lofter.com/rsc/android/loftcam.apk'; //android下载地址
var defaultUrl = 'http://dongjian.163.com/'; // pc端主页
jump(iosUrl, anUrl, defaultUrl);
// 去下载
function jump(iosUrl, anUrl, defaultUrl) {
var ua = navigator.userAgent, appVer = navigator.appVersion;
var isAndroid = ua.indexOf('Android') > -1 || ua.indexOf('Linux') > -1;
var isIOS = !!ua.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/);
// 是安卓浏览器
if (isAndroid) {
window.location.href = anUrl; // 跳android端下载地址
}
// 是iOS浏览器
if (isIOS) {
window.location.href = iosUrl; // 跳AppStore下载地址
}
// 是微信内部webView
if (isWeixn()) {
// window.location.href = iosUrl; // 跳AppStore下载地址
var txtNode = document.createTextNode('请点击右上角按钮, 点击使用浏览器打开');
document.querySelector('#txt').appendChild(txtNode);
alert("请点击右上角按钮, 点击使用浏览器打开");
}
// 是PC端
if (isPC()) {
window.location.href = defaultUrl; // 公司主页
}
}
// 是微信浏览器
function isWeixn(){
var ua = navigator.userAgent.toLowerCase();
if(ua.match(/MicroMessenger/i)=="micromessenger") {
return true;
} else {
return false;
}
}
function isPC() {
var uaInfo = navigator.userAgent;
var agents = ["Android", "iPhone",
"SymbianOS", "Windows Phone",
"iPad", "iPod"];
var flag = true;
for (var v = 0; v < agents.length; v++) {
if (uaInfo.indexOf(agents[v]) > 0) {
flag = false;
break;
}
}
return flag;
}
</script>
</body>
</html>

函数式编程

发表于 2017-02-27   |   分类于 JS

函数式编程

函数式编程(通常简称为FP)是指通过符合纯函数来构建软件的过程。它避免了共享状态(share state),易变的数据(mutable data)以及副作用(side-effects)。函数式编程是一种编程范式,一种软件构建的思维方式。

以下这些名词定义中蕴含了许多思想,只有理解了它们,才能够开始掌握函数式编程真正的意义:

  • 纯函数(Pure functions)
  • 函数复合(Function composition)
  • 避免共享状态(Avoid shared state)
  • 避免改变状态(Avoid mutating state)
  • 避免副作用(Avoid side effects)

1.纯函数

  • 给它同样的输入,总是返回同样的结果;
  • 没有副作用;

纯函数有着许多对函数式编程而言非常重要的属性,包括引用透明(你可以将一个函数调用替换成它的结果值,而不会对程序的运行造成影响。详细:什么是纯函数

2.函数复合

  • 函数复合是结合两个或多个函数,从而产生一个新函数或进行某些计算的过程。eg:f(g(x))

详细:函数复合

3.共享状态

  • 共享状态 的意思是任意变量、对象或者内存空间存在于共享作用域下,或者作为对象的属性在各个作用域之间被传递。共享作用域包括全局作用域和闭包作用域。

同步竞争和调用时序变更导致的问题都是共享状态常见的bug。

  • 同步竞争:举个例子,操作A向服务器请求改变B的状态,然后马上用C操作向服务器发送请求,该请求也会改变B的状态,不幸的是C操作有可能早于A返回,这就导致了同步竞争的bug。
  • 调用时序变更:这个好理解,因为共享状态操作是有时序的,如果颠倒执行顺序就会导致共享状态出现错乱。

4.不可变性

一个不可变的(immutable)对象是指一个对象不会在它创建之后被改变。不可变性是函数式编程的一个核心概念,因为没有它,你的程序中的数据流是有损的。

JavaScript 提供了一个方法,能够浅冻结一个对象:

1
2
3
4
5
6
7
8
9
const a = Object.freeze({
foo: 'Hello',
bar: 'world',
baz: '!'
});
a.foo = 'Goodbye';
// Error: Cannot assign to read only property 'foo' of object Object

但是深层仍旧可以被改变。

在许多函数式编程语言中,有特殊的不可变数据结构,被称为 trie 数据结构(trie 的发音为 tree),这一结构有效地深冻结 —— 意味任何属性无论它的对象层级如何都不能被改变。

有一些 JavaScript 的库使用了 tries,包括 Immutable.js 和 Mori。

immutable更多使用实例:10 Tips for Better Redux Architecture。

ps: 在 JavaScript 中,很重要的一点是不要混淆了 const 和不变性。const 创建一个变量绑定,让该变量不能再次被赋值。const 并不创建不可变对象。你虽然不能改变绑定到这个变量名上的对象,但你仍然可以改变它的属性,这意味着 const 的变量仍然是可变的,而不是不可变的。

5.副作用

副作用是指除了函数返回值以外,任何在函数调用之外观察到的应用程序状态改变。副作用包括:

  • 改变了任何外部变量或对象属性(例如,全局变量,或者一个在父级函数作用域链上的变量)
  • 写日志
  • 在屏幕输出
  • 写文件
  • 发网络请求
  • 触发任何外部进程
  • 调用另一个有副作用的函数

6.使用高阶函数提升重用性

高阶函数指的是一个函数以函数为参数,或以函数为返回值,或者既以函数为参数又以函数为返回值。高阶函数经常用于:

  • 抽象或隔离行为、作用,异步控制流程作为回调函数,promises,monads,等等……
  • 创建可以泛用于各种数据类型的功能
  • 部分应用于函数参数(偏函数应用)或创建一个柯里化的函数,用于复用或函数复合。
  • 接受一个函数列表并返回一些由这个列表中的函数组成的复合函数。

函数式编程倾向于复用一组通用的函数功能来处理数据。面向对象编程倾向于把方法和数据集中到对象上。那些被集中的方法只能用来操作设计好的数据类型,通常是那些包含在特定对象实例上的数据。

在函数式编程里,对任何类型的数据一视同仁。同样的 map() 操作可以 map 对象、字符串、数字或任何别的类型,因为它接受一个函数参数,来适当地操作给定类型。函数式编程通过使用高阶函数来实现这一技巧。

关于高阶函数就不展开了。自行google。

7.命令式 vs 声明式

函数式编程是声明式的,而不是命令式的,应用程序的状态通过纯函数流转。

  • 声明式:意思是说程序逻辑不需要通过明确描述控制流程来表达。程序抽象了控制流过程,花费大量代码描述的是数据流:即做什么。
  • 命令式:程序花费大量代码来描述用来达成期望结果的特定步骤 —— 控制流:即如何做。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 命令式
const doubleMap = numbers => {
const doubled = [];
for (let i = 0; i < numbers.length; i++) {
doubled.push(numbers[i] * 2);
}
return doubled;
};
console.log(doubleMap([2, 3, 4])); // [4, 6, 8]
// 声明式
const doubleMap = numbers => numbers.map(n => n * 2);
console.log(doubleMap([2, 3, 4])); // [4, 6, 8]
// 命令式 代码中频繁使用语句。语句是指一小段代码,它用来完成某个行为。
// 声明式 代码更多依赖表达式。表达式是指一小段代码,它用来计算某个值。

8.结论

函数式编程偏好:

  • 使用纯函数而不是使用共享状态和副作用
  • 让可变数据成为不可变的
  • 用函数复合替代命令控制流
  • 使用高阶函数来操作许多数据类型,创建通用、可复用功能取代只是操作集中的数据的方法。
  • 使用声明式而不是命令式代码(关注做什么,而不是如何做)
  • 使用表达式替代语句

HTTP/2 vs HTTP/1.X

发表于 2017-02-26

HTTP/2 vs HTTP/1.X

1. 什么是HTTP/2

超文本传输协议第二版,是自HTTP协议1999年HTTP1.1发布后的首个更新,主要是基于SPDY/2协议(是google开发的基于TCP的应用层协议,它的设计目标是降低 50% 的页面加载时间。用以最小化网络延迟,提升网络速度,优化用户的网络体验)。

HTTP/2跟SPDY区别:

  • HTTP/2支持明文HTTP传输,而SPDY强制使用HTTPS
  • HTTP/2消息头的压缩算法采用HPACK,而非SPDY采用的DELEFT

2. 与HTTP/1.X相比,区别有:

  1. HTTP/2采用的是二进制格式传输数据,而非HTTP/1.X文本格式。二进制格式在协议的解析和优化扩展上带来更多的优势和可能。比起文本格式,二进制协议解析更高效,错误更少。

  2. HTTP/2是完全多路复用,而非HTTP/1.X有序并阻塞的。只需要一个连接即可实现并行,这个很关键,高效。直白的说就是所有请求都是通过一个TCP连接并发完成。HTTP/1.X虽然能利用一个连接完成多次请求,但是多个请求之间有先后顺序的,后面发送的请求必须等待前面的请求返回了才能发送响应。就会导致请求被阻塞,而HTTP/2做到了真正的并发请求。同时,流还支持优先级和流量控制。

    HTTP/1.X 有个问题叫线端阻塞(head-of-line blocking), 它是指一个连接(connection)一次只提交一个请求的效率比较高, 多了就会变慢。 HTTP/1.1 试过用流水线(pipelining)来解决这个问题, 但是效果并不理想(数据量较大或者速度较慢的响应, 会阻碍排在他后面的请求). 此外, 由于网络媒介(intermediary )和服务器不能很好的支持流水线, 导致部署起来困难重重。而多路传输(Multiplexing)能很好的解决这些问题, 因为它能同时处理多个消息的请求和响应; 甚至可以在传输过程中将一个消息跟另外一个掺杂在一起。所以客户端只需要一个连接就能加载一个页面。
    Multiplexing

  3. HTTP/2 对消息头采用 HPACK 进行压缩传输,能够节省消息头占用的网络的流量,增大有效数据传输量。

  4. Server Push:让服务器可以响应主动“推送”到客户端缓存中。例如服务端可以主动把 JS 和 CSS 文件推送给客户端,而不需要客户端解析 HTML 再发送这些请求。当客户端需要的时候,它已经在客户端了。

结束语

本文简化了很多 HTTP/2 协议中的具体细节,只描述了 HTTP/2 中主要特性实现的基本过程。

如果你想实现一个支持 HTTP/2 的服务器,那么你可以移步 HTTP/2 官网 做更多了解,它还提供了一份已经实现 HTTP/2 的项目列表: https://github.com/http2/http2-spec/wiki/Implementations 。

另外,关于 HTTP/2 性能如何,可以参考官方小组给出的例子: https://http2.akamai.com/demo

grammar更改笔记

发表于 2016-11-20

Atom中文件识别默认语法修改

  • 需求:公司使用自己开发的css预处理器mcss来进行web样式开发,这类文件的后缀为.mcss,在Atom中无法识别,因此每次打开这类文件,都需要ctrl+shift+L选择语言(grammar)版本,通常会选择scss或者less来代替一下。重复度比较大。
  • 思路:解决这个问题有2个办法:一个就是写个Atom插件,让Atom可以识别mcss语言;另一个就是将就策略,让Atom自动用scss或者less的语法来识别.mcss文件。考虑到办法一短时间无法实现(好吧,其实是因为菜),这里先用方法二处理下,后续有缘再进行插件的开发。

Atom语法介绍

加载了一个文件以后,Atom会做一些事情来试图识别出文件的类型。大部分情况下,Atom 会通过文件的扩展名(.md 通常是一个 Markdown 文件,等等)来完成这项工作,但有时只通过扩展名难以判断,它会对文件内容进行一些检查来确定。

如果加载了一个 Atom 无法判断语法的文件,它会默认为是最简单的纯文本类型(Plain Text)。如果它把文件默认为纯文本,或者弄错了文件类型,再或者由于一些原因你想修改文件的当前作用语法,可以按下 ctrl-shift-L 调出语法选择器。

一旦手动修改了一个文件的语法,Atom会记住它,除非你将语法设置回自动检测,或者手动选择一个不同的语法。

语法选择器的功能在 atom/grammar-selector 这个 package 里实现。

实现

在Atom的配置文件夹目录下有一个init.coffee的文件,用于做Atom初始化设置。

在其中添加代码

# add ".mcss" to SCSS grammar:
for grammar of atom.grammars.grammars
  if grammar.name is "Less"
    grammar.fileTypes.push('mcss')
atom.grammars.onDidAddGrammar (grammar) ->
  if grammar.name is "Less"
    grammar.fileTypes.push('mcss')

表示在Less的语法中,添加识别文件类型mcss。从而使mcss文件能够被当做Less识别

文件上传组件小结

发表于 2016-11-06   |   分类于 JS

文件/图片上传组件小结

分为两部分来总结,第一部分为自定义样式,第二部分为js实现。

一. 自定义样式

通常来说,input[type=file]的默认样式都不能满足视觉的需求,所以需要套壳包装。常用的方式就是隐藏默认上传控件,并在其父级添加一个元素(比如label标签)来自定义样式,这样可以做到保留功能同时自定义样式。具体css代码如下:

1
2
3
4
5
//HTML5
<img src='' ref=preview>
<label class="u-btn btn-upload">
<input type="file" ref=files on-change={this.fileChange()}>上传图片
</label>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
//CSS
.u-btn { //基础按钮样式
font: inherit;
position:relative;
line-height: 34px;
display: inline-block;
box-sizing: border-box;
height: 34px;
margin: 0;
padding: 0 12px;
cursor: pointer;
text-decoration: none;
color: #444;
border: 1px solid #ddd;
-moz-border-radius: 3px;
border-radius: 3px;
background: #f4f4f4;
-webkit-appearance: none;
}
.u-btn:focus, .u-btn:hover {
text-decoration: none;border: 1px solid #adadad;background: #e5e5e5;
}
.btn-upload {
overflow: hidden;
input {
opacity: 0;
filter:alpha(opacity=0);
font-size: 100px;
position: absolute;
top: 0;right: 0
}
}

二. js实现

需求:上传单张图片,并能预览。

框架:regular

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 这里使用regular框架,不详细说明
// 在`input[type=file]`上绑定了`onchange`监听事件,回调为`fileChange`方法
fileChange: function(ev) {
var input = this.$refs.files; // regular获取节点的方式
var preview = this.$refs.preview; // 获取预览dom
var file = input.files[0]; // 获取上传的文件
if (!/\/(?:jpeg|jpg|png)/i.test(file.type)) { //检查上传文件的类型,此处需要图片类型的文件,并且规定了后缀条件。
//提示错误信息
return;
}
var reader = new FileReader();
var self = this;
reader.onload = function(e) {
var result = self.img = this.result; // this指向reader,这个result即上传文件数据,将这个result用ajax传输即可。
// 组件操作,可忽略
self.data.show = true;
self.data.str = '重新选择'
self.$emit('upload');
preview.src = result; // 预览dom设置src
// console.log(preview.naturalWidth)
// console.log(preview.naturalHeight)
// 组件操作,可忽略
self.imgNatrualSize = {
width: preview.naturalWidth,
height: preview.naturalHeight
}
// 获取文件名
var imgName = input.value;
var index = imgName.lastIndexOf('\\');
self.imgName = index !== -1 ? imgName.slice(index+1) : imgName;
input.value = ''; // 清空图片上传框的值
};
reader.readAsDataURL(file); // 将文件转成base64的格式.
},

附录 FileReader API简介

###1. FileReader对象的方法

FileReader 的实例拥有 4 个方法,其中 3 个用以读取文件,另一个用来中断读取。下面的表格列出了这些方法以及他们的参数和功能,需要注意的是 ,无论读取成功或失败,方法并不会返回读取结果,这一结果存储在 result属性中。

方法名 参数 描述
abort none 中断读取
readAsBinaryString file 将文件读取为二进制码
readAsDataURL file 将文件读取为 DataURL
readAsText file 将文件读取为文本

readAsText:该方法有两个参数,其中第二个参数是文本的编码方式,默认值为 UTF-8。这个方法非常容易理解,将文件以文本方式读取,读取的结果即是这个文本文件中的内容。

readAsBinaryString:该方法将文件读取为二进制字符串,通常我们将它传送到后端,后端可以通过这段字符串存储文件。

readAsDataURL:这是例子程序中用到的方法,该方法将文件读取为一段以 data: 开头的字符串,这段字符串的实质就是 Data URL,Data URL是一种将小文件直接嵌入文档的方案。这里的小文件通常是指图像与 html 等格式的文件。

2. FileReader对象的事件

FileReader 包含了一套完整的事件模型,用于捕获读取文件时的状态,下面这个表格归纳了这些事件。

事件 描述
onabort 中断时触发
onerror 出错时触发
onload 文件读取成功完成时触发
onloadend 读取完成触发,无论成功或失败
onloadstart 读取开始时触发
onprogress 读取中

文件一旦开始读取,无论成功或失败,实例的 result 属性都会被填充。如果读取失败,则 result 的值为 null ,否则即是读取的结果,绝大多数的程序都会在成功读取文件的时候,抓取这个值。

var reader = new FileReader();
reader.onload = function() {  
    this.result;  
};

ps 可以利用这些事件制作文件读取的进度条,此处不详细展开,有空在做补充。

git子模块

发表于 2016-09-17   |   分类于 git

git子模块

问题

在开发项目A时,需要依赖项目B(实时更新)。同时希望A,B能够独立处理。

实际案例

举例来说,现在我参与的项目网易有数(Youdata),它依赖了一个独立开发的图形库NEV,他们是并行开发的。如果只是简单的将NEV的代码copy到Youdata中,显然是有点傻逼,因为无法自动更新NEV的迭代。

处理方式

Git 通过子模块处理这个问题。子模块允许你将一个 Git仓库当作另外一个Git仓库的子目录。这允许你克隆另外一个仓库到你的项目中并且保持你的提交相对独立。

子模块相关操作:

1.添加子模块 $ git submodule add [url] [path]

如

$ git sub module add https://git.hz.netease.com/git/NEV/NEV.git src/webapp/res/nev

操作成功之后会生成一个.submodules的文件,其中记录了每个submodule的引用信息,知道在当前项目的位置以及仓库的所在。

2.查看子模块 $ git submodule

如图

ps: 如果子模块前面有一个-,说明子模块文件还未检入(空文件夹)

3.初始化子模块: $ git submodule init — 在首次检出仓库时运行一次

4. 更新子模块:$ git submodule update

这个命令才是最常用的,每次子模块更新或者切换分支了,就执行一次。

5. 删除子模块:

  • $ git rm -cached [path]
  • 编辑.gitmodules文件,删除对应的子模块配置
  • 编辑.git/config文件,删除对应的子模块配置
  • 最后删除子模块的目录

图解密码技术总结

发表于 2016-09-14   |   分类于 书

《图解密码技术》总结

一. 历史上的密码

1.1. 凯撒密码:
  • 将明文按照字母表进行一定的“平移”来进行加密的加密算法。

1.2. 简单替换密码:
  • 两套字母表进行乱序一一对应,那么无论哪种对应关系都可以作为密码来使用。这种将明文中用到的字母表替换成另一套字母表的密码就是简单替换密码(simple substitution cipher)。如下图。

1.3. Enigma:
  • 相信很多人看过由卷福饰演图灵的电影《模仿游戏》,那么对于二战时期大名鼎鼎的德国密码机Enigma一定不陌生了。
  • Enigma在德语中的意思就是“谜”。
  • 发明之初是作为商用的,到了纳粹时期,经过改良后用于军事用途。
  • Enigma是一种由键盘、齿轮、电池和灯泡组成的机器。通过一台机器可以完成加密解密两种操作。
  • 其实Enigma相当于一个密码算法, 它并不依赖于隐蔽式安全性(security by obscurity)—隐蔽式安全性:顾名思义就是依靠算法的隐蔽性来获得安全保障,一旦算法被曝光就会被破解。而Enigma密码机就算被密码破译者得到,只要不知道Enigma的设置(相当于密钥),就无法破译密码。
1.3.1 下图为Enigma进行加密通信的过程图。

1.3.2 Enigma加密

下图为Enigma进行加密nacht的过程图。

在进行通信之前,发送者和接受者都需要持有国防军密码本,这里面记载了发送者和接受者需要使用的每日密码。如果有一本国防军密码本被缴获,那就需要全部替换新的密码本。这里就涉及到密钥的配送问题。

  1. 设置Enigma

    发送者查阅国防军密码本,找到当天的每日密码,并按照该密码设置Enigma。

  2. 加密通信密码

    接下来,发送者需要想出3个字母(这里假设为psv),并将其加密。这3个字母称为通信密码。

    由于Enigma的时代,无线电的质量很差,可能发生通信错误。所以通信密码需要连续输入两次(psvpsv),以便接受者可以进行校验。在Enigma中输入psvpsv这6个字母,则会得到对应的密文并记录,假设密文为ATCDVT(密文用大写字母表示)。

    这里可以看出,每日密码其实是用来加密通信密钥的密钥。这样的密钥,一般称为密钥加密密钥(Key Encryptiong Key,KEK)。

  3. 重新设置Enigma

    此时,根据通信密码(psv)重新设置Enigma。

  4. 加密信息

    接下来,发送者就可以对消息进行加密了。假设消息(明文)为nacht,进过加密得到KXNWP。

  5. 拼接

    最后,将2中得到的通信密码“ATCDVT”与加密后的消息“KXNWP”进行拼接,将“ATCDVTKXNWP”作为电文通过无线电发送出去。

1.3.3 Enigma解密

理解了加密步骤,解密就简单了。

  • 首先将密文分成两部分,即开头6个字母ATCDVT和剩下的字母KXNWP。
  • 根据和发送者相同的每日密码设置Enigma。将ATCDVT破译得到psvpsv。
  • 根据psv设置Enigma。
  • 然后破译剩下的字母KXNWP,得到明文nacht。
1.3.4 Enigma的弱点
  • 通信密码连续输入两次。
  • 通信密码人为选定。理论上通信密码应该具有不可预测性。
  • 必须派发国防军密码本。前面也说了,如果泄露一本,就需要全部替换新的。

===

二. 对称密码

  • 所谓对称密码,就是说加密和解密使用的是相同的密钥。
  • 算法的核心是利用位运算中“异或”的方式来实现的。简单的讲:“01”组成的比特序列经过与密钥的一次“异或”
    即可得到密文,再和相同的密钥进行一次“异或”就能还原明文。
  • 对称密码的算法:DES、三重DES、AES(AES的标准所选定的密码算法叫作Rijndael)。

2.1 一次性密码本 — 绝对不会被破译的密码

  • 原理是: 将明文和一串随机的比特序列进行XOR运算。
  • 那么为什么它是无法破译的呢:无法破译并不是说不能解出明文,而是说无法判断它是否是正确的明文,因为在解密的过程(暴力破解)中所有的排列组合都会出现,因此就无法判断哪一个才是正确的明文。

===

三. 公钥密码—用公钥加密,用私钥解密

  • 书里关于投币寄存柜的比喻很好:钱是关闭寄存柜的密钥,钥匙是打开寄存柜的密钥。类比公钥密码,钱就是公钥,谁都可以用来加密寄存柜,但是要打开寄存柜只能用私钥“钥匙”。

3.1 密钥配送问题

如果密钥如同密文一样通过网络直接发送,那么也很容易被窃听。那么如何解决这个问题呢。

  • 通过事先共享的密钥来解决。
  • 通过密钥分配中心来解决。
  • 通过Diffie-Hellman密钥交换来解决。
  • 通过公钥密码来解决。

3.2 那么如何通过公钥密钥来解决密钥配送问题呢

3.2.1 首先来介绍下公钥的通信流程
  1. Bob生成一个包含公钥和私钥的密钥对。私钥有Bob自己保管。
  2. Bob将公钥发送给Alice。Bob的公钥被窃听者Eve获取也没关系。将公钥发送给Alice表示让他用这个公钥加密消息并发送给Bob。
  3. Alice用Bob的公钥加密消息,加密后的消息只能用Bob的私钥才能解密。Alice的公钥是无法解密的。
  4. Alice将密文发送给Bob。这样密文就算被窃听也没有关系。
  5. Bob用私钥进行解密。

3.2.2 解决密钥配送问题

因此,我们可以用公钥来加密对称密码的密钥,从而解决密钥配送的问题。

3.2.3 公钥密码无法解决的问题
  1. 公钥密码的速度只有对称密码的几百分之一。速度问题如何解决。
  2. 如何判断公钥的正确合法性,这个问题是公钥认证的问题。举例:中间人攻击,如图

3.2.4 公钥密码的算法
  • 算法的核心是利用mod运算(取余运算)
  • 简单介绍下现在使用最广泛的公钥密码算法 — RSA
    • 密文 = 明文 E MOD N (RSA加密:明文的E次方除以N的余数)
    • 明文 = 密文 D MOD N (RSA解密:密文的D次方除以N的余数)
    • E和N的组合就是公钥
    • D和N的组合就是私钥

===

四. 混合密码系统 — 用对称密码提高速度,用公钥密码保护会话密钥

在3.2.3中提到公钥密码还有两个很大的问题。一是速度问题,二是认证问题。这一节介绍的混合密码系统可以解决第一个速度问题。第二个问题在后面的小节中介绍。

4.1 混合密码系统组成

  • 用对称密码加密消息
  • 通过伪随机数生成器生成对称密码加密中使用的会话秘钥
  • 用公钥密码加密会话密钥(这就是解决速度问题的方法,因为对称密码的密钥一般比消息本身要短)
  • 从混合密码系统外部赋予公钥密码加密时使用的密钥

4.2 混合密码系统的加密过程

4.3 混合密码系统的解密过程

===
认证部分

===

五. 单向散列函数 — 获取消息的“指纹”

5.1 什么是单向散列值(one-way hash function)

  • 单向散列函数可以根据消息的内容计算出固定长度的散列值,用于验证消息的完整性。

5.2 单向散列函数的性质

  • 根据任意长度的消息计算出固定长度的散列值。
  • 能够快速计算出散列值。
  • 消息不同散列值不同。
    • 弱抗碰撞性:要找到和该条消息具有相同散列值的另一条消息是非常困难的。
    • 强抗碰撞性:要找到散列值相同的两条不同的消息是非常困难的。
  • 具备单向性

5.3 单向散列函数的实际应用

  1. 检测软件是否被篡改
  2. 基于口令的加密(Password Based Encryption,PBE):将口令和盐(salt,通过伪随机数生成器产生的随机值)混合后计算其散列值,然后将这个散列值用作加密的密钥。这样做可以防御针对口令的字典攻击。
  3. 消息认证码:消息认证码是将“发送者和接受者之间的共享密钥”和“消息”进行混合计算出的散列值。使用消息认证码可以检测并防止通信过程中的错误、篡改以及伪装。
  4. 数字签名:数字签名一般是通过单向散列函数计算出消息的散列值,然后在这个散列值上施加数字签名。
  5. 伪随机数生成器
  6. 一次性口令:常被用于服务器对客户端的合法性认证。

5.4 单向散列函数的具体例子

  1. MD4、MD5
  2. SHA-1、SHA-256、SHA-384、SHA-512
  3. RIPEMD-160

===

六. 消息认证码 — 消息被正确传送了吗

6.1 什么是消息认证码

  • 消息认证码(message authentication code)是一种确认完整性并进行认证的技术,取三个单词的首字母,简称MAC。
  • 消息认证码是输入包括任意长度的消息和一个发送者和接收者之间共享的密钥,它可以输出固定长度的数据,这个数据成为MAC值。
  • 消息认证码是一种与密钥相关的单向散列函数。如下图

6.2 消息认证码的使用步骤图

6.3 消息认证码的密钥的配送问题

看上图就知道,共享密钥的配送问题依旧存在。解决方法也和对称密码差不多,例如公钥密码,Diffie-Hellman密钥交换等,具体项目具体安排。

6.4 消息认证码的应用实例

  1. SWIFT
  2. IPsec
  3. SSL/TLS

6.5 对消息认证码的攻击

  1. 重放攻击
  2. 密钥推测攻击

6.6 消息认证码无法解决的问题

  1. 对第三方证明。第三方无法知道消息来自A,B哪一方。(数字签名可以证明)
  2. 防止否认。(数字签名可以防止否认)

===

七. 数字签名 — 消息到底是谁写的

  • 数字签名可以识别篡改和伪装,还可以防止否认。
  • 数字签名可以看成是公钥密码的反用,如图

公钥密码与数字签名的密钥使用方式

公钥密码

数字签名

7.1 数字签名的方法

  • 直接对消息签名的方法
  • 对消息的散列值签名的方法(常用)

7.2 实际应用

  1. 安全信息公告:一些信息安全方面的组织会在其网站上发布一些安全漏洞的警告,那么如何验证这些警告信息真的是这个组织发布的呢?此时就可以用数字签名。
  2. 软件下载
  3. 公钥证书:用于验证公钥的合法性。
  4. SSL/TLS

7.3 通过RSA实现数字签名

  • 签名 = 消息 D MOD N (消息的D次方除以N取余数)
  • 由签名求得的消息 = 签名 E MOD N (签名的E次方除以N取余数)
  • D和N就是签名者的私钥
  • E和N就是签名者的公钥

7.4 对数字签名的攻击

  1. 中间人攻击。(可以通过公钥证书来防止)
  2. 对单向散列函数的攻击
  3. 利用数字签名攻击公钥密码

7.5 各类密码技术的对比

===

八 证书 — 为公钥加上数字签名

8.1 什么是证书

公钥证书(Public-Key Certificae,PKC):里面记有姓名、组织、邮箱地址等个人信息,以及属于此人的公钥,并由认证机构(Certification Authority、Certifying Authority, CA)施加数字签名。只要看到公钥证书,我们就可以知道认证机构认定该公钥的确属于此人。公钥证书简称为证书(certificate)

8.2 证书的应用场景

8.3 公钥基础设施(PKI)

就是为了能够更有效的运用公钥而制定的一系列规范和规格的总称。

组成要素:

  • 用户 — 使用PKI的人
  • 认证机构 — 颁发证书的人
  • 仓库 — 保存证书的数据库

===

九 随机数 — 不可预测性的源泉

9.1 随机数的性质

  • 随机性 — 不存在统计学偏差,是完全杂乱的数列。(弱伪随机数)
  • 不可预测性 — 不能从过去的数列推测出下一个出现的数。(强伪随机数)
  • 不可重现性 — 除非将数列本身保存下来,否则不能重现相同的数列。(真随机数)

    上面三种性质中,越往下越严格。

  • 对于软件所生成的数列,周期必定是有限的。凡是有限周期的数列,都不具备不可重现性。
  • 要生成不可重现的随机书里额,需要从不可重现的物理现象中获取信息。

===

十 SSL/TLS — 为了更安全的通信

10.1 什么是SSL/TLS

  • 是目前世界上最广泛的密码通信方法。
  • 综合运用了前面提到的技术:对称密码、消息认证码、公钥密码、数字签名、伪随机数生成器等密码技术。

===

十一 密码技术与现实社会

11.1 密码学家的工具箱

11.2 密码技术与压缩技术

  • 密钥是机密性的精华
  • 散列值是完整性的精华
  • 认证符号(MAC值和签名)是认证的精华
  • 种子是不可预测性的精华

11.3 只有完美的密码,没有完美的人

不同unicode编码的空格挖的坑

发表于 2016-08-04   |   分类于 JS

前段时间在做搜索的时候,遇到一个关于空格的小问题,总结一下。

有时在文本值中会插入一些空格字符 (Unicode 字符集值 32 和 160) ,比如说标题之类的。当你对包含空格的值进行排序、 筛选或搜索时,这些字符有时会导致意外的结果。本次就是因为把数据存放在dom节点上,取出来做搜索的时候,发现编码发现了改变(从32变成了160),导致无法正确匹配。

The non-breaking space (U+00A0 Unicode, 160 decimal,  ) is not the same as the space character (U+0020 Unicode, 32 decimal). Well, both of them seems to be a “space”, but they are absolutely different characters.

这里的解决方案是:采用正则替换成统一字符,如下

var s = ' ' // 假设这里是一个160的空格。
var reg = new RegExp(String.fromCharCode(160),"gm");
var 32sp = String.fromCharCode(32)
s = s.replace(reg, 32sp);

后来重构代码,直接废除了将数据存在dom上这种方案,就更好了。

除了空格字符,非打印字符在进行排序、 筛选或搜索操作时,也可能会遇到这类问题,参考。要注意~

12
黑月

黑月

13 日志
5 分类
19 标签
RSS
© 2017 黑月
由 Hexo 强力驱动
主题 - NexT.Muse